@@ -213,7 +213,7 @@ def to_markdown(self) -> str:
213213 order = [
214214 "Question" , "Data" , "Identification" ,
215215 "Estimator" , "Results" , "Robustness" ,
216- "Pipeline notes" , "Causal DAG" , "References" ,
216+ "Reviewer Audit" , " Pipeline notes" , "Causal DAG" , "References" ,
217217 ]
218218 chunks : List [str ] = []
219219 for title in order :
@@ -405,7 +405,7 @@ def to_qmd(
405405 order = [
406406 "Question" , "Data" , "Identification" ,
407407 "Estimator" , "Results" , "Robustness" ,
408- "Pipeline notes" , "Causal DAG" , "References" ,
408+ "Reviewer Audit" , " Pipeline notes" , "Causal DAG" , "References" ,
409409 ]
410410 # When self.dag is set, regenerate the Causal DAG body with the
411411 # Quarto-native mermaid block instead of the markdown text-art.
@@ -1021,6 +1021,124 @@ def _section_from_workflow(
10211021 return sections
10221022
10231023
1024+ def _reviewer_audit_section (
1025+ * ,
1026+ workflow : Any = None ,
1027+ result : Any = None ,
1028+ estimator : Optional [str ] = None ,
1029+ degradations : Optional [List [Dict [str , Any ]]] = None ,
1030+ ) -> str :
1031+ """Build a reviewer-facing audit section for a fitted draft."""
1032+ lines : List [str ] = []
1033+ lines .append ("**Reviewer-mode audit**" )
1034+ lines .append ("" )
1035+
1036+ # Estimator registry evidence.
1037+ est_name = estimator
1038+ if not est_name and workflow is not None :
1039+ rec = getattr (workflow , "recommendation" , None )
1040+ try :
1041+ if rec is not None and rec .recommendations :
1042+ est_name = rec .recommendations [0 ].get ("function" )
1043+ except Exception :
1044+ est_name = None
1045+ if est_name :
1046+ try :
1047+ from ..registry import describe_function
1048+ spec = describe_function (est_name )
1049+ lines .append (
1050+ f"- **Registry**: `sp.{ est_name } ` is "
1051+ f"`stability={ spec .get ('stability' )} `, "
1052+ f"`validation_status={ spec .get ('validation_status' )} `."
1053+ )
1054+ notes = spec .get ("validation_notes" ) or []
1055+ if notes :
1056+ shown = "; " .join (str (n ) for n in notes [:3 ])
1057+ lines .append (f"- **Validation evidence**: { shown } ." )
1058+ limitations = spec .get ("limitations" ) or []
1059+ if limitations :
1060+ lines .append ("- **Known implementation limitations**:" )
1061+ lines .extend (f" - { lim } " for lim in limitations [:5 ])
1062+ except Exception as exc :
1063+ lines .append (
1064+ f"- **Registry**: lookup for `sp.{ est_name } ` failed "
1065+ f"({ type (exc ).__name__ } : { exc } )."
1066+ )
1067+
1068+ # Identification report.
1069+ diag = getattr (workflow , "diagnostics" , None ) if workflow is not None else None
1070+ if diag is not None :
1071+ verdict = getattr (diag , "verdict" , None )
1072+ if verdict :
1073+ lines .append (f"- **Identification verdict**: `{ verdict } `." )
1074+ findings = getattr (diag , "findings" , None ) or []
1075+ if findings :
1076+ lines .append ("- **Identification findings to defend**:" )
1077+ for finding in findings [:6 ]:
1078+ sev = getattr (finding , "severity" , "info" )
1079+ msg = getattr (finding , "message" , str (finding ))
1080+ lines .append (f" - [{ str (sev ).upper ()} ] { msg } " )
1081+
1082+ # Post-estimation contract.
1083+ target = result or getattr (workflow , "result" , None )
1084+ if target is not None :
1085+ try :
1086+ from ..postestimation import postestimation_contract
1087+ contract = postestimation_contract (target )
1088+ available = ", " .join (sorted (contract ["available" ].keys ())[:10 ])
1089+ lines .append (f"- **Post-estimation surface**: { available } ." )
1090+ except Exception as exc :
1091+ lines .append (
1092+ f"- **Post-estimation surface**: unavailable "
1093+ f"({ type (exc ).__name__ } : { exc } )."
1094+ )
1095+
1096+ violations = getattr (target , "violations" , None )
1097+ if callable (violations ):
1098+ try :
1099+ v = violations ()
1100+ if v :
1101+ lines .append ("- **Result violations**:" )
1102+ if isinstance (v , dict ):
1103+ iterable = list (v .items ())[:8 ]
1104+ lines .extend (f" - `{ k } `: { val } " for k , val in iterable )
1105+ else :
1106+ lines .append (f" - { v } " )
1107+ else :
1108+ lines .append ("- **Result violations**: none reported by result." )
1109+ except Exception as exc :
1110+ lines .append (
1111+ f"- **Result violations**: check failed "
1112+ f"({ type (exc ).__name__ } : { exc } )."
1113+ )
1114+
1115+ prov = get_provenance (target )
1116+ if prov is not None :
1117+ lines .append (
1118+ f"- **Provenance**: run `{ prov .run_id } `, "
1119+ f"data hash `{ prov .data_hash or 'not recorded' } `."
1120+ )
1121+ else :
1122+ lines .append ("- **Provenance**: no attached provenance record found." )
1123+
1124+ if degradations :
1125+ lines .append ("- **Pipeline degradations**:" )
1126+ for item in degradations [:8 ]:
1127+ detail = f" ({ item .get ('detail' )} )" if item .get ("detail" ) else ""
1128+ lines .append (
1129+ f" - { item .get ('section' )} : { item .get ('error_type' )} : "
1130+ f"{ item .get ('message' )} { detail } "
1131+ )
1132+
1133+ lines .append ("" )
1134+ lines .append ("**Reviewer checklist**" )
1135+ lines .append ("- Re-run the replication script or `sp.replication_pack()` output." )
1136+ lines .append ("- Check identification assumptions against the study design, not only the code." )
1137+ lines .append ("- Inspect overlap, pre-trends, weak instruments, or bandwidth sensitivity when relevant." )
1138+ lines .append ("- Confirm exported tables are generated from `tidy()`/`glance()`/`sp.collect()` artifacts." )
1139+ return "\n " .join (lines )
1140+
1141+
10241142# --------------------------------------------------------------------- #
10251143# Top-level entry point
10261144# --------------------------------------------------------------------- #
@@ -1054,6 +1172,7 @@ def paper(
10541172 # v1.13: forwarded to sp.causal -> sp.recommend; default False
10551173 # keeps frontier MVP estimators out of auto-generated drafts.
10561174 allow_experimental : bool = False ,
1175+ reviewer_mode : bool = False ,
10571176) -> PaperDraft :
10581177 """End-to-end "data → publication-draft" pipeline.
10591178
@@ -1097,6 +1216,10 @@ def paper(
10971216 when you are explicitly drafting a paper around frontier
10981217 methods (e.g. ``causal_text`` or ``did_multiplegt_dyn``); the
10991218 Pipeline notes section records what was filtered.
1219+ reviewer_mode : bool, default False
1220+ Add a "Reviewer Audit" section summarizing registry validation
1221+ status, identification findings, post-estimation capabilities,
1222+ provenance, violations, and a replication checklist.
11001223
11011224 Returns
11021225 -------
@@ -1142,6 +1265,7 @@ def paper(
11421265 output_path = output_path ,
11431266 include_robustness = include_robustness ,
11441267 cite = cite , dag = dag ,
1268+ reviewer_mode = reviewer_mode ,
11451269 )
11461270
11471271 cols = list (data .columns )
@@ -1348,12 +1472,20 @@ def paper(
13481472 exc = exc ,
13491473 detail = f"result_type={ type (workflow .result ).__name__ } " ,
13501474 )
1351- sections [ "References" ] = (
1475+ references_body = (
13521476 "\n " .join (f"- { c } " for c in citations )
13531477 if citations else "_(No explicit citations attached — see "
13541478 "`workflow.result.cite()` if available.)_"
13551479 )
13561480
1481+ if reviewer_mode :
1482+ sections ["Reviewer Audit" ] = _reviewer_audit_section (
1483+ workflow = workflow ,
1484+ result = workflow .result ,
1485+ degradations = degradations ,
1486+ )
1487+ sections ["References" ] = references_body
1488+
13571489 pipeline_notes : List [str ] = []
13581490 for error in pipeline_errors :
13591491 _record_note (pipeline_notes , error )
@@ -1402,6 +1534,7 @@ def paper_from_question(
14021534 include_robustness : bool = True ,
14031535 cite : bool = True ,
14041536 dag : Any = None ,
1537+ reviewer_mode : bool = False ,
14051538) -> PaperDraft :
14061539 """Build a :class:`PaperDraft` from a :class:`CausalQuestion`.
14071540
@@ -1446,6 +1579,9 @@ def paper_from_question(
14461579 Pre-built ``sp.dag`` graph. When provided, the draft's
14471580 Identification section gains a *Causal DAG* subsection (text
14481581 rendering for markdown / mermaid for qmd).
1582+ reviewer_mode : bool, default False
1583+ Add a reviewer-facing audit section with registry validation,
1584+ post-estimation capabilities, provenance, and replication checks.
14491585
14501586 Returns
14511587 -------
@@ -1621,13 +1757,23 @@ def paper_from_question(
16211757 exc = exc ,
16221758 detail = f"underlying_type={ type (result .underlying ).__name__ } " ,
16231759 )
1624- sections [ "References" ] = (
1760+ references_body = (
16251761 "\n " .join (f"- { c } " for c in citations )
16261762 if citations
16271763 else "_(No explicit citations attached — see "
16281764 "`result.underlying.cite()` if available.)_"
16291765 )
16301766
1767+ if reviewer_mode :
1768+ target = result .underlying if result .underlying is not None else result
1769+ sections ["Reviewer Audit" ] = _reviewer_audit_section (
1770+ workflow = None ,
1771+ result = target ,
1772+ estimator = plan .estimator ,
1773+ degradations = degradations ,
1774+ )
1775+ sections ["References" ] = references_body
1776+
16311777 # DAG appendix when the user attached one.
16321778 if dag is not None :
16331779 try :
0 commit comments