0. この資料の使い方
上から順番に読む必要はありません。まず「全体像」をつかみ、 次に自分でコードを開いて、説明と実装を照らし合わせてください。
① 予想する
コードを読む前に「このファイルは何をするはずか」を一度考えます。外れていても問題ありません。
② 1つだけ追う
いきなり全機能を理解せず、「収入が画面から計算結果に届く流れ」など、1本の流れだけ追います。
③ 動かして確かめる
テスト、ログ、画面の入力で予想を検証します。理解は「読んだ量」より「確かめた回数」で深まります。
ソフトウェア開発では、経験者も必要な場所を検索しながら作業します。 大切なのは、目的を小さく分け、変更前後をテストで確認する習慣です。
このページは実装と一緒に更新する学習資料です。最終照合日は 2026-08-16です。コードを変更したら、リンク先の実装・テスト・READMEと このページの説明が一致しているかも確認してください。
1. 学習前のセルフチェック
合否を決めるためではなく、どこから読み始めるかを決めるための確認表です。 すべてに答えられなくても問題ありません。答えられない項目を、次に読む節の目印にしてください。
| テーマ | セルフチェックの質問 | 到達の目安 | 次の練習 |
|---|---|---|---|
| システムの目的 | このアプリは誰の、どのような課題を解決するものか説明できるか? | 一文で説明 | READMEと「全体像」を読み、入力・計算・保存・表示を結びつける。 |
| Pythonモデル | 変数、関数、クラス、型の違いを、簡単な例で説明できるか? | 例を書ける | 「基礎用語」とengine/models.pyの抜粋を読む。 |
| Webの流れ | フォームの入力がHTTP、FastAPI、モデル、DB、HTMLを通る順番を追えるか? | 順番を説明 | 「Webの流れ」の図で、GETとPOSTの違いを確認する。 |
| 月次計算 | 収入、税、支出、残高が月ごとに更新される理由を説明できるか? | 項目を追える | MonthlyCashflowの項目とcashflow.pyのループを読む。 |
| 構成の分離 | engine、web、db、mcp_serverの役割を予想できるか? |
役割を説明 | リポジトリ探検表と1本のリクエストの流れを照らし合わせる。 |
| パラメータ設計 | 制度の数値をコードから分離する理由と、適用日・出典の意味を説明できるか? | 理由を説明 | YAMLとParameterStoreの節で日付による値の切り替えを確認する。 |
| テスト・デバッグ | 不具合を再現し、期待値を決め、修正後に再発しないことを確認できるか? | 手順を実行 | テストの基本形と「不具合修正の手順」を小さな例で試す。 |
| 新規プロジェクト | 誰の課題を解くか、MVPで何を作らないかを決められるか? | 範囲を定義 | 「新規プロジェクト」の設計質問を使って1ページに整理する。 |
たとえばWebの流れが曖昧なら、まず「Webの流れ」と「コードの読み方」を読み、 実際のフォーム、モデル、DB保存、テンプレートを1本だけ追います。
2. まず全体像:このリポジトリは何をするか
FPシミュレーターは、家族・収入・支出・年金・住宅・ローンなどを入力として受け取り、 世帯の将来を月ごとに計算します。結果はWeb画面から見られ、 SQLiteに保存でき、さらにLLMがMCPツールとして参照・計算・更新できます。
図1:入口(ブラウザ/MCP)と、共通の計算・保存の層
このシステムを一文で言うと
「画面が見えること」だけがアプリではありません。入力を正しいデータに変換し、 計算し、保存し、結果を別の入口からも利用できるようにする一連の仕組みがアプリケーションです。
3. 先に覚える基礎用語
用語は単独で暗記せず、「このリポジトリのどこに登場するか」とセットで覚えます。
Python:データと処理
変数は値に名前をつける箱、関数は入力から出力を作る処理、クラスはデータと関連する操作をまとめる設計図です。
def net(income, tax, expense):
return income - tax - expense
result = net(300000, 50000, 180000)
# result は 70000
この小さな関数が、月次収支の考え方を最小化した例です。
型:値の取り違えを防ぐ
intは整数、strは文字列、dateは日付、listは複数の値です。型を明示すると、「日付に文字列を足す」のような間違いを早く見つけられます。
monthly_amount: int
start_month: int
bonus_months: list[int]
実際の定義は engine/models.py にあります。
Web:リクエストとレスポンス
ブラウザがURLへアクセスするとリクエストが送られ、サーバーがHTMLやJSONをレスポンスとして返します。フォーム送信は、入力値を含むPOSTリクエストです。
HTMLのひな型はJinja2で値を埋め込み、部分更新には
HTMXを使います。実装はweb/templates/と
web/main.pyを対応させて読みます。
DB:後で取り出すための保存
メモリ上の変数はプログラム終了で消えます。SQLiteのようなデータベースに保存すると、次のリクエストや再起動後にも世帯データを取り出せます。
INSERT INTO households
(id, name, data, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE ...
新規作成と既存データの更新を1つのSQLで扱う方式をUPSERTと呼びます。
非同期(async / await)
DBやネットワークの待ち時間に、サーバーが他の処理を進めやすくする書き方です。async defは非同期関数、awaitは完了を待つ場所を表します。
まずは「DBを待つ処理にはawaitがつく」と理解し、詳細は小さな非同期関数で練習します。
テスト:動作を固定する例
テストは「この入力ならこの出力になる」という実行可能な説明です。変更後に自動実行して、以前の機能が壊れていないかを確認します。
テストは開発者への採点ではなく、未来の変更から守る安全網です。
4. リポジトリ探検:どのファイルから読むか
ファイル名を全部覚える必要はありません。変更したいものから、入口 → データ → 処理 → 出力の順で探します。
| 場所 | 役割 | 最初に見るポイント | 代表ファイル |
|---|---|---|---|
src/fp_simulator/engine/ |
ライフプランのドメイン計算 | 入力と出力、月次ループ、税・支出の合計 | cashflow.py |
src/fp_simulator/engine/models.py |
世帯、家族、収入、支出などの形 | 必須/任意、型、入力値の検証 | models.py |
src/fp_simulator/web/ |
FastAPIのルートとHTMLテンプレート | GETで表示、POSTで保存、計算結果の表示 | main.py |
src/fp_simulator/web/templates/ |
ブラウザへ返すHTML | フォームのname属性、送信先action、表示する変数 | base.html |
src/fp_simulator/db/ |
SQLiteへの保存と取得 | SQL、JSON化、モデルへの復元 | database.py |
src/fp_simulator/parameters/ + parameters/ |
制度の数値と適用日・出典 | パラメータパス、日付、source | loader.py |
src/fp_simulator/mcp_server/ |
LLMから呼べるツール | 引数、JSONレスポンス、計算エンジンの再利用 | server.py |
src/fp_simulator/web/auth.py |
IAP認証とMCP APIキー認証 | ローカルと本番の認証条件、認証ヘッダー、401/503 | auth.py |
tests/ |
動作の確認と回帰防止 | テスト名、入力、期待値、失敗時の意味 | test_cashflow_integration.py |
docs/要件定義書.md |
なぜ作るか、何を作るかの記録 | 目的、MVP、将来拡張、前提条件 | 要件定義書.md |
docs/引き継ぎ資料.md |
運用・開発を引き継ぐための記録 | 現状、注意点、次に確認すること | 引き継ぎ資料.md |
.github/workflows/ + deploy/ |
教材公開・本番デプロイ・バックアップ | GitHub Pages、Docker、Cloud Run、Litestream | pages.yml / deploy/ |
出力側の画面をたどる
入力ウィザードを読んだ後は、main.pyのルートとテンプレートを対応させると、
計算結果がどのように表示されるかを確認できます。
| 入口・テンプレート | 役割 | 読むポイント |
|---|---|---|
/households/{id}/simulate → result.html |
通常のシミュレーション結果 | simulate()のサマリーとライフマップの表示 |
/households/{id}/simulate/monthly → monthly_result.html |
月ごとの明細 | MonthlyCashflowの各項目とトレース |
/households/{id}/compare → compare.html |
プランの比較 | ベースラインと代替案が同じエンジンを使うこと |
/households/{id}/disaster → disaster.html |
災害・死亡などのシナリオ | simulate(..., scenario)の第3引数 |
/households/{id}/audit → audit.html |
操作履歴・監査ログ | 誰がどの入口から何を変更したか |
lifemap.html |
複数画面で再利用するJinja2マクロ | 独立したルートではなく、結果やウィザードへ埋め込まれる部品 |
/debug/parameters → debug_parameters.html |
読み込んだ制度パラメータの確認 | YAML、ParameterStore、適用日を照合する |
読む順番のテンプレート
- README.mdで目的、起動方法、構成を確認する。
- 要件定義書で「正しい動作」の背景と用語を確認する。
- 引き継ぎ資料で、現在の運用上の注意点と未完了の課題を確認する。
- 目的に近いテストを先に読み、入力と期待結果を知る。
- そのテストが呼ぶengine / modelsを読む。
- 最後にweb / template / dbを読んで、入口と保存をつなげる。
main.pyやcashflow.pyは大きなファイルです。
まず関数名やURL、テスト名を検索し、関係する範囲だけを読んでください。
5. Webの流れ:入力フォームから結果画面まで
HTTPからFastAPI、モデル、DB、テンプレートへ進む流れを、1本の処理として見てみましょう。 ここでは「新しい世帯を作る」操作を例にします。
図中の「田中家」や金額は説明用の架空データです。実在する個人情報や家計情報を入力して 公開環境へ送信しないでください。
図2:POSTで受け取り、モデルで検証し、DBへ保存して次の画面へ進む
GETとPOSTを区別する
GET:見せる・取得する
URLを開いたときの処理です。例:@app.get("/households/{household_id}/members")。DBから世帯を取得し、Jinja2テンプレートに値を渡してHTMLを返します。
POST:送る・変更する
フォームの送信など、データを変更する処理です。例:@app.post("/households/{household_id}/members")。入力をモデルにし、保存した後に次のGETへリダイレクトします。
POSTの保存が終わった後に303(See Other)でGETのURLへ移す設計を POST/Redirect/GETと呼びます。ブラウザの再読み込みでPOSTを再送して 二重保存する事故を避け、結果画面をGETとして安全に開けるようにします。
①フォームのname → ②POST関数の引数 → ③モデルの値 →
④DB保存 → ⑤GETの取得 → ⑥テンプレートの変数、の順に確認します。
6. 計算エンジンを理解する:月次キャッシュフロー
このリポジトリの中心は engine/cashflow.py です。
世帯主の基準年月から想定寿命まで、月を1つずつ進めながら収入・税・支出・残高を計算します。
Webの/households/{household_id}/simulate、比較画面、災害シナリオ、
月次結果画面はそれぞれmain.pyからsimulate()を呼びます。
MCPのrun_simulationも同じエンジンを再利用します。入口が違っても計算を共有する設計です。
図3:月次の計算順序は、月ごとの支払いや税のタイミングを表現する
月末残高 = 前月末残高 + 収支
MonthlyCashflowを読む
@dataclass
class MonthlyCashflow:
date: datetime.date
age: int
salary_income: int = 0
pension_income: int = 0
income_tax: int = 0
living_expense: int = 0
loan_payment: int = 0
balance: int = 0
@property
def net(self) -> int:
return self.total_income - self.total_tax_si - self.total_expense
これは完全なコードではなく、読み方を示す抜粋です。
@dataclassはデータをまとめるための仕組み、
@propertyはオブジェクトから計算結果を読むための仕組みです。
netは保存された入力ではなく、他の項目から導く値です。
なぜ月次なのか
税のタイミング
所得税は月次源泉徴収と年末調整、住民税は前年所得をもとに翌年6月から、というずれがあります。
ローンの返済
元利均等・元金均等の返済や、ボーナス月、繰上返済を月ごとに表す必要があります。
イベント・教育費
入学、車検、住宅購入など、特定の月に大きな支出が発生します。
7. コードの読み方:モデル・パラメータ・保存
7-1. Pydanticモデルは「データの契約」
engine/models.pyのモデルは、「世帯データはどんな形でなければならないか」という契約です。
画面、DB、計算エンジン、MCPが同じ契約を使うことで、層ごとに別の形へ変換する量を減らせます。
class Income(BaseModel):
id: str
member_id: str
monthly_amount: int
bonus_months: list[int] = Field(default_factory=list)
annual_raise_rate: float = 0.0
これは実装の一部を抜き出した例です。完全な定義と他の項目はengine/models.pyを確認してください。
必須項目
デフォルト値がない項目は、入力しないとモデルを作れません。たとえばidやmonthly_amountです。
検証ルール
model_validatorで、頭金が物件価格を超えないなど、1項目だけでは判断できない条件を確認できます。
画面の文字列をそのまま計算に渡さず、まず「正しい形のデータ」に変換します。 これにより、計算エンジンはブラウザの都合を知らずに済み、テストもしやすくなります。
7-2. YAMLパラメータは「変わる制度」と「変わりにくい計算」を分ける
税率や控除額は法改正で変わる可能性があります。
parameters/に適用開始日と出典を保存し、ParameterStoreが計算日から有効な値を選びます。
- path: 所得税.基礎控除.控除額
description: 基礎控除
values:
- from: 2013-01-01
value: 380000
source: https://www.nta.go.jp/taxes/shiraberu/taxanswer/shotoku/1199.htm
- from: 2020-01-01
value: 480000
source: https://www.nta.go.jp/taxes/shiraberu/taxanswer/shotoku/1199.htm
- from: 2025-01-01
value: 580000
source: https://www.nta.go.jp/taxes/shiraberu/taxanswer/shotoku/1199.htm
ParameterStoreは、計算日以前のfromのうち最も新しい値を選びます。
まだ適用開始日前なら最古の値を使います。既定のparameters/以外を使う本番設定には
FP_PARAMETERS_DIR、テストでストアを作り直すときにはreset_store()を確認します。
図4:計算ロジックを大きく変えず、制度の値を時系列で更新できる
7-3. DBはモデルJSONを保存する
このリポジトリでは、世帯全体をPydanticのJSONとしてSQLiteのdata列に保存します。
まずはシンプルな構成で、保存時と取得時に同じモデルへ変換します。
# 保存
data = household.model_dump_json()
await db.execute(
"""INSERT INTO households (id, name, data, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
name=excluded.name, data=excluded.data, updated_at=excluded.updated_at""",
(household.id, household.name, data, now, now),
)
# 取得
household = Household.model_validate_json(row[0])
ON CONFLICT(id) DO UPDATEは、IDが未登録ならINSERT、登録済みならUPDATEに切り替える
UPSERTです。保存関数を読むときは、新規作成だけでなく既存世帯の更新時にどの列が変わるかも確認します。
これは実装を短くした抜粋です。一般的な大規模システムでは、検索しやすいように項目を別テーブルへ分解する設計もあります。 ここでは「ドメインモデルをそのまま保存する」というシンプルさとのトレードオフを学べます。
8. テストを読む・書く:仕様を実行可能にする
テストは、実装を理解するための入口でもあります。 「何を入力し、何を期待しているか」が短く書かれているため、巨大な実装を読む前に仕様を把握できます。
テストの基本形(Arrange / Act / Assert)
import datetime
from fp_simulator.engine.cashflow import MonthlyCashflow
def test_net_is_income_minus_tax_and_expense():
# Arrange(準備)
monthly = MonthlyCashflow(
date=datetime.date(2026, 1, 1),
age=40,
salary_income=300000,
income_tax=50000,
living_expense=180000,
)
# Act(実行)
result = monthly.net
# Assert(確認)
assert result == 70000
必須項目のdateとageを含めた、実際に実行できる最小例です。
- 再現条件を小さくする:家族全員ではなく、最小の世帯・1つの収入・1つの支出にする。
- 期待値を決める:手計算できる値、または要件・制度資料から正しい値を決める。
- 失敗を確認する:修正前にテストが失敗すると、不具合の再現に成功したことになる。
- 修正する:最小の変更を入れ、同じテストが通ることを確認する。
- 全体を確認する:関連テストを実行してデグレがないか見る。
3か月後にコードを見返したとき、テストの入力と期待値が「この機能の大事なルール」を教えてくれます。
9. 不具合修正の手順:入力から出力までを追う
「入力が関数に渡り、意図した出力になるか処理の流れを追う」という考え方を、 再現・観測・修正・回帰確認のループにします。
図5:ログだけで終わらず、テストで再現条件を固定する
例:「給与を入力したのに収入が0円」
- 画面で再現:最小の世帯、1人のメンバー、1件の給与、対象月を決める。
- POSTを確認:フォームの
nameとincomes_addの引数名・型を照合する。 - モデルを確認:
Income.member_idが存在するメンバーを指しているか、月額が数値になっているか確認する。 - 計算を確認:
cashflow.pyで対象月・開始年齢・終了年齢の条件に入っているか確認する。 - 中間値を観測:必要ならログで給与モデル、対象日、計算された
salary_incomeを出す。 - 再現テストを追加:同じ入力をテストデータにし、修正前に失敗、修正後に成功することを確認する。
関係しそうなコードを一度にたくさん変更すること。 何が原因だったか分からなくなり、別の不具合を生む可能性があります。 1つの仮説につき、できるだけ1つの変更にします。
10. MCPを理解する:LLM用の別の入口
MCP(Model Context Protocol)は、 LLMアプリケーションが外部の機能を「ツール」として呼び出すための共通の接続方法です。 このリポジトリでは、Web画面とは別に、世帯の参照・シミュレーション・金額の説明・更新を提供します。
図6:MCPは新しい計算エンジンではなく、既存機能をLLMから呼ぶ入口
図には代表的なツールを載せています。実際の定義は
mcp_server/server.pyにあり、世帯・税制パラメータの参照系と、
シミュレーション・明細・根拠説明・更新系に分かれています。
実装されているMCPツール
| ツール | 役割 | 主な入力 |
|---|---|---|
list_households |
世帯の一覧を返す | なし |
get_household |
世帯の詳細JSONを返す | household_id |
list_tax_parameters |
利用可能な税制パラメータのパスを返す | なし |
get_tax_parameter |
指定日の値と出典を返す | path、任意のdate |
run_simulation |
シミュレーションのサマリーを返す | household_id |
get_cashflow |
指定年月のキャッシュフローを返す | household_id、年、月 |
explain_amount |
金額の計算根拠を返す | 世帯、年月、項目 |
update_household |
世帯JSONを検証して更新する | household_id、世帯JSON |
ツールの設計で考えること
- 入力引数は何か(ID、年、月など)
- 成功時のJSONの形は何か
- データがない場合のエラーは何か
- 更新操作の権限・監査ログは十分か
Webとの共通点・違い
共通点は、入力を受け、モデルを読み、計算し、結果を返すことです。違いは、WebはHTML画面、MCPは機械が読みやすいJSONを主に返すことです。
11. 新しいWebアプリを最初から作る設計テンプレート
目標は「このリポジトリを暗記すること」ではなく、同じ考え方を別の題材に移せることです。 たとえば読書記録、家計簿、学習進捗、料理レシピ管理にも応用できます。
図7:要件から始め、テストを通じて小さく反復する
設計時に自分へ問いかける質問
| 段階 | 質問 | FPシミュレーターでの例 |
|---|---|---|
| 要件 | 誰が、何を入力し、何を知りたいのか? | 世帯の情報から将来残高を知りたい。 |
| モデル | 必要なデータと、その型・制約は何か? | Income.monthly_amount: int、月額は数値。 |
| コア処理 | 画面やDBなしでも計算できるか? | simulate(store, household)が結果を返す。 |
| 保存 | 再起動後にも必要なデータは何か? | 世帯、保存プラン、監査ログ。 |
| 画面/API | 利用者はどの操作をどの順番でするか? | 家族 → 収入 → 支出 → シミュレーション。 |
| テスト | 正しい例、境界値、失敗例は何か? | 給与開始月、終了月、0円、年齢境界。 |
最初から認証、AI連携、複雑な分析を全部作りません。 まず「1種類のデータを登録し、1つの計算をし、結果を表示し、テストする」縦切りを完成させます。
12. 学習ロードマップ
順番にチェックし、各項目で小さな成果物を残してください。 チェック状態はこのブラウザに保存されます(別の端末とは共有されません)。
フェーズA:Pythonの基礎
フェーズB:このリポジトリを動かす
フェーズC:小さな変更をテスト付きで行う
フェーズD:新規プロジェクト
進捗を計算中…
13. 確認クイズ:自分の言葉で説明する
答えを先に見ず、紙やメモに書いてから開いてください。 正答率よりも、どこが曖昧かを見つけることが目的です。
Q1. engineとwebを分ける理由は?
engineを画面やHTTPから独立させると、同じ計算をWebとMCPから再利用でき、 計算だけを小さくテストできます。画面の変更が計算ロジックに直接影響しにくくなります。
Q2. Pydanticモデルは何を守る?
入力データの形、型、必須項目、値の制約を守ります。 文字列のまま届いたフォーム値を、計算で扱える正しいデータへ変換・検証する契約です。
Q3. 税率をPythonコードではなくYAMLに置く利点は?
法改正による値の変更を計算アルゴリズムの変更から分離できます。 適用開始日と出典も管理でき、過去の計算を再現しやすくなります。
Q4. 「給与が0円」の不具合で、どの順番に見る?
フォームのname → FastAPIの引数 → Incomeモデル → DB保存/取得 → 対象月の条件 → cashflowのsalary_income → テンプレート表示、の順に小さく追います。 まず再現テストを作ると、修正の効果を自動確認できます。
Q5. MCPはこのリポジトリで何を追加する?
新しい計算ロジックではなく、LLMが世帯参照、シミュレーション、キャッシュフロー取得、 金額の根拠説明、更新をツールとして呼ぶための入口を追加します。
Q6. 新規アプリで最初に決めることは?
技術を選ぶ前に、誰のどんな課題を解くのか、MVPで何を作らないのかを決めます。 その後、データモデル、コア処理、保存、画面、テストを小さくつなぎます。
14. 用語集
| 用語 | この資料での意味 | リポジトリで探す場所 |
|---|---|---|
| ドメイン | アプリが扱う対象領域。この場合は家計・税・年金・ライフプラン。 | engine/ |
| 純粋関数 | 同じ入力なら同じ出力になり、外部状態を直接変更しない関数。 | 税計算、ローン計算、補助関数 |
| モデル | データの形と制約を表す設計図。 | engine/models.py |
| ルート | URLとHTTPメソッドに対応するサーバー処理。 | web/main.pyの@app.get/@app.post |
| テンプレート | サーバーのデータを埋め込んで返すHTMLのひな型。 | web/templates/ |
| Jinja2 | Pythonの値をHTMLテンプレートへ埋め込むテンプレートエンジン。 | web/main.py、web/templates/ |
| HTMX | HTML属性を使って、ページ全体を再読み込みせずHTTPリクエストや部分更新を行うライブラリ。 | web/templates/base.html |
| ミドルウェア | リクエストをアプリ本体へ渡す前後に、認証など共通処理を挟む仕組み。 | web/auth.py |
| 303リダイレクト | POST後に別URLをGETで表示させるHTTPレスポンス。POST/Redirect/GETで使う。 | web/main.pyのRedirectResponse |
| UPSERT | 登録がなければINSERT、あればUPDATEする保存方式。 | db/database.pyのON CONFLICT |
| シングルトン | アプリ内で共有する1つの状態やインスタンスを使い回す設計。 | parameters/loader.pyのget_store() |
| 永続化 | プログラム終了後もデータを残すこと。 | db/database.py |
| 回帰(デグレ) | 新しい変更によって、以前動いていた機能が壊れること。 | tests/で防ぐ |
| トレーサビリティ | 数値がどの入力・式・パラメータから出たかをたどれること。 | TraceEntry、explain_amount |
| MVP | 価値を確認できる最小限の実用版。 | 要件定義・新規プロジェクト設計 |
| 依存関係 | ある部品が別の部品・ライブラリ・サービスを必要とする関係。 | pyproject.toml |