初期仕様案

This commit is contained in:
Akira
2026-02-15 10:33:34 +09:00
commit ed899fb97d
6 changed files with 2699 additions and 0 deletions

View File

@@ -0,0 +1,936 @@
# Keina System - Gemini向け実装指示書
## 📋 このドキュメントについて
これは、農業生産者向けの作付け計画管理システム「Keina System」の実装に必要な全情報を統合したドキュメントです。
**実装エージェントGemini/OpenCodeは、以下の順序でドキュメントを読み、理解してから実装を開始してください。**
---
## 🎯 システムの全体像
### 目的
年間の作付け計画を管理し、役場への申請書類(水稲共済細目書・中山間地域等直接支払交付金)を自動生成するシステム。
### ユーザー
- 65歳の農家元プログラマー
- シングルユーザー(マルチテナント不要)
- PCで登録・編集、スマホで参照
### 主要機能Phase 1 / MVP
1. 作付け計画の一覧表示・編集
2. 水稲共済細目書のCSV出力
3. 中山間交付金申請書のCSV出力
4. 前年度作付けのコピー機能
5. 圃場情報のスマホ参照
### 技術スタック
- **バックエンド**: Django 5.0 + Django REST Framework + GeoDjango
- **フロントエンド**: Next.js 14 (App Router) + Tailwind CSS
- **データベース**: PostgreSQL 16 + PostGIS 3.4
- **インフラ**: Docker Compose
---
## 📚 必読ドキュメント(この順に読むこと)
実装前に、以下のドキュメントを**必ず全て読んでください**。各ドキュメントには実装に必要な重要な情報が含まれています。
### 1. プロダクトビジョン.md
- システムの目的、ユーザー像、成功の定義
- なぜこのシステムを作るのか、何を目指すのか
- **👉 読むべき理由**: ゴールを理解しないと、適切な実装判断ができない
### 2. ユーザーストーリー.md
- 具体的な利用シーン(優先度付き)
- 受け入れ基準、実装すべき機能の詳細
- **👉 読むべき理由**: 何を作るべきか、どこまで作るべきかが明確になる
### 3. データ仕様書.md
- 3種類のデータ実圃場、共済マスタ、中山間マスタの関係
- 紐付けロジックM:1関係の詳細
- 申請書CSV出力のアルゴリズム
- **👉 読むべき理由**: データモデルの設計ミスは後から修正困難
### 4. 画面設計書.md
- 全画面のワイヤーフレーム
- UI共通仕様カラー、フォント、レスポンシブ
- **👉 読むべき理由**: UIの実装イメージが具体的につかめる
### 5. 実装優先順位.md
- 10日間の実装スケジュール
- 技術スタックの詳細、潜在的な課題と対策
- **👉 読むべき理由**: 何から作るか、どう作るかの指針
---
## 🚀 実装の進め方(ステップバイステップ)
### Step 0: 事前準備(必須)
1. 上記5つのドキュメントを**全て読む**
2. 不明点があれば、実装開始前に質問する
3. 実データ(`吉田農地台帳.ods`, `水稲共済細目用.ods`, `中山間.ods`)を確認
### Step 1: 環境構築Day 1
```bash
# プロジェクト構造
keinasystem/
├── docker-compose.yml
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── manage.py
│ └── keinasystem/
│ ├── settings.py
│ ├── urls.py
│ └── apps/
│ ├── fields/ # 圃場管理
│ ├── plans/ # 作付け計画
│ └── reports/ # 申請書出力
└── frontend/
├── Dockerfile
├── package.json
├── app/
│ ├── login/
│ ├── dashboard/
│ ├── allocation/ # 作付け計画編集
│ └── reports/ # 申請書ダウンロード
└── lib/
├── api.ts # API呼び出し
└── types.ts # 型定義
```
#### docker-compose.yml
```yaml
version: '3.8'
services:
db:
image: postgis/postgis:16-3.4
environment:
POSTGRES_DB: keina_db
POSTGRES_USER: keina_user
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
backend:
build: ./backend
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./backend:/app
ports:
- "8000:8000"
environment:
DATABASE_URL: postgis://keina_user:${DB_PASSWORD}@db:5432/keina_db
SECRET_KEY: ${SECRET_KEY}
depends_on:
- db
frontend:
build: ./frontend
command: npm run dev
volumes:
- ./frontend:/app
- /app/node_modules
ports:
- "3000:3000"
environment:
NEXT_PUBLIC_API_URL: http://localhost:8000
volumes:
postgres_data:
```
### Step 2: Django セットアップDay 1-2
#### backend/requirements.txt
```txt
Django==5.0
djangorestframework==3.14
django-cors-headers==4.3
psycopg2-binary==2.9
djoser==2.2
djangorestframework-simplejwt==5.3
pandas==2.1
odfpy==1.4
WeasyPrint==60.1
```
#### backend/keinasystem/settings.py重要な設定のみ
```python
INSTALLED_APPS = [
# Django標準
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.gis', # GeoDjango
# サードパーティ
'rest_framework',
'rest_framework_simplejwt',
'djoser',
'corsheaders',
# 自作アプリ
'apps.fields',
'apps.plans',
'apps.reports',
]
# PostGIS設定
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': 'keina_db',
'USER': 'keina_user',
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': 'db',
'PORT': '5432',
}
}
# REST Framework設定
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
# JWT設定
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
}
# CORS設定
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
]
```
### Step 3: データモデル実装Day 3
#### apps/fields/models.py
```python
from django.contrib.gis.db import models
class OfficialKyosaiField(models.Model):
"""共済マスタ(水稲共済細目用.ods"""
k_num = models.IntegerField("耕地番号")
s_num = models.IntegerField("分筆番号")
address = models.CharField("地名地番", max_length=200)
kanji_name = models.CharField("漢字地名", max_length=200)
area = models.FloatField("本地面積(m2)")
class Meta:
unique_together = [['k_num', 's_num']]
ordering = ['k_num', 's_num']
class OfficialChusankanField(models.Model):
"""中山間マスタ(中山間.ods"""
c_id = models.IntegerField("ID", unique=True)
oaza = models.CharField("大字", max_length=100)
aza = models.CharField("", max_length=100)
chiban = models.IntegerField("地番")
area = models.IntegerField("農地面積(m2)")
payment_amount = models.IntegerField("交付金額", null=True, blank=True)
class Meta:
ordering = ['c_id']
class Field(models.Model):
"""実圃場(吉田農地台帳.ods"""
name = models.CharField("名称", max_length=100)
address = models.CharField("住所", max_length=200)
area_tan = models.FloatField("面積(反)")
area_m2 = models.IntegerField("面積(m2)") # area_tan * 1000
owner_name = models.CharField("地主", max_length=100)
# 紐付けキーraw値
raw_kyosai_k_num = models.IntegerField("細目_耕地番号")
raw_kyosai_s_num = models.IntegerField("細目_分筆番号")
raw_chusankan_id = models.IntegerField("中山間_ID", null=True, blank=True)
# 外部キー(紐付け済み)
kyosai_field = models.ForeignKey(
OfficialKyosaiField,
on_delete=models.SET_NULL,
null=True,
related_name='fields'
)
chusankan_field = models.ForeignKey(
OfficialChusankanField,
on_delete=models.SET_NULL,
null=True,
related_name='fields'
)
# 地理情報Phase 1では使用しないが、構造だけ準備
location = models.PointField(null=True, blank=True)
class Meta:
ordering = ['name']
```
#### apps/plans/models.py
```python
from django.db import models
from apps.fields.models import Field
class Crop(models.Model):
"""作物マスタ"""
name = models.CharField("作物名", max_length=50, unique=True)
is_planting = models.BooleanField("作付けする", default=True)
class Meta:
ordering = ['name']
class Variety(models.Model):
"""品種マスタ"""
crop = models.ForeignKey(Crop, on_delete=models.CASCADE, related_name='varieties')
name = models.CharField("品種名", max_length=100)
class Meta:
ordering = ['crop', 'name']
class Plan(models.Model):
"""作付け計画"""
field = models.ForeignKey(Field, on_delete=models.CASCADE, related_name='plans')
year = models.IntegerField("年度")
crop = models.ForeignKey(Crop, on_delete=models.PROTECT, null=True, blank=True)
variety = models.ForeignKey(Variety, on_delete=models.SET_NULL, null=True, blank=True)
notes = models.TextField("備考", blank=True)
class Meta:
unique_together = [['field', 'year']]
ordering = ['year', 'field']
```
### Step 4: インポート機能Day 4
#### apps/fields/views.pyインポートAPI
```python
from rest_framework.decorators import api_view
from rest_framework.response import Response
import pandas as pd
@api_view(['POST'])
def import_kyosai_master(request):
"""共済マスタのインポート"""
file = request.FILES['file']
df = pd.read_excel(file, engine='odf')
for _, row in df.iterrows():
OfficialKyosaiField.objects.update_or_create(
k_num=row['耕地番号'],
s_num=row['分筆番号'],
defaults={
'address': row['地名 地番'],
'kanji_name': row['漢字地名'],
'area': row['本地面積 (m2)'],
}
)
return Response({'status': 'success', 'imported': len(df)})
@api_view(['POST'])
def import_yoshida_fields(request):
"""吉田農地台帳のインポート(紐付け処理含む)"""
file = request.FILES['file']
df = pd.read_excel(file, engine='odf')
for _, row in df.iterrows():
# 共済マスタとの紐付け
try:
kyosai = OfficialKyosaiField.objects.get(
k_num=row['細目_耕地番号'],
s_num=row['細目_分筆番号']
)
except OfficialKyosaiField.DoesNotExist:
kyosai = None
# 中山間マスタとの紐付け
chusankan = None
if pd.notna(row['中山間_ID']):
try:
chusankan = OfficialChusankanField.objects.get(
c_id=int(row['中山間_ID'])
)
except OfficialChusankanField.DoesNotExist:
pass
# 実圃場を作成
Field.objects.update_or_create(
name=row['名称'],
defaults={
'address': row['住所'],
'area_tan': row['面積(反)'],
'area_m2': int(row['面積(反)'] * 1000),
'owner_name': row['地主'],
'raw_kyosai_k_num': row['細目_耕地番号'],
'raw_kyosai_s_num': row['細目_分筆番号'],
'raw_chusankan_id': int(row['中山間_ID']) if pd.notna(row['中山間_ID']) else None,
'kyosai_field': kyosai,
'chusankan_field': chusankan,
}
)
return Response({'status': 'success', 'imported': len(df)})
```
### Step 5: 作付け計画APIDay 5
#### apps/plans/views.py
```python
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Plan
from .serializers import PlanSerializer
class PlanViewSet(viewsets.ModelViewSet):
queryset = Plan.objects.all()
serializer_class = PlanSerializer
def get_queryset(self):
queryset = super().get_queryset()
year = self.request.query_params.get('year')
if year:
queryset = queryset.filter(year=year)
return queryset
@action(detail=False, methods=['post'])
def bulk_update(self, request):
"""一括更新"""
field_ids = request.data.get('field_ids', [])
crop_id = request.data.get('crop_id')
variety_id = request.data.get('variety_id')
year = request.data.get('year')
for field_id in field_ids:
Plan.objects.update_or_create(
field_id=field_id,
year=year,
defaults={
'crop_id': crop_id,
'variety_id': variety_id,
}
)
return Response({'status': 'success', 'updated': len(field_ids)})
@action(detail=False, methods=['post'])
def copy_from_previous_year(self, request):
"""前年度コピー"""
source_year = request.data.get('source_year')
target_year = request.data.get('target_year')
plans = Plan.objects.filter(year=source_year)
for plan in plans:
Plan.objects.update_or_create(
field=plan.field,
year=target_year,
defaults={
'crop': plan.crop,
'variety': plan.variety,
'notes': plan.notes,
}
)
return Response({'status': 'success', 'copied': len(plans)})
```
### Step 6: 申請書PDF生成Day 7
#### apps/reports/views.py
```python
from rest_framework.decorators import api_view
from django.http import HttpResponse
from django.template.loader import render_to_string
from weasyprint import HTML
from apps.fields.models import OfficialKyosaiField, OfficialChusankanField
from apps.plans.models import Plan
@api_view(['GET'])
def generate_kyosai_pdf(request):
"""水稲共済細目書PDF"""
year = int(request.query_params.get('year'))
# 1. データ集約
output_rows = []
for kyosai in OfficialKyosaiField.objects.all().order_by('k_num', 's_num'):
# この共済区画に紐づく実圃場を取得
fields = kyosai.fields.all()
# 各実圃場の作付け計画を取得
plans = Plan.objects.filter(
field__in=fields,
year=year
).select_related('crop', 'variety')
# 作物名と品種を集約
crops = sorted(list(set([p.crop.name for p in plans if p.crop])))
varieties = sorted(list(set([p.variety.name for p in plans if p.variety])))
output_rows.append({
'k_num': kyosai.k_num,
's_num': kyosai.s_num,
'address': kyosai.address,
'kanji_name': kyosai.kanji_name,
'area': kyosai.area,
'crops': ','.join(crops) if crops else '未設定',
'varieties': ','.join(varieties) if varieties else '',
'note': f'{len(fields)}筆合算' if len(fields) > 1 else ''
})
# 2. HTMLテンプレートで表を生成
html = render_to_string('reports/kyosai_template.html', {
'year': year,
'rows': output_rows
})
# 3. HTML → PDF変換
pdf = HTML(string=html).write_pdf()
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="kyosai_{year}.pdf"'
return response
@api_view(['GET'])
def generate_chusankan_pdf(request):
"""中山間交付金PDF"""
year = int(request.query_params.get('year'))
# データ集約(共済と同様)
output_rows = []
for chusankan in OfficialChusankanField.objects.all().order_by('c_id'):
fields = chusankan.fields.all()
plans = Plan.objects.filter(field__in=fields, year=year).select_related('crop', 'variety')
crops = sorted(list(set([p.crop.name for p in plans if p.crop])))
varieties = sorted(list(set([p.variety.name for p in plans if p.variety])))
output_rows.append({
'c_id': chusankan.c_id,
'oaza': chusankan.oaza,
'aza': chusankan.aza,
'chiban': chusankan.chiban,
'area': chusankan.area,
'crops': ','.join(crops) if crops else '未設定',
'varieties': ','.join(varieties) if varieties else '',
'note': f'{len(fields)}筆合算' if len(fields) > 1 else ''
})
html = render_to_string('reports/chusankan_template.html', {
'year': year,
'rows': output_rows
})
pdf = HTML(string=html).write_pdf()
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="chusankan_{year}.pdf"'
return response
```
#### apps/reports/templates/reports/kyosai_template.html
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
@page {
size: A4;
margin: 2cm;
}
body {
font-family: "MS Gothic", "Yu Gothic", monospace;
font-size: 10pt;
}
h1 {
text-align: center;
font-size: 14pt;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 9pt;
}
th, td {
border: 1px solid black;
padding: 4px;
text-align: center;
}
th {
background-color: #f0f0f0;
font-weight: bold;
}
.left {
text-align: left;
}
</style>
</head>
<body>
<h1>水稲共済細目書({{ year }}年度)</h1>
<table>
<thead>
<tr>
<th style="width: 8%;">耕地番号</th>
<th style="width: 8%;">分筆番号</th>
<th style="width: 20%;">地名地番</th>
<th style="width: 18%;">漢字地名</th>
<th style="width: 10%;">本地面積<br>(m2)</th>
<th style="width: 12%;">作付品目</th>
<th style="width: 14%;">品種</th>
<th style="width: 10%;">備考</th>
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
<td>{{ row.k_num }}</td>
<td>{{ row.s_num }}</td>
<td class="left">{{ row.address }}</td>
<td class="left">{{ row.kanji_name }}</td>
<td>{{ row.area }}</td>
<td class="left">{{ row.crops }}</td>
<td class="left">{{ row.varieties }}</td>
<td>{{ row.note }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
```
#### apps/reports/templates/reports/chusankan_template.html
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
@page { size: A4; margin: 2cm; }
body { font-family: "MS Gothic", "Yu Gothic", monospace; font-size: 10pt; }
h1 { text-align: center; font-size: 14pt; margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; font-size: 9pt; }
th, td { border: 1px solid black; padding: 4px; text-align: center; }
th { background-color: #f0f0f0; font-weight: bold; }
.left { text-align: left; }
</style>
</head>
<body>
<h1>中山間地域等直接支払交付金({{ year }}年度)</h1>
<table>
<thead>
<tr>
<th style="width: 8%;">ID</th>
<th style="width: 15%;">大字</th>
<th style="width: 15%;"></th>
<th style="width: 10%;">地番</th>
<th style="width: 12%;">農地面積<br>(m2)</th>
<th style="width: 15%;">作付品目</th>
<th style="width: 15%;">品種</th>
<th style="width: 10%;">備考</th>
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
<td>{{ row.c_id }}</td>
<td class="left">{{ row.oaza }}</td>
<td class="left">{{ row.aza }}</td>
<td>{{ row.chiban }}</td>
<td>{{ row.area }}</td>
<td class="left">{{ row.crops }}</td>
<td class="left">{{ row.varieties }}</td>
<td>{{ row.note }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
```
#### apps/reports/urls.py
```python
from django.urls import path
from . import views
urlpatterns = [
path('kyosai/', views.generate_kyosai_pdf, name='kyosai_pdf'),
path('chusankan/', views.generate_chusankan_pdf, name='chusankan_pdf'),
]
```
### Step 7: フロントエンド実装Day 6, 8-9
#### frontend/app/reports/page.tsx申請書ダウンロード画面
```tsx
'use client'
import { useState } from 'react'
export default function ReportsPage() {
const [year, setYear] = useState(2025)
const downloadPDF = async (type: 'kyosai' | 'chusankan') => {
const endpoint = type === 'kyosai'
? `/api/reports/kyosai/?year=${year}`
: `/api/reports/chusankan/?year=${year}`
try {
const response = await fetch(`http://localhost:8000${endpoint}`, {
headers: {
'Authorization': `JWT ${localStorage.getItem('accessToken')}`
}
})
const blob = await response.blob()
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = type === 'kyosai'
? `水稲共済細目書_${year}年度.pdf`
: `中山間交付金_${year}年度.pdf`
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
} catch (error) {
console.error('PDF download failed:', error)
alert('PDFのダウンロードに失敗しました')
}
}
const previewPDF = (type: 'kyosai' | 'chusankan') => {
const endpoint = type === 'kyosai'
? `/api/reports/kyosai/?year=${year}`
: `/api/reports/chusankan/?year=${year}`
const url = `http://localhost:8000${endpoint}`
window.open(url, '_blank')
}
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4"></h1>
<div className="mb-4">
<label className="mr-2">:</label>
<select
value={year}
onChange={e => setYear(parseInt(e.target.value))}
className="border rounded px-4 py-2"
>
<option value={2024}>2024</option>
<option value={2025}>2025</option>
<option value={2026}>2026</option>
</select>
</div>
<div className="grid gap-4">
{/* 水稲共済細目書 */}
<div className="border rounded p-4">
<h2 className="text-xl font-bold mb-2">📄 </h2>
<p className="text-sm text-gray-600 mb-2">提出時期: 2月52</p>
<p className="text-sm text-gray-600 mb-4">区画数: 31区画</p>
<div className="flex gap-2">
<button
onClick={() => previewPDF('kyosai')}
className="bg-blue-500 text-white px-4 py-2 rounded"
>
</button>
<button
onClick={() => downloadPDF('kyosai')}
className="bg-green-500 text-white px-4 py-2 rounded"
>
PDFダウンロード
</button>
</div>
</div>
{/* 中山間交付金 */}
<div className="border rounded p-4">
<h2 className="text-xl font-bold mb-2">📄 </h2>
<p className="text-sm text-gray-600 mb-2">提出時期: 5月1</p>
<p className="text-sm text-gray-600 mb-4">区画数: 71区画</p>
<div className="flex gap-2">
<button
onClick={() => previewPDF('chusankan')}
className="bg-blue-500 text-white px-4 py-2 rounded"
>
</button>
<button
onClick={() => downloadPDF('chusankan')}
className="bg-green-500 text-white px-4 py-2 rounded"
>
PDFダウンロード
</button>
</div>
</div>
</div>
</div>
)
}
```
---
## ⚠️ 重要な実装上の注意点
### 1. データベース設計
- **面積単位**: DB内部は全て `m2` で保存、表示時に `反` に変換
- **紐付けキー**: `raw_*` フィールドと外部キー `*_field` の両方を持つ
- **ユニーク制約**: `(field, year)` で作付け計画は1つまで
### 2. 申請書CSV生成
- **共済マスタをベースにループ**: 実圃場ベースではない
- **作物の名寄せ**: Pythonのセットで重複排除
- **未割当の扱い**: 「未設定」として出力
### 3. UI/UX
- **未割当の強調**: 赤または黄色の背景色
- **スマホ対応**: 文字サイズ16px以上、タップ領域44px以上
- **検索のリアルタイム性**: `useState` + `filter` で実装
### 4. 認証
- **JWT認証**: アクセストークン1日+ リフレッシュトークン7日
- **シンプルな実装**: パスワードリセットはPhase 2
### 5. パフォーマンス
- **Phase 1では最適化不要**: 圃場数39筆、共済31区画、中山間71区画 → 十分軽い
- **Phase 2で検討**: 栽培履歴が増えたらページネーション
---
## 📝 実装チェックリスト
実装完了時に、以下を全て確認してください:
### 環境構築
- [ ] `docker-compose up` でコンテナが全て起動する
- [ ] PostgreSQL + PostGIS が正常に動作する
- [ ] Django の `makemigrations` / `migrate` が成功する
- [ ] Next.js の `npm run dev` が成功する
### バックエンド
- [ ] `/admin` でDjango管理画面にアクセスできる
- [ ] `/api/auth/jwt/create/` でJWTトークンを取得できる
- [ ] `/api/fields/` で圃場一覧を取得できる
- [ ] `/api/plans/?year=2025` で作付け計画を取得できる
- [ ] `/api/reports/kyosai/?year=2025` でPDFをダウンロードできる
- [ ] `/api/reports/chusankan/?year=2025` でPDFをダウンロードできる
### フロントエンド
- [ ] `/login` でログインできる
- [ ] `/allocation` で作付け計画一覧を表示できる
- [ ] 作付け計画を編集できる
- [ ] `/reports` で申請書をダウンロードできる
- [ ] PDFをプレビュー表示できる新しいタブ
- [ ] スマホで見やすいChrome DevToolsで確認
### データ整合性
- [ ] 共済PDFの耕地番号が正しい
- [ ] 中山間PDFのIDが正しい
- [ ] 作物の名寄せが正しい
- [ ] 未割当の圃場が適切に扱われる
- [ ] PDFの表レイアウトが整っている
- [ ] PDFをA4用紙に印刷して読めるフォントサイズ、余白
---
## 🆘 トラブルシューティング
### PostGISが動かない
```python
# settings.py に以下を追加
GDAL_LIBRARY_PATH = '/usr/lib/libgdal.so'
GEOS_LIBRARY_PATH = '/usr/lib/libgeos_c.so'
```
### ODSファイルが読めない
```bash
pip install odfpy --break-system-packages
```
### WeasyPrintのインストールエラー
```bash
# Ubuntu/Debianの場合、システムライブラリが必要
apt-get install python3-cffi python3-brotli libpango-1.0-0 libpangoft2-1.0-0
pip install WeasyPrint --break-system-packages
```
### PDFの日本語が表示されない
```python
# HTMLテンプレートのCSSに日本語フォントを明示
body {
font-family: "MS Gothic", "Yu Gothic", "Hiragino Sans", sans-serif;
}
```
### CORSエラー
```python
# settings.py
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
]
```
### マイグレーションエラー
```bash
docker-compose exec backend python manage.py makemigrations
docker-compose exec backend python manage.py migrate
```
---
## 🎉 実装完了の定義
以下が全て完了したら、Phase 1は実装完了とする
1. ✅ ログインできる
2. ✅ 作付け計画を編集できる
3. ✅ 水稲共済PDFをダウンロードできる
4. ✅ 中山間PDFをダウンロードできる
5. ✅ PDFをプレビュー表示できる
6. ✅ PDFをA4用紙に印刷してそのまま提出できる品質
7. ✅ 前年度コピーができる
8. ✅ スマホで圃場情報を見られる
9. ✅ 実データ3つのODSファイルをインポートできる
---
## 📞 質問・不明点がある場合
実装中に疑問が生じた場合、以下を確認してください:
1. **データ仕様書.md**: データの紐付けロジックが不明な場合
2. **画面設計書.md**: UIの詳細が不明な場合
3. **ユーザーストーリー.md**: 機能の目的・受け入れ基準が不明な場合
それでも解決しない場合は、**実装を止めて質問してください**。
---
**Good luck with the implementation! 🚀**

View File

@@ -0,0 +1,156 @@
# プロダクトビジョン
## 🎯 システムの目的
**「作付け計画を起点とした農業経営データの一元管理」**
このシステムは、年間の作付け計画を中心に、以下の3つの課題を解決する
1. **申請書類の作成負担を減らす**
- 水稲共済細目書年2回: 2月・5月
- 中山間地域等直接支払交付金年1回: 5月
- これらの申請に必要なデータを、作付け計画から自動生成
2. **実圃場と申請区画のずれを管理する**
- 実際に作業する圃場39筆と、申請書上の区画共済31区画、中山間71区画が異なる
- 複数の実圃場が1つの申請区画に紐づく関係M:1を明示的に管理
- 紐付けは半自動化するが、手動修正も可能にする
3. **将来の拡張を見据えた設計**
- Phase 2: 栽培履歴(播種日、農薬・肥料の散布記録)
- Phase 3: 資材計画(種苗・肥料・農薬の必要量計算)
- Phase 4: 収穫管理・販売管理との連携
---
## 👤 ユーザー像
**主要ユーザーSole User:**
- 65歳の農家元プログラマー、50歳まで従事
- ITリテラシー: 高い(自分でシステムを設計・実装できるレベル)
- 経営規模: 39筆の圃場を管理
**利用デバイス:**
- 🖥️ **PC**: 作付け計画の登録・編集、申請書のダウンロード(メイン操作)
- 📱 **スマホ/タブレット**: 圃場での参照(品種確認、面積確認、将来的には栽培履歴)
**利用シーズン:**
- **11月3月**: 作付け計画の策定・修正(前年度コピー→微調整)
- **2月**: 水稲共済細目書の提出1回目
- **5月**: 水稲共済細目書2回目中山間交付金の申請
- **通年**: スマホでの現場参照
---
## 📊 現状の課題とシステムによる解決
| 課題 | 現状Before | システム導入後After |
|------|---------------|----------------------|
| 申請書作成 | 紙の台帳から手作業で転記・集計 | ボタン1つでPDFダウンロード→印刷 |
| 圃場と申請区画の対応 | Excelで手動管理、照合が大変 | 自動紐付けUI上で視覚的に確認・修正 |
| 前年度データの再利用 | 前年のExcelをコピー→手作業で修正 | 年度コピー機能で一括複製 |
| 作物の変更履歴 | 紙のメモ、記憶頼み | 過去年度の作付け計画を参照可能 |
| 現場での情報確認 | 家に戻って紙の台帳を確認 | スマホでその場で品種・面積を確認 |
---
## ✅ 成功の定義KPI
**Phase 1MVPの成功指標:**
1. **申請書作成時間の短縮**
- 水稲共済: 手作業2時間 → システム5分96%削減)
- 中山間: 手作業1時間 → システム3分95%削減)
2. **データの正確性向上**
- 転記ミスゼロ(自動集計のため)
- 圃場と申請区画の対応ミスゼロUIで視覚的に確認
3. **使いやすさ**
- 作付け計画の登録・修正が、PCで10分以内に完了
- スマホでの圃場情報参照が、3タップ以内で完了
- PDFを印刷してそのまま提出できる品質レイアウト調整不要
**Phase 2以降の展望:**
- 栽培履歴の記録により、GAP認証の取得が可能に
- 資材計画の自動化により、発注漏れ・過剰在庫を削減
- 収穫実績と計画の比較により、翌年の計画精度が向上
---
## 🔐 非機能要件
**シンプルさ最優先:**
- シングルユーザー(マルチテナント不要)
- 認証は最小限(メール+パスワード)
- 複雑な権限管理は不要
**レスポンシブ対応:**
- PC: 作付け計画の編集、申請書ダウンロード
- スマホ/タブレット: 参照メイン(将来的には簡易な記録入力も)
**データの永続性:**
- 最低5年分のデータを保持補助金の監査対応
- バックアップ機能CSV/Excelでのエクスポート
**パフォーマンス:**
- 圃場一覧の表示: 1秒以内
- 申請書CSVの生成: 3秒以内
- スマホでの圃場詳細表示: 2秒以内
---
## 🚫 やらないことNon-Goals
**Phase 1では以下は含めない:**
- マルチユーザー対応(将来的にも不要の可能性高)
- 地図上での圃場描画・編集GeoJSON等は後回し
- 自動ジオコーディング住所→座標変換は手動でOK
- リアルタイム同期(オフライン対応は不要)
- モバイルアプリPWAで十分
---
## 🎨 デザイン原則
1. **シンプル・イズ・ベスト**
- 1画面1機能を徹底
- 複雑なUIコンポーネントは避けるドラッグ&ドロップ、カレンダーなど)
2. **情報の優先順位を明確に**
- 最もよく使う情報を最も目立つ位置に
- 圃場一覧では「名称」「作付け作物」「面積」を最優先表示
3. **エラーを起こしにくい設計**
- 入力必須項目は最小限に
- 選択式(ドロップダウン)を優先、自由入力は最小限
4. **スマホファースト(参照時)**
- 文字サイズ: 最低16px
- タップ領域: 最低44px×44px
- 横スクロールは避ける
5. **既存データを尊重**
- 役場データ(共済・中山間)の面積不整合は「そういうもの」として扱う
- ユーザーの運用を変えさせない(紙の台帳と同じ感覚で使える)
---
## 📅 開発フェーズ
**Phase 1MVP: 2025年2月まで**
- 作付け計画の登録・編集
- 申請書水稲共済・中山間のCSV出力
- 圃場一覧の参照PC/スマホ)
**Phase 2: 2025年3月**
- 栽培履歴の記録(播種日、農薬散布など)
- 作業予定のカレンダー表示
**Phase 3: 2025年度中**
- 資材計画(種苗・肥料・農薬の必要量計算)
- 収穫記録
**Phase 4: 将来**
- お米販売システムとの連携API経由
- スマート農業機器との連携(センサーデータ取込)

View File

@@ -0,0 +1,299 @@
# ユーザーストーリー
## 📖 ストーリー記法
```
【優先度】タイトル
As a ユーザー(役割)
I want ◯◯したい
So that △△できる(目的・価値)
【受け入れ基準】
- [ ] 条件1
- [ ] 条件2
```
---
## 🔴 Phase 1MVP- 必須機能
### P1-1: 作付け計画の一覧表示
**As a** 農家(システムの唯一のユーザー)
**I want** 全ての圃場と、それぞれに何を作付けしたかを一覧で見たい
**So that** 今年の作付け状況を俯瞰でき、未割当の圃場を見つけられる
**【受け入れ基準】**
- [ ] 全39筆の圃場が一覧表示される
- [ ] 各圃場に「名称」「面積」「今年の作付け作物」が表示される
- [ ] 作付け未設定の圃場は警告色(赤/黄)でハイライトされる
- [ ] 年度を切り替えられる2024年度、2025年度など
- [ ] PC・スマホ両方で見やすいレイアウト
**【UIイメージ】**
```
┌────────────────────────────────┐
│ 📅 2025年度 作付け計画 │
├────────────────────────────────┤
│ 🔍 検索: [___________] 🔽絞込 │
├────────────────────────────────┤
│┌────┬──────┬────┬──────────┐│
││名称 │面積 │作付 │操作 ││
│├────┼──────┼────┼──────────┤│
││田A │1.2反 │米 │ [編集] ││
││田B │0.5反 │❗未設定│ [割当] ││
│└────┴──────┴────┴──────────┘│
└────────────────────────────────┘
```
---
### P1-2: 圃場への作物割当
**As a** 農家
**I want** 各圃場に作物を割り当てたい
**So that** 今年の作付け計画を記録できる
**【受け入れ基準】**
- [ ] 圃場を選択して「作物」を設定できる
- [ ] 作物は以下から選択:
- 作付けしない: 休耕、緑肥、景観作物、その他野菜
- 作付けする: 米(品種選択)、トウモロコシ、エンドウ、野菜
- [ ] 品種も選択できる(例: 米 → にこまる、たちはるか、たちはるか特栽)
- [ ] 複数の圃場を一括選択して、同じ作物を割り当てられる
- [ ] 割当後、一覧画面に即座に反映される
**【作物マスタ】**
```
作付けしない:
- 休耕
- 緑肥
- 景観作物
- その他野菜
作付けする:
- 米
└ 品種: にこまる、たちはるか、たちはるか(特栽)
- トウモロコシ
└ 品種: (毎年変わる→自由入力)
- エンドウ
└ 品種: 久留米豊
- 野菜
└ 品種: (増減あり→自由入力)
```
---
### P1-3: 水稲共済細目書のPDF出力
**As a** 農家
**I want** 水稲共済細目書に必要なデータをPDFでダウンロードしたい
**So that** 2月と5月の申請時に、印刷してそのまま役場に提出できる
**【受け入れ基準】**
- [ ] 年度を指定してPDFをダウンロードできる
- [ ] PDFは表形式で、以下の列を含む:
```
耕地番号 | 分筆番号 | 地名地番 | 漢字地名 | 本地面積(m2) | 作付品目 | 品種 | 備考
```
- [ ] A4サイズ、縦向き、見やすいフォントサイズ10pt以上
- [ ] ヘッダーに「水稲共済細目書2025年度」などのタイトル
- [ ] ページ番号(複数ページになる場合)
- [ ] 共済マスタ31区画をベースに、紐づく実圃場の作付け情報を集約
- [ ] 複数の実圃場が1つの共済区画に紐づく場合、作物をカンマ区切りで列挙例: "米,野菜"
- [ ] 作付け未設定の共済区画も出力(空欄または「未設定」)
- [ ] ダウンロード前にプレビュー表示できる
**【集計ロジック】**
1. 共済マスタ(`水稲共済細目用.ods`の31区画をループ
2. 各共済区画に紐づく実圃場を取得(`吉田農地台帳`の`細目_耕地番号`/`細目_分筆番号`で結合)
3. 紐づく実圃場の作付け情報を集約(作物名をユニーク化してカンマ区切り)
4. HTMLテンプレートで表を生成 → PDF変換
---
### P1-4: 中山間交付金申請のPDF出力
**As a** 農家
**I want** 中山間地域等直接支払交付金の申請に必要なデータをPDFでダウンロードしたい
**So that** 5月の申請時に、印刷してそのまま役場に提出できる
**【受け入れ基準】**
- [ ] 年度を指定してPDFをダウンロードできる
- [ ] PDFは表形式で、以下の列を含む:
```
ID | 大字 | 字 | 地番 | 農地面積(m2) | 作付品目 | 品種 | 備考
```
- [ ] A4サイズ、縦向き、見やすいフォントサイズ10pt以上
- [ ] ヘッダーに「中山間地域等直接支払交付金2025年度」などのタイトル
- [ ] 中山間マスタ71区画をベースに、紐づく実圃場の作付け情報を集約
- [ ] 作付け未設定の区画も出力(空欄または「未設定」)
- [ ] ダウンロード前にプレビュー表示できる
**【集計ロジック】**
- 水稲共済と同様、中山間マスタをループして実圃場を集約 → PDF生成
---
### P1-5: 前年度作付け計画のコピー
**As a** 農家
**I want** 前年度の作付け計画を丸ごと新年度にコピーしたい
**So that** 毎年ゼロから入力せずに、微調整だけで済む
**【受け入れ基準】**
- [ ] 「前年度をコピー」ボタンを押すと、前年度の作付け情報が新年度に複製される
- [ ] 圃場マスタはコピーしない(マスタは共通)
- [ ] コピー後、作物の種類を個別に変更できる
- [ ] コピー前に確認ダイアログを表示(上書き防止)
---
### P1-6: スマホでの圃場情報参照
**As a** 農家
**I want** 田んぼにいるときに、スマホでその圃場の情報を見たい
**So that** 「この田んぼに植えた品種は何だっけ?」「面積はいくつだっけ?」をその場で確認できる
**【受け入れ基準】**
- [ ] スマホで圃場一覧を見られる
- [ ] 検索・絞り込み機能で目的の圃場を素早く見つけられる
- [ ] 圃場詳細画面で以下を確認:
- 名称
- 住所
- 面積
- 今年の作付け作物・品種
- (将来)過去の作付け履歴
- [ ] 文字サイズ: 16px以上
- [ ] タップ領域: 44px×44px以上
---
## 🟡 Phase 2 - 栽培履歴機能
### P2-1: 播種日・定植日の記録
**As a** 農家
**I want** 各圃場の播種日(種まき日)や定植日を記録したい
**So that** スマホで「いつ植えたか」を確認でき、次の作業(追肥など)のタイミングを判断できる
**【受け入れ基準】**
- [ ] 圃場ごとに「播種日」「定植日」を入力できる
- [ ] カレンダーUIで日付を選択
- [ ] スマホで過去の記録を閲覧できる
---
### P2-2: 農薬・肥料散布の記録
**As a** 農家
**I want** 除草剤や肥料をまいた日を記録したい
**So that** スマホで「いつ除草剤まいたか」を確認でき、次回の散布タイミングを判断できる
**【受け入れ基準】**
- [ ] 圃場ごとに「作業日」「作業内容」「使用資材」を入力
- [ ] 作業内容は選択式(播種、定植、除草剤散布、追肥、収穫など)
- [ ] スマホで作業履歴を時系列で閲覧
---
### P2-3: 作業予定のカレンダー表示
**As a** 農家
**I want** 今後の作業予定をカレンダーで見たい
**So that** 「来週は何をする予定だっけ?」を俯瞰できる
**【受け入れ基準】**
- [ ] 月間カレンダーで作業予定を表示
- [ ] 各圃場の作業予定を色分け
- [ ] 日付をクリックすると、その日の作業一覧を表示
---
## 🟢 Phase 3 - 資材計画機能
### P3-1: 種苗必要量の自動計算
**As a** 農家
**I want** 今年の作付け計画から、必要な種苗の量を自動計算してほしい
**So that** 種の発注漏れや過剰発注を防げる
**【受け入れ基準】**
- [ ] 作物ごとに「面積あたり必要量」をマスタ登録
- [ ] 作付け計画から、作物別の合計面積を算出
- [ ] 必要量を一覧表示(例: にこまる 30kg、トウモロコシ 5袋
---
### P3-2: 肥料・農薬の必要量計算
**As a** 農家
**I want** 施肥計画や農薬散布計画を立てたい
**So that** 資材の購入計画を立てられる
**【受け入れ基準】**
- [ ] 作物ごとの施肥基準をマスタ登録
- [ ] 作付け面積から必要な肥料量を計算
- [ ] 農薬も同様に計算
---
## 🔵 Phase 4 - 収穫・販売管理
### P4-1: 収穫記録
**As a** 農家
**I want** 収穫量を記録したい
**So that** 計画と実績を比較し、来年の計画精度を上げられる
---
### P4-2: お米販売システムとの連携
**As a** 農家
**I want** 作付け計画と収穫実績を、お米販売システムに自動連携したい
**So that** 在庫管理や販売計画を効率化できる
---
## 📊 優先度マトリクス
| ストーリー | 優先度 | Phase | 工数(想定) |
|-----------|--------|-------|------------|
| P1-1: 作付け計画一覧 | 🔴 高 | 1 | 2日 |
| P1-2: 作物割当 | 🔴 高 | 1 | 3日 |
| P1-3: 水稲共済PDF出力 | 🔴 高 | 1 | 3日 |
| P1-4: 中山間PDF出力 | 🔴 高 | 1 | 2日 |
| P1-5: 前年度コピー | 🔴 高 | 1 | 1日 |
| P1-6: スマホ参照 | 🔴 高 | 1 | 1日 |
| P2-1: 播種日記録 | 🟡 中 | 2 | 2日 |
| P2-2: 作業履歴 | 🟡 中 | 2 | 3日 |
| P2-3: カレンダー | 🟡 中 | 2 | 3日 |
| P3-1: 種苗計算 | 🟢 低 | 3 | 2日 |
| P3-2: 資材計算 | 🟢 低 | 3 | 2日 |
| P4-1: 収穫記録 | 🔵 将来 | 4 | TBD |
| P4-2: 販売連携 | 🔵 将来 | 4 | TBD |
---
## 🎯 Phase 1 完成の定義
以下が全て完了したら、Phase 1MVPは完成とする
1. **機能要件**
- [ ] 作付け計画を登録・編集できる
- [ ] 水稲共済細目書のPDFを出力できる
- [ ] 中山間交付金申請のPDFを出力できる
- [ ] 前年度の作付けをコピーできる
- [ ] スマホで圃場情報を参照できる
2. **品質要件**
- [ ] PCで快適に操作できるレスポンス1秒以内
- [ ] スマホで見やすい文字サイズ16px以上
- [ ] 出力されるPDFが正確で見やすい手動検証でOK
- [ ] PDFをA4用紙に印刷してそのまま提出できる
3. **ユーザビリティ**
- [ ] 作付け計画の登録が10分以内で完了する
- [ ] 申請書のダウンロードが3クリック以内で完了する
- [ ] スマホでの圃場検索が3タップ以内で完了する

418
03_データ仕様書.md Normal file
View File

@@ -0,0 +1,418 @@
# データ仕様書
## 📊 データ構造の全体像
このシステムで扱うデータは3種類
1. **実圃場データ**(吉田農地台帳.ods- 実際に作業する農地
2. **共済マスタ**(水稲共済細目用.ods- 申請書用の区画
3. **中山間マスタ**(中山間.ods- 申請書用の区画
**紐付けの関係:**
- 実圃場 → 共済区画: **M対1**複数の実圃場が1つの共済区画に対応
- 実圃場 → 中山間区画: **M対1**複数の実圃場が1つの中山間区画に対応
```mermaid
erDiagram
実圃場 }o--|| 共済区画 : "紐づく(M:1)"
実圃場 }o--|| 中山間区画 : "紐づく(M:1)"
実圃場 ||--o{ 作付け計画 : "持つ(1:N)"
実圃場 {
int id PK
string 名称
string 住所
float 面積_反
string 地主
int 細目_耕地番号 "共済紐付けキー"
int 細目_分筆番号 "共済紐付けキー"
int 中山間_ID "中山間紐付けキー"
}
共済区画 {
int id PK
string 地名_地番
int 耕地番号
int 分筆番号
float 本地面積_m2
string 漢字地名
}
中山間区画 {
int id PK
int ID
string 大字
string 字
int 地番
int 農地面積_m2
int 交付金額
}
作付け計画 {
int id PK
int 実圃場_id FK
int 年度
string 作物
string 品種
date 播種日
date 収穫日
}
```
---
## 1. 実圃場データ(吉田農地台帳.ods
### ファイル情報
- **行数:** 39行39筆の圃場
- **列数:** 7列
### カラム定義
| カラム名 | データ型 | 必須 | 説明 | 例 |
|---------|---------|-----|------|---|
| 名称 | string | ○ | 圃場の名称(自由記述) | "口神 1反2畝" |
| 住所 | string | ○ | 圃場の住所 | "口神笹ヶ谷374-1)" |
| 面積(反) | float | ○ | 面積(単位: 反※1反=1000m2=10a | 1.20 |
| 地主 | string | ○ | 地主の氏名 | "山崎 出祥" |
| 細目_耕地番号 | int | ○ | 共済マスタとの紐付けキー1/2 | 2 |
| 細目_分筆番号 | int | ○ | 共済マスタとの紐付けキー2/2 | 1 |
| 中山間_ID | int | △ | 中山間マスタとの紐付けキー | 50 |
### データサンプル
```
名称 住所 面積(反) 細目_耕地番号 細目_分筆番号 中山間_ID
口神 1反2畝 口神笹ヶ谷374-1) 1.20 2 1 50
口神 北東 口神笹ヶ谷374-1) 0.40 2 2 50
口神 北中 口神笹ヶ谷374-1) 0.43 2 2 50
```
### 特記事項
- **中山間_IDは一部NULL**: 39筆中2筆が中山間の対象外`NaN`
- **同じ共済区画に複数の実圃場**: 例えば共済キー「2-2」には3つの実圃場が紐づく
- **面積単位**: DB内部では「反」と「m2」の両方を保持する変換: 1反=1000m2
---
## 2. 共済マスタ(水稲共済細目用.ods
### ファイル情報
- **行数:** 31行31区画
- **列数:** 5列
### カラム定義
| カラム名 | データ型 | 必須 | 説明 | 例 |
|---------|---------|-----|------|---|
| 地名 地番 | string | ○ | 地名と地番(スペース区切り) | "四万十町 ササガタニ 374-1" |
| 耕地番号 | int | ○ | 共済区画の識別子1/2 | 2 |
| 分筆番号 | int | ○ | 共済区画の識別子2/2 | 1 |
| 本地面積 (m2) | float | ○ | 申請上の面積(単位: m2 | 25.4 |
| 漢字地名 | string | ○ | 漢字表記の地名 | "四万十町 笹ヶ谷 374-1" |
### データサンプル
```
地名 地番 耕地番号 分筆番号 本地面積(m2) 漢字地名
四万十町 ササガタニ 374-1 2 1 25.4 四万十町 笹ヶ谷 374-1
四万十町 ササガタニ 374-1 2 2 12.0 四万十町 笹ヶ谷 374-1
```
### 特記事項
- **面積の不整合は許容**: 役場データが古いため、実圃場の合計面積と一致しないことがある
- 例: 共済キー「2-2」の面積は12.0m2だが、実圃場の合計は1.33反=1330m2
- これは「そういうもの」として扱い、システム側で修正しない
- **重複キーなし**: (耕地番号, 分筆番号)の組み合わせは一意
---
## 3. 中山間マスタ(中山間.ods
### ファイル情報
- **行数:** 71行71区画
- **列数:** 17列うち使用するのは一部
### カラム定義(主要なもの)
| カラム名 | データ型 | 必須 | 説明 | 例 |
|---------|---------|-----|------|---|
| ID | int | ○ | 中山間区画の識別子 | 50 |
| 大字 | string | ○ | 大字名 | "口神ノ川" |
| 字 | string | ○ | 字名 | "壱町切" |
| 地番 | int | ○ | 地番 | 1694 |
| 農地面積 | int | ○ | 面積(単位: m2 | 2900 |
| 作付け品目 | string | △ | (役場が記入、システムでは上書き) | "ニラ" |
| 交付金額 | int | △ | 交付金額 | 37700 |
### データサンプル
```
ID 大字 字 地番 農地面積 作付け品目 交付金額
50 口神ノ川 笹ヶ谷 374 2698 米 xxxxxx
```
### 特記事項
- **使用する列は限定的**: システムでは主に「ID」「大字」「字」「地番」「農地面積」を使用
- **作付け品目は上書き**: 役場が記入した「作付け品目」は参考情報で、システムで上書きする
- **面積の不整合は許容**: 共済マスタと同様、実圃場との差異は受け入れる
---
## 4. 作付け計画データ(システム内部)
### テーブル定義
| カラム名 | データ型 | 必須 | 説明 |
|---------|---------|-----|------|
| id | int | ○ | 主キー(自動採番) |
| field_id | int | ○ | 実圃場ID外部キー |
| year | int | ○ | 年度2025など |
| crop | string | ○ | 作物(「米」「トウモロコシ」など) |
| variety | string | △ | 品種(「にこまる」など) |
| planting_date | date | △ | 播種日/定植日Phase 2 |
| harvest_date | date | △ | 収穫日Phase 2 |
| notes | text | △ | 備考 |
### 制約
- **ユニーク制約**: (field_id, year) - 1つの圃場に対して1年度につき1つの作付け計画のみ
- Phase 2で二毛作対応する場合は、この制約を見直す
---
## 5. 作物マスタ
### 作付けしない
- 休耕
- 緑肥
- 景観作物
- その他野菜
### 作付けする
#### 米
- 品種:
- にこまる
- たちはるか
- たちはるか(特栽)
#### トウモロコシ
- 品種: 自由入力(毎年変わるため)
#### エンドウ
- 品種: 久留米豊
#### 野菜
- 品種: 自由入力(増減あり)
---
## 6. データインポート仕様
### 初期セットアップ時
1. **共済マスタのインポート**
- `水稲共済細目用.ods` を読み込み
- `OfficialKyosaiField` テーブルに保存
2. **中山間マスタのインポート**
- `中山間.ods` を読み込み
- `OfficialChusankanField` テーブルに保存
3. **実圃場データのインポート**
- `吉田農地台帳.ods` を読み込み
- `Field` テーブルに保存
- 同時に共済・中山間マスタとの紐付けを確立:
- `細目_耕地番号` + `細目_分筆番号``OfficialKyosaiField.id` を外部キーとして保存
- `中山間_ID``OfficialChusankanField.id` を外部キーとして保存
### 紐付けロジック
```python
# 共済マスタとの紐付け
kyosai_record = OfficialKyosaiField.objects.get(
k_num=row['細目_耕地番号'],
s_num=row['細目_分筆番号']
)
field.kyosai_field_ref = kyosai_record.id
# 中山間マスタとの紐付け
if pd.notna(row['中山間_ID']):
chusankan_record = OfficialChusankanField.objects.get(
c_id=int(row['中山間_ID'])
)
field.chusankan_field_ref = chusankan_record.id
```
---
## 7. 申請書PDF出力ロジック
### 水稲共済細目書
**出力形式:**
- A4サイズ、縦向き
- ヘッダー: 「水稲共済細目書(◯◯年度)」
- 表形式(罫線あり)
- フォントサイズ: 10pt
- ページ番号(複数ページの場合)
**表の列:**
```
耕地番号 | 分筆番号 | 地名地番 | 漢字地名 | 本地面積(m2) | 作付品目 | 品種 | 備考
1 | 1 | 四万十町... | 四万十町... | 2.2 | 米 |にこまる|
2 | 1 | 四万十町... | 四万十町... | 25.4 | 米 |にこまる|
2 | 2 | 四万十町... | 四万十町... | 12.0 |米,野菜|にこまる,トマト|複数圃場
```
**集計ロジック:**
```python
def generate_kyosai_pdf(year):
# 1. データ集約CSVと同じロジック
output_rows = []
for kyosai in OfficialKyosaiField.objects.all().order_by('k_num', 's_num'):
fields = Field.objects.filter(kyosai_field_ref=kyosai.id)
plans = Plan.objects.filter(field__in=fields, year=year)
crops = list(set([p.crop.name for p in plans if p.crop]))
varieties = list(set([p.variety.name for p in plans if p.variety]))
row = {
'耕地番号': kyosai.k_num,
'分筆番号': kyosai.s_num,
'地名地番': kyosai.address,
'漢字地名': kyosai.kanji_name,
'本地面積(m2)': kyosai.area,
'作付品目': ','.join(crops) if crops else '未設定',
'品種': ','.join(varieties) if varieties else '',
'備考': f'{len(fields)}筆合算' if len(fields) > 1 else ''
}
output_rows.append(row)
# 2. HTMLテンプレートで表を生成
html = render_to_string('reports/kyosai_template.html', {
'year': year,
'rows': output_rows
})
# 3. HTML → PDF変換
pdf = HTML(string=html).write_pdf()
return pdf
```
**HTMLテンプレート例reports/kyosai_template.html:**
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
@page { size: A4; margin: 2cm; }
body { font-family: "MS Gothic", monospace; font-size: 10pt; }
h1 { text-align: center; font-size: 14pt; margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid black; padding: 5px; text-align: center; }
th { background-color: #f0f0f0; }
</style>
</head>
<body>
<h1>水稲共済細目書({{ year }}年度)</h1>
<table>
<thead>
<tr>
<th>耕地番号</th>
<th>分筆番号</th>
<th>地名地番</th>
<th>漢字地名</th>
<th>本地面積(m2)</th>
<th>作付品目</th>
<th>品種</th>
<th>備考</th>
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
<td>{{ row.耕地番号 }}</td>
<td>{{ row.分筆番号 }}</td>
<td>{{ row.地名地番 }}</td>
<td>{{ row.漢字地名 }}</td>
<td>{{ row.本地面積(m2) }}</td>
<td>{{ row.作付品目 }}</td>
<td>{{ row.品種 }}</td>
<td>{{ row.備考 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
```
### 中山間交付金申請
**出力形式:**
- A4サイズ、縦向き
- ヘッダー: 「中山間地域等直接支払交付金(◯◯年度)」
- 表形式(罫線あり)
**表の列:**
```
ID | 大字 | 字 | 地番 | 農地面積(m2) | 作付品目 | 品種 | 備考
50 | 口神ノ川 | 笹ヶ谷 | 374 | 2698 | 米,野菜 | にこまる,トマト | 7筆合算
```
**集計ロジック:**
- 水稲共済と同様、中山間マスタをループして実圃場を集約 → HTMLテンプレート → PDF
---
## 8. データ移行・メンテナンス
### 年度更新
- **圃場マスタ**: 年度をまたいで共通(更新不要)
- **作付け計画**: 年度ごとに独立(前年度コピー機能で複製)
### マスタデータの更新
- **共済・中山間マスタ**: 役場から新しいファイルをもらった場合、再インポート
- 既存データは上書きせず、差分を確認してマージ
- または、全削除→再インポートの2段階処理
### バックアップ
- 全テーブルをCSV/Excelでエクスポート可能にする
- 最低5年分のデータを保持補助金監査対応
---
## 9. 面積単位の扱い
システム内部では以下のように統一:
| 表示単位 | DB保存 | 変換式 |
|---------|--------|--------|
| 反(たん) | m2 | 1反 = 1000m2 |
| アールa | m2 | 1a = 100m2 |
| ヘクタールha | m2 | 1ha = 10000m2 |
**実装方針:**
- DB内部は全て `m2` で保存(整数型)
- 表示時にユーザー設定に応じて変換(デフォルトは「反」)
- 入力時は「反」で受け付け、内部で `m2` に変換
---
## 10. データ整合性チェック
### チェック項目
1. **紐付けの存在確認**
- 実圃場の `細目_耕地番号`/`細目_分筆番号` が共済マスタに存在するか
- 実圃場の `中山間_ID` が中山間マスタに存在するか
2. **面積の整合性(参考情報)**
- 1つの共済区画に紐づく実圃場の合計面積と、共済マスタの面積を比較
- ⚠️ 不整合があっても警告のみ(修正はしない)
3. **作付け未設定の検出**
- 指定年度で作付け計画が未設定の圃場をリストアップ
### 実装
- インポート時にバリデーションを実行
- 管理画面で「データ整合性レポート」を表示

