docs: refine pesticide management spec

This commit is contained in:
akira
2026-04-09 14:56:21 +09:00
parent 10f2b6f77f
commit 70fe3824b3

View File

@@ -59,7 +59,7 @@
### 特別栽培向け成分数集計
「節減対象農薬(`is_non_target=False`)の有効成分が何種類使われたか」を年度×作物単位でカウントする。
「節減対象農薬(`is_non_target=False`)の有効成分`is_active=True`が何種類使われたか」を年度×作物単位でカウントする。
上限はなく、報告用の集計値として表示する。
---
@@ -102,10 +102,25 @@
| name | CharField(200) | required | 成分名称(例: MEP |
| concentration | CharField(100) | blank | 含有濃度(例: 50.0% |
| is_active | BooleanField | default=True | 有効成分かどうかFalse = その他成分) |
- `unique_together = ['pesticide', 'name']`
### PesticideIngredientLimit有効成分の総使用回数上限作物別
**テーブル名**: `pesticide_pesticideingredientlimit`
農水省の「○○を含む農薬の総使用回数」は作物ごとに異なりうるため、有効成分本体とは分離して作物別に保持する。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | BigAutoField | PK | |
| pesticide | FK(Pesticide) | CASCADE | 取得元農薬 |
| ingredient_name | CharField(200) | required | 成分名称(例: MEP |
| crop_name | CharField(200) | required | 作物名(農水省登録情報の表記、例: 稲) |
| max_total_uses | IntegerField | null=True | この成分を含む農薬の総使用回数上限 |
| use_timing_note | TextField | blank | 使用時期別制限のテキスト(例: 種もみへの処理は1回以内、… |
- `unique_together = ['pesticide', 'name']`
- `unique_together = ['pesticide', 'ingredient_name', 'crop_name']`
### PesticideProductLimit製品の使用回数上限作物別
@@ -123,6 +138,22 @@
- `unique_together = ['pesticide', 'crop_name']`
### PesticideCropAlias農水省作物名と内部作物の対応
**テーブル名**: `pesticide_pesticidecropalias`
農水省の適用表上の作物名と、内部 `plans.Crop` の作物を対応付けるための正規化テーブル。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | BigAutoField | PK | |
| crop | FK(plans.Crop) | PROTECT | 内部作物 |
| alias_name | CharField(200) | required, unique | 農水省登録情報の作物名(例: 稲, 水稲) |
| is_primary | BooleanField | default=False | 代表表記かどうか |
- 使用回数チェック時は `crop_id` から本テーブルを逆引きし、`PesticideProductLimit.crop_name` / `PesticideIngredientLimit.crop_name` と照合する
- 初期データ例: `Crop=水稲` に対し `alias_name=稲`, `alias_name=水稲` を登録
### SprayEvent散布イベント
**テーブル名**: `pesticide_sprayevent`
@@ -139,6 +170,8 @@
| target_group | CharField(50) | blank | 対象が圃場グループの場合group_name |
| target_crop | FK(plans.Crop) | null=True, PROTECT | 対象が作物の場合 |
| target_variety | FK(plans.Variety) | null=True, PROTECT | 対象が品種の場合 |
| crop_snapshot | CharField(100) | blank | 保存時点の集計対象作物名 |
| variety_snapshot | CharField(100) | blank | 保存時点の対象品種名 |
| notes | TextField | blank | 備考 |
| created_at | DateTimeField | auto | |
| updated_at | DateTimeField | auto | |
@@ -152,6 +185,27 @@
| `crop` | target_crop | 特定の作物に対して散布(作付け計画と照合) |
| `variety` | target_variety | 特定の品種に対して散布(作付け計画と照合) |
- 保存時に `crop_snapshot` / `variety_snapshot` を自動設定し、後日の作付け変更やグループ名変更があっても過去実績の集計結果が変わらないようにする
### SprayEventResolvedField散布イベント対象圃場スナップショット
**テーブル名**: `pesticide_sprayeventresolvedfield`
`target_type=group` / `crop` / `variety` のように複数圃場へ展開される散布について、保存時点で対象圃場を確定保存する。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | BigAutoField | PK | |
| event | FK(SprayEvent) | CASCADE | |
| field | FK(fields.Field) | PROTECT | 対象圃場 |
| field_name_snapshot | CharField(100) | required | 保存時点の圃場名 |
| group_name_snapshot | CharField(50) | blank | 保存時点のグループ名 |
| crop_name_snapshot | CharField(100) | required | 保存時点の作物名 |
| variety_name_snapshot | CharField(100) | blank | 保存時点の品種名 |
- `unique_together = ['event', 'field']`
- `target_type=field` の場合も 1 行作成しておくと、集計ロジックを統一しやすい
### SprayEventPesticide散布農薬明細
**テーブル名**: `pesticide_sprayeventpesticide`
@@ -177,9 +231,9 @@
**年度 × 作物** を基本単位とする。
- `target_type=field`/`group` の場合: そのイベントの年度の作付け計画Planから作物を特定
- `target_type=crop` の場合: target_crop が直接作物
- `target_type=variety` の場合: target_variety の crop が作物
- 集計対象作物は `SprayEvent.crop_snapshot``SprayEventResolvedField.crop_name_snapshot` を正とする
- `target_type=field`/`group`/`crop`/`variety` の違いにかかわらず、保存時に解決済み対象圃場と作物スナップショットを作成する
- 使用回数上限の照合は、内部 `Crop` から `PesticideCropAlias` を介して農水省表記へ変換して行う
### 製品使用回数の集計
@@ -194,6 +248,7 @@
有効成分総使用回数 = COUNT(SprayEventPesticide)
where SprayEventPesticide.pesticide に
当該有効成分PesticideIngredient.nameが含まれる
かつ PesticideIngredient.is_active=True
かつ is_non_target=False
で year × 作物 でフィルタしたもの
```
@@ -203,6 +258,7 @@
```
使用成分数 = COUNT(DISTINCT PesticideIngredient.name)
where 当該年度×作物の散布イベントで使用された農薬の有効成分
かつ PesticideIngredient.is_active=True
かつ pesticide.is_non_target=False
```
@@ -243,9 +299,7 @@
"id": 1,
"name": "MEP",
"concentration": "50.0%",
"is_active": true,
"max_total_uses": 3,
"use_timing_note": "種もみへの処理は1回以内、育苗箱散布は1回以内、本田では2回以内"
"is_active": true
}
],
"product_limits": [
@@ -255,6 +309,23 @@
"max_uses": 2,
"use_timing_note": "収穫21日前まで"
}
],
"ingredient_limits": [
{
"id": 1,
"ingredient_name": "MEP",
"crop_name": "稲",
"max_total_uses": 3,
"use_timing_note": "種もみへの処理は1回以内、育苗箱散布は1回以内、本田では2回以内"
}
],
"crop_aliases": [
{
"crop": 1,
"crop_name": "水稲",
"alias_name": "稲",
"is_primary": true
}
]
}
```
@@ -347,6 +418,17 @@
"target_type": "group",
"target_group": "田中エリア",
"target_display": "田中エリア(グループ)",
"crop_snapshot": "水稲",
"variety_snapshot": "",
"resolved_fields": [
{
"field": 5,
"field_name_snapshot": "田中上",
"group_name_snapshot": "田中エリア",
"crop_name_snapshot": "水稲",
"variety_name_snapshot": "コシヒカリ"
}
],
"notes": "曇り、風弱し",
"pesticides": [
{
@@ -373,7 +455,8 @@
{
"year": 2026,
"crop_id": 1,
"crop_name": "稲",
"crop_name": "稲",
"crop_aliases": ["稲", "水稲"],
"product_usage": [
{
"pesticide_id": 1,
@@ -454,7 +537,7 @@ URL: `https://pesticide.maff.go.jp/`
| 作物名 | 作物名 | `PesticideProductLimit.crop_name` |
| 本剤の使用回数 | 「N回以内」から N を抽出 | `PesticideProductLimit.max_uses` |
| 使用時期 | テキストそのまま | `PesticideProductLimit.use_timing_note` |
| `{成分名}を含む農薬の総使用回数` | 「N回以内(...)」から N と補足を抽出 | `PesticideIngredient.max_total_uses` / `use_timing_note` |
| `{成分名}を含む農薬の総使用回数` | 「N回以内(...)」から N と補足を抽出 | `PesticideIngredientLimit.max_total_uses` / `use_timing_note` |
**「総使用回数」テキストのパース規則:**
@@ -477,6 +560,7 @@ Django management command として実装。APIエンドポイントから呼び
- アクセスは農薬マスタ登録時の1件ずつに限定バルク取得は行わない
- 農水省サイトの内部ID`system_id`)と農薬の公式登録番号は別物
- タイムアウト: 10秒
- 適用表の作物名は `PesticideCropAlias` で内部 `Crop` と対応付ける前提で保存する
---
@@ -508,11 +592,14 @@ Django management command として実装。APIエンドポイントから呼び
## 設計判断と制約
1. **散布対象の特定**: `target_type` + 対象FK/文字列で柔軟に対応。作物ごとの集計は作付け計画Planと照合。
2. **使用回数上限は作物別に保持**: 同一農薬でも稲と野菜で上限が異なるため `PesticideProductLimit` を作物別に複数行保持。
3. **総使用回数はテキストパース**: 農水省サイトの「○○を含む農薬の総使用回数」カラムから正規表現で数値を抽出
4. **保存はブロックしない**: 使用回数超過は警告表示のみ。農薬散布の記録は法的義務があるため、超過でも保存できるようにする。
5. **`SprayEventPesticide.pesticide` は PROTECT**: 散布記録に使用中の農薬は削除不可
6. **`is_spreader=True``is_non_target` 扱い**: 展着剤はカウント除外のため、展着剤フラグをセットすれば節減対象外フラグも自動的に True 扱いDB保存は別フィールド
2. **使用回数上限は作物別に保持**: 同一農薬でも作物ごとに上限が異なるため `PesticideProductLimit``PesticideIngredientLimit` を作物別に複数行保持する
3. **作物名の照合は別名テーブルで吸収**: 農水省表記の「稲」と内部の「水稲」のような差異を吸収するため、`PesticideCropAlias` を必須とする
4. **散布対象は保存時に確定保存する**: 後日のグループ名変更や作付け変更で過去実績の集計結果が変わらないよう、`SprayEventResolvedField``crop_snapshot` / `variety_snapshot` を保持する。
5. **総使用回数はテキストパース**: 農水省サイトの「○○を含む農薬の総使用回数」カラムから正規表現で数値を抽出する
6. **保存はブロックしない**: 使用回数超過は警告表示のみ。農薬散布の記録は法的義務があるため、超過でも保存できるようにする
7. **`SprayEventPesticide.pesticide` は PROTECT**: 散布記録に使用中の農薬は削除不可。
8. **成分集計は `is_active=True` のみ対象**: 「その他成分」は総使用回数・特別栽培の成分数集計に含めない。
9. **`is_spreader=True``is_non_target` 扱い**: 展着剤はカウント除外のため、展着剤フラグをセットすれば節減対象外フラグも自動的に True 扱いDB保存は別フィールド
---
@@ -520,7 +607,7 @@ Django management command として実装。APIエンドポイントから呼び
| ファイル | 説明 |
|---|---|
| `backend/apps/pesticide/models.py` | Pesticide, PesticideIngredient, PesticideProductLimit, SprayEvent, SprayEventPesticide |
| `backend/apps/pesticide/models.py` | Pesticide, PesticideIngredient, PesticideIngredientLimit, PesticideProductLimit, PesticideCropAlias, SprayEvent, SprayEventResolvedField, SprayEventPesticide |
| `backend/apps/pesticide/serializers.py` | DRF シリアライザ |
| `backend/apps/pesticide/views.py` | ViewSet |
| `backend/apps/pesticide/urls.py` | URL ルーティング |