22
33import json
44import math
5- import os
65from pathlib import Path
7- from typing import Any , Iterable , List , Optional , Tuple
6+ from typing import Any , Iterable , List , Tuple
87
9- # Headless backend (no GUI)
108import matplotlib
119matplotlib .use ("Agg" , force = True )
1210
1513from matplotlib .ticker import NullLocator
1614
1715RESULTS_DIR = Path ("results" )
16+ MAX_PNG_PER_SCENARIO = 6 # profesional: no inundar el repo
1817
1918def _is_num (x : Any ) -> bool :
20- if isinstance (x , (int , float )) and not isinstance (x , bool ):
21- return math .isfinite (float (x ))
22- return False
19+ return isinstance (x , (int , float )) and not isinstance (x , bool ) and math .isfinite (float (x ))
2320
2421def _looks_like_series (v : Any ) -> bool :
25- if not isinstance (v , list ):
22+ if not isinstance (v , list ) or len ( v ) < 3 :
2623 return False
27- if len (v ) < 3 :
28- return False
29- # must be mostly numeric
3024 nums = sum (1 for x in v if _is_num (x ))
3125 return nums >= int (0.9 * len (v ))
3226
@@ -42,47 +36,28 @@ def _walk(obj: Any, prefix: str = "") -> Iterable[Tuple[str, Any]]:
4236 yield (p , v )
4337 yield from _walk (v , p )
4438
45- def _pick_series (candidates : List [Tuple [str , List [float ]]], prefer_terms : List [str ]) -> Optional [Tuple [str , List [float ]]]:
46- # deterministic: stable sort by (priority, -len, key)
47- scored = []
48- for k , s in candidates :
49- k_low = k .lower ()
50- pr = min ([prefer_terms .index (t ) for t in prefer_terms if t in k_low ], default = 10_000 )
51- scored .append ((pr , - len (s ), k , s ))
52- scored .sort (key = lambda x : (x [0 ], x [1 ], x [2 ]))
53- if not scored :
54- return None
55- _ , _ , k , s = scored [0 ]
56- return (k , s )
57-
58- def _safe_plot_series (out_png : Path , title : str , y : List [float ]) -> None :
39+ def _safe_plot (out_png : Path , title : str , y : List [float ]) -> None :
5940 out_png .parent .mkdir (parents = True , exist_ok = True )
6041
6142 fig = Figure (figsize = (10 , 4 ), dpi = 140 )
6243 canvas = FigureCanvas (fig )
63-
6444 ax = fig .add_subplot (111 )
6545
66- # CRÍTICO: eliminar ticks/locators para esquivar el bug de deepcopy en Py3.14 + mpl (ticks crean MarkerStyle)
46+ # Evita ticks/markers que en algunos entornos Py3.14+ mpl disparan deepcopy recursivo
6747 ax .xaxis .set_major_locator (NullLocator ())
6848 ax .yaxis .set_major_locator (NullLocator ())
6949 ax .tick_params (bottom = False , left = False , labelbottom = False , labelleft = False )
7050
71- # Plot
7251 x = list (range (len (y )))
7352 ax .plot (x , y )
7453
75- # Etiquetas "manuales" (sin ticks)
7654 fig .text (0.01 , 0.98 , title , ha = "left" , va = "top" )
77- fig .text (0.01 , 0.02 , f"n={ len (y )} min={ min (y ):.3g } max={ max (y ):.3g } " , ha = "left" , va = "bottom" )
55+ fig .text (0.01 , 0.02 , f"n={ len (y )} min={ min (y ):.6g } max={ max (y ):.6g } " , ha = "left" , va = "bottom" )
7856
79- fig .tight_layout (rect = [0 , 0.05 , 1 , 0.92 ])
57+ fig .tight_layout (rect = [0 , 0.06 , 1 , 0.92 ])
8058 canvas .draw ()
8159 fig .savefig (out_png )
8260
83- def _load_json (path : Path ) -> Any :
84- return json .loads (path .read_text (encoding = "utf-8" ))
85-
8661def generate () -> int :
8762 if not RESULTS_DIR .exists ():
8863 raise SystemExit ("Missing results/ directory. Run: python -m scripts.run_scenarios" )
@@ -94,50 +69,34 @@ def generate() -> int:
9469 wrote = 0
9570
9671 for jf in json_files :
97- obj = _load_json (jf )
72+ obj = json . loads (jf . read_text ( encoding = "utf-8" ) )
9873
99- series_candidates : List [Tuple [str , List [float ]]] = []
74+ candidates : List [Tuple [str , List [float ]]] = []
10075 for k , v in _walk (obj ):
10176 if _looks_like_series (v ):
102- series_candidates .append ((k , [float (x ) for x in v ]))
103-
104- # Si NO hay series numéricas, reporta claves reales para debugging (determinista)
105- if not series_candidates :
106- keys = []
107- if isinstance (obj , dict ):
108- keys = sorted (list (obj .keys ()))
109- print (f"[generate_figures] { jf .name } : NO numeric series found. top-level keys={ keys } " )
110- continue
111-
112- # Selección determinista por prioridad textual
113- o2_pick = _pick_series (series_candidates , prefer_terms = ["o2" , "oxygen" ])
114- w_pick = _pick_series (series_candidates , prefer_terms = ["water" , "h2o" ])
77+ candidates .append ((k , [float (x ) for x in v ]))
11578
116- # Si no hay match semántico, NO adivinamos: reportamos candidatos y seguimos
117- if o2_pick is None or w_pick is None :
118- print (f"[generate_figures] { jf .name } : cannot select o2/water deterministically." )
119- print (" candidates:" )
120- for k , s in sorted (series_candidates , key = lambda x : x [0 ])[:50 ]:
121- print (f" - { k } (len={ len (s )} )" )
79+ # Reporte determinista
80+ if not candidates :
81+ top_keys = sorted (list (obj .keys ())) if isinstance (obj , dict ) else []
82+ print (f"[generate_figures] { jf .name } : NO numeric series found. top-level keys={ top_keys } " )
12283 continue
12384
124- base = jf .stem
125- o2_key , o2_series = o2_pick
126- w_key , w_series = w_pick
127-
128- out_o2 = RESULTS_DIR / f"{ base } _o2.png"
129- out_w = RESULTS_DIR / f"{ base } _water.png"
85+ # Ordena por longitud desc, y luego por key asc (determinista)
86+ candidates .sort (key = lambda kv : (- len (kv [1 ]), kv [0 ]))
13087
131- _safe_plot_series (out_o2 , f"{ base } :: O2 (source={ o2_key } )" , o2_series )
132- _safe_plot_series (out_w , f"{ base } :: Water (source={ w_key } )" , w_series )
133-
134- print (f"[generate_figures] wrote: { out_o2 .as_posix ()} { out_w .as_posix ()} " )
135- wrote += 2
88+ base = jf .stem
89+ for i , (k , s ) in enumerate (candidates [:MAX_PNG_PER_SCENARIO ], start = 1 ):
90+ out = RESULTS_DIR / f"{ base } _series_{ i :02d} .png"
91+ title = f"{ base } :: series_{ i :02d} (source={ k } )"
92+ _safe_plot (out , title , s )
93+ print (f"[generate_figures] wrote: { out .as_posix ()} (len={ len (s )} )" )
94+ wrote += 1
13695
13796 return wrote
13897
13998if __name__ == "__main__" :
14099 n = generate ()
141100 if n == 0 :
142- raise SystemExit ("No PNGs created. Either no numeric series exist in results/S*.json or selection could not be deterministic ." )
101+ raise SystemExit ("No PNGs created. Your results JSON contain no numeric series arrays ." )
143102 print (f"OK: created { n } PNGs" )
0 commit comments