主角线:Guspu Frillyknots / Ral Fastenhatchets / Alath Blottedmine 由线索挖掘器自动选定,按因果链切集,经 5 维度自评闸门(≥12 分)后入库。
142 lines
4.6 KiB
Python
142 lines
4.6 KiB
Python
"""事件显著度评分与小人物过滤。
|
||
|
||
真实数据给的教训:40 万条事件里绝大多数是流水账
|
||
(change hf state 7.3 万、change hf job 5.8 万、written content composed 3.9 万),
|
||
直接按类型切章会得到 5910 章,毫无叙事价值。
|
||
|
||
因此策略是:
|
||
1. 只保留天然有叙事张力的事件类型(攻城、灭国、神器、死亡、结盟……)
|
||
2. 再按"参与者有多重要"做二次排序,让编年史跟着大人物走
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from collections import Counter
|
||
|
||
from dfannals.legends import Event, World
|
||
|
||
# 事件类型 → 基础分。0 分表示不单独叙事(流水账)
|
||
NARRATIVE: dict[str, int] = {
|
||
# ---- 天崩地裂级
|
||
"destroyed site": 100,
|
||
"razed structure": 100,
|
||
"entity overthrown": 100,
|
||
"hf destroyed site": 95,
|
||
"plundered site": 95,
|
||
"field battle": 95,
|
||
"entity dissolved": 90,
|
||
"holy city declaration": 90,
|
||
"site taken over": 90,
|
||
"entity breach feature layer": 90,
|
||
"entity alliance formed": 85,
|
||
"peace accepted": 85,
|
||
"hf enslaved": 85,
|
||
"hf revived": 80,
|
||
"entity relocate": 80,
|
||
"hf ransomed": 80,
|
||
"peace rejected": 78,
|
||
# ---- 重大
|
||
"created site": 75,
|
||
"reclaim site": 72,
|
||
"attacked site": 70,
|
||
"hf attacked site": 70,
|
||
"entity created": 70,
|
||
"artifact destroyed": 70,
|
||
"new site leader": 65,
|
||
"created world construction": 60,
|
||
"entity persecuted": 60,
|
||
"hf convicted": 60,
|
||
"site dispute": 55,
|
||
"failed intrigue corruption": 55,
|
||
"artifact recovered": 60,
|
||
"artifact possessed": 60,
|
||
# ---- 值得一记
|
||
"artifact created": 45,
|
||
"artifact lost": 45,
|
||
"creature devoured": 42,
|
||
"hf abducted": 40,
|
||
"assume identity": 40,
|
||
"knowledge discovered": 38,
|
||
"artifact given": 36,
|
||
"agreement formed": 36,
|
||
"hf gains secret goal": 36,
|
||
"item stolen": 34,
|
||
"hf performed horrible experiments": 45,
|
||
"failed frame attempt": 40,
|
||
"hf profaned structure": 38,
|
||
"changed creature type": 36,
|
||
"hf interrogated": 34,
|
||
"created structure": 30,
|
||
"hf wounded": 30,
|
||
"hf learns secret": 34,
|
||
"changed hf body state": 30,
|
||
}
|
||
|
||
# 死亡单独处理:分数取决于死者本身有多重要
|
||
DEATH_TYPES = ("hf died",)
|
||
|
||
CATEGORIES: list[tuple[tuple[str, ...], str]] = [
|
||
(("war", "battle", "siege", "raid", "attack", "plunder", "destroy", "sack", "taken over"), "战事"),
|
||
(("died", "death", "slain", "murder", "assassin", "enslaved", "ransomed", "revived"), "生死"),
|
||
(("created site", "reclaim", "relocate", "dissolved", "overthrown", "alliance", "entity created"), "兴亡"),
|
||
(("artifact", "masterpiece", "construction", "structure", "razed"), "器物"),
|
||
(("peace", "agreement", "tribute", "persecuted", "convicted", "dispute", "intrigue"), "政争"),
|
||
(("knowledge", "secret", "abducted", "identity", "experiment", "profaned", "devoured"), "异闻"),
|
||
]
|
||
|
||
|
||
def category(event: Event) -> str:
|
||
t = event.type.lower()
|
||
for keys, label in CATEGORIES:
|
||
if any(k in t for k in keys):
|
||
return label
|
||
return "杂记"
|
||
|
||
|
||
def prominence(world: World) -> Counter[int]:
|
||
"""每个人物被卷入的事件次数——用来衡量他在史料里有多重要。
|
||
|
||
改用字段分类器后覆盖了 40+ 个人物字段(旧版只数 6 个),数值尺度整体变大,
|
||
因此 PROMINENT 阈值必须按新尺度重校。结果缓存在 World 上,避免反复重算。
|
||
"""
|
||
cached = getattr(world, "_prominence", None)
|
||
if cached is not None:
|
||
return cached
|
||
counts: Counter[int] = Counter()
|
||
for e in world.events:
|
||
counts.update(e.figure_ids())
|
||
world._prominence = counts # type: ignore[attr-defined]
|
||
return counts
|
||
|
||
|
||
def _participants(event: Event) -> list[int]:
|
||
return event.figure_ids()
|
||
|
||
|
||
# 人物重要度阈值:参与事件数达到这个量级才算"要角"
|
||
PROMINENT = 60
|
||
|
||
|
||
def significance(event: Event, prom: Counter[int] | None = None, prominent: int = PROMINENT) -> int:
|
||
"""事件的故事价值。0 表示不值得单独写。"""
|
||
t = event.type.lower()
|
||
base = NARRATIVE.get(t, 0)
|
||
people = _participants(event)
|
||
top = max((prom.get(p, 0) for p in people), default=0) if prom else 0
|
||
|
||
if t in DEATH_TYPES:
|
||
# 无名死者不写;要角之死按重要度加分
|
||
if top < prominent:
|
||
return 0
|
||
return 55 + min(45, top // 10)
|
||
|
||
if base == 0:
|
||
return 0
|
||
|
||
# 参与者越重要,越值得写;但不要盖过类型本身的分量
|
||
bonus = min(25, top // 25) if prom else 0
|
||
return base + bonus
|
||
|
||
|
||
def is_narrative(event: Event, prom: Counter[int] | None = None) -> bool:
|
||
return significance(event, prom) > 0
|