Spaces:
Sleeping
Sleeping
release: v1.8.2 ICH M15 citation alignment
Browse files- stem_ai/render.py +89 -18
stem_ai/render.py
CHANGED
|
@@ -161,10 +161,12 @@ def render_markdown(result: dict[str, Any], mode: str, pages: int) -> str:
|
|
| 161 |
f"`{calibration.get('profile_read_mode', 'unknown')}`, "
|
| 162 |
f"`{calibration.get('profile_status', 'unknown')}`)",
|
| 163 |
*([f"**Calibration Effect:** {calibration_effect}"] if calibration_effect else []),
|
|
|
|
| 164 |
f"**Final Score:** **{score['final_score']} / 100**",
|
| 165 |
f"**Formal Tier:** **{score['formal_tier']}**",
|
| 166 |
f"**Tier Meaning:** {score['formal_tier']} = {score['use_scope']}",
|
| 167 |
f"**Use Scope:** {score['use_scope']}",
|
|
|
|
| 168 |
"",
|
| 169 |
"## Score Matrix",
|
| 170 |
"",
|
|
@@ -172,7 +174,7 @@ def render_markdown(result: dict[str, Any], mode: str, pages: int) -> str:
|
|
| 172 |
"| --- | ---: | ---: |",
|
| 173 |
f"| Stage 1 README Evidence Signal | 0.40 | {score['stage_1_readme_intent']} |",
|
| 174 |
f"| Stage 2R Repo-Local Consistency | 0.20 | {score['stage_2_repo_local_consistency']} |",
|
| 175 |
-
f"| Stage 3 Code/Bio Responsibility | 0.40 | {score['stage_3_code_bio']} |",
|
| 176 |
f"| Risk Penalty | -- | {score['risk_penalty']} |",
|
| 177 |
"",
|
| 178 |
"## Replication Evidence Lane",
|
|
@@ -291,6 +293,43 @@ def render_explain(result: dict[str, Any]) -> str:
|
|
| 291 |
return "\n".join(out) + "\n"
|
| 292 |
|
| 293 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
def _markdown_reasoning_summary(reasoning: dict[str, Any]) -> str:
|
| 295 |
if not reasoning:
|
| 296 |
return "Reasoning diagnostics are not available."
|
|
@@ -303,9 +342,9 @@ def _markdown_reasoning_summary(reasoning: dict[str, Any]) -> str:
|
|
| 303 |
f"Diagnostic-only heuristic `{reasoning.get('version', 'unknown')}` "
|
| 304 |
f"({policy.get('weights', 'uncalibrated')}); "
|
| 305 |
f"lane consistency `{coherence.get('status', 'unknown')}` "
|
| 306 |
-
f"({coherence.get('overall', 'n/a')}), "
|
| 307 |
f"uncertainty band `{uncertainty.get('status', 'unknown')}` "
|
| 308 |
-
f"({uncertainty.get('uncertainty', 'n/a')}), "
|
| 309 |
f"risk heuristic `{gate.get('status', 'unknown')}` "
|
| 310 |
f"({gate.get('evidence_risk', 'n/a')}), "
|
| 311 |
f"confidence envelope {envelope.get('lower', 'n/a')}-"
|
|
@@ -418,9 +457,11 @@ def _markdown_regulatory_section(result: dict[str, Any]) -> list[str]:
|
|
| 418 |
continue
|
| 419 |
lines.append(f"### {stage_key.replace('_', ' ').title()}")
|
| 420 |
for item in items:
|
|
|
|
|
|
|
| 421 |
lines.append(
|
| 422 |
f"- **{item['requirement_id']}** — {item['status']} "
|
| 423 |
-
f"(mapping confidence: {item['mapping_confidence']}, evidence strength: {item['evidence_strength']})"
|
| 424 |
)
|
| 425 |
lines.append(f" - {item['note']}")
|
| 426 |
lines.append("")
|
|
@@ -651,24 +692,49 @@ def _stage1_semantics_suffix(key: str, item: dict[str, Any]) -> str:
|
|
| 651 |
|
| 652 |
|
| 653 |
def _markdown_remediation_targets(result: dict[str, Any]) -> list[str]:
|
| 654 |
-
|
| 655 |
stage2 = result.get("stage_2r_rubric", {})
|
| 656 |
code_integrity = result.get("code_integrity", {})
|
| 657 |
if "R2R_D2_missing_clinical_use_boundary" in stage2:
|
| 658 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 659 |
if "R2R_D4_unsupported_workflow_claim" in stage2:
|
| 660 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 661 |
if code_integrity.get("C2_dependency_pinning", {}).get("status") in {"WARN", "FAIL"}:
|
| 662 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
if code_integrity.get("C5_compliance_boundary_integrity", {}).get("status") in {"WARN", "FAIL"}:
|
| 664 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 665 |
if code_integrity.get("C6_mock_auth_or_fail_open_boundary", {}).get("status") in {"WARN", "FAIL"}:
|
| 666 |
-
|
| 667 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
return []
|
| 669 |
-
lines = [
|
| 670 |
-
|
| 671 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
return lines
|
| 673 |
|
| 674 |
|
|
@@ -787,9 +853,11 @@ def _explain_regulatory_section(result: dict[str, Any]) -> list[str]:
|
|
| 787 |
continue
|
| 788 |
lines.append(f" {stage_key:<31}")
|
| 789 |
for item in items:
|
|
|
|
| 790 |
lines.append(
|
| 791 |
f" {item['requirement_id']}: {item['status']} "
|
| 792 |
f"(mapping={item['mapping_confidence']}, evidence={item['evidence_strength']})"
|
|
|
|
| 793 |
)
|
| 794 |
lines.append(f" note: {item['note']}")
|
| 795 |
summary = result.get("regulatory_traceability", {}).get("summary")
|
|
@@ -1900,12 +1968,14 @@ def _page5_compact_closure(result: dict[str, Any]) -> list[Any]:
|
|
| 1900 |
_style(f"AIRC_{str(risk.get('id', 'risk'))[:8]}", 8, 10, _DGRAY),
|
| 1901 |
))
|
| 1902 |
if airi.get("known_gaps_in_bundle"):
|
|
|
|
| 1903 |
gap_preview = ", ".join(
|
| 1904 |
f"{g.get('id', '—')} {_xt(str(g.get('title', '')))}"
|
| 1905 |
-
for g in
|
| 1906 |
)
|
|
|
|
| 1907 |
story.append(Paragraph(
|
| 1908 |
-
f'<font color="{_DGRAY}" size="8">Known gaps
|
| 1909 |
_style("AIRI_GAPC", 8, 10, _DGRAY),
|
| 1910 |
))
|
| 1911 |
|
|
@@ -2024,10 +2094,11 @@ def _page6_method_airi(result: dict[str, Any]) -> list[Any]:
|
|
| 2024 |
gaps = airi.get("known_gaps_in_bundle", [])
|
| 2025 |
if gaps:
|
| 2026 |
gap_preview = ", ".join(
|
| 2027 |
-
f"{g.get('id', '—')} {_xt(str(g.get('title', '')))}" for g in gaps[:
|
| 2028 |
)
|
|
|
|
| 2029 |
story.append(Paragraph(
|
| 2030 |
-
f'<font color="{_DGRAY}" size="8">Known gaps
|
| 2031 |
_style("AIRIPDF_GAPS", 8, 11, _DGRAY),
|
| 2032 |
))
|
| 2033 |
|
|
|
|
| 161 |
f"`{calibration.get('profile_read_mode', 'unknown')}`, "
|
| 162 |
f"`{calibration.get('profile_status', 'unknown')}`)",
|
| 163 |
*([f"**Calibration Effect:** {calibration_effect}"] if calibration_effect else []),
|
| 164 |
+
*_classification_applied_md(result),
|
| 165 |
f"**Final Score:** **{score['final_score']} / 100**",
|
| 166 |
f"**Formal Tier:** **{score['formal_tier']}**",
|
| 167 |
f"**Tier Meaning:** {score['formal_tier']} = {score['use_scope']}",
|
| 168 |
f"**Use Scope:** {score['use_scope']}",
|
| 169 |
+
*_tier_lock_label_md(result),
|
| 170 |
"",
|
| 171 |
"## Score Matrix",
|
| 172 |
"",
|
|
|
|
| 174 |
"| --- | ---: | ---: |",
|
| 175 |
f"| Stage 1 README Evidence Signal | 0.40 | {score['stage_1_readme_intent']} |",
|
| 176 |
f"| Stage 2R Repo-Local Consistency | 0.20 | {score['stage_2_repo_local_consistency']} |",
|
| 177 |
+
f"| Stage 3 Code/Bio Responsibility | 0.40 | {score['stage_3_code_bio']}{_s3_formula(result)} |",
|
| 178 |
f"| Risk Penalty | -- | {score['risk_penalty']} |",
|
| 179 |
"",
|
| 180 |
"## Replication Evidence Lane",
|
|
|
|
| 293 |
return "\n".join(out) + "\n"
|
| 294 |
|
| 295 |
|
| 296 |
+
def _classification_applied_md(result: dict[str, Any]) -> list[str]:
|
| 297 |
+
cls = result.get("classification", {})
|
| 298 |
+
ca = cls.get("ca_severity", "none")
|
| 299 |
+
cap = cls.get("score_cap")
|
| 300 |
+
t0 = cls.get("t0_hard_floor", False)
|
| 301 |
+
cap_str = str(cap) if cap is not None else "none"
|
| 302 |
+
t0_str = "active" if t0 else "clear"
|
| 303 |
+
return [f"**Classification Applied:** ca_severity={ca} | score_cap={cap_str} | t0_floor={t0_str}"]
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
def _tier_lock_label_md(result: dict[str, Any]) -> list[str]:
|
| 307 |
+
cls = result.get("classification", {})
|
| 308 |
+
score_cap = cls.get("score_cap")
|
| 309 |
+
if score_cap is None:
|
| 310 |
+
return []
|
| 311 |
+
if cls.get("t0_hard_floor"):
|
| 312 |
+
return [
|
| 313 |
+
f"**Tier Lock [T0-FLOOR]:** Score ceiling active at **39** (T0 maximum). "
|
| 314 |
+
f"CA-DIRECT classification with insufficient code presence. "
|
| 315 |
+
f"Resolving this condition is required before any tier advancement."
|
| 316 |
+
]
|
| 317 |
+
return [
|
| 318 |
+
f"**Tier Lock [CA-CAP]:** Score ceiling active at **{score_cap}** (T2 maximum). "
|
| 319 |
+
f"Clinical-adjacent surface detected without explicit non-clinical boundary. "
|
| 320 |
+
f"Adding a non-diagnostic disclaimer resolves this lock."
|
| 321 |
+
]
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def _s3_formula(result: dict[str, Any]) -> str:
|
| 325 |
+
s3_raw = result.get("stage_3_rubric", {}).get("stage_3_raw_total", {})
|
| 326 |
+
raw = s3_raw.get("score")
|
| 327 |
+
max_val = s3_raw.get("max")
|
| 328 |
+
if raw is None or not max_val:
|
| 329 |
+
return ""
|
| 330 |
+
return f" (raw: {raw}/{max_val})"
|
| 331 |
+
|
| 332 |
+
|
| 333 |
def _markdown_reasoning_summary(reasoning: dict[str, Any]) -> str:
|
| 334 |
if not reasoning:
|
| 335 |
return "Reasoning diagnostics are not available."
|
|
|
|
| 342 |
f"Diagnostic-only heuristic `{reasoning.get('version', 'unknown')}` "
|
| 343 |
f"({policy.get('weights', 'uncalibrated')}); "
|
| 344 |
f"lane consistency `{coherence.get('status', 'unknown')}` "
|
| 345 |
+
f"({coherence.get('overall', 'n/a')}; >=0.80=consistent, >=0.55=mixed, <0.55=divergent), "
|
| 346 |
f"uncertainty band `{uncertainty.get('status', 'unknown')}` "
|
| 347 |
+
f"({uncertainty.get('uncertainty', 'n/a')}; <0.20=low-spread, <=0.45=review-advised, >0.45=manual-review), "
|
| 348 |
f"risk heuristic `{gate.get('status', 'unknown')}` "
|
| 349 |
f"({gate.get('evidence_risk', 'n/a')}), "
|
| 350 |
f"confidence envelope {envelope.get('lower', 'n/a')}-"
|
|
|
|
| 457 |
continue
|
| 458 |
lines.append(f"### {stage_key.replace('_', ' ').title()}")
|
| 459 |
for item in items:
|
| 460 |
+
_src = ", ".join(item.get("source_ids", []))
|
| 461 |
+
_src_tag = f" `[{_src}]`" if _src else ""
|
| 462 |
lines.append(
|
| 463 |
f"- **{item['requirement_id']}** — {item['status']} "
|
| 464 |
+
f"(mapping confidence: {item['mapping_confidence']}, evidence strength: {item['evidence_strength']}){_src_tag}"
|
| 465 |
)
|
| 466 |
lines.append(f" - {item['note']}")
|
| 467 |
lines.append("")
|
|
|
|
| 692 |
|
| 693 |
|
| 694 |
def _markdown_remediation_targets(result: dict[str, Any]) -> list[str]:
|
| 695 |
+
rows: list[tuple[str, str, str]] = []
|
| 696 |
stage2 = result.get("stage_2r_rubric", {})
|
| 697 |
code_integrity = result.get("code_integrity", {})
|
| 698 |
if "R2R_D2_missing_clinical_use_boundary" in stage2:
|
| 699 |
+
rows.append((
|
| 700 |
+
"R2R_D2 missing clinical boundary",
|
| 701 |
+
"Add non-clinical/non-diagnostic disclaimer to README and all adjacent docs",
|
| 702 |
+
"+20 S2R (+4 final) | unlocks tier cap",
|
| 703 |
+
))
|
| 704 |
if "R2R_D4_unsupported_workflow_claim" in stage2:
|
| 705 |
+
rows.append((
|
| 706 |
+
"R2R_D4 unsupported workflow claim",
|
| 707 |
+
"Align README workflow/demo/CLI claims with actual local support surfaces",
|
| 708 |
+
"+15 S2R (+3 final)",
|
| 709 |
+
))
|
| 710 |
if code_integrity.get("C2_dependency_pinning", {}).get("status") in {"WARN", "FAIL"}:
|
| 711 |
+
rows.append((
|
| 712 |
+
"C2 dependency pinning WARN",
|
| 713 |
+
"Pin production dependencies; document external-service dependence explicitly",
|
| 714 |
+
"C2 -> PASS (no direct score delta)",
|
| 715 |
+
))
|
| 716 |
if code_integrity.get("C5_compliance_boundary_integrity", {}).get("status") in {"WARN", "FAIL"}:
|
| 717 |
+
rows.append((
|
| 718 |
+
"C5 compliance boundary WARN",
|
| 719 |
+
"Remove unsupported legal/compliance language or add backing governance evidence",
|
| 720 |
+
"C5 -> PASS (no direct score delta)",
|
| 721 |
+
))
|
| 722 |
if code_integrity.get("C6_mock_auth_or_fail_open_boundary", {}).get("status") in {"WARN", "FAIL"}:
|
| 723 |
+
rows.append((
|
| 724 |
+
"C6 mock-auth boundary WARN",
|
| 725 |
+
"Separate mock-auth/auto-login flows from production trust boundary narrative",
|
| 726 |
+
"C6 -> PASS (no direct score delta)",
|
| 727 |
+
))
|
| 728 |
+
if not rows:
|
| 729 |
return []
|
| 730 |
+
lines = [
|
| 731 |
+
"", "## Remediation Roadmap",
|
| 732 |
+
"",
|
| 733 |
+
"| Finding | Action | Expected Impact |",
|
| 734 |
+
"| --- | --- | --- |",
|
| 735 |
+
]
|
| 736 |
+
for finding, action, impact in rows:
|
| 737 |
+
lines.append(f"| {finding} | {action} | {impact} |")
|
| 738 |
return lines
|
| 739 |
|
| 740 |
|
|
|
|
| 853 |
continue
|
| 854 |
lines.append(f" {stage_key:<31}")
|
| 855 |
for item in items:
|
| 856 |
+
_src = ", ".join(item.get("source_ids", []))
|
| 857 |
lines.append(
|
| 858 |
f" {item['requirement_id']}: {item['status']} "
|
| 859 |
f"(mapping={item['mapping_confidence']}, evidence={item['evidence_strength']})"
|
| 860 |
+
+ (f" | source: {_src}" if _src else "")
|
| 861 |
)
|
| 862 |
lines.append(f" note: {item['note']}")
|
| 863 |
summary = result.get("regulatory_traceability", {}).get("summary")
|
|
|
|
| 1968 |
_style(f"AIRC_{str(risk.get('id', 'risk'))[:8]}", 8, 10, _DGRAY),
|
| 1969 |
))
|
| 1970 |
if airi.get("known_gaps_in_bundle"):
|
| 1971 |
+
_all_gaps = airi.get("known_gaps_in_bundle", [])
|
| 1972 |
gap_preview = ", ".join(
|
| 1973 |
f"{g.get('id', '—')} {_xt(str(g.get('title', '')))}"
|
| 1974 |
+
for g in _all_gaps[:5]
|
| 1975 |
)
|
| 1976 |
+
_gap_extra = f" (+{len(_all_gaps) - 5} more)" if len(_all_gaps) > 5 else ""
|
| 1977 |
story.append(Paragraph(
|
| 1978 |
+
f'<font color="{_DGRAY}" size="8">Known gaps: {gap_preview}{_gap_extra}</font>',
|
| 1979 |
_style("AIRI_GAPC", 8, 10, _DGRAY),
|
| 1980 |
))
|
| 1981 |
|
|
|
|
| 2094 |
gaps = airi.get("known_gaps_in_bundle", [])
|
| 2095 |
if gaps:
|
| 2096 |
gap_preview = ", ".join(
|
| 2097 |
+
f"{g.get('id', '—')} {_xt(str(g.get('title', '')))}" for g in gaps[:5]
|
| 2098 |
)
|
| 2099 |
+
_gap_extra = f" (+{len(gaps) - 5} more)" if len(gaps) > 5 else ""
|
| 2100 |
story.append(Paragraph(
|
| 2101 |
+
f'<font color="{_DGRAY}" size="8">Known gaps: {gap_preview}{_gap_extra}</font>',
|
| 2102 |
_style("AIRIPDF_GAPS", 8, 11, _DGRAY),
|
| 2103 |
))
|
| 2104 |
|