|
9 | 9 | from .git_local import GitLocalCollector, GitState |
10 | 10 | from .signals import ( |
11 | 11 | DICleanCatalog, |
| 12 | + PortalScoutSummary, |
12 | 13 | RadarSummary, |
13 | 14 | RepoSignals, |
14 | 15 | SourceObservatorySignals, |
15 | 16 | parse_di_clean_catalog, |
| 17 | + parse_portal_scout_summary, |
16 | 18 | parse_radar_summary, |
17 | 19 | parse_repo_signals, |
18 | 20 | parse_source_observatory_signals, |
@@ -51,6 +53,7 @@ def __init__( |
51 | 53 | # Cache for remote signal fetches — avoids double requests within one build |
52 | 54 | self._so_signals_cache: SourceObservatorySignals | None | type[_UNSET] = _UNSET |
53 | 55 | self._radar_cache: RadarSummary | None | type[_UNSET] = _UNSET |
| 56 | + self._portal_scout_cache: PortalScoutSummary | None | type[_UNSET] = _UNSET |
54 | 57 | self._di_signals_cache: RepoSignals | None | type[_UNSET] = _UNSET |
55 | 58 | self._di_clean_catalog_cache: DICleanCatalog | None | type[_UNSET] = _UNSET |
56 | 59 |
|
@@ -155,6 +158,10 @@ def render_session_bootstrap(self) -> str: |
155 | 158 | catalog = self._fetch_di_clean_catalog() |
156 | 159 | lines += self._render_dataset_catalog_section(catalog) |
157 | 160 |
|
| 161 | + # Portal scout (nuovi candidati strutturati) |
| 162 | + scout = self._fetch_portal_scout() |
| 163 | + lines += self._render_portal_scout_section(scout) |
| 164 | + |
158 | 165 | return "\n".join(lines) |
159 | 166 |
|
160 | 167 | def _fetch_radar_summary(self) -> RadarSummary | None: |
@@ -192,6 +199,42 @@ def _render_radar_section(self, radar: RadarSummary | None) -> list[str]: |
192 | 199 | lines.append("") |
193 | 200 | return lines |
194 | 201 |
|
| 202 | + def _fetch_portal_scout(self) -> PortalScoutSummary | None: |
| 203 | + if self._portal_scout_cache is not _UNSET: |
| 204 | + return self._portal_scout_cache # type: ignore[return-value] |
| 205 | + raw = self.github_collector.get_raw_file( |
| 206 | + "source-observatory", "data/portal_scout/discovered_portals_summary.json" |
| 207 | + ) |
| 208 | + if raw is None: |
| 209 | + self._portal_scout_cache = None |
| 210 | + return None |
| 211 | + try: |
| 212 | + result = parse_portal_scout_summary(raw) |
| 213 | + except ValueError as exc: |
| 214 | + self.github_collector.fetch_errors["source-observatory:portal_scout"] = str(exc) |
| 215 | + result = None |
| 216 | + self._portal_scout_cache = result |
| 217 | + return result |
| 218 | + |
| 219 | + def _render_portal_scout_section(self, scout: PortalScoutSummary | None) -> list[str]: |
| 220 | + lines = ["## Portal Scout", ""] |
| 221 | + if scout is None: |
| 222 | + lines.append("> *discovered_portals_summary unavailable*") |
| 223 | + lines.append("") |
| 224 | + return lines |
| 225 | + lines.append( |
| 226 | + f"Portali rilevati: {scout.total_portals} — " |
| 227 | + f"nuovi candidati: {scout.new_candidates} — " |
| 228 | + f"strutturati confermati: {scout.new_confirmed_protocol}" |
| 229 | + ) |
| 230 | + if scout.new_structured: |
| 231 | + lines.append("") |
| 232 | + lines.append("**Nuovi candidati strutturati:**") |
| 233 | + for c in scout.new_structured: |
| 234 | + lines.append(f"- `{c.domain}` — {c.protocol.upper()}") |
| 235 | + lines.append("") |
| 236 | + return lines |
| 237 | + |
195 | 238 | def _fetch_source_observatory_signals(self) -> SourceObservatorySignals | None: |
196 | 239 | if self._so_signals_cache is not _UNSET: |
197 | 240 | return self._so_signals_cache # type: ignore[return-value] |
@@ -232,7 +275,7 @@ def _render_source_health_section( |
232 | 275 | issues = so.regressions + so.alerts |
233 | 276 | if issues: |
234 | 277 | for s in issues: |
235 | | - lines.append(f"- **{s.source}** ({s.protocol}): {s.result} — {s.detail}") |
| 278 | + lines.append(f"- **{s.source}** ({s.protocol}): {s.result}") |
236 | 279 | if s.suggested_action and s.suggested_action != "nessuna": |
237 | 280 | lines.append(f" - azione: {s.suggested_action}") |
238 | 281 | else: |
@@ -316,6 +359,7 @@ def render_workspace_triage(self) -> dict[str, Any]: |
316 | 359 | "source_health": self._build_source_health_dict(), |
317 | 360 | "pipeline_state": self._build_pipeline_state_dict(), |
318 | 361 | "dataset_catalog": self._build_dataset_catalog_dict(), |
| 362 | + "portal_scout": self._build_portal_scout_dict(), |
319 | 363 | } |
320 | 364 | return triage |
321 | 365 |
|
@@ -469,17 +513,9 @@ def _render_dataset_catalog_section( |
469 | 513 | ) |
470 | 514 | for dataset in clean_ready[:8]: |
471 | 515 | period = self._format_period(dataset.period) |
472 | | - location = dataset.location.get("path", "") |
473 | | - line = f"- **{dataset.slug}** ({dataset.status}, {dataset.visibility}): " |
474 | | - line += dataset.name |
| 516 | + line = f"- **{dataset.slug}** ({dataset.visibility}): {dataset.name}" |
475 | 517 | if period: |
476 | 518 | line += f" [{period}]" |
477 | | - line += ( |
478 | | - f" - {dataset.metric_columns} metric, " |
479 | | - f"{dataset.dimension_columns} dimension columns" |
480 | | - ) |
481 | | - if location: |
482 | | - line += f" - `{location}`" |
483 | 519 | lines.append(line) |
484 | 520 | if len(clean_ready) > 8: |
485 | 521 | lines.append(f"- *...and {len(clean_ready) - 8} more clean_ready datasets*") |
@@ -532,6 +568,23 @@ def _format_period(period: dict[str, Any]) -> str: |
532 | 568 | return str(start) |
533 | 569 | return f"{start or '?'}-{end or '?'}" |
534 | 570 |
|
| 571 | + def _build_portal_scout_dict(self) -> dict[str, Any]: |
| 572 | + scout = self._fetch_portal_scout() |
| 573 | + if scout is None: |
| 574 | + return {"available": False} |
| 575 | + return { |
| 576 | + "available": True, |
| 577 | + "generated_at": scout.generated_at, |
| 578 | + "total_portals": scout.total_portals, |
| 579 | + "new_candidates": scout.new_candidates, |
| 580 | + "new_confirmed_protocol": scout.new_confirmed_protocol, |
| 581 | + "by_protocol": scout.by_protocol, |
| 582 | + "new_structured": [ |
| 583 | + {"domain": c.domain, "protocol": c.protocol} |
| 584 | + for c in scout.new_structured |
| 585 | + ], |
| 586 | + } |
| 587 | + |
535 | 588 | def _build_pipeline_state_dict(self) -> dict[str, Any]: |
536 | 589 | di = self._fetch_di_pipeline_signals() |
537 | 590 | if di is None: |
|
0 commit comments