575
04_画面設計書.md Normal file
View File

@@ -0,0 +1,575 @@
# 画面設計書
## 🎨 デザイン原則(再掲)
1. **シンプル・イズ・ベスト**: 1画面1機能
2. **情報の優先順位**: 最重要情報を最も目立つ位置に
3. **エラーを起こしにくい**: 選択式優先、自由入力最小限
4. **スマホファースト(参照時)**: 文字16px以上、タップ領域44px以上
5. **既存データを尊重**: 紙の台帳と同じ感覚で使える
---
## 📱 画面一覧
### Phase 1MVP
1. **ログイン画面**
2. **ダッシュボード**将来拡張用、Phase 1では簡易版
3. **作付け計画一覧**(メイン画面)
4. **作付け計画編集**(モーダル/サイドパネル)
5. **圃場詳細**(スマホ参照用)
6. **申請書ダウンロード**
7. **データ管理**(インポート・エクスポート)
### Phase 2以降
8. 栽培履歴入力
9. 作業カレンダー
10. 資材計画
---
## 画面1: ログイン画面
### 目的
シンプルな認証(メール+パスワード)
### レイアウトPC/スマホ共通)
```
┌─────────────────────────────────────┐
│ │
│ 🌾 Keina System │
│ 作付け計画管理システム │
│ │
│ ┌───────────────────────┐ │
│ │ メールアドレス │ │
│ │ [___________________] │ │
│ └───────────────────────┘ │
│ │
│ ┌───────────────────────┐ │
│ │ パスワード │ │
│ │ [___________________] │ │
│ └───────────────────────┘ │
│ │
│ [ ログイン ] │
│ │
└─────────────────────────────────────┘
```
### 機能要件
- [ ] メールアドレスとパスワードで認証
- [ ] ログイン成功 → ダッシュボードへ遷移
- [ ] ログイン失敗 → エラーメッセージ表示
- [ ] 「パスワードを忘れた」リンクPhase 2
---
## 画面2: ダッシュボード
### 目的
システムの入り口Phase 1では簡易版、将来拡張
### レイアウトPC
```
┌────────────────────────────────────────────────────┐
│ 🌾 Keina System 2025年度 ▼ 👤ログアウト │
├────────────────────────────────────────────────────┤
│ │
│ 📊 概要 │
│ ┌────────────────┬────────────────┬─────────────┐│
│ │ 全圃場数 │ 作付け済み │ 未割当 ││
│ │ 39筆 │ 35筆 │ 4筆 ❗ ││
│ └────────────────┴────────────────┴─────────────┘│
│ │
│ 🔗 クイックアクセス │
│ ┌──────────────────────────────────────────────┐│
│ │ [📝 作付け計画を編集] [📄 申請書ダウンロード] ││
│ └──────────────────────────────────────────────┘│
│ │
│ 📌 最近の変更 │
│ • 2025/02/10: 田A に「米(にこまる)」を割当 │
│ • 2025/02/09: 田B を「休耕」に変更 │
│ │
└────────────────────────────────────────────────────┘
```
### 機能要件Phase 1
- [ ] 年度選択(ドロップダウン)
- [ ] 作付け状況のサマリー表示
- [ ] 「作付け計画を編集」→ 画面3へ
- [ ] 「申請書ダウンロード」→ 画面6へ
---
## 画面3: 作付け計画一覧(メイン画面)
### 目的
全圃場の作付け状況を一覧で確認・編集
### レイアウトPC
```
┌──────────────────────────────────────────────────────────────┐
│ 🌾 Keina System > 作付け計画一覧 2025年度 ▼ 👤ログアウト │
├──────────────────────────────────────────────────────────────┤
│ │
│ 🔍 [検索: 圃場名・住所_____________] 🔽絞込: [全て▼] [作物▼]│
│ │
│ ☐ 未割当のみ表示 [📋 前年度をコピー] [📊 申請書作成] │
│ │
├──────────────────────────────────────────────────────────────┤
│ No. ☐ 名称 住所 面積 作付 品種 操作│
├──────────────────────────────────────────────────────────────┤
│ 1 ☐ おまけ 口神ノ川足川 351 0.2反 米 にこまる [編集]│
│ 2 ☐ 口神1反2畝 口神ノ川198... 1.2反 米 にこまる [編集]│
│ 3 ☐ 口神 北東 口神ノ川198... 0.4反 野菜 トマト [編集]│
│ 4 ☐ 口神 北中 口神ノ川198... 0.4反 ❗未設定 [割当]│
│ 5 ☐ 口神 北西 口神ノ川198... 0.5反 休耕 - [編集]│
│ │
│ ... (39行) │
│ │
├──────────────────────────────────────────────────────────────┤
│ ページ: 1 / 2 表示件数: [25件▼] │
└──────────────────────────────────────────────────────────────┘
```
### レイアウト(スマホ)
```
┌────────────────────────────────┐
│ 🌾 作付け計画 2025年度 ▼ ☰ │
├────────────────────────────────┤
│ 🔍 [検索_______________] [🔽] │
├────────────────────────────────┤
│ ┌────────────────────────────┐│
│ │ おまけ ││
│ │ 口神ノ川足川 351 ││
│ │ 0.2反 | 米(にこまる) ││
│ │ [編集] ││
│ └────────────────────────────┘│
│ │
│ ┌────────────────────────────┐│
│ │ 口神 北中 ││
│ │ 口神ノ川198... ││
│ │ 0.4反 | ❗未設定 ││
│ │ [割当] ││
│ └────────────────────────────┘│
│ │
│ ... (39圃場) │
│ │
└────────────────────────────────┘
```
### 機能要件
- [ ] 全圃場を一覧表示39行
- [ ] 各行に以下を表示:
- チェックボックス(一括操作用)
- 名称
- 住所
- 面積(反)
- 作付け作物(未設定の場合は警告色)
- 品種
- [編集]ボタン
- [ ] 検索機能:
- 圃場名・住所で部分一致検索
- リアルタイム絞り込み
- [ ] フィルタ機能:
- 作物で絞り込み(米、野菜、休耕など)
- 「未割当のみ」トグル
- [ ] 一括操作:
- 複数行を選択 → 「一括割当」ボタン表示
- 同じ作物を一括で割り当て
- [ ] [前年度をコピー]ボタン:
- 確認ダイアログ表示
- 前年度の作付けを全圃場にコピー
- [ ] [申請書作成]ボタン:
- 画面6へ遷移
### デザインノート
- **未割当の強調**: 赤または黄色の背景色
- **チェックボックスの位置**: 行の左端(スマホでも押しやすい)
- **スマホ版**: カード型レイアウト1圃場1カード
---
## 画面4: 作付け計画編集(モーダル)
### 目的
個別の圃場に作物を割り当てる
### レイアウトPC - モーダルウィンドウ)
```
┌────────────────────────────────────┐
│ 作付け計画を編集 [×] │
├────────────────────────────────────┤
│ │
│ 圃場: 口神 北中 │
│ 住所: 口神笹ヶ谷374-1) │
│ 面積: 0.4反 (400m2) │
│ │
│ ───────────────────────────────── │
│ │
│ 作物 * │
│ ┌────────────────────────────────┐│
│ │ [米 ▼] ││
│ └────────────────────────────────┘│
│ │
│ 品種 │
│ ┌────────────────────────────────┐│
│ │ [にこまる ▼] ││
│ └────────────────────────────────┘│
│ │
│ 備考 │
│ ┌────────────────────────────────┐│
│ │ [_____________________________]││
│ └────────────────────────────────┘│
│ │
│ [キャンセル] [保存] │
│ │
└────────────────────────────────────┘
```
### 作物選択のドロップダウン
```
┌────────────────────────────────────┐
│ 作付けしない │
│ • 休耕 │
│ • 緑肥 │
│ • 景観作物 │
│ • その他野菜 │
│ │
│ 作付けする │
│ • 米 │
│ • トウモロコシ │
│ • エンドウ │
│ • 野菜 │
└────────────────────────────────────┘
```
### 品種選択(作物に応じて動的に変化)
**作物=「米」の場合:**
```
┌────────────────────────────────────┐
│ • にこまる │
│ • たちはるか │
│ • たちはるか(特栽) │
│ • [その他: 自由入力_____________] │
└────────────────────────────────────┘
```
**作物=「トウモロコシ」の場合:**
```
┌────────────────────────────────────┐
│ [自由入力_____________________] │
│ (品種は毎年変わるため) │
└────────────────────────────────────┘
```
**作物=「休耕」などの場合:**
```
(品種選択フィールドは非表示)
```
### 機能要件
- [ ] 作物をドロップダウンで選択
- [ ] 作物に応じて品種選択が動的に変化
- [ ] 「作付けしない」系の作物は、品種選択を非表示
- [ ] 自由入力欄を用意(トウモロコシ、野菜など)
- [ ] [保存]ボタン → 作付け計画を保存して一覧に戻る
- [ ] [キャンセル]ボタン → 変更を破棄して一覧に戻る
### 一括割当の場合
- モーダルのタイトルを「一括割当」に変更
- 「圃場: 5件選択中」と表示
- 保存時、選択中の全圃場に同じ作付けを適用
---
## 画面5: 圃場詳細(スマホ参照用)
### 目的
田んぼにいるときに、その圃場の情報を確認
### レイアウト(スマホ)
```
┌────────────────────────────────┐
│ ← 一覧に戻る 2025年度 │
├────────────────────────────────┤
│ │
│ 口神 北中 │
│ │
│ 📍 住所 │
│ 口神笹ヶ谷374-1) │
│ │
│ 📏 面積 │
│ 0.4反 (400m2) │
│ │
│ 🌾 今年の作付け │
│ 米(にこまる) │
│ │
│ 📋 共済情報 │
│ 耕地番号: 2 / 分筆: 2 │
│ │
│ 📋 中山間情報 │
│ ID: 50 │
│ │
│ ───────────────────────────── │
│ │
│ 📅 過去の作付け履歴Phase 2
│ • 2024年: 米(にこまる) │
│ • 2023年: 米(にこまる) │
│ • 2022年: 休耕 │
│ │
└────────────────────────────────┘
```
### 機能要件
- [ ] 圃場の基本情報を見やすく表示
- [ ] 文字サイズ: 18px以上スマホで見やすく
- [ ] 余白: 十分に確保(誤タップ防止)
- [ ] 将来的に栽培履歴も表示Phase 2
---
## 画面6: 申請書ダウンロード
### 目的
水稲共済細目書・中山間交付金のPDFをダウンロード
### レイアウトPC
```
┌────────────────────────────────────────────────────┐
│ 🌾 Keina System > 申請書ダウンロード 👤ログアウト │
├────────────────────────────────────────────────────┤
│ │
│ 年度: [2025年度 ▼] │
│ │
│ ┌────────────────────────────────────────────────┐│
│ │ 📄 水稲共済細目書 ││
│ │ ││
│ │ 提出時期: 2月・5月年2回 ││
│ │ 区画数: 31区画 ││
│ │ ││
│ │ ⚠️ 未割当の圃場: 4筆 ││
│ │ → 作付け計画を完成させてください ││
│ │ ││
│ │ [プレビュー] [PDFダウンロード] ││
│ └────────────────────────────────────────────────┘│
│ │
│ ┌────────────────────────────────────────────────┐│
│ │ 📄 中山間地域等直接支払交付金 ││
│ │ ││
│ │ 提出時期: 5月年1回 ││
│ │ 区画数: 71区画 ││
│ │ ││
│ │ ✅ 全て割当済み ││
│ │ ││
│ │ [プレビュー] [PDFダウンロード] ││
│ └────────────────────────────────────────────────┘│
│ │
└────────────────────────────────────────────────────┘
```
### 機能要件
- [ ] 年度を選択
- [ ] 各申請書について:
- 区画数を表示
- 未割当の警告(ある場合)
- [プレビュー]ボタン → 新しいタブでPDFプレビュー
- [PDFダウンロード]ボタン → ファイルダウンロード
- [ ] ダウンロードされるPDFのファイル名:
- 水稲共済: `水稲共済細目書_2025年度.pdf`
- 中山間: `中山間交付金_2025年度.pdf`
- [ ] PDFはA4サイズ、印刷してそのまま提出可能
### プレビュー機能
[プレビュー]ボタンをクリックすると、新しいタブでPDFを表示
```
┌────────────────────────────────────────────────────┐
│ ← 戻る 水稲共済細目書_2025年度.pdf [印刷] [⬇] │
├────────────────────────────────────────────────────┤
│ │
│ 水稲共済細目書2025年度
│ │
│ ┌────────────────────────────────────────────────┐│
│ │ 耕地 分筆 地名地番 面積 作付 品種 ││
│ │ ──────────────────────────────────────────── ││
│ │ 1 1 四万十町 ... 2.2 米 にこまる ││
│ │ 2 1 四万十町 ... 25.4 米 にこまる ││
│ │ 2 2 四万十町 ... 12.0 米,野菜 ... ││
│ │ ││
│ │ ... (31行) ││
│ └────────────────────────────────────────────────┘│
│ │
│ 1 / 2 │
└────────────────────────────────────────────────────┘
```
- [ ] ブラウザの標準PDFビューアで表示
- [ ] 印刷ボタンで直接印刷可能
- [ ] ダウンロードボタンでローカル保存
---
## 画面7: データ管理
### 目的
圃場マスタや申請マスタのインポート・エクスポート
### レイアウトPC
```
┌────────────────────────────────────────────────────┐
│ 🌾 Keina System > データ管理 👤ログアウト │
├────────────────────────────────────────────────────┤
│ │
│ 📥 データインポート │
│ │
│ タブ: [吉田農地台帳] [共済マスタ] [中山間マスタ] │
│ │
│ ┌────────────────────────────────────────────────┐│
│ │ 吉田農地台帳 (実圃場データ) ││
│ │ ││
│ │ 最終更新: 2025/02/01 ││
│ │ 登録圃場数: 39筆 ││
│ │ ││
│ │ ファイルを選択: [ファイルを選択] 📎 ││
│ │ ││
│ │ ⚠️ 既存データは上書きされます ││
│ │ ││
│ │ [プレビュー] [インポート実行] ││
│ └────────────────────────────────────────────────┘│
│ │
│ 📤 データエクスポート │
│ │
│ • [全圃場データ (CSV)] ※バックアップ用 │
│ • [作付け計画 (CSV)] ※バックアップ用 │
│ • [バックアップ (全データ ZIP)] │
│ │
│ 📄 申請書PDF生成 │
│ → 「申請書ダウンロード」画面へ │
│ │
└────────────────────────────────────────────────────┘
```
### 機能要件
- [ ] 3種類のマスタをタブで切り替え
- [ ] ファイルアップロードODS/Excel対応
- [ ] プレビュー機能(インポート前に確認)
- [ ] インポート実行(確認ダイアログ付き)
- [ ] エクスポート機能CSV/ZIP
---
## 🎨 UI共通仕様
### カラーパレット
```
プライマリカラー(緑系):
#2E7D32 濃い緑(ヘッダー、ボタン)
#4CAF50 緑(アクセント)
#81C784 淡い緑(ホバー)
セカンダリカラー(土系):
#8D6E63 茶色(サブヘッダー)
#BCAAA4 淡い茶(背景)
警告・状態色:
#F44336 赤(エラー、未設定)
#FF9800 オレンジ(警告)
#4CAF50 緑(成功)
#2196F3 青(情報)
グレースケール:
#212121 ダークグレー(テキスト)
#757575 グレー(サブテキスト)
#E0E0E0 ライトグレー(ボーダー)
#FAFAFA ホワイト(背景)
```
### タイポグラフィ
```
フォント:
- システムフォント優先
- 日本語: "Hiragino Sans", "Yu Gothic", sans-serif
- 英数字: "Roboto", "Helvetica", sans-serif
サイズ:
- 見出し(h1): 28px / 太字
- 見出し(h2): 22px / 太字
- 本文: 16px / 通常
- 小文字: 14px / 通常
行間:
- 本文: 1.6
- 見出し: 1.3
```
### スペーシング
```
余白の基本単位: 8px
8px: 最小余白
16px: 標準余白(要素間)
24px: セクション間
32px: 画面の上下余白
```
### レスポンシブブレークポイント
```
スマートフォン: 〜767px
タブレット: 768px〜1023px
PC: 1024px〜
```
---
## 🖱️ インタラクション
### ボタン
- **プライマリボタン**: 緑背景、白文字、影付き
- **セカンダリボタン**: 白背景、緑文字、ボーダー付き
- **ホバー**: 10%明るく、カーソルpointer
- **押下**: 5%暗く、影を小さく
### モーダル
- **背景**: 半透明黒opacity: 0.5
- **アニメーション**: フェードイン0.2秒)
- **閉じる**: 背景クリック or [×]ボタン or Escキー
### トースト通知
- **位置**: 画面右上
- **表示時間**: 3秒自動消去
- **種類**: 成功(緑)、エラー(赤)、警告(オレンジ)、情報(青)
---
## 📱 スマホ特有の配慮
### タップ領域
- **最小サイズ**: 44px × 44px
- **余白**: ボタン間は最低8px
### スクロール
- **慣性スクロール**: `-webkit-overflow-scrolling: touch`
- **無限スクロール**: 不要全39筆なので一覧でOK
### 入力
- **キーボードタイプ**: 適切に指定email, number, textなど
- **オートコンプリート**: 有効化
### ナビゲーション
- **ハンバーガーメニュー**: 右上に配置
- **戻るボタン**: 画面左上に配置

