Skip to content

Commit

Permalink
vespa db 1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
stevegerrits committed Nov 24, 2024
1 parent efe8cdb commit 06abf65
Show file tree
Hide file tree
Showing 17 changed files with 358 additions and 205 deletions.
1 change: 0 additions & 1 deletion src/components/MapPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ export default {
try {
await vespaStore.fetchObservationDetails(properties.id);
if (vespaStore.selectedObservation && !vespaStore.selectedObservation.visible) {
console.error("Observation is not visible");
return;
}
vespaStore.isDetailsPaneOpen = true;
Expand Down
120 changes: 70 additions & 50 deletions src/components/ObservationDetailsComponent.vue

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions src/components/TableViewPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -182,15 +182,12 @@ export default {
// if (vespaStore.lastAppliedFilters === null || vespaStore.lastAppliedFilters === 'null') {
// console.log("set hier?")
// vespaStore.setLastAppliedFilters();
// }
// Avoid calling getObservations if data is already loaded with the same filters
if (vespaStore.table_observations.length === 0 || JSON.stringify(vespaStore.filters) !== JSON.stringify(vespaStore.lastAppliedFilters)) {
//vespaStore.setLastAppliedFilters();
vespaStore.getObservations(page.value, pageSize.value, sortBy.value, sortOrder.value);
vespaStore.getObservationsGeoJson();
Expand Down
8 changes: 0 additions & 8 deletions src/stores/vespaStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,6 @@ export const useVespaStore = defineStore('vespaStore', {
}
} catch (error) {
console.error('Error fetching observation details:', error);
this.error = 'Het ophalen van observatiedetails is mislukt.';
}
},
formatToISO8601(datetime) {
Expand All @@ -293,17 +292,10 @@ export const useVespaStore = defineStore('vespaStore', {
return date.toISOString();
},
async updateObservation(observation) {
if (observation.observation_datetime) {
observation.observation_datetime = this.formatToISO8601(observation.observation_datetime);
}
if (observation.eradication_date) {
observation.eradication_date = this.formatDateWithEndOfDayTime(observation.eradication_date);
}
try {
const response = await ApiService.patch(`/observations/${observation.id}/`, observation);
if (response.status === 200) {
this.selectedObservation = response.data;

const colorByResult = this.getColorByStatus(response.data.eradication_result);
this.updateMarkerColor(observation.id, colorByResult, '#ea792a', 4, 'active-marker');
return response.data;
Expand Down
2 changes: 1 addition & 1 deletion vespadb/observations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class ObservationAdmin(gis_admin.GISModelAdmin):
actions = ["send_email_to_observers", "mark_as_eradicated", "mark_as_not_visible"]

readonly_fields = (
"wn_notes",
"notes",
"source",
"wn_id",
"wn_validation_status",
Expand Down
18 changes: 18 additions & 0 deletions vespadb/observations/migrations/0031_observation_source_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2024-11-11 18:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('observations', '0030_alter_observation_observer_phone_number'),
]

operations = [
migrations.AddField(
model_name='observation',
name='source_id',
field=models.IntegerField(blank=True, help_text='Original identifier when importing data', null=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2024-11-11 18:13

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('observations', '0031_observation_source_id'),
]

operations = [
migrations.RenameField(
model_name='observation',
old_name='wn_notes',
new_name='notes',
),
]
3 changes: 2 additions & 1 deletion vespadb/observations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,9 @@ class Observation(models.Model):
modified_datetime = models.DateTimeField(auto_now=True, help_text="Datetime when the observation was last modified")
location = gis_models.PointField(help_text="Geographical location of the observation")
source = models.CharField(max_length=255, blank=True, null=True, help_text="Source of the observation")
source_id = models.IntegerField(blank=True, null=True, help_text="Original identifier when importing data")

wn_notes = models.TextField(blank=True, null=True, help_text="Notes about the observation")
notes = models.TextField(blank=True, null=True, help_text="Notes about the observation")
wn_admin_notes = models.TextField(blank=True, null=True, help_text="Admin notes about the observation")
wn_validation_status = models.CharField(
max_length=50,
Expand Down
38 changes: 19 additions & 19 deletions vespadb/observations/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"observation_datetime",
"modified_by",
"created_by",
"province",
"eradication_date",
"municipality",
"province",
Expand All @@ -45,7 +44,7 @@
"municipality_name",
"modified_by_first_name",
"created_by_first_name",
"wn_notes",
"notes",
"eradication_result",
"wn_id",
"wn_validation_status",
Expand Down Expand Up @@ -139,7 +138,7 @@ class Meta:
"modified_datetime": {"help_text": "Datetime when the observation was last modified."},
"location": {"help_text": "Geographical location of the observation as a point."},
"source": {"help_text": "Source of the observation."},
"wn_notes": {"help_text": "Notes about the observation."},
"notes": {"help_text": "Notes about the observation."},
"wn_admin_notes": {"write_only": True},
"wn_validation_status": {"help_text": "Validation status of the observation."},
"nest_height": {"help_text": "Height of the nest."},
Expand Down Expand Up @@ -262,23 +261,24 @@ def to_representation(self, instance: Observation) -> dict[str, Any]: # noqa: C

if request and request.user.is_authenticated:
user: VespaUser = request.user
permission_level = user.get_permission_level()
user_municipality_ids = user.municipalities.values_list("id", flat=True)
is_inside_user_municipality = (
instance.municipality and instance.municipality.id in user_municipality_ids
)

# Voor gebruikers zonder toegang tot specifieke gemeenten
if permission_level == "logged_in_without_municipality":
return {field: data[field] for field in public_read_fields if field in data}

if not request.user.is_superuser:
# Non-admins should not see admin-specific fields
admin_fields = set(admin_or_special_permission_fields)
for field in admin_fields:
data.pop(field, None)

is_inside_user = instance.municipality and instance.municipality.id in user_municipality_ids
if not is_inside_user:
# Do not show reserved_by for users outside the municipality and not admins
data.pop("reserved_by", None)
return {
field: data[field]
for field in set(user_read_fields + conditional_fields + admin_or_special_permission_fields)
if field in data
}
# Voor gebruikers met toegang tot specifieke gemeenten, extra gegevens tonen indien binnen hun gemeenten
if is_inside_user_municipality or request.user.is_superuser:
return {field: data[field] for field in user_read_fields if field in data}

# Voor observaties buiten de gemeenten van de gebruiker, beperk tot publieke velden
return {field: data[field] for field in public_read_fields if field in data}

# Voor niet-ingelogde gebruikers, retourneer enkel de publieke velden
return {field: data[field] for field in public_read_fields if field in data}

def validate_reserved_by(self, value: VespaUser) -> VespaUser:
Expand Down Expand Up @@ -360,7 +360,7 @@ def update(self, instance: Observation, validated_data: dict[Any, Any]) -> Obser
if eradication_result == EradicationResultEnum.SUCCESSFUL:
validated_data["reserved_datetime"] = None
validated_data["reserved_by"] = None
validated_data["eradication_date"] = datetime.now(timezone("EST")).date()
#validated_data["eradication_date"] = datetime.now(timezone("EST")).date()

if not user.is_superuser:
# Non-admins cannot update admin-specific fields, so remove them
Expand Down
105 changes: 67 additions & 38 deletions vespadb/observations/tasks/observation_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,42 +26,75 @@

logger = logging.getLogger("vespadb.observations.tasks")

mapping_dict: dict[int, dict[str, str]] = {
329: {
"Hoger dan 4 meter": "hoger_dan_4_meter",
"Higher than 4 meters": "hoger_dan_4_meter",
"Lager dan 4 meter": "lager_dan_4_meter",
"Lower than 4 meters": "lager_dan_4_meter",
},
330: {
"Groter dan 25 cm": "groter_dan_25_cm",
"Kleiner dan 25 cm": "kleiner_dan_25_cm",
"Larger than 25cm": "groter_dan_25_cm",
"Smaller than 25cm": "kleiner_dan_25_cm",
},
331 : {
"Binnen, in gebouw of constructie": "binnen_in_gebouw_of_constructie",
"Buiten, maar overdekt door constructie": "buiten_maar_overdekt_door_constructie",
"Buiten, natuurlijk overdekt": "buiten_natuurlijk_overdekt",
"Buiten, onbedekt in boom of struik": "buiten_onbedekt_in_boom_of_struik",
"Buiten, onbedekt op gebouw": "buiten_onbedekt_op_gebouw",
"Inside, in a building or construction": "binnen_in_gebouw_of_constructie",
"Outside, but covered by construction": "buiten_maar_overdekt_door_constructie",
"Outside, natural cover": "buiten_natuurlijk_overdekt",
"Outside, uncovered in a tree or bush": "buiten_onbedekt_in_boom_of_struik",
"Outside, uncovered on building": "buiten_onbedekt_op_gebouw",
}
}

ENUMS_MAPPING: dict[str, type[TextChoices]] = {
"Nesthoogte": NestHeightEnum,
"Nestgrootte": NestSizeEnum,
"Nestplaats": NestLocationEnum,
"Nesttype": NestTypeEnum,
"Resultaat": EradicationResultEnum,
"Problemen": EradicationProblemsEnum,
"Methode": EradicationMethodEnum,
"Nest height": NestHeightEnum,
"Nest size": NestSizeEnum,
"Nest location": NestLocationEnum,
"Nest type": NestTypeEnum,
"Result": EradicationResultEnum,
"Problems": EradicationProblemsEnum,
"Method": EradicationMethodEnum,
"Product": EradicationProductEnum,
}
ENUM_FIELD_MAPPING: dict[str, str] = {
"Nesthoogte": "nest_height",
"Nestgrootte": "nest_size",
"Nestplaats": "nest_location",
"Nesttype": "nest_type",
"Resultaat": "eradication_result",
"Problemen": "eradication_problems",
"Methode": "eradication_method",
"Product": "eradication_product",
ENUM_FIELD_MAPPING: dict[int, str] = {
329: "nest_height",
330: "nest_size",
331: "nest_location",
}
# Literal mapping functions
def map_nest_height_attribute_to_enum(value: str) -> Any | None:
"""Maps Nest height values to enums based on literal mapping."""
return mapping_dict[329].get(value.strip())

def map_nest_size_attribute_to_enum(value: str) -> Any | None:
"""Maps Nest size values to enums based on literal mapping."""
return mapping_dict[330].get(value.strip())

def map_attribute_to_enum(value: str, enum: type[TextChoices]) -> TextChoices | None:
"""
Map a single attribute value to an enum using close match.
def map_nest_location_attribute_to_enum(value: str) -> str | None:
"""Maps Nest location values to enums based on literal mapping."""
return mapping_dict[331].get(value.strip())

:param value: The value from the API that needs to be mapped to an enum.
:param enum: The enum type that the value is expected to map to.
:return: The corresponding enum value if a match is found, otherwise None.
def map_attribute_to_enum(attribute_id: int, value: str) -> str | None:
"""
enum_dict = {e.value: e for e in enum}
closest_match = get_close_matches(value, enum_dict.keys(), n=1, cutoff=0.6)
return enum_dict.get(closest_match[0]) if closest_match else None

Maps a single attribute value to an enum using literal mapping functions.
"""
if attribute_id == 329:
return map_nest_height_attribute_to_enum(value)
elif attribute_id == 330:
return map_nest_size_attribute_to_enum(value)
elif attribute_id == 331:
return map_nest_location_attribute_to_enum(value)
else:
return None

def map_attributes_to_enums(api_attributes: list[dict[str, str]]) -> dict[str, TextChoices]:
def map_attributes_to_enums(api_attributes: list[dict[str, Any]]) -> dict[str, str]:
"""
Map API attributes to model enums based on configured mappings.
Expand All @@ -70,17 +103,17 @@ def map_attributes_to_enums(api_attributes: list[dict[str, str]]) -> dict[str, T
"""
mapped_values = {}
for attribute in api_attributes:
attribute_id = int(attribute.get("attribute", 0))
attr_name = attribute.get("name")
value = str(attribute.get("value"))
if attr_name in ENUMS_MAPPING:
mapped_enum = map_attribute_to_enum(value, ENUMS_MAPPING[attr_name])
if attribute_id in mapping_dict:
mapped_enum = map_attribute_to_enum(attribute_id, value)
if mapped_enum:
mapped_values[ENUM_FIELD_MAPPING[attr_name]] = mapped_enum
mapped_values[ENUM_FIELD_MAPPING[attribute_id]] = mapped_enum
else:
logger.warning(f"No enum match found for {attr_name}: {value}")
logger.debug(f"No enum match found for {attr_name}: {value}")
return mapped_values


def map_validation_status_to_enum(validation_status: str) -> ValidationStatusEnum | None:
"""
Map a single validation status to an enum.
Expand Down Expand Up @@ -165,6 +198,7 @@ def map_external_data_to_observation_model(external_data: dict[str, Any]) -> dic
cluster_id = None
if nest:
cluster_id = nest.get("id")

mapped_data = {
"wn_id": external_data["id"],
"location": location,
Expand All @@ -182,7 +216,6 @@ def map_external_data_to_observation_model(external_data: dict[str, Any]) -> dic
**mapped_enums,
}

# Additional user data
user_data = external_data.get("user", {})
if user_data:
mapped_data.update({
Expand All @@ -191,10 +224,8 @@ def map_external_data_to_observation_model(external_data: dict[str, Any]) -> dic
"observer_name": user_data.get("name"),
})

# Eradication specifics
eradication_flagged = False

# Check for eradication keywords in notes
if (
"notes" in external_data
and external_data["notes"]
Expand All @@ -203,9 +234,8 @@ def map_external_data_to_observation_model(external_data: dict[str, Any]) -> dic
):
eradication_flagged = True

# Check for "BESTREDEN" in 'Remark (Asian hornet)' attribute
for attribute in external_data.get("attributes", []):
if attribute.get("name") == "Remark (Asian hornet)" and "BESTREDEN" in attribute.get("value", "").upper():
if attribute.get("attribute") == 369 and "BESTREDEN" in attribute.get("value", "").upper():
eradication_flagged = True
break

Expand All @@ -215,7 +245,6 @@ def map_external_data_to_observation_model(external_data: dict[str, Any]) -> dic

return mapped_data


def check_existing_eradication_date(wn_id: str) -> bool:
"""
Check if the eradication_date is already set for the given wn_id.
Expand Down
2 changes: 1 addition & 1 deletion vespadb/observations/tasks/observation_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,4 +325,4 @@ def fetch_and_update_observations(self: Task, since_week: int | None = None, dat

logger.info("Finished processing observations")
manage_observations_visibility(token)
logger.info("Finished managing observations visibility")
logger.info("Finished managing observations visibility")
Loading

0 comments on commit 06abf65

Please sign in to comment.