← Files DesignlyARCHIVED FILE

skills/creative-director/scripts/generate_mocs.py

7.63 KB · Oct 3, 2026 · 06:31 UTC

↓ Download file

# /// script
# requires-python = ">=3.11"
# dependencies = ["pyyaml"]
# ///

"""Generate 6 MOC files from legendary-campaigns card library."""

import sys
import re
from collections import defaultdict
from pathlib import Path

import yaml

CARDS_DIR = Path(__file__).parent.parent / "references" / "legendary-campaigns" / "cards"
MOCS_DIR = Path(__file__).parent.parent / "references" / "legendary-campaigns"

PATTERN_ORDER = [f"P{i:02d}" for i in range(1, 19)]
CATEGORY_ORDER = ["film", "integrated", "stunt_pr", "social", "ooh_print", "experiential", "digital_product", "long_form"]
BUDGET_ORDER = ["low", "medium", "high"]


def parse_frontmatter(path: Path) -> dict | None:
    """Parse YAML frontmatter from a markdown file, return None on failure."""
    text = path.read_text(encoding="utf-8")
    match = re.match(r"^---\n(.*?)\n---", text, re.DOTALL)
    if not match:
        return None
    try:
        return yaml.safe_load(match.group(1)) or {}
    except yaml.YAMLError as exc:
        print(f"WARNING: {path.name}: invalid YAML — {exc}", file=sys.stderr)
        return None


def get(card: dict, key: str) -> str:
    """Return card field as string, '?' if missing or None."""
    val = card.get(key)
    if val is None:
        return "?"
    return str(val)


def format_card_line(card: dict) -> str:
    """Format a single MOC list item with inline context."""
    cid = get(card, "id")
    title = get(card, "title")
    brand = get(card, "brand")
    year = get(card, "year")
    category = get(card, "category")
    budget = get(card, "budget")

    emotions = card.get("emotion") or []
    primary_emotion = emotions[0] if emotions else "?"

    awards = card.get("awards") or []
    top_award = awards[0] if awards else "unknown"

    return f"- [[cards/{cid}.md|{title}]] — {brand} · {year} · {category} · {primary_emotion} · {budget} · {top_award}"


def moc_header(title: str, total: int) -> list[str]:
    """Return standard MOC file header lines."""
    return [
        f"# {title}",
        "",
        "> Auto-generated by `scripts/generate_mocs.py`. Do not edit by hand.",
        "",
        f"Total: {total} cards",
    ]


def write_moc(path: Path, lines: list[str]) -> None:
    """Write MOC lines to file, joining with newlines."""
    path.write_text("\n".join(lines) + "\n", encoding="utf-8")


def generate_moc_index(cards: list[dict]) -> list[str]:
    """Generate MOC-index: all cards sorted by year DESC, quality_score DESC."""
    sorted_cards = sorted(
        cards,
        key=lambda c: (-int(c.get("year") or 0), -int(c.get("quality_score") or 0)),
    )
    lines = moc_header("MOC — Index", len(cards))
    lines.append("")
    for card in sorted_cards:
        lines.append(format_card_line(card))
    return lines


def generate_moc_pattern(cards: list[dict]) -> list[str]:
    """Generate MOC-pattern: grouped by pattern P01–P18, quality_score DESC."""
    by_pattern: dict[str, list[dict]] = defaultdict(list)
    for card in cards:
        patterns = card.get("pattern") or []
        if not patterns:
            by_pattern["?"].append(card)
        for p in patterns:
            by_pattern[p].append(card)

    lines = moc_header("MOC — Pattern", len(cards))
    order = PATTERN_ORDER + sorted(k for k in by_pattern if k not in PATTERN_ORDER)

    for p in order:
        group = by_pattern.get(p)
        if not group:
            continue
        group_sorted = sorted(group, key=lambda c: -int(c.get("quality_score") or 0))
        lines += ["", f"## {p} ({len(group_sorted)} cases)"]
        for card in group_sorted:
            lines.append(format_card_line(card))
    return lines


