narty-2027/gen_viz.py
oskar 7ef5c34e52 Graph viewer: viz.html z knowledge-catalog + skrypt generujący
Viewer jest samowystarczalny — bundle jest wpiekany do HTML jako
window.BUNDLE (nodes/edges/bodies), nie ma odczytu .md w runtime, więc
nie potrzeba manifestu ani konkretnej ścieżki serwowania. Źródłem danych
jest gen_viz.py, uruchamiany po zmianach w bundle'u.

Logika viewera (viz.js, viz.css, szkielet templates/viz.html) wstawiona
dosłownie z upstreamu — zweryfikowane bajt w bajt. gen_viz.py koryguje
wyłącznie zbieranie danych, w dwóch miejscach niezgodnych ze spec:

- _extract_links pomijał linki bundle-relative "/..." (generator.py:74),
  czyli formę zalecaną w §5.1 — bundle zgodny ze spec dawał graf z 0
  krawędzi. Sam viewer te linki rozumie (viz.js:297).
- _walk_concepts pomijał tylko index.md, więc zarezerwowany log.md
  trafiał do grafu jako koncept typu "Unknown" (wbrew §3.1).

Wynik: 9 konceptów, 9 krawędzi (było 9 i 0).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:48:32 +02:00

101 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Generuje viz.html (graph viewer) dla tego bundle'a.
Viewer z GoogleCloudPlatform/knowledge-catalog jest samowystarczalny: cały
bundle jest wpiekany do pliku HTML jako `window.BUNDLE` (nodes/edges/bodies).
Nie czyta plików .md w runtime, więc nie potrzebuje manifestu ani serwera —
źródłem danych jest ten skrypt, uruchamiany po każdej zmianie bundle'a.
Logika viewera (templates/viz.html, static/viz.js, static/viz.css) jest
wstawiana dosłownie, bez zmian. Skrypt koryguje wyłącznie warstwę zbierania
danych, w dwóch punktach, w których upstreamowy generator rozmija się ze
specyfikacją OKF:
1. `_extract_links` pomija linki bundle-relative zaczynające się od `/`
(generator.py:74), czyli dokładnie formę zalecaną przez §5.1. Bundle
trzymający się zalecenia dostaje graf bez krawędzi. Sam viewer te linki
rozumie — viz.js:297 rozwiązuje `/...md` na węzeł — więc dokładamy je do
ekstrakcji krawędzi.
2. `_walk_concepts` pomija tylko `index.md`, przez co zarezerwowany `log.md`
ląduje w grafie jako koncept typu "Unknown". Wg §3.1 zarezerwowane nazwy
nie są konceptami.
Wymaga: PyYAML oraz repozytorium knowledge-catalog (domyślnie
/tmp/knowledge-catalog — nadpisz zmienną KNOWLEDGE_CATALOG).
Uruchomienie: python3 gen_viz.py
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
BUNDLE_ROOT = Path(__file__).resolve().parent
BUNDLE_NAME = "Ski 2027 — Skicircus Saalbach"
CATALOG = Path(os.environ.get("KNOWLEDGE_CATALOG", "/tmp/knowledge-catalog"))
sys.path.insert(0, str(CATALOG / "okf" / "src"))
try:
from reference_agent.viewer import generator as G
except ModuleNotFoundError as exc: # pragma: no cover
sys.exit(
f"Nie znaleziono viewera ({exc}).\n"
f"Sklonuj: git clone https://github.com/GoogleCloudPlatform/"
f"knowledge-catalog {CATALOG}"
)
RESERVED = {"index.md", "log.md"}
_upstream_extract_links = G._extract_links
_upstream_walk_concepts = G._walk_concepts
def _extract_links(body: str, doc_dir: Path, bundle_root: Path) -> list[str]:
"""Upstream + linki bundle-relative (§5.1), które upstream odrzuca."""
out = _upstream_extract_links(body, doc_dir, bundle_root)
seen = set(out)
for match in G._LINK_RE.finditer(body):
target = match.group(1)
if "://" in target or not target.startswith("/"):
continue
rel = target[1:]
if rel.endswith(".md"):
rel = rel[:-3]
if rel and rel not in seen:
seen.add(rel)
out.append(rel)
return out
def _walk_concepts(bundle_root: Path):
"""Upstream, ale bez plików zarezerwowanych (§3.1)."""
return [
c
for c in _upstream_walk_concepts(bundle_root)
if f"{c.id}.md".rsplit("/", 1)[-1] not in RESERVED
]
G._extract_links = _extract_links
G._walk_concepts = _walk_concepts
def main() -> int:
out_path = BUNDLE_ROOT / "viz.html"
stats = G.generate_visualization(
BUNDLE_ROOT, out_path, bundle_name=BUNDLE_NAME
)
print(f"Zapisano {out_path}")
print(
f" koncepty: {stats['concepts']}, krawędzie: {stats['edges']}, "
f"rozmiar: {stats['bytes'] / 1024:.1f} KiB"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())