T1 scripts/birth_sky.py: real alt/az of the seven visible grahas (swe.calc_ut + swe.azalt, geometric altitude, azimuth converted from swe's south origin to north-east) and Yale BSC5 stars V<=4.0 (IAU 1976 precession); phase day / twilight_morning / twilight_evening / night. Thin POST /api/birth_sky registration in do_POST (no new handler method, no __new__). T2 GET /api/birth-sky?subject=: birth truth read server side through loadSubjectBirth + prepareChartViewProfile; zod contract; no billing, no writes, no birth data in the response. T3/T4 lib/birth-sky: sentence table, pure Canvas 2D drawBirthSky (night / paper palettes from DESIGN tokens, east on the left, serif headline with 1.5 s font wait), PNG export via navigator.share or Blob + a.download. Chart page prefetches and paints the cover once the chart is ok; the header button appears only when the PNG is ready. Dialog is a lazy chunk. T7 DESIGN §17, VOICE, CONTEXT glossary, CHANGELOG, real-device checklist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
74 lines
3.2 KiB
TypeScript
74 lines
3.2 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import test from "node:test";
|
||
|
||
import {
|
||
BIRTH_SKY_HEADLINES,
|
||
birthSkyFactLine,
|
||
birthSkyFootnote,
|
||
birthSkyHeadline,
|
||
octantName,
|
||
} from "../src/lib/birth-sky/sentence.ts";
|
||
import type { BirthSkyOk } from "../src/lib/birth-sky/contract.ts";
|
||
import { birthSkyGolden, goldenSky } from "./birth-sky-test-support.ts";
|
||
|
||
test("the fictional sample (07:40) reads exactly as the brief", () => {
|
||
const sky = goldenSky("day");
|
||
assert.equal(birthSkyGolden.times.day, "07:40");
|
||
assert.equal(birthSkyHeadline(sky), "星星都在,只是还看不见。");
|
||
assert.equal(birthSkyFactLine(sky), "太阳在东,月亮在脚下,火星正悬在东南");
|
||
});
|
||
|
||
test("each phase takes its one headline from the four-line table", () => {
|
||
assert.deepEqual(Object.values(BIRTH_SKY_HEADLINES), [
|
||
"星星都在,只是还看不见。",
|
||
"天快亮了,星星正一颗颗退场。",
|
||
"天刚暗下来,星星正一颗颗出来。",
|
||
"那一刻,头顶就是这片天。",
|
||
]);
|
||
assert.equal(birthSkyHeadline(goldenSky("day")), BIRTH_SKY_HEADLINES.day);
|
||
assert.equal(birthSkyHeadline(goldenSky("twilight_morning")), "天快亮了,星星正一颗颗退场。");
|
||
assert.equal(birthSkyHeadline(goldenSky("twilight_evening")), "天刚暗下来,星星正一颗颗出来。");
|
||
assert.equal(birthSkyHeadline(goldenSky("night")), "那一刻,头顶就是这片天。");
|
||
});
|
||
|
||
test("outside daytime the fact line never names the Sun", () => {
|
||
for (const phase of ["twilight_morning", "twilight_evening", "night"] as const) {
|
||
const line = birthSkyFactLine(goldenSky(phase));
|
||
assert.doesNotMatch(line, /太阳/);
|
||
assert.match(line, /^月亮在(脚下|北|东北|东|东南|南|西南|西|西北)/);
|
||
assert.ok(line.split(",").length <= 2, line);
|
||
}
|
||
});
|
||
|
||
test("fact line combinations: moon above, no visible planet, highest planet wins", () => {
|
||
const base = goldenSky("night");
|
||
const planets = (overrides: Record<string, [number, number]>): BirthSkyOk["planets"] => base.planets.map((planet) => {
|
||
const next = overrides[planet.id];
|
||
return next ? { ...planet, altitude: next[0], azimuth: next[1] } : { ...planet, altitude: -10 };
|
||
});
|
||
assert.equal(
|
||
birthSkyFactLine({ phase: "night", planets: planets({ moon: [40, 225], jupiter: [20, 10], venus: [35, 300] }) }),
|
||
"月亮在西南,金星正悬在西北",
|
||
);
|
||
assert.equal(birthSkyFactLine({ phase: "night", planets: planets({}) }), "月亮在脚下");
|
||
assert.equal(
|
||
birthSkyFactLine({ phase: "day", planets: planets({ sun: [10, 225], saturn: [5, 170] }) }),
|
||
"太阳在西南,月亮在脚下,土星正悬在南",
|
||
);
|
||
});
|
||
|
||
test("eight-point compass rounds to the nearest 45 degrees, north at both ends", () => {
|
||
assert.equal(octantName(0), "北");
|
||
assert.equal(octantName(22.4), "北");
|
||
assert.equal(octantName(22.6), "东北");
|
||
assert.equal(octantName(84.69), "东");
|
||
assert.equal(octantName(117.24), "东南");
|
||
assert.equal(octantName(191.14), "南");
|
||
assert.equal(octantName(350), "北");
|
||
});
|
||
|
||
test("the reported-time footnote appears only when the time was not adopted", () => {
|
||
assert.equal(birthSkyFootnote({ timeBasis: "reported" }), "按你填写的时间");
|
||
assert.equal(birthSkyFootnote({ timeBasis: "adopted" }), null);
|
||
});
|