コンテンツにスキップ

プロジェクト・アーキテクチャ

このプロジェクトは 「指導方針のドキュメント」と「複数の学習指導アプリ」 で構成される(予定を含む)。 このページは、両者を破綻なく育てていくための全体構成と設計方針をまとめる。実装の順序(ロードマップ)と、 なぜその形にするのかの根拠を残すことが目的。

このページの位置づけ

ここは設計の正本。構成・レイヤ・データの持ち方を変えたら、まずこのページを更新してから実装する。 コード側の使い方の詳細は 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.pySCHOOLSMISTAKE_CAUSES がPython定数として存在し、 docs/候補校リスト.mddocs/保護者サポート.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.pyapp/parent.py)。 全機能版 app/app.py も残す(ホスト先の既定)。

  • 理由:同じデータを使うが、UXの要求が正反対。
    • 🧒 子ども用:大きなボタン・選択肢は少なく・励ます。毎日のドリル/スクショ問題に挑戦/再テスト/今日のおすすめ。
    • 👤 親用:表・フィルタ・数値。過去問トラッカー/問題スキャン登録/問題データベース/間違いの確認。
  • 分割は安い:views は core にしか依存していないので、st.navigation のページ束を分けるだけ。 画面ファイル(app/views/*.py)は3エントリで共有し、コピーを増やさない。
  • 起動:streamlit run app/kids.pyapp/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.yamlmistake_causes.yamlcontent.py。定数2つを置換 極小 ドリフト即解消・すぐ体感 ✅ 完了
2 tools/check_content.py:docs表とYAMLの突合(模試名/出典URL必須も検査) 方針違反を機械検出 ✅ 完了
3 content/curriculum/*.yaml 化、math_gensuggest が参照 単元定義の一元化 ✅ 完了
4 core/takao_core パッケージ化(pyproject.tomlsys.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.SCHOOLSstorage.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.pyCATEGORIESUNIT_LABELSUNIT_METHODcontent から生成(旧ハードコードは撤去)。 問題を作る REGISTRY(生成関数)はコードに残す——単元名が content とコードの結合キー。
  • math_gen 起動時に 単元名 ⇄ REGISTRY の双方向一致を検査(欠落/余剰があれば即エラー)。
  • tools/check_content.py に算数カリキュラム検査を追加:スキーマ(name/units/method 必須・単元名一意)+ カテゴリ名が docs/科目別/算数.md の見出しと一致するか。
  • yaml は現行コードから機械生成し、差し替え前後で値が完全一致することを確認済み(転記ミスなし)。
  • 理科・国語のフラッシュカード(rika.pykokugo.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.pyTAKAO_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.pyINTERVAL_DAYS=7next_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.shkidsparent を追加(./run.sh kids./run.sh parent)。
  • 単体テスト(pytest)を packages/takao_core/tests/ に整備:srsmistakescontentmath_genstoragesuggest の6ファイル・37ケース。使い捨ての TAKAO_DATA_DIRconftest.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.mdREADME