88from .github import GitHubCollector , PR
99from .git_local import GitLocalCollector , GitState
1010from .signals import (
11+ RadarSummary ,
1112 RepoSignals ,
1213 SourceObservatorySignals ,
14+ parse_radar_summary ,
1315 parse_repo_signals ,
1416 parse_source_observatory_signals ,
1517)
@@ -46,6 +48,7 @@ def __init__(
4648 self .fixed_timestamp = fixed_timestamp or datetime .now ().isoformat ()
4749 # Cache for remote signal fetches — avoids double requests within one build
4850 self ._so_signals_cache : SourceObservatorySignals | None | type [_UNSET ] = _UNSET
51+ self ._radar_cache : RadarSummary | None | type [_UNSET ] = _UNSET
4952 self ._di_signals_cache : RepoSignals | None | type [_UNSET ] = _UNSET
5053
5154 def render_session_bootstrap (self ) -> str :
@@ -133,6 +136,10 @@ def render_session_bootstrap(self) -> str:
133136 lines .append (f"- { topic_name } " )
134137 lines .append ("" )
135138
139+ # Radar health (GREEN/YELLOW/RED per fonte)
140+ radar = self ._fetch_radar_summary ()
141+ lines += self ._render_radar_section (radar )
142+
136143 # Source health (only issues — skip stable sources)
137144 so = self ._fetch_source_observatory_signals ()
138145 lines += self ._render_source_health_section (so )
@@ -143,6 +150,41 @@ def render_session_bootstrap(self) -> str:
143150
144151 return "\n " .join (lines )
145152
153+ def _fetch_radar_summary (self ) -> RadarSummary | None :
154+ if self ._radar_cache is not _UNSET :
155+ return self ._radar_cache # type: ignore[return-value]
156+ raw = self .github_collector .get_raw_file (
157+ "source-observatory" , "data/radar/radar_summary.json"
158+ )
159+ if raw is None :
160+ self ._radar_cache = None
161+ return None
162+ try :
163+ result = parse_radar_summary (raw )
164+ except ValueError as exc :
165+ self .github_collector .fetch_errors ["source-observatory:radar_summary" ] = str (exc )
166+ result = None
167+ self ._radar_cache = result
168+ return result
169+
170+ def _render_radar_section (self , radar : RadarSummary | None ) -> list [str ]:
171+ lines = ["## Radar Status" , "" ]
172+ if radar is None :
173+ lines .append ("> *radar_summary unavailable*" )
174+ lines .append ("" )
175+ return lines
176+ lines .append (
177+ f"Fonti: { radar .sources_total } — "
178+ f"GREEN { radar .green } · YELLOW { radar .yellow } · RED { radar .red } "
179+ f"(probe: { radar .probe_date } )"
180+ )
181+ if radar .unhealthy :
182+ lines .append ("" )
183+ for s in radar .unhealthy :
184+ lines .append (f"- **{ s .id } ** ({ s .protocol } ): { s .status } [HTTP { s .http_code } ]" )
185+ lines .append ("" )
186+ return lines
187+
146188 def _fetch_source_observatory_signals (self ) -> SourceObservatorySignals | None :
147189 if self ._so_signals_cache is not _UNSET :
148190 return self ._so_signals_cache # type: ignore[return-value]
@@ -263,11 +305,29 @@ def render_workspace_triage(self) -> dict[str, Any]:
263305 for repo , state in repos_state .items ()
264306 },
265307 "warnings" : self ._collect_warnings (prs , repos_state ),
308+ "radar" : self ._build_radar_dict (),
266309 "source_health" : self ._build_source_health_dict (),
267310 "pipeline_state" : self ._build_pipeline_state_dict (),
268311 }
269312 return triage
270313
314+ def _build_radar_dict (self ) -> dict [str , Any ]:
315+ radar = self ._fetch_radar_summary ()
316+ if radar is None :
317+ return {"available" : False }
318+ return {
319+ "available" : True ,
320+ "probe_date" : radar .probe_date ,
321+ "sources_total" : radar .sources_total ,
322+ "green" : radar .green ,
323+ "yellow" : radar .yellow ,
324+ "red" : radar .red ,
325+ "unhealthy" : [
326+ {"id" : s .id , "status" : s .status , "protocol" : s .protocol , "http_code" : s .http_code }
327+ for s in radar .unhealthy
328+ ],
329+ }
330+
271331 def _build_source_health_dict (self ) -> dict [str , Any ]:
272332 so = self ._fetch_source_observatory_signals ()
273333 if so is None :
0 commit comments