# マスタードキュメント:農薬散布管理機能 > **作成**: 2026-04-09 > **最終更新**: 2026-04-09 > **対象機能**: 農薬散布管理(農薬マスタ・散布記録・使用回数チェック・特別栽培向け成分数集計) > **実装状況**: 未着手(仕様確定済み) > **Gitea Issue**: akira/keinasystem#18 --- ## 概要 農業生産者が散布した農薬を記録・管理し、農薬取締法に基づく使用基準(製品ごと・有効成分ごとの使用回数制限)への適合確認と、特別栽培認証用の成分数集計を行う機能。 ### 機能スコープ(IN / OUT) | IN(実装対象) | OUT(対象外) | |---|---| | 農薬マスタ管理(CRUD) | 農薬の在庫管理・購入管理 | | 農林水産省サイトからの農薬情報自動取得 | 農薬費用の管理 | | 散布イベント記録(圃場/グループ/作物/品種対象) | 希釈液の量管理 | | 製品ごとの使用回数チェック(年度×作物) | 農薬の廃棄記録 | | 有効成分ごとの総使用回数チェック(年度×作物) | 農薬散布マップ(GIS) | | 特別栽培用:節減対象農薬の使用成分数集計 | 農薬の処方箋・防除暦の自動作成 | | 回数超過アラート表示 | | --- ## 使用回数カウントのルール 農薬の使用回数は **製品単位** と **有効成分単位** の2軸で管理する。 ### ルール1:製品ごとの使用回数 農薬製品(例: 住化スミチオン乳剤)を1シーズンに使用した回数 ≤ 登録情報の「本剤の使用回数」上限。 ### ルール2:有効成分ごとの総使用回数 同一有効成分を含む複数製品を使用した場合、その有効成分の総使用回数として合算カウントする。 ``` 例)「MEP乳剤A(上限3回)」と「MEP乳剤B(上限3回)」、MEP成分の総上限3回 → A剤2回 + B剤1回 = 合計3回 → OK → A剤2回 + B剤2回 = 合計4回 → 超過! ``` ### ルール3:使用時期別カウント 育苗期・本圃期など時期別に別カウントになる場合がある(登録情報のテキストとして記録)。 システムでは現フェーズで時期別の自動判定は行わず、登録情報テキストを参照情報として表示する。 ### カウント対象外農薬(節減対象外) 以下の農薬は使用回数・成分数のカウントから除外する(`is_non_target` フラグで管理): - 展着剤(`is_spreader` フラグでも管理) - 有機JAS別表2に掲げる農薬(除虫菊乳剤・硫黄剤・天敵生物農薬・性フェロモン剤等) - 化学合成でないと認められた農薬(カスガマイシン剤・ポリオキシン剤・バリダマイシン剤等) ### 特別栽培向け成分数集計 「節減対象農薬(`is_non_target=False`)の有効成分が何種類使われたか」を年度×作物単位でカウントする。 上限はなく、報告用の集計値として表示する。 --- ## データモデル ### Pesticide(農薬マスタ) **アプリ**: `apps/pesticide` **テーブル名**: `pesticide_pesticide` | フィールド | 型 | 制約 | 説明 | |---|---|---|---| | id | BigAutoField | PK | | | name | CharField(200) | required | 農薬名(例: 住化スミチオン乳剤) | | pesticide_type | CharField(100) | blank | 農薬の種類(例: MEP乳剤) | | registration_number | CharField(20) | blank | 農薬登録番号(公式登録番号) | | system_id | CharField(20) | blank | 農水省サイトの内部ID(詳細URLに使用) | | purpose | CharField(100) | blank | 用途(例: 殺虫剤) | | formulation | CharField(100) | blank | 剤型(例: 乳剤) | | toxicity | CharField(20) | blank | 製剤毒性(普/毒/劇等) | | is_spreader | BooleanField | default=False | 展着剤フラグ | | is_non_target | BooleanField | default=False | 節減対象外フラグ(カウント除外) | | notes | TextField | blank | 備考 | | fetched_at | DateTimeField | null=True | 農水省サイトからの最終取得日時 | | created_at | DateTimeField | auto | | | updated_at | DateTimeField | auto | | - `name` は unique 制約なし(同名で複数登録番号が存在しうる) - `is_spreader=True` の場合、`is_non_target` も自動的に `True` 扱いとする ### PesticideIngredient(有効成分) **テーブル名**: `pesticide_pesticideingredient` | フィールド | 型 | 制約 | 説明 | |---|---|---|---| | id | BigAutoField | PK | | | pesticide | FK(Pesticide) | CASCADE | | | name | CharField(200) | required | 成分名称(例: MEP) | | concentration | CharField(100) | blank | 含有濃度(例: 50.0%) | | is_active | BooleanField | default=True | 有効成分かどうか(False = その他成分) | | max_total_uses | IntegerField | null=True | この成分を含む農薬の総使用回数上限 | | use_timing_note | TextField | blank | 使用時期別制限のテキスト(例: 種もみへの処理は1回以内、…) | - `unique_together = ['pesticide', 'name']` ### PesticideProductLimit(製品の使用回数上限:作物別) **テーブル名**: `pesticide_pesticideproductlimit` 農水省の適用表は作物ごとに上限が異なるため、作物名をキーとして保存する。 | フィールド | 型 | 制約 | 説明 | |---|---|---|---| | id | BigAutoField | PK | | | pesticide | FK(Pesticide) | CASCADE | | | crop_name | CharField(200) | required | 作物名(農水省登録情報の表記、例: 稲) | | max_uses | IntegerField | required | 本剤の使用回数上限 | | use_timing_note | TextField | blank | 使用時期・条件の補足テキスト | - `unique_together = ['pesticide', 'crop_name']` ### SprayEvent(散布イベント) **テーブル名**: `pesticide_sprayevent` 1回の散布作業を1件として記録する。 | フィールド | 型 | 制約 | 説明 | |---|---|---|---| | id | BigAutoField | PK | | | year | IntegerField | required | 年度(集計フィルタ用) | | date | DateField | required | 散布日 | | target_type | CharField(20) | required | 対象種別: `field` / `group` / `crop` / `variety` | | target_field | FK(fields.Field) | null=True, PROTECT | 対象が圃場の場合 | | target_group | CharField(50) | blank | 対象が圃場グループの場合(group_name) | | target_crop | FK(plans.Crop) | null=True, PROTECT | 対象が作物の場合 | | target_variety | FK(plans.Variety) | null=True, PROTECT | 対象が品種の場合 | | notes | TextField | blank | 備考 | | created_at | DateTimeField | auto | | | updated_at | DateTimeField | auto | | #### target_type 別のバリデーション | target_type | 必須フィールド | 意味 | |---|---|---| | `field` | target_field | 特定の圃場1筆に散布 | | `group` | target_group | 同一 group_name の全圃場に散布 | | `crop` | target_crop | 特定の作物に対して散布(作付け計画と照合) | | `variety` | target_variety | 特定の品種に対して散布(作付け計画と照合) | ### SprayEventPesticide(散布農薬明細) **テーブル名**: `pesticide_sprayeventpesticide` 1つの散布イベントに複数農薬を紐づける。 | フィールド | 型 | 制約 | 説明 | |---|---|---|---| | id | BigAutoField | PK | | | event | FK(SprayEvent) | CASCADE | | | pesticide | FK(Pesticide) | PROTECT | 使用農薬 | | dilution_ratio | CharField(50) | blank | 希釈倍率(例: 1000倍) | | amount_used | CharField(50) | blank | 使用量(例: 500mL、単位込みで自由記述) | | notes | TextField | blank | 備考 | - `pesticide` は PROTECT(使用済み農薬は削除不可) --- ## 使用回数集計の仕組み ### 集計単位 **年度 × 作物** を基本単位とする。 - `target_type=field`/`group` の場合: そのイベントの年度の作付け計画(Plan)から作物を特定 - `target_type=crop` の場合: target_crop が直接作物 - `target_type=variety` の場合: target_variety の crop が作物 ### 製品使用回数の集計 ``` 製品使用回数 = COUNT(SprayEventPesticide where pesticide=X) で year × 作物 でフィルタしたもの ``` ### 有効成分総使用回数の集計 ``` 有効成分総使用回数 = COUNT(SprayEventPesticide) where SprayEventPesticide.pesticide に 当該有効成分(PesticideIngredient.name)が含まれる かつ is_non_target=False で year × 作物 でフィルタしたもの ``` ### 特別栽培・使用成分数の集計 ``` 使用成分数 = COUNT(DISTINCT PesticideIngredient.name) where 当該年度×作物の散布イベントで使用された農薬の有効成分 かつ pesticide.is_non_target=False ``` --- ## API エンドポイント すべて JWT 認証(`Authorization: Bearer `)が必要。 ### 農薬マスタ | メソッド | URL | 説明 | |---|---|---| | GET | `/api/pesticide/pesticides/` | 一覧取得 | | POST | `/api/pesticide/pesticides/` | 新規作成 | | GET | `/api/pesticide/pesticides/{id}/` | 詳細取得 | | PUT/PATCH | `/api/pesticide/pesticides/{id}/` | 更新 | | DELETE | `/api/pesticide/pesticides/{id}/` | 削除(使用中は 400) | | POST | `/api/pesticide/pesticides/fetch/` | 農水省サイトから情報取得 | 農薬マスタ レスポンス例: ```json { "id": 1, "name": "住化スミチオン乳剤", "pesticide_type": "MEP乳剤", "registration_number": "4962", "system_id": "4962", "purpose": "殺虫剤", "formulation": "乳剤", "toxicity": "普", "is_spreader": false, "is_non_target": false, "notes": "", "fetched_at": "2026-04-09T10:00:00Z", "ingredients": [ { "id": 1, "name": "MEP", "concentration": "50.0%", "is_active": true, "max_total_uses": 3, "use_timing_note": "種もみへの処理は1回以内、育苗箱散布は1回以内、本田では2回以内" } ], "product_limits": [ { "id": 1, "crop_name": "稲", "max_uses": 2, "use_timing_note": "収穫21日前まで" } ] } ``` #### `POST /api/pesticide/pesticides/fetch/` 農水省農薬登録情報提供システムから農薬情報を取得してマスタに保存する。 取得に失敗した場合は `fetch_error` を返し、手動入力に切り替える。 リクエスト: ```json { "name": "スミチオン" } ``` レスポンス(成功): ```json { "status": "ok", "candidates": [ { "system_id": "4962", "name": "住化スミチオン乳剤", "pesticide_type": "MEP乳剤", "registration_number": "4962" }, { "system_id": "4991", "name": "ホクコースミチオン乳剤", "pesticide_type": "MEP乳剤", "registration_number": "4991" } ] } ``` 候補が複数ある場合はフロントで選択させ、選択後に詳細取得リクエストを投げる: ```json { "system_id": "4962" } ``` レスポンス(失敗): ```json { "status": "error", "message": "農林水産省サイトへの接続に失敗しました。手動で入力してください。" } ``` ### 散布イベント | メソッド | URL | 説明 | |---|---|---| | GET | `/api/pesticide/events/?year={year}` | 年度別一覧 | | POST | `/api/pesticide/events/` | 新規作成 | | GET | `/api/pesticide/events/{id}/` | 詳細取得 | | PUT/PATCH | `/api/pesticide/events/{id}/` | 更新 | | DELETE | `/api/pesticide/events/{id}/` | 削除 | 散布イベント POST リクエスト例(圃場グループを対象に複数農薬散布): ```json { "year": 2026, "date": "2026-05-10", "target_type": "group", "target_group": "田中エリア", "notes": "曇り、風弱し", "pesticides": [ { "pesticide": 1, "dilution_ratio": "1000倍", "amount_used": "500mL" }, { "pesticide": 3, "dilution_ratio": "2000倍", "amount_used": "200mL" } ] } ``` 散布イベント レスポンス例: ```json { "id": 10, "year": 2026, "date": "2026-05-10", "target_type": "group", "target_group": "田中エリア", "target_display": "田中エリア(グループ)", "notes": "曇り、風弱し", "pesticides": [ { "id": 15, "pesticide": 1, "pesticide_name": "住化スミチオン乳剤", "dilution_ratio": "1000倍", "amount_used": "500mL" } ], "created_at": "2026-04-09T10:00:00Z", "updated_at": "2026-04-09T10:00:00Z" } ``` ### 使用回数チェック #### `GET /api/pesticide/usage-summary/?year={year}&crop_id={crop_id}` 年度×作物単位で使用回数の集計・チェック結果を返す。 レスポンス例: ```json { "year": 2026, "crop_id": 1, "crop_name": "稲", "product_usage": [ { "pesticide_id": 1, "pesticide_name": "住化スミチオン乳剤", "used_count": 2, "max_uses": 2, "remaining": 0, "is_over": false } ], "ingredient_usage": [ { "ingredient_name": "MEP", "used_count": 2, "max_total_uses": 3, "remaining": 1, "is_over": false, "products_used": ["住化スミチオン乳剤"] } ], "component_count": 2, "has_violation": false } ``` --- ## 農水省サイトスクレイピング仕様 ### 対象サイト 農林水産省 農薬登録情報提供システム URL: `https://pesticide.maff.go.jp/` ### アクセスフロー ``` 1. GET /agricultural-chemicals/name-search/ → JSESSIONID クッキー + CSRF トークン(フォーム埋め込み)取得 2. POST /agricultural-chemicals/name-search Content-Type: application/x-www-form-urlencoded Body: _csrf=&agriculturalChemicalsName=<農薬名>&agriculturalChemicalsType= → 302 リダイレクト先: /agricultural-chemicals/list 3. GET /agricultural-chemicals/list → 検索結果一覧 HTML → からリンク抽出 4. GET /agricultural-chemicals/details/{system_id} → 詳細ページ HTML → 下記データをパース ``` ### 詳細ページ パース項目 **基本情報テーブル(`th[scope=col]` + `td` ペア):** | th テキスト | 取得項目 | 保存先 | |---|---|---| | 登録番号 | 登録番号 | `registration_number` | | 農薬の種類 | 種類名 | `pesticide_type` | | 農薬の名称 | 農薬名 | `name` | | 用途 | 用途 | `purpose` | | 剤型 | 剤型 | `formulation` | | 製剤毒性 | 毒性区分 | `toxicity` | **有効成分テーブル:** - 「有効成分」行: `is_active=True`、成分名・含有濃度を取得 - 「その他成分」行: `is_active=False` **適用表(作物×病害虫ごとの行):** 各行のカラム(`data-label` 属性でカラム識別): | data-label | 取得項目 | 保存先 | |---|---|---| | 作物名 | 作物名 | `PesticideProductLimit.crop_name` | | 本剤の使用回数 | 「N回以内」から N を抽出 | `PesticideProductLimit.max_uses` | | 使用時期 | テキストそのまま | `PesticideProductLimit.use_timing_note` | | `{成分名}を含む農薬の総使用回数` | 「N回以内(...)」から N と補足を抽出 | `PesticideIngredient.max_total_uses` / `use_timing_note` | **「総使用回数」テキストのパース規則:** ``` 入力例: "3回以内(種もみへの処理は1回以内、育苗箱散布は1回以内、本田では2回以内)" → max_total_uses = 3 → use_timing_note = "種もみへの処理は1回以内、育苗箱散布は1回以内、本田では2回以内" 正規表現: r'(\d+)回以内(?:\((.+)\))?' ``` ### 実装場所 `apps/pesticide/management/commands/fetch_pesticide.py` Django management command として実装。APIエンドポイントから呼び出す。 ### 注意事項 - セッション(`requests.Session`)を使用し、クッキーとCSRFを維持する - アクセスは農薬マスタ登録時の1件ずつに限定(バルク取得は行わない) - 農水省サイトの内部ID(`system_id`)と農薬の公式登録番号は別物 - タイムアウト: 10秒 --- ## 画面仕様 ### 農薬マスタ画面(`/pesticide/`) - 登録済み農薬の一覧表示 - 農薬名で検索 → 農水省サイトから候補を取得 → 選択して詳細取得 → 保存 - 取得失敗時は手動入力フォームに切り替え - 展着剤フラグ・節減対象外フラグの編集 ### 散布記録入力画面(`/pesticide/events/new`) - 散布日・年度入力 - 対象種別(圃場/グループ/作物/品種)選択 → 対象を選択 - 農薬を追加(複数可): 農薬マスタから選択 + 希釈倍率 + 使用量 - 保存時に使用回数チェックを実行し、超過がある場合は警告を表示(保存はブロックしない) ### 使用回数チェック画面(`/pesticide/usage`) - 年度・作物でフィルタ - **製品使用回数テーブル**: 農薬名 / 使用回数 / 上限 / 残回数(超過時は赤表示) - **有効成分総使用回数テーブル**: 成分名 / 使用回数 / 上限 / 残回数 / 使用製品一覧(超過時は赤表示) - **特別栽培欄**: 節減対象農薬の使用成分数(報告用) --- ## 設計判断と制約 1. **散布対象の特定**: `target_type` + 対象FK/文字列で柔軟に対応。作物ごとの集計は作付け計画(Plan)と照合。 2. **使用回数上限は作物別に保持**: 同一農薬でも稲と野菜で上限が異なるため `PesticideProductLimit` を作物別に複数行保持。 3. **総使用回数はテキストパース**: 農水省サイトの「○○を含む農薬の総使用回数」カラムから正規表現で数値を抽出。 4. **保存はブロックしない**: 使用回数超過は警告表示のみ。農薬散布の記録は法的義務があるため、超過でも保存できるようにする。 5. **`SprayEventPesticide.pesticide` は PROTECT**: 散布記録に使用中の農薬は削除不可。 6. **`is_spreader=True` は `is_non_target` 扱い**: 展着剤はカウント除外のため、展着剤フラグをセットすれば節減対象外フラグも自動的に True 扱い(DB保存は別フィールド)。 --- ## ソースファイル索引(実装後に更新) | ファイル | 説明 | |---|---| | `backend/apps/pesticide/models.py` | Pesticide, PesticideIngredient, PesticideProductLimit, SprayEvent, SprayEventPesticide | | `backend/apps/pesticide/serializers.py` | DRF シリアライザ | | `backend/apps/pesticide/views.py` | ViewSet | | `backend/apps/pesticide/urls.py` | URL ルーティング | | `backend/apps/pesticide/management/commands/fetch_pesticide.py` | 農水省スクレイパー | | `frontend/src/app/pesticide/page.tsx` | 農薬マスタ一覧・散布記録 | | `frontend/src/app/pesticide/usage/page.tsx` | 使用回数チェック画面 | | `frontend/src/lib/types.ts` | 型定義(Pesticide, SprayEvent 等) |