# pyright: reportMissingImports=false, reportAttributeAccessIssue=false # 说明:LSP 的 Python 环境看不到本项目包(未安装进它的 site-packages), # 会把「包内互相 import」误报为缺失模块。运行时导入已由执行验证通过。 """生成迷你年表与人物索引(随成品入库的两种辅助文件)。""" from __future__ import annotations from collections import Counter from pathlib import Path from dfannals import legends as lg from dfannals import score from dfannals.legends import Event, World # 评分标准统一在 dfannals/score.py,年表与切章共用同一套。 significance = score.significance category = score.category def event_line(world: World, e: Event) -> str: """把一条事件压成一行可读文字。""" chunks: list[str] = [e.type] for tag, kind in lg.ID_FIELDS.items(): for i in e.ids(tag): if kind == "figure": name = world.figure_name(i) elif kind == "site": name = world.site_name(i) elif kind == "entity": name = world.entity_name(i) else: name = "" if name: chunks.append(f"{tag}={name}") return f"- **{e.year}** [{category(e)}] " + " · ".join(chunks) def build_timeline(world: World, min_score: int = 70, per_decade: int = 14) -> str: """按十年分组的重要事件年表。""" prom = score.prominence(world) lo, hi = world.event_years() important = [e for e in world.events if significance(e, prom) >= min_score] buckets: dict[int, list[Event]] = {} for e in important: buckets.setdefault((e.year // 10) * 10, []).append(e) lines = [ f"# {world.name} · 年表", "", f"- 历史跨度:{lo}–{hi} 年" if hi else "- 历史跨度:未知", f"- 事件总数:{len(world.events)},其中重要事件 {len(important)}", f"- 历史人物 {len(world.figures)} 位 · 文明与组织 {len(world.entities)} 个 · 地点 {len(world.sites)} 处", "", "> 本表由 legends 导出数据自动生成,只收录叙事权重较高的事件;" "完整数据留在本地,不入库。", "", ] for decade in sorted(buckets): evs = sorted(buckets[decade], key=lambda e: (e.year, e.seconds72, e.id)) shown = sorted(evs, key=lambda e: -significance(e, prom))[:per_decade] shown.sort(key=lambda e: (e.year, e.seconds72, e.id)) lines.append(f"## {decade}s") lines.append("") lines.extend(event_line(world, e) for e in shown) if len(evs) > len(shown): lines.append(f"- …另有 {len(evs) - len(shown)} 条次要事件未列") lines.append("") return "\n".join(lines) def build_figures(world: World, top: int = 120) -> str: """人物索引:按参与事件数排序,给出身份与生卒。""" involvement: Counter[int] = Counter() for e in world.events: seen: set[int] = set() for tag, kind in lg.ID_FIELDS.items(): if kind != "figure": continue for i in e.ids(tag): seen.add(i) involvement.update(seen) lines = [ f"# {world.name} · 人物索引", "", f"共 {len(world.figures)} 位历史人物,按在史料中出现的次数排序(前 {top} 位)。", "", "| 人物 | 种族 | 生卒 | 职业 | 事件数 |", "|---|---|---|---|---|", ] ranked = sorted(world.figures.values(), key=lambda f: -involvement.get(f.id, 0)) for fig in ranked[:top]: name = lg.pretty(fig.name) or f"HF#{fig.id}" prof = fig.profession or "—" lines.append( f"| {name} | {fig.race or '—'} | {fig.alive_span} | {prof} | {involvement.get(fig.id, 0)} |" ) lines.append("") return "\n".join(lines) def write_outputs(world: World, out_dir: Path, min_score: int = 40) -> list[Path]: out_dir.mkdir(parents=True, exist_ok=True) timeline = out_dir / "timeline.md" figures = out_dir / "figures.md" timeline.write_text(build_timeline(world, min_score=min_score), encoding="utf-8") figures.write_text(build_figures(world), encoding="utf-8") return [timeline, figures]