プロジェクト・アーキテクチャ¶
このプロジェクトは 「指導方針のドキュメント」と「複数の学習指導アプリ」 で構成される(予定を含む)。 このページは、両者を破綻なく育てていくための全体構成と設計方針をまとめる。実装の順序(ロードマップ)と、 なぜその形にするのかの根拠を残すことが目的。
このページの位置づけ
ここは設計の正本。構成・レイヤ・データの持ち方を変えたら、まずこのページを更新してから実装する。
コード側の使い方の詳細は app/README.md、Claude向けの作業指針は CLAUDE.md を参照。
全体像(3つの層)¶
このプロジェクトには、役割の違う 3つの成果物 がある。
| 層 | 実体 | 役割 | 読む人 |
|---|---|---|---|
| 方針(docs) | docs/(MkDocs Material) |
学習の方針・志望校情報・保護者向けガイドを文章で記す正本 | 親(=運用者) |
| 正本データ(content) | content/(YAML・予定) |
学校/偏差値/受験資格/単元/原因分類などを構造化データで持つ単一の真実 | 人+プログラム両方 |
| アプリ(apps) | apps/(Streamlit) |
上記データを使って日々の練習・管理を行う道具 | 子ども/親 |
キモは 「方針の文章(docs)」と「機械が使うデータ(content)」を分け、アプリは content を参照するだけにする こと。 これで「docsに書いた学校名・単元」と「アプリ内の選択肢」の手動同期を廃止し、ドリフト(食い違い)を構造的に防ぐ。
duoduo/
├── docs/ # 方針の文章(MkDocs)※学校表などは content から生成/検査
├── content/ # ★単一の真実(YAML)
│ ├── schools.yaml # 校名/偏差値(模試名+出典)/受験資格/通学 → アプリの SCHOOLS
│ ├── mistake_causes.yaml # 原因→対策 → アプリの MISTAKE_CAUSES
│ ├── curriculum/*.yaml # 算数35単元・科目別単元・到達目標
│ └── roadmap.yaml # フェーズ→期間→重点(週間/年間計画の元)
├── packages/
│ └── takao_core/ # ★共有カーネル(pip install -e、全アプリが依存)
│ ├── pyproject.toml
│ ├── takao_core/ # import 名 takao_core(フラット構成)
│ │ ├── content.py # content/ ローダ(学校・原因分類・カリキュラム)
│ │ ├── storage.py # データ層(TAKAO_DATA_DIR で場所解決)
│ │ ├── srs.py # ★復習サイクル(1週間後に再テスト)を一箇所に
│ │ ├── mistakes.py # 間違い直しノートのドメイン操作
│ │ ├── math_gen.py # 算数ジェネレータ/suggest/flashcard 等
│ │ └── …
│ └── tests/ # pytest 単体テスト(srs/mistakes/content/math_gen/storage/suggest)
├── app/ # アプリ(画面。views は3エントリで共有)
│ ├── app.py # 全機能(ホスト先の既定エントリ)
│ ├── kids.py # 🧒 子ども用:毎日のドリル・挑戦・再テスト・おすすめ
│ ├── parent.py # 👤 親用:過去問トラッカー・スキャン・問題DB・間違い確認
│ ├── views/*.py # 各ページ(3エントリが共有。from takao_core import で利用)
│ └── data/ # 学習SQLite(app.db+画像。TAKAO_DATA_DIR で解決・Git管理外)
└── tools/
└── check_content.py # docs⇄content 一致チェック(pre-commit / mkdocs build 前)
現状との対応
ロードマップ #1〜#6 まで完了。content/(正本データ)・共有カーネル packages/takao_core/(import takao_core)・
カリキュラムのデータ化・復習サイクルの一元化・対象者別エントリ(kids/parent)・単体テストまで実装済み。
画面 app/views/*.py は3エントリ(app/kids/parent)で共有する(分割してもコピーを増やさない)。
設計の3原則¶
① コアはパッケージにする(apps は薄く)¶
共有ロジック(データ層・問題生成・復習サイクル・おすすめ)は packages/takao_core に集約し、
pip install -e で各アプリが import takao_core で使う。
- 理由:アプリが増えても、各アプリが
sys.path.insert(...)のような相対パス hack を持たずに済む。 現状app/app.pyはこの hack でcoreを通しているが、多アプリ前提では破綻する。 - 効果:コアだけを単体でテストできる。アプリ側は「画面(UI)」に専念できる。
既にレイヤは綺麗
現状も views → core → storage の一方向依存で、views 同士は互いに依存していない(import は全部 core 経由)。
そのため「コアをパッケージ化」「アプリを分割」のコストは低い。この綺麗さを崩さないのが編集方針。
② コンテンツはデータにする(docs と apps の橋)¶
学校・偏差値・受験資格・単元・間違いの原因分類など、docsとアプリの両方に出てくる事実は content/*.yaml を唯一の出所にする。
- 理由:かつては
core/storage.pyにSCHOOLS/MISTAKE_CAUSESがPython定数として存在し、docs/候補校リスト.md・docs/保護者サポート.mdと手作業で「そろえる」運用だった(必ずいつか食い違う)。 フェーズ1〜3でcontent/へ移し、takao_core.contentから参照する形にした。 - やり方:
- アプリ:定数 →
content.schools()/content.mistake_causes()に置換(出所はYAMLだけ)。 - docs:学校表などを YAML から生成 または 突合検査する(
tools/check_content.py)。 - スキーマで方針を強制:例)偏差値には模試名と出典URLが必須(
CLAUDE.md「学校情報を扱うときの注意」に対応)。空なら検査で落とす。
- アプリ:定数 →
content/schools.yaml ──┬─→ docs(表を生成/一致を検査)
└─→ apps(SCHOOLS などの選択肢)
= 手動同期を廃止し、単一の真実にする
③ アプリは対象者で分ける(採用済み)¶
「複数の学習指導アプリ」は 子ども用(kids)と親用(parent) に分けた(app/kids.py/app/parent.py)。
全機能版 app/app.py も残す(ホスト先の既定)。
- 理由:同じデータを使うが、UXの要求が正反対。
- 🧒 子ども用:大きなボタン・選択肢は少なく・励ます。毎日のドリル/スクショ問題に挑戦/再テスト/今日のおすすめ。
- 👤 親用:表・フィルタ・数値。過去問トラッカー/問題スキャン登録/問題データベース/間違いの確認。
- 分割は安い:views は core にしか依存していないので、
st.navigationのページ束を分けるだけ。 画面ファイル(app/views/*.py)は3エントリで共有し、コピーを増やさない。 - 起動:
streamlit run app/kids.py/app/parent.py(./run.sh kids/./run.sh parent)。
分割の軸は将来変えられる
「対象者別(kids/parent)」以外に「目的別(練習/管理/分析)」「科目別(算数/国語…)」も選べる。 現時点の推奨は対象者別。決めたらこのページに追記する。
データの持ち方¶
- 保存先:ローカルSQLite
data/app.db(学習ログ・間違い直し・過去問記録・問題DB・作文ログ・設定を1ファイルに集約)。 - 場所の解決:アプリのファイル位置に対する相対パスではなく、
TAKAO_DATA_DIR環境変数(未設定時は既定のdata/)で解決する。 複数アプリが同じDBを共有でき、app/ごと別の場所へ移しても壊れない。 - 画像:スクショ等のバイナリはファイルのまま
data/problem_images/に置き、DBにはファイル名だけを保存(移植性のため)。 - Git管理外:
data/(学習データ)とapp/kakomon/(正規入手した過去問PDF)は絶対にコミットしない(著作物・個人情報)。
スキーマは急いで固めない
現状の storage.py は「コレクション+JSON」の汎用ストア。親用の進捗分析で集計が要るようになったら、
ドメイン型(models.py)+リポジトリ層を core に足し、呼び出し側は生のdictでなく型で扱えるようにする。
汎用ストアの全面スキーマ化は必要になってから。
移行ロードマップ(低リスク順)¶
生きている個人ツールなので、小さく・壊さず・すぐ効く順に進める。
| # | 内容 | リスク | 効果 | 状態 |
|---|---|---|---|---|
| 1 | content/schools.yaml・mistake_causes.yaml + content.py。定数2つを置換 |
極小 | ドリフト即解消・すぐ体感 | ✅ 完了 |
| 2 | tools/check_content.py:docs表とYAMLの突合(模試名/出典URL必須も検査) |
小 | 方針違反を機械検出 | ✅ 完了 |
| 3 | content/curriculum/*.yaml 化、math_gen/suggest が参照 |
中 | 単元定義の一元化 | ✅ 完了 |
| 4 | core/ を takao_core パッケージ化(pyproject.toml・sys.path hack削除) |
中 | 多アプリ前提の土台 | ✅ 完了 |
| 5 | srs.py/ドメイン型を view から抽出、TAKAO_DATA_DIR 一元化 |
中 | 復習サイクルの一箇所化 | ✅ 完了 |
| 6 | ナビを kids/parent へ分割(対象者別) | 小 | 対象別UX。views は core依存のみで安い | ✅ 完了 |
フェーズ1(完了)のメモ
content/はリポジトリ直下(docs/と並置)。ローダcore/content.pyは 環境変数TAKAO_CONTENT_DIR→ 上位ディレクトリ探索の順でcontent/を解決する。- ローダは
content.py(フェーズ4でapp/core/→packages/takao_core/takao_core/へ移設済み)。 - 旧
storage.SCHOOLS/storage.MISTAKE_CAUSESは削除し、content.school_names()/content.mistake_causes()に置換済み。 - デプロイ注意:ホスト先へ
app/だけを配置する場合、content/が上位に無いと読み込めない。 その際はcontent/を同梱するかTAKAO_CONTENT_DIRを設定する。
フェーズ2(完了)のメモ — content⇄docs チェッカ
content/ を変更したら python tools/check_content.py を実行する(終了コード 0=OK / 1=違反)。
検査内容:
schools.yamlのスキーマ(key一意・name/category必須・safetyは真偽値)- 偏差値(
deviations)があれば「模試名+出典URL」を必須化(CLAUDE.mdの方針を機械強制。出典なしは落ちる) - 全校が
docs/候補校リスト.mdに載っているか(短縮名⇄正式名の差はステム正規化で吸収。特殊な表記はdoc_match:で上書き可) mistake_causes.yamlの「原因→対策」がdocs/保護者サポート.mdの表と対応まで一致しているか
アプリ・docs に依存しない独立ツール。将来 pre-commit / CI / mkdocs build 前に組み込む。
フェーズ3(完了)のメモ — カリキュラムのデータ化
算数の単元定義を content/curriculum/math.yaml(8カテゴリ・35単元・各単元の解き方)に一元化した。
core/math_gen.pyはCATEGORIES/UNIT_LABELS/UNIT_METHODを content から生成(旧ハードコードは撤去)。 問題を作るREGISTRY(生成関数)はコードに残す——単元名が content とコードの結合キー。math_gen起動時に 単元名 ⇄ REGISTRY の双方向一致を検査(欠落/余剰があれば即エラー)。tools/check_content.pyに算数カリキュラム検査を追加:スキーマ(name/units/method必須・単元名一意)+ カテゴリ名がdocs/科目別/算数.mdの見出しと一致するか。- yaml は現行コードから機械生成し、差し替え前後で値が完全一致することを確認済み(転記ミスなし)。
- 理科・国語のフラッシュカード(
rika.py/kokugo.py)のデータ化は未実施(必要になれば同方式でcontent/curriculum/に追加)。
フェーズ4(完了)のメモ — 共有カーネルのパッケージ化
app/core/ を packages/takao_core/takao_core/ へ移設し、pip install -e ./packages/takao_core で
導入する共有パッケージ takao_core にした。
- 全ファイルの
from core import …→from takao_core import …、sys.path.insert(...)ハックを 全撤去(app.py+全11 view)。views は純粋なUIになり、パッケージ解決に依存。 app/requirements.txtに-e ./packages/takao_coreを追加(リポジトリ直下からpip install)。- データ位置(フェーズ5の一部を前倒し):
storage.pyはTAKAO_DATA_DIR→ 上位のapp/data探索の順で解決。app/app.pyが起動時にTAKAO_DATA_DIR=<app>/dataを設定し、既存のapp/data/app.dbを保つ。 - 起動は従来どおり
streamlit run app/app.py(リポジトリ直下から)。./run.sh --installで editable 導入も入る。
フェーズ5(完了)のメモ — 復習サイクルとドメインの抽出
散在していた「1週間後に再テスト」を takao_core/srs.py に一元化し、間違い直しノートは
ドメインモジュール takao_core/mistakes.py に集約した。
srs.py:INTERVAL_DAYS=7・next_review_date()・is_due()・due()・review_fields()。 全アプリ・全単元の復習判定がここ1箇所に。mistakes.py:コレクション名mistakes.json・レコードの形・原因→対策の補完・復習日付与を閉じ込め、add() / all() / due() / pending() / resolved() / mark_done() / reschedule() / delete()を提供。- 間違い記録の4サイト(算数ドリル・理科国語カード・スクショ挑戦・手動追加)が
mistakes.add(...)に統一。 過去問トラッカー・ホームの復習日/期限判定もsrsに統一。"mistakes.json"の直書きはmistakes.pyの1箇所だけに。 - リファクタ前後の挙動同値を検証済み(
next_review_date==plus_days(7)、is_dueが旧フィルタと一致、add()が旧レコードと同 shape〔answerの有無まで一致〕)。データ位置一元化(TAKAO_DATA_DIR)はフェーズ4で実施済み。
フェーズ6(完了)のメモ — 対象者別アプリと単体テスト
- 対象者別エントリを追加:
app/kids.py(子ども用8ページ)・app/parent.py(親用5ページ)。 全機能版app/app.pyは残す。画面app/views/*.pyは3エントリで共有(コピーを増やさない)。 各エントリは先頭でTAKAO_DATA_DIR=<app>/dataを設定してからst.navigation。 run.shにkids/parentを追加(./run.sh kids・./run.sh parent)。- 単体テスト(pytest)を
packages/takao_core/tests/に整備:srs/mistakes/content/math_gen/storage/suggestの6ファイル・37ケース。使い捨てのTAKAO_DATA_DIR(conftest.py)で本番DBを汚さない。 実行:pip install -e "./packages/takao_core[test]"後、cd packages/takao_core && pytest。
テスト・検査(まとめ)¶
このリポジトリの健全性は次の3つで担保する。変更後はこれらを回す。
| 何を | コマンド | 何が守られる |
|---|---|---|
| 共有カーネルの単体テスト | cd packages/takao_core && pytest |
srs/mistakes/content/math_gen/storage/suggest の挙動 |
| content ⇄ docs 一致 | python tools/check_content.py |
学校・原因分類・カリキュラムと docs の食い違い、偏差値の出典必須 |
| 算数ジェネレータ自己テスト | python packages/takao_core/takao_core/math_gen.py |
全35単元が数値的に整合する問題を生成できる |
サイトのビルド確認は mkdocs build --strict(リンク切れ検出)。
この設計と CLAUDE.md の対応¶
このアーキテクチャは、CLAUDE.md の編集方針を構造で守るための仕掛けでもある。
- 「偏差値は模試名+出典URLとセット」→
schools.yamlのスキーマで必須化し検査で強制。 - 「学校の絞り込みを変えたら候補校リスト等も合わせて更新」→ content が単一の真実なので1箇所直せば波及。
- 「復習サイクルを基調に」→ SRS を
srs.pyに一箇所化し、全アプリで同じ挙動に。 - 「科目間の優先順位を保つ」→ 単元・重点を
curriculum/roadmap.yamlに持たせ、おすすめドリルが方針どおり誘導。
個人情報・著作権の前提は不変
アプリ・データを分割・移設しても、インターネット非公開(ローカル閲覧)、過去問PDF・問題文はコミットしない、
公開時は先に匿名化 という前提は変わらない。詳細は CLAUDE.md・README。