Compare commits
5 Commits
10f2b6f77f
...
627d7e4f59
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
627d7e4f59 | ||
|
|
9059b2b51e | ||
|
|
7d2eb1ebe2 | ||
|
|
3e2942b479 | ||
|
|
70fe3824b3 |
@@ -59,7 +59,7 @@
|
||||
|
||||
### 特別栽培向け成分数集計
|
||||
|
||||
「節減対象農薬(`is_non_target=False`)の有効成分が何種類使われたか」を年度×作物単位でカウントする。
|
||||
「節減対象農薬(`is_non_target=False`)の有効成分(`is_active=True`)が何種類使われたか」を年度×作物単位でカウントする。
|
||||
上限はなく、報告用の集計値として表示する。
|
||||
|
||||
---
|
||||
@@ -102,10 +102,28 @@
|
||||
| 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']`
|
||||
- 同一成分・同一作物であれば製品が異なっても上限値は同一(農水省登録情報の仕様)
|
||||
- 保存時バリデーション: 同一 `ingredient_name + crop_name` の既存レコードと異なる `max_total_uses` を保存しようとした場合はエラーにする
|
||||
- 使用回数チェック API の `ingredient_usage.max_total_uses` は、同一 `ingredient_name + crop_name` の値が一意であることを前提に単一値を返す
|
||||
|
||||
### PesticideProductLimit(製品の使用回数上限:作物別)
|
||||
|
||||
@@ -123,6 +141,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`
|
||||
@@ -152,6 +186,27 @@
|
||||
| `crop` | target_crop | 特定の作物に対して散布(作付け計画と照合) |
|
||||
| `variety` | target_variety | 特定の品種に対して散布(作付け計画と照合) |
|
||||
|
||||
- 保存時に全対象圃場を `SprayEventResolvedField` として確定保存し、後日の作付け変更やグループ名変更があっても過去実績の集計結果が変わらないようにする
|
||||
|
||||
### 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`
|
||||
@@ -168,6 +223,7 @@
|
||||
| notes | TextField | blank | 備考 |
|
||||
|
||||
- `pesticide` は PROTECT(使用済み農薬は削除不可)
|
||||
- `unique_together = ['event', 'pesticide']`(同一イベント内で同じ農薬を2回登録不可)
|
||||
|
||||
---
|
||||
|
||||
@@ -175,35 +231,52 @@
|
||||
|
||||
### 集計単位
|
||||
|
||||
**年度 × 作物** を基本単位とする。
|
||||
**年度 × 作物** を基本単位とする(農薬取締法上、使用回数は作物単位で管理する義務がある)。
|
||||
|
||||
- `target_type=field`/`group` の場合: そのイベントの年度の作付け計画(Plan)から作物を特定
|
||||
- `target_type=crop` の場合: target_crop が直接作物
|
||||
- `target_type=variety` の場合: target_variety の crop が作物
|
||||
- 集計対象作物は `SprayEventResolvedField.crop_name_snapshot` を正とする(圃場ごとに記録)
|
||||
- `target_type=field`/`group`/`crop`/`variety` の違いにかかわらず、保存時に全対象圃場の `SprayEventResolvedField` を作成し、各圃場の作物をスナップショットとして保持する
|
||||
- **グループ内に複数作物が混在する場合**、同一の散布イベント・散布農薬でも作物ごとに使用回数がカウントされる。例:グループ内に「水稲」3筆・「大豆」1筆が含まれる場合、そのイベントの農薬は水稲の回数にも大豆の回数にも +1 される
|
||||
- 使用回数上限の照合は、`SprayEventResolvedField.crop_name_snapshot` → `PesticideCropAlias` → `PesticideProductLimit` / `PesticideIngredientLimit` の順に行う
|
||||
|
||||
### 製品使用回数の集計
|
||||
|
||||
1イベント = 1散布作業 = 1回。`unique_together=['event', 'pesticide']` により同一イベント内で同一農薬は1行しか存在しないため、イベント単位でカウントして正確。
|
||||
|
||||
```
|
||||
製品使用回数 = COUNT(SprayEventPesticide where pesticide=X)
|
||||
で year × 作物 でフィルタしたもの
|
||||
製品使用回数(年度Y・作物C・農薬P)=
|
||||
COUNT(DISTINCT SprayEvent.id)
|
||||
where SprayEvent に SprayEventPesticide(pesticide=P) が紐づく
|
||||
かつ SprayEvent に SprayEventResolvedField(crop_name_snapshot=C) が紐づく
|
||||
かつ SprayEvent.year = Y
|
||||
```
|
||||
|
||||
※ 1イベントで複数圃場に散布しても「1回」とカウントする(1イベント=1散布作業)
|
||||
|
||||
### 有効成分総使用回数の集計
|
||||
|
||||
1回の散布作業(イベント)= 有効成分の使用回数1回。同一成分を含む複数製品を同一イベントで施用することは実務上なく、仮に混合散布しても農薬取締法上「1回の散布 = 1回の使用」と解釈される。
|
||||
|
||||
```
|
||||
有効成分総使用回数 = COUNT(SprayEventPesticide)
|
||||
where SprayEventPesticide.pesticide に
|
||||
当該有効成分(PesticideIngredient.name)が含まれる
|
||||
かつ is_non_target=False
|
||||
で year × 作物 でフィルタしたもの
|
||||
有効成分総使用回数(年度Y・作物C・成分名I)=
|
||||
COUNT(DISTINCT SprayEvent.id)
|
||||
where SprayEvent に SprayEventPesticide が紐づく
|
||||
かつ SprayEventPesticide.pesticide の PesticideIngredient に
|
||||
name=I かつ is_active=True のものが存在する
|
||||
かつ SprayEventPesticide.pesticide.is_non_target=False
|
||||
かつ SprayEvent に SprayEventResolvedField(crop_name_snapshot=C) が紐づく
|
||||
かつ SprayEvent.year = Y
|
||||
```
|
||||
|
||||
※ `SprayEventResolvedField` は圃場ごとに複数行あるため、結合で行が増えても `DISTINCT SprayEvent.id` で 1散布作業を1回だけ数える
|
||||
|
||||
### 特別栽培・使用成分数の集計
|
||||
|
||||
```
|
||||
使用成分数 = COUNT(DISTINCT PesticideIngredient.name)
|
||||
where 当該年度×作物の散布イベントで使用された農薬の有効成分
|
||||
かつ pesticide.is_non_target=False
|
||||
使用成分数(年度Y・作物C)=
|
||||
COUNT(DISTINCT PesticideIngredient.name)
|
||||
where 上記条件(年度Y・作物C)の散布イベントで使用された農薬に含まれる
|
||||
かつ PesticideIngredient.is_active=True
|
||||
かつ SprayEventPesticide.pesticide.is_non_target=False
|
||||
```
|
||||
|
||||
---
|
||||
@@ -243,9 +316,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 +326,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 +435,15 @@
|
||||
"target_type": "group",
|
||||
"target_group": "田中エリア",
|
||||
"target_display": "田中エリア(グループ)",
|
||||
"resolved_fields": [
|
||||
{
|
||||
"field": 5,
|
||||
"field_name_snapshot": "田中上",
|
||||
"group_name_snapshot": "田中エリア",
|
||||
"crop_name_snapshot": "水稲",
|
||||
"variety_name_snapshot": "コシヒカリ"
|
||||
}
|
||||
],
|
||||
"notes": "曇り、風弱し",
|
||||
"pesticides": [
|
||||
{
|
||||
@@ -373,7 +470,8 @@
|
||||
{
|
||||
"year": 2026,
|
||||
"crop_id": 1,
|
||||
"crop_name": "稲",
|
||||
"crop_name": "水稲",
|
||||
"crop_aliases": ["稲", "水稲"],
|
||||
"product_usage": [
|
||||
{
|
||||
"pesticide_id": 1,
|
||||
@@ -454,7 +552,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` |
|
||||
|
||||
**「総使用回数」テキストのパース規則:**
|
||||
|
||||
@@ -466,6 +564,11 @@ URL: `https://pesticide.maff.go.jp/`
|
||||
正規表現: r'(\d+)回以内(?:\((.+)\))?'
|
||||
```
|
||||
|
||||
**整合性チェック:**
|
||||
|
||||
- 同一 `ingredient_name + crop_name` に対して既存の `PesticideIngredientLimit.max_total_uses` と異なる値が取得された場合、その農薬の自動取込はエラーとし、手動確認を促す
|
||||
- `use_timing_note` の差異は許容し、より詳細なテキストで上書きしてよい
|
||||
|
||||
### 実装場所
|
||||
|
||||
`apps/pesticide/management/commands/fetch_pesticide.py`
|
||||
@@ -477,6 +580,7 @@ Django management command として実装。APIエンドポイントから呼び
|
||||
- アクセスは農薬マスタ登録時の1件ずつに限定(バルク取得は行わない)
|
||||
- 農水省サイトの内部ID(`system_id`)と農薬の公式登録番号は別物
|
||||
- タイムアウト: 10秒
|
||||
- 適用表の作物名は `PesticideCropAlias` で内部 `Crop` と対応付ける前提で保存する
|
||||
|
||||
---
|
||||
|
||||
@@ -507,12 +611,17 @@ 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保存は別フィールド)。
|
||||
1. **散布対象の特定**: `target_type` + 対象FK/文字列で柔軟に対応。保存時に `SprayEventResolvedField` で対象圃場と作物を確定保存する。作付け計画(Plan)はあくまで保存時の解決に使うだけで、集計の正源ではない。
|
||||
2. **使用回数上限は作物別に保持**: 同一農薬でも作物ごとに上限が異なるため `PesticideProductLimit` と `PesticideIngredientLimit` を作物別に複数行保持する。
|
||||
3. **作物名の照合は別名テーブルで吸収**: 農水省表記の「稲」と内部の「水稲」のような差異を吸収するため、`PesticideCropAlias` を必須とする。
|
||||
4. **散布対象は保存時に確定保存する**: 後日のグループ名変更や作付け変更で過去実績の集計結果が変わらないよう、`SprayEventResolvedField` に圃場・作物をスナップショット保存する。`SprayEvent` 自体には作物情報を持たない。
|
||||
5. **有効成分総使用回数も「1イベント=1回」**: 同一成分を含む複数製品を同一イベントで施用することは実務上なく、仮に混合散布しても農薬取締法上「1回の散布=1回の使用」。製品使用回数と同様に `COUNT(DISTINCT SprayEvent.id)` で集計する。`SprayEventResolvedField` との結合で行が増えても `DISTINCT` で正確にカウントできる。
|
||||
6. **総使用回数はテキストパース**: 農水省サイトの「○○を含む農薬の総使用回数」カラムから正規表現で数値を抽出する。
|
||||
7. **有効成分上限の整合性は保存時に保証する**: 同一 `ingredient_name + crop_name` の `max_total_uses` は製品をまたいで一致している前提とし、異なる値を保存しようとした場合はエラーにする。
|
||||
8. **保存はブロックしない**: 使用回数超過は警告表示のみ。農薬散布の記録は法的義務があるため、超過でも保存できるようにする。
|
||||
9. **`SprayEventPesticide.pesticide` は PROTECT**: 散布記録に使用中の農薬は削除不可。
|
||||
10. **成分集計は `is_active=True` のみ対象**: 「その他成分」は総使用回数・特別栽培の成分数集計に含めない。
|
||||
11. **`is_spreader=True` は `is_non_target` 扱い**: 展着剤はカウント除外のため、展着剤フラグをセットすれば節減対象外フラグも自動的に True 扱い(DB保存は別フィールド)。
|
||||
|
||||
---
|
||||
|
||||
@@ -520,7 +629,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 ルーティング |
|
||||
|
||||
Reference in New Issue
Block a user