315
05_実装優先順位.md Normal file
View File

@@ -0,0 +1,315 @@
# 実装優先順位とマイルストーン
## 🎯 MVPPhase 1の完成定義
以下の全てが完了したら、Phase 1は完成とする
### 機能完成基準
- [ ] ログイン・認証機能
- [ ] 作付け計画の一覧表示PC/スマホ)
- [ ] 作付け計画の編集(個別・一括)
- [ ] 水稲共済細目書のCSV出力
- [ ] 中山間交付金のCSV出力
- [ ] 前年度作付けのコピー機能
- [ ] 3種類のマスタデータインポート
### 品質基準
- [ ] PCで快適に操作できるレスポンス1秒以内
- [ ] スマホで見やすい文字16px以上
- [ ] 出力されるCSVが正確手動検証でOK
### ユーザビリティ基準
- [ ] 作付け計画の登録が10分以内で完了
- [ ] 申請書のダウンロードが3クリック以内
- [ ] スマホでの圃場検索が3タップ以内
**目標完成日: 2025年2月末水稲共済の1回目申請に間に合わせる**
---
## 📅 実装スケジュール10日間想定
### Day 1-2: 環境構築 & 基盤実装
**Day 1: プロジェクトセットアップ**
- [ ] Dockerコンテナの構築
- PostgreSQL (PostGIS拡張)
- Django (バックエンド)
- Next.js (フロントエンド)
- [ ] Djangoプロジェクト初期化
- `django-admin startproject`
- GIS設定 (`GDAL`, `GEOS`)
- 環境変数管理 (`.env`)
- [ ] Next.jsプロジェクト初期化
- `create-next-app`
- Tailwind CSS設定
- 環境変数管理
**Day 2: 認証機能**
- [ ] Django: ユーザーモデル (メール認証)
- [ ] Django: JWT認証設定 (`djoser`, `djangorestframework-simplejwt`)
- [ ] Next.js: ログイン画面
- [ ] Next.js: 認証コンテキスト (`AuthContext`)
- [ ] 動作確認: ログイン→ダッシュボード遷移
---
### Day 3-4: データモデル & インポート機能
**Day 3: データベース設計**
- [ ] Django: モデル定義
- `Field` (実圃場)
- `OfficialKyosaiField` (共済マスタ)
- `OfficialChusankanField` (中山間マスタ)
- `Plan` (作付け計画)
- `Crop` (作物マスタ)
- `Variety` (品種マスタ)
- [ ] マイグレーション実行
- [ ] 管理画面での動作確認
**Day 4: インポート機能**
- [ ] Django: インポートAPI実装
- `pandas` + `odfpy` でODS読み込み
- 共済マスタインポート
- 中山間マスタインポート
- 吉田農地台帳インポート(紐付け処理含む)
- [ ] Next.js: データ管理画面
- ファイルアップロードUI
- プレビュー表示
- インポート実行ボタン
- [ ] 動作確認: 実際のODSファイルをインポート
---
### Day 5-6: 作付け計画機能(コア機能)
**Day 5: 作付け計画API**
- [ ] Django: 作物・品種マスタの初期データ投入
- [ ] Django: 作付け計画API
- 一覧取得 (`GET /api/plans/?year=2025`)
- 作成・更新 (`POST /api/plans/`, `PATCH /api/plans/{id}/`)
- 一括更新 (`POST /api/plans/bulk/`)
- 前年度コピー (`POST /api/plans/copy_from_previous_year/`)
- [ ] API動作確認 (Postman or curl)
**Day 6: 作付け計画UI**
- [ ] Next.js: 作付け計画一覧画面
- テーブル表示
- 検索・フィルタ機能
- 未割当のハイライト
- [ ] Next.js: 作付け計画編集モーダル
- 作物選択(ドロップダウン)
- 品種選択(動的に変化)
- 一括割当対応
- [ ] Next.js: 前年度コピーボタン
- [ ] 動作確認: 作付け計画を実際に入力
---
### Day 7-8: 申請書出力機能
**Day 7: 申請書ロジック実装**
- [ ] Django: 水稲共済細目書PDF生成
- 共済マスタをベースに集約
- 紐づく実圃場の作付けを名寄せ
- HTMLテンプレート作成
- WeasyPrintでPDF変換
- [ ] Django: 中山間交付金PDF生成
- 中山間マスタをベースに集約
- HTMLテンプレート作成
- [ ] 動作確認: PDFの内容と見た目を手動チェック
**Day 8: 申請書ダウンロードUI**
- [ ] Next.js: 申請書ダウンロード画面
- 年度選択
- プレビュー表示新しいタブでPDF
- PDFダウンロードボタン
- [ ] 動作確認: PDFをダウンロードして印刷してみる
---
### Day 9: スマホ対応 & UI調整
**Day 9: レスポンシブ対応**
- [ ] Next.js: スマホ用レイアウト調整
- 作付け計画一覧(カード型)
- 圃場詳細画面
- タップ領域の調整
- [ ] 文字サイズ・余白の調整
- [ ] 動作確認: 実機またはChrome DevToolsのモバイルビュー
---
### Day 10: テスト & 微調整
**Day 10: 総合テスト**
- [ ] 全機能の動作確認
- ログイン→作付け計画編集→申請書ダウンロードの一連の流れ
- 前年度コピー→編集→保存
- スマホでの参照
- [ ] バグ修正
- [ ] ドキュメント整備README.md、使い方ガイド
- [ ] 本番デプロイ準備Dockerイメージのビルド
---
## 🔧 技術スタックの詳細
### バックエンド (Django)
| 項目 | 採用技術 | 理由 |
|------|---------|------|
| フレームワーク | Django 5.0 | 安定性、豊富なエコシステム |
| REST API | Django REST Framework | 標準的なAPI構築ツール |
| 認証 | djoser + SimpleJWT | JWT認証の簡単な実装 |
| GIS | GeoDjango (PostGIS) | 地理情報の扱いに最適 |
| ファイル解析 | pandas + odfpy | ODS/Excelの読み込みに対応 |
| PDF生成 | WeasyPrint | HTML→PDF変換、日本語対応 |
| データベース | PostgreSQL 16 + PostGIS 3.4 | 空間データの保存・検索 |
### フロントエンド (Next.js)
| 項目 | 採用技術 | 理由 |
|------|---------|------|
| フレームワーク | Next.js 14 (App Router) | SSR/SSG対応、モダンな開発体験 |
| スタイリング | Tailwind CSS | 高速なUI開発 |
| 状態管理 | React Context API | シンプルな認証状態管理 |
| HTTPクライアント | fetch API (native) | 軽量、標準API |
| テーブル | react-table (TanStack Table) | 高機能なテーブルコンポーネント |
### インフラ (Docker)
| 項目 | 採用技術 | 理由 |
|------|---------|------|
| コンテナ化 | Docker Compose | 開発・本番環境の統一 |
| データベース | postgis/postgis:16-3.4 | PostGIS公式イメージ |
| リバースプロキシ | Nginx (開発環境) | 静的ファイル配信 |
---
## 📦 実装の粒度(コードレベル)
### 最小限の実装で済むもの
- ログイン画面: メール+パスワードのみ(パスワードリセットは後回し)
- ダッシュボード: サマリー表示のみ(グラフは不要)
- 地図機能: Phase 1では不要住所テキストのみ
### しっかり作り込むもの
- 作付け計画一覧: 検索・フィルタ・ソート機能
- 編集モーダル: 入力バリデーション、エラー表示
- 申請書CSV: 正確なデータ集計ロジック
### Phase 2以降に回すもの
- 栽培履歴(播種日、作業記録)
- カレンダー表示
- 資材計画
- 過去年度の比較
---
## 🚀 デプロイ計画
### 開発環境
- ローカルマシン: Docker Compose
- URL: `http://localhost:3000`
### 本番環境Phase 1後
- サーバー: VPS or クラウドAWS/GCP/さくらVPS
- ドメイン: `keina.example.com` (仮)
- HTTPS: Let's Encrypt
- リバースプロキシ: Traefik or Nginx
### バックアップ戦略
- データベース: 毎日自動バックアップ(`pg_dump`
- ファイル: CSVエクスポートでユーザー自身がバックアップ
---
## 🧪 テスト戦略
### Phase 1では自動テスト不要
- **理由**: シングルユーザー、手動検証で十分
- **代わりに**: 手動チェックリストで品質保証
### 手動チェックリスト
#### 機能テスト
- [ ] ログインできる
- [ ] 作付け計画を登録できる
- [ ] 作付け計画を編集できる
- [ ] 一括割当ができる
- [ ] 前年度コピーができる
- [ ] 水稲共済PDFをダウンロードできる
- [ ] 中山間PDFをダウンロードできる
- [ ] PDFをプレビュー表示できる
- [ ] スマホで圃場詳細を見られる
#### データ整合性テスト
- [ ] 共済PDFの耕地番号が正しい
- [ ] 中山間PDFのIDが正しい
- [ ] 作物の名寄せが正しい(重複排除)
- [ ] 未割当の圃場が適切に扱われる
- [ ] PDFの表レイアウトが整っている
- [ ] PDFをA4用紙に印刷して見やすい
#### UI/UXテスト
- [ ] PCで見やすい文字サイズ、余白
- [ ] スマホで見やすい(タップ領域、スクロール)
- [ ] エラーメッセージがわかりやすい
- [ ] ローディング中の表示
---
## 🔍 潜在的な技術的課題と対策
### 課題1: PostGISの設定
**問題**: Dockerコンテナ内でGDAL/GEOSのパスが通らない
**対策**: 公式PostGISイメージを使用、Djangoの`GDAL_LIBRARY_PATH`を明示的に設定
### 課題2: ODSファイルの文字コード
**問題**: 日本語の文字化け
**対策**: `pd.read_excel(..., engine='odf')` でUTF-8として読み込み
### 課題3: 作物の名寄せロジック
**問題**: 複数の実圃場が1つの共済区画に紐づく場合の集約
**対策**: Pythonのセットで重複排除 → カンマ区切りで結合
### 課題4: スマホでのテーブル表示
**問題**: 横スクロールが発生
**対策**: カード型レイアウトに変更Tailwindの`@media`クエリ)
### 課題5: 大量データのパフォーマンス
**問題**: Phase 2以降、栽培履歴が増えると遅くなる
**対策**: ページネーション、インデックス最適化Phase 2で対応
---
## 📝 開発時の注意点
### コーディング規約
- **Python**: PEP 8準拠、型ヒント推奨
- **JavaScript**: ESLint + Prettier、関数コンポーネント優先
- **命名**: 英語camelCase or snake_case、略語は避ける
### コミットメッセージ
```
feat: 作付け計画一覧APIを実装
fix: 共済CSVの面積計算バグを修正
docs: READMEにセットアップ手順を追加
style: Tailwindクラスを整理
```
### ブランチ戦略
- `main`: 本番環境
- `develop`: 開発環境
- `feature/*`: 機能開発
---
## 🎉 Phase 1完成後の振り返り
完成したら、以下を実施:
1. **使用感チェック**: 実際に作付け計画を入力してみる
2. **申請書検証**: 出力されたCSVを役場の書式と照合
3. **改善点の洗い出し**: 「ここがもっとこうだったら...」を記録
4. **Phase 2の要件整理**: 栽培履歴機能の詳細を詰める