def generate_moc_emotion(cards: list[dict]) -> list[str]:
    """Generate MOC-emotion: grouped by emotion_tier 3→2→1, quality_score DESC."""
    by_tier: dict[int, list[dict]] = defaultdict(list)
    for card in cards:
        tier = card.get("emotion_tier")
        by_tier[int(tier) if tier is not None else 0].append(card)

    lines = moc_header("MOC — Emotion", len(cards))

    for tier in [3, 2, 1, 0]:
        group = by_tier.get(tier)
        if not group:
            continue
        group_sorted = sorted(group, key=lambda c: -int(c.get("quality_score") or 0))
        label = f"Tier {tier}" if tier else "Unknown Tier"
        lines += ["", f"## {label} ({len(group_sorted)} cases)"]
        for card in group_sorted:
            lines.append(format_card_line(card))
    return lines


def generate_moc_format(cards: list[dict]) -> list[str]:
    """Generate MOC-format: grouped by category, year DESC."""
    by_cat: dict[str, list[dict]] = defaultdict(list)
    for card in cards:
        cat = card.get("category") or "?"
        by_cat[cat].append(card)

    lines = moc_header("MOC — Format", len(cards))
    order = CATEGORY_ORDER + sorted(k for k in by_cat if k not in CATEGORY_ORDER)

    for cat in order:
        group = by_cat.get(cat)
        if not group:
            continue
        group_sorted = sorted(group, key=lambda c: -int(c.get("year") or 0))
        lines += ["", f"## {cat} ({len(group_sorted)} cases)"]
        for card in group_sorted:
            lines.append(format_card_line(card))
    return lines


def generate_moc_industry(cards: list[dict]) -> list[str]:
    """Generate MOC-industry: grouped by industry alphabetically, quality_score DESC."""
    by_industry: dict[str, list[dict]] = defaultdict(list)
    for card in cards:
        ind = card.get("industry") or "?"
        by_industry[ind].append(card)

    lines = moc_header("MOC — Industry", len(cards))

    for ind in sorted(by_industry.keys()):
        group = sorted(by_industry[ind], key=lambda c: -int(c.get("quality_score") or 0))
        lines += ["", f"## {ind} ({len(group)} cases)"]
        for card in group:
            lines.append(format_card_line(card))
    return lines


def generate_moc_budget(cards: list[dict]) -> list[str]:
    """Generate MOC-budget: grouped by budget low/medium/high, quality_score DESC."""
    by_budget: dict[str, list[dict]] = defaultdict(list)
    for card in cards:
        bud = card.get("budget") or "?"
        by_budget[bud].append(card)

    lines = moc_header("MOC — Budget", len(cards))
    order = BUDGET_ORDER + sorted(k for k in by_budget if k not in BUDGET_ORDER)

    for bud in order:
        group = by_budget.get(bud)
        if not group:
            continue
        group_sorted = sorted(group, key=lambda c: -int(c.get("quality_score") or 0))
        lines += ["", f"## {bud} ({len(group_sorted)} cases)"]
        for card in group_sorted:
            lines.append(format_card_line(card))
    return lines


def load_cards() -> tuple[list[dict], int]:
    """Load and parse all card files, return (cards, error_count)."""
    cards: list[dict] = []
    errors = 0

    for md_file in sorted(CARDS_DIR.glob("*.md")):
        fm = parse_frontmatter(md_file)
        if fm is None:
            print(f"WARNING: skipping {md_file.name} — no valid frontmatter", file=sys.stderr)
            errors += 1
            continue
        cards.append(fm)

    return cards, errors


def main() -> None:
    """Entry point: load cards, write 6 MOC files, print summary."""
    cards, errors = load_cards()

    mocs = [
        ("MOC-index.md", generate_moc_index(cards)),
        ("MOC-pattern.md", generate_moc_pattern(cards)),
        ("MOC-emotion.md", generate_moc_emotion(cards)),
        ("MOC-format.md", generate_moc_format(cards)),
        ("MOC-industry.md", generate_moc_industry(cards)),
        ("MOC-budget.md", generate_moc_budget(cards)),
    ]

    for filename, lines in mocs:
        write_moc(MOCS_DIR / filename, lines)

    print(f"Generated {len(mocs)} MOCs from {len(cards)} cards ({errors} errors skipped)")


if __name__ == "__main__":
    main()

SHA-256: ebede99cce9b4bbfec4b0e8b6be5105c99400fb973d1e19a67e1a49002286a37