Files
Jyotisha/frontend/tests/panchanga-names-zh.test.ts
T
Jesse_ChenandClaude Opus 5.5 dd8912fea0 fix(ephemeris): alias the engine's three panchanga spellings to the table
Coordinator ruling (brief author, 2026-09-26): Aindra -> Indra (因陀罗),
Variyana -> Variyan (安逸), Dhanishtha -> Dhanishta (虚宿). Spelling aliases
only; the brief's table and its typed test copy are unchanged. BLOCKED item
struck through, PROGRESS / checklist / task index updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-09-26 06:58:41 +08:00

125 lines
7.0 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
KARANA_ZH,
NAKSHATRA_ZH,
TITHI_ZH,
YOGA_ZH,
normalizePanchangaName,
panchangaNameZh,
splitTithiName,
} from "../src/lib/panchanga-names-zh.ts";
/* The brief's table (TASK-people-ephemeris-ui-20260926 「中文名对照表」),
typed a second time here on purpose: the module and this list must agree
entry for entry, so an edit to either one alone fails. */
const BRIEF_TITHI: ReadonlyArray<readonly [string, string]> = [
["Pratipada", "初一"], ["Dwitiya", "初二"], ["Tritiya", "初三"], ["Chaturthi", "初四"],
["Panchami", "初五"], ["Shashthi", "初六"], ["Saptami", "初七"], ["Ashtami", "初八"],
["Navami", "初九"], ["Dashami", "初十"], ["Ekadashi", "十一"], ["Dwadashi", "十二"],
["Trayodashi", "十三"], ["Chaturdashi", "十四"],
];
const BRIEF_NAKSHATRA: ReadonlyArray<readonly [string, string]> = [
["Ashwini", "娄宿"], ["Bharani", "胃宿"], ["Krittika", "昴宿"], ["Rohini", "毕宿"],
["Mrigashira", "觜宿"], ["Ardra", "参宿"], ["Punarvasu", "井宿"], ["Pushya", "鬼宿"],
["Ashlesha", "柳宿"], ["Magha", "星宿"], ["Purva Phalguni", "张宿"], ["Uttara Phalguni", "翼宿"],
["Hasta", "轸宿"], ["Chitra", "角宿"], ["Swati", "亢宿"], ["Vishakha", "氐宿"],
["Anuradha", "房宿"], ["Jyeshtha", "心宿"], ["Mula", "尾宿"], ["Purva Ashadha", "箕宿"],
["Uttara Ashadha", "斗宿"], ["Shravana", "女宿"], ["Dhanishta", "虚宿"], ["Shatabhisha", "危宿"],
["Purva Bhadrapada", "室宿"], ["Uttara Bhadrapada", "壁宿"], ["Revati", "奎宿"],
];
const BRIEF_YOGA: ReadonlyArray<readonly [string, string]> = [
["Vishkambha", "支柱"], ["Priti", "喜爱"], ["Ayushman", "长寿"], ["Saubhagya", "好运"],
["Shobhana", "光彩"], ["Atiganda", "大险"], ["Sukarma", "善业"], ["Dhriti", "坚持"],
["Shula", "刺痛"], ["Ganda", "险结"], ["Vriddhi", "增长"], ["Dhruva", "恒定"],
["Vyaghata", "打击"], ["Harshana", "欢喜"], ["Vajra", "金刚"], ["Siddhi", "成就"],
["Vyatipata", "灾变"], ["Variyan", "安逸"], ["Parigha", "阻碍"], ["Shiva", "吉祥"],
["Siddha", "圆满"], ["Sadhya", "可成"], ["Shubha", "吉善"], ["Shukla", "清白"],
["Brahma", "梵天"], ["Indra", "因陀罗"], ["Vaidhriti", "分离"],
];
const BRIEF_KARANA: ReadonlyArray<readonly [string, string]> = [
["Bava", "巴瓦"], ["Balava", "巴拉瓦"], ["Kaulava", "考拉瓦"], ["Taitila", "泰提拉"],
["Garija", "加拉"], ["Vanija", "瓦尼贾"], ["Vishti", "毗湿提"], ["Shakuni", "沙昆尼"],
["Chatushpada", "四足"], ["Naga", "蛇"], ["Kimstughna", "金斯图格纳"],
];
test("the module tables are exactly the brief's tables, nothing added or dropped", () => {
assert.deepEqual(
Object.entries(TITHI_ZH).sort(),
[...BRIEF_TITHI, ["Purnima", "望日(满月)"], ["Amavasya", "朔日(新月)"]].sort(),
);
assert.deepEqual(Object.entries(NAKSHATRA_ZH).sort(), [...BRIEF_NAKSHATRA].sort());
assert.deepEqual(Object.entries(YOGA_ZH).sort(), [...BRIEF_YOGA].sort());
assert.deepEqual(Object.entries(KARANA_ZH).sort(), [...BRIEF_KARANA].sort());
assert.equal(Object.keys(NAKSHATRA_ZH).length, 27);
assert.equal(Object.keys(YOGA_ZH).length, 27);
assert.equal(Object.keys(KARANA_ZH).length, 11);
});
test("every tithi reads 白分 / 黑分 + day, full and new moon keep their own label", () => {
for (const [name, day] of BRIEF_TITHI) {
assert.equal(panchangaNameZh("tithi", `Shukla ${name}`), `白分${day}`, name);
assert.equal(panchangaNameZh("tithi", `Krishna ${name}`), `黑分${day}`, name);
}
assert.equal(panchangaNameZh("tithi", "Shukla Purnima"), "望日(满月)");
assert.equal(panchangaNameZh("tithi", "Krishna Amavasya"), "朔日(新月)");
assert.equal(panchangaNameZh("tithi", "Purnima"), "望日(满月)");
assert.equal(panchangaNameZh("tithi", "Amavasya"), "朔日(新月)");
// A bare day name has no fortnight; it is not guessed into one.
assert.equal(panchangaNameZh("tithi", "Dashami"), null);
});
test("every nakshatra, yoga and karana in the brief resolves", () => {
for (const [name, zh] of BRIEF_NAKSHATRA) assert.equal(panchangaNameZh("nakshatra", name), zh, name);
for (const [name, zh] of BRIEF_YOGA) assert.equal(panchangaNameZh("yoga", name), zh, name);
for (const [name, zh] of BRIEF_KARANA) assert.equal(panchangaNameZh("karana", name), zh, name);
});
test("the three listed aliases resolve to the same entry", () => {
assert.equal(panchangaNameZh("karana", "Gara"), "加拉");
assert.equal(panchangaNameZh("karana", "Garija"), "加拉");
assert.equal(panchangaNameZh("tithi", "Shukla Dvitiya"), "白分初二");
assert.equal(panchangaNameZh("tithi", "Krishna Dwitiya"), "黑分初二");
assert.equal(panchangaNameZh("tithi", "Shukla Shashti"), "白分初六");
assert.equal(panchangaNameZh("tithi", "Krishna Shashthi"), "黑分初六");
});
test("the engine's own spellings reach the existing entries (coordinator ruling 2026-09-26)", () => {
// `scripts/muhurta.py` spells these three differently from the brief's table.
// 原值:6de277a4 里断言这三个引擎拼法返回 null(照写梵文、待裁决)。
// 新值:映射到表内既有条目。
// 原因:协调方 2026-09-26 以任务书作者身份裁定补拼写别名,译名不变。
assert.equal(panchangaNameZh("yoga", "Aindra"), "因陀罗");
assert.equal(panchangaNameZh("yoga", "Indra"), "因陀罗");
assert.equal(panchangaNameZh("yoga", "Variyana"), "安逸");
assert.equal(panchangaNameZh("yoga", "Variyan"), "安逸");
assert.equal(panchangaNameZh("nakshatra", "Dhanishtha"), "虚宿");
assert.equal(panchangaNameZh("nakshatra", "Dhanishta"), "虚宿");
// An alias stays inside its own limb.
assert.equal(panchangaNameZh("nakshatra", "Aindra"), null);
});
test("case, spaces and hyphens are ignored", () => {
assert.equal(normalizePanchangaName("Uttara-Bhadra pada"), "uttarabhadrapada");
assert.equal(panchangaNameZh("nakshatra", "uttara bhadrapada"), "壁宿");
assert.equal(panchangaNameZh("nakshatra", "UTTARA-BHADRAPADA"), "壁宿");
assert.equal(panchangaNameZh("nakshatra", "PurvaPhalguni"), "张宿");
assert.equal(panchangaNameZh("tithi", "shukla-purnima"), "望日(满月)");
assert.equal(panchangaNameZh("karana", " vishti "), "毗湿提");
});
test("a name the table does not know returns null so the caller keeps the engine name", () => {
assert.equal(panchangaNameZh("nakshatra", "Abhijit"), null);
assert.equal(panchangaNameZh("yoga", ""), null);
assert.equal(panchangaNameZh("karana", "Unknown"), null);
// A yoga name is not looked up in another limb's table.
assert.equal(panchangaNameZh("nakshatra", "Vriddhi"), null);
});
test("tithi split separates the fortnight from the day", () => {
assert.deepEqual(splitTithiName("Shukla Purnima"), { paksha: "shukla", name: "purnima" });
assert.deepEqual(splitTithiName("Krishna Amavasya"), { paksha: "krishna", name: "amavasya" });
assert.deepEqual(splitTithiName("Navami"), { paksha: null, name: "navami" });
});