第 1 卷第 1 章:1–9 年

This commit is contained in:
Chen Yi
2026-10-05 17:30:11 +08:00
parent c56394afdd
commit 3468433c9a
34 changed files with 2564 additions and 4 deletions
+108
View File
@@ -0,0 +1,108 @@
# 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]