1717from pathlib import Path
1818from typing import Any
1919
20-
2120ALL_RENDER_TARGETS : tuple [str , ...] = (
2221 "pymdp" ,
2322 "rxinfer" ,
@@ -104,7 +103,9 @@ def _first_mapping_value(mapping: dict[str, Any]) -> dict[str, Any]:
104103
105104
106105def _check_pipeline_summary (output_dir : Path , report : ContractReport ) -> None :
107- summary_path = output_dir / "00_pipeline_summary" / "pipeline_execution_summary.json"
106+ summary_path = (
107+ output_dir / "00_pipeline_summary" / "pipeline_execution_summary.json"
108+ )
108109 summary = _load_json (summary_path , report )
109110 if not isinstance (summary , dict ):
110111 return
@@ -116,20 +117,31 @@ def _check_pipeline_summary(output_dir: Path, report: ContractReport) -> None:
116117 )
117118
118119 steps = summary .get ("steps" , [])
119- report .require (isinstance (steps , list ) and len (steps ) >= 25 , "Pipeline summary must include all 25 numbered steps." )
120+ report .require (
121+ isinstance (steps , list ) and len (steps ) >= 25 ,
122+ "Pipeline summary must include all 25 numbered steps." ,
123+ )
120124 if isinstance (steps , list ):
121125 bad_steps : list [str ] = []
122126 for step in steps :
123127 if not isinstance (step , dict ):
124128 continue
125- step_name = str (step .get ("script_name" ) or step .get ("step" ) or step .get ("name" ) or "unknown" )
129+ step_name = str (
130+ step .get ("script_name" )
131+ or step .get ("step" )
132+ or step .get ("name" )
133+ or "unknown"
134+ )
126135 exit_code = step .get ("exit_code" )
127136 status = str (step .get ("status" , "" )).lower ()
128137 if exit_code not in (0 , 2 , None ) or status in {"failed" , "error" }:
129- bad_steps .append (f"{ step_name } : status={ status !r} exit_code={ exit_code !r} " )
138+ bad_steps .append (
139+ f"{ step_name } : status={ status !r} exit_code={ exit_code !r} "
140+ )
130141 report .require (
131142 not bad_steps ,
132- "Pipeline summary contains non-success/non-warning steps: " + "; " .join (bad_steps ),
143+ "Pipeline summary contains non-success/non-warning steps: "
144+ + "; " .join (bad_steps ),
133145 )
134146 report .note ("pipeline summary" )
135147
@@ -138,10 +150,20 @@ def _check_parse_outputs(output_dir: Path, report: ContractReport) -> None:
138150 summary_path = output_dir / "3_gnn_output" / "gnn_processing_summary.json"
139151 summary = _load_json (summary_path , report )
140152 if isinstance (summary , dict ):
141- report .require (summary .get ("total_files" ) == 1 , "Step 3 should process exactly one GridWorld model." )
142- report .require (summary .get ("successful_parses" ) == 1 , "Step 3 should parse the GridWorld model successfully." )
143- parsed_candidates = sorted ((output_dir / "3_gnn_output" ).glob (f"**/{ GRIDWORLD_STEM } *_parsed.json" ))
144- report .require (bool (parsed_candidates ), "Step 3 parsed GridWorld JSON artifact is missing." )
153+ report .require (
154+ summary .get ("total_files" ) == 1 ,
155+ "Step 3 should process exactly one GridWorld model." ,
156+ )
157+ report .require (
158+ summary .get ("successful_parses" ) == 1 ,
159+ "Step 3 should parse the GridWorld model successfully." ,
160+ )
161+ parsed_candidates = sorted (
162+ (output_dir / "3_gnn_output" ).glob (f"**/{ GRIDWORLD_STEM } *_parsed.json" )
163+ )
164+ report .require (
165+ bool (parsed_candidates ), "Step 3 parsed GridWorld JSON artifact is missing."
166+ )
145167 report .note ("parse outputs" )
146168
147169
@@ -151,31 +173,56 @@ def _check_render_outputs(output_dir: Path, report: ContractReport) -> None:
151173 if not isinstance (summary , dict ):
152174 return
153175
154- report .require (summary .get ("total_files" ) == 1 , "Step 11 should render exactly one GridWorld model." )
155- report .require (summary .get ("successful_files" ) == 1 , "Step 11 should mark the GridWorld file successful." )
176+ report .require (
177+ summary .get ("total_files" ) == 1 ,
178+ "Step 11 should render exactly one GridWorld model." ,
179+ )
180+ report .require (
181+ summary .get ("successful_files" ) == 1 ,
182+ "Step 11 should mark the GridWorld file successful." ,
183+ )
156184 report .require (
157185 not summary .get ("failed_framework_renderings" ),
158186 "Step 11 has failed framework renderings: "
159187 + json .dumps (summary .get ("failed_framework_renderings" ), sort_keys = True ),
160188 )
161189
162- file_result = _first_mapping_value (summary .get ("file_results" , {}) if isinstance (summary .get ("file_results" ), dict ) else {})
190+ file_result = _first_mapping_value (
191+ summary .get ("file_results" , {})
192+ if isinstance (summary .get ("file_results" ), dict )
193+ else {}
194+ )
163195 framework_results = file_result .get ("framework_results" , {})
164- report .require (isinstance (framework_results , dict ), "Step 11 summary lacks framework_results mapping." )
196+ report .require (
197+ isinstance (framework_results , dict ),
198+ "Step 11 summary lacks framework_results mapping." ,
199+ )
165200 if isinstance (framework_results , dict ):
166201 missing = [name for name in ALL_RENDER_TARGETS if name not in framework_results ]
167- report .require (not missing , f"Step 11 did not attempt all render targets: { missing } " )
202+ report .require (
203+ not missing , f"Step 11 did not attempt all render targets: { missing } "
204+ )
168205 failed = [
169206 f"{ name } : { result .get ('message' , '' )} "
170207 for name , result in framework_results .items ()
171- if name in ALL_RENDER_TARGETS and isinstance (result , dict ) and not result .get ("success" )
208+ if name in ALL_RENDER_TARGETS
209+ and isinstance (result , dict )
210+ and not result .get ("success" )
172211 ]
173- report .require (not failed , "Step 11 render target failures: " + "; " .join (failed ))
212+ report .require (
213+ not failed , "Step 11 render target failures: " + "; " .join (failed )
214+ )
174215
175216 for framework in ALL_RENDER_TARGETS :
176217 framework_dir = render_dir / GRIDWORLD_STEM / framework
177- report .require (framework_dir .exists (), f"Missing Step 11 framework directory: { framework_dir } " )
178- report .require (any (framework_dir .glob ("*" )), f"Step 11 framework directory is empty: { framework_dir } " )
218+ report .require (
219+ framework_dir .exists (),
220+ f"Missing Step 11 framework directory: { framework_dir } " ,
221+ )
222+ report .require (
223+ any (framework_dir .glob ("*" )),
224+ f"Step 11 framework directory is empty: { framework_dir } " ,
225+ )
179226 report .note ("render outputs" )
180227
181228
@@ -196,23 +243,42 @@ def _simulation_payloads(output_dir: Path, framework: str) -> list[dict[str, Any
196243
197244
198245def _check_execute_outputs (output_dir : Path , report : ContractReport ) -> None :
199- summary_path = output_dir / "12_execute_output" / "summaries" / "execution_summary.json"
246+ summary_path = (
247+ output_dir / "12_execute_output" / "summaries" / "execution_summary.json"
248+ )
200249 summary = _load_json (summary_path , report )
201250 if isinstance (summary , dict ):
202251 encoded = json .dumps (summary , sort_keys = True ).lower ()
203252 report .require (
204- any (token in encoded for token in ("succeeded" , "failed" , "skipped" , "success_count" , "failure_count" )),
253+ any (
254+ token in encoded
255+ for token in (
256+ "succeeded" ,
257+ "failed" ,
258+ "skipped" ,
259+ "success_count" ,
260+ "failure_count" ,
261+ )
262+ ),
205263 "Step 12 summary should include explicit success/skipped/error accounting." ,
206264 )
207265
208266 for framework in STRICT_EXECUTION_TARGETS :
209267 payloads = _simulation_payloads (output_dir , framework )
210- report .require (payloads , f"Missing Step 12 collected simulation results for { framework } ." )
268+ report .require (
269+ payloads , f"Missing Step 12 collected simulation results for { framework } ."
270+ )
211271 if not payloads :
212272 continue
213273 payload = payloads [0 ]
214- report .require (payload .get ("success" ) is True , f"{ framework } simulation payload is not successful." )
215- report .require (payload .get ("num_timesteps" ) == 15 , f"{ framework } should report 15 timesteps." )
274+ report .require (
275+ payload .get ("success" ) is True ,
276+ f"{ framework } simulation payload is not successful." ,
277+ )
278+ report .require (
279+ payload .get ("num_timesteps" ) == 15 ,
280+ f"{ framework } should report 15 timesteps." ,
281+ )
216282 report .require (
217283 payload .get ("model_parameters" , {}).get ("B_shape" ) == [9 , 9 , 5 ],
218284 f"{ framework } should preserve GridWorld B shape [9, 9, 5]." ,
@@ -226,7 +292,9 @@ def _check_execute_outputs(output_dir: Path, report: ContractReport) -> None:
226292
227293def _check_analysis_outputs (output_dir : Path , report : ContractReport ) -> None :
228294 analysis_dir = output_dir / "16_analysis_output"
229- manifest_path = analysis_dir / "cross_framework" / "gridworld_analysis_manifest.json"
295+ manifest_path = (
296+ analysis_dir / "cross_framework" / "gridworld_analysis_manifest.json"
297+ )
230298 manifest = _load_json (manifest_path , report )
231299 if isinstance (manifest , dict ):
232300 frameworks = sorted (manifest .get ("frameworks" , []))
@@ -239,16 +307,31 @@ def _check_analysis_outputs(output_dir: Path, report: ContractReport) -> None:
239307 "GridWorld analysis manifest should confirm matching matrix provenance." ,
240308 )
241309 outputs = manifest .get ("outputs" , {})
242- report .require (bool (outputs .get ("png" )), "GridWorld analysis manifest should list PNG outputs." )
243- report .require (len (outputs .get ("gif" , [])) >= 7 , "GridWorld analysis manifest should list GridWorld GIF outputs." )
244- report .require (bool (outputs .get ("statistics" )), "GridWorld analysis manifest should list statistics outputs." )
310+ report .require (
311+ bool (outputs .get ("png" )),
312+ "GridWorld analysis manifest should list PNG outputs." ,
313+ )
314+ report .require (
315+ len (outputs .get ("gif" , [])) >= 7 ,
316+ "GridWorld analysis manifest should list GridWorld GIF outputs." ,
317+ )
318+ report .require (
319+ bool (outputs .get ("statistics" )),
320+ "GridWorld analysis manifest should list statistics outputs." ,
321+ )
245322
246323 pngs = sorted (analysis_dir .glob ("**/*.png" ))
247324 gifs = sorted (analysis_dir .glob ("**/*.gif" ))
248325 report .require (bool (pngs ), "Step 16 PNG analysis outputs are missing." )
249326 report .require (bool (gifs ), "Step 16 GIF animation outputs are missing." )
250- report .require (all (path .stat ().st_size > 0 for path in pngs [:10 ]), "One or more Step 16 PNG outputs are empty." )
251- report .require (all (path .stat ().st_size > 0 for path in gifs ), "One or more Step 16 GIF outputs are empty." )
327+ report .require (
328+ all (path .stat ().st_size > 0 for path in pngs [:10 ]),
329+ "One or more Step 16 PNG outputs are empty." ,
330+ )
331+ report .require (
332+ all (path .stat ().st_size > 0 for path in gifs ),
333+ "One or more Step 16 GIF outputs are empty." ,
334+ )
252335 report .note ("analysis outputs" )
253336
254337
@@ -267,9 +350,17 @@ def _check_report_and_website_outputs(output_dir: Path, report: ContractReport)
267350 )
268351
269352 website_dir = output_dir / "20_website_output"
270- required_pages = ("index.html" , "pipeline.html" , "reports.html" , "analysis.html" , "visualization.html" )
353+ required_pages = (
354+ "index.html" ,
355+ "pipeline.html" ,
356+ "reports.html" ,
357+ "analysis.html" ,
358+ "visualization.html" ,
359+ )
271360 for page in required_pages :
272- report .require ((website_dir / page ).exists (), f"Step 20 website page is missing: { page } " )
361+ report .require (
362+ (website_dir / page ).exists (), f"Step 20 website page is missing: { page } "
363+ )
273364 website_pages = [website_dir / page for page in required_pages ]
274365 report .require (
275366 any (_text_contains_marker (path ) for path in website_pages ),
0 commit comments