#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 把主資料庫網頁與研究儀表板接起來 ================================ 在主資料庫網頁的每一則案件加上「案件歷程」區塊與跳至標籤, 並在頁首加上前往研究儀表板的連結。 python _pipeline\\接上儀表板.py 只重寫兩個 HTML,不連網、不重抓、不動 資料庫.json、不動 Markdown 筆記。 資料來源:_pipeline/資料庫.json 與 40_案件歷程/案件歷程.csv。 """ import csv, json, sys, collections, importlib.util from pathlib import Path PIPE = Path(__file__).resolve().parent ROOT = PIPE.parent D = ROOT / "40_案件歷程" csv.field_size_limit(10 ** 8) spec = importlib.util.spec_from_file_location("builder", PIPE / "建置資料庫.py") B = importlib.util.module_from_spec(spec) spec.loader.exec_module(B) def history_index(): """釋憲案 → {n, nc, m, c};c 為各鏈的 [審級, 案號] 摘要。""" p = D / "案件歷程.csv" if not p.exists(): return {} rows = list(csv.DictReader(open(p, encoding="utf-8-sig"))) by = collections.defaultdict(list) for r in rows: by[r["釋憲案"]].append(r) ORD = {"確定終局裁判": 0, "前審": 1, "再前審": 2, "第四層": 3, "第五層": 4, "第六層": 5, "未命中": 9} out = {} for k, rs in by.items(): byno = {} for x in rs: byno.setdefault(x["案號"], x) has_par = any("上層案號" in x for x in rs) kids = collections.defaultdict(list) if has_par: for x in rs: p = (x.get("上層案號") or "").strip() if p: kids[p].append(x) else: for x in rs: p = (x.get("前審案號") or "").split(";")[0].strip() if p and p in byno: kids[x["案號"]].append(byno[p]) used, chains, miss = set(), [], 0 def descend(x, depth, acc): if x["案號"] in used: return used.add(x["案號"]) acc.append([x["審級"], x["案號"], depth]) for c2 in kids.get(x["案號"], []): descend(c2, depth + 1, acc) for x in sorted(rs, key=lambda r: ORD.get(r["審級"], 5)): if x["審級"] != "確定終局裁判" or x["案號"] in used: continue if has_par and (x.get("上層案號") or "").strip(): continue acc = [] descend(x, 0, acc) if acc: chains.append(acc) for x in sorted(rs, key=lambda r: ORD.get(r["審級"], 5)): if x["案號"] in used: continue if x["審級"] == "未命中": used.add(x["案號"]) miss += 1 else: acc = [] descend(x, 0, acc) if acc: chains.append(acc) if not chains: continue out[k] = {"n": sum(len(c) for c in chains), "nc": len(chains), "m": miss, "c": chains} return out def main(): try: sys.stdout.reconfigure(encoding="utf-8") except Exception: pass dbp = PIPE / "資料庫.json" if not dbp.exists(): print("找不到 _pipeline/資料庫.json") sys.exit(1) recs = json.load(open(dbp, encoding="utf-8")) print(f"載入 {len(recs)} 筆") # 資料庫.json 不一定是「已補完」的版本,這裡在記憶體內重做一次合併, # 避免重寫網頁時把白話解說與 OCR 補正洗掉。不回寫 資料庫.json。 def load(name): p = PIPE / name return json.load(open(p, encoding="utf-8")) if p.exists() else {} ai, ocr, opai = load("白話解說.json"), load("OCR補正.json"), load("意見書白話.json") n_ai = n_ocr = n_op = 0 for r in recs: if not r.get("ai") and r["k"] in ai: r["ai"] = ai[r["k"]] if r.get("ai"): n_ai += 1 for i, o in enumerate(r["o"], 1): key = f'{r["k"]}#{i}' if not o.get("x") and key in ocr: o["x"] = ocr[key] + "\n\n(本篇原始 PDF 無文字層,以繁體中文 OCR 擷取,可能有辨識誤差;請以官網 PDF 為準)" o.pop("low", None) if not o.get("ai") and key in opai: o["ai"] = opai[key] if o.get("x"): n_ocr += 1 if o.get("ai"): n_op += 1 print(f"白話解說 {n_ai} 則;有文字之意見書 {n_ocr} 篇;意見書白話 {n_op} 篇") if ai and n_ai < len(ai): print(f"中止:白話解說只掛上 {n_ai} 則,少於 白話解說.json 的 {len(ai)} 則。") sys.exit(1) if opai and n_op < len(opai): print(f"中止:意見書白話只掛上 {n_op} 篇,少於 意見書白話.json 的 {len(opai)} 篇。") sys.exit(1) hx = history_index() print(f"有案件歷程者 {len(hx)} 則") if not hx: print("警告:40_案件歷程/案件歷程.csv 不存在或為空,網頁只會加上頁首連結。") n = 0 for r in recs: if r["k"] in hx: r["hx"] = hx[r["k"]] n += 1 else: r.pop("hx", None) print(f"掛上歷程 {n} 則") # 安全閥:新檔明顯比舊檔小,代表內容掉了,先備份再說。 def guard(name, nbytes): p = ROOT / name if p.exists() and nbytes < p.stat().st_size * 0.95: print(f"中止:{name} 新檔 {nbytes:,} bytes 比現有 {p.stat().st_size:,} bytes 小超過 5%," f"疑似內容遺失,已保留原檔不覆寫。") sys.exit(1) print("重寫網頁(完整版)…") doc1 = B.build_html(recs) guard("釋字及憲法法庭資料庫.html", len(doc1.encode("utf-8"))) out1 = ROOT / "釋字及憲法法庭資料庫.html" out1.write_text(doc1, encoding="utf-8") s1 = len(doc1) print("重寫網頁(輕量版)…") doc2 = B.build_html(recs, lite=True) guard("釋字及憲法法庭資料庫_輕量版.html", len(doc2.encode("utf-8"))) out2 = ROOT / "釋字及憲法法庭資料庫_輕量版.html" out2.write_text(doc2, encoding="utf-8") s2 = len(doc2) print("=" * 60) print("完成,資料庫.json 未更動。") print(f" 完整版:{out1} {s1/1048576:.1f} MB") print(f" 輕量版:{out2} {s2/1048576:.1f} MB") print(" 頁首右上角有「研究儀表板 →」,每則案件多了「案件歷程」區塊。") print("=" * 60) if __name__ == "__main__": main()