Code / tools/publish.py

tools/publish.py 90 lines
#!/usr/bin/env python3
# =============================================================================
#  Project   : modelmap
#  File      : tools/publish.py
#  Purpose   : Export validated atlas entries to site/data — refuses incomplete
#  Author    : Simon-Pierre Boucher
#  Contact   : contact@spboucher.ai
#  Website   : https://modelmap.io
#  Created   : 2026-08-12
#  Modified  : 2026-08-12
#  Platform  : macOS / Apple Silicon (arm64)
#  License   : All rights reserved (research code)
# =============================================================================
"""Publish gate for the atlas (charter §14 hard constraints).

Walks atlas/<model>/<type>/<version>/, validates each entry's mapcard.json
(schema v0) plus the presence of provenance.json and confidence.md, and exports
valid entries to site/data/atlas-index.json. Any violation blocks that entry
and is reported; nothing incomplete ever reaches modelmap.io.
"""

from __future__ import annotations

import json
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "src"))

from modelmap.atlas.mapcard import MapCard


def entries():
    atlas = ROOT / "atlas"
    for model in sorted(p for p in atlas.iterdir() if p.is_dir()):
        for map_type in sorted(p for p in model.iterdir() if p.is_dir()):
            yield from sorted(p for p in map_type.iterdir() if p.is_dir())


def main() -> int:
    exported, blocked = [], []
    for entry in entries():
        rel = entry.relative_to(ROOT)
        problems = []
        for required in ("provenance.json", "confidence.md", "mapcard.json"):
            if not (entry / required).exists():
                problems.append(f"missing {required}")
        card = None
        if not problems:
            try:
                card = MapCard.load(entry / "mapcard.json")
                problems += card.validate()
            except (json.JSONDecodeError, TypeError) as e:
                problems.append(f"mapcard.json unreadable: {e}")
        if problems:
            blocked.append((str(rel), problems))
        else:
            exported.append({
                "path": str(rel),
                "map_id": card.map_id,
                "map_type": card.map_type,
                "model_id": card.model_id,
                "quantization": card.quantization,
                "confidence_level": card.confidence_level,
                "replication_rate": card.replication_rate,
                "negative_result": card.negative_result,
                "created": card.created,
            })

    out = ROOT / "site" / "data"
    out.mkdir(parents=True, exist_ok=True)
    (out / "atlas-index.json").write_text(json.dumps({
        "author": "Simon-Pierre Boucher",
        "contact": "contact@spboucher.ai",
        "website": "https://modelmap.io",
        "entries": exported,
    }, indent=2) + "\n")

    print(f"publish: {len(exported)} entry(ies) exported to site/data/atlas-index.json")
    for rel, problems in blocked:
        print(f"BLOCKED {rel}:")
        for p in problems:
            print(f"  - {p}")
    return 1 if blocked else 0


if __name__ == "__main__":
    sys.exit(main())