1. 目的と境界

Student 2 の複数参加者マッチングだけを、 標準ライブラリのみの単一 Python ファイルで動かし、 「ゲート → ペア採点 → 貪欲グループ」の構造を体感するためのデモです。 この HTML は想定構成の説明であり、デモ実行本体ではありません。

flowchart LR
  subgraph Goal["デモのゴール"]
    U["python matching_demo.py
--scenario A"] --> R["除外 / ペア点 / グループを
ターミナルに表示"] end subgraph Out["スコープ外"] DB[(DB)] API[HTTP API] GEN[Event 生成] LOG[AI ログ] WEB[ブラウザ UI] end Goal -.->|接続しない| Out

図 1 — デモはマッチング理解専用。永続化・生成・ログ・Web UI は切る。

形式の決定: 単一ファイル matching_demo.py。 依存は Python 標準ライブラリのみ(dataclasses / datetime / argparse など)。 FastAPI・pytest・プロジェクトの app.* は import しない。

2. 入れるもの/切るもの

flowchart TB
  subgraph IN["matching_demo.py に入れる"]
    C["設定定数"]
    T["時刻ユーティリティ"]
    D["候補・Beacon・プロフィール"]
    G["除外ゲート"]
    P["ペア採点 7要因"]
    F["貪欲グループ形成"]
    V["CLI 出力
print / 整形テーブル"] end subgraph OUT["入れない"] S["status API / DB"] M["micro_event_generator"] A["ai_event_logs"] O["オーケストレーション"] Auth["認証"] HTML["HTML / JS / CDN"] PIP["サードパーティ pip"] end IN ~~~ OUT

図 2 — 含有範囲のブロック図。

区分要素
入れる重み・閾値・準備分・人数上下限設定
入れる区間交差・クリップ・共通分数時刻
入れる候補 / Beacon / プロフィールのインメモリ構造入力
入れる除外 → ペア → グループ → score_detailコア
入れるargparse でシナリオ選択・結果を stdout に整形出力CLI
切るPostgreSQL / Redis / FastAPI / Next基盤
切るEvent 作成・カテゴリ・manager・自動 Join生成
切る時限文字列の本番パーサ(デモは時刻直書き可)簡略化
切るfrom app... や pip パッケージ依存

3. ファイル内の論理ブロック

形式は 1 つの Python ファイル。 マッチング本体は純関数/ローカル dataclass のみで完結させ、 末尾の main() がシナリオ読込 → 実行 → 印刷を行う。

flowchart TB
  FILE["matching_demo.py(単一ファイル)"]

  FILE --> B1["① CONFIG
重み・閾値・定数"] FILE --> B2["② TYPES / FIXTURES
dataclass + シナリオ辞書"] FILE --> B3["③ TIME UTILS
交差・結合・準備後クリップ"] FILE --> B4["④ SCORING
7要因 + 加重合計"] FILE --> B5["⑤ PIPELINE
filter → pairs → groups"] FILE --> B6["⑥ CLI
argparse / print_report"] B1 --> B5 B2 --> B5 B3 --> B4 B4 --> B5 B5 --> B6 B2 --> B6

図 3 — 単一ファイル内部のブロック構成と依存方向。

依存の向きは常に 下位(設定・データ・純関数)→ パイプライン → CLImain から採点式を直書きせず、run_matching(...) 一本に集約すると、本番の matching_service.run_matching と対応が取りやすい。

4. データモデル(デモ入力)

erDiagram
  CANDIDATE ||--o{ INTERVAL : has
  CANDIDATE }o--o| BEACON : current_location
  PROFILE ||--|| CANDIDATE : same_student
  PROFILE }o--o{ PROFILE : friends
  RUN ||--|{ CANDIDATE : inputs
  RUN ||--|{ BEACON : catalog
  RUN ||--o{ DUPSET : duplicate_sets

  CANDIDATE {
    string student_id
    string[] intents
    datetime expires_at
    string location_id
  }
  INTERVAL {
    datetime start
    datetime end
  }
  BEACON {
    string id
    int capacity
    int max_parallel
    string building_id
    string campus_id
  }
  PROFILE {
    string student_id
    string department_id
    string[] friend_ids
  }
  DUPSET {
    string[] student_ids
  }
        

図 4 — デモが扱う入力エンティティ(永続化なし)。Python では @dataclass(frozen=True) を推奨。

1 回の実行に渡す塊

  • now — 固定時刻(再現性のためシナリオに埋め込み)
  • candidates: list[Candidate] — 参加者スナップショット
  • beacons: dict[str, Beacon] — 場所カタログ
  • profiles: dict[str, Profile] — 学科・友人
  • interests — 省略可(常に None → 0.50)
  • duplicate_student_sets: frozenset[frozenset[str]] — 既 ACTIVE 相当(任意)

5. データフロー(実行時)

flowchart LR
  IN["Scenario
候補 N 人"] --> GATE["除外ゲート"] GATE --> ELIG["eligible"] GATE --> EX["exclusions"] ELIG --> PAIR["全ペア採点"] PAIR --> EDGES["edges ≥ 0.60"] PAIR --> ALLP["全 pair_scores"] EDGES --> FORM["貪欲グループ
2〜5人"] FORM --> GROUPS["groups
score ≥ 0.70"] FORM --> REJECT["閾値未満グループ"] ELIG --> PLACE["Beacon 選定"] PLACE --> FORM EX --> VIEW["print_report"] ALLP --> VIEW GROUPS --> VIEW REJECT --> VIEW

図 5 — 実行時データフロー。右端は stdout への観察ポイント。

sequenceDiagram
  participant CLI as main / argparse
  participant Run as run_matching
  participant Gate as filter_eligible
  participant Score as score_pair
  participant Form as form_groups
  participant Out as print_report

  CLI->>Run: scenario (candidates, beacons, profiles, now)
  Run->>Gate: candidates
  Gate-->>Run: eligible + exclusions
  loop 全ペア
    Run->>Score: (a, b)
    Score-->>Run: factors + total
  end
  Run->>Form: edges ≥ 0.60
  Form-->>Run: groups + selection_score
  Run-->>CLI: MatchingResult
  CLI->>Out: result
  Out-->>CLI: stdout(3セクション)
        

図 6 — CLI とコア関数のシーケンス。

ゲートで落ちる典型

flowchart TD
  C["候補"] --> Q1{"expires_at > now
かつ空きあり?"} Q1 -->|No| X1["inactive / NO_AVAILABILITY"] Q1 -->|Yes| Q2{"準備15分後に
残り ≥ 30分?"} Q2 -->|No| X2["insufficient_remaining_time"] Q2 -->|Yes| Q3{"適合 Beacon あり?"} Q3 -->|No| X3["no_suitable_beacon / parallel_full"] Q3 -->|Yes| Q4{"duplicate set でない?"} Q4 -->|No| X4["duplicate_candidate_event"] Q4 -->|Yes| OK["eligible"]

図 7 — 除外ゲート詳細(スコア前)。

6. CLI / 出力の想定構成

flowchart TB
  subgraph Entry["起動"]
    CMD["python matching_demo.py
--scenario A|B|C|D|E
[--list]"] end subgraph Load["読込"] SC["SCENARIOS[name]
now / candidates / beacons / ..."] end subgraph Core["コア"] RUN["run_matching(...)"] end subgraph Out["stdout 3 セクション"] R1["[1] Exclusions
student_id + reason_code"] R2["[2] Pair scores
総合点 + 7要因"] R3["[3] Groups
members / beacon / scores"] end CMD --> SC --> RUN --> Out

図 8 — CLI ブロック。結果は必ず 3 セクションを順に出すと構造が伝わる。

セクション見せるもの理解ポイント
[1] Exclusionsstudent_id + reason_codeスコア以前に落ちる条件
[2] Pair scores辺ごとの 7 要因と合計、閾値 0.60説明可能性
[3] Groupsメンバー、matching / selection、選定 Beacon貪欲・非再利用・0.70

起動例

  • python matching_demo.py --list — シナリオ一覧
  • python matching_demo.py --scenario A — 成功パス
  • python matching_demo.py --scenario C — 複数グループ

対話編集 UI は必須ではない。シナリオはファイル内の辞書を書き換えて再実行する形で十分。 必要なら後から --scenario 以外のフラグを足す。

7. 埋め込みシナリオ(推奨セット)

flowchart LR
  S0["共通 now
Beacon 2〜3"] --> S1["Scenario A
3人: 揃う → 1グループ"] S0 --> S2["Scenario B
空き不足 → 除外"] S0 --> S3["Scenario C
6人: 2グループ + 余り"] S0 --> S4["Scenario D
duplicate set → 除外"] S0 --> S5["Scenario E
友人・同学科で加点差"]

図 9 — 学習用シナリオの分岐。A→C→D の順で見ると理解が進む。

  • A: intent・時間・同一 Beacon が重なり、ペア・グループとも閾値超過
  • B: 共通時間が準備後に 30 分未満 → ゲート落ち
  • C: 人数が多く、貪欲の非再利用で第2グループ/余りが出る
  • D: 同じ候補集合が duplicate_student_sets にあり除外
  • E: 低重み(友人 0.05 / 学科 0.05)の差が表で見える

8. 本番コードとの対応

flowchart LR
  subgraph Demo["matching_demo.py"]
    D1["CONFIG"]
    D2["time utils"]
    D3["score_pair / form_groups"]
    D4["run_matching"]
    D5["main / print_report"]
  end
  subgraph Prod["リポジトリ"]
    P1["student2/config.py"]
    P2["student2/availability.py"]
    P3["matching_service.py"]
    P4["run_matching"]
    P5["orchestration / API"]
  end
  D1 -.対応.-> P1
  D2 -.対応.-> P2
  D3 -.対応.-> P3
  D4 -.対応.-> P4
  D5 -.デモ専用.-> P5
        

図 10 — デモブロックと本番モジュールの対応。CLI 以降は本番と切り離してよい。

デモ本番
CONFIG 定数app/student2/config.py
区間ユーティリティavailability.py + matching 内の interval 関数
run_matchingmatching_service.run_matching
fixture / SCENARIOSStatusStore + Port(Beacon/Profile)の代わり
main / print_reportデモ専用(API や画面には載せない)
(なし)micro_event_generator / API / DB

9. ソース配置スケッチ

1 ファイル内の定義順(上から下)の目安です。

# matching_demo.py — stdlib only # 1. imports dataclasses, datetime, argparse, ... # 2. CONFIG 重み・閾値・準備分・人数 # 3. TYPES TimeInterval, Candidate, Beacon, Profile, ... # 4. FIXTURES BEACONS, PROFILES, SCENARIOS = {"A": ..., ...} # 5. time utils intersect, clip, common_minutes_after_prep # 6. scoring 7 factors + weighted_total # 7. pipeline filter_eligible score_pair / score_all_pairs form_groups run_matching # 公開入口(本番と同名推奨) # 8. report print_exclusions / print_pairs / print_groups # 9. main argparse → load scenario → run → print if __name__ == "__main__": main()
置き場所の候補: docs/human/demos/matching_demo.py (人間向けドキュメント配下)。本番の backend/app/ には混ぜず、 from app... も行わない。

実行例: python docs/human/demos/matching_demo.py --scenario A

関連: Student 2 完了実装ガイド · 本番ロジック正本 backend/app/services/matching_service.py