Files
H2W-Public-Legal/scripts/build-manifest.py
Your Name c2dfc607a1
Build legal manifest / build-manifest (push) Successful in 22s
HT-14: PDF-lépés eltávolítva — csak HTML render
Az ÁSZF saját 3.2. pontja "megismerhető, megjeleníthető és letölthető"
szöveget kér — ezt egy önálló (külső CSS/JS-függőség nélküli) HTML oldal
is teljesíti (böngészőből menthető/nyomtatható), külön PDF-render nélkül.

Ez NEM ugyanaz, mint a HRF-33/34 ajánlat-szerződés PDF-je — az egy
ténylegesen elfogadott, hash-elt clickwrap-példány, más indokkal; azt nem
érinti ez a változás.

Egyszerűsödött CI-környezet: WeasyPrint + natív libek (pango/cairo) törölve,
csak pandoc + python3 kell. A manifest "pdf" mezője megszűnt.

A rendereletHTML immár kicsi (nincs PDF), ezért a teljes dist/ kimenet
visszacommitolódik "manifest/" néven (nem csak a JSON) — a fogyasztók
ugyanazzal a git clone-nal a HTML-t is megkapják, nem csak a metaadatot.
2026-07-17 14:09:34 +02:00

129 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""Builds dist/legal-manifest.json (HT-14): renders each project's markdown legal documents
to HTML and computes a content_hash, producing the single canonical manifest every consumer
backend imports at deploy time (HT-15) instead of hashing independently.
HTML only, no PDF: the ÁSZF's own 3.2. clause requires the text to be "megismerhető,
megjeleníthető és letölthető" (accessible, viewable, downloadable) before acceptance — a
self-contained HTML page (no external CSS/JS) satisfies that on its own (savable/printable
from the browser) without a separate render step. Unlike HRF-33/34's offer-contract PDF —
that one is a *formally accepted, hashed instance* via clickwrap, a genuinely different
requirement — nothing here is "accepted" as a fixed artifact, so there's no reason to add a
second render toolchain (pandoc's PDF path needs a LaTeX/wkhtmltopdf/WeasyPrint engine) just
to reproduce what the browser already does with an HTML page opened in a new tab.
content_hash source (interim, per the HT-3 "Design döntés" comment): the source markdown's
git blob hash (`git hash-object`), NOT the rendered artifact's hash yet — that requires a
proven byte-for-byte-deterministic render, which hasn't been tested for the pandoc HTML step
(ResidentFirst's HRF-34 proved a *different* pipeline deterministic, not this one). Switching
later doesn't invalidate old receipts, since a receipt is bound to document_version, not
content_hash alone.
effective_from source: parsed from each document's own "Hatálybalépés napja: <dátum>" line —
the one human-controlled field carrying the legally relevant date. A document whose date is
still the unfilled "[dátum]" placeholder gets effective_from: null (not yet effective)
rather than an invented date.
version: the short hash of the last commit that actually touched this file (NOT repo HEAD —
see HT-15's belépő ellenőrzés) — same convention as the two consumer sync scripts already
use for document_version.
Usage: python3 scripts/build-manifest.py
"""
import json
import re
import subprocess
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
OUTPUT_DIR = REPO_ROOT / "dist"
HUNGARIAN_MONTHS = {
"január": 1, "február": 2, "március": 3, "április": 4, "május": 5, "június": 6,
"július": 7, "augusztus": 8, "szeptember": 9, "október": 10, "november": 11, "december": 12,
}
# e.g. "*Hatálybalépés napja: 2026. július 17. · Verzió: 1.0*"
DATE_LINE_RE = re.compile(r"Hatálybalépés napja:\s*(.+?)\s*·\s*Verzió:")
HU_DATE_RE = re.compile(r"(\d{4})\.\s*([a-záéíóöőúüű]+)\s*(\d{1,2})\.")
def parse_effective_from(markdown: str) -> str | None:
m = DATE_LINE_RE.search(markdown)
if not m:
return None
hu = HU_DATE_RE.match(m.group(1).strip())
if not hu:
return None # still the "[dátum]" placeholder, or an unrecognized format
year, month_name, day = hu.groups()
month = HUNGARIAN_MONTHS.get(month_name.lower())
if month is None:
return None
return f"{int(year):04d}-{month:02d}-{int(day):02d}"
def _git(*args: str) -> str:
return subprocess.run(
["git", *args], cwd=REPO_ROOT, check=True, capture_output=True, text=True
).stdout.strip()
def git_blob_hash(path: Path) -> str:
return _git("hash-object", str(path))
def last_commit_hash(path: Path) -> str:
relative = path.relative_to(REPO_ROOT)
# NOTE: requires full history (fetch-depth: 0 in CI) — a shallow clone would silently
# resolve every file to the same single available commit. See HT-15.
return _git("log", "-1", "--format=%h", "--", str(relative))
def render_html(markdown_path: Path, html_path: Path) -> None:
subprocess.run(
["pandoc", str(markdown_path), "-o", str(html_path), "--standalone", "--metadata", "lang=hu"],
check=True,
)
def build_manifest_for_project(project_dir: Path) -> dict:
manifest = {}
for md_path in sorted(project_dir.glob("*.md")):
doc_type = md_path.stem
markdown = md_path.read_text(encoding="utf-8")
out_dir = OUTPUT_DIR / project_dir.name
out_dir.mkdir(parents=True, exist_ok=True)
html_path = out_dir / f"{doc_type}.html"
render_html(md_path, html_path)
manifest[doc_type] = {
"version": last_commit_hash(md_path),
"effective_from": parse_effective_from(markdown),
"content_hash": git_blob_hash(md_path),
"html": f"{project_dir.name}/{doc_type}.html",
}
return manifest
def main() -> None:
OUTPUT_DIR.mkdir(exist_ok=True)
full_manifest = {}
for project_dir in sorted(REPO_ROOT.iterdir()):
if not project_dir.is_dir() or project_dir.name.startswith(".") or project_dir.name in ("dist", "scripts"):
continue
if not list(project_dir.glob("*.md")):
continue
print(f"Renderelés: {project_dir.name}/")
full_manifest[project_dir.name] = build_manifest_for_project(project_dir)
manifest_path = OUTPUT_DIR / "legal-manifest.json"
manifest_path.write_text(json.dumps(full_manifest, indent=2, ensure_ascii=False), encoding="utf-8")
print(f"Manifest írva: {manifest_path}")
if __name__ == "__main__":
main()