Files

814 lines
31 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ruff: noqa: E501
"""Seed the wildcard-pipeline DB with mock data for integration testing.
Idempotent: re-running with the same DB skips entries that already exist
(matched by name + type). Designed so manual exploration of the SPA Manager,
ComfyUI graph, and Test Runner has realistic content to work against —
including nested ``@{8hex}`` references between wildcards, ``$var`` flow
across kinds, and constraint/derivation/pipeline composition.
Usage::
# Default DB location (~/.comfyui/wildcard-pipeline.db on Linux/macOS,
# C:\\Users\\<you>\\.comfyui\\wildcard-pipeline.db on Windows):
python scripts/seed_mock_data.py
# Override DB path:
WP_DB_PATH=/tmp/seeded.db python scripts/seed_mock_data.py
The script prints every row it inserts and reports the resolved 8-hex UUIDs
so you can verify ``@{uuid}`` ref insertion in the SPA picker autocomplete
and the catalog walk against your live database.
"""
from __future__ import annotations
import sys
from pathlib import Path
# conftest's sys.path shim only kicks in under pytest; for a standalone
# script we add the repo root manually.
_REPO_ROOT = Path(__file__).resolve().parent.parent
if str(_REPO_ROOT) not in sys.path:
sys.path.insert(0, str(_REPO_ROOT))
from engine.db.connection import get_connection, resolve_db_path # noqa: E402
from engine.db.migrations import migrate # noqa: E402
from engine.db.repositories import ( # noqa: E402
CategoryRepository,
ModuleRepository,
)
# ── Categories ──────────────────────────────────────────────────────────
CATEGORIES = [
{"name": "Subjects", "color": "#a78bfa", "icon": "user", "sort_order": 0},
{"name": "Style", "color": "#f472b6", "icon": "palette", "sort_order": 1},
{"name": "Composition", "color": "#34d399", "icon": "pi-objects-column", "sort_order": 2},
{"name": "Edge cases", "color": "#fb7185", "icon": "pi-exclamation-triangle", "sort_order": 3},
{"name": "Quality", "color": "#fbbf24", "icon": "pi-sparkles", "sort_order": 4},
]
# ── Wildcards ───────────────────────────────────────────────────────────
#
# Use placeholder uuids in the @{} refs (see PLACEHOLDER_REFS below) — the
# script rewrites them post-create with the actual repository-generated uuids.
PLACEHOLDER_REFS = {
"<COLOR_UUID>": "color",
"<MOOD_UUID>": "mood",
"<HAIR_UUID>": "hair_style",
"<SHIRT_UUID>": "shirt",
"<BACKDROP_UUID>": "backdrop",
# Deep-chain test fixtures — chain_a → chain_b → chain_c → chain_d
"<CHAIN_A_UUID>": "chain_a",
"<CHAIN_B_UUID>": "chain_b",
"<CHAIN_C_UUID>": "chain_c",
"<CHAIN_D_UUID>": "chain_d",
# Cycle test fixtures — cycle_a → cycle_b → cycle_a (walker should
# record `cycle_detected` overflow without infinite-looping)
"<CYCLE_A_UUID>": "cycle_a",
"<CYCLE_B_UUID>": "cycle_b",
# Self-ref edge case — self_ref → @{self_ref} (immediate cycle)
"<SELF_REF_UUID>": "self_ref",
}
WILDCARDS = [
{
"name": "color",
"category": "Style",
"tags": ["palette", "core"],
"is_favorite": True,
"var_binding": "color",
"sub_categories": ["warm", "cool", "neutral"],
"options": [
{"value": "red", "weight": 1, "sub_category": "warm"},
{"value": "burnt orange", "weight": 1, "sub_category": "warm"},
{"value": "blue", "weight": 1, "sub_category": "cool"},
{"value": "emerald green", "weight": 2, "sub_category": "cool"},
{"value": "deep violet", "weight": 1, "sub_category": "cool"},
{"value": "slate grey", "weight": 1, "sub_category": "neutral"},
{"value": "ivory", "weight": 1, "sub_category": "neutral"},
],
},
{
"name": "mood",
"category": "Style",
"tags": ["atmosphere", "core"],
"is_favorite": True,
"var_binding": "mood",
"sub_categories": ["positive", "negative"],
"options": [
{"value": "serene", "weight": 1, "sub_category": "positive"},
{"value": "playful", "weight": 1, "sub_category": "positive"},
{"value": "joyful", "weight": 1, "sub_category": "positive"},
{"value": "tense", "weight": 1, "sub_category": "negative"},
{"value": "melancholic", "weight": 1, "sub_category": "negative"},
],
},
{
"name": "hair_style",
"category": "Subjects",
"tags": ["body", "appearance"],
"var_binding": "hair_style",
"sub_categories": ["long", "short"],
"options": [
{"value": "long flowing", "weight": 1, "sub_category": "long"},
{"value": "wavy shoulder-length", "weight": 1, "sub_category": "long"},
{"value": "braided down the back", "weight": 1, "sub_category": "long"},
{"value": "short cropped", "weight": 1, "sub_category": "short"},
{"value": "buzz cut", "weight": 1, "sub_category": "short"},
{"value": "pixie cut", "weight": 1, "sub_category": "short"},
],
},
{
"name": "shirt",
"category": "Subjects",
"tags": ["clothing", "torso"],
"var_binding": "shirt",
"sub_categories": ["formal", "casual"],
"options": [
# Single-level @-ref into color
{"value": "@{<COLOR_UUID>} t-shirt", "weight": 2, "sub_category": "casual"},
{"value": "@{<COLOR_UUID>} hoodie", "weight": 1, "sub_category": "casual"},
{"value": "white linen shirt", "weight": 1, "sub_category": "formal"},
{"value": "@{<COLOR_UUID>} silk blouse", "weight": 1, "sub_category": "formal"},
{"value": "tailored @{<COLOR_UUID>} button-down", "weight": 1, "sub_category": "formal"},
],
},
{
"name": "outfit",
"category": "Subjects",
"tags": ["clothing", "favorite-test"],
"is_favorite": True,
"var_binding": "outfit",
"sub_categories": [],
"options": [
# Nested 2-deep ref: outfit → shirt → color
{"value": "@{<SHIRT_UUID>} with denim jeans", "weight": 2, "sub_category": None},
# Inline pick mixed with single ref
{"value": "{leather|wool} jacket over @{<SHIRT_UUID>}", "weight": 1, "sub_category": None},
# Multi-pick with separator
{"value": "{2$$, $$silver|gold|pearl} jewelry", "weight": 1, "sub_category": None},
# Deep-chain ref into the chain test fixtures (3 levels)
{"value": "@{<CHAIN_A_UUID>} accessories", "weight": 1, "sub_category": None},
],
},
{
"name": "backdrop",
"category": "Composition",
"tags": ["scene", "environment"],
"var_binding": "backdrop",
"sub_categories": ["indoor", "outdoor"],
"options": [
{"value": "studio seamless paper", "weight": 1, "sub_category": "indoor"},
{"value": "minimal interior with @{<COLOR_UUID>} accents", "weight": 1, "sub_category": "indoor"},
{"value": "@{<MOOD_UUID>} city street at dusk", "weight": 1, "sub_category": "outdoor"},
{"value": "forest clearing under @{<COLOR_UUID>} sky", "weight": 1, "sub_category": "outdoor"},
{"value": "rocky beach at golden hour", "weight": 1, "sub_category": "outdoor"},
],
},
# ── Deep-chain stress test (4 levels: A → B → C → D → leaf) ────────
{
"name": "chain_a",
"category": "Edge cases",
"tags": ["walker-test", "depth-test"],
"var_binding": "chain_a",
"sub_categories": [],
"options": [
{"value": "A-leaf-1", "weight": 1, "sub_category": None},
{"value": "A→@{<CHAIN_B_UUID>}", "weight": 2, "sub_category": None},
],
},
{
"name": "chain_b",
"category": "Edge cases",
"tags": ["walker-test", "depth-test"],
"var_binding": "chain_b",
"sub_categories": [],
"options": [
{"value": "B-leaf", "weight": 1, "sub_category": None},
{"value": "B→@{<CHAIN_C_UUID>}", "weight": 2, "sub_category": None},
],
},
{
"name": "chain_c",
"category": "Edge cases",
"tags": ["walker-test", "depth-test"],
"var_binding": "chain_c",
"sub_categories": [],
"options": [
{"value": "C-leaf", "weight": 1, "sub_category": None},
{"value": "C→@{<CHAIN_D_UUID>}", "weight": 2, "sub_category": None},
],
},
{
"name": "chain_d",
"category": "Edge cases",
"tags": ["walker-test", "depth-test"],
"var_binding": "chain_d",
"sub_categories": [],
"options": [
{"value": "D-deep-leaf", "weight": 1, "sub_category": None},
],
},
# ── Cycle pair (A → B → A) — walker overflow test ────────────────
{
"name": "cycle_a",
"category": "Edge cases",
"tags": ["walker-test", "cycle-test"],
"var_binding": "cycle_a",
"sub_categories": [],
"options": [
{"value": "cyc-a-leaf", "weight": 1, "sub_category": None},
{"value": "A→@{<CYCLE_B_UUID>}", "weight": 1, "sub_category": None},
],
},
{
"name": "cycle_b",
"category": "Edge cases",
"tags": ["walker-test", "cycle-test"],
"var_binding": "cycle_b",
"sub_categories": [],
"options": [
{"value": "cyc-b-leaf", "weight": 1, "sub_category": None},
{"value": "B→@{<CYCLE_A_UUID>}", "weight": 1, "sub_category": None},
],
},
# ── Self-ref edge case (immediate cycle on self) ─────────────────
{
"name": "self_ref",
"category": "Edge cases",
"tags": ["walker-test", "cycle-test"],
"var_binding": "self_ref",
"sub_categories": [],
"options": [
{"value": "self-leaf", "weight": 1, "sub_category": None},
{"value": "loop→@{<SELF_REF_UUID>}", "weight": 1, "sub_category": None},
],
},
# ── Single-option (no randomness) ────────────────────────────────
{
"name": "signature",
"category": "Edge cases",
"tags": ["edge-case"],
"var_binding": "signature",
"sub_categories": [],
"options": [
{"value": "©2026 mock", "weight": 1, "sub_category": None},
],
},
# ── Heavy-weight bias (one option ~10x more likely) ──────────────
{
"name": "weighted_demo",
"category": "Edge cases",
"tags": ["edge-case", "weights"],
"var_binding": "weighted_demo",
"sub_categories": [],
"options": [
{"value": "common-result", "weight": 10, "sub_category": None},
{"value": "rare-result", "weight": 1, "sub_category": None},
],
},
]
# ── Fixed values ────────────────────────────────────────────────────────
FIXED_VALUES = [
{
"name": "presets",
"category": "Quality",
"tags": ["preset", "quality", "core"],
"is_favorite": True,
"values": [
{"id": "v_quality", "name": "quality", "value": "high detail, sharp focus"},
{"id": "v_camera", "name": "camera", "value": "85mm portrait lens"},
{"id": "v_negative", "name": "negative", "value": "blurry, low quality, distorted"},
{"id": "v_aspect", "name": "aspect_ratio", "value": "3:4 portrait"},
],
},
{
"name": "model_settings",
"category": "Quality",
"tags": ["preset", "model"],
"values": [
{"id": "v_steps", "name": "steps", "value": "30"},
{"id": "v_cfg", "name": "cfg", "value": "7.5"},
{"id": "v_sampler", "name": "sampler", "value": "DPM++ 2M Karras"},
],
},
{
"name": "negatives_extended",
"category": "Quality",
"tags": ["preset", "negative"],
"values": [
{"id": "v_neg_anatomy", "name": "neg_anatomy",
"value": "extra fingers, malformed hands, asymmetrical eyes"},
{"id": "v_neg_artifacts", "name": "neg_artifacts",
"value": "jpeg artifacts, watermark, signature, text overlay"},
],
},
]
# ── Combines ────────────────────────────────────────────────────────────
COMBINES = [
{
"name": "subject_phrase",
"category": "Subjects",
"tags": ["combine", "subject", "core"],
"is_favorite": True,
# Pulls $hair_style + $outfit (which transitively chains through
# @{shirt} → @{color}) plus $mood from upstream.
"template": "a portrait of a person with $hair_style hair, wearing $outfit, $mood expression",
"output_var": "subject_phrase",
"input_vars": ["hair_style", "outfit", "mood"],
},
{
"name": "scene_phrase",
"category": "Composition",
"tags": ["combine", "scene"],
"template": "$subject_phrase, set against $backdrop, $camera, $quality",
"output_var": "scene_phrase",
"input_vars": ["subject_phrase", "backdrop", "camera", "quality"],
},
# Combine with inline brace-pick syntax inside the template (exercises
# both $var resolution AND inline {a|b|c} expansion).
{
"name": "framing",
"category": "Composition",
"tags": ["combine", "framing"],
"template": "{close-up|medium shot|wide angle} on $subject_phrase, $lighting",
"output_var": "framing",
"input_vars": ["subject_phrase", "lighting"],
},
# Combine that consumes the derivation-produced var (mood_to_lighting → $lighting).
{
"name": "final_prompt",
"category": "Composition",
"tags": ["combine", "final"],
"template": "$framing. $aspect_ratio, $sampler, cfg $cfg, steps $steps. avoid: $negative, $neg_anatomy",
"output_var": "final_prompt",
"input_vars": ["framing", "aspect_ratio", "sampler", "cfg", "steps", "negative", "neg_anatomy"],
},
]
# ── Derivations ─────────────────────────────────────────────────────────
DERIVATIONS = [
{
"name": "mood_to_lighting",
"category": "Style",
"tags": ["derivation", "lighting", "core"],
"is_favorite": True,
# IF / ELIF / ELSE chain over `equals` op. Mutates `lighting`.
"rules": [
{
"id": "r_lighting",
"branches": [
{
"condition": {"var": "mood", "op": "equals", "value": "tense"},
"action": {"target_var": "lighting", "mode": "replace",
"value": "harsh side lighting with deep shadows"},
},
{
"condition": {"var": "mood", "op": "equals", "value": "serene"},
"action": {"target_var": "lighting", "mode": "replace",
"value": "soft golden hour glow"},
},
{
"condition": {"var": "mood", "op": "equals", "value": "playful"},
"action": {"target_var": "lighting", "mode": "replace",
"value": "bright airy daylight"},
},
],
"else": {
"action": {"target_var": "lighting", "mode": "replace",
"value": "neutral diffused light"},
},
}
],
},
# Exercise all four condition operators (equals, not_equals, contains,
# matches) plus all three action modes (replace, append, prepend).
{
"name": "color_modifiers",
"category": "Style",
"tags": ["derivation", "operators-test"],
"rules": [
# Rule 1: `not_equals` — anything that isn't a warm color triggers
# an "ambient" prepend on the negative var.
{
"id": "r_op_not_equals",
"branches": [
{
"condition": {"var": "color", "op": "not_equals", "value": "red"},
"action": {"target_var": "negative", "mode": "prepend",
"value": "no red tint, "},
},
],
},
# Rule 2: `contains` — if outfit mentions "jacket", append a
# texture cue.
{
"id": "r_op_contains",
"branches": [
{
"condition": {"var": "outfit", "op": "contains", "value": "jacket"},
"action": {"target_var": "quality", "mode": "append",
"value": ", visible fabric texture"},
},
],
},
# Rule 3: `matches` — regex against backdrop. Triggers when the
# backdrop string starts with "forest".
{
"id": "r_op_matches",
"branches": [
{
"condition": {"var": "backdrop", "op": "matches", "value": "^forest"},
"action": {"target_var": "lighting", "mode": "append",
"value": ", dappled canopy light"},
},
],
},
],
},
]
# ── Constraints ────────────────────────────────────────────────────────
CONSTRAINTS = [
{
"name": "shirt_x_color_compat",
"category": "Subjects",
"tags": ["constraint", "subcategory-matrix"],
# Sub-category × sub-category matrix: rows = source sub_cats,
# cols = target sub_cats. The "no formal-shirt-with-warm-colors"
# rule keeps the seeded library's combinations a bit more
# opinionated. Per-option overrides belong in `exceptions`.
"source_wildcard": "shirt",
"target_wildcard": "color",
"matrix": {
"formal": {
"warm": {"mode": "reduce", "factor": 0.4},
},
},
"exceptions": [
# Carry the original "white linen × red is hard out" intent
# across as a per-option exception so the test fixture's
# narrative still matches.
{"source": "white linen shirt", "target": "red", "mode": "exclude", "factor": 0},
],
},
# Sub-category-keyed matrix — matrix rows/cols are sub-category names
# rather than full option strings. Constraint editor's "show by sub-
# category" toggle should pick these up.
{
"name": "mood_x_color_subcats",
"category": "Style",
"tags": ["constraint", "subcategory-matrix", "core"],
"is_favorite": True,
"source_wildcard": "mood",
"target_wildcard": "color",
"matrix": {
# negative moods boosted with cool colors, reduced with warm ones
"negative": {
"cool": {"mode": "boost", "factor": 2.0},
"warm": {"mode": "reduce", "factor": 0.3},
},
# positive moods do the inverse
"positive": {
"warm": {"mode": "boost", "factor": 1.8},
"cool": {"mode": "allow", "factor": 1.0},
"neutral": {"mode": "reduce", "factor": 0.5},
},
},
# Exception: tense + ivory should be hard-excluded even though the
# sub-cat matrix says "negative × neutral" is allow.
"exceptions": [
{"source": "tense", "target": "ivory", "mode": "exclude", "factor": 0},
],
},
# Hair-style x mood compatibility — multiple exceptions to test the
# exception-table editor at scale.
{
"name": "hair_x_mood",
"category": "Subjects",
"tags": ["constraint", "exceptions-heavy"],
"source_wildcard": "hair_style",
"target_wildcard": "mood",
"matrix": {
"long": {"positive": {"mode": "allow", "factor": 1.0}},
"short": {"positive": {"mode": "allow", "factor": 1.0}},
},
"exceptions": [
{"source": "buzz cut", "target": "serene", "mode": "reduce", "factor": 0.3},
{"source": "buzz cut", "target": "tense", "mode": "boost", "factor": 1.6},
{"source": "braided down the back", "target": "playful", "mode": "boost", "factor": 1.4},
{"source": "pixie cut", "target": "joyful", "mode": "boost", "factor": 1.5},
],
},
]
# ── Helpers ────────────────────────────────────────────────────────────
def _category_id_for(repo: CategoryRepository, name: str | None) -> str | None:
if not name:
return None
for row in repo.list():
if row["name"] == name:
return row["id"]
return None
def _ensure_category(repo: CategoryRepository, spec: dict) -> dict:
for row in repo.list():
if row["name"] == spec["name"]:
print(f" · category '{spec['name']}' already present (id={row['id']})")
return row
row = repo.create(
name=spec["name"],
color=spec.get("color"),
icon=spec.get("icon"),
sort_order=spec.get("sort_order", 0),
)
print(f" + category '{row['name']}' (id={row['id']})")
return row
def _existing_module_by_name(repo: ModuleRepository, name: str, type_: str) -> dict | None:
for row in repo.list(type=type_):
if row["name"] == name:
return row
return None
def _ensure_wildcard(
mod_repo: ModuleRepository, cat_repo: CategoryRepository, spec: dict,
) -> dict:
payload = {
"options": [{"id": f"opt_{i}", **opt} for i, opt in enumerate(spec["options"])],
"var_binding": spec["var_binding"],
"sub_categories": list(spec.get("sub_categories", [])),
}
existing = _existing_module_by_name(mod_repo, spec["name"], "wildcard")
if existing is not None:
# In-place update so re-runs of the seed picker pick up new fields
# (sub_categories, is_favorite, expanded options, additional tags)
# WITHOUT changing the uuid — every constraint / pipeline payload
# that references this wildcard by `id` keeps working.
updated = mod_repo.update(
existing["id"],
name=spec["name"],
category_id=_category_id_for(cat_repo, spec.get("category")),
tags=spec.get("tags", []),
payload=payload,
is_favorite=bool(spec.get("is_favorite", False)),
)
print(f" ~ wildcard '{spec['name']}' updated (uuid={updated['id']})")
return updated
row = mod_repo.create(
type="wildcard",
name=spec["name"],
description="",
category_id=_category_id_for(cat_repo, spec.get("category")),
tags=spec.get("tags", []),
payload=payload,
is_favorite=bool(spec.get("is_favorite", False)),
)
print(f" + wildcard '{row['name']}' (uuid={row['id']})")
return row
def _rewrite_refs(
mod_repo: ModuleRepository, wildcards: dict[str, dict],
) -> None:
"""Second pass — replace placeholder ``@{<NAME_UUID>}`` tokens in every
wildcard option value with the real ``@{8hex}`` from `wildcards`."""
name_to_uuid = {wc["name"]: wc["id"] for wc in wildcards.values()}
print(" · rewriting @{<NAME>_UUID} placeholders → real uuids …")
for wc in wildcards.values():
payload = dict(wc["payload"])
options = payload.get("options", [])
changed = False
new_options: list[dict] = []
for opt in options:
value = opt.get("value", "")
new_value = value
for placeholder, target_name in PLACEHOLDER_REFS.items():
if target_name in name_to_uuid:
new_value = new_value.replace(
placeholder, name_to_uuid[target_name],
)
if new_value != value:
changed = True
new_options.append({**opt, "value": new_value})
if changed:
payload["options"] = new_options
updated = mod_repo.update(wc["id"], payload=payload)
print(f" · {wc['name']} (uuid={updated['id']}): rewrote refs")
def _ensure_fixed(
mod_repo: ModuleRepository, cat_repo: CategoryRepository, spec: dict,
) -> dict:
payload = {"values": spec["values"]}
existing = _existing_module_by_name(mod_repo, spec["name"], "fixed_values")
if existing is not None:
updated = mod_repo.update(
existing["id"],
name=spec["name"],
category_id=_category_id_for(cat_repo, spec.get("category")),
tags=spec.get("tags", []),
payload=payload,
is_favorite=bool(spec.get("is_favorite", False)),
)
print(f" ~ fixed_values '{spec['name']}' updated (uuid={updated['id']})")
return updated
row = mod_repo.create(
type="fixed_values",
name=spec["name"],
description="",
category_id=_category_id_for(cat_repo, spec.get("category")),
tags=spec.get("tags", []),
payload=payload,
is_favorite=bool(spec.get("is_favorite", False)),
)
print(f" + fixed_values '{row['name']}' (uuid={row['id']})")
return row
def _ensure_combine(
mod_repo: ModuleRepository, cat_repo: CategoryRepository, spec: dict,
) -> dict:
payload = {
"template": spec["template"],
"output_var": spec["output_var"],
"input_vars": spec.get("input_vars", []),
}
existing = _existing_module_by_name(mod_repo, spec["name"], "combine")
if existing is not None:
updated = mod_repo.update(
existing["id"],
name=spec["name"],
category_id=_category_id_for(cat_repo, spec.get("category")),
tags=spec.get("tags", []),
payload=payload,
is_favorite=bool(spec.get("is_favorite", False)),
)
print(f" ~ combine '{spec['name']}' updated (uuid={updated['id']})")
return updated
row = mod_repo.create(
type="combine",
name=spec["name"],
description="",
category_id=_category_id_for(cat_repo, spec.get("category")),
tags=spec.get("tags", []),
payload=payload,
is_favorite=bool(spec.get("is_favorite", False)),
)
print(f" + combine '{row['name']}' (uuid={row['id']})")
return row
def _ensure_derivation(
mod_repo: ModuleRepository, cat_repo: CategoryRepository, spec: dict,
) -> dict:
payload = {"rules": spec["rules"]}
existing = _existing_module_by_name(mod_repo, spec["name"], "derivation")
if existing is not None:
updated = mod_repo.update(
existing["id"],
name=spec["name"],
category_id=_category_id_for(cat_repo, spec.get("category")),
tags=spec.get("tags", []),
payload=payload,
is_favorite=bool(spec.get("is_favorite", False)),
)
print(f" ~ derivation '{spec['name']}' updated (uuid={updated['id']})")
return updated
row = mod_repo.create(
type="derivation",
name=spec["name"],
description="",
category_id=_category_id_for(cat_repo, spec.get("category")),
tags=spec.get("tags", []),
payload=payload,
is_favorite=bool(spec.get("is_favorite", False)),
)
print(f" + derivation '{row['name']}' (uuid={row['id']})")
return row
def _ensure_constraint(
mod_repo: ModuleRepository, cat_repo: CategoryRepository,
wildcards: dict[str, dict], spec: dict,
) -> dict:
src = wildcards[spec["source_wildcard"]]
tgt = wildcards[spec["target_wildcard"]]
payload = {
"source_wildcard_id": src["id"],
"target_wildcard_id": tgt["id"],
"matrix": spec["matrix"],
"exceptions": spec["exceptions"],
}
existing = _existing_module_by_name(mod_repo, spec["name"], "constraint")
if existing is not None:
updated = mod_repo.update(
existing["id"],
name=spec["name"],
category_id=_category_id_for(cat_repo, spec.get("category")),
tags=spec.get("tags", []),
payload=payload,
is_favorite=bool(spec.get("is_favorite", False)),
)
print(f" ~ constraint '{spec['name']}' updated (uuid={updated['id']})")
return updated
row = mod_repo.create(
type="constraint",
name=spec["name"],
description="",
category_id=_category_id_for(cat_repo, spec.get("category")),
tags=spec.get("tags", []),
payload=payload,
is_favorite=bool(spec.get("is_favorite", False)),
)
print(f" + constraint '{row['name']}' (uuid={row['id']})")
return row
def main() -> int:
db_path = resolve_db_path()
print(f"Seeding mock data into: {db_path}")
conn = get_connection()
migrate(conn)
cat_repo = CategoryRepository(conn)
mod_repo = ModuleRepository(conn)
print("\n[Categories]")
for spec in CATEGORIES:
_ensure_category(cat_repo, spec)
print("\n[Wildcards]")
wildcards: dict[str, dict] = {}
for spec in WILDCARDS:
row = _ensure_wildcard(mod_repo, cat_repo, spec)
wildcards[row["name"]] = row
# Second pass: rewrite placeholder refs to real uuids
_rewrite_refs(mod_repo, wildcards)
# Refresh cache so downstream sees the updated payloads
for name in list(wildcards.keys()):
wildcards[name] = mod_repo.get(wildcards[name]["id"])
print("\n[Fixed values]")
fixed: dict[str, dict] = {}
for spec in FIXED_VALUES:
row = _ensure_fixed(mod_repo, cat_repo, spec)
fixed[row["name"]] = row
print("\n[Combines]")
combines: dict[str, dict] = {}
for spec in COMBINES:
row = _ensure_combine(mod_repo, cat_repo, spec)
combines[row["name"]] = row
print("\n[Derivations]")
derivations: dict[str, dict] = {}
for spec in DERIVATIONS:
row = _ensure_derivation(mod_repo, cat_repo, spec)
derivations[row["name"]] = row
print("\n[Constraints]")
for spec in CONSTRAINTS:
_ensure_constraint(mod_repo, cat_repo, wildcards, spec)
print("\nDone. Seeded uuids worth poking at:")
for name, row in wildcards.items():
print(f" wildcard '{name}': @{{{row['id']}}}")
print("\nQuick sanity checks you can run from the SPA:")
print(" · Test Runner on 'outfit' → samples should resolve nested refs")
print(" (outfit → shirt → color, max chain depth 2)")
print(" · Test Runner on 'scene_phrase' (combine) → uses upstream $vars")
return 0
if __name__ == "__main__":
sys.exit(main())