Files
keinasystem/document/18_マスタードキュメント_農薬散布管理編.md

26 KiB
Raw Permalink Blame History

マスタードキュメント:農薬散布管理機能

作成: 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)の有効成分(is_active=True)が何種類使われたか」を年度×作物単位でカウントする。 上限はなく、報告用の集計値として表示する。


データモデル

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 = その他成分)
  • 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', 'ingredient_name', 'crop_name']
  • 同一成分・同一作物であれば製品が異なっても上限値は同一(農水省登録情報の仕様)
  • 保存時バリデーション: 同一 ingredient_name + crop_name の既存レコードと異なる max_total_uses を保存しようとした場合はエラーにする
  • 使用回数チェック API の ingredient_usage.max_total_uses は、同一 ingredient_name + crop_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']

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

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 特定の品種に対して散布(作付け計画と照合)
  • 保存時に全対象圃場を 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

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使用済み農薬は削除不可
  • unique_together = ['event', 'pesticide']同一イベント内で同じ農薬を2回登録不可

使用回数集計の仕組み

集計単位

年度 × 作物 を基本単位とする(農薬取締法上、使用回数は作物単位で管理する義務がある)。

  • 集計対象作物は SprayEventResolvedField.crop_name_snapshot を正とする(圃場ごとに記録)
  • target_type=field/group/crop/variety の違いにかかわらず、保存時に全対象圃場の SprayEventResolvedField を作成し、各圃場の作物をスナップショットとして保持する
  • グループ内に複数作物が混在する場合、同一の散布イベント・散布農薬でも作物ごとに使用回数がカウントされる。例グループ内に「水稲」3筆・「大豆」1筆が含まれる場合、そのイベントの農薬は水稲の回数にも大豆の回数にも +1 される
  • 使用回数上限の照合は、SprayEventResolvedField.crop_name_snapshotPesticideCropAliasPesticideProductLimit / PesticideIngredientLimit の順に行う

製品使用回数の集計

1イベント = 1散布作業 = 1回。unique_together=['event', 'pesticide'] により同一イベント内で同一農薬は1行しか存在しないため、イベント単位でカウントして正確。

製品使用回数年度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回の使用」と解釈される。

有効成分総使用回数年度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回だけ数える

特別栽培・使用成分数の集計

使用成分数年度Y・作物C=
  COUNT(DISTINCT PesticideIngredient.name)
  where 上記条件年度Y・作物Cの散布イベントで使用された農薬に含まれる
  かつ PesticideIngredient.is_active=True
  かつ SprayEventPesticide.pesticide.is_non_target=False

API エンドポイント

すべて JWT 認証(Authorization: Bearer <token>)が必要。

農薬マスタ

メソッド 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/ 農水省サイトから情報取得

農薬マスタ レスポンス例:

{
  "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
    }
  ],
  "product_limits": [
    {
      "id": 1,
      "crop_name": "稲",
      "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
    }
  ]
}

POST /api/pesticide/pesticides/fetch/

農水省農薬登録情報提供システムから農薬情報を取得してマスタに保存する。 取得に失敗した場合は fetch_error を返し、手動入力に切り替える。

リクエスト:

{
  "name": "スミチオン"
}

レスポンス(成功):

{
  "status": "ok",
  "candidates": [
    {
      "system_id": "4962",
      "name": "住化スミチオン乳剤",
      "pesticide_type": "MEP乳剤",
      "registration_number": "4962"
    },
    {
      "system_id": "4991",
      "name": "ホクコースミチオン乳剤",
      "pesticide_type": "MEP乳剤",
      "registration_number": "4991"
    }
  ]
}

候補が複数ある場合はフロントで選択させ、選択後に詳細取得リクエストを投げる:

{ "system_id": "4962" }

レスポンス(失敗):

{
  "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 リクエスト例(圃場グループを対象に複数農薬散布):

{
  "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"
    }
  ]
}

散布イベント レスポンス例:

{
  "id": 10,
  "year": 2026,
  "date": "2026-05-10",
  "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": [
    {
      "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}

年度×作物単位で使用回数の集計・チェック結果を返す。

レスポンス例:

{
  "year": 2026,
  "crop_id": 1,
  "crop_name": "水稲",
  "crop_aliases": ["稲", "水稲"],
  "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=<token>&agriculturalChemicalsName=<農薬名>&agriculturalChemicalsType=
   → 302 リダイレクト先: /agricultural-chemicals/list

3. GET /agricultural-chemicals/list
   → 検索結果一覧 HTML
   → <a href="/agricultural-chemicals/details/{system_id}"> からリンク抽出

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 と補足を抽出 PesticideIngredientLimit.max_total_uses / use_timing_note

「総使用回数」テキストのパース規則:

入力例: "3回以内(種もみへの処理は1回以内、育苗箱散布は1回以内、本田では2回以内)"
→ max_total_uses = 3
→ use_timing_note = "種もみへの処理は1回以内、育苗箱散布は1回以内、本田では2回以内"

正規表現: r'(\d+)回以内(?:\((.+)\))?'

整合性チェック:

  • 同一 ingredient_name + crop_name に対して既存の PesticideIngredientLimit.max_total_uses と異なる値が取得された場合、その農薬の自動取込はエラーとし、手動確認を促す
  • use_timing_note の差異は許容し、より詳細なテキストで上書きしてよい

実装場所

apps/pesticide/management/commands/fetch_pesticide.py
Django management command として実装。APIエンドポイントから呼び出す。

注意事項

  • セッション(requests.Sessionを使用し、クッキーとCSRFを維持する
  • アクセスは農薬マスタ登録時の1件ずつに限定バルク取得は行わない
  • 農水省サイトの内部IDsystem_id)と農薬の公式登録番号は別物
  • タイムアウト: 10秒
  • 適用表の作物名は PesticideCropAlias で内部 Crop と対応付ける前提で保存する

画面仕様

農薬マスタ画面(/pesticide/

  • 登録済み農薬の一覧表示
  • 農薬名で検索 → 農水省サイトから候補を取得 → 選択して詳細取得 → 保存
  • 取得失敗時は手動入力フォームに切り替え
  • 展着剤フラグ・節減対象外フラグの編集

散布記録入力画面(/pesticide/events/new

  • 散布日・年度入力
  • 対象種別(圃場/グループ/作物/品種)選択 → 対象を選択
  • 農薬を追加(複数可): 農薬マスタから選択 + 希釈倍率 + 使用量
  • 保存時に使用回数チェックを実行し、超過がある場合は警告を表示(保存はブロックしない)

使用回数チェック画面(/pesticide/usage

  • 年度・作物でフィルタ
  • 製品使用回数テーブル: 農薬名 / 使用回数 / 上限 / 残回数(超過時は赤表示)
  • 有効成分総使用回数テーブル: 成分名 / 使用回数 / 上限 / 残回数 / 使用製品一覧(超過時は赤表示)
  • 特別栽培欄: 節減対象農薬の使用成分数(報告用)

設計判断と制約

  1. 散布対象の特定: target_type + 対象FK/文字列で柔軟に対応。保存時に SprayEventResolvedField で対象圃場と作物を確定保存する。作付け計画Planはあくまで保存時の解決に使うだけで、集計の正源ではない。
  2. 使用回数上限は作物別に保持: 同一農薬でも作物ごとに上限が異なるため PesticideProductLimitPesticideIngredientLimit を作物別に複数行保持する。
  3. 作物名の照合は別名テーブルで吸収: 農水省表記の「稲」と内部の「水稲」のような差異を吸収するため、PesticideCropAlias を必須とする。
  4. 散布対象は保存時に確定保存する: 後日のグループ名変更や作付け変更で過去実績の集計結果が変わらないよう、SprayEventResolvedField に圃場・作物をスナップショット保存する。SprayEvent 自体には作物情報を持たない。
  5. 有効成分総使用回数も「1イベント=1回」: 同一成分を含む複数製品を同一イベントで施用することは実務上なく、仮に混合散布しても農薬取締法上「1回の散布=1回の使用」。製品使用回数と同様に COUNT(DISTINCT SprayEvent.id) で集計する。SprayEventResolvedField との結合で行が増えても DISTINCT で正確にカウントできる。
  6. 総使用回数はテキストパース: 農水省サイトの「○○を含む農薬の総使用回数」カラムから正規表現で数値を抽出する。
  7. 有効成分上限の整合性は保存時に保証する: 同一 ingredient_name + crop_namemax_total_uses は製品をまたいで一致している前提とし、異なる値を保存しようとした場合はエラーにする。
  8. 保存はブロックしない: 使用回数超過は警告表示のみ。農薬散布の記録は法的義務があるため、超過でも保存できるようにする。
  9. SprayEventPesticide.pesticide は PROTECT: 散布記録に使用中の農薬は削除不可。
  10. 成分集計は is_active=True のみ対象: 「その他成分」は総使用回数・特別栽培の成分数集計に含めない。
  11. is_spreader=Trueis_non_target 扱い: 展着剤はカウント除外のため、展着剤フラグをセットすれば節減対象外フラグも自動的に True 扱いDB保存は別フィールド

ソースファイル索引(実装後に更新)

ファイル 説明
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 ルーティング
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 等)