From 78acb5510d8535cbf7faaebeb7435623cb136b35 Mon Sep 17 00:00:00 2001 From: Mohammad Sajad Pourajam Date: Mon, 27 Apr 2026 03:12:33 +0330 Subject: [PATCH] UPDATE --- __init__.py | 39 ++++++++++++++++++ common.py | 32 +++++++++++++++ crop_simulation_current_farm_chart.py | 42 +++++++++++++++++++ crop_simulation_growth.py | 46 +++++++++++++++++++++ crop_simulation_growth_status.py | 59 +++++++++++++++++++++++++++ crop_simulation_harvest_prediction.py | 37 +++++++++++++++++ crop_simulation_yield_prediction.py | 41 +++++++++++++++++++ economy_overview.py | 47 +++++++++++++++++++++ farm_data_upsert.py | 53 ++++++++++++++++++++++++ fertilization_recommend.py | 49 ++++++++++++++++++++++ irrigation_list.py | 39 ++++++++++++++++++ irrigation_recommend.py | 49 ++++++++++++++++++++++ rag_chat.py | 50 +++++++++++++++++++++++ soile_anomaly_detection.py | 42 +++++++++++++++++++ soile_health_summary.py | 37 +++++++++++++++++ soile_moisture_heatmap.py | 41 +++++++++++++++++++ weather_water_need_prediction.py | 46 +++++++++++++++++++++ 17 files changed, 749 insertions(+) create mode 100644 __init__.py create mode 100644 common.py create mode 100644 crop_simulation_current_farm_chart.py create mode 100644 crop_simulation_growth.py create mode 100644 crop_simulation_growth_status.py create mode 100644 crop_simulation_harvest_prediction.py create mode 100644 crop_simulation_yield_prediction.py create mode 100644 economy_overview.py create mode 100644 farm_data_upsert.py create mode 100644 fertilization_recommend.py create mode 100644 irrigation_list.py create mode 100644 irrigation_recommend.py create mode 100644 rag_chat.py create mode 100644 soile_anomaly_detection.py create mode 100644 soile_health_summary.py create mode 100644 soile_moisture_heatmap.py create mode 100644 weather_water_need_prediction.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..d6c82b8 --- /dev/null +++ b/__init__.py @@ -0,0 +1,39 @@ +from .common import RouteContract +from .crop_simulation_current_farm_chart import CONTRACT as CROP_SIMULATION_CURRENT_FARM_CHART_CONTRACT +from .crop_simulation_growth import CONTRACT as CROP_SIMULATION_GROWTH_CONTRACT +from .crop_simulation_growth_status import CONTRACT as CROP_SIMULATION_GROWTH_STATUS_CONTRACT +from .crop_simulation_harvest_prediction import CONTRACT as CROP_SIMULATION_HARVEST_PREDICTION_CONTRACT +from .crop_simulation_yield_prediction import CONTRACT as CROP_SIMULATION_YIELD_PREDICTION_CONTRACT +from .economy_overview import CONTRACT as ECONOMY_OVERVIEW_CONTRACT +from .farm_data_upsert import CONTRACT as FARM_DATA_UPSERT_CONTRACT +from .fertilization_recommend import CONTRACT as FERTILIZATION_RECOMMEND_CONTRACT +from .irrigation_list import CONTRACT as IRRIGATION_LIST_CONTRACT +from .irrigation_recommend import CONTRACT as IRRIGATION_RECOMMEND_CONTRACT +from .rag_chat import CONTRACT as RAG_CHAT_CONTRACT +from .soile_anomaly_detection import CONTRACT as SOILE_ANOMALY_DETECTION_CONTRACT +from .soile_health_summary import CONTRACT as SOILE_HEALTH_SUMMARY_CONTRACT +from .soile_moisture_heatmap import CONTRACT as SOILE_MOISTURE_HEATMAP_CONTRACT +from .weather_water_need_prediction import CONTRACT as WEATHER_WATER_NEED_PREDICTION_CONTRACT + +ROUTE_CONTRACTS: dict[str, RouteContract] = { + contract.path: contract + for contract in [ + RAG_CHAT_CONTRACT, + SOILE_MOISTURE_HEATMAP_CONTRACT, + SOILE_HEALTH_SUMMARY_CONTRACT, + SOILE_ANOMALY_DETECTION_CONTRACT, + FARM_DATA_UPSERT_CONTRACT, + WEATHER_WATER_NEED_PREDICTION_CONTRACT, + ECONOMY_OVERVIEW_CONTRACT, + IRRIGATION_LIST_CONTRACT, + IRRIGATION_RECOMMEND_CONTRACT, + FERTILIZATION_RECOMMEND_CONTRACT, + CROP_SIMULATION_GROWTH_CONTRACT, + CROP_SIMULATION_GROWTH_STATUS_CONTRACT, + CROP_SIMULATION_CURRENT_FARM_CHART_CONTRACT, + CROP_SIMULATION_HARVEST_PREDICTION_CONTRACT, + CROP_SIMULATION_YIELD_PREDICTION_CONTRACT, + ] +} + +__all__ = ['ROUTE_CONTRACTS', 'RouteContract'] diff --git a/common.py b/common.py new file mode 100644 index 0000000..8f01345 --- /dev/null +++ b/common.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from typing import Any, Generic, TypeAlias, TypeVar + +from pydantic import BaseModel, ConfigDict + +JsonValue: TypeAlias = Any +JsonObject: TypeAlias = dict[str, Any] +JsonList: TypeAlias = list[Any] + +T = TypeVar('T') + + +class SchemaModel(BaseModel): + model_config = ConfigDict(extra='allow', populate_by_name=True) + + +class ApiEnvelope(SchemaModel, Generic[T]): + code: int + msg: str + data: T + + +class RouteContract(SchemaModel): + method: str + path: str + request_model: str + response_model: str + + +class EmptyRequest(SchemaModel): + pass diff --git a/crop_simulation_current_farm_chart.py b/crop_simulation_current_farm_chart.py new file mode 100644 index 0000000..d43d1dc --- /dev/null +++ b/crop_simulation_current_farm_chart.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, JsonObject, JsonValue, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/crop-simulation/current-farm-chart/' + + +class CropSimulationCurrentFarmChartRequest(SchemaModel): + farm_uuid: UUID + plant_name: str | None = None + + +class CropSimulationCurrentFarmChartResponseData(SchemaModel): + farm_uuid: str | None = None + plant_name: str | None = None + engine: str | None = None + model_name: str | None = None + scenario_id: int | None = None + simulation_warning: str | None = None + categories: list[str] = Field(default_factory=list) + series: JsonValue | None = None + summary: JsonObject = Field(default_factory=dict) + current_state: JsonObject = Field(default_factory=dict) + metrics: JsonObject = Field(default_factory=dict) + daily_output: JsonObject = Field(default_factory=dict) + + +class CropSimulationCurrentFarmChartResponse(ApiEnvelope[CropSimulationCurrentFarmChartResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=CropSimulationCurrentFarmChartRequest.__name__, + response_model=CropSimulationCurrentFarmChartResponse.__name__, +) diff --git a/crop_simulation_growth.py b/crop_simulation_growth.py new file mode 100644 index 0000000..642cf07 --- /dev/null +++ b/crop_simulation_growth.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from uuid import UUID + +from pydantic import Field, model_validator + +from .common import ApiEnvelope, JsonObject, JsonValue, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/crop-simulation/growth/' + + +class CropSimulationGrowthRequest(SchemaModel): + plant_name: str + dynamic_parameters: list[str] = Field(min_length=1) + farm_uuid: UUID | None = None + weather: JsonValue | None = None + soil_parameters: JsonObject | None = None + site_parameters: JsonObject | None = None + crop_parameters: JsonObject | None = None + agromanagement: JsonObject | None = None + page_size: int | None = Field(default=None, ge=1, le=50) + + @model_validator(mode='after') + def validate_farm_or_weather(self) -> 'CropSimulationGrowthRequest': + if self.farm_uuid is None and self.weather is None: + raise ValueError('Either farm_uuid or weather must be provided.') + return self + + +class CropSimulationGrowthResponseData(SchemaModel): + task_id: str + status_url: str + plant_name: str + + +class CropSimulationGrowthResponse(ApiEnvelope[CropSimulationGrowthResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=CropSimulationGrowthRequest.__name__, + response_model=CropSimulationGrowthResponse.__name__, +) diff --git a/crop_simulation_growth_status.py b/crop_simulation_growth_status.py new file mode 100644 index 0000000..655d390 --- /dev/null +++ b/crop_simulation_growth_status.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from pydantic import Field + +from .common import ApiEnvelope, JsonObject, JsonValue, JsonList, RouteContract, SchemaModel + +HTTP_METHOD = 'GET' +ROUTE_PATH = '/api/crop-simulation/growth//status/' + + +class CropSimulationGrowthStatusRequest(SchemaModel): + task_id: str + page: int | None = Field(default=None, ge=1) + page_size: int | None = Field(default=None, ge=1) + + +class CropSimulationPagination(SchemaModel): + page: int + page_size: int + total_items: int + total_pages: int + has_next: bool + has_previous: bool + + +class CropSimulationGrowthResult(SchemaModel): + plant_name: str | None = None + dynamic_parameters: list[str] = Field(default_factory=list) + engine: str | None = None + model_name: str | None = None + scenario_id: int | None = None + simulation_warning: str | None = None + summary_metrics: JsonObject = Field(default_factory=dict) + stage_timeline: JsonList = Field(default_factory=list) + stages_page: JsonList = Field(default_factory=list) + pagination: CropSimulationPagination | None = None + daily_records_count: int | None = None + default_page_size: int | None = None + + +class CropSimulationGrowthStatusResponseData(SchemaModel): + task_id: str + status: str + message: str | None = None + progress: JsonObject = Field(default_factory=dict) + result: CropSimulationGrowthResult | None = None + error: str | None = None + + +class CropSimulationGrowthStatusResponse(ApiEnvelope[CropSimulationGrowthStatusResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=CropSimulationGrowthStatusRequest.__name__, + response_model=CropSimulationGrowthStatusResponse.__name__, +) diff --git a/crop_simulation_harvest_prediction.py b/crop_simulation_harvest_prediction.py new file mode 100644 index 0000000..78d363a --- /dev/null +++ b/crop_simulation_harvest_prediction.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, JsonObject, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/crop-simulation/harvest-prediction/' + + +class CropSimulationHarvestPredictionRequest(SchemaModel): + farm_uuid: UUID + plant_name: str | None = None + + +class CropSimulationHarvestPredictionResponseData(SchemaModel): + date: str + dateFormatted: str + daysUntil: int + description: str | None = None + optimalWindowStart: str | None = None + optimalWindowEnd: str | None = None + gddDetails: JsonObject = Field(default_factory=dict) + + +class CropSimulationHarvestPredictionResponse(ApiEnvelope[CropSimulationHarvestPredictionResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=CropSimulationHarvestPredictionRequest.__name__, + response_model=CropSimulationHarvestPredictionResponse.__name__, +) diff --git a/crop_simulation_yield_prediction.py b/crop_simulation_yield_prediction.py new file mode 100644 index 0000000..43f0784 --- /dev/null +++ b/crop_simulation_yield_prediction.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, JsonObject, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/crop-simulation/yield-prediction/' + + +class CropSimulationYieldPredictionRequest(SchemaModel): + farm_uuid: UUID + plant_name: str | None = None + + +class CropSimulationYieldPredictionResponseData(SchemaModel): + farm_uuid: str + plant_name: str | None = None + predictedYieldTons: float | None = None + predictedYieldRaw: float | None = None + unit: str | None = None + sourceUnit: str | None = None + simulationEngine: str | None = None + simulationModel: str | None = None + scenarioId: int | None = None + simulationWarning: str | None = None + supportingMetrics: JsonObject = Field(default_factory=dict) + + +class CropSimulationYieldPredictionResponse(ApiEnvelope[CropSimulationYieldPredictionResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=CropSimulationYieldPredictionRequest.__name__, + response_model=CropSimulationYieldPredictionResponse.__name__, +) diff --git a/economy_overview.py b/economy_overview.py new file mode 100644 index 0000000..362ed56 --- /dev/null +++ b/economy_overview.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/economy/overview/' + + +class EconomyOverviewRequest(SchemaModel): + farm_uuid: UUID + + +class EconomyDataItem(SchemaModel): + title: str + value: str + subtitle: str | None = None + avatarIcon: str | None = None + avatarColor: str | None = None + + +class ChartSeriesItem(SchemaModel): + name: str + data: list[float] = Field(default_factory=list) + + +class EconomyOverviewResponseData(SchemaModel): + farm_uuid: str + source: str | None = None + economicData: list[EconomyDataItem] = Field(default_factory=list) + chartSeries: list[ChartSeriesItem] = Field(default_factory=list) + chartCategories: list[str] = Field(default_factory=list) + + +class EconomyOverviewResponse(ApiEnvelope[EconomyOverviewResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=EconomyOverviewRequest.__name__, + response_model=EconomyOverviewResponse.__name__, +) diff --git a/farm_data_upsert.py b/farm_data_upsert.py new file mode 100644 index 0000000..ebfe7cd --- /dev/null +++ b/farm_data_upsert.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from uuid import UUID + +from pydantic import Field, model_validator + +from .common import ApiEnvelope, JsonObject, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/farm-data/' + + +class FarmBoundaryCorner(SchemaModel): + lat: float + lon: float + + +class FarmDataUpsertRequest(SchemaModel): + farm_uuid: UUID + farm_boundary: JsonObject + sensor_key: str | None = 'sensor-7-1' + sensor_payload: JsonObject | None = None + plant_ids: list[int] = Field(default_factory=list) + irrigation_method_id: int | None = None + + @model_validator(mode='after') + def validate_payload_sources(self) -> 'FarmDataUpsertRequest': + if not self.sensor_payload and not self.plant_ids and self.irrigation_method_id is None: + raise ValueError('At least one of sensor_payload, plant_ids or irrigation_method_id must be provided.') + return self + + +class FarmDataUpsertResponseData(SchemaModel): + farm_uuid: UUID + center_location_id: int | None = None + weather_forecast_id: int | None = None + sensor_payload: JsonObject = Field(default_factory=dict) + plant_ids: list[int] = Field(default_factory=list) + irrigation_method_id: int | None = None + created_at: str | None = None + updated_at: str | None = None + + +class FarmDataUpsertResponse(ApiEnvelope[FarmDataUpsertResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=FarmDataUpsertRequest.__name__, + response_model=FarmDataUpsertResponse.__name__, +) diff --git a/fertilization_recommend.py b/fertilization_recommend.py new file mode 100644 index 0000000..cef7189 --- /dev/null +++ b/fertilization_recommend.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from typing import Literal +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/fertilization/recommend/' + + +class FertilizationRecommendRequest(SchemaModel): + farm_uuid: UUID + sensor_uuid: UUID | None = None + plant_name: str | None = None + growth_stage: str | None = None + query: str | None = None + + +class FertilizationSection(SchemaModel): + type: Literal['recommendation', 'list', 'warning', 'info'] + title: str + icon: str | None = None + content: str | None = None + items: list[str] = Field(default_factory=list) + fertilizerType: str | None = None + amount: str | None = None + applicationMethod: str | None = None + timing: str | None = None + validityPeriod: str | None = None + expandableExplanation: str | None = None + + +class FertilizationRecommendResponseData(SchemaModel): + sections: list[FertilizationSection] = Field(default_factory=list) + + +class FertilizationRecommendResponse(ApiEnvelope[FertilizationRecommendResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=FertilizationRecommendRequest.__name__, + response_model=FertilizationRecommendResponse.__name__, +) diff --git a/irrigation_list.py b/irrigation_list.py new file mode 100644 index 0000000..a16d1d7 --- /dev/null +++ b/irrigation_list.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from pydantic import RootModel + +from .common import EmptyRequest, RouteContract, SchemaModel + +HTTP_METHOD = 'GET' +ROUTE_PATH = '/api/irrigation/' + + +class IrrigationListRequest(EmptyRequest): + pass + + +class IrrigationMethodSchema(SchemaModel): + id: int + name: str + category: str | None = None + description: str | None = None + water_efficiency_percent: float | None = None + water_pressure_required: str | None = None + flow_rate: str | None = None + coverage_area: str | None = None + soil_type: str | None = None + climate_suitability: str | None = None + created_at: str | None = None + updated_at: str | None = None + + +class IrrigationListResponse(RootModel[list[IrrigationMethodSchema]]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=IrrigationListRequest.__name__, + response_model=IrrigationListResponse.__name__, +) diff --git a/irrigation_recommend.py b/irrigation_recommend.py new file mode 100644 index 0000000..a8b6653 --- /dev/null +++ b/irrigation_recommend.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from typing import Literal +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/irrigation/recommend/' + + +class IrrigationRecommendRequest(SchemaModel): + farm_uuid: UUID + sensor_uuid: UUID | None = None + plant_name: str | None = None + growth_stage: str | None = None + irrigation_method_name: str | None = None + query: str | None = None + + +class IrrigationSection(SchemaModel): + type: Literal['recommendation', 'list', 'warning', 'info'] + title: str + icon: str | None = None + content: str | None = None + items: list[str] = Field(default_factory=list) + frequency: str | None = None + amount: str | None = None + timing: str | None = None + validityPeriod: str | None = None + expandableExplanation: str | None = None + + +class IrrigationRecommendResponseData(SchemaModel): + sections: list[IrrigationSection] = Field(default_factory=list) + + +class IrrigationRecommendResponse(ApiEnvelope[IrrigationRecommendResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=IrrigationRecommendRequest.__name__, + response_model=IrrigationRecommendResponse.__name__, +) diff --git a/rag_chat.py b/rag_chat.py new file mode 100644 index 0000000..a197d0b --- /dev/null +++ b/rag_chat.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from typing import Literal +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, JsonObject, JsonValue, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/rag/chat/' + + +class RagChatRequest(SchemaModel): + farm_uuid: UUID + query: str | None = None + message: str | None = None + history: list[JsonObject] | str | None = None + image_urls: list[str] = Field(default_factory=list) + image: str | None = None + images: list[str] = Field(default_factory=list) + + +class RagChatSection(SchemaModel): + type: Literal['recommendation', 'list', 'warning', 'info', 'summary'] + title: str + icon: str | None = None + content: str | None = None + items: list[str] = Field(default_factory=list) + primaryAction: str | None = None + timing: str | None = None + validityPeriod: str | None = None + expandableExplanation: str | None = None + metadata: JsonValue | None = None + + +class RagChatResponseData(SchemaModel): + sections: list[RagChatSection] = Field(default_factory=list) + + +class RagChatResponse(ApiEnvelope[RagChatResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=RagChatRequest.__name__, + response_model=RagChatResponse.__name__, +) diff --git a/soile_anomaly_detection.py b/soile_anomaly_detection.py new file mode 100644 index 0000000..6e4051c --- /dev/null +++ b/soile_anomaly_detection.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from typing import Literal +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, JsonObject, JsonList, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/soile/anomaly-detection/' + + +class SoileAnomalyDetectionRequest(SchemaModel): + farm_uuid: UUID + + +class SoileAnomalyDetectionResponseData(SchemaModel): + farm_uuid: str + summary: str + explanation: str | None = None + likely_cause: str | None = None + recommended_action: str | None = None + monitoring_priority: Literal['low', 'medium', 'high', 'urgent'] | str + confidence: float | None = None + generated_at: str | None = None + anomalies: JsonList = Field(default_factory=list) + interpretation: JsonObject = Field(default_factory=dict) + knowledge_base: str | None = None + raw_response: str | None = None + + +class SoileAnomalyDetectionResponse(ApiEnvelope[SoileAnomalyDetectionResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=SoileAnomalyDetectionRequest.__name__, + response_model=SoileAnomalyDetectionResponse.__name__, +) diff --git a/soile_health_summary.py b/soile_health_summary.py new file mode 100644 index 0000000..69c2676 --- /dev/null +++ b/soile_health_summary.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, JsonObject, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/soile/health-summary/' + + +class SoileHealthSummaryRequest(SchemaModel): + farm_uuid: UUID + + +class SoileHealthSummaryResponseData(SchemaModel): + farm_uuid: str + healthScore: int | float + profileSource: str | None = None + healthScoreDetails: JsonObject = Field(default_factory=dict) + healthLanguage: JsonObject = Field(default_factory=dict) + avgSoilMoisture: int | float | None = None + avgSoilMoistureRaw: float | None = None + avgSoilMoistureStatus: str | None = None + + +class SoileHealthSummaryResponse(ApiEnvelope[SoileHealthSummaryResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=SoileHealthSummaryRequest.__name__, + response_model=SoileHealthSummaryResponse.__name__, +) diff --git a/soile_moisture_heatmap.py b/soile_moisture_heatmap.py new file mode 100644 index 0000000..b360016 --- /dev/null +++ b/soile_moisture_heatmap.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, JsonObject, JsonList, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/soile/moisture-heatmap/' + + +class SoileMoistureHeatmapRequest(SchemaModel): + farm_uuid: UUID + + +class SoileMoistureHeatmapResponseData(SchemaModel): + farm_uuid: str + location: JsonObject = Field(default_factory=dict) + current_sensor: JsonObject = Field(default_factory=dict) + soil_profile: JsonList = Field(default_factory=list) + timestamp: str | None = None + grid_resolution: JsonObject = Field(default_factory=dict) + grid_cells: JsonList = Field(default_factory=list) + sensor_points: JsonList = Field(default_factory=list) + quality_legend: JsonObject = Field(default_factory=dict) + depth_layers: JsonList = Field(default_factory=list) + model_metadata: JsonObject = Field(default_factory=dict) + summary: JsonObject = Field(default_factory=dict) + + +class SoileMoistureHeatmapResponse(ApiEnvelope[SoileMoistureHeatmapResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=SoileMoistureHeatmapRequest.__name__, + response_model=SoileMoistureHeatmapResponse.__name__, +) diff --git a/weather_water_need_prediction.py b/weather_water_need_prediction.py new file mode 100644 index 0000000..2042cff --- /dev/null +++ b/weather_water_need_prediction.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from uuid import UUID + +from pydantic import Field + +from .common import ApiEnvelope, JsonList, RouteContract, SchemaModel + +HTTP_METHOD = 'POST' +ROUTE_PATH = '/api/weather/water-need-prediction/' + + +class WeatherWaterNeedPredictionRequest(SchemaModel): + farm_uuid: UUID + + +class WaterNeedInsight(SchemaModel): + summary: str | None = None + irrigation_outlook: str | None = None + recommended_action: str | None = None + risk_note: str | None = None + confidence: float | None = None + + +class WeatherWaterNeedPredictionResponseData(SchemaModel): + farm_uuid: str + totalNext7Days: float | None = None + unit: str | None = None + categories: list[str] = Field(default_factory=list) + series: JsonList = Field(default_factory=list) + dailyBreakdown: JsonList = Field(default_factory=list) + insight: WaterNeedInsight = Field(default_factory=WaterNeedInsight) + knowledge_base: str | None = None + raw_response: str | None = None + + +class WeatherWaterNeedPredictionResponse(ApiEnvelope[WeatherWaterNeedPredictionResponseData]): + pass + + +CONTRACT = RouteContract( + method=HTTP_METHOD, + path=ROUTE_PATH, + request_model=WeatherWaterNeedPredictionRequest.__name__, + response_model=WeatherWaterNeedPredictionResponse.__name__, +)