Staging's backend gate (run 6338) failed three budgets: handler method count 218 > 217, JyotishAPIHandler.__new__ sites 6 > 5, and the scripts/ root .py count. The package and facts code now live in scripts/annual_service/ (package.py, report_facts.py, api.py with compute_annual_package / compute_annual_report_facts and AnnualServiceInputError). jyotish_api_server.py registers /api/annual mode=package, /api/annual/package and /api/annual/report_facts thinly through a module-level _annual_service helper, like /api/chart_points, with no handler methods: back to 217. Tests call the module functions and keep a thin-registration test; no cap or baseline changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
9.2 KiB
年度数据包 v1(annual_package_version = 1)
依据:Gitea #152 裁定(jesse 2026-11-01 确认)「落地前要先做的四件事」。这是 #153(年运报告)和 #154(入口遮罩页)的数据接口。只做计算和数据搬运,不含解读文字,不建候选窗口。
入口
POST /api/annual,请求体加"mode": "package";或POST /api/annual/package(同一个处理函数)。"detail": "slim"(#154 遮罩用):只回报告范围、每张年盘的有效期、生日分界和块状态,几 KB;不回运限行、行运和生产器数据。默认"full"。- 年运报告的事实与结论卡:
POST /api/annual/report_facts(同样的请求体,加必填的birth_time_confirmed),见annual-report-v1.md。 - 不带
mode的/api/annual仍返回原来的基础太阳返照报告(report),形状不变;只多传了node_mode(默认 mean,结果不变)。 - 代码:
scripts/annual_service/package.py(normalize_request、build_annual_package);请求解析与入口在scripts/annual_service/api.py(compute_annual_package、compute_annual_report_facts,输入错误AnnualServiceInputError映射为 400),scripts/jyotish_api_server.py只做薄注册(_annual_service,与/api/chart_points同一做法)。前端合同:frontend/src/lib/annual-package-contract.ts(zod + 映射,无界面)。
请求字段
| 字段 | 说明 |
|---|---|
year month day hour minute second |
出生时间,秒会传到所有生产器 |
lat lon tz |
出生地;tz 缺省时按经纬度推算 |
timezone_id / timezone |
可选 IANA 时区;有则返照本地时间、报告范围按该时区(含夏令时) |
ayanamsa |
岁差,默认同其他接口 |
node_mode |
mean / true |
report_range |
{kind, year?, start_date?},kind 为 birthday_year(生日年度)/ calendar_year(公历年)/ next_12_months(未来十二个月) |
observation_date |
报告观测日期,默认今天;next_12_months 不给 start_date 时从这天起 |
return_location |
{mode: "birth_place"}。v1 只开放出生地,其它值返回 400;字段保留,以后加所在地对照不改形状 |
响应结构
{success, endpoint: "annual", mode: "package", annual_package_version: 1, package},package 的键序:
schema=jyotish.annual_package.v1、annual_package_versionparameters:出生时刻(UTC、出生固定偏移下的本地时间)、岁差、交点、宫制、回归地点、观测日期、本命盘经度(natal_chart)report_range:报告范围year_charts[]:覆盖报告范围的每张年盘natal_periods:vimshottari、narayana(legacy+rath)slow_transits:木星、土星、罗睺、计都independent_annual_views.tithi_pravesha:独立年度视角computation_boundaries:时间语义与计算边界的说明not_included:没放进来的项和原因block_index:所有块的路径、状态、生产器、原因;blocked_count
块状态
每个块都有 status:computed / blocked / not_applicable,以及 producer;非 computed 一定有 reason。生产器原有的状态(如 partial_verified、parameter_sensitive)放在 producer_status,不提升。生产器报错、返回 blocked、两个年运生产器结果冲突(producer_conflict:...,两个值都在 conflict_values)都写成 blocked。某个块 blocked 不影响整个响应。
时间语义(四种时间分开表示)
所有边界都是一个“时刻”:{utc, local, utc_offset, timezone_id, jd_ut}。local 用回归地点的时区(v1 即出生地;给了 IANA 就按当时的规则取偏移,否则用出生固定偏移)。出生时刻本身用出生固定偏移显示,因为排盘按这个偏移读出生时钟(例:上海 1986–1991 年有夏令时)。
- 报告范围
report_range:start含、end不含。- 生日年度:本次太阳返照到下次太阳返照;
- 公历年:当地 1 月 1 日 0 点到次年 1 月 1 日 0 点;
- 未来十二个月:
start_date当地 0 点到十二个日历月后(月底按月末截)。 year_chart_years列出与范围重叠的年盘,跨生日时是相邻两张;crosses_birthday、birthday_boundaries给出换年盘的时刻。observation:观测日期、在范围内 / 前 / 后,回看起点(默认三个月)。
- 年盘有效期
year_charts[].validity:本次返照(含)到下次返照(不含),end_source = next_solar_return;in_report_range是与报告范围的交集。 - 各运限自己的年制:Mudda 365.25 天、Patyayini 固定 365.25 天,都从返照时刻起;Vimshottari 365.25 天,从出生时刻起;Narayana 在年龄轴上,日期 = 出生时刻 + 年龄 × 365.25 天(与引擎算当前年龄的口径一致)。各行保留原始起止,
in_report_range是裁到报告范围的部分,不拉伸到返照年(返照年约 365.256 天,Mudda / Patyayini 的年末比下次返照早几分钟)。 - 计算边界:生产器在本地时钟上加天数的(Mudda、Patyayini、Vimshottari),按“从锚点时刻起经过的时间”换成 UTC,跨夏令时也对。月盘第 12 张止于下次返照。
候选窗口不在本期,由 #153 用上面这些时刻去建。
各块由谁计算
| 块 | 生产器 |
|---|---|
| 年盘、上升及上升主、宫位 | cmd_solar_return → solar_return.solar_return_full_report(结果交给 annual_tajika_pack.build_annual_tajika_pack) |
| Muntha、Muntha 主落宫 | solar_return._muntha_from_natal_ascendant(经年运包);落宫按年盘整宫数 |
| 年主 | tajika_year_lord.select_annual_year_lord(Panchadhikari,经年运包冲突门) |
| 年度力量 | tajika.calc_tajika_strength_layers:Panchavargiya、Harsha Bala;不是本命 Shadbala |
| Sahams | tajika.calc_all_sahams(原样保留公式来源与昼夜依据) |
| Tajika Yoga | tajika_named_yoga_authority(只有结构,不翻译成事件) |
| Tripataki | annual_native.tripataki_chakra |
| Mudda | tajika_mudda.annual_mudda_dasha(出生月宿起运,与年主无关) |
| Patyayini | annual_native.patyayini.build_native_patyayini(tajika-annual skill 指定的年度原生入口;不用 extended_dashas.py 的等长旧函数),输入八点的经度一并给出 |
| Tajika 月盘 | annual_native.monthly_charts.build_monthly_charts:太阳每前进 30° 一张,不是公历月,也不是月亮回归盘 |
| Tithi Pravesha | annual_native.tithi_pravesh.build_tithi_pravesh,独立单列:起点规则不同于太阳回归,地点用出生地和出生固定偏移(生产器自己的规则);找到几个候选就列几个,不替用户选 |
| Vimshottari MD/AD/PD | jyotish_engine.cmd_dasha(出生时刻起运) |
| Narayana | narayana_dasha.narayana_dasha_full_report(产品现用的旧算法,used_by_product_timeline: true)和 narayana_rath(Sanjay Rath 版,占星师口径,并列展示,false);只列与范围重叠的 MD / AD |
| 慢星行运 | 位置取 jyotish_engine._calc_sidereal_planets_for_jd(与本命盘同一参考系,跟随交点和岁差);每天 12:00 UT 取样,再二分到 1 秒以内。给出:范围起点的位置(从上升和月亮各算宫)、换宫、停滞、与本命十点(上升 + 九曜)的精确合相经过及次数、木星 / 土星的逆行回路(停滞时刻与经度、回扫弧段、前后影区进出时刻、落在弧段内的本命点及每次经过) |
本期修的参数缺口(BUG-1501)
solar_return.calc_solar_return_chart 排出生盘和年盘时交点口径写死为 mean,node_mode=true 传不到年盘,年盘罗睺 / 计都和 Tripataki 总是平均交点。现在 calc_solar_return_chart / solar_return_full_report 接受 node_mode(默认 mean),cmd_solar_return 和 cmd_tajika 都会传。
没放进来的(not_included)
- Varsha Narayana:本仓没有原生生产器(上游 yinduzhanxing 有,未同步)。
- Yoga / Nakshatra Pravesha:上游只有研究版,裁定不进报告。
- 外部引擎回放(PyJHora):只在完整数据版的子进程里跑,这里不执行。
- 所在地返照:v1 只开放出生地。
- 行运的 BAV / SAV、Vedha、Sade Sati 阶段:报告阶段做;BAV / SAV 来自
/api/ashtakavarga。 - 候选窗口:#153。
- Real Case Calibration:按裁定仍是 blocked,不作准确率声明。
已知限制
- Narayana 旧算法的 AD 年龄由生产器保留 4 位小数,换成日期约有 53 分钟误差;MD 年龄是整年相加,没有这个问题。旧算法只排一轮,范围超出这一轮时写 blocked。
- 慢星事件按天取样,一天内相继发生的两件事可能合并(慢星实际不会出现)。真交点每天取样。
- 回路的影区边界落在扫描窗口(范围前后各 220 天)之外时写
null,并在shadow_note里说明。 - 返照、Mudda、Patyayini 的时刻都按返照求解器的精确时刻(JD)对齐;生产器内部用的是截到秒的时间,差不到 1 秒。
- 一个公历年的完整包大约 350 KB(大头是 Mudda / Patyayini 的子运行,每行有三个时刻)。