// 汉·公卿年表 —— 「我在某朝为官,身边站着谁」。 // // 定位:与 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 };