UPDATE
This commit is contained in:
+114
-13
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
from numbers import Number
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
@@ -49,13 +50,13 @@ def get_farm_details(farm_uuid: str):
|
||||
depths.sort(key=lambda item: DEPTH_PRIORITY.index(item.depth_label) if item.depth_label in DEPTH_PRIORITY else 99)
|
||||
|
||||
soil_metrics = _surface_soil_metrics(depths)
|
||||
sensor_metrics = _flatten_sensor_metrics(farm.sensor_payload)
|
||||
sensor_metrics, sensor_metric_sources = _resolve_sensor_metrics(farm.sensor_payload)
|
||||
|
||||
resolved_metrics = dict(soil_metrics)
|
||||
metric_sources = {key: "soil" for key in soil_metrics}
|
||||
for key, value in sensor_metrics.items():
|
||||
resolved_metrics[key] = value
|
||||
metric_sources[key] = "sensor"
|
||||
metric_sources[key] = sensor_metric_sources[key]
|
||||
|
||||
return {
|
||||
"center_location": {
|
||||
@@ -97,11 +98,7 @@ def resolve_center_location_from_boundary(farm_boundary: dict | list) -> SoilLoc
|
||||
if len(normalized_points) < 3:
|
||||
raise ValueError("farm_boundary باید حداقل 3 گوشه معتبر داشته باشد.")
|
||||
|
||||
lat_sum = sum(lat for lat, _ in normalized_points)
|
||||
lon_sum = sum(lon for _, lon in normalized_points)
|
||||
count = Decimal(len(normalized_points))
|
||||
center_lat = (lat_sum / count).quantize(DECIMAL_PRECISION, rounding=ROUND_HALF_UP)
|
||||
center_lon = (lon_sum / count).quantize(DECIMAL_PRECISION, rounding=ROUND_HALF_UP)
|
||||
center_lat, center_lon = _compute_polygon_centroid(normalized_points)
|
||||
|
||||
with transaction.atomic():
|
||||
location, _ = SoilLocation.objects.update_or_create(
|
||||
@@ -152,16 +149,83 @@ def ensure_location_and_weather_data(location: SoilLocation) -> tuple[SoilLocati
|
||||
return location, weather_forecast
|
||||
|
||||
|
||||
def _flatten_sensor_metrics(sensor_payload: dict | None) -> dict:
|
||||
def _resolve_sensor_metrics(sensor_payload: dict | None) -> tuple[dict, dict]:
|
||||
if not isinstance(sensor_payload, dict):
|
||||
return {}
|
||||
return {}, {}
|
||||
|
||||
flattened = {}
|
||||
for sensor_values in sensor_payload.values():
|
||||
readings_by_metric: dict[str, list[tuple[str, object]]] = {}
|
||||
for sensor_key, sensor_values in sorted(sensor_payload.items()):
|
||||
if not isinstance(sensor_values, dict):
|
||||
continue
|
||||
flattened.update(sensor_values)
|
||||
return flattened
|
||||
for metric_key, metric_value in sensor_values.items():
|
||||
readings_by_metric.setdefault(metric_key, []).append((sensor_key, metric_value))
|
||||
|
||||
resolved_metrics = {}
|
||||
metric_sources = {}
|
||||
for metric_key, readings in readings_by_metric.items():
|
||||
resolved_value, source = _resolve_metric_readings(readings)
|
||||
resolved_metrics[metric_key] = resolved_value
|
||||
metric_sources[metric_key] = source
|
||||
return resolved_metrics, metric_sources
|
||||
|
||||
|
||||
def _resolve_metric_readings(readings: list[tuple[str, object]]) -> tuple[object, dict[str, object]]:
|
||||
if not readings:
|
||||
return None, {"type": "sensor", "strategy": "empty", "sensor_keys": []}
|
||||
|
||||
sensor_keys = [sensor_key for sensor_key, _value in readings]
|
||||
distinct_values: list[object] = []
|
||||
for _sensor_key, value in readings:
|
||||
if value not in distinct_values:
|
||||
distinct_values.append(value)
|
||||
|
||||
if len(distinct_values) == 1:
|
||||
return distinct_values[0], {
|
||||
"type": "sensor",
|
||||
"strategy": "single_value",
|
||||
"sensor_keys": sensor_keys,
|
||||
"sensor_count": len(sensor_keys),
|
||||
}
|
||||
|
||||
numeric_values = [_coerce_numeric(value) for value in distinct_values]
|
||||
if all(value is not None for value in numeric_values):
|
||||
average = sum(numeric_values) / len(numeric_values)
|
||||
resolved_value = _normalize_numeric_result(average, distinct_values)
|
||||
return resolved_value, {
|
||||
"type": "sensor",
|
||||
"strategy": "average",
|
||||
"sensor_keys": sensor_keys,
|
||||
"sensor_count": len(sensor_keys),
|
||||
"conflict": True,
|
||||
"distinct_values": distinct_values,
|
||||
}
|
||||
|
||||
return distinct_values, {
|
||||
"type": "sensor",
|
||||
"strategy": "distinct_values",
|
||||
"sensor_keys": sensor_keys,
|
||||
"sensor_count": len(sensor_keys),
|
||||
"conflict": True,
|
||||
"distinct_values": distinct_values,
|
||||
}
|
||||
|
||||
|
||||
def _coerce_numeric(value: object) -> float | None:
|
||||
if isinstance(value, bool):
|
||||
return None
|
||||
if isinstance(value, Number):
|
||||
return float(value)
|
||||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _normalize_numeric_result(value: float, source_values: list[object]) -> int | float:
|
||||
if all(isinstance(item, int) and not isinstance(item, bool) for item in source_values):
|
||||
if value.is_integer():
|
||||
return int(value)
|
||||
return float(Decimal(str(value)).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP))
|
||||
|
||||
|
||||
def _surface_soil_metrics(depths) -> dict:
|
||||
@@ -240,3 +304,40 @@ def _serialize_boundary(boundary: dict | list) -> dict:
|
||||
"type": "Polygon",
|
||||
"coordinates": [coordinates],
|
||||
}
|
||||
|
||||
|
||||
def _compute_polygon_centroid(points: list[tuple[Decimal, Decimal]]) -> tuple[Decimal, Decimal]:
|
||||
polygon = list(points)
|
||||
if polygon[0] != polygon[-1]:
|
||||
polygon.append(polygon[0])
|
||||
|
||||
twice_area = Decimal("0")
|
||||
centroid_lon = Decimal("0")
|
||||
centroid_lat = Decimal("0")
|
||||
|
||||
for index in range(len(polygon) - 1):
|
||||
lat1, lon1 = polygon[index]
|
||||
lat2, lon2 = polygon[index + 1]
|
||||
cross = (lon1 * lat2) - (lon2 * lat1)
|
||||
twice_area += cross
|
||||
centroid_lon += (lon1 + lon2) * cross
|
||||
centroid_lat += (lat1 + lat2) * cross
|
||||
|
||||
if twice_area == 0:
|
||||
return _compute_average_center(points)
|
||||
|
||||
factor = Decimal("3") * twice_area
|
||||
return (
|
||||
(centroid_lat / factor).quantize(DECIMAL_PRECISION, rounding=ROUND_HALF_UP),
|
||||
(centroid_lon / factor).quantize(DECIMAL_PRECISION, rounding=ROUND_HALF_UP),
|
||||
)
|
||||
|
||||
|
||||
def _compute_average_center(points: list[tuple[Decimal, Decimal]]) -> tuple[Decimal, Decimal]:
|
||||
lat_sum = sum(lat for lat, _ in points)
|
||||
lon_sum = sum(lon for _, lon in points)
|
||||
count = Decimal(len(points))
|
||||
return (
|
||||
(lat_sum / count).quantize(DECIMAL_PRECISION, rounding=ROUND_HALF_UP),
|
||||
(lon_sum / count).quantize(DECIMAL_PRECISION, rounding=ROUND_HALF_UP),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user