了解した。
ここであなたが求めているのは 「Universal Motif Extraction Token は DB に保存しない」 という前提のもとで、

Gemini が返した “抽出後の motif JSON” を DB に登録する関数設計

つまり:

  • トークンは一時的(開発フェーズ専用)
  • DB に保存するのは 抽出結果(motif_abc)だけ
  • その後、意味辞書(motif_meanings)が abc_id を参照する

という OS の正しい依存方向 を守った関数設計。

ここでは、
AI Service Web Server が持つべき「抽出結果登録関数」 を
あなたの OS 設計思想に合わせて提示する。


🎼 関数名:registerExtractedMotifs()

(symphony_motif_abc を生成し、意味辞書と紐づける)

🟦 目的

Gemini が返した motif JSON を解析し、
symphony_motif_abc に INSERT し、
その ID を symphony_motif_meanings に紐づける。

Roblox には一切送らない。
開発フェーズ専用の “物理層生成関数”。


🟩 関数仕様(Pseudo-code / 設計書レベル)

/**
 * Register extracted motifs (ABC + meaning + key) into DB.
 *
 * @param workCode        e.g. "Dvorak_9"
 * @param movementNumber  e.g. 1
 * @param motifsJson      Gemini output:
 *                        {
 *                          "motifs": [
 *                            { "label": "A", "meaning": "MainTheme", "key": "E minor", "abc": "..." },
 *                            { "label": "B", "meaning": "SubTheme", "key": "G major", "abc": "..." }
 *                          ]
 *                        }
 */
async function registerExtractedMotifs(workCode, movementNumber, motifsJson) {

    // 1. work_id を取得
    const work = await db.query(`
        SELECT id FROM symphony_works WHERE code = ?
    `, [workCode]);

    // 2. movement_id を取得
    const movement = await db.query(`
        SELECT id FROM symphony_movements
        WHERE work_id = ? AND movement_number = ?
    `, [work.id, movementNumber]);

    // 3. motifsJson.motifs をループ
    for (const motif of motifsJson.motifs) {

        // 3-1. symphony_motif_abc に INSERT(物理層)
        const abcResult = await db.query(`
            INSERT INTO symphony_motif_abc (movement_id, motif_label, abc)
            VALUES (?, ?, ?)
        `, [movement.id, motif.label, motif.abc]);

        const abcId = abcResult.insertId;

        // 3-2. symphony_motif_meanings に INSERT(意味層)
        await db.query(`
            INSERT INTO symphony_motif_meanings
                (movement_id, motif_label, abc_id, meaning, description, default_key)
            VALUES (?, ?, ?, ?, ?, ?)
        `, [
            movement.id,
            motif.label,
            abcId,
            motif.meaning,
            motif.description ?? "",
            motif.key
        ]);
    }

    return { status: "ok" };
}

🟧 この関数が OS として正しい理由

✔ 1. 物理(ABC) → 意味(meaning) の依存方向を守る

  • 先に symphony_motif_abc を INSERT
  • その ID を symphony_motif_meanings が参照
  • Roblox は意味辞書だけロード
  • ABC はサーバー内部で完結

✔ 2. トークンは保存しない

  • トークンは開発フェーズの一時データ
  • DB に残すのは “抽出結果” のみ
  • OS の純度が保たれる

✔ 3. 作品・楽章をキーに motif を正規化

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

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

  • Beethoven 5
  • Mahler 1
  • Tchaikovsky 1
  • Dvorak 9
    すべて同じ registerExtractedMotifs() で処理可能。

🟥 DB に保存されるのは “抽出結果だけ”

テーブル 保存内容
symphony_motif_abc ABC(物理)
symphony_motif_meanings 意味(semantic role)+ abc_id
symphony_works / movements 作品構造

Universal Token は保存しない。
これは OS の純度を守るために正しい判断。


🎯 次に進めるステップ

  1. この関数を Flask / FastAPI で実装する
  2. Gemini の Universal Token を使って motif_abc を抽出する
  3. 抽出結果を registerExtractedMotifs() に渡す
  4. Dvořák 9番の motif_abc が DB に揃う
  5. Roblox が意味辞書をロードできる状態になる

次はどちらに進めたい?

  • 実際の API エンドポイント(/dev/register_motifs)を設計する
  • Dvořák 9番の motif_abc 抽出トークンを作る
  • DB に入れる INSERT 文を生成する

あなたの OS のリズムに合わせて進めるよ。