Add CLAUDE.md defining the Palladium master notebook conventions and Red Panda Approval criteria, plus a review-notebook slash command for LLM-driven notebook review. Expand .gitignore to block client/engagement documents and generated exports, keeping masters client-clean while allowing text/image sources. Normalize slider widget numeric values from floats to integers in notebook JSON.
63 lines
2.3 KiB
Python
63 lines
2.3 KiB
Python
"""Structured engagement exports — the JSON is the source-of-truth artifact.
|
|
|
|
``exports/{engagement_id}.json`` — the full Engagement, serialized.
|
|
``exports/{engagement_id}.csv`` — one row per competency, for
|
|
cross-engagement spreadsheet analysis (build spec §8).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pandas as pd
|
|
|
|
from .models import DiagnosticConfig, Engagement
|
|
|
|
CSV_COLUMNS = [
|
|
"engagement_id", "client_name", "industry", "workshop_date",
|
|
"competency_id", "dimension", "score", "evidence",
|
|
"is_foundational", "is_binding_constraint",
|
|
]
|
|
|
|
|
|
def engagement_json(engagement: Engagement) -> str:
|
|
return json.dumps(engagement.model_dump(mode="json"), indent=2,
|
|
ensure_ascii=False)
|
|
|
|
|
|
def scores_dataframe(engagement: Engagement,
|
|
config: DiagnosticConfig) -> pd.DataFrame:
|
|
binding = set(engagement.computed_value.binding_constraints
|
|
if engagement.computed_value else [])
|
|
foundational = set(config.foundational_competencies)
|
|
rows = [{
|
|
"engagement_id": engagement.engagement_id,
|
|
"client_name": engagement.client_name,
|
|
"industry": engagement.industry_config,
|
|
"workshop_date": engagement.workshop_date.isoformat(),
|
|
"competency_id": s.competency_id,
|
|
"dimension": s.dimension,
|
|
"score": s.score,
|
|
"evidence": s.evidence,
|
|
"is_foundational": s.competency_id in foundational,
|
|
"is_binding_constraint": s.competency_id in binding,
|
|
} for s in engagement.scores]
|
|
return pd.DataFrame(rows, columns=CSV_COLUMNS)
|
|
|
|
|
|
def write_exports(engagement: Engagement, config: DiagnosticConfig,
|
|
exports_dir: Path) -> tuple[Path, Path]:
|
|
"""Write both artifacts; returns ``(json_path, csv_path)``."""
|
|
exports_dir.mkdir(parents=True, exist_ok=True)
|
|
json_path = exports_dir / f"{engagement.engagement_id}.json"
|
|
csv_path = exports_dir / f"{engagement.engagement_id}.csv"
|
|
json_path.write_text(engagement_json(engagement), encoding="utf-8")
|
|
scores_dataframe(engagement, config).to_csv(csv_path, index=False)
|
|
return json_path, csv_path
|
|
|
|
|
|
def load_engagement(json_path: Path) -> Engagement:
|
|
"""Reload a saved engagement for review (acceptance §10 nice-to-have)."""
|
|
return Engagement.model_validate_json(json_path.read_text(encoding="utf-8"))
|