同一イベント内で同じ有効成分を含む複数製品を使った場合、総使用回数を過少計上します。
18_マスタードキュメント_農薬散布管理編.md:39 (line 39) では「同一有効成分を含む複数製品は合算カウント」と定義していますが、集計式は 同:251 (line 251) の COUNT(DISTINCT SprayEvent.id) です。これだと 1 回の散布で MEP剤A と MEP剤B を同時使用したケースが 2 回ではなく 1 回になります。1イベント=1回 は製品単位には合っても、有効成分の「複数製品合算」とは衝突しています。

SprayEventResolvedField を正源にしたはずなのに、設計判断がまだ旧仕様のままで矛盾しています。
集計の正源は 同:232 (line 232) で SprayEventResolvedField.crop_name_snapshot に統一されていますが、設計判断では 同:599 (line 599) に「作付け計画(Plan)と照合」と残っています。さらに 同:602 (line 602) では削除したはずの crop_snapshot / variety_snapshot をまだ保持対象として書いています。実装者がここを読むと旧設計に引っ張られます。

製品使用回数も、同一イベント内の重複明細をどう扱うかが未定義で、式とモデルが噛み合っていません。
集計式は 同:239 (line 239) の COUNT(DISTINCT SprayEvent.id) ですが、明細モデルには 同:213 (line 213) 以降で event + pesticide の一意制約がありません。つまり同じ農薬を同一イベントに 2 行入れられる設計なのに、集計では 1 回に潰れます。仕様として「同一イベント内で同一農薬は1回しか登録できない」を明記して一意制約を持たせるか、重複明細の意味を定義した方が安全です。

大筋ではかなり良くなっていて、特に「作物単位での法的管理」と「圃場ごとの正源を SprayEventResolvedField に寄せた」方向は明快でした。上の3点だけ揃えると、実装時の解釈ぶれがかなり減ります。
This commit is contained in:
akira
2026-04-09 15:16:45 +09:00
parent 3e2942b479
commit 7d2eb1ebe2

View File

@@ -220,6 +220,7 @@
| notes | TextField | blank | 備考 |
- `pesticide` は PROTECT使用済み農薬は削除不可
- `unique_together = ['event', 'pesticide']`同一イベント内で同じ農薬を2回登録不可
---
@@ -236,6 +237,8 @@
### 製品使用回数の集計
1イベント = 1散布作業 = 1回。`unique_together=['event', 'pesticide']` により同一イベント内で同一農薬は1行しか存在しないため、イベント単位でカウントして正確。
```
製品使用回数年度Y・作物C・農薬P=
COUNT(DISTINCT SprayEvent.id)
@@ -244,21 +247,24 @@
かつ SprayEvent.year = Y
```
※ 1イベントで複数圃場に散布しても、そのイベントは「1回」とカウントする1イベント=1散布作業
※ 1イベントで複数圃場に散布しても「1回」とカウントする1イベント=1散布作業
### 有効成分総使用回数の集計
有効成分の総使用回数は「製品」単位でカウントする農薬取締法の「○○を含む農薬の総使用回数」の定義に従う。1イベントでMEP剤AとMEP剤Bを同時使用した場合、MEP成分は **2回** カウントされる。
```
有効成分総使用回数年度Y・作物C・成分名I=
COUNT(DISTINCT SprayEvent.id)
where SprayEvent に SprayEventPesticide が紐づく
かつ SprayEventPesticide.pesticide の PesticideIngredient に
COUNT(SprayEventPesticide)
where SprayEventPesticide.pesticide の PesticideIngredient に
name=I かつ is_active=True のものが存在する
かつ SprayEventPesticide.pesticide.is_non_target=False
かつ SprayEvent に SprayEventResolvedField(crop_name_snapshot=C) が紐づく
かつ SprayEvent.year = Y
かつ SprayEventPesticide.event に SprayEventResolvedField(crop_name_snapshot=C) が紐づく
かつ SprayEventPesticide.event.year = Y
```
`unique_together=['event', 'pesticide']` により、同一イベント内で同一農薬は重複しないため単純な COUNT で正確に合算できる
### 特別栽培・使用成分数の集計
```
@@ -596,15 +602,16 @@ Django management command として実装。APIエンドポイントから呼び
## 設計判断と制約
1. **散布対象の特定**: `target_type` + 対象FK/文字列で柔軟に対応。作物ごとの集計は作付け計画Planと照合
1. **散布対象の特定**: `target_type` + 対象FK/文字列で柔軟に対応。保存時に `SprayEventResolvedField` で対象圃場と作物を確定保存する。作付け計画Planはあくまで保存時の解決に使うだけで、集計の正源ではない
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保存は別フィールド
4. **散布対象は保存時に確定保存する**: 後日のグループ名変更や作付け変更で過去実績の集計結果が変わらないよう、`SprayEventResolvedField` に圃場・作物をスナップショット保存する。`SprayEvent` 自体には作物情報を持たない
5. **有効成分総使用回数は `SprayEventPesticide` 単位で集計する**: 1イベントでMEP剤AとMEP剤Bを同時使用した場合、MEP成分は2回とカウントする。`unique_together=['event', 'pesticide']` で重複行を防ぎ、単純な COUNT で正確に計算できる。
6. **総使用回数はテキストパース**: 農水省サイトの「○○を含む農薬の総使用回数」カラムから正規表現で数値を抽出する。
7. **保存はブロックしない**: 使用回数超過は警告表示のみ。農薬散布の記録は法的義務があるため、超過でも保存できるようにする
8. **`SprayEventPesticide.pesticide` は PROTECT**: 散布記録に使用中の農薬は削除不可
9. **成分集計は `is_active=True` のみ対象**: 「その他成分」は総使用回数・特別栽培の成分数集計に含めない
10. **`is_spreader=True``is_non_target` 扱い**: 展着剤はカウント除外のため、展着剤フラグをセットすれば節減対象外フラグも自動的に True 扱いDB保存は別フィールド
---