← Files Investment BankingARCHIVED FILE
skills/investment-banking/internal-support/style-guide-adapter/scripts/build_style_profile.py
9.53 KB · Oct 2, 2026 · 00:27 UTC
#!/usr/bin/env python3
"""Build a deterministic style profile package from extractor output or sparse intake."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any, Mapping, Sequence
PLUGIN_ROOT = Path(__file__).resolve().parents[5]
if str(PLUGIN_ROOT) not in sys.path:
sys.path.insert(0, str(PLUGIN_ROOT))
from shared.artifacts import ( # noqa: E402
artifact_item,
logs_dir,
render_html_report,
support_dir,
write_artifact_manifest,
write_dashboard_contract,
)
def load_payload(path: Path | None) -> dict[str, Any]:
if path is None:
return {}
return json.loads(path.read_text(encoding="utf-8"))
def as_rows(value: Any, fallback: list[dict[str, Any]]) -> list[dict[str, Any]]:
if isinstance(value, list) and all(isinstance(item, dict) for item in value):
return value
return fallback
def write_support_payload(
outdir: Path, payload: Mapping[str, Any], name: str = "intake.json"
) -> Path:
target = support_dir(outdir) / name
target.write_text(json.dumps(dict(payload), indent=2) + "\n", encoding="utf-8")
return target
def print_summary(
json_run_log: bool, quiet: bool, summary: dict[str, Any], human_lines: Sequence[str]
) -> None:
if json_run_log:
print(json.dumps(summary, indent=2))
elif not quiet:
for line in human_lines:
print(line)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--input", type=Path)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--json-run-log", "--json", dest="json_run_log", action="store_true")
parser.add_argument("--quiet-human-output", action="store_true")
args = parser.parse_args()
out = args.output_dir
out.mkdir(parents=True, exist_ok=True)
payload = load_payload(args.input)
profile = {
"contract_name": "style_guide_adapter_style_profile",
"style_profile_id": payload.get("style_profile_id", "STYLE-001"),
"target_style_scope": payload.get("target_style_scope", "presentation and memo materials"),
"artifact_type": payload.get("artifact_type", "mixed"),
"style_source_records": payload.get(
"style_source_records",
[
{
"source_id": "STYLE-SRC-001",
"source_name": "style intake",
"source_type": "user_supplied",
"source_priority": "primary",
"source_scope": "style",
"source_date_or_period": payload.get("as_of_date", "not_provided"),
"freshness": "unknown",
"relevance": "medium",
"confidence": "medium",
"limitations": "deterministic style profile scaffold",
}
],
),
"source_basis_summary": "Style profile generated from supplied style/extractor payload; visual review still required.",
"style_provenance_labels": ["user_supplied_style"],
"visual_system": payload.get(
"visual_system", "preserve source hierarchy, typography, and exhibit conventions"
),
"layout_grammar": payload.get("layout_grammar", "preserve page density and section order"),
"exhibit_conventions": payload.get(
"exhibit_conventions", "preserve chart/table labeling and footnotes"
),
"writing_voice": payload.get("writing_voice", "banker concise"),
"citation_and_footnote_norms": payload.get(
"citation_and_footnote_norms", "preserve existing source/citation treatment"
),
"artifact_specific_rules": payload.get(
"artifact_specific_rules", "do not alter model numbers or legal language"
),
"do_not_change_rules": payload.get(
"do_not_change_rules", "no substantive edits without explicit instruction"
),
"style_assumptions": ["style source requires visual confirmation"],
"style_conflicts": ["not reviewed against all target artifacts"],
"open_style_questions": [{"item": "Confirm latest house style source", "status": "open"}],
"style_confidence": "medium",
"style_freshness_status": "unknown",
"visual_review_status": "needs_review",
"style_limitations": "No native restyling performed by this profile builder.",
"circulation_caveats": [
{
"caveat": "Visual/style profile requires QC before client circulation",
"impact": "not client-ready",
"owner": "Associate",
}
],
}
change_log = {
"contract_name": "style_guide_adapter_change_log",
"change_log_id": "STYLE-CHANGE-001",
"style_profile_id": profile["style_profile_id"],
"source_artifact": str(args.input or "not_provided"),
"output_artifact": "style_profile_report.html",
"artifact_type": profile["artifact_type"],
"artifact_version": "draft",
"mode": "profile_only",
"style_sources_used": ["STYLE-SRC-001"],
"changes_made": [
{
"location": "profile",
"area": "style",
"change": "generated deterministic style profile",
"basis": "input payload",
"provenance_label": "user_supplied_style",
"confidence": "medium",
"content_impact": "none",
}
],
"preserved_elements": ["content", "numbers", "formulas"],
"substantive_edits": ["none"],
"data_formula_source_integrity_status": "preserved",
"visual_review_status": "needs_review",
"visual_review_notes": "No before/after visual diff created by profile builder.",
"open_items": [
{
"item_id": "STYLE-OI-001",
"description": "Run visual QC",
"why_it_matters": "style-only changes can still break circulation quality",
"owner": "Associate",
"status": "open",
"due_date": "not_provided",
"blocks_circulation": True,
"suggested_remediation": "Run ib-deck-qc/style visual review",
}
],
"non_client_ready_reasons": ["visual review not complete"],
"downstream_qc_required": True,
"circulation_caveats": profile["circulation_caveats"],
}
support = support_dir(out)
profile_path = support / "style_profile.json"
change_path = support / "style_change_log.json"
profile_path.write_text(json.dumps(profile, indent=2) + "\n", encoding="utf-8")
change_path.write_text(json.dumps(change_log, indent=2) + "\n", encoding="utf-8")
contract = logs_dir(out) / "style_profile_report_contract.json"
write_dashboard_contract(
contract,
"style-guide-adapter",
"Style Profile Report",
"Style profile",
"report_only",
out / "style_profile_report.html",
report_body=[
{
"heading": "Style Profile",
"body": "Profile generated as a control layer. Native restyling and visual diff/QC remain required before circulation.",
}
],
sections=[
{
"title": "Style Controls",
"modules": [
{
"type": "table",
"title": "Open Style Questions",
"rows": profile["open_style_questions"],
},
{"type": "table", "title": "Change Log", "rows": change_log["changes_made"]},
],
}
],
)
html = render_html_report(contract, out, "style_profile_report.html")
write_artifact_manifest(
out,
"style-guide-adapter",
"html_report",
html,
support_artifacts=[
artifact_item(
profile_path,
"support_artifact",
"json",
"Schema-backed style profile handoff.",
False,
True,
"Style profile JSON is machine-readable handoff support.",
),
artifact_item(
change_path,
"support_artifact",
"json",
"Schema-backed style change log.",
False,
True,
"Change-log JSON is machine-readable handoff support.",
),
artifact_item(
contract,
"support_artifact",
"json",
"Dashboard-builder render contract.",
False,
False,
"Render contract is internal plumbing.",
),
],
blocked_or_partial_status={
"status": "partial",
"reason": "Native restyling and visual diff/QC are not complete.",
"missing_inputs": ["visual diff", "native Office restyle", "deck QC"],
},
)
summary = {
"primary_human_deliverable": str(html),
"style_profile": str(profile_path),
"change_log": str(change_path),
"manifest": str(out / "manifest.json"),
}
print_summary(
args.json_run_log,
args.quiet_human_output,
summary,
[
"Style profile report complete",
f"Open first: {html}",
"Support style profile/change-log JSON written under support/.",
],
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
SHA-256: 6e05fab93a03b43141cd03141dd801c1e19692aa66b2fc6bdd39027f3f0b0c59