500 lines
13 KiB
Markdown
500 lines
13 KiB
Markdown
|
|
# پیشنهاد معماری سنسور برای Backend و AI
|
||
|
|
|
||
|
|
## مسئله اصلی
|
||
|
|
|
||
|
|
تو سناریوی شما ممکن است:
|
||
|
|
|
||
|
|
- یک مزرعه چندین سنسور داشته باشد
|
||
|
|
- سنسورها از vendorهای مختلف باشند
|
||
|
|
- هر سنسور payload متفاوتی بفرستد
|
||
|
|
- بعضی سنسورها یک metric مشترک مثل `soil_moisture` را همزمان گزارش کنند
|
||
|
|
- AI مجبور باشد هم raw data را بفهمد و هم یک view تجمیعشده برای recommendation داشته باشد
|
||
|
|
|
||
|
|
پس معماری سنسور باید این سه ویژگی را همزمان داشته باشد:
|
||
|
|
|
||
|
|
1. **انعطافپذیری در نوع سنسور**
|
||
|
|
2. **قابلیت نگهداری چند سنسور برای یک farm**
|
||
|
|
3. **قابلیت تبدیل raw sensor data به metricهای استاندارد برای AI**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## چیزی که الان در پروژه دارید
|
||
|
|
|
||
|
|
### در Backend
|
||
|
|
|
||
|
|
- `Backend/farm_hub/models.py` مدل `FarmSensor` را دارد برای register کردن سنسورهای هر مزرعه
|
||
|
|
- `Backend/sensor_catalog/models.py` مدل `SensorCatalog` را دارد برای تعریف نوع/کاتالوگ سنسور
|
||
|
|
- `Backend/sensor_external_api/models.py` لاگ raw requestهای دستگاهها را نگه میدارد
|
||
|
|
|
||
|
|
یعنی Backend الان تا حد خوبی نقش **device registry + ingestion gateway + audit log** را دارد.
|
||
|
|
|
||
|
|
### در AI
|
||
|
|
|
||
|
|
- `Ai/farm_data/models.py::SensorData` داده سنسورها را در `sensor_payload` نگه میدارد
|
||
|
|
- `Ai/farm_data/models.py::SensorParameter` پارامترهای قابل پشتیبانی هر `sensor_key` را نگه میدارد
|
||
|
|
- `Ai/farm_data/services.py` روی payloadهای چند سنسوره metric resolve میکند
|
||
|
|
|
||
|
|
یعنی AI الان نقش **farm context store + normalized sensor context** را دارد.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## جمعبندی نقشها
|
||
|
|
|
||
|
|
به نظر من بهترین تفکیک این است:
|
||
|
|
|
||
|
|
### Backend = owner دستگاه و جریان ورود داده
|
||
|
|
|
||
|
|
Backend باید مسئول اینها باشد:
|
||
|
|
|
||
|
|
- ثبت سنسور برای مزرعه
|
||
|
|
- نگهداری metadata دستگاه
|
||
|
|
- احراز هویت request دستگاه
|
||
|
|
- ثبت raw payload
|
||
|
|
- forward کردن داده به AI
|
||
|
|
|
||
|
|
### AI = owner داده تحلیلی و context مزرعه
|
||
|
|
|
||
|
|
AI باید مسئول اینها باشد:
|
||
|
|
|
||
|
|
- نگهداری normalized view از داده سنسورها برای هر farm
|
||
|
|
- استانداردسازی metricها
|
||
|
|
- merge / aggregation / conflict resolution
|
||
|
|
- feed کردن irrigation / fertilization / crop simulation / alerts / RAG
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## پیشنهاد معماری نهایی
|
||
|
|
|
||
|
|
## 1) در Backend فقط registry و ingestion را canonical نگه دار
|
||
|
|
|
||
|
|
### `SensorCatalog`
|
||
|
|
این جدول باید تعریفکننده template سنسور باشد:
|
||
|
|
|
||
|
|
- `code`
|
||
|
|
- `name`
|
||
|
|
- `description`
|
||
|
|
- `returned_data_fields`
|
||
|
|
- `sample_payload`
|
||
|
|
- `customizable_fields`
|
||
|
|
- `supported_power_sources`
|
||
|
|
|
||
|
|
ولی بهتر است بعداً این metadataها را قویتر کنی:
|
||
|
|
|
||
|
|
- `measurement_schema`
|
||
|
|
- `supported_metrics`
|
||
|
|
- `payload_mapping`
|
||
|
|
- `transport_type` مثل `http`, `mqtt`, `lorawan`
|
||
|
|
- `vendor`
|
||
|
|
- `model`
|
||
|
|
- `firmware_constraints`
|
||
|
|
|
||
|
|
### `FarmSensor`
|
||
|
|
این جدول باید instance واقعی دستگاه روی مزرعه باشد.
|
||
|
|
|
||
|
|
پیشنهاد فیلدهای مفهومی:
|
||
|
|
|
||
|
|
- `farm`
|
||
|
|
- `sensor_catalog`
|
||
|
|
- `physical_device_uuid`
|
||
|
|
- `name`
|
||
|
|
- `sensor_type`
|
||
|
|
- `installation_zone`
|
||
|
|
- `depth_cm`
|
||
|
|
- `position`
|
||
|
|
- `status`
|
||
|
|
- `last_seen_at`
|
||
|
|
- `calibration`
|
||
|
|
- `specifications`
|
||
|
|
- `power_source`
|
||
|
|
- `metadata`
|
||
|
|
|
||
|
|
یعنی `FarmSensor` باید بگوید:
|
||
|
|
|
||
|
|
- این دستگاه چیست
|
||
|
|
- کجا نصب شده
|
||
|
|
- چه زمانی آخرین بار data فرستاده
|
||
|
|
- به چه farm تعلق دارد
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2) در Backend raw event را جدا از registry نگه دار
|
||
|
|
|
||
|
|
الان `SensorExternalRequestLog` فقط لاگ request است. این خوب است، ولی برای مقیاسپذیری بهتر است دو لایه داشته باشی:
|
||
|
|
|
||
|
|
### لایه اول: audit log
|
||
|
|
|
||
|
|
همان چیزی که الان داری:
|
||
|
|
|
||
|
|
- raw payload
|
||
|
|
- request time
|
||
|
|
- physical device uuid
|
||
|
|
- farm uuid
|
||
|
|
|
||
|
|
### لایه دوم: normalized ingestion event
|
||
|
|
|
||
|
|
اگر بخواهی معماری تمیزتر شود، بهتر است یک مدل جدا هم داشته باشی، مثلاً:
|
||
|
|
|
||
|
|
- `SensorReadingEvent`
|
||
|
|
|
||
|
|
که در آن اینها ذخیره شود:
|
||
|
|
|
||
|
|
- `farm_sensor`
|
||
|
|
- `recorded_at`
|
||
|
|
- `received_at`
|
||
|
|
- `payload_raw`
|
||
|
|
- `payload_normalized`
|
||
|
|
- `ingestion_status`
|
||
|
|
- `validation_errors`
|
||
|
|
|
||
|
|
این مدل لازم نیست الان فوراً ساخته شود، ولی اگر سنسورها زیاد شوند خیلی کمک میکند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3) در AI داده سنسورها را به شکل sensor-centric نگه دار، نه فقط metric-centric
|
||
|
|
|
||
|
|
الان `Ai/farm_data/models.py::SensorData` این ساختار را دارد:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"sensor-7-1": {
|
||
|
|
"soil_moisture": 22.4,
|
||
|
|
"soil_temperature": 18.1
|
||
|
|
},
|
||
|
|
"leaf-sensor": {
|
||
|
|
"leaf_wetness": 11
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
این از نظر انعطافپذیری خوب است، ولی یک ضعف دارد:
|
||
|
|
|
||
|
|
- `sensor_key` بیشتر نوع سنسور را نشان میدهد، نه instance سنسور
|
||
|
|
|
||
|
|
اگر یک farm دو سنسور از یک type داشته باشد، این ساختار collision میدهد.
|
||
|
|
|
||
|
|
### پیشنهاد بهتر
|
||
|
|
|
||
|
|
در AI payload را بر اساس **sensor instance** نگه دار، نه فقط type:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"device:8c1e...": {
|
||
|
|
"sensor_key": "sensor-7-1",
|
||
|
|
"sensor_type": "soil_probe",
|
||
|
|
"recorded_at": "2026-05-02T10:15:00Z",
|
||
|
|
"metrics": {
|
||
|
|
"soil_moisture": 22.4,
|
||
|
|
"soil_temperature": 18.1
|
||
|
|
},
|
||
|
|
"metadata": {
|
||
|
|
"depth_cm": 20,
|
||
|
|
"zone": "north-1"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"device:91af...": {
|
||
|
|
"sensor_key": "sensor-7-1",
|
||
|
|
"sensor_type": "soil_probe",
|
||
|
|
"recorded_at": "2026-05-02T10:15:30Z",
|
||
|
|
"metrics": {
|
||
|
|
"soil_moisture": 24.1,
|
||
|
|
"soil_temperature": 17.9
|
||
|
|
},
|
||
|
|
"metadata": {
|
||
|
|
"depth_cm": 40,
|
||
|
|
"zone": "north-1"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
یعنی key اصلی بهتر است یکی از اینها باشد:
|
||
|
|
|
||
|
|
- `physical_device_uuid`
|
||
|
|
- یا `farm_sensor.uuid`
|
||
|
|
|
||
|
|
نه فقط `sensor-7-1`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4) در AI یک لایه normalized metrics جدا بساز
|
||
|
|
|
||
|
|
AI برای recommendation نباید هر بار raw payload را از صفر تفسیر کند.
|
||
|
|
|
||
|
|
بهترین مدل ذهنی این است:
|
||
|
|
|
||
|
|
### لایه A: raw sensor context
|
||
|
|
- هر sensor instance چه چیزی فرستاده؟
|
||
|
|
|
||
|
|
### لایه B: resolved farm metrics
|
||
|
|
- برای farm در این لحظه `soil_moisture` نهایی چقدر است؟
|
||
|
|
- source این metric کدام sensorها بوده؟
|
||
|
|
- strategy حل conflict چه بوده؟
|
||
|
|
|
||
|
|
الان `Ai/farm_data/services.py` بخشی از این کار را انجام میدهد. این مسیر درست است.
|
||
|
|
|
||
|
|
پیشنهاد من:
|
||
|
|
|
||
|
|
- `sensor_payload` = raw/near-raw normalized by device
|
||
|
|
- `resolved_metrics` = خروجی استاندارد شده برای AI
|
||
|
|
- `metric_sources` = توضیح اینکه هر metric از کجا آمده
|
||
|
|
|
||
|
|
اینها لازم نیست حتماً همگی DB column جدا باشند؛ فعلاً میتوانند در service layer ساخته شوند. ولی از نظر معماری باید explicit باشند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5) resolution strategy باید قابل تنظیم باشد
|
||
|
|
|
||
|
|
وقتی چند سنسور یک metric مشترک دارند، AI باید بداند چطور resolve کند.
|
||
|
|
|
||
|
|
مثلاً برای `soil_moisture`:
|
||
|
|
|
||
|
|
- اگر چند سنسور همعمق و همزون باشند → average
|
||
|
|
- اگر depth فرق دارد → shallow و deep را جدا نگه دار
|
||
|
|
- اگر یکی unhealthy باشد → ignore
|
||
|
|
- اگر یکی stale باشد → وزن کمتر بگیرد یا حذف شود
|
||
|
|
|
||
|
|
### بنابراین برای هر metric این چیزها مهم میشوند:
|
||
|
|
|
||
|
|
- `recorded_at`
|
||
|
|
- `depth_cm`
|
||
|
|
- `zone`
|
||
|
|
- `sensor_health`
|
||
|
|
- `priority`
|
||
|
|
- `confidence`
|
||
|
|
|
||
|
|
الان average ساده خوب است برای شروع، ولی برای طراحی نهایی کافی نیست.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6) schema mapping را از business logic جدا کن
|
||
|
|
|
||
|
|
Backend و AI نباید به aliasهای vendor-specific وابسته بمانند.
|
||
|
|
|
||
|
|
مثلاً:
|
||
|
|
|
||
|
|
- `moisture_percent`
|
||
|
|
- `soilMoisture`
|
||
|
|
- `moisture`
|
||
|
|
- `soil_moisture`
|
||
|
|
|
||
|
|
همه باید به یک metric استاندارد map شوند:
|
||
|
|
|
||
|
|
- `soil_moisture`
|
||
|
|
|
||
|
|
### بهترین محل این mapping
|
||
|
|
|
||
|
|
به نظر من mapping باید در یک لایه canonical تعریف شود:
|
||
|
|
|
||
|
|
- در `SensorCatalog`
|
||
|
|
- یا در AI داخل `SensorParameter.metadata`
|
||
|
|
|
||
|
|
مثلاً:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"code": "soil_moisture",
|
||
|
|
"metadata": {
|
||
|
|
"aliases": ["moisture_percent", "soilMoisture", "moisture"],
|
||
|
|
"unit": "%",
|
||
|
|
"valid_range": [0, 100]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## پیشنهاد عملی برای Backend
|
||
|
|
|
||
|
|
## Backend چه چیزی نگه دارد؟
|
||
|
|
|
||
|
|
### مدلهای اصلی
|
||
|
|
|
||
|
|
- `SensorCatalog` = تعریف نوع سنسور
|
||
|
|
- `FarmSensor` = دستگاه نصبشده روی مزرعه
|
||
|
|
- `SensorExternalRequestLog` = raw ingress log
|
||
|
|
|
||
|
|
### مسئولیتها
|
||
|
|
|
||
|
|
- ثبت sensor inventory
|
||
|
|
- validate کردن physical device
|
||
|
|
- ثبت raw payload برای audit
|
||
|
|
- attach کردن payload به farm درست
|
||
|
|
- forward کردن payload به AI
|
||
|
|
|
||
|
|
### چیزی که Backend نباید owner آن باشد
|
||
|
|
|
||
|
|
- منطق aggregation نهایی برای recommendation
|
||
|
|
- conflict resolution تخصصی برای سنسورهای متعدد
|
||
|
|
- semantic interpretation نهایی برای AI outputs
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## پیشنهاد عملی برای AI
|
||
|
|
|
||
|
|
## AI چه چیزی نگه دارد؟
|
||
|
|
|
||
|
|
### `SensorData`
|
||
|
|
برای هر `farm_uuid`:
|
||
|
|
|
||
|
|
- `sensor_payload` بر اساس device instance
|
||
|
|
- `plants`
|
||
|
|
- `irrigation_method`
|
||
|
|
- `center_location`
|
||
|
|
- `weather_forecast`
|
||
|
|
|
||
|
|
### `SensorParameter`
|
||
|
|
برای تعریف metricهای استاندارد:
|
||
|
|
|
||
|
|
- `sensor_key`
|
||
|
|
- `code`
|
||
|
|
- `name_fa`
|
||
|
|
- `unit`
|
||
|
|
- `data_type`
|
||
|
|
- `metadata`
|
||
|
|
|
||
|
|
ولی پیشنهاد میکنم `metadata` را برای این موارد غنیتر کنی:
|
||
|
|
|
||
|
|
- `aliases`
|
||
|
|
- `valid_range`
|
||
|
|
- `aggregation_strategy`
|
||
|
|
- `semantic_group`
|
||
|
|
- `recommended_for`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ساختار پیشنهادی payload بین Backend و AI
|
||
|
|
|
||
|
|
بهجای فرستادن فقط این:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"sensor-7-1": {
|
||
|
|
"soil_moisture": 45.2
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
بهتر است به این سمت بروی:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"device:22222222-2222-2222-2222-222222222222": {
|
||
|
|
"sensor_key": "sensor-7-1",
|
||
|
|
"sensor_type": "soil_probe",
|
||
|
|
"recorded_at": "2026-05-02T10:15:00Z",
|
||
|
|
"metrics": {
|
||
|
|
"soil_moisture": 45.2,
|
||
|
|
"soil_temperature": 22.5
|
||
|
|
},
|
||
|
|
"metadata": {
|
||
|
|
"depth_cm": 20,
|
||
|
|
"zone": "zone-a"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
اگر فعلاً نمیخواهی API را بشکنی، حداقل این migration path را برو:
|
||
|
|
|
||
|
|
1. فعلاً `sensor_payload` فعلی را نگه دار
|
||
|
|
2. اجازه بده علاوه بر `sensor_key`، `physical_device_uuid` هم به AI برسد
|
||
|
|
3. در AI key داخلی را با device uuid بساز
|
||
|
|
4. بعداً schema را کامل migrate کن
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## الگوی تصمیمگیری برای چند سنسور
|
||
|
|
|
||
|
|
برای AI پیشنهاد میکنم سه خروجی داشته باشی:
|
||
|
|
|
||
|
|
### 1) `raw_sensor_payload`
|
||
|
|
همه دادههای هر device بدون از دست رفتن context
|
||
|
|
|
||
|
|
### 2) `resolved_metrics`
|
||
|
|
مثلاً:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"soil_moisture": 23.2,
|
||
|
|
"soil_temperature": 18.5
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3) `metric_sources`
|
||
|
|
مثلاً:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"soil_moisture": {
|
||
|
|
"strategy": "weighted_average",
|
||
|
|
"sensor_keys": ["device:a", "device:b"],
|
||
|
|
"depths": [20, 40],
|
||
|
|
"conflict": true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
این ساختار برای explainability خیلی مهم است.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## چیزی که من پیشنهاد میکنم همین الان تغییر بدهی
|
||
|
|
|
||
|
|
### تغییرات سریع و پرارزش
|
||
|
|
|
||
|
|
#### در Backend
|
||
|
|
|
||
|
|
- به `FarmSensor` این فیلدها را اضافه کن:
|
||
|
|
- `metadata`
|
||
|
|
- `installation_zone`
|
||
|
|
- `depth_cm`
|
||
|
|
- `last_seen_at`
|
||
|
|
- `status`
|
||
|
|
|
||
|
|
#### در AI
|
||
|
|
|
||
|
|
- `sensor_payload` را بهسمت instance-based key ببر
|
||
|
|
- `SensorParameter.metadata` را برای alias و aggregation strategy غنی کن
|
||
|
|
- resolver فعلی را از `average ساده` به strategy-based resolver ارتقا بده
|
||
|
|
|
||
|
|
#### در API بین Backend و AI
|
||
|
|
|
||
|
|
- همراه payload این اطلاعات را هم بفرست:
|
||
|
|
- `physical_device_uuid`
|
||
|
|
- `sensor_catalog_uuid`
|
||
|
|
- `sensor_type`
|
||
|
|
- `recorded_at`
|
||
|
|
- `depth_cm` یا `zone`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## پیشنهاد naming
|
||
|
|
|
||
|
|
برای شفافیت بیشتر:
|
||
|
|
|
||
|
|
- `SensorCatalog` = نوع سنسور
|
||
|
|
- `FarmSensor` = سنسور نصبشده
|
||
|
|
- `SensorExternalRequestLog` = raw ingest log
|
||
|
|
- `SensorData` = farm sensor context
|
||
|
|
|
||
|
|
اگر بعداً مدل event اضافه کردی:
|
||
|
|
|
||
|
|
- `SensorReadingEvent` = reading normalized per device
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## تصمیم نهایی من
|
||
|
|
|
||
|
|
اگر بخواهم خیلی خلاصه بگویم:
|
||
|
|
|
||
|
|
- **Backend** باید registry, ingestion, audit را handle کند
|
||
|
|
- **AI** باید normalization, aggregation, context-building, recommendation input را handle کند
|
||
|
|
- key اصلی داده سنسور باید **sensor instance** باشد، نه فقط `sensor_key`
|
||
|
|
- raw sensor data و resolved farm metrics باید از هم جدا باشند
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## نتیجه یکخطی
|
||
|
|
|
||
|
|
برای سنسورهای انعطافپذیر و چندتایی، Backend را بهعنوان لایه ثبت دستگاه و ورود raw data نگه دار و AI را بهعنوان لایه استانداردسازی و تجمیع metricها؛ و مهمتر از همه، دادهها را بر اساس **device instance** مدل کن نه فقط نوع سنسور.
|