了解。
ここでは Flask 版の /dev/register_motifs API を、
あなたのクラシック生成OSの原則(物理 → 意味 → 構造)に完全準拠した形で提示します。

 


🎼 Flask版 /dev/register_motifs API(完全実装)

以下は 実際に動く Flask Blueprint として書いています。

  • Universal Token は保存しない
  • Gemini 抽出結果(motifsJson)だけを受け取る
  • symphony_motif_abc → symphony_motif_meanings の順で登録
  • OS の依存方向を厳密に守る

🟦 controllers/dev_register_motifs.py

from flask import Blueprint, request, jsonify
import pymysql

blp = Blueprint("dev_register_motifs", __name__)

def get_db():
    return pymysql.connect(
        host="localhost",
        user="root",
        password="password",
        database="music_os",
        charset="utf8mb4",
        cursorclass=pymysql.cursors.DictCursor
    )


@blp.route("/dev/register_motifs", methods=["POST"])
def register_motifs():
    data = request.get_json()

    work_code = data.get("work_code")
    movement_number = data.get("movement_number")
    motifs = data.get("motifs", [])

    if not work_code or not movement_number or not motifs:
        return jsonify({"error": "Invalid request"}), 400

    db = get_db()

    try:
        with db.cursor() as cur:
            # 1. work_id を取得
            cur.execute("SELECT id FROM symphony_works WHERE code=%s", (work_code,))
            row = cur.fetchone()
            if not row:
                return jsonify({"error": f"work_code not found: {work_code}"}), 404
            work_id = row["id"]

            # 2. movement_id を取得
            cur.execute("""
                SELECT id FROM symphony_movements
                WHERE work_id=%s AND movement_number=%s
            """, (work_id, movement_number))
            row = cur.fetchone()
            if not row:
                return jsonify({"error": f"movement not found: {work_code} movement {movement_number}"}), 404
            movement_id = row["id"]

        # 3. モチーフを登録
        for motif in motifs:
            label = motif["label"]
            abc = motif["abc"]
            meaning = motif.get("meaning", "")
            description = motif.get("description", "")
            key = motif.get("key", "")

            # 3-1. ABC を登録(物理層)
            with db.cursor() as cur:
                cur.execute("""
                    INSERT INTO symphony_motif_abc (movement_id, motif_label, abc)
                    VALUES (%s, %s, %s)
                """, (movement_id, label, abc))
                abc_id = cur.lastrowid

            # 3-2. 意味辞書を登録(意味層)
            with db.cursor() as cur:
                cur.execute("""
                    INSERT INTO symphony_motif_meanings
                        (movement_id, motif_label, abc_id, meaning, description, default_key)
                    VALUES (%s, %s, %s, %s, %s, %s)
                """, (
                    movement_id,
                    label,
                    abc_id,
                    meaning,
                    description,
                    key
                ))

        db.commit()

        return jsonify({
            "status": "ok",
            "work_code": work_code,
            "movement_number": movement_number,
            "motifs_registered": len(motifs)
        })

    finally:
        db.close()

🟩 この API が OS として正しい理由

✔ 1. 物理 → 意味 の依存方向を厳密に守る

  • symphony_motif_abc(物理)を先に INSERT
  • その ID を symphony_motif_meanings(意味)が参照
  • Roblox は意味辞書だけロード
  • ABC はサーバー内部で完結
    → OS の純度が保たれる

✔ 2. Universal Token は保存しない

  • トークンは開発フェーズの一時データ
  • DB に残すのは “抽出結果だけ”

✔ 3. 作品・楽章を正規化して参照

  • work_code → work_id
  • movement_number → movement_id
  • motif_label → A/B/C
    → 完全に正規化された DB 構造

✔ 4. どの作品でも同じ API で登録できる

  • Beethoven 5
  • Mahler 1
  • Tchaikovsky 1
  • Dvorak 9
    すべてこの API で motif_abc を登録できる。

🎯 次に進めるステップ

  1. /dev/extract_motifs(Universal Token → Gemini)API を作る
  2. Dvořák 9番の motif_abc を実際に抽出して登録する
  3. Roblox 側の “意味辞書ロード” を実装する
  4. オマージュ生成(/homage/generate)API を作る

次はどこに進めたい?