Add the ledger schema, class router, lint codes, ingest/build pipeline, and three work-tree examples: align derivation, superfig delegation, and supertensor delegation.
101 lines
2.9 KiB
Python
Executable File
101 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Figure-class router. suggest() returns a class, never a toolkit."""
|
|
|
|
from __future__ import annotations
|
|
|
|
NUMERIC = frozenset({"loss-curve", "bar", "scatter", "histogram", "numeric-plot"})
|
|
RASTER = frozenset({"table", "photo", "apparatus", "ui"})
|
|
TENSOR = frozenset({
|
|
"axis", "shape", "transpose", "broadcast", "gather",
|
|
"shard", "contraction", "face",
|
|
})
|
|
DERIVE = frozenset({"rewrite-figure", "cancel-visual", "subst-visual"})
|
|
FIG = frozenset({
|
|
"architecture", "pipeline", "data-flow", "state", "time",
|
|
"dependency", "what-eats-what", "argument-map",
|
|
})
|
|
|
|
CLASS_SIGNALS = {
|
|
"numeric": NUMERIC,
|
|
"raster": RASTER,
|
|
"tensor": TENSOR,
|
|
"derive": DERIVE,
|
|
"fig": FIG,
|
|
}
|
|
|
|
VECTOR = frozenset({"superfig", "supertensor", "superderive", "matplotlib"})
|
|
SIBLING = frozenset({"superfig", "supertensor", "superderive"})
|
|
|
|
|
|
def classify(signals: set[str]) -> list[str]:
|
|
s = set(signals)
|
|
hit = [c for c, vocab in CLASS_SIGNALS.items() if s & vocab]
|
|
return hit or ["none"]
|
|
|
|
|
|
def suggest(signals: set[str], *, phase: int = 1) -> str:
|
|
"""Return a class name, or 'SPLIT:a+b+...' in CLASS_SIGNALS order."""
|
|
classes = classify(signals)
|
|
if len(classes) > 1:
|
|
return "SPLIT:" + "+".join(classes)
|
|
return classes[0]
|
|
|
|
|
|
def accepted(cls: str, *, phase: int = 1) -> frozenset[str]:
|
|
if cls == "numeric":
|
|
return frozenset({"screenshot", "matplotlib"})
|
|
if cls == "raster":
|
|
return frozenset({"screenshot"})
|
|
if cls == "tensor":
|
|
return frozenset({"supertensor"})
|
|
if cls == "fig":
|
|
return frozenset({"superfig"})
|
|
if cls == "derive":
|
|
return frozenset({"align", "superderive"} if phase >= 2 else {"align"})
|
|
if cls == "none":
|
|
return frozenset({"none"})
|
|
raise KeyError(cls)
|
|
|
|
|
|
def include_pdf(fig_id: str) -> str:
|
|
return f"figures/{fig_id}/build/{fig_id}.pdf"
|
|
|
|
|
|
def include_png(fig_id: str) -> str:
|
|
return f"figures/{fig_id}/orig.png"
|
|
|
|
|
|
def check_row(
|
|
signals: set[str],
|
|
toolkit: str,
|
|
*,
|
|
status: str = "planned",
|
|
include: str | None = None,
|
|
fig_id: str = "F1",
|
|
phase: int = 1,
|
|
) -> str | None:
|
|
"""First matching code, or None. SP010 is not an include/status rule."""
|
|
classes = classify(signals)
|
|
if len(classes) > 1:
|
|
return "SP011"
|
|
if toolkit not in accepted(classes[0], phase=phase):
|
|
return "SP010"
|
|
if toolkit == "none" and status != "dropped":
|
|
return "SP013"
|
|
if toolkit in {"align", "none"} and include is not None:
|
|
return "SP014"
|
|
if status == "included" and toolkit in VECTOR and include != include_pdf(fig_id):
|
|
return "SP015"
|
|
if status == "included" and toolkit == "screenshot" and include != include_png(fig_id):
|
|
return "SP016"
|
|
return None
|
|
|
|
|
|
def phase_from_env() -> int:
|
|
import os
|
|
raw = os.environ.get("SUPERPAPER_PHASE", "1")
|
|
try:
|
|
return int(raw)
|
|
except ValueError:
|
|
return 1
|