|
8 | 8 | from .github import GitHubCollector, PR |
9 | 9 | from .git_local import GitLocalCollector, GitState |
10 | 10 | from .signals import ( |
| 11 | + DICleanCatalog, |
11 | 12 | RadarSummary, |
12 | 13 | RepoSignals, |
13 | 14 | SourceObservatorySignals, |
| 15 | + parse_di_clean_catalog, |
14 | 16 | parse_radar_summary, |
15 | 17 | parse_repo_signals, |
16 | 18 | parse_source_observatory_signals, |
@@ -50,6 +52,7 @@ def __init__( |
50 | 52 | self._so_signals_cache: SourceObservatorySignals | None | type[_UNSET] = _UNSET |
51 | 53 | self._radar_cache: RadarSummary | None | type[_UNSET] = _UNSET |
52 | 54 | self._di_signals_cache: RepoSignals | None | type[_UNSET] = _UNSET |
| 55 | + self._di_clean_catalog_cache: DICleanCatalog | None | type[_UNSET] = _UNSET |
53 | 56 |
|
54 | 57 | def render_session_bootstrap(self) -> str: |
55 | 58 | """Render session_bootstrap.md. |
@@ -148,6 +151,10 @@ def render_session_bootstrap(self) -> str: |
148 | 151 | di = self._fetch_di_pipeline_signals() |
149 | 152 | lines += self._render_pipeline_state_section(di) |
150 | 153 |
|
| 154 | + # Dataset catalog (clean/queryable datasets) |
| 155 | + catalog = self._fetch_di_clean_catalog() |
| 156 | + lines += self._render_dataset_catalog_section(catalog) |
| 157 | + |
151 | 158 | return "\n".join(lines) |
152 | 159 |
|
153 | 160 | def _fetch_radar_summary(self) -> RadarSummary | None: |
@@ -308,6 +315,7 @@ def render_workspace_triage(self) -> dict[str, Any]: |
308 | 315 | "radar": self._build_radar_dict(), |
309 | 316 | "source_health": self._build_source_health_dict(), |
310 | 317 | "pipeline_state": self._build_pipeline_state_dict(), |
| 318 | + "dataset_catalog": self._build_dataset_catalog_dict(), |
311 | 319 | } |
312 | 320 | return triage |
313 | 321 |
|
@@ -381,6 +389,23 @@ def _fetch_di_pipeline_signals(self) -> RepoSignals | None: |
381 | 389 | self._di_signals_cache = result |
382 | 390 | return result |
383 | 391 |
|
| 392 | + def _fetch_di_clean_catalog(self) -> DICleanCatalog | None: |
| 393 | + if self._di_clean_catalog_cache is not _UNSET: |
| 394 | + return self._di_clean_catalog_cache # type: ignore[return-value] |
| 395 | + raw = self.github_collector.get_raw_file( |
| 396 | + "dataset-incubator", "registry/clean_catalog.json" |
| 397 | + ) |
| 398 | + if raw is None: |
| 399 | + self._di_clean_catalog_cache = None |
| 400 | + return None |
| 401 | + try: |
| 402 | + result = parse_di_clean_catalog(raw) |
| 403 | + except ValueError as exc: |
| 404 | + self.github_collector.fetch_errors["dataset-incubator:clean_catalog"] = str(exc) |
| 405 | + result = None |
| 406 | + self._di_clean_catalog_cache = result |
| 407 | + return result |
| 408 | + |
384 | 409 | def _render_pipeline_state_section(self, di: RepoSignals | None) -> list[str]: |
385 | 410 | lines = [] |
386 | 411 | lines.append("## Pipeline State") |
@@ -417,6 +442,96 @@ def _render_pipeline_state_section(self, di: RepoSignals | None) -> list[str]: |
417 | 442 | lines.append("") |
418 | 443 | return lines |
419 | 444 |
|
| 445 | + def _render_dataset_catalog_section( |
| 446 | + self, catalog: DICleanCatalog | None |
| 447 | + ) -> list[str]: |
| 448 | + lines = [] |
| 449 | + lines.append("## Dataset Catalog") |
| 450 | + lines.append("") |
| 451 | + if catalog is None: |
| 452 | + err = self.github_collector.fetch_errors.get( |
| 453 | + "dataset-incubator:clean_catalog" |
| 454 | + ) or self.github_collector.fetch_errors.get( |
| 455 | + "dataset-incubator:registry/clean_catalog.json" |
| 456 | + ) |
| 457 | + if err: |
| 458 | + lines.append(f"> *clean_catalog unavailable — {err}*") |
| 459 | + else: |
| 460 | + lines.append("> *clean_catalog unavailable*") |
| 461 | + lines.append("") |
| 462 | + return lines |
| 463 | + |
| 464 | + clean_ready = catalog.clean_ready |
| 465 | + public_count = sum(1 for d in clean_ready if d.visibility == "public") |
| 466 | + lines.append( |
| 467 | + f"*{len(clean_ready)} clean_ready dataset(s), " |
| 468 | + f"{public_count} public* (updated {catalog.updated_at})" |
| 469 | + ) |
| 470 | + for dataset in clean_ready[:8]: |
| 471 | + 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 |
| 475 | + if period: |
| 476 | + 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 | + lines.append(line) |
| 484 | + if len(clean_ready) > 8: |
| 485 | + lines.append(f"- *...and {len(clean_ready) - 8} more clean_ready datasets*") |
| 486 | + lines.append("") |
| 487 | + return lines |
| 488 | + |
| 489 | + def _build_dataset_catalog_dict(self) -> dict[str, Any]: |
| 490 | + catalog = self._fetch_di_clean_catalog() |
| 491 | + if catalog is None: |
| 492 | + return { |
| 493 | + "available": False, |
| 494 | + "errors": { |
| 495 | + k: v for k, v in self.github_collector.fetch_errors.items() |
| 496 | + if "clean_catalog" in k |
| 497 | + }, |
| 498 | + } |
| 499 | + return { |
| 500 | + "available": True, |
| 501 | + "schema_version": catalog.schema_version, |
| 502 | + "name": catalog.name, |
| 503 | + "updated_at": catalog.updated_at, |
| 504 | + "summary": { |
| 505 | + "total": len(catalog.datasets), |
| 506 | + "clean_ready": len(catalog.clean_ready), |
| 507 | + "public": sum(1 for d in catalog.clean_ready if d.visibility == "public"), |
| 508 | + }, |
| 509 | + "datasets": [ |
| 510 | + { |
| 511 | + "slug": d.slug, |
| 512 | + "name": d.name, |
| 513 | + "status": d.status, |
| 514 | + "visibility": d.visibility, |
| 515 | + "period": d.period, |
| 516 | + "location": d.location, |
| 517 | + "metric_columns": d.metric_columns, |
| 518 | + "dimension_columns": d.dimension_columns, |
| 519 | + "column_count": d.column_count, |
| 520 | + } |
| 521 | + for d in catalog.datasets |
| 522 | + ], |
| 523 | + } |
| 524 | + |
| 525 | + @staticmethod |
| 526 | + def _format_period(period: dict[str, Any]) -> str: |
| 527 | + start = period.get("start") |
| 528 | + end = period.get("end") |
| 529 | + if start is None and end is None: |
| 530 | + return "" |
| 531 | + if start == end: |
| 532 | + return str(start) |
| 533 | + return f"{start or '?'}-{end or '?'}" |
| 534 | + |
420 | 535 | def _build_pipeline_state_dict(self) -> dict[str, Any]: |
421 | 536 | di = self._fetch_di_pipeline_signals() |
422 | 537 | if di is None: |
|
0 commit comments