作成: 2026-04-09
最終更新: 2026-05-23
対象機能: 農薬散布管理(農薬マスタ・散布記録・使用回数チェック・特別栽培向け成分数集計)
実装状況: 実装済み
Gitea Issue: akira/keinasystem#18, akira/keinasystem#45, akira/keinasystem#69(在庫計算量入力改善), akira/keinasystem#77(農薬マスタ画面整理)
関連方針:docs/00_設計ポリシー_生産履歴中心設計.md
農業生産者が散布した農薬を記録・管理し、農薬取締法に基づく使用基準(製品ごと・有効成分ごとの使用回数制限)への適合確認と、特別栽培認証用の成分数集計を行う機能。
本機能は、生産履歴中心設計の下で、実際にどの圃場・どの品種へ・いつ・どの農薬が使われたか を残す役割を持つ。
計画や作業導線とは独立して、散布実績そのものを正本として保持する。
| IN(実装対象) | OUT(対象外) |
|---|---|
| 農薬マスタ管理(CRUD) | 農薬の購入管理 |
| 農林水産省サイトからの農薬情報自動取得 | 農薬費用の管理 |
| 散布イベント記録(圃場/グループ/作物/品種/種もみ処理対象) | 希釈液の量管理 |
| 製品ごとの使用回数チェック(年度×作物) | 農薬の廃棄記録 |
| 有効成分ごとの総使用回数チェック(年度×作物) | 農薬散布マップ(GIS) |
| 特別栽培用:節減対象農薬の使用成分数集計 | 農薬の処方箋・防除暦の自動作成 |
| 自社在庫を使用した農薬散布の在庫減算 | |
| 委託散布・購入苗/購入種子由来の農薬使用履歴管理 | |
| 回数超過アラート表示 | |
| ほ場別農薬使用量の記録(#74) |
農薬の使用回数は 製品単位 と 有効成分単位 の2軸で管理する。
農薬製品(例: 住化スミチオン乳剤)を1シーズンに使用した回数 ≤ 登録情報の「本剤の使用回数」上限。
同一有効成分を含む複数製品を使用した場合、その有効成分の総使用回数として合算カウントする。
例)「MEP乳剤A(上限3回)」と「MEP乳剤B(上限3回)」、MEP成分の総上限3回
→ A剤2回 + B剤1回 = 合計3回 → OK
→ A剤2回 + B剤2回 = 合計4回 → 超過!
育苗期・本圃期など時期別に別カウントになる場合がある(登録情報のテキストとして記録)。
システムでは現フェーズで時期別の自動判定は行わず、登録情報テキストを参照情報として表示する。
以下の農薬は使用回数・成分数のカウントから除外する(is_non_target フラグで管理):
is_spreader フラグでも管理)「節減対象農薬(is_non_target=False)の有効成分(is_active=True)が何種類使われたか」を年度×作物単位でカウントする。
上限はなく、報告用の集計値として表示する。
購入苗由来 購入種子由来 などの区分は補助情報であり、実際にその作物へ帰属する農薬使用履歴として記録するアプリ: 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 扱いとするmaterial は materials.Material への 1:1 リンクを持つmaterials.Material.name とし、/pesticide の表示・API レスポンスもリンク済みレコードについては Material.name を返すPesticide.name は移行互換と未リンクレコード用に残しており、リンク済みレコードでは Material.name と同期するテーブル名: 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']テーブル名: pesticide_pesticideingredientlimit
農水省の「○○を含む農薬の総使用回数」は作物ごとに異なりうるため、有効成分本体とは分離して作物別に保持する。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | BigAutoField | PK | |
| pesticide | FK(Pesticide) | CASCADE | 取得元農薬 |
| ingredient_name | CharField(200) | required | 成分グループキー(例: MEP)。MAFF 上限ヘッダから抽出し NFKC 正規化して保存する |
| 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 を保存しようとした場合はエラーにするingredient_usage.max_total_uses は、同一 ingredient_name + crop_name の値が一意であることを前提に単一値を返すingredient_name は保存時に NFKC 正規化する(例: イプコナゾール → イプコナゾール)。複数農薬の上限が同一グループキーにまとまることを保証するingredient_name は化学名・IUPAC 名ではなく MAFF が使う通称・略称であることが多い。PesticideIngredient.name との文字列一致は保証しないテーブル名: 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']テーブル名: pesticide_pesticidecroppreference
散布実績登録時に「農薬 × 内部作物」の組み合わせで最後に選んだ MAFF 適用作物登録を記憶する。次回登録時のデフォルト値として使用する。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | BigAutoField | PK | |
| pesticide | FK(Pesticide) | CASCADE | |
| crop | FK(plans.Crop) | CASCADE | 内部作物(散布対象の作物) |
| product_limit | FK(PesticideProductLimit) | SET_NULL, null=True | 最後に選んだ MAFF 適用作物登録 |
| updated_at | DateTimeField | auto_now |
unique_together = ['pesticide', 'crop']設計背景: MAFF の作物分類は大作物群→中作物群→小作物群→作物名の4階層構造を持ち、農薬の適用登録はどの階層レベルでも行われる。フラットな alias テーブルでは「葉菜類」登録が「キャベツ」に適用されるといった包含関係を自動解決できない。そのため自動マッチングをやめ、ユーザーが散布時に適用登録を明示選択する方式を採用した。
テーブル名: pesticide_sprayevent
1回の散布作業を1件として記録する。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | BigAutoField | PK | |
| year | IntegerField | required | 年度(date.year から自動設定、フロント入力不要) |
| date | DateField | required | 散布日 |
| target_type | CharField(20) | required | 対象種別: field / group / crop / variety / seed_treatment |
| target_fields | M2M(fields.Field) | blank, 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 | 必須フィールド | 意味 |
|---|---|---|
field |
target_fields | 特定の圃場(複数可)に散布 |
group |
target_group | 同一 group_name の全圃場に散布 |
crop |
target_crop | 特定の作物に対して散布(作付け計画と照合) |
variety |
target_variety | 特定の品種に対して散布(作付け計画と照合) |
seed_treatment |
target_variety | 種もみへの処理(浸種消毒等)。対象品種を作付けする圃場へ帰属 |
target_type について、保存時に対象圃場を SprayEventResolvedField として確定保存し、後日の作付け変更やグループ名変更があっても過去実績の集計結果が変わらないようにするtarget_type=seed_treatment は、保存時点の Plan を使って「対象品種を作付けする圃場」へ展開して SprayEventResolvedField を作成するテーブル名: pesticide_sprayeventresolvedfield
target_type=group / crop / variety / seed_treatment のように複数圃場へ展開される散布について、保存時点で対象圃場を確定保存する。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| 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 | 保存時点の作物名(Crop.name。当該年度に Plan が未登録の場合は "未定") |
| variety_name_snapshot | CharField(100) | blank | 保存時点の品種名 |
unique_together = ['event', 'field']target_type=field の場合も 1 行作成しておくと、集計ロジックを統一しやすいcrop_name_snapshot="未定" のレコードは使用回数集計の対象外となる(applied_limit 経由でカウントするため)テーブル名: pesticide_sprayeventpesticide
1つの散布イベントに複数農薬を紐づける。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | BigAutoField | PK | |
| event | FK(SprayEvent) | CASCADE | |
| pesticide | FK(Pesticide) | PROTECT | 使用農薬 |
| usage_source | CharField(30) | default=self_stock |
使用元(self_stock / contractor / purchased_seedling / purchased_seed / unknown) |
| dilution_ratio | CharField(50) | blank | 希釈倍率(例: 1000倍) |
| amount_used | CharField(50) | blank | 使用量(例: 500mL、単位込みで自由記述) |
| stock_quantity_base | DecimalField(10,3) | null=True | 在庫計算用の基準数量(内部計算単位。mL または g で保存) |
| stock_unit_base | CharField(20) | blank | 在庫計算用の基準単位(mL または g。画面での入力単位とは異なる場合がある) |
| applied_limit | FK(PesticideProductLimit) | null=True, SET_NULL | この散布で選択した MAFF 適用作物登録(使用回数カウントの基準) |
| notes | TextField | blank | 備考 |
pesticide は PROTECT(使用済み農薬は削除不可)unique_together = ['event', 'pesticide'](同一イベント内で同じ農薬を2回登録不可)applied_limit を null にすると使用回数チェックの対象外になる(展着剤等)usage_source=self_stock の場合のみ、自社資材在庫に対して StockTransaction(USE) を作成するusage_source=contractor / purchased_seedling / purchased_seed / unknown は散布履歴・使用回数集計には含めるが、自社在庫は減算しないpurchased_seedling / purchased_seed は、外部調達資材に由来する農薬使用を生産履歴へ含めるための区分であり、在庫管理上の都合だけの項目ではないPesticide.material を正とするamount_used は法定記録互換の自由記述を維持し、在庫計算には stock_quantity_base / stock_unit_base を使用するmL、粒剤・粉剤は g を基準単位の第一候補とする2900mL は 5本と400mL と表示するusage_source=self_stock の場合は stock_quantity_base と stock_unit_base を必須とするstock_quantity_base に格納する(Issue #69)Material.package_quantity / Material.package_label を参照するテーブル名: pesticide_sprayeventfieldpesticideusage
追加Issue: #74
1回の散布イベントに含まれる農薬明細(SprayEventPesticide)について、ほ場ごとの使用量を記録する。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | BigAutoField | PK | |
| event_pesticide | FK(SprayEventPesticide) | CASCADE | 散布農薬明細 |
| field | FK(fields.Field) | PROTECT | 対象圃場 |
| amount_used | CharField(50) | blank | ほ場別使用量(自由記述。例: 3袋、100mL、5パック) |
unique_together = ['event_pesticide', 'field']event_pesticide の CASCADE により、散布農薬明細削除時に連動削除されるfield は PROTECT(圃場を削除する場合は事前に関連レコードを削除する必要がある)amount_used は SprayEventPesticide.amount_used と同様の自由記述テキスト。在庫計算用の数値フィールドは持たないSprayEventPesticide.stock_quantity_base / stock_unit_base で行う(実コードの stock_service.py と一致)。本テーブルは生産履歴・ほ場別記録の用途にのみ使用するtarget_type=seed_treatment の散布イベントではレコードを作成しない(種もみ処理のほ場トレーサビリティは田植え実績の苗由来バッチ経由で担保する)field_usages が空であることで区別する| パターン | 操作 | 対応する散布形態 |
|---|---|---|
| 面積按分 | 合計量(amount_used)を入力 → 「面積で按分」ボタンでほ場面積比率から自動計算 |
液剤・フロアブル剤(動噴)、田植え機の箱剤 |
| ほ場別直接入力 | ほ場ごとのセルに使用量を直接入力 | ジャンボ剤(パック単位)、苗箱への手まき箱剤 |
どちらのパターンを使ったかはデータに持たない。保存されるのは確定後のほ場別使用量のみ。
農薬在庫を「内部は中身量で管理し、画面では容器単位に戻して表示する」ため、農薬系 Material には以下の情報を持たせる前提とする。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| stock_unit_base | CharField(20) | required | 在庫計算の基準単位(例: mL, g)。未設定の場合は散布記録の保存がブロックされる |
| package_quantity | DecimalField(10,3) | null=True | 1容器あたり内容量(例: 500)。未設定でも stock_unit_base があれば直接入力で保存可能 |
| package_unit | CharField(20) | blank | 内容量単位(例: mL, g) |
| package_label | CharField(20) | blank | 容器表示名(例: 本, 袋)。未設定でも保存可能 |
本数 × package_quantity を stock_unit_base に換算して入庫するstock_quantity_base を package_quantity と package_label を使って容器単位へ戻して表示する500mL × 12本 = 6000mL 入庫、3100mL 使用後は残量 2900mL、表示は 5本と400mLpackage_quantity / package_label は農薬マスタ画面(/pesticide/masters)のほか、散布記録入力画面からもインラインで設定可能(→ 4a 節参照)本機能の目的は「出荷した農産物が法令順守されていることの担保」であるため、使用回数集計の正本は 年度 × 圃場 × 品種 とする。
| 区分 | 集計キー | 用途 |
|---|---|---|
| 正本(出荷担保) | 年度 × 圃場 × 品種 | 出荷可否判断、トレーサビリティ、認証対応 |
| 参照(補助) | 年度 × MAFF 適用作物名 | MAFF 適用登録との照合、全体傾向確認 |
| 散布種別 | 例 | 出荷担保集計への帰属 |
|---|---|---|
| 1) 播種前に品種へ使用 | 種もみ処理、浸種消毒 | 年度Y・対象品種Vを作付けした各圃場Fへ配賦(Plan と SprayEventResolvedField で解決) |
| 2) 作付け前に圃場へ使用 | 土壌処理 | 年度Y・対象圃場F・当該圃場の品種Vへ帰属 |
| 3) 作付け後に圃場へ使用 | 本田散布 | 年度Y・対象圃場F・当該圃場の品種Vへ帰属 |
| 4) 収穫後出荷前に使用 | (現場運用なし) | 対象外(将来拡張) |
applied_limit.crop_name / PesticideIngredientLimit)を使うapplied_limit が null の場合(展着剤等)はカウント対象外1イベント = 1散布作業 = 1回。
製品使用回数(年度Y・圃場F・品種V・農薬P)=
COUNT(DISTINCT SprayEvent.id)
where SprayEventPesticide.pesticide = P
かつ SprayEvent.year = Y
かつ SprayEventPesticide.applied_limit IS NOT NULL
かつ イベントが (F, V) に帰属する
製品使用回数(年度Y・MAFF作物名C・農薬P)=
COUNT(DISTINCT SprayEvent.id)
where SprayEventPesticide.pesticide = P
かつ SprayEventPesticide.applied_limit.crop_name = C
かつ SprayEvent.year = Y
製品使用回数(年度Y・圃場F・品種V・農薬P)=
COUNT(DISTINCT SprayEvent.id)
where SprayEventPesticide.pesticide = P
かつ SprayEvent.year = Y
かつ SprayEventResolvedField.field = F
かつ SprayEventResolvedField.variety_name_snapshot = V
かつ SprayEventPesticide.applied_limit IS NOT NULL
target_type=seed_treatment も集計対象とする(対象品種を作付けする圃場へ展開)max_uses は SprayEventPesticide.applied_limit.max_uses を使用するSprayEventResolvedField.variety_name_snapshot で品種別に分けて表示する製品使用回数(年度Y・品種V・圃場F・農薬P)=
COUNT(DISTINCT SprayEvent.id)
where SprayEventPesticide.pesticide = P
かつ SprayEvent.year = Y
かつ SprayEventResolvedField.variety_name_snapshot = V
かつ SprayEventResolvedField.field = F
かつ SprayEventPesticide.applied_limit IS NOT NULL
by-variety API は 年度 × 品種 で全圃場のエントリを返すfield_name 昇順成分グループキー I は PesticideIngredientLimit.ingredient_name(MAFF の「○○を含む農薬の総使用回数」ヘッダの「○○」部分、NFKC 正規化済み)。PesticideIngredient.name(化学名・IUPAC 名)との文字列突合は行わない。
[ステップ1] 散布農薬の成分グループキーを収集
PesticideIngredientLimit.objects.filter(
pesticide__in=used_pesticides,
crop_name=C,
) → ingredient_name ごとにグループ化
[ステップ2] 成分グループキー I の used_count を計算
有効成分総使用回数(年度Y・MAFF作物名C・成分グループキーI)=
COUNT(DISTINCT SprayEvent.id)
where SprayEventPesticide.pesticide に
PesticideIngredientLimit(ingredient_name=I, crop_name=C) が存在する
かつ SprayEventPesticide.applied_limit.crop_name = C
かつ SprayEventPesticide.pesticide.is_non_target=False
かつ SprayEvent.year = Y
使用成分数(年度Y・MAFF作物名C)=
COUNT(DISTINCT PesticideIngredient.name)
where SprayEventPesticide.applied_limit.crop_name = C
かつ PesticideIngredient.is_active=True
かつ SprayEventPesticide.pesticide.is_non_target=False
かつ SprayEvent.year = Y
すべて 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回以内"
}
],
"preferences": [
{
"crop": 1,
"crop_name": "水稲",
"product_limit_id": 1,
"product_limit_crop_name": "稲"
}
],
"stock_unit_base": "ml",
"stock_unit_base_display": "mL",
"package_quantity": 500.0,
"package_label": "本"
}
stock_unit_base / stock_unit_base_display:資材マスタの基準単位。未設定の場合は nullpackage_quantity:1容器あたりの内容量(数値)。未設定の場合は null(この場合、散布入力画面では基準単位での直接入力のみ可能)package_label:容器表示名("本" / "袋" / "個" 等)。未設定の場合は nullPOST /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 リクエスト例(圃場グループを対象に複数農薬散布):
{
"date": "2026-05-10",
"target_type": "group",
"target_group": "田中エリア",
"notes": "曇り、風弱し",
"pesticides": [
{
"pesticide": 1,
"dilution_ratio": "1000倍",
"amount_used": "500mL",
"applied_limit": 1
},
{
"pesticide": 3,
"dilution_ratio": "2000倍",
"amount_used": "200mL",
"applied_limit": null
}
]
}
year はサーバー側で date.year から自動設定(リクエストに含めない)applied_limit: PesticideProductLimit.id。展着剤等カウント対象外の場合は null散布イベント レスポンス例:
{
"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",
"applied_limit": 1,
"applied_limit_crop_name": "稲"
}
],
"created_at": "2026-04-09T10:00:00Z",
"updated_at": "2026-04-09T10:00:00Z"
}
GET /api/pesticide/usage-summary/?year={year}&maff_crop_name={crop_name}年度 × MAFF 作物名単位で使用回数の集計結果を返す(参照用)。
maff_crop_name は PesticideProductLimit.crop_name(農水省登録情報の作物名、例: 稲)。
レスポンス例:
{
"year": 2026,
"maff_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
}
使用された MAFF 作物名の一覧取得(フィルタ用):
GET /api/pesticide/usage-summary/crop-names/?year={year}
["稲", "大豆"]
GET /api/pesticide/usage-summary/by-field/?year={year}&field_id={field_id}圃場×品種単位で、出荷担保の正本となる使用回数集計を返す(主API)。field_id は fields.Field.id。
レスポンス例:
{
"year": 2026,
"field_id": 5,
"field_name": "田中上",
"entries": [
{
"variety_name": "コシヒカリ",
"maff_crop_name": "稲",
"product_usage": [
{
"pesticide_id": 1,
"pesticide_name": "住化スミチオン乳剤",
"used_count": 3,
"max_uses": 3,
"remaining": 0,
"is_over": false
}
],
"ingredient_usage": [
{
"ingredient_name": "MEP",
"used_count": 3,
"max_total_uses": 3,
"remaining": 0,
"is_over": false,
"products_used": ["住化スミチオン乳剤"]
}
],
"has_violation": false
}
]
}
entries は同一圃場に複数品種が作付けされている場合、品種ごとに分かれるvariety_name_snapshot=""")のイベントは variety_name: "未定" としてまとめるhas_violation はそのエントリ内に1件でも超過があれば truetarget_type=seed_treatment も除外せず、対象品種を作付けした圃場エントリへ帰属させるGET /api/pesticide/usage-summary/by-variety/?year={year}&variety_id={variety_id}品種選択だけで対象年度の全圃場を一括確認するための集計API。variety_id は plans.Variety.id。
レスポンス例:
{
"year": 2026,
"variety_id": 12,
"variety_name": "たちはるか",
"entries": [
{
"field_id": 5,
"field_name": "自然農園",
"maff_crop_name": "稲",
"product_usage": [],
"ingredient_usage": [],
"has_violation": true
},
{
"field_id": 8,
"field_name": "東田",
"maff_crop_name": "稲",
"product_usage": [],
"ingredient_usage": [],
"has_violation": false
}
],
"has_violation": true
}
entries は 超過あり圃場を先頭に返すmaff_crop_name ごとに別エントリ農林水産省 農薬登録情報提供システム
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 |
ingredient_name(成分グループキー)の抽出と正規化規則:
カラムヘッダ例: "MEPを含む農薬の総使用回数"
→ ingredient_name = ヘッダから「を含む農薬の総使用回数」を除いた残り = "MEP"
→ 抽出後に unicodedata.normalize('NFKC', value) を適用して保存する
例: "イプコナゾールを含む農薬の総使用回数" → ingredient_name = "イプコナゾール"
「総使用回数」テキストのパース規則:
入力例: "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エンドポイントから呼び出す。
農水省の農薬登録情報は年次更新されるため、マスタ登録時の1回取得だけでは適用変更が反映されない。
system_id が登録済みの農薬のみを対象とする(未連携農薬は対象外)PesticideIngredient / PesticideProductLimit / PesticideIngredientLimit を全削除→再作成fetched_at を更新して農薬マスタ画面に表示するスクレイピングロジックは apps/pesticide/services.py に実装し、ViewSet の fetch アクションおよび management command から呼び出す。シングルユーザー・1件ずつの取得なので同期 HTTP で十分。
requests.Session)を使用し、クッキーとCSRFを維持するsystem_id と registration_number は実際には同一値(MAFF サイト調査で確認)。念のため別フィールドとして保持するが、registration_number から詳細 URL を直接構築可能(/agricultural-chemicals/details/{registration_number})PesticideProductLimit.crop_name に保存する/pesticide/)ナビゲーション配置: 実績グループ
/pesticide/events/new へ遷移)、編集、削除/pesticide/usage へ遷移)/pesticide/masters へ遷移)/pesticide/masters/)ナビゲーション配置: マスターグループ
fetched_at を表示)stock_unit_base(基準単位)、package_quantity(1容器あたり内容量)、package_label(容器単位名)system_id 登録済み農薬のみsystem_id 未登録(未連携)農薬は再取得対象に含まれないため、個別検索・選択による連携が必要運用上の誤解を防ぐため、以下を実装済み(Issue #35)。
MAFF連携状態 を表示
system_id あり: 緑バッジ 連携済み + fetched_atsystem_id なし: オレンジバッジ 未連携連携済みを再取得: 現行 全件再取得 相当。system_id 登録済み農薬のみを再取得する未連携を一括紐付け (N件): 未連携件数を表示し、N=0 の場合は非表示未連携を一括紐付け モーダルを追加
POST /api/pesticide/pesticides/fetch/ { name } を順次呼び、候補一覧を自動取得する候補あり / 自動選択 / 候補なし / スキップ で表示する自動選択済み・要確認 として表示し、保存前にユーザー確認を必須とする候補なし の場合は、完全一致に近い検索だけで終わらせず、段階的な前方語検索を自動で試す
ビルダーフェルテラゼクサロン 1kg → ビルダーフェルテラゼクサロンダブルカットバリタ → ダブルカット別名検索: ダブルカット のように表示する候補あり として手動選択を求める自動選択 またはユーザー選択済みの農薬のみを対象に順次紐付けするfetch/ API に pesticide_id モードを追加する
{ name }: 候補一覧を返す(既存){ system_id }: MAFF 農薬を新規作成または既存更新する(既存){ system_id, pesticide_id }: 既存の未連携農薬レコードに system_id を紐付け、詳細情報・成分・上限を更新する(新規)system_id が別レコードで使用済みの場合は 400 を返し、重複レコード作成を防止する手動で追加 連携済みを再取得 未連携を一括紐付け (N件) を横並びで表示する未連携を一括紐付け を押すとモーダルを開く候補なし を表示する別名検索 として表示する自動選択済み・要確認 として表示し、候補複数件の場合は未選択のラジオボタンで表示するスキップ を行う選択した農薬を紐付け保存 を押すと、選択済みの農薬だけを順次 fetch(system_id, pesticide_id) で保存する/pesticide/events/new)year はサーバー側で自動設定)seed_treatment(種もみ処理)選択時は品種を選択。保存時に対象品種を作付けする圃場へ展開して SprayEventResolvedField を作成するPesticideProductLimit の一覧を表示(「MAFF 適用作物を選択」)
PesticideCropPreference に記録がある場合はその適用作物をデフォルト選択is_spreader=True)の場合は選択不要(applied_limit=null)使用元 を選択
自社在庫使用委託先農薬購入苗由来購入種子由来不明使用元=自社在庫使用 の場合は在庫反映対象として扱う使用元=自社在庫使用 の場合のみ表示)(Issue #69)[数量] [単位 ▼] → 換算結果をリアルタイム表示1/3、1/2)を入力可package_quantity(1容器あたり内容量)が設定済みの場合:容器単位(本/袋/個)を先頭に表示し、基準単位(mL/g)も選択可数量 × 資材の1容器内容量 で mL/g に換算してプレビュー表示stock_quantity_base に格納stock_unit_base(基準単位)が未設定の場合のみpackage_quantity(1容器あたり内容量)が未設定でも、stock_unit_base が設定済みであれば保存可能(基準単位での直接入力で対応)stock_unit_base 未設定時: 赤い警告バナーを表示し、「農薬マスタで設定する(別タブ)→」リンクを提示する。設定後に「設定後に再読み込み」ボタンで農薬データを再取得できるpackage_quantity 未設定時(stock_unit_base は設定済みの場合): 単位ラベルの横に「容器情報を設定」ボタンを表示する。押下するとフォームをインライン展開し、内容量・単位(mL/g)・容器単位名を入力できる
1{容器単位名} = {内容量}{単位} をリアルタイム表示PATCH /api/materials/materials/{material_id}/(package_quantity, package_unit, package_label)target_type=seed_treatment 以外の場合)
使用元=自社在庫使用 の明細のみ資材在庫へ USE を記録するPesticideCropPreference を upsert(次回のデフォルト記憶を更新)/pesticide/usage)「圃場・品種別(正本)」「MAFF作物別(参照)」の2タブ構成。
GET /api/pesticide/usage-summary/by-field/?year=&field_id= を呼び出すGET /api/pesticide/usage-summary/by-variety/?year=&variety_id= を呼び出す/api/pesticide/usage-summary/crop-names/ で選択肢取得)GET /api/pesticide/usage-summary/?year=&maff_crop_name= を呼び出すtarget_type + 対象FK/文字列で柔軟に対応し、使用回数集計は最終的に 年度 × 圃場 × 品種 へ帰属させる。target_type=seed_treatment(浸種消毒等の種もみ処理)も対象品種を作付けする圃場へ展開して SprayEventResolvedField に反映する。作付け計画(Plan)はこの帰属解決に使う。年度 × 圃場 × 品種 とする。applied_limit.crop_name(MAFF登録作物名)は上限参照のために使い、主集計キーにはしない。PesticideCropPreference で選択を記憶)。SprayEventResolvedField に圃場・作物・品種をスナップショット保存する。使用回数集計の正源はこのスナップショット(圃場×品種)とする。target_type / target_fields / target_group / target_crop / target_variety)に変更がある場合のみ全削除→再作成する。notes / pesticides のみの変更では再作成しない。COUNT(DISTINCT SprayEvent.id) で集計する。ingredient_name + crop_name の max_total_uses は製品をまたいで一致している前提とし、異なる値を保存しようとした場合はエラーにする。SprayEventPesticide.pesticide は PROTECT: 散布記録に使用中の農薬は削除不可。is_active=True のみ対象: 「その他成分」は総使用回数・特別栽培の成分数集計に含めない。is_spreader=True は is_non_target 扱い: 展着剤はカウント除外のため、展着剤フラグをセットすれば節減対象外フラグも自動的に True 扱い(DB保存は別フィールド)。展着剤は applied_limit=null で登録する。system_id 登録済み農薬のみ。system_id 未登録の農薬は MAFF 適用情報を持たないため、散布入力時に PesticideProductLimit が表示されない。#35 では未連携可視化、一括候補検索、fetch(system_id, pesticide_id) による既存レコードへの安全な紐付け導線を追加し、重複レコード作成を防止する。Material.name とする: リンク済み農薬の表示名・編集結果・MAFF 連携結果は materials.Material.name に集約する。Pesticide.name は移行互換と未リンクレコード対応のため当面保持するが、リンク済みレコードでは Material.name と同期する。残るモデル整理・API 整理は Issue #36 で継続管理する。StockTransaction(USE) を作成し、修正時は該当トランザクションを全置換、削除時は対応トランザクションも削除する。履歴整合の考え方は肥料散布と同じにする。usage_source=self_stock の場合のみ行う: 委託散布、購入苗・購入種子に由来する農薬使用は法定記録・履歴には残すが、自社在庫は動かさない。mL、粒剤・粉剤は g を基準単位の第一候補とし、散布記録には在庫計算用の構造化フィールドを持たせる。package_quantity と package_label を使って 5本と400mL のように容器単位 + 端数量へ変換して見せる。計算の正本と表示の見せ方は分離する。500mL × 12本 のような購入入力は、内部では 6000mL として StockTransaction(PURCHASE) に記録する。容器数は表示補助情報として扱う。SprayEventFieldPesticideUsage は生産履歴・ほ場別記録の用途に特化する。在庫計算の根拠は引き続き SprayEventPesticide.stock_quantity_base / stock_unit_base とする(amount_used は法定記録用の自由記述であり在庫計算には使用しない)。target_type=seed_treatment の散布イベントに SprayEventFieldPesticideUsage は作成しない。生産履歴時には「田植え実績 → 苗由来バッチ → 品種 → 散布イベント」の経路で遡ることができる。年度×圃場×品種(by-field / by-variety)とし、年度×MAFF作物名 は参照用として残す。これにより、同一作物内で圃場ごとに施用履歴が異なるケースでも、出荷単位で法令順守を判定できる。運用負荷を下げるため、正本ビューには「品種で一括確認(全圃場一覧、超過先頭表示)」を持たせる。PesticideIngredientLimit.ingredient_name をグループキーとする: PesticideIngredient.name(化学名・IUPAC 名)との文字列突合は行わない。MAFF が「○○を含む農薬の総使用回数」ヘッダに使う通称・略称(例: MEP、イプコナゾール)が農薬横断の集計グループキーとして機能する。ingredient_name は保存時に NFKC 正規化して半角カナ等の表記ゆれを吸収する(Issue #40)。これにより、化学名と通称・略称の不一致(例: PesticideIngredient.name と MAFF ヘッダ)があっても、農薬横断の総使用回数を同一キーで集計できる。半角カナ/全角カナの揺れ(例: イプコナゾール / イプコナゾール)も同一成分グループとして扱える。| ファイル | 説明 |
|---|---|
backend/apps/pesticide/models.py |
Pesticide, PesticideIngredient, PesticideIngredientLimit, PesticideProductLimit, PesticideCropPreference, SprayEvent, SprayEventResolvedField, SprayEventPesticide, SprayEventFieldPesticideUsage |
backend/apps/pesticide/services.py |
農水省スクレイパー(fetch_pesticide ロジック) |
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 |
CLI 用 management command(services.py を呼び出す薄いラッパー) |
frontend/src/app/pesticide/page.tsx |
農薬散布管理画面(散布記録の年度別一覧・追加・削除) |
frontend/src/app/pesticide/masters/page.tsx |
農薬マスタ画面(MAFF検索・手動追加・容器情報編集・一括紐付けモーダル) |
frontend/src/app/pesticide/usage/page.tsx |
使用回数チェック画面(正本タブ: 圃場単位/品種一括確認 + 参照タブ: MAFF作物別) |
frontend/src/types/index.ts |
型定義(Pesticide, SprayEvent 等) |