Files
digital-me-history/digital-me-backend/src/gongqing.js
T
admin 0593ff7e62
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
数字我 Digital Me:全量入库(大文件走 Git LFS)
2026-10-10 16:24:43 +08:00

1676 lines
106 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 汉·公卿年表 —— 「我在某朝为官,身边站着谁」。
//
// 定位:与 dynasties.js / decisions.js / talents.js 同级的【纯常量表 + 纯函数】。无 IO、无 DB、无 LLM。
//
// 四刀的位置:
// 第一刀 dynasties.js —— 我在每朝每代的【位置】(性格 × 制度 → 位阶)
// 第二刀 dynasties.js —— 那个位置的【天花板】(通道即杠杆)
// 第三刀 decisions.js —— 那个位置上的【选择】,与它能传多远
// 第四刀 本文件 —— 你站在那个位置上的【那一年】,身边站着谁
//
// ⚠ courtAt / fateOf 必须是确定性纯函数,绝不调 LLM。理由与 dynasties.js / decisions.js 同:
// 「景帝三年的御史大夫是晁错」是史料里的硬事实,不是可以现编的东西。让 LLM 编一份同僚名单,
// 用户查一次《汉书》就发现是假的 —— 而这个功能的用户,正是会去查《汉书》的那批人。
//
// ══════════════════════════════════════════════════════════════════════════
// 【这张表凭什么值钱】
//
// 《汉书·百官公卿表》分上下两卷,是两样完全不同的东西:
// · 上卷 = 官制【说明书】:某官秦置,掌某事,秩若干石,属官若干。—— 那是 dynasties.js 的料。
// · 下卷 = 公卿【年表】:按帝王纪年逐年记,哪一年谁拜了丞相、谁免了、谁薨了、谁腰斩了。
//
// ★ 下卷比上卷值钱,因为【皇帝是天然的时间轴】。给定「景帝三年,君迁中大夫」,
// 下卷能告诉你那一年:丞相是陶青、太尉是周亚夫(刚拜)、御史大夫是晁错。
// ——而景帝三年正月壬子,晁错要斩。那年七国反。而你在场。
//
// ★★【同僚图谱不是装饰,它是决策点的生成器】:
// 你在哪一年、在哪个位置、身边站着谁 —— 这三样一齐,关口自己就有了。
// decisions.js 的关口是【手写】的(南齐四户两道题);本文件是想让关口【长出来】的那一半。
// ⚠ 但本文件【不生成关口】,它只提供事实。生成是别人的事,别在这儿加。
//
// ══════════════════════════════════════════════════════════════════════════
// 史学口径 —— 本文件的史料经 2026-07-16 一轮考实:
// · curl -sk 取生 HTML 逐字比对,【未用 WebFetch】。
// · 两个独立见证:汉川草庐 www.sidneyluo.net/a/a02/019b.htm(繁体)与 ctext.org
// han-shu/bai-guan-gong-qing-biao-xia。两边各自解析成 218 行的表格,行数、纪年逐行相符。
// · 景帝元年—武帝後元二年(rows 56–125)区间内【逐格】比对:291 格,异文 24 处。分账:
// - 14 处「衞/衛」同字异形(无关文义)
// - 2 处 ctext 的 OCR 坏字(「平【觉】侯」「風發【鲷】」,见 WITNESS_NOTE.ctextFlaws)
// - 3 处「歐/敺」同音异写(顏師古卷内注「歐讀與驅同」、卷四十九注「音區」,可证同音)
// - 5 处【真要紧的】。其中 3 处落在本表已收的条目里,已逐条记进 caveat,
// 【一处都没有替读者选】:晁错之名(朝/晁)、直不疑之终(死/免)、霍光之月(三/二)。
// 另 2 处落在本表【未收】的格 —— 元狩六年右内史「正鼂/王晁」(人名两字全异,
// 且不在覆盖范围)、景帝中二年中尉「一格只有官名无人」(整格是阙文,按 SHIGU_NOTE 丢弃)。
// ⚠ 照实说:那两处不是「解决了」,是【没收它们】。
//
// 【已删除的错误说法,不许复活】
//
// · ✗「表里说晁错腰斩于东市」→ 错。本表(卷十九下)在景帝三年御史大夫栏下只有【九个字】:
// 「正月壬子,錯有罪要斬」。「东市」二字【不在这一卷】,出《汉书》卷四十九《爰盎晁錯傳》
// (「錯衣朝衣斬東巿」)。这是【两卷】。带卷次的错误比没出处的错误危害大十倍 ——
// 把传里的细节记到表的账上,正是这种错误。
// ⚠ 又:本表作「要斬」不作「腰斬」(要,通腰)。照录原字,不许径改。
//
// · ✗「「N年遷」可以加到拜官那年算出去职之年」→ 错,已【实测自相矛盾】,不许复活:
// - 周亚夫:景帝三年(前154)拜太尉,表注「五年遷」;实际迁丞相在景帝七年(前150)。
// 前154 算第 1 年 → 前150 是第 5 年。【含拜年】才对得上。
// - 陶青:孝文後元二年(前162)拜御史大夫,表注「七年遷」;实际迁丞相在孝景二年(前155)。
// 前162 + 7 = 前155。【不含拜年】才对得上。
// - 刘舍:景帝七年(前150)拜御史大夫,表注「三年遷」;实际迁丞相在中元三年(前147)。
// 含拜年是第 4 年,不含拜年是 +3 = 前147 —— 两种读法在这里【又反过来】。
// 三个样本,两种读法互斥,没有一种读法通吃。→ 本表【绝不计算】去职之年:
// 「N年」只照录进 quote,任期终点一律取【后任的拜官记录】(那是有日子的硬记载)。
// ⚠ 顏師古在本卷卷首的注里【自己就提示过】这一类记法有问题(见 SHIGU_NOTE),
// 虽然他说的是另一个毛病,但方向一致:这张表的「若干年」不是可算的字段。
//
// · ✗「景帝前元三年」→ 表原文【没有「前元」】。本表纪年栏原文只作「孝景元年、二、三……七」,
// 然后「中元年、二……六」,然后「後元年、二、三」。只有「中」「後」两个年号,
// 头一段【无号】。「前元」是后人为了跟「中/後」对称造出来的叫法。晁错死在【孝景三年】。
//
// · ✗「汉的纪年跟公历一一对应」→ 错,见 CALENDAR_NOTE。太初改历之前,一个汉年跨两个公历年。
//
// · ✗「表里的列将军栏可以当那一年的将军名单」→ 错,本表【自己就阙】:元狩四年栏内写
// 「大將軍衞青為大司馬大將軍」,可是【卫青拜大将军这一笔,表中根本没有】。
// 一个「大将军」凭空出现在他升迁的那一格里。→ 列将军栏是【散记】,不是名单。见 COVERAGE。
//
// · ✗「表里同名就是同一个人」→ 错,本表元光四年【同一年】有两个「張敺」:
// 「中尉張敺為御史大夫」与「宣平侯張敺為太常」——一个是中尉迁御史大夫,一个是宣平侯拜太常,
// 封邑都不同。→ 除非本表自己的记事链能把人串起来(见 PERSON_NOTE),否则【不许并人】。
//
// 【必须带限定语的说法】
// · 本表把「太尉」和「大司馬」排在【同一栏】。但「大司马是不是太尉的后身」是制度史上的问题
// (武帝的大司马是加在大将军/票骑将军头上的加官,不是独立的一府),【本文件不表态】——
// 它只照录「表把它们排在同一栏」这个事实。要论制度,那是 dynasties.js 的地界,且得先有史料。
// · 武帝诸年号(建元、元光……)是否【自始行用】、抑或后来追建,学界有争论。
// 本文件照《汉书》的纪年书写,【不表态】—— 那超出这张表能证的范围。
// · 公历年是【换算】,原表只有纪年。换算与 ctext 该表自带的「公元前」栏逐行相符
// (前206 起连续递减到前87,无跳档),但那一栏也是【今人加的】,不是班固的字。
// ══════════════════════════════════════════════════════════════════════════
// ── 出处:全表共用一个 source ──
export const BIAO = '《汉书》卷十九下·百官公卿表第七下';
// ══════════════════════════════════════════════════════════════════════════
// ★★★ TABLE_SCOPE —— 【这张表说的是哪个汉】
//
// 2026-07-17 第五刀加。加它的理由,是本刀查出来的头号事实,而且它推翻了这一刀的前提:
//
// 【本表 = 西汉】景帝元年(前156)— 武帝後元二年(前87)。
// 【dynasties.js 的 'han' = 东汉】era:'东汉' span:'25–220'。
// ⇒ 两段【不重叠一年】。本产品的七朝里【没有一朝】装得下这张表。
//
// ★ 这不是「数据少了一点」,这是【叫「汉」的不是同一个汉】——
// 而它正是 dynasties.js 自己的头号铁律禁的那一件事(时代错置)。证据是本表自己的官名:
// 本表通篇的「御史大夫」,在引擎那个汉里【根本不存在】——《续汉书·百官志一》刘昭注引
// 《汉官仪》:「綏和元年,罷御史大夫官」,绥和元年 = 前 8 年,比东汉开国早三十余年。
// (dynasties.js bizhao.ceiling 那一段逐字写过这件事,是本表最贵的那种错的样本。)
// ⇒ 把 courtAt(-154) 接到 dynastyId='han' 上,就是把晁错、周亚夫塞进一部【东汉】的传里 ——
// 那本书的位阶是按东汉的察举/辟召算的,而它身边站着一群西汉人。
// ★★ kaoyan.js 早就把这件事写成了断言(renwu.js XIHAN_ONLY),只是【没人把它接到这张表上】。
//
// 【所以本表怎么办】—— 一句话:本表【不认领】任何一朝,而这是【数据】不是注释:
// dynastyIds: [] ⇒ services/court.js 查不到任何一朝 ⇒ 一律照实答「本表没有这一朝的年表」。
// ⚠ 这【不是】「那一年没人」。两回事,见 courtAt 的 status:'不載'。
//
// ★ 哪天有人真把西汉加进 dynasties.js:把它的 id 填进 dynastyIds 即可,
// 而 assertGongqingInvariants() 会【逐个核对它的 span 真的盖得住本表的年份】——
// 填错了当场报错。⇒ 认领是【要被验的】,不是写上就算。
// ══════════════════════════════════════════════════════════════════════════
export const TABLE_SCOPE = Object.freeze({
era: '西汉',
what: '《汉书》卷十九下·百官公卿表第七下 —— 汉景帝元年(前156)至汉武帝後元二年(前87)',
years: { from: -156, to: -87 },
// ★★ 本表认领的朝代 id(dynasties.js 的 DYNASTIES[].id)。【故意是空的】。
dynastyIds: [],
emptyWhy:
'★★【本产品的七朝里没有一朝装得下这张表】:本表是【西汉】(前156–前87),' +
'而 dynasties.js 的「汉」是【东汉】(span 25–220)—— 两段不重叠一年。' +
'⚠ 故这里是空的【不是漏填】,是结论:本表【不认领】任何一朝。' +
'★ 证据是本表自己的官名:通篇的「御史大夫」在东汉【不存在】(绥和元年/前8 罢,' +
'《续汉书·百官志一》刘昭注引《汉官仪》;dynasties.js 自己逐字写过)。' +
'⇒ 把本表接到 han 上 = 把晁错塞进一部东汉的传里 = 时代错置。' +
'这正是 kaoyan.js 对 renwu.js 那十个人已经断言过的同一件事,' +
'只是此前【没有人把那条断言接到这张表上】。',
note:
'★ 要让本表接得上,需要的【不是代码,是老板拍板】:往 dynasties.js 加一朝【西汉】。' +
'⚠ 而那不是本刀能替他做的决定 —— 加一朝要有梯子(秩石)、通道、天花板与原文,' +
'是第一刀那个量级的活。⇒ 本刀照实报,不替他假装接上了。',
});
// 见证与异文的总说明。⚠ 前端展示引文时必须能拿到它。
export const WITNESS_NOTE = Object.freeze({
witnesses: [
'汉川草庐 www.sidneyluo.net/a/a02/019b.htm(繁体 UTF-8,证书自签,取生 HTML)',
'ctext.org han-shu/bai-guan-gong-qing-biao-xia(取生 HTML)',
],
method:
'两边各自解析成 218 行表格,纪年逐行相符;景帝元年—武帝後元二年区间 291 格逐格比对,' +
'异文 24 处 = 14 同字异形(衞/衛)+ 2 ctext 坏字 + 3 同音异写(歐/敺)+ 5 要紧的。' +
'★ 落在已收条目里的异文一律记进 caveat,不替读者选;' +
'⚠ 另有 2 处要紧异文落在【未收的格】(元狩六年右内史、景帝中二年中尉),' +
'那不是「解决了」,是【没收它们】—— 照实说。',
ctextFlaws:
'⚠ ctext 该表有 OCR 坏字,且【自带指纹】——坏字是简体,混在繁体正文里一眼可辨:' +
'「平棘侯薛澤」讹作「平觉侯」、「孝文廟風發瓦」讹作「風發鲷」。凡遇 ctext 单独出简体字,' +
'一律判为 ctext 之误,从草庐。这不是偏袒草庐,是这两处有【内证】。',
punctuation:
'★ 引文【用字】照两个见证逐字比对;【标点为本项目自断】,未采用任何现代点校本的标点、' +
'分段或校勘成果。⚠ 这一条不是客套:两个见证的标点【完全一致】,正说明它们同出一个现代' +
'点校祖本(很可能是中华书局本)—— 原文字符属公有领域(班固,东汉),标点却是现代点校者的' +
'智力成果,受著作权保护。见证一致恰恰是【不能照抄】的理由,不是可以照抄的理由。',
});
// 顏師古在本卷【卷首】的注。★ 这条注是本文件三个数据结构决定的依据,故必须留着。
// ⚠ 它是【注】不是正文 —— 顏師古是唐人,班固是东汉人。凡「師古曰/應劭曰/如淳曰」开头的
// 一律是后人注,本文件的 quote 字段里【一个字都不许有】(解析时已按 /師古曰:「…」/ 剥除)。
// ⚠ 单证:只见于草庐本,ctext 的该表不载注。照实说,不装作有两个见证。
export const SHIGU_NOTE = Object.freeze({
what: '顏師古卷首注:这张表自己承认哪些地方是阙文',
source: '《汉书》卷十九下·百官公卿表第七下 卷首 顏師古注',
quote:
'此表中記公卿姓名不具,及但舉其官而無名,或言若干年不載遷免死者,皆史之闕文,不可得知。',
caveat:
'⚠【是注不是正文】:顏師古,唐人。⚠【单证】:草庐本有,ctext 该表不载注,未取得第二见证。' +
'⚠ 标点为本项目自断。',
why:
'★ 本文件三处设计直接依据这条注:' +
'(1)「記公卿姓名不具」→ 表里大量只有一个字的人名(「奉常斿」「廷尉勝」),' +
'person 一律置 null,【不许替它猜姓】;' +
'(2)「但舉其官而無名」→ 表里真有只写官名不写人的格(中元二年中尉栏只有「中尉」二字),' +
'这种格【整格丢弃】,不入库;' +
'(3)「或言若干年不載遷免死者」→ 「若干年」这种记法连顏師古都点了名,' +
'本表故【不计算】去职之年(另见上文「不许复活」第二条:实测三个样本,两种读法互斥)。',
});
// ── 纪年 → 公历:这一段是整个模块最容易出错、也最容易被人抓住的地方 ──
export const CALENDAR_NOTE = Object.freeze({
yearSign:
'★ year 是【公元前的年数取负】:-154 读作「公元前 154 年」。' +
'⚠ 这【不是】天文学年号 —— 天文学年号有公元 0 年,公元前 154 年在那套里记作 -153。' +
'两套差一。本表一律用前者(史学惯例)。不许混。',
suiShou:
'★★ 汉初承秦制,【以十月为岁首】。武帝太初元年才改:《汉书·武帝纪》太初元年正文' +
'「夏五月,正歷,以正月為歲首」(这是班固正文;紧跟其后的「師古曰」是注,不算)。' +
'→ 太初改历【之前】,一个汉年跨两个公历年:十月、十一月、十二月在前一个公历年,' +
'正月到九月在后一个。' +
'本表 year 取【该汉年正月所在的公历年】(与 ctext 该表的「公元前」栏一致)。' +
'⚠ 因此:太初元年(前104)之前,凡 month 是十月/十一月/十二月的条目,' +
'其【实际公历年应减一】—— 用 gregorianYearOf() 拿,不要直接读 year。' +
'实例:建元二年「十月丞相嬰免」,year 是 -139,可窦婴免相那个十月实际在公元前 140 年。',
taichuYear: -104,
eraNames:
'⚠ 表原文【没有「前元」】:景帝纪年原文只作「孝景元年、二、三……七」「中元年……」' +
'「後元年……」。只有「中」「後」两个号,头一段无号。「景帝前元三年」是后人的叫法。',
reignEras:
'⚠ 武帝诸年号是否自始行用抑或后来追建,学界有争论。本文件照《汉书》的纪年书写,不表态。',
});
// ══════════════════════════════════════════════════════════════════════════
// RULERS —— 帝王 + 纪年 → 公历。★ 这正是我们缺的那张纪年表。
//
// era.raw 表的纪年原文写法(表只在改元那年写全,其余年只写数字,本表补全并标明是【补的】)
// from/to 公历(负数 = 公元前)
// count 这一段几年 —— ⚠ 不是我数的,是【表自己的行数】:解析出的 218 行里,
// 改元行到下一个改元行之间正好有这么多行,且与 ctext 的「公元前」栏连续递减相符。
// 两个见证 + 行数 + 公历连续性,三重交叉。
// seedOnly ★ true = 这个帝王的公卿数据【不在本表覆盖范围内】,列他只为让 courtAt 接得上:
// 景帝元年的丞相是文帝朝拜的,不把文帝那两笔放进来,courtAt(-156) 就是空的。
// ══════════════════════════════════════════════════════════════════════════
export const RULERS = [
{
id: 'wen',
name: '汉文帝',
tableName: '孝文',
seedOnly: true,
seedNote: '★ 文帝朝【不在覆盖范围】。列他只为接住景帝元年在任的丞相与御史大夫。',
eras: [
{ key: '孝文', raw: '孝文元年', name: null, from: -179, to: -164, count: 16 },
{ key: '孝文後', raw: '後元年', name: '後', from: -163, to: -157, count: 7 },
],
},
{
id: 'jing',
name: '汉景帝',
tableName: '孝景',
eras: [
{
key: '孝景',
raw: '孝景元年',
name: null,
nameNote: '⚠ 无年号。表原文只作「孝景元年、二、三……七」。「前元」是后人的叫法,不许复活。',
from: -156,
to: -150,
count: 7,
},
{ key: '孝景中', raw: '中元年', name: '中', from: -149, to: -144, count: 6 },
{ key: '孝景後', raw: '後元年', name: '後', from: -143, to: -141, count: 3 },
],
},
{
id: 'wu',
name: '汉武帝',
tableName: '孝武',
eras: [
{ key: '建元', raw: '孝武建元元年', name: '建元', from: -140, to: -135, count: 6 },
{ key: '元光', raw: '元光元年', name: '元光', from: -134, to: -129, count: 6 },
{ key: '元朔', raw: '元朔元年', name: '元朔', from: -128, to: -123, count: 6 },
{ key: '元狩', raw: '元狩元年', name: '元狩', from: -122, to: -117, count: 6 },
{ key: '元鼎', raw: '元鼎元年', name: '元鼎', from: -116, to: -111, count: 6 },
{ key: '元封', raw: '元封元年', name: '元封', from: -110, to: -105, count: 6 },
{ key: '太初', raw: '太初元年', name: '太初', from: -104, to: -101, count: 4 },
{ key: '天漢', raw: '天漢元年', name: '天漢', from: -100, to: -97, count: 4 },
{ key: '太始', raw: '太始元年', name: '太始', from: -96, to: -93, count: 4 },
{ key: '征和', raw: '征和元年', name: '征和', from: -92, to: -89, count: 4 },
{ key: '後元', raw: '後元元年', name: '後元', from: -88, to: -87, count: 2 },
],
},
];
const ALL_ERAS = RULERS.flatMap((r) => r.eras.map((e) => ({ ...e, rulerId: r.id, rulerName: r.name, seedOnly: !!r.seedOnly })));
// ── 覆盖范围。★ 必须能被程序读到,不能只写在注释里 ──
// 【为什么要有这个】:这张表是【逐格抠出来的】,不是全抠完的。
// 一个只有三公的库,如果 courtAt 返回时不说「九卿我没抠全」,前端就会拿它当全名单画图,
// 然后有人发现元狩三年的九卿只有一个 —— 那不是数据少,那是【说了谎】。
export const COVERAGE = Object.freeze({
years: { from: -156, to: -87 },
yearsNote: '孝景元年(前156)— 孝武後元二年(前87)。景帝、武帝两朝,七十年。',
sangong: {
complete: true,
offices: ['丞相', '相國', '左丞相', '右丞相', '太尉', '大司馬', '御史大夫'],
note:
'★【全覆盖】:本表丞相栏、太尉/大司马栏、御史大夫栏在前156—前87 之间的每一格都已录入,' +
'一格没落。故 courtAt 的三公是可信的。',
},
jiangjun: {
complete: false,
note:
'⚠【表自己就阙】:列将军栏是散记不是名单 —— 元狩四年写「大將軍衞青為大司馬大將軍」,' +
'而卫青【拜大将军那一笔表中根本没有】。已录入的照录,但【不许当作那一年的将军名单】。',
},
jiuqing: {
complete: false,
note:
'⚠【只抠了两处,其余没抠】:(1) 孝景三年(前154)那一年的九卿全格 —— 因为那是样板年;' +
'(2) 主角们的起家一步(晁错、公孙弘、张汤)。其余年份的九卿栏【没抠】,不是没有。' +
'★ courtAt 的 others 字段【绝不许】当作那一年的九卿全名单。',
},
seeds: {
note:
'文帝朝录入 4 笔【接续用】的记事(灌婴、申屠嘉、陶青),只为让 courtAt(-156) 接得上。' +
'文帝朝【不在覆盖范围】,不许拿 courtAt(-160) 当数。',
},
});
// ── 座次:本表按【原表的栏】分,不按制度分 ──
// ⚠ 为什么不叫「三公」而叫「栏」:把太尉和大司马归一栏是【原表的编排】,
// 不是本项目对「大司马是不是太尉后身」的判断。本文件不表态,见卷首「必须带限定语的说法」。
export const SEATS = [
{ id: 'chengxiang', name: '丞相之位', column: 1, offices: ['相國', '丞相', '左丞相', '右丞相'] },
{
id: 'taiwei',
name: '太尉/大司马之位',
column: 2,
offices: ['太尉', '大司馬大將軍', '大司馬票騎將軍'],
note: '⚠ 表把太尉与大司马排在同一栏。这是原表的编排,不是本文件对二者关系的判断。',
},
{ id: 'yushi', name: '御史大夫之位', column: 3, offices: ['御史大夫'] },
{
id: 'jiangjun',
name: '列将军',
column: 4,
offices: ['大將軍', '票騎將軍', '車騎將軍', '衞將軍', '左將軍'],
note: '⚠ 散记,非名单。见 COVERAGE.jiangjun。',
},
];
const SEAT_BY_OFFICE = {};
for (const s of SEATS) for (const o of s.offices) SEAT_BY_OFFICE[o] = s.id;
// ── 事件词:一律用【表自己的字】,不许翻译成「离职」「任命」这种现代词 ──
// ⚠ 为什么这也要较真:「免」「薨」「卒」「死」「自殺」「要斬」「下獄死」「誅」在表里是
// 分得清清楚楚的七八个词。合并成「离任」,这张表最值钱的东西——【一个人怎么完的】——就没了。
export const EVENTS = [
{ id: 'bai', word: '拜', terminal: false, gloss: '就任。表的写法是「某为某官」。' },
{ id: 'mian', word: '免', terminal: true, gloss: '免职。活着。' },
{ id: 'qian', word: '遷', terminal: true, gloss: '迁转他官。' },
{ id: 'hong', word: '薨', terminal: true, gloss: '死在任上。诸侯与贵臣之死曰薨。' },
{ id: 'zu', word: '卒', terminal: true, gloss: '死在任上。' },
{ id: 'si', word: '死', terminal: true, gloss: '死。表在这个字上很省,别替它推死因。' },
{ id: 'zisha', word: '自殺', terminal: true, gloss: '自杀。表多作「有罪自殺」。' },
{ id: 'yaozhan', word: '要斬', terminal: true, gloss: '腰斩。★ 表作「要斬」,要通腰。照录原字。' },
{ id: 'xiayusi', word: '下獄死', terminal: true, gloss: '下狱而死。' },
{ id: 'zhu', word: '誅', terminal: true, gloss: '诛。' },
{ id: 'guansheng', word: '官省', terminal: true, gloss: '这个官【裁了】。★ 太尉一栏大半个世纪都是这两个字。' },
];
const EVENT_WORDS = new Set(EVENTS.map((e) => e.word));
const TERMINAL_WORDS = new Set(EVENTS.filter((e) => e.terminal).map((e) => e.word));
// ── 人名:本表怎么认人 ──
export const PERSON_NOTE = Object.freeze({
rule:
'本表【记事链】的行文惯例:一个人头回出现给全名(「魏其侯竇嬰為丞相」),' +
'此后同一个位子上再提他就只用名(「十月丞相嬰免」)。' +
'name 一律照录原文那一格的写法;person 是【本项目按这条链解出来的】规范名。',
limit:
'★ 解不出来就置 null,【不许猜】:「奉常殷」「廷尉勝」「御史大夫介」这类只有一个字的,' +
'正是顏師古说的「記公卿姓名不具……皆史之闕文,不可得知」。null 不是数据缺失,是史料如此。',
homonym:
'⚠【同名不并人】:元光四年同一年两个「張敺」(中尉張敺迁御史大夫、宣平侯張敺拜太常),' +
'封邑不同,显非一人。凡本表记事链串不起来的,一律各是各的,personUncertain 标出来。',
});
// ══════════════════════════════════════════════════════════════════════════
// GONGQING —— 事件流。★ 注意:这张表是【记事链】,不是【年度花名册】。
//
// 它只记【变动】:拜、免、薨、要斩。景帝三年那一格里【没有丞相】,
// 因为丞相陶青是景帝二年八月拜的,那年他没动。
// → 所以 courtAt(year) 只能【重放】这条链,不能查表。这跟 decisions.js 的 replay() 是同一个道理。
//
// 字段:
// year 公历(负 = 公元前)。⚠ 十月/十一月/十二月的条目见 CALENDAR_NOTE,用 gregorianYearOf()
// era 纪年(本项目补全的完整写法,如「孝景三年」)
// eraRaw 纪年【原文】(表在非改元年只写数字,如「三」)—— 补全的和原文的分开放
// month/day 表给了才有;【原文没写的一律 null,不许补】
// office 官名,照录原文用字
// seat 座次(由 office 映射;见 SEATS)
// name 人名,照录原文那一格的写法
// person 规范名(按记事链解出);解不出 = null
// event 事件,用表自己的字(见 EVENTS)
// quote 引文。★ 用字照两个见证逐字比对;【标点为本项目自断】
// witnesses 2 = 两个见证逐字相同;1 = 只有一个见证
// caveat 异文/限定语。有就必须给。
// seed true = 文帝朝的接续记事,不在覆盖范围
// ══════════════════════════════════════════════════════════════════════════
const E = (o) => o; // 只为让下面每条对齐好读,不做任何事
export const GONGQING = [
// ────────────────────────────────────────────────────────────────────────
// 接续用(文帝朝)—— ★ 不把这几笔放进来,courtAt(-156) 就是空的:
// 景帝元年的丞相是文帝後元二年拜的,太尉早在文帝三年就裁了。
// ────────────────────────────────────────────────────────────────────────
E({
year: -179, era: '孝文元年', eraRaw: '孝文元年', month: '十月', day: '辛亥',
office: '太尉', seat: 'taiwei', name: '灌嬰', person: '灌嬰', from: '將軍', event: '拜',
quote: '十月辛亥,將軍灌嬰為太尉,二年遷,官省。',
source: BIAO, witnesses: 2, seed: true,
caveat:
'⚠ 句末「二年遷,官省」照录,【不据以计算】年份(见卷首「不许复活」第二条)。' +
'⚠ 十月在岁首,此事实际在公元前 180 年(见 CALENDAR_NOTE)。',
}),
E({
year: -177, era: '孝文三年', eraRaw: '三', month: null, day: '乙亥',
office: '太尉', seat: 'taiwei', name: '灌嬰', person: '灌嬰', event: '遷', to: '丞相',
quote: '十二月,丞相勃免。乙亥,太尉灌嬰為丞相。',
source: BIAO, witnesses: 2, seed: true,
caveat: '⚠ 「十二月」系于「丞相勃免」,灌婴迁丞相只给了日「乙亥」【未给月】。原文没写的不补,month 置 null。',
}),
E({
year: -177, era: '孝文三年', eraRaw: '三', month: null, day: null,
office: '太尉', seat: 'taiwei', name: null, person: null, event: '官省',
quote: '十月辛亥,將軍灌嬰為太尉,二年遷,官省。',
source: BIAO, witnesses: 2, seed: true, yearInferred: true,
caveat:
'★【年份是推断,不是原文】:「官省」二字在【孝文元年】灌婴拜太尉那一格的句末,表未另系年。' +
'系于孝文三年的依据是同表孝文三年「乙亥,太尉灌嬰為丞相」(灌婴是年去太尉任),' +
'且此后至景帝三年周亚夫拜太尉之间,表中【无】太尉记事。' +
'⚠ 这是本项目的推断,courtAt 会把 inferred 标出来。要翻案就翻在这一条。',
}),
E({
year: -162, era: '孝文後元二年', eraRaw: '二', month: '八月', day: '庚午',
office: '丞相', seat: 'chengxiang', name: '申屠嘉', person: '申屠嘉', from: '御史大夫', event: '拜',
quote: '八月戊戌,丞相蒼免。庚午,御史大夫申屠嘉為丞相。',
source: BIAO, witnesses: 2, seed: true,
caveat: '★ 景帝元年在任的丞相就是他。景帝二年那格的「丞相嘉」= 申屠嘉,全名由这一笔解出。',
}),
E({
year: -162, era: '孝文後元二年', eraRaw: '二', month: '八月', day: '庚午',
office: '御史大夫', seat: 'yushi', name: '陶青', person: '陶青', from: '開封侯', event: '拜',
quote: '八月庚午,開封侯陶青為御史大夫,七年遷。',
source: BIAO, witnesses: 2, seed: true,
caveat:
'★ 景帝元年在任的御史大夫就是他。' +
'⚠ 「七年遷」照录不计算 —— 他实际迁丞相在孝景二年(前155);前162+7=前155 对得上,' +
'可周亚夫那条「五年遷」【含拜年才对得上】。两种读法互斥,故一律不算。见卷首第二条。',
}),
// ────────────────────────────────────────────────────────────────────────
// 孝景元年(前156)—— 晁错起家的那一年
// ────────────────────────────────────────────────────────────────────────
E({
year: -156, era: '孝景元年', eraRaw: '孝景元年', month: null, day: null,
office: '左內史', seat: null, name: '朝錯', person: '朝錯', from: '中大夫', event: '拜',
quote: '中大夫朝錯為左內史,一年遷。',
source: BIAO, witnesses: 1,
nameVariants: ['晁錯', '鼂錯'],
caveat:
'★★【异文,且是人名】:草庐本作「朝錯」,ctext 作「晁錯」—— 两个见证在这一格【不一致】。' +
'而下一年(孝景二年)那格【两个见证都作「朝錯」】。' +
'★ 更要命的是:同一个草庐本,卷四十九的【卷题】作「爰盎晁錯傳」,而该卷【正文十一处全作「鼂錯」】,' +
'一个本子里三种写法。→ 结论:朝/晁/鼂是【同一个名字的异写】,没有一个本子是自洽的。' +
'name 照录本格原文(朝錯),异写入 nameVariants,fateOf() 三种写法都认。' +
'⚠【不许】拿其中一种去「校正」另一种 —— 那是点校者的活,不是我们的活。',
}),
// ────────────────────────────────────────────────────────────────────────
// 孝景二年(前155)
// ────────────────────────────────────────────────────────────────────────
E({
year: -155, era: '孝景二年', eraRaw: '二', month: '六月', day: null,
office: '丞相', seat: 'chengxiang', name: '嘉', person: '申屠嘉', event: '薨',
quote: '六月丞相嘉薨。',
source: BIAO, witnesses: 2,
caveat: '「嘉」= 申屠嘉,由同表孝文後元二年「御史大夫申屠嘉為丞相」解出(见 PERSON_NOTE)。',
}),
E({
year: -155, era: '孝景二年', eraRaw: '二', month: '八月', day: '丁未',
office: '丞相', seat: 'chengxiang', name: '陶青', person: '陶青', from: '御史大夫', event: '拜',
quote: '八月丁未,御史大夫陶青為丞相。',
source: BIAO, witnesses: 2,
caveat: '★ 景帝三年七国反、晁错死的那一年,在位的丞相就是他 —— 他这一格【一年没动】,故那年不出现。',
}),
E({
year: -155, era: '孝景二年', eraRaw: '二', month: '八月', day: '丁巳',
office: '御史大夫', seat: 'yushi', name: '朝錯', person: '朝錯', from: '左內史', event: '拜',
quote: '八月丁巳,左內史朝錯為御史大夫。',
source: BIAO, witnesses: 2,
nameVariants: ['晁錯', '鼂錯'],
caveat:
'★ 这一格两个见证【都作「朝錯」】,与上一年那格的异文相反。见 -156 条的说明。' +
'⚠ 拜于八月丁巳,五个月后(次年正月壬子)要斩。这两笔之间【什么都没有】——' +
'表不记他为什么死,表只记他哪天死。',
}),
// ────────────────────────────────────────────────────────────────────────
// ★★ 孝景三年(前154)—— 这一年就是这个功能的样板。
// 七国反。你在场。而这一年表里记了七笔,笔笔要命。
// ────────────────────────────────────────────────────────────────────────
E({
year: -154, era: '孝景三年', eraRaw: '三', month: '正月', day: '壬子',
office: '御史大夫', seat: 'yushi', name: '錯', person: '朝錯', event: '要斬',
quote: '正月壬子,錯有罪要斬。',
source: BIAO, witnesses: 2, officeFrom: 'column',
nameVariants: ['晁錯', '鼂錯'],
caveat:
'★★ 两个见证【逐字相同】,一个字都不差。' +
'⚠【官名是本格文字没写的】:这九个字里没有「御史大夫」四字,官名取自【表的栏】。' +
'这不算补 —— 同表上一年「八月丁巳,左內史朝錯為御史大夫」把他放在这个位子上,' +
'栏与记事链两头对得上,故 officeFrom 标 column,让读者自己判。' +
'⚠【表作「要斬」不作「腰斬」】,要通腰。照录原字。' +
'★★【「东市」不在这一卷】:本表只有这九个字,没有「东市」,没有「衣朝衣」,也没有「七国反」。' +
'那些在《汉书》卷四十九《爰盎晁錯傳》(「錯衣朝衣斬東巿」,草庐/ctext 双证)。' +
'那是【另一卷】。把传里的细节记到表的账上,正是本项目最忌的那种错。' +
'(又:两个见证该处都把「市」写作「巿」,疑为共同的编码讹字;因非本卷之事,' +
'本表不引其文,只在此说明,不替它定谳。)',
}),
E({
year: -154, era: '孝景三年', eraRaw: '三', month: null, day: null,
office: '太尉', seat: 'taiwei', name: '周亞夫', person: '周亞夫', from: '中尉', event: '拜',
quote: '中尉周亞夫為太尉,五年遷,官省。',
source: BIAO, witnesses: 2,
caveat:
'★★ 太尉这个位子【空了二十三年】(自文帝三年灌婴迁丞相官省),七国反的这一年才重新拜人 ——' +
'这是本表最有力的一条:官制的开合,就是战事的影子。' +
'⚠ 「五年遷,官省」照录,【不据以算年】:他实际迁丞相在景帝七年(前150),' +
'前154 算第 1 年恰是第 5 年(含拜年);可陶青那条「七年遷」【不含拜年才对】。' +
'两种读法互斥 —— 这正是「N年不可计算」那条铁律的头一个样本。',
}),
E({
year: -154, era: '孝景三年', eraRaw: '三', month: null, day: null,
office: '大將軍', seat: 'jiangjun', name: '竇嬰', person: '竇嬰', from: '故詹事', event: '拜',
quote: '故詹事竇嬰為大將軍。',
source: BIAO, witnesses: 2,
corroboration: {
what: '★ 另一卷把这一笔的【为什么】写了出来,且与本表严丝合缝',
source: '《汉书》卷四十九·爰盎晁錯傳第十九',
quote: '嬰入言,上乃召盎。盎入見,竟言吳所以反,獨急斬錯以謝吳,吳可罷。上拜盎為泰常,竇嬰為大將軍。',
caveat:
'★★ 双证(草庐 049.htm + ctext han-shu/yuan-ang-chao-cuo-zhuan),逐字相同' +
'(仅「迺/乃」一字异写)。★ 这与本表孝景三年【两格】对得上:「故吳相袁盎為奉常」与' +
'「故詹事竇嬰為大將軍」—— 表记【谁升了】,传记【为什么升】:窦婴引袁盎入见,' +
'袁盎劝景帝斩晁错以谢吴,二人当年即各得一官。' +
'⚠ 是【旁证不是本表】,属卷四十九。⚠ 卷四十九作「泰常」,本表作「奉常」——' +
'而本表自己在中元六年记「奉常利更為太常」,可见景帝三年的正式官名是奉常,' +
'卷四十九的「泰常」是随手写。⚠ 标点为本项目自断。',
},
caveat: '⚠ 本表【没有】窦婴此前的记事,「故詹事」三字是本格自带的,不是别处解出来的。',
}),
E({
year: -154, era: '孝景三年', eraRaw: '三', month: null, day: null,
office: '奉常', seat: null, name: '袁盎', person: '袁盎', from: '故吳相', event: '拜',
quote: '故吳相袁盎為奉常。',
source: BIAO, witnesses: 2,
caveat:
'★★ 这一格是整张表里最刺人的一格:【吴反,而他是故吴相】——那年他从反王的相,' +
'变成了朝廷的奉常。同一年,主张削吴的晁错要斩。表把这两笔【并排放在同一行】,一个字的评论都没有。' +
'(为什么升,见同年窦婴条的 corroboration:卷四十九,双证。)',
}),
E({
year: -154, era: '孝景三年', eraRaw: '三', month: null, day: null,
office: '奉常', seat: null, name: '殷', person: null, event: '拜',
quote: '奉常殷。',
source: BIAO, witnesses: 2,
caveat:
'⚠【姓名不具】:只有一个「殷」字,person 置 null(见 SHIGU_NOTE)。' +
'⚠ 同年同官两笔(袁盎、殷),表【不说谁先谁后】,也不给月 —— 别替它排序。',
}),
E({
year: -154, era: '孝景三年', eraRaw: '三', month: null, day: null,
office: '廷尉', seat: null, name: '勝', person: null, event: '拜',
quote: '廷尉勝。',
source: BIAO, witnesses: 2,
caveat: '⚠【姓名不具】,person 置 null。见 SHIGU_NOTE。',
}),
E({
year: -154, era: '孝景三年', eraRaw: '三', month: null, day: null,
office: '宗正', seat: null, name: '劉通', person: '劉通', from: '德侯', event: '拜',
quote: '德侯劉通為宗正,三年薨。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「三年薨」照录,不据以算年。',
}),
E({
year: -154, era: '孝景三年', eraRaw: '三', month: null, day: null,
office: '中尉', seat: null, name: '衞綰', person: '衞綰', from: '河間大傅', event: '拜',
quote: '河間大傅衞綰為中尉,四年賜告,後為太子太傅。',
source: BIAO, witnesses: 2,
caveat:
'⚠ 表作「大傅」(= 太傅),照录不径改。⚠ ctext 作「衛綰」(衞/衛同字异形)。' +
'⚠ 「四年賜告」照录,不据以算年。' +
'★ 他后来做到丞相(景帝後元年),是这一年在场的人里【走得最远又善终】的一个 —— ' +
'与同年要斩的晁错正好是一对照。',
}),
// ────────────────────────────────────────────────────────────────────────
// 孝景四年—七年
// ────────────────────────────────────────────────────────────────────────
E({
year: -153, era: '孝景四年', eraRaw: '四', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '介', person: null, event: '拜',
quote: '御史大夫介。',
source: BIAO, witnesses: 2,
caveat: '⚠【姓名不具】,person 置 null。★ 接晁错位子的这个人,表只留下一个字。',
}),
E({
year: -150, era: '孝景七年', eraRaw: '七', month: '六月', day: '乙巳',
office: '丞相', seat: 'chengxiang', name: '青', person: '陶青', event: '免',
quote: '六月乙巳,丞相青免。',
source: BIAO, witnesses: 2,
}),
E({
year: -150, era: '孝景七年', eraRaw: '七', month: null, day: null,
office: '丞相', seat: 'chengxiang', name: '周亞夫', person: '周亞夫', from: '太尉', event: '拜',
quote: '太尉周亞夫為丞相。',
source: BIAO, witnesses: 2,
caveat: '⚠ 同格前句「六月乙巳」系于「丞相青免」,周亚夫拜相【未给月日】。不补。',
}),
E({
year: -150, era: '孝景七年', eraRaw: '七', month: null, day: null,
office: '太尉', seat: 'taiwei', name: '周亞夫', person: '周亞夫', event: '遷', to: '丞相',
quote: '太尉周亞夫為丞相。',
source: BIAO, witnesses: 2,
caveat:
'★ 他离开太尉这一笔,靠的就是同格「太尉周亞夫為丞相」六个字 —— 表没有单写「太尉某免」,' +
'是这一句自己说了他从哪儿走的。courtAt 靠它把人从太尉那个位子上拿下来。',
}),
E({
year: -150, era: '孝景七年', eraRaw: '七', month: null, day: null,
office: '太尉', seat: 'taiwei', name: null, person: null, event: '官省',
quote: '中尉周亞夫為太尉,五年遷,官省。',
source: BIAO, witnesses: 2, yearInferred: true,
caveat:
'★【年份是推断】:「官省」在【景帝三年】周亚夫拜太尉那格的句末,表未另系年。' +
'系于景帝七年的依据是同表是年「太尉周亞夫為丞相」(他去了太尉任),' +
'且此后至建元元年田蚡拜太尉之间表中无太尉记事。⚠ 推断,非原文。',
}),
E({
year: -150, era: '孝景七年', eraRaw: '七', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '劉舍', person: '劉舍', from: '太僕', event: '拜',
quote: '太僕劉舍為御史大夫,三年遷。',
source: BIAO, witnesses: 2,
caveat:
'⚠ 「三年遷」照录,不据以算年 —— 他实际迁丞相在中元三年(前147):含拜年是第 4 年、' +
'不含拜年是 +3。★ 这一条是「N年不可计算」的第三个样本,' +
'而且它跟周亚夫那条【两种读法都对不上同一种】。见卷首第二条。',
}),
// ────────────────────────────────────────────────────────────────────────
// 孝景中三年—後元年
// ────────────────────────────────────────────────────────────────────────
E({
year: -147, era: '孝景中三年', eraRaw: '三', month: '九月', day: '戊戌',
office: '丞相', seat: 'chengxiang', name: '亞夫', person: '周亞夫', event: '免',
quote: '九月戊戌,丞相亞夫免。',
source: BIAO, witnesses: 2,
caveat: '★ 表只记到「免」为止。周亚夫后来怎么死的,【不在这一卷】—— 别替表往下写。',
}),
E({
year: -147, era: '孝景中三年', eraRaw: '三', month: null, day: null,
office: '丞相', seat: 'chengxiang', name: '劉舍', person: '劉舍', from: '御史大夫', event: '拜',
quote: '御史大夫劉舍為丞相。',
source: BIAO, witnesses: 2,
caveat: '⚠ 同格前句的「九月戊戌」系于「丞相亞夫免」,此拜未给月日。不补。',
}),
E({
year: -147, era: '孝景中三年', eraRaw: '三', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '衞綰', person: '衞綰', from: '太子太傅', event: '拜',
quote: '太子太傅衞綰為御史大夫,四年遷。',
source: BIAO, witnesses: 2,
caveat: '⚠ ctext 作「衛綰」(同字异形)。⚠ 「四年遷」照录,不算。',
}),
E({
year: -143, era: '孝景後元年', eraRaw: '後元年', month: '七月', day: '丙午',
office: '丞相', seat: 'chengxiang', name: '舍', person: '劉舍', event: '免',
quote: '七月丙午,丞相舍免。',
source: BIAO, witnesses: 2,
}),
E({
year: -143, era: '孝景後元年', eraRaw: '後元年', month: '八月', day: '壬辰',
office: '丞相', seat: 'chengxiang', name: '衞綰', person: '衞綰', from: '御史大夫', event: '拜',
quote: '八月壬辰,御史大夫衞綰為丞相。',
source: BIAO, witnesses: 2,
caveat: '⚠ ctext 作「衛綰」。★ 景帝三年他还只是个中尉,十一年后他是丞相。',
}),
E({
year: -143, era: '孝景後元年', eraRaw: '後元年', month: '八月', day: '壬辰',
office: '御史大夫', seat: 'yushi', name: '直不疑', person: '直不疑', from: '衞尉', event: '拜',
quote: '八月壬辰,衞尉直不疑為御史大夫。',
source: BIAO, witnesses: 2,
caveat:
'★★【要紧异文,且关乎一个人的下场】:句末草庐本作「三年【死】」,ctext 作「三年【免】」。' +
'死与免,是死在任上还是活着离任 —— 两个见证给的是两回事。' +
'★ 故本条 quote【截到「為御史大夫」为止】,那半句两边一致;有分歧的四个字不进 quote,' +
'也【不给他 fate】。fateOf("直不疑") 只会告诉你「两个见证打架,本表不裁」。' +
'⚠ 这不是数据缺失,这是【查不实的宁可不写】在这一格的样子。',
variantUnresolved: { field: 'fate', witnessA: '三年死(草庐)', witnessB: '三年免(ctext)' },
}),
// ────────────────────────────────────────────────────────────────────────
// 孝武建元元年(前140)—— 窦婴、田蚡同时上来的那一年
// ────────────────────────────────────────────────────────────────────────
E({
year: -140, era: '孝武建元元年', eraRaw: '孝武建元元年', month: '六月', day: null,
office: '丞相', seat: 'chengxiang', name: '綰', person: '衞綰', event: '免',
quote: '六月,丞相綰免。',
source: BIAO, witnesses: 2,
}),
E({
year: -140, era: '孝武建元元年', eraRaw: '孝武建元元年', month: null, day: '丙寅',
office: '丞相', seat: 'chengxiang', name: '竇嬰', person: '竇嬰', from: '魏其侯', event: '拜',
quote: '丙寅,魏其侯竇嬰為丞相。',
source: BIAO, witnesses: 2,
caveat:
'⚠ 只给日「丙寅」,【未给月】。同格前句作「六月,丞相綰免」,读起来像六月丙寅,' +
'但原文没有重出月份 —— 不补,month 置 null。' +
'★ 景帝三年他是大将军,十四年后他是丞相 —— 而他这个丞相只当了几个月(见次年)。',
}),
E({
year: -140, era: '孝武建元元年', eraRaw: '孝武建元元年', month: null, day: null,
office: '太尉', seat: 'taiwei', name: '田蚡', person: '田蚡', from: '武安侯', event: '拜',
quote: '武安侯田蚡為太尉。',
source: BIAO, witnesses: 2,
caveat: '★ 太尉这个位子自景帝七年裁掉,隔了十年,为他一个人又开了一次 —— 开了一年(见次年)。',
}),
E({
year: -140, era: '孝武建元元年', eraRaw: '孝武建元元年', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '牛抵', person: '牛抵', from: '齊相', event: '拜',
quote: '齊相牛抵為御史大夫。',
source: BIAO, witnesses: 2,
}),
// ────────────────────────────────────────────────────────────────────────
// 孝武建元二年(前139)
// ────────────────────────────────────────────────────────────────────────
E({
year: -139, era: '孝武建元二年', eraRaw: '二', month: '十月', day: null,
office: '丞相', seat: 'chengxiang', name: '嬰', person: '竇嬰', event: '免',
quote: '十月丞相嬰免。',
source: BIAO, witnesses: 2,
caveat:
'★★【这一格是「岁首在十月」的活证据】:十月在【三月乙未】之前 —— 同一格里十月排在三月前头,' +
'正因为汉初的年是从十月起的。' +
'⚠ 故此事实际在【公元前 140 年】十月,不是前 139 年。year 记 -139 是本表的口径' +
'(取该汉年正月所在的公历年),要实际公历年请用 gregorianYearOf()。见 CALENDAR_NOTE。',
}),
E({
year: -139, era: '孝武建元二年', eraRaw: '二', month: '三月', day: '乙未',
office: '丞相', seat: 'chengxiang', name: '許昌', person: '許昌', from: '太常', event: '拜',
quote: '三月乙未,太常許昌為丞相。',
source: BIAO, witnesses: 2,
}),
E({
year: -139, era: '孝武建元二年', eraRaw: '二', month: null, day: null,
office: '太尉', seat: 'taiwei', name: '蚡', person: '田蚡', event: '免',
quote: '太尉蚡免,官省。',
source: BIAO, witnesses: 2,
}),
E({
year: -139, era: '孝武建元二年', eraRaw: '二', month: null, day: null,
office: '太尉', seat: 'taiwei', name: null, person: null, event: '官省',
quote: '太尉蚡免,官省。',
source: BIAO, witnesses: 2,
caveat:
'★ 这一条的「官省」【是原文自带年份的】—— 与灌婴、周亚夫那两条不同,' +
'它就写在建元二年这一格里,不用推断,故【没有 yearInferred】。' +
'★★ 自此至後元二年霍光拜大司马大将军,太尉一栏【五十二年空白】。' +
'「太尉是谁」这个问题,在武帝朝大半时间里的答案是:没有这个官。',
}),
E({
year: -139, era: '孝武建元二年', eraRaw: '二', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '趙綰', person: '趙綰', event: '自殺',
quote: '御史大夫趙綰,有罪自殺。',
source: BIAO, witnesses: 2,
caveat:
'⚠★【表阙】:赵绾【拜御史大夫这一笔,本表没有】—— 他是凭空出现在自己的死讯里的。' +
'上一年的御史大夫是牛抵,牛抵怎么走的表也没记。' +
'这正是顏師古说的「史之闕文,不可得知」。别替它填。',
}),
// ────────────────────────────────────────────────────────────────────────
// 孝武建元四年—六年
// ────────────────────────────────────────────────────────────────────────
E({
year: -137, era: '孝武建元四年', eraRaw: '四', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '嚴青翟', person: '嚴青翟', from: '武強侯', event: '拜',
quote: '武強侯嚴青翟為御史大夫,二年,坐竇太后喪不辦免。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「二年……免」照录,不据以算年。★ 他后来做到丞相,然后自杀(元鼎二年)。',
}),
E({
year: -135, era: '孝武建元六年', eraRaw: '六', month: '六月', day: '癸巳',
office: '丞相', seat: 'chengxiang', name: '昌', person: '許昌', event: '免',
quote: '六月癸巳,丞相昌免。',
source: BIAO, witnesses: 2,
}),
E({
year: -135, era: '孝武建元六年', eraRaw: '六', month: null, day: null,
office: '丞相', seat: 'chengxiang', name: '田蚡', person: '田蚡', from: '武安侯', event: '拜',
quote: '武安侯田蚡為丞相。',
source: BIAO, witnesses: 2,
caveat: '⚠ 同格「六月癸巳」系于「丞相昌免」,此拜未给月日。不补。',
}),
E({
year: -135, era: '孝武建元六年', eraRaw: '六', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '韓安國', person: '韓安國', from: '大農令', event: '拜',
quote: '大農令韓安國為御史大夫,四年病免。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「四年病免」照录,不据以算年。',
}),
// ────────────────────────────────────────────────────────────────────────
// 元光四年(前131)—— 田蚡薨
// ────────────────────────────────────────────────────────────────────────
E({
year: -131, era: '元光四年', eraRaw: '四', month: '三月', day: '乙卯',
office: '丞相', seat: 'chengxiang', name: '蚡', person: '田蚡', event: '薨',
quote: '三月乙卯,丞相蚡薨。',
source: BIAO, witnesses: 2,
caveat:
'★ 表给田蚡的字是「薨」—— 死在丞相任上,善终的写法。' +
'⚠ 而窦婴怎么完的,【本表一个字都没有】:他建元二年免相之后就再没进过这张表。' +
'公卿年表只记公卿;你不在那个位子上,你的死不归它管。' +
'★★ 这本身就是这张表最冷的一句话。',
}),
E({
year: -131, era: '元光四年', eraRaw: '四', month: '五月', day: '丁巳',
office: '丞相', seat: 'chengxiang', name: '薛澤', person: '薛澤', from: '平棘侯', event: '拜',
quote: '五月丁巳,平棘侯薛澤為丞相。',
source: BIAO, witnesses: 2,
caveat:
'⚠ 异文:ctext 作「平【觉】侯」。判为 ctext 之误 —— 「觉」是简体字,混在繁体正文里,' +
'是 OCR 坏字的指纹(同页另有「風發【鲷】」同类)。从草庐作「平棘侯」。见 WITNESS_NOTE.ctextFlaws。',
}),
E({
year: -131, era: '元光四年', eraRaw: '四', month: '九月', day: null,
office: '御史大夫', seat: 'yushi', name: '張敺', person: null, personUncertain: true, from: '中尉', event: '拜',
quote: '九月,中尉張敺為御史大夫,五年老病免,食上大夫祿。',
source: BIAO, witnesses: 2,
nameVariants: ['張歐'],
caveat:
'⚠ 异文:ctext 作「張歐」(歐/敺,顏師古于卷内注「歐讀與驅同」、卷四十九注「張𢿛也,音區」,' +
'三形同音同人)。' +
'⚠★【同名不并人,故 person 置 null】:本表【同一年】还有一笔「宣平侯張敺為太常」——' +
'一个是中尉迁御史大夫,一个是宣平侯拜太常,封邑不同,显非一人。' +
'本表另有景帝元年「廷尉歐」、景帝五年「安丘侯張歐為奉常」。这几笔是几个人,' +
'本表的记事链【串不起来】,故一律不并。见 PERSON_NOTE.homonym。' +
'⚠ 「五年老病免」照录,不算。★ 但「老病免,食上大夫祿」是这张表里少见的好下场。',
}),
// ────────────────────────────────────────────────────────────────────────
// 元光五年(前130)—— 公孙弘起家
// ────────────────────────────────────────────────────────────────────────
E({
year: -130, era: '元光五年', eraRaw: '五', month: null, day: null,
office: '左內史', seat: null, name: '公孫弘', person: '公孫弘', from: '博士', event: '拜',
quote: '博士公孫弘為左內史,四年遷。',
source: BIAO, witnesses: 2,
caveat:
'⚠ 「四年遷」照录,不算。' +
'★ 他由【博士】起家,六年后是御史大夫,八年后是丞相 —— 这条线是本表里' +
'「一个没有军功、没有外戚身份的人能走多远」的样本。',
}),
// ────────────────────────────────────────────────────────────────────────
// 元朔三年—五年
// ────────────────────────────────────────────────────────────────────────
E({
year: -126, era: '元朔三年', eraRaw: '三', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '公孫弘', person: '公孫弘', from: '左內史', event: '拜',
quote: '左內史公孫弘為御史大夫,二年遷。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「二年遷」照录,不算(实际迁丞相在元朔五年)。',
}),
E({
year: -126, era: '元朔三年', eraRaw: '三', month: null, day: null,
office: '廷尉', seat: null, name: '張湯', person: '張湯', from: '中大夫', event: '拜',
quote: '中大夫張湯為廷尉,五年遷。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「五年遷」照录,不算。',
}),
E({
year: -124, era: '元朔五年', eraRaw: '五', month: '十一月', day: '乙丑',
office: '丞相', seat: 'chengxiang', name: '澤', person: '薛澤', event: '免',
quote: '十一月乙丑,丞相澤免。',
source: BIAO, witnesses: 2,
caveat: '⚠ 十一月在岁首之后、正月之前 → 此事实际在公元前 125 年。见 CALENDAR_NOTE。',
}),
E({
year: -124, era: '元朔五年', eraRaw: '五', month: null, day: null,
office: '丞相', seat: 'chengxiang', name: '公孫弘', person: '公孫弘', from: '御史大夫', event: '拜',
quote: '御史大夫公孫弘為丞相。',
source: BIAO, witnesses: 2,
caveat: '⚠ 同格「十一月乙丑」系于「丞相澤免」,此拜未给月日。不补。',
}),
E({
year: -124, era: '元朔五年', eraRaw: '五', month: '四月', day: '丁未',
office: '御史大夫', seat: 'yushi', name: '番係', person: '番係', from: '河東太守', event: '拜',
quote: '四月丁未,河東太守九江番係為御史大夫。',
source: BIAO, witnesses: 2,
caveat: '「九江」是籍贯。⚠ 顏師古注「番音普安反」(是注,不入 quote)。',
}),
// ────────────────────────────────────────────────────────────────────────
// 元狩元年—六年
// ────────────────────────────────────────────────────────────────────────
E({
year: -122, era: '元狩元年', eraRaw: '元狩元年', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '李蔡', person: '李蔡', from: '樂安侯', event: '拜',
quote: '樂安侯李蔡為御史大夫,一年遷。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「一年遷」照录,不算。',
}),
E({
year: -121, era: '元狩二年', eraRaw: '二', month: '三月', day: '戊寅',
office: '丞相', seat: 'chengxiang', name: '弘', person: '公孫弘', event: '薨',
quote: '三月戊寅,丞相弘薨。',
source: BIAO, witnesses: 2,
caveat: '★ 表给公孙弘的字是「薨」:他死在丞相任上。这张表里的丞相,能拿到这个字的不多。',
}),
E({
year: -121, era: '元狩二年', eraRaw: '二', month: null, day: '壬辰',
office: '丞相', seat: 'chengxiang', name: '李蔡', person: '李蔡', from: '御史大夫', event: '拜',
quote: '壬辰,御史大夫李蔡為丞相。',
source: BIAO, witnesses: 2,
caveat: '⚠ 只给日「壬辰」,未给月(同格前句作「三月戊寅」)。不补。',
}),
E({
year: -120, era: '元狩三年', eraRaw: '三', month: '三月', day: '壬辰',
office: '御史大夫', seat: 'yushi', name: '張湯', person: '張湯', from: '廷尉', event: '拜',
quote: '三月壬辰,廷尉張湯為御史大夫。',
source: BIAO, witnesses: 2,
caveat:
'⚠ 原格句末尚有「六年有罪自殺」六字,两个见证一致;本条 quote 截到拜官为止,' +
'其自杀另出一条(见 fate 条),【年份不算,只照录】。',
fateInline: {
words: '六年有罪自殺',
quote: '三月壬辰,廷尉張湯為御史大夫,六年有罪自殺。',
caveat:
'★【下场写在拜官那一格里】:表的惯例是把结局压缩进任命句尾。' +
'⚠ 故张汤【死于哪一年,本表算不出来】——「六年」是那个不可计算的记法。' +
'fateOf("張湯") 会给你「自殺」这个结局,year 一律 null。' +
'(《汉书》卷六·武帝纪元鼎二年另有「御史大夫張湯有罪,自殺」,那是【另一卷】,' +
'本表不据以补年 —— 要补,得先按本项目的规矩把那一卷也做两个见证。)',
},
}),
E({
year: -120, era: '元狩三年', eraRaw: '三', month: null, day: null,
office: '票騎將軍', seat: 'jiangjun', name: '霍去病', person: '霍去病', from: '冠軍侯', event: '拜',
quote: '冠軍侯霍去病為票騎將軍。',
source: BIAO, witnesses: 2,
caveat: '⚠ 表作「票騎」不作「驃騎」。照录原字。',
}),
E({
year: -119, era: '元狩四年', eraRaw: '四', month: null, day: null,
office: '大司馬大將軍', seat: 'taiwei', name: '衞青', person: '衞青', from: '大將軍', event: '拜',
quote: '大將軍衞青為大司馬大將軍。',
source: BIAO, witnesses: 2,
caveat:
'⚠ ctext 作「衛青」(同字异形)。' +
'⚠★【表阙的活证据】:这一格管卫青叫「大將軍」,可【卫青拜大将军那一笔,本表从头到尾没有】。' +
'一个大将军凭空出现在他升迁的那一格里 —— 这就是列将军栏不能当名单的原因。见 COVERAGE.jiangjun。' +
'⚠ 「大司马」放在太尉栏,是【原表的编排】,不是本文件对二者关系的判断。见 SEATS.taiwei。',
}),
E({
year: -119, era: '元狩四年', eraRaw: '四', month: null, day: null,
office: '大司馬票騎將軍', seat: 'taiwei', name: '霍去病', person: '霍去病', from: '票騎將軍', event: '拜',
quote: '票騎將軍霍去病為大司馬票騎將軍。',
source: BIAO, witnesses: 2,
caveat: '★ 与卫青同格同年 —— 太尉那一栏,这一年头一回同时坐了两个人。',
}),
E({
year: -118, era: '元狩五年', eraRaw: '五', month: '三月', day: '甲午',
office: '丞相', seat: 'chengxiang', name: '蔡', person: '李蔡', event: '自殺',
quote: '三月甲午,丞相蔡有罪自殺。',
source: BIAO, witnesses: 2,
}),
E({
year: -118, era: '元狩五年', eraRaw: '五', month: '四月', day: '乙卯',
office: '丞相', seat: 'chengxiang', name: '嚴青翟', person: '嚴青翟', from: '太子少傅', event: '拜',
quote: '四月乙卯,太子少傅嚴青翟為丞相。',
source: BIAO, witnesses: 2,
}),
E({
year: -117, era: '元狩六年', eraRaw: '六', month: '九月', day: null,
office: '大司馬票騎將軍', seat: 'taiwei', name: '去病', person: '霍去病', event: '薨',
quote: '九月,大司馬去病薨。',
source: BIAO, witnesses: 2,
}),
// ────────────────────────────────────────────────────────────────────────
// 元鼎二年—六年
// ────────────────────────────────────────────────────────────────────────
E({
year: -115, era: '元鼎二年', eraRaw: '二', month: '二月', day: '壬辰',
office: '丞相', seat: 'chengxiang', name: '青翟', person: '嚴青翟', event: '自殺',
quote: '二月壬辰,丞相青翟有罪自殺。',
source: BIAO, witnesses: 2,
caveat: '★ 连着两任丞相「有罪自殺」(李蔡、严青翟)。表不评论,表只是一笔一笔地记。',
}),
E({
year: -115, era: '元鼎二年', eraRaw: '二', month: '二月', day: '辛亥',
office: '丞相', seat: 'chengxiang', name: '趙周', person: '趙周', from: '太子太傅', event: '拜',
quote: '二月辛亥,太子太傅趙周為丞相。',
source: BIAO, witnesses: 2,
}),
E({
year: -115, era: '元鼎二年', eraRaw: '二', month: '二月', day: '辛亥',
office: '御史大夫', seat: 'yushi', name: '石慶', person: '石慶', from: '太子太傅', event: '拜',
quote: '二月辛亥,太子太傅石慶為御史大夫,三年遷。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「三年遷」照录,不算。⚠ 与赵周同日(二月辛亥)、同出太子太傅 —— 表如此,不深说。',
}),
E({
year: -112, era: '元鼎五年', eraRaw: '五', month: '九月', day: '辛巳',
office: '丞相', seat: 'chengxiang', name: '周', person: '趙周', event: '下獄死',
quote: '九月辛巳,丞相周下獄死。',
source: BIAO, witnesses: 2,
}),
E({
year: -112, era: '元鼎五年', eraRaw: '五', month: null, day: '丙申',
office: '丞相', seat: 'chengxiang', name: '石慶', person: '石慶', from: '御史大夫', event: '拜',
quote: '丙申,御史大夫石慶為丞相。',
source: BIAO, witnesses: 2,
caveat: '⚠ 只给日,未给月(同格前句作「九月辛巳」)。不补。',
}),
E({
year: -111, era: '元鼎六年', eraRaw: '六', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '卜式', person: '卜式', from: '齊相', event: '拜',
quote: '齊相卜式為御史大夫,一年貶為太子太傅。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「一年貶」照录,不算。',
}),
// ────────────────────────────────────────────────────────────────────────
// 元封元年—五年
// ────────────────────────────────────────────────────────────────────────
E({
year: -110, era: '元封元年', eraRaw: '元封元年', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '兒寬', person: '兒寬', from: '左內史', event: '拜',
quote: '左內史兒寬為御史大夫,八年卒。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「八年卒」照录,不算。★ 御史大夫这个位子上,能拿到「卒」字(死在任上、无罪)的不多。',
}),
E({
year: -106, era: '元封五年', eraRaw: '五', month: null, day: null,
office: '大司馬大將軍', seat: 'taiwei', name: '青', person: '衞青', event: '薨',
quote: '大將軍青薨。',
source: BIAO, witnesses: 2,
caveat:
'⚠ 表这里称他「大將軍」而非「大司馬大將軍」,office 取其元狩四年所拜之全名。' +
'⚠ ctext 作「衛青」。',
}),
// ────────────────────────────────────────────────────────────────────────
// 太初二年—三年
// ────────────────────────────────────────────────────────────────────────
E({
year: -103, era: '太初二年', eraRaw: '二', month: '正月', day: '戊寅',
office: '丞相', seat: 'chengxiang', name: '慶', person: '石慶', event: '薨',
quote: '正月戊寅,丞相慶薨。',
source: BIAO, witnesses: 2,
caveat: '★ 太初元年已改历(以正月为岁首),故此后「正月」就是岁首。见 CALENDAR_NOTE。',
}),
E({
year: -103, era: '太初二年', eraRaw: '二', month: '閏月', day: '丁丑',
office: '丞相', seat: 'chengxiang', name: '公孫賀', person: '公孫賀', from: '大僕', event: '拜',
quote: '閏月丁丑,大僕公孫賀為丞相。',
source: BIAO, witnesses: 2,
caveat: '⚠ 表作「大僕」(= 太僕),照录不径改。⚠ 「閏月」照录,本表不推它是闰几月。',
}),
E({
year: -102, era: '太初三年', eraRaw: '三', month: '正月', day: null,
office: '御史大夫', seat: 'yushi', name: '延廣', person: '延廣', from: '膠東太守', event: '拜',
quote: '正月,膠東太守延廣為御史大夫。',
source: BIAO, witnesses: 2,
}),
// ────────────────────────────────────────────────────────────────────────
// 天漢元年—三年
// ────────────────────────────────────────────────────────────────────────
E({
year: -100, era: '天漢元年', eraRaw: '天漢元年', month: null, day: null,
office: '御史大夫', seat: 'yushi', name: '王卿', person: '王卿', from: '濟南太守', event: '拜',
quote: '濟南太守琅邪王卿為御史大夫,二年有罪自殺。',
source: BIAO, witnesses: 2,
caveat: '「琅邪」是籍贯。⚠ 「二年有罪自殺」照录,不算。',
fateInline: { words: '二年有罪自殺', quote: '濟南太守琅邪王卿為御史大夫,二年有罪自殺。' },
}),
E({
year: -98, era: '天漢三年', eraRaw: '三', month: '二月', day: null,
office: '御史大夫', seat: 'yushi', name: '杜周', person: '杜周', from: '執金吾', event: '拜',
quote: '二月,執金吾杜周為御史大夫,四年卒。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「四年卒」照录,不算。',
fateInline: { words: '四年卒', quote: '二月,執金吾杜周為御史大夫,四年卒。' },
}),
// ────────────────────────────────────────────────────────────────────────
// 太始三年
// ────────────────────────────────────────────────────────────────────────
E({
year: -94, era: '太始三年', eraRaw: '三', month: '三月', day: null,
office: '御史大夫', seat: 'yushi', name: '暴勝之', person: '暴勝之', from: '光祿大夫', event: '拜',
quote: '三月,光祿大夫河東暴勝之公子為御史大夫,三年下獄自殺。',
source: BIAO, witnesses: 2,
caveat:
'「河東」是籍贯,「公子」是字(顏師古注「公子,亦勝之字也」—— 是注,不入 quote)。' +
'⚠ 「三年下獄自殺」照录,不算。',
fateInline: { words: '三年下獄自殺', quote: '三月,光祿大夫河東暴勝之公子為御史大夫,三年下獄自殺。' },
}),
// ────────────────────────────────────────────────────────────────────────
// 征和二年—四年 —— 巫蛊。这三年表里全是「下獄」「要斬」「祝詛」。
// ────────────────────────────────────────────────────────────────────────
E({
year: -91, era: '征和二年', eraRaw: '二', month: '四月', day: '壬申',
office: '丞相', seat: 'chengxiang', name: '賀', person: '公孫賀', event: '下獄死',
quote: '四月壬申,丞相賀下獄死。',
source: BIAO, witnesses: 2,
}),
E({
year: -91, era: '征和二年', eraRaw: '二', month: '五月', day: '丁巳',
office: '左丞相', seat: 'chengxiang', name: '劉屈氂', person: '劉屈氂', from: '涿郡太守', event: '拜',
quote: '五月丁巳,涿郡太守劉屈氂為左丞相。',
source: BIAO, witnesses: 2,
caveat: '⚠ 表作【左丞相】,不是丞相 —— 照录,不许径写成「丞相」。',
}),
E({
year: -91, era: '征和二年', eraRaw: '二', month: '九月', day: null,
office: '御史大夫', seat: 'yushi', name: '商丘成', person: '商丘成', from: '大鴻臚', event: '拜',
quote: '九月,大鴻臚商丘成為御史大夫,四年坐祝詛自殺。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「四年坐祝詛自殺」照录,不算。',
fateInline: { words: '四年坐祝詛自殺', quote: '九月,大鴻臚商丘成為御史大夫,四年坐祝詛自殺。' },
}),
E({
year: -90, era: '征和三年', eraRaw: '三', month: '六月', day: '壬寅',
office: '左丞相', seat: 'chengxiang', name: '屈氂', person: '劉屈氂', event: '要斬',
quote: '六月壬寅,丞相屈氂下獄要斬。',
source: BIAO, witnesses: 2,
corroboration: {
what: '★ 另一卷同记,且同用「要斬」二字',
source: '《汉书》卷六·武帝纪第六',
quote: '六月,丞相屈氂下獄要斬,妻梟首。',
caveat:
'⚠ 单证(草庐 006.htm),未取第二见证。⚠ 是【另一卷】,引作旁证。⚠ 标点为本项目自断。' +
'★ 两卷同用「要斬」,可与晁错条互看:本表全篇只有这两个人拿到这两个字。',
},
caveat:
'⚠ 本格作「丞相屈氂」,而他拜的是【左丞相】(征和二年)。表自己前后不一,照录两处原文,不调和。' +
'★★ 这是本表第二个「要斬」—— 头一个是晁错(景帝三年正月壬子)。' +
'相隔六十四年,同一个词,同一张表。',
}),
E({
year: -89, era: '征和四年', eraRaw: '四', month: '六月', day: '丁巳',
office: '丞相', seat: 'chengxiang', name: '田千秋', person: '田千秋', from: '大鴻臚', event: '拜',
quote: '六月丁巳,大鴻臚田千秋為丞相。',
source: BIAO, witnesses: 2,
}),
// ────────────────────────────────────────────────────────────────────────
// 後元二年(前87)—— 武帝崩。本表覆盖到此为止。
// ────────────────────────────────────────────────────────────────────────
E({
year: -87, era: '後元二年', eraRaw: '二', month: null, day: '丁卯',
office: '大司馬大將軍', seat: 'taiwei', name: '霍光', person: '霍光', from: '侍中奉車都尉', event: '拜',
quote: '丁卯,侍中奉車都尉霍光為大司馬大將軍。',
source: BIAO, witnesses: 2,
caveat:
'★★【要紧异文:月份】草庐本作「【三月】丁卯」,ctext 作「【二月】丁卯」。' +
'★ 旁证(不裁决,只摆出来):《汉书》卷六·武帝纪正文作「二月,行幸盩厔五柞宮……' +
'丁卯,帝崩于五柞宮……三月甲申,葬茂陵」——【二月丁卯是武帝崩的那一天】,' +
'而三月甲申是葬日;丁卯到甲申恰十七日,与「二月末崩、三月中葬」相合。' +
'若作三月丁卯,则在二月丁卯之后整六十日、葬后四十余日,霍光受遗诏就说不通了。' +
'⚠★ 但这是【本项目的推算】,不是史料的裁断 —— 故 month 一律置 null,' +
'两说并陈,不替读者选。要坐实,得先按规矩把武帝纪也做两个见证。' +
'★ 又:太尉那一栏自建元二年官省,空了五十二年,到武帝断气这一天才又坐上人。',
variantUnresolved: { field: 'month', witnessA: '三月(草庐)', witnessB: '二月(ctext)' },
}),
E({
year: -87, era: '後元二年', eraRaw: '二', month: '二月', day: '乙卯',
office: '御史大夫', seat: 'yushi', name: '桑弘羊', person: '桑弘羊', from: '搜粟都尉', event: '拜',
quote: '二月乙卯,搜粟都尉桑弘羊為御史大夫,七年坐謀反誅。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「七年坐謀反誅」照录,不算 —— 那已是昭帝朝的事,【不在本表覆盖范围】。',
fateInline: { words: '七年坐謀反誅', quote: '二月乙卯,搜粟都尉桑弘羊為御史大夫,七年坐謀反誅。' },
}),
E({
year: -87, era: '後元二年', eraRaw: '二', month: null, day: null,
office: '車騎將軍', seat: 'jiangjun', name: '金日磾', person: '金日磾', from: '侍中駙馬都尉', event: '拜',
quote: '侍中駙馬都尉金日磾為車騎將軍,一年薨。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「一年薨」照录,不算。',
fateInline: { words: '一年薨', quote: '侍中駙馬都尉金日磾為車騎將軍,一年薨。' },
}),
E({
year: -87, era: '後元二年', eraRaw: '二', month: null, day: null,
office: '左將軍', seat: 'jiangjun', name: '上官桀', person: '上官桀', from: '太僕', event: '拜',
quote: '太僕上官桀為左將軍,七年反,誅。',
source: BIAO, witnesses: 2,
caveat: '⚠ 「七年反,誅」照录,不算 —— 昭帝朝事,不在覆盖范围。',
fateInline: { words: '七年反,誅', quote: '太僕上官桀為左將軍,七年反,誅。' },
}),
];
// ══════════════════════════════════════════════════════════════════════════
// 纯函数
// ══════════════════════════════════════════════════════════════════════════
// ── 纪年 ↔ 公历 ──
// 那一年是谁的天下、纪年怎么写。year: -154 → 「孝景三年」。
export function eraAt(year) {
const y = Number(year);
if (!Number.isInteger(y)) return null;
const e = ALL_ERAS.find((x) => y >= x.from && y <= x.to);
if (!e) return null;
const n = y - e.from + 1;
return {
rulerId: e.rulerId,
rulerName: e.rulerName,
eraKey: e.key,
eraName: e.name,
n,
// 表在改元那年写全,其余年只写数字 —— label 是【本项目补全的】完整写法
label: n === 1 ? e.raw : `${e.key}${numCn(n)}年`,
labelNote: '★ 这是本项目补全的写法:表在非改元年只写数字(如「三」)。',
raw: n === 1 ? e.raw : numCn(n),
seedOnly: !!e.seedOnly,
covered: y >= COVERAGE.years.from && y <= COVERAGE.years.to,
};
}
// 「孝景」+ 3 → -154。eraKey 见 RULERS[].eras[].key。
export function yearOfEra(eraKey, n) {
const e = ALL_ERAS.find((x) => x.key === eraKey);
if (!e) return null;
const k = Number(n);
if (!Number.isInteger(k) || k < 1 || k > e.count) return null;
return e.from + k - 1;
}
const CN = ['', '元', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
function numCn(n) {
if (n <= 10) return CN[n];
if (n < 20) return `十${CN[n - 10]}`;
return `${CN[Math.floor(n / 10)]}十${n % 10 ? CN[n % 10] : ''}`;
}
// ★★ 十月岁首的修正 —— CALENDAR_NOTE.suiShou 的可执行形式。
//
// ⚠ 为什么必须有这个函数而不能只写在注释里:year 字段长得就像公历年,
// 谁都会直接拿去减、拿去画时间轴。「建元二年十月窦婴免相」的 year 是 -139,
// 而那个十月【实际在公元前 140 年】。规矩写在注释里没用,写成函数才算数
// (与 decisions.js 的 assertNoGameMechanics 同性质)。
export function gregorianYearOf(entry) {
if (!entry || !Number.isInteger(entry.year)) return null;
const winter = entry.month === '十月' || entry.month === '十一月' || entry.month === '十二月';
// 太初元年(前104)夏五月「正歷,以正月為歲首」→ 此后十月不再是岁首,无须修正。
const beforeReform = entry.year < CALENDAR_NOTE.taichuYear;
if (winter && beforeReform) {
return {
year: entry.year - 1,
adjusted: true,
why:
`太初改历前以十月为岁首:${entry.era}的十月/十一月/十二月落在【前一个公历年】。` +
`本条 year=${entry.year} 是该汉年正月所在的公历年,其实际公历年是 ${entry.year - 1}。`,
};
}
return { year: entry.year, adjusted: false, why: '' };
}
// ── courtAt:那一年,你身边站着谁 ──
//
// ⚠ 这张表是【记事链】不是花名册:它只记变动。景帝三年那一行【没有丞相】,
// 因为丞相陶青是景帝二年八月拜的,那年他没动。→ 只能重放,不能查表。
// (与 decisions.js 的 replay() 同一个道理:状态量不可直接写,只能由链推出。)
//
// ★★【重放的前提是链完整 —— 链不完整的栏,重放出来的是假的】
// 这一条是本函数最要紧的设计,也是先写测试才发现的:
// · 三公(丞相/太尉·大司马/御史大夫)的链是全的(COVERAGE.sangong.complete),
// 故【可以】把上一年的在任者接下来。
// · 列将军的链【是破的】——「大將軍衞青為大司馬大將軍」,而卫青拜大将军那一笔表里根本没有;
// 窦婴景帝三年拜大将军之后,表也从没记过他何时去职。
// 若照三公那样接下来,courtAt(前100) 会一本正经地告诉你「大将军是窦婴」——
// 窦婴那时候早就不在了,只是这张表没说。★ 那不是数据少,那是【编出来的】。
// · 九卿同理:只抠了两处(见 COVERAGE.jiuqing)。
// → 故:三公【接】,列将军与九卿【只报表在那一年记了的,不接】。
//
// ⚠ 年内先后:表在很多格【不给月】,同一年的两笔谁先谁后【不可知】。
// 故本函数给的是【那一年在过这个位子上的人】(holders),不是「某月某日的那一个」。
// 景帝三年的御史大夫就是晁错 —— 哪怕他正月壬子就要斩了。他在场。这才是这个功能要的答案。
export function courtAt(year) {
const y = Number(year);
const era = eraAt(y);
if (!era) return null;
const single = new Set(['chengxiang', 'yushi']); // 单座:新任即代前任
const lists = { chengxiang: [], taiwei: [], yushi: [], jiangjun: [] };
const others = [];
const abolished = {};
const recOf = (e) => ({
office: e.office, name: e.name, person: e.person ?? null,
personUncertain: !!e.personUncertain,
since: e.year, sinceEra: e.era, month: e.month ?? null, day: e.day ?? null,
from: e.from ?? null, quote: e.quote, source: e.source,
witnesses: e.witnesses, caveat: e.caveat ?? '', left: null,
});
// ⚠ 只有确实是他才拿下来 —— 表里同官同名的坑见 PERSON_NOTE.homonym
const matches = (r, e) =>
!r.left && ((e.person && r.person && r.person === e.person) || (!e.person && r.name === e.name));
for (const e of GONGQING) {
if (e.year > y) break; // GONGQING 按年升序(assertGongqingInvariants 守着)
if (e.event === '官省') {
// ⚠【官省是【那个官】被裁,不是【那一栏】空了】:太尉官省之后,大司马照样坐在同一栏里。
// 把它记在栏上,courtAt(前110) 就会说「太尉栏:不载」——可太尉明明是【裁了】,
// 而那一栏坐着大司马卫青。故 abolished 按【官】记,不按栏记。(先写测试才发现的。)
abolished[e.office] = {
office: e.office, seat: e.seat ?? null, since: e.year, sinceEra: e.era,
quote: e.quote, inferred: !!e.yearInferred, source: e.source, caveat: e.caveat ?? '',
};
continue;
}
const list = e.seat ? lists[e.seat] : others;
if (e.event === '拜') {
if (e.seat && single.has(e.seat)) {
const prev = list.find((r) => !r.left);
if (prev) {
prev.left = {
year: e.year, era: e.era, event: '代', month: null, day: null,
quote: e.quote,
note: '⚠ 表【没有】单写他怎么去的 —— 由后任的拜官记录推出他此时已去。',
};
}
}
list.push(recOf(e));
continue;
}
if (TERMINAL_WORDS.has(e.event)) {
const r = list.find((x) => matches(x, e));
const left = {
year: e.year, era: e.era, event: e.event, month: e.month ?? null, day: e.day ?? null,
quote: e.quote, source: e.source, caveat: e.caveat ?? '',
};
if (r) r.left = left;
else {
// ★ 表只记了他的结局,没记他的拜 —— 赵绾就是这样:凭空出现在自己的死讯里。
list.push({
...recOf(e), since: null, sinceEra: null,
sinceNote:
'★★【表阙】:本表【没有】他拜此官的记录 —— 他是凭空出现在自己的结局里的。' +
'这正是顏師古说的「史之闕文,不可得知」。见 SHIGU_NOTE。',
left,
});
}
}
}
// 那一年在过这个位子上的人:拜于此年之前(或此年)、且去于此年之后(或此年、或根本没去)
const held = (r) => (r.since === null || r.since <= y) && (!r.left || r.left.year >= y);
// ⚠ 链不完整的栏不许接 —— 只报表在那一年【自己记了】的
const thisYearOnly = (r) => r.since === y || (r.left && r.left.year === y);
const seatOut = (id) => {
const holders = lists[id].filter(held);
const gone = Object.values(abolished).filter((a) => a.seat === id);
let status = 'held';
if (!holders.length) status = gone.length ? '省' : '不載';
return {
holders,
abolished: gone,
status,
note:
status === '省'
? `★ 这个位子【裁了】:${gone.map((a) => `${a.office}(${a.sinceEra}官省)`).join('、')}。不是没人,是没这个官。`
: status === '不載'
? '★ 表在这一年【没有】这个位子的在任记事。不载 ≠ 无人 —— 见 SHIGU_NOTE。'
: '',
};
};
return {
year: y,
era,
ruler: { id: era.rulerId, name: era.rulerName },
// 三公(照表的栏分,非制度判断 —— 见 SEATS.taiwei.note)
chengxiang: seatOut('chengxiang'),
taiwei: seatOut('taiwei'),
yushi: seatOut('yushi'),
// ★ 不接:链是破的。见本函数顶上那一段。
jiangjun: lists.jiangjun.filter(thisYearOnly),
others: others.filter(thisYearOnly),
coverage: {
sangong: COVERAGE.sangong.complete && era.covered,
jiangjun: false,
others: false,
inRange: era.covered,
carryForward:
'★ 三公【接了】上一年的在任者(链是全的);列将军与九卿【没接】,' +
'只报表在这一年自己记了的 —— 链不完整的栏,接出来的是编的。',
note: era.covered
? COVERAGE.sangong.note
: `⚠ ${era.label} 不在覆盖范围(${COVERAGE.yearsNote})。此处只有接续用的记事,【不许当数】。`,
},
withinYearNote:
'⚠ 表在很多格不给月份,同一年两笔的先后【不可知】。故这里给的是' +
'【那一年在过这个位子上的人】,不是「某月某日的那一个」—— 不许拿 holders 的顺序排先后。',
othersNote: COVERAGE.jiuqing.note,
};
}
// ── fateOf:这个人后来怎么了 ──
//
// ★ 下卷记了大量「免」「薨」「下獄死」「要斬」—— 这个函数就是把它们摊开。
// ⚠ 表有【两种】写下场的办法,必须都认:
// (1) 单独一笔,有年有月:「正月壬子,錯有罪要斬。」→ 年份是硬的。
// (2) 压在拜官句尾:「廷尉張湯為御史大夫,六年有罪自殺。」→ 年份是那个【不可计算】的记法。
// 故 fateOf 返回的 end.year 在第二种情况下【一律 null】—— 那不是偷懒,那是卷首第二条铁律。
export function fateOf(name) {
const q = String(name || '').trim();
if (!q) return null;
const hit = (e) =>
e.person === q || e.name === q || (e.nameVariants || []).includes(q);
const entries = GONGQING.filter(hit);
if (!entries.length) {
return {
query: q, found: false, entries: [],
note: '★ 本表【没有】这个人。⚠ 不载 ≠ 没这个人 —— 公卿年表只记公卿,' +
'你不在那个位子上,你的事不归它管(窦婴免相之后就是这样)。见 COVERAGE。',
};
}
const person = entries.find((e) => e.person)?.person || null;
const variants = [...new Set(entries.flatMap((e) => [e.name, ...(e.nameVariants || [])]).filter(Boolean))];
// 结局:先找单独记下来的终结事件(年份硬)
const term = entries.filter((e) => TERMINAL_WORDS.has(e.event) && e.event !== '官省');
let end = null;
if (term.length) {
const last = term[term.length - 1];
end = {
word: last.event, year: last.year, era: last.era, month: last.month ?? null, day: last.day ?? null,
office: last.office, quote: last.quote, source: last.source,
yearReliable: true,
caveat: last.caveat ?? '',
};
} else {
// 再找压在拜官句尾的
const inline = entries.filter((e) => e.fateInline);
if (inline.length) {
const last = inline[inline.length - 1];
end = {
word: last.fateInline.words, year: null, era: null, month: null, day: null,
office: last.office, quote: last.fateInline.quote, source: last.source,
yearReliable: false,
yearNote:
'★ 年份【算不出来】:下场压在拜官句尾,只给了「若干年」。' +
'实测三个样本(周亚夫/陶青/刘舍)两种读法互斥,故本表一律不算。见卷首「不许复活」第二条。',
caveat: last.fateInline.caveat ?? '',
};
}
}
// 两个见证打架而本表不裁的(直不疑)
const unresolved = entries.filter((e) => e.variantUnresolved).map((e) => ({
year: e.year, era: e.era, office: e.office, ...e.variantUnresolved, caveat: e.caveat,
}));
return {
query: q, found: true, person, variants,
entries: entries.map((e) => ({
year: e.year, era: e.era, month: e.month ?? null, day: e.day ?? null,
office: e.office, from: e.from ?? null, event: e.event,
quote: e.quote, source: e.source, witnesses: e.witnesses, caveat: e.caveat ?? '',
seed: !!e.seed,
})),
end,
unresolved,
note:
end
? ''
: '⚠ 本表【没记他怎么完的】。不载 ≠ 善终 —— 表只管你在位子上那段。' +
'(田蚡「薨」有记,窦婴免相后一个字都没有 —— 这就是这张表最冷的地方。)',
};
}
// ══════════════════════════════════════════════════════════════════════════
// ★★ eventsAt(year) —— 那一年,表里记了什么事
//
// courtAt 给的是【谁在场】(状态),eventsAt 给的是【那一年发生了什么】(事件)。
// 两个是不同的问题,都得有人答:
// courtAt(-154) → 丞相陶青(景帝二年拜的,那年没动,故那一格里没有他)
// eventsAt(-154) → 正月壬子,錯有罪要斬。—— 陶青不在这里,因为他那年没出事。
//
// ★★【一个人在你眼前被腰斩,那就是大事件】—— 这就是本函数存在的理由:
// 表已经把「免/薨/下獄死/要斬/自殺/誅」这七八个字分得清清楚楚(见 EVENTS),
// 本函数只是把它们按年摊开。⚠ 它【一个字都不新增】,只是换个索引读同一份 GONGQING。
//
// ⚠⚠【本函数不生成关口】—— 这一条是本文件文件头逐字写的,别在这儿加:
// 「但本文件【不生成关口】,它只提供事实。生成是别人的事,别在这儿加。」
// ⇒ 本函数返回【事实】:那一年表记了哪几笔、哪几笔是有人完了。
// 「景帝三年 + 中大夫 + 晁错在场 ⇒ 这是个关口」——那一步的判断【不在这儿】。
//
// ★★★【空 ≠ 没事】—— 这是本函数最要紧的一句,也是「零命中比单证更危险」的同一族:
// events 是空的,意思是【表在这一年没记事】,不是【那一年太平无事】。
// 本表只记【变动】,且九卿栏只抠了两处(COVERAGE.jiuqing)。
// ⇒ 故 empty 时必须给 emptyNote,【绝不许】让前端读成「这一年没有大事」。
// ══════════════════════════════════════════════════════════════════════════
export function eventsAt(year) {
const y = Number(year);
const era = eraAt(y);
if (!era) return null;
const rows = GONGQING.filter((e) => e.year === y);
const out = rows.map((e) => {
const def = EVENTS.find((x) => x.word === e.event) || null;
const g = gregorianYearOf(e);
return {
era: e.era,
eraRaw: e.eraRaw,
month: e.month ?? null,
day: e.day ?? null,
office: e.office,
seat: e.seat ?? null,
name: e.name ?? null,
person: e.person ?? null,
personUncertain: !!e.personUncertain,
from: e.from ?? null,
to: e.to ?? null,
// ★ 用【表自己的字】。✗ 不许翻译成「离职/任命」——那样这张表最值钱的东西
// (一个人【怎么】完的)就没了。见 EVENTS 那段注。
event: e.event,
eventGloss: def?.gloss || '',
// ★ 有人完了(免/薨/卒/死/自殺/要斬/下獄死/誅),不含「官省」(那是官没了,不是人完了)
terminal: TERMINAL_WORDS.has(e.event) && e.event !== '官省',
abolition: e.event === '官省',
quote: e.quote,
source: e.source,
witnesses: e.witnesses,
caveat: e.caveat ?? '',
corroboration: e.corroboration ?? null,
variantUnresolved: e.variantUnresolved ?? null,
seed: !!e.seed,
// ★ 年份是推断不是原文的,标出来(灌婴/周亚夫那两条「官省」)
yearInferred: !!e.yearInferred,
// ★★ 十月岁首:year 长得就像公历年,谁都会拿去减 —— 故把修正结果一起给出来。
gregorian: g,
};
});
const terminal = out.filter((e) => e.terminal);
return {
year: y,
era,
ruler: { id: era.rulerId, name: era.rulerName },
events: out,
// ★ 这一年【有人完了】的那几笔 —— 「一个人在你眼前被腰斩」的那个「腰斩」。
terminal,
// 「官省」:这个官【裁了】。★ 太尉一栏大半个世纪都是这两个字。
abolished: out.filter((e) => e.abolition),
covered: era.covered,
coverage: {
inRange: era.covered,
sangong: COVERAGE.sangong.complete && era.covered,
jiuqing: false,
jiangjun: false,
note: era.covered
? '★ 三公(丞相/太尉·大司马/御史大夫)这三栏在本表覆盖年内【一格没落】,' +
'故三公的事是全的。⚠ 九卿只抠了两处、列将军是散记(见 othersNote)——' +
'那两栏【没记 ≠ 没发生】。'
: `⚠ ${era.label} 不在覆盖范围(${COVERAGE.yearsNote})。此处只有接续用的记事,【不许当数】。`,
},
// ★★★ 这一句是本函数最要紧的一句,见函数头。
emptyNote: out.length
? ''
: '★★【表在这一年没记事 ≠ 那一年没有事】。本表是【记事链】,它只记【变动】:' +
'那一年三公没换人,这一格就是空的 —— 而不是那一年天下太平。' +
'(景帝三年那一格里没有丞相,正因为陶青是前一年拜的,他那年没动。)' +
'⚠ 又:九卿栏本表只抠了两处,列将军栏表自己就阙。⇒ 空【不是结论】。',
withinYearNote:
'⚠ 表在很多格【不给月】,同一年两笔的先后【不可知】。' +
'★ events 的顺序是【本表的录入顺序】,不是发生顺序 —— 不许拿它排先后、不许画时间轴。',
othersNote: COVERAGE.jiuqing.note,
calendarNote: CALENDAR_NOTE.suiShou,
};
}
// ── 不变量自检:规矩写在注释里没用,写成断言才算数 ──
// (与 decisions.js 的 assertInvariants() 同性质。返回违规列表,空数组 = 通过。)
//
// ⚠ dynasties 参数:TABLE_SCOPE.dynastyIds 的核对要拿 DYNASTIES 才验得了,而本文件
// 【绝不 import dynasties.js】(它是纯常量表,无依赖,且另一条线正在改它)。
// ⇒ 由调用方把 DYNASTIES 递进来(services/court.js 递)。不给就跳过那一段 ——
// ⚠ 跳过【不是通过】:court.js 那边另有一条断言钉着这件事。
export function assertGongqingInvariants({ dynasties = null } = {}) {
const bad = [];
const seatIds = new Set(SEATS.map((s) => s.id));
const eraKeys = new Set(ALL_ERAS.map((e) => e.key));
// 纪年表自洽:count 必须与 from/to 对得上,且各段首尾相接、不重不漏
for (const r of RULERS) {
for (const e of r.eras) {
if (e.to - e.from + 1 !== e.count) bad.push(`${r.name} ${e.key}: count=${e.count} 与 from/to 不符`);
}
}
const spans = ALL_ERAS.slice().sort((a, b) => a.from - b.from);
for (let i = 1; i < spans.length; i++) {
if (spans[i].from !== spans[i - 1].to + 1) {
bad.push(`纪年不连续:${spans[i - 1].key}(到${spans[i - 1].to}) → ${spans[i].key}(从${spans[i].from})`);
}
}
let prevYear = -Infinity;
for (const e of GONGQING) {
const tag = `${e.era}/${e.office}/${e.name ?? '—'}/${e.event}`;
// courtAt 靠年份升序做重放,乱序会静默算错
if (e.year < prevYear) bad.push(`${tag}: GONGQING 必须按年升序 —— courtAt 靠它重放`);
prevYear = e.year;
if (!Number.isInteger(e.year)) bad.push(`${tag}: year 必须是整数(负 = 公元前)`);
if (!e.era || !e.eraRaw) bad.push(`${tag}: era / eraRaw 都得有 —— 补全的和原文的分开放`);
if (!EVENT_WORDS.has(e.event)) bad.push(`${tag}: event「${e.event}」不在 EVENTS 里 —— 只许用表自己的字`);
if (!e.quote) bad.push(`${tag}: 缺 quote —— 正文是推演,注是原文,没有原文就不许有这条`);
if (e.source !== BIAO) bad.push(`${tag}: source 必须是本卷;旁证请放 corroboration`);
if (e.seat && !seatIds.has(e.seat)) bad.push(`${tag}: 未知 seat「${e.seat}」`);
if (e.seat && SEAT_BY_OFFICE[e.office] !== e.seat) {
bad.push(`${tag}: office「${e.office}」与 seat「${e.seat}」对不上(见 SEATS)`);
}
// ★ 反过来也得管:三公的官【漏了 seat】会被 courtAt 扔进 others,
// 于是 COVERAGE.sangong 声称的「全覆盖」就成了假话,而且【一声不响】。
if (!e.seat && SEAT_BY_OFFICE[e.office]) {
bad.push(
`${tag}: office「${e.office}」属于座次「${SEAT_BY_OFFICE[e.office]}」却没标 seat —— ` +
'courtAt 会把它扔进 others,三公的「全覆盖」当场变成假话'
);
}
// ★ 引文里【一个字的顏師古注都不许有】—— 他是唐人,班固是东汉人
for (const w of ['師古曰', '應劭曰', '如淳曰', '晉灼曰', '臣瓚曰', '張晏曰', '文穎曰']) {
if (String(e.quote).includes(w)) bad.push(`${tag}: quote 里混进了后人注「${w}」—— 必须剔`);
}
// ★ 年份不许从「N年」算出来:凡 quote 里带「N年遷/免/卒…」的,本表都只照录
if (/[一二三四五六七八九十]+年(遷|免|卒|薨|死|徙|貶|賜告|老病免|病免|有罪自殺)/.test(e.quote) && !e.caveat) {
bad.push(`${tag}: quote 带「若干年」记法却没有 caveat 说明「照录不计算」—— 见卷首第二条`);
}
// ★★ 姓名不具就必须是 null,不许猜 —— 但【由记事链解出来的不算猜】。
// 本表的行文惯例:头回出现给全名(「魏其侯竇嬰為丞相」),此后只用名(「丞相嬰免」)。
// → 一个单字名要能给 person,必须在【本表之前的某一笔】里,同一个 person 出过全名。
// 解得出 = 有据;解不出 = 猜。这条差别不能靠自觉,得算出来。(先写测试才逼出来的:
// 原先一刀切禁掉单字名 + person,把「丞相嘉薨」= 申屠嘉这种【有据的】也误伤了。)
if (e.name && [...e.name].length === 1 && e.person) {
const introduced = GONGQING.some(
(x) => x.year <= e.year && x.person === e.person && [...(x.name || '')].length > 1
);
if (!introduced) {
bad.push(
`${tag}: 单字人名「${e.name}」给了 person「${e.person}」,` +
'可本表【在此之前没有任何一笔给过他全名】—— 那就是猜的(见 PERSON_NOTE / SHIGU_NOTE)'
);
}
}
// ★ 两个见证打架的,不许硬给结论
if (e.variantUnresolved && !e.caveat) bad.push(`${tag}: 有 variantUnresolved 却没 caveat`);
// ★ 推断出来的年份必须标出来
if (e.yearInferred && !/推断|推算/.test(e.caveat || '')) {
bad.push(`${tag}: yearInferred=true 却没在 caveat 里说清楚这是推断`);
}
// ★ 只有一个见证的必须说
if (e.witnesses !== 2 && !e.caveat) bad.push(`${tag}: witnesses=${e.witnesses} 却没有 caveat 交代`);
}
// 覆盖范围不许自吹:三公说全覆盖,就不许有落在范围外的三公条目冒充
for (const e of GONGQING) {
const inRange = e.year >= COVERAGE.years.from && e.year <= COVERAGE.years.to;
if (!inRange && !e.seed) bad.push(`${e.era}/${e.office}: 落在覆盖范围外却没标 seed`);
}
return bad;
}
export { ALL_ERAS, SEAT_BY_OFFICE, TERMINAL_WORDS };