Data model — brand, variant, attribute
同じ「獺祭」でも磨きの違いで味が違い、同じ「十四代」でも中取り無濾過と龍の落とし子は別物だ。この違いを図鑑で別の番号にするか、1 つの番号の中の姿違いにするかを比べた。結論は後者で、さらに「生・原酒・無濾過・季節もの」は図鑑のマスターに入れず、投稿に付く属性として持つ 3 層構造をすすめる。
同日夜に shika さんが方針を決めた。1 マス=銘柄は維持、姿の分母は蔵元公式サイトの定番だけ、それ以外はユーザーが名前を打って自分の姿として記録(運営への登録は不要)、生酒・原酒・無濾過は投稿ではなく姿(製品)のマスター情報。これに沿ってローカルで実装・検証した(§7〜§9)。初回の銘柄数の検討は §10。
sake_products の 1 行が「獺祭 純米大吟醸 45」のような製品で、銘柄をまとめる列やテーブルは無い(shushu-supabase/supabase/migrations/20260301000003_create_sake_products.sql)dex_no は sake_products の行ごとに振られ(20260914000001_zukan_gamification.sql)、collection_progress() と award_badges() は「投稿に付いた sake_product_id の種類数」を数える。つまり今の実装はもう「完全に別銘柄」の案になっているbusiness/shushu/docs/sake-candidates/SPEC.md は brands[] の中に products[] を持つ形で、生成スクリプト shushu-supabase/scripts/gen-seed.py が銘柄を捨てて製品だけを sake_products に平らに流しているdocs/sake-candidates/survey/responses.md)。このまま入れると「151 銘柄の図鑑」のつもりが約 390 マスになるposts.attribute_tags(原酒・生酒・生貯蔵・生詰・無濾過・にごり・ひやおろし・新酒・古酒・スパークリング・生酛・山廃・あらばしり)を入れたshushu-supabase/supabase/seed-data/ には README しかなく JSON が無い(2026-09-15 観測)。今のローカルの seed.sql は架空の 500 件国税庁「清酒の製法品質表示基準の概要」を読むと、ラベルに書ける違いは性質の違う 2 つの枠に分かれている。そこに蔵ごとの製品ラインが重なる。
| 層 | 例 | 性質 | 図鑑での持ち方 |
|---|---|---|---|
| 銘柄 | 獺祭・十四代・田酒 | ブランド。蔵に紐づき、何年も変わらない | 図鑑の 1 マス(マスター) |
| 製品(姿) | 獺祭 磨き二割三分/田酒 特別純米/新政 No.6 S-type | 蔵の定番ライン。特定名称・精米歩合・米で決まることが多いが、特定名称だけでは区別できない(獺祭の 4 本はすべて純米大吟醸) | 銘柄の中の姿(マスター。定番だけ) |
| 状態・時期 | 生原酒・無濾過・ひやおろし・しぼりたて・その年の限定 | 同じ製品の出荷形態や季節で変わる。年度やロットで出たり消えたりする | 定番なら公式の姿の属性(マスター)。季節限定などはユーザーが作る自分の姿の属性 |
状態・時期を姿としてマスターに入れると、たとえば「特定名称 8 × 火入れの別(生酒・生貯蔵・生詰・火入れ)4 × 原酒か否か 2 × 無濾過か否か 2 = 128 通り」が 1 銘柄ごとに候補になる。実際に出ているのはその一部だが、どれが出ているかを運営が毎年追う必要が出る。
shika さんの指摘どおり、蔵が製品を分ける軸は特定名称だけではない。候補データ 975 本を名前で数えると、分け方は少なくとも 6 通りある(件数はスクリプトで名前の文字列を数えたもので、分類の重なりを含む)。
| 分け方 | 候補データの例 | 件数の目安 | 名前から読めるか |
|---|---|---|---|
| 特定名称で分ける | 田酒 特別純米酒/純米吟醸/純米大吟醸 | — | 読めることが多いが、「純吟」「上撰」のような略称・旧呼称も混ざる |
| 精米歩合で分ける | 獺祭 純米大吟醸 45/磨き三割九分/磨き二割三分、七田 純米 七割五分磨き | 30 本 | 読めない。名前の数字や「磨き」「割」を含む 30 本のうち、玉川 Time Machine 1712・三井の寿 +14 大辛口・亀泉 CEL-24 は精米歩合ではない。書き方も「45」「50%」「精米歩合50」「二割三分」「七割五分磨き」とばらばら |
| 米・酵母で分ける | 而今 純米吟醸 山田錦/千本錦、鍋島 純米吟醸 山田錦/五百万石、亀泉 CEL-24/高育63号 | 71 本 | 酒米名は読めるが、酵母の名前は記号で読めない |
| 独自の名前を付ける | 久保田 千寿/萬寿、〆張鶴 純/月/花、黒龍 石田屋/しずく、新政 亜麻猫、北雪 大吟醸 YK35 | 85 本 | 読めない(名前に特定名称の語が無い) |
| 名前に銘柄名が入らない | 赤武 → AKABU 純米酒、加茂錦 → 荷札酒 黄水仙、五人娘 → 醍醐のしずく、大嶺 → Ohmine 3grain | 27 本 | 読めない。ローマ字表記・シリーズ名・別名で売られている。中には別の銘柄として扱うべきものが混ざっている可能性がある(未確認) |
| 年度で名前が変わる | 産土 2022 山田錦/2022 穂増 | — | 年を姿の名前に含めると毎年新しい姿が増える |
YK35 について。特定の蔵の製品名ではなく、山田錦(Y)・熊本酵母とも呼ばれる 9 号酵母(K)・精米歩合 35%(35)で造れば鑑評会で金賞が取れる、という業界の通称(Wikipedia「YK35」。二次情報)。候補データでは北雪が製品名に使っている。つまり同じ文字列が複数の蔵の製品名になりうるし、名前の「35」を精米歩合と読むのは偶然合っているだけ、という典型。
さらに、1 つの蔵が別の銘柄として出しているものもある。候補データで 1 蔵に 2 銘柄あるのは 10 蔵(黒龍酒造の黒龍と九頭龍、油長酒造の風の森と鷹長、清水清三郎商店の作と鈴鹿川 など)。
products[] に key を足し、ID はそこから作る。いまの gen-seed.py は製品 ID を製品名から作っているので、表記を直すだけで ID が変わり、その製品に付いた投稿が迷子になる(今のうちに直すべき既存の穴)。移行時は、今の名前から作った ID をそのまま key の初期値にすれば ID は変わらないlabel_name(「北雪 大吟醸 YK35」「荷札酒 黄水仙 純米大吟醸」)と、姿の一覧で使う短い variant_name(「YK35」「黄水仙」「磨き二割三分」)の 2 本「獺祭 純米大吟醸 45」と「獺祭 磨き二割三分」が別の図鑑番号。飲んだ製品がそのまま 1 マス。
図鑑番号は銘柄に付く。どの製品を飲んでもマスが埋まる。銘柄の詳細を開くと姿の一覧があり、飲んだ姿に印が付く。姿の分母は定番の製品だけ。
「獺祭 純米大吟醸」「田酒 特別純米」「田酒 純米吟醸」を別のマスにする。製品ラインは持たず、特定名称の 8 種+その他で切る。
| 観点 | A 製品ごと | B 銘柄+姿 | C 銘柄×特定名称 |
|---|---|---|---|
| 1 杯目で埋まる手触り | 製品まで分かれば埋まる | 銘柄が分かれば必ず埋まる | 特定名称まで分かれば埋まる |
| 完成率の分母 | 約 390(151 銘柄の場合の見積り) | 151 | 銘柄の製品構成しだい |
| 永久に埋まらないマス | 季節限定・終売で発生 | 分母は定番だけなので出ない | 特定名称の終売で発生しうる |
| 2 周目の遊び | 無い(全部が 1 周目) | 姿集め・姿コンプの称号 | 無い |
| 投稿の手間 | 製品を正確に選ぶ必要 | 銘柄だけで可、姿は任意 | 特定名称の選択が必須 |
| マスターに無い酒 | 製品単位で漏れる | 銘柄が入っていれば記録できる | 銘柄×特定名称で漏れる |
| 運営のマスター作業 | 製品数に比例、季節ものを毎年追う | 銘柄が必須、定番の姿は後から足せる | 銘柄ごとに特定名称を確認 |
| 味データの粒度 | 製品単位(ただし 1 製品あたりの投稿は少ない) | 銘柄・姿・属性の 3 段で出せる | 銘柄×特定名称 |
| さけのわとの差 | 製品単位の味は差になるが、図鑑が埋めにくい | 「埋め切れる図鑑」を守りつつ、姿・属性別の味で差 | — |
| いまからの改修 | 不要 | DB 1 本+生成スクリプト+アプリ図鑑まわり | B と同程度 |
「約 390」は候補データの平均(975 ÷ 377 = 2.59 本)を 151 に掛けた見積りで、採用される 151 銘柄の実数ではない。
昼の §6 までは「銘柄テーブルを新設する」案で書いていたが、実装では既存の sake_products を銘柄(1 マス)として残し、その下に姿のテーブルを足す形にした。図鑑番号・進捗・称号・飲める場所の DB 関数と、アプリの 16 ファイルの参照を変えずに済むため。
マイグレーション: shushu-supabase/supabase/migrations/20260915000001_sake_variants.sql
| もの | 中身 | 守っていること |
|---|---|---|
sake_products(既存) | 銘柄 = 図鑑の 1 マス。変更なし | 図鑑番号・collection_progress()・award_badges()・飲める場所は銘柄単位のまま |
sake_variants(新規) | 公式の姿。name(ラベルの名前そのまま)・variant_name(短い名前)・特定名称・精米歩合・米・酵母・度数・attribute_tags・source_url(定番の根拠にした公式ページ)・並び順・is_listed | 誰でも読めるが、クライアントからは足せない。is_listed = false(終売)は分母から外すが行は消さない |
user_sake_variants(新規) | 自分の姿。名前・特定名称・精米歩合・属性。同じ人・同じ銘柄・同じ名前(表記ゆれは正規化で吸収)は 1 行 | 読めるのは本人と、その姿が付いた投稿を見られる人だけ(投稿の公開範囲に従う)。作る・直す・消すは本人だけ |
posts.sake_variant_id / user_variant_id(新規) | どちらか一方、または無し(銘柄だけの記録) | トリガー sync_post_variant(): 姿を付けると銘柄はその姿の銘柄に揃う/他人の姿は付けられない/編集で銘柄だけ変えたら前の姿は外れる |
posts.attribute_tags | 削除(09-14 に入れた列。クラウド未反映だった) | 属性は姿の attribute_tags に移した。許す値は DB 関数 sake_attribute_tags_valid() とアプリの constants/tags.ts で二重に固定 |
variant_progress(銘柄) | 銘柄詳細の姿の図鑑。公式の姿・自分の姿・達成率(記録した定番 / 定番の数)・姿を選ばずに記録した件数を 1 往復で | 分母は公式の定番だけ。終売は自分が飲んだものだけ出す |
variant_counts() | 図鑑カードの「姿 2/4」を全銘柄分 1 回で | 銘柄 ID を URL に並べない(09-13 の 414 の教訓) |
scripts/gen-seed.py | 候補 JSON の brand → sake_products、products → sake_variants。姿の ID は products[].key から作る(省略時は名前) | 精米歩合・属性・特定名称の値を生成前に検査(不正なら終了コード 2) |
| 画面 | 変えたこと | ファイル |
|---|---|---|
| 投稿の作成・編集 | 銘柄の下に「姿」を置いた。公式の定番と自分の姿を同じチップで並べ、無ければ「+ 名前を入力」で名前・特定名称・属性を入れる。投稿の時点で自分の姿を作る。投稿の「酒の属性」チップは削除。銘柄を選び直すと姿は外れる | components/post/VariantPicker.tsx(新規)、screens/post/PostCreateScreen.tsx、PostEditScreen.tsx |
| 銘柄詳細 | 「姿の図鑑」を入れ子で表示。達成率(記録数 / 定番数・%)のバー、公式の定番のマス、「自分で記録した姿」のマス(同じ見た目に「マイ」印)、姿を選ばずに記録した件数。マスを押すと、その姿を選んだ状態で投稿画面が開く。銘柄情報から特定名称・米・酵母・度数の行を外した(姿ごとに違うため) | components/collection/VariantDex.tsx(新規)、screens/collection/SakeDetailScreen.tsx |
| 図鑑一覧 | 収集済みのカードに「姿 1/4」 | SakeProductCard.tsx・CollectionGrid.tsx・CollectionScreen.tsx・stores/collectionStore.ts |
| 投稿の表示 | 名前は「銘柄 姿」(新政 極上 純米吟醸 生原酒)。投稿詳細に「姿: …(マイ)」と「姿の属性」 | PostCard.tsx・PostGridCard.tsx・TimelinePostCard.tsx・PostDetailScreen.tsx、取得列は services/posts.ts・timeline.ts・collection.ts |
制約。オフラインで投稿した場合、新しく名前を打った姿は作成に通信が要るので付かない(銘柄だけの記録になる)。公式の姿と既存の自分の姿はオフラインでも付く。
観測 2026-09-15 20:30〜20:50 JST。ローカルの Supabase で supabase db reset → scripts/seed-test-data.py(自分の姿 2 件を含む)を流したうえで、ロールを切り替えて SQL を実行した。
| 確かめたこと | 結果 |
|---|---|
| 姿を付けた投稿の銘柄が、姿の銘柄に揃う | 5 件すべて一致 |
tester の variant_progress(新政 極上) | 定番 4 のうち 1 を記録、終売「旧ラベル」は出ない、自分の姿「極上 ひやおろし 2026」が 1 件 |
| 公式の姿をクライアント権限で足す | 拒否: new row violates row-level security policy for table "sake_variants" |
| 不正な属性で自分の姿を作る | 拒否: violates check constraint "user_sake_variants_attribute_tags_check" |
| 他人の姿を自分の投稿に付ける | 拒否: user_variant_id must belong to the post owner |
| 姿の付いた投稿で銘柄だけ変える | 姿が外れる |
| 別の銘柄の公式の姿を付ける | 銘柄がその姿の銘柄に変わる |
| 非公開投稿に付けた自分の姿が、相互フォローの花子・未ログインから見えるか | 見えない(本人からは見える) |
| 花子が tester の銘柄の姿の図鑑を引く | 自分の姿は空、記録 0(他人の記録は混ざらない) |
| 公開投稿に付いた他人の姿の名前 | 花子からも未ログインからも見える(投稿が見える範囲と同じ。仕様として §11 で確認) |
| 図鑑の進捗 | 銘柄単位のまま(total 500 / collected 6) |
シード生成(候補 east.json をスクラッチに生成。本物の seed.sql は上書きしていない) | 銘柄 137・姿 339 を出力 |
アプリの型検査 npx tsc --noEmit・静的解析 npm run lint | どちらも終了コード 0 |
まだやっていないこと。実機・シミュレータでの画面確認(見た目は未確認)。クラウドへの反映(未反映のマイグレーションは 09-13・09-14×2・09-15 の 4 本)。姿の名前での検索(「YK35」で引ける別名テーブル)。姿コンプの称号。
調査エージェントがアンケート回答 13 件(tally.py の突き合わせ)と候補データをスクリプトで数え、蔵元公式サイトを 2026-09-15 20:20〜20:42 JST に取得して定番を数えた。「実測」「見積り」を分けて書く。
票には自然な切れ目が 2 か所ある。3 票以上 = 54 銘柄、2 票以上 = 92 銘柄。ちょうど 50 や 100 にすると、境界の同票(3 票の 27 銘柄、1 票の 138 銘柄)から誰かが選ぶことになる。
| 選び方 | 銘柄数 | 挙がった酒の被覆(173 銘柄を挙げた回答 3 を除く延べ票) | 覆える県 |
|---|---|---|---|
| 票順 | 50 | 62.7% | 23 / 47 |
| 各県 1 銘柄を先に確保 → 票順 | 50 | 44.5% | 47 / 47(46 県が 1 銘柄) |
| 3 票以上 54 + 県埋め 23 | 77 | 70.7% | 47 / 47(35 県が 1 銘柄) |
| 票順 | 100 | 85.9% | 32 / 47 |
| 2 票以上 92 + 県埋め 15 | 107 | 85.9% | 47 / 47(24 県が 1 銘柄) |
| 票順 | 151 | 92.4% | 37 / 47 |
被覆は 50 → 100 で大きく伸び、100 → 151 ではあまり伸びない。票が 0 の県(香川・愛媛・長崎・宮崎)は、塗り絵のために入れるなら何を入れるかを人が決める必要がある。
| 銘柄 | 定番(下限〜上限) | メモ |
|---|---|---|
| 冩樂 | 2 | 残りはすべて発売月付きの季節品 |
| 而今 | 0〜2 | ほぼ全商品に出荷月が付く。「毎年同じ月に出る」を定番と数えると 26 |
| 上善如水 | 4 | |
| 黒龍 | 5〜6 | 限定品・季節限定品を除く |
| 獺祭 | 6〜17 | 限定の表記が無いシリーズが定番か判別できない |
| 八海山 | 6〜12 | 「数量限定」だけの 6 本の扱いで幅 |
| 久保田 | 9 | 季節限定 7 は除く |
| 新政 | 11〜12 | 公式サイトの掲載が 2021 年度のまま。現行の構成は未確認 |
| 酔鯨 | 13〜15 | |
| 一ノ蔵 | 15〜20 | 「限定品」表記の意味が同社ページ内で食い違う |
| 浦霞 | 21〜23 | 県内限定 7・季節限定 9 は除く |
11 銘柄の中央値は下限 6・上限 12、平均は 8.36〜11.09。田酒は商品名が画像で総数が未確認、十四代は公式サイトが無いとの二次情報しか無く測れなかった。入手困難な人気の銘柄ほど、「公式サイトの定番」を分母にすると姿の図鑑が空に近くなる。
仮定: 1 銘柄あたりの固定の手間 25 分(公式サイトを探し、定番の境界を決める)+ 1 製品あたり 8 分(規格と属性を確かめて入力)。
| 銘柄数 | 姿の総数 | 作業時間 | 週 10 時間なら |
|---|---|---|---|
| 50 | 約 300〜550 | 約 60〜95 時間 | 約 8 週 |
| 77 | 約 460〜850 | 約 95〜150 時間 | 約 10〜15 週 |
| 100 | 約 600〜1,100 | 約 120〜190 時間 | 約 15 週 |
| 151 | 約 900〜1,700 | 約 180〜290 時間 | — |
77 は 100 の値を 77/100 倍した近似。画像の権利確認・読み仮名・紹介文は含まない。
business/shushu/docs/sake-candidates/{SPEC.md,east.json,central.json,west.json}、shushu-supabase/supabase/migrations/、shushu-supabase/scripts/gen-seed.py、shushu-app/src/(2026-09-15 に読んだ。件数はスクリプトで数えた)