Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
14 KiB
PROGRESS · 上游同步 3:返照时区本地化 / 年运面板核实 / PL9 紧凑写出(2026-09-29)
任务书:TASK-upstream-sync3-20260929.md。分支 codex/upstream-sync3-20260929,基线 origin/staging = 15be6a06。上游本机检出 /workspace/yinduzhanxing @ db8cdf4a。执行方:Claude fork 子代理;只 commit 不 push。
一句话结论
- T1 做了,且比任务书写的更严重:本仓返照的
dt_local从来就是 UT 原样(find_solar_return_ut注释「调用方另行加 tz」,但没有任何调用方加过)。北京盘读者版报告里的「Varshapravesha:2026-06-15 08:04:52」实际是 UT,应为 16:04:52;2028 年那行连日期都错(06-14 → 06-15)。所以硬红线 1「北京盘逐字节相同」无法满足,也不应满足——差异恰好只有被修正的三处(见 §T1 等价比对表),已把改前改后每个差异路径列出,请验收方按语义判断。 - T2 不适用:12 个键里 10 个在本仓从来没有生产者(
annual_tajika_pack.py不产出面板类键,只有pl9_reader_export.py防御性读取),不是装配时丢失。BUG-1096 记closed_not_applicable。 - T3 做了,但没抄上游的实现:实测
json.dump流式在 CPython 走纯 Python 编码器,比dumps慢 45%;改为紧凑dumps一次写出,保留上游stream=签名。文件 9.57 MB → 4.61 MB、序列化峰值 46.9 → 22.1 MB、0.41 → 0.30 s。
上游对照与取舍
| 上游 SHA | 内容 | 本仓落点 | 取舍 |
|---|---|---|---|
41d1c740 |
_localize_return_datetime(ZoneInfo 按返照瞬间取偏移)、annual_location、返照地点 current_location 模式与 CLI 参数、外部 PyJHora 回放在异地时 blocked |
scripts/solar_return.py(helper、calc_solar_return_chart / solar_return_full_report 四个新 kwargs、dt_local 真正本地化、Sahams 用 annual_location);scripts/cmd_solar_return.py(新增 solar_return_location_kwargs,同时接受年运包命名空间 return_* 与 CLI 命名空间 --solar-return-location-mode / --current-* / --timezone-id / API timezone);scripts/annual_tajika_pack.py(_args_from_payload 读 settings.current_location,出生 timezone/timezone_id 是 IANA 时传 return_timezone_id;新增 _iana_timezone_id);scripts/jyotish_engine.py(_annual_payload_from_args 写 current_location 与 birth.timezone_id;_build_pl9_full_annual_section 异地时回放 blocked;full-reading Step 4.12 透传;_add_solar_return_location_args 挂到 solar-return / full-reading / pl9-export) |
全抄,另加 --timezone-id(出生地 IANA 区)——上游只在 current_location 模式带 IANA 区,出生地模式仍是固定偏移;本仓把出生地 IANA 区也接上,否则 T1 对默认模式没有意义。tajika_monthly.py / _local_eight_year_overview / _annual_special_points 本仓不存在,对应块不抄 |
089858c0 |
_preserve_annual_report_surface:全量报告装配时把 12 个年运面板键从快照补回 |
无 | 不适用(§T2 核实表)。同批 full_report_quality_gate.py +86 行(80756803)内容是逐年 Muntha / Year Lord 一致性与 Ashtakavarga 12 宫映射校验,与面板无关,任务书假设的「面板覆盖检查」不存在,未接 |
59c9139f |
output_json(data, *, stream=None) 流式写出,PL9 紧凑 |
scripts/jyotish_engine.py::output_json 与 main() 的 pl9 --output 落盘 |
签名照抄,实现改为紧凑 dumps(§T3 数据) |
T1 · 返照时刻本地化(BUG-1095)
事实
- 改前
find_solar_return_ut返回'dt_local': dt_ut(scripts/solar_return.py:221),calc_solar_return_chart原样透传;solar_return_full_report把它当本地时间交给calc_all_sahams(..., tz=birth_tz)做昼夜判定(saham_daynight.determine_daytime再减一次 tz → 判定时刻比真实返照早 8 小时)。 - 读者版
pl9_reader_export.py:1830/2201直接打印dt_local。北京虚构盘(1990-06-15 10:30,39.9042/116.4074,+8,目标 2026)reader_main 渲染:
| 行 | 改前 | 改后 |
|---|---|---|
- Varshapravesha: |
2026-06-15 08:04:52 | 2026-06-15 16:04:52 |
| 2027 表行 | 2027-06-15 14:07:50 | 2027-06-15 22:07:50 |
| 2028 表行 | 2028-06-14 20:25:46 | 2028-06-15 04:25:46 |
- 昼夜判定时刻:
sahams.daynight_evidence.julian_day_ut2461206.5034(=06-15 00:04 UT)→ 2461206.8367(=真实返照 08:04:52 UT)。这张盘两者恰好都是白天,判定没翻;其他盘会翻。
等价比对(硬红线 1)
方法:改前用基线树(git worktree add --detach <scratch> 15be6a06,用完已删)、改后用本分支,同一命令、PYTHONHASHSEED=0,JSON 解析后递归比对,剔除 generated_at / elapsed_seconds / *_hash 等易变键。
| 命令 | 差异路径数 | 差异内容 |
|---|---|---|
solar-return 北京盘 |
6 | solar_return.dt_local 08:04:52 → 16:04:52;solar_return.annual_location / 顶层 annual_location 新增;sahams.daynight_evidence.julian_day_ut 改为真实返照时刻;sunrise_jd_ut / sunset_jd_ut 第 9 位小数浮动(rise_trans 起点变了) |
tajika --age 36 北京盘 |
0 | — |
pl9-export --pack full 北京盘 |
83 | 上面三项在 full_report_pack.sections.annual.*、personal_report_producer.source_pack_contracts.annual_tajika_pack.*、raw_module_index[35].field_names 的传播;其余全是易变项(VedAstro 快照 called_at、slowest_stages 顺序、authority / quality-gate 哈希 id——两次改后运行之间同样有 30 处这类差异) |
solar-return 北京盘 固定 +8 vs --timezone-id Asia/Shanghai |
0 | 无夏令时地区 IANA 区与固定偏移等价 |
结论:北京盘不是逐字节相同,差异恰为修正项本身;这一条需要验收方确认接受。
夏令时与异地验证(虚构纽约盘 1985-07-04 08:00,40.7128/-74.006,--tz -5,目标 2026)
| 模式 | dt_ut |
dt_local |
annual_location.utc_offset |
返照盘上升 |
|---|---|---|---|---|
| 固定偏移(改后默认) | 2026-07-05 01:08:03 | 2026-07-04 20:08:03 | −5.0 | Capricorn |
--timezone-id America/New_York |
同 | 2026-07-04 21:08:03 | −4.0(EDT) | Capricorn(盘按 UT 起,不变) |
--solar-return-location-mode current_location --current-lat 51.5074 --current-lon -0.1278 --current-tz 0 --current-timezone-id Europe/London |
同 | 2026-07-05 02:08:03 | +1.0(BST) | Taurus |
时区 ID 能否传到年运路径(任务书要求核查)
- 引擎侧已通:CLI
--timezone-id;APIbirth.timezone/timezone_name(calculation_profile_contract._normalize_timezone认这两个键)→ profiletimezone.name→_annual_payload_from_args写birth.timezone_id→annual_tajika_pack._args_from_payload→return_timezone_id;full-reading Step 4.12 走solar_return_location_kwargs(args)读timezone_id/timezone/timezone_name。 - 前端缺口(未修,前端不在本单范围):
frontend/src/lib/personal-report-longform-birth.ts:79只传数字tz: profile.timezoneOffset;账户资料其实存了 IANA 区(account-profile-patch.ts:55timezone_id,全球出生地必填)。网页报告因此仍走固定偏移:中国用户无影响;夏令时地区用户只有当资料里的偏移恰好等于返照瞬间的偏移时才对。补一行timezone: profile.timezone_id即可接上,建议另开前端小单。
T2 · 年运面板核实表(BUG-1096,closed_not_applicable)
北京盘 pl9-export --pack full --format json(selected_report_scope = full),worksheets.timing_and_predictive_systems.annual_tajika_pack:
| 键 | 状态 | 说明 |
|---|---|---|
profile |
有 | |
patyayini_dasha |
有 | 同时出现在 external_engine_comparison.pyjhora 与 exports.json.field_statuses |
monthly_chart_pack |
缺 | 全包任何路径都没有;本仓 annual_tajika_pack.py 无生产者 |
normalized_tables |
缺 | 同上 |
lifetime_annual_chart_pages |
缺 | 同上 |
annual_chart_series_pack |
缺 | 同上 |
pl9_annual_visual_panels |
缺 | 同上 |
pl9_annual_page_map |
缺 | 同上(本仓源码 0 处引用) |
pl9_current_year_results |
缺 | 同上 |
pl9_annual_dasha_results |
缺 | 同上 |
annual_special_points |
缺 | 同上 |
tripataki_chakra |
缺 | 同上 |
本仓年运包实际键:annual_chart, audit, exports, external_engine_comparison, monthly_windows, mudda_dasha, muntha, patyayini_dasha, profile, report_sections, sahams, schema, solar_return, tajika_yogas, year_lord(15 个)。「缺」不是装配丢失(_apply_pl9_pack_selection 在 full 时原样返回 packet),而是从未生成;上游的补回逻辑没有可补的来源。不抄代码。
T3 · PL9 紧凑写出(BUG-1097)
序列化单步基准(改后 4.61 MB 语义包,tracemalloc,5 次取中位)
| 变体 | 耗时 | 峰值内存 | 文件 |
|---|---|---|---|
A 旧:dumps(indent=2) + 写 |
0.410 s | 46.9 MB | 9.57 MB |
B 上游:json.dump(stream, compact) |
0.593 s | 0.1 MB | 4.61 MB |
C 采用:紧凑 dumps 一次写出 |
0.303 s | 22.1 MB | 4.61 MB |
B 慢是因为 json.dump 只能走 iterencode 纯 Python 路径(C 编码器只在 dumps 且 indent=None 时启用)。本仓生产 API 路径(jyotish_api_server.py)不经过 output_json,本项只影响 CLI 与 --output 落盘,所以选耗时最优的 C,保留 stream= 签名。三种变体解析后与源对象相等。
整条 CLI 交错 A/B(pl9-export --pack full --format json,基线树 / 本分支交替各 3 次,resource.getrusage 子进程)
| 轮 | 基线耗时 | 基线 RSS | 改后耗时 | 改后 RSS |
|---|---|---|---|---|
| 1 | 6.92 s | 197.9 MB | 6.58 s | 191.3 MB |
| 2 | 6.62 s | 169.2 MB | 6.42 s | 159.4 MB |
| 3 | 6.42 s | 169.6 MB | 6.38 s | 159.0 MB |
输出文件 9.57 MB → 4.61 MB;非 PL9 命令(solar-return / tajika)仍 indent=2,输出逐字节不变(tajika 0 差异)。
测试
- 新增
tests/test_solar_return_timezone.py(10 条):ZoneInfo 夏令 / 标准偏移;北京dt_local = dt_ut + 8h与昼夜判定时刻回归;Asia/Shanghai等价固定 +8;纽约 IANA 区差 1 小时且盘不变;伦敦异地返照盘上升不同;返照地点不全时报错;两种命名空间的 kwargs;年运包参数映射与缺字段报错;output_json只对 PL9 紧凑且可解析。 - 任务书点名:
tests/test_tajika.py tests/test_annual_tajika_pack.py tests/test_full_report_quality_gate.py tests/test_professional_report_reference_api.py tests/test_api_server_growth_contract.py tests/test_cli_smoke.py→ 118 passed。 - 快速门(
run_quality_gate.py --profile quick,系统python33.13.5 + swisseph,/workspace/Jyotisha/.venv在本机不存在):- 基线
15be6a06第一次(无 Node):Python 步 1001 passed / 1 skipped,npm testexit 127(PATH 无 node)。 - 基线第二次(
PATH=/exec-daemonNode 22.14):Python 步 1 failedtests/test_consultation_native_layers.py::test_annual_route_attaches_the_annual_pack_verbatim('blocked' == 'parameter_sensitive'),门在 Python 步停下、未到 npm。该测试改后单独跑 3/3 通过(exit 0);当时同机另有两个子代理在跑全量测试,判为负载相关的瞬时失败,不是本单改动(基线树、改动前)。 - 改后(Node 22):见文末「快速门改后」一节。
- 基线
未完成 / 缺口
- 前端 IANA 区未传(见 T1 末节)。
full_report_quality_gate.py未改(上游同批改动与面板无关)。saham_daynight的昼夜判定输入现在是真实返照本地时刻;其他盘的昼夜结论可能因此翻转(这是修正,不是回归),未逐盘统计。
快速门改后(PATH=/exec-daemon Node 22.14,系统 python3 3.13.5)
- Python 步:1001 passed / 1 skipped / 3 subtests passed(149.84 s),与基线第一次逐条一致;基线第二次瞬时失败的
test_annual_route_attaches_the_annual_pack_verbatim本次通过。 npm test步:sh: 1: tsx: not found→ exit 127。原因是本 worktree 没有frontend/node_modules(基线两次同样在这一步 exit 127,逐条一致)。本单不改前端,另用临时软链到主检出的frontend/node_modules单独跑了一次npm test(见下一节;软链跑完即删,不入库)。
前端 npm test(本单零前端改动;临时软链主检出 frontend/node_modules,Node 22.14,跑完即删)
# tests 4357 / # pass 4302 / # fail 24 / # cancelled 0 / # skipped 31,exit 1。24 条失败全是需要 Docker / 本地 PostgreSQL / 部署套件的用例(local PostgreSQL applies the reviewed business schema…、migration runner…、staging backups…、Better Auth… 等),与 09-29 已知基线 4302/24 的环境失败清单一致(无 Docker),无新增失败。
Claude 验收(2026-09-29)
- 测试:
tests/test_solar_return_timezone.py+ 任务书点名 5 个文件 +test_api_server_growth_contract.py+test_repo_privacy_markers.py= 156 passed。 - 等价比对(独立重做):基线
15be6a06独立 worktree vs 本分支,虚构北京盘 1990-06-15 10:30,PYTHONHASHSEED=0:solar-return6 处差异(dt_local08:04:52→16:04:52、annual_location×2 新增、daynight_evidence.julian_day_ut改为真实返照时刻、sunrise/sunset 第 9 位小数);tajika --age 360 差异。与执行方表逐条一致。 - 硬红线 1 裁定:旧代码
solar_return.py:221/249'dt_local': dt_ut # 调用方另行加 tz,全仓无调用方加过,报告里的 Varshapravesha 时刻一直是 UT。「北京盘逐字节相同」的前提不成立,差异恰为修正项本身,接受。 - 快速门:Python 步 1001 passed / 1 skipped(与执行方基线一致);
npm test步 exit 127 为本 worktree 无node_modules的环境缺口,本单零前端改动,执行方软链跑出 4302/24 与基线一致。 - 结论:T1 / T3 通过,T2 不适用成立。遗留另开小单:前端
personal-report-longform-birth.ts:79只传数字tz,需把账户 IANAtimezone_id送到引擎。