Merge remote-tracking branch 'origin/staging' into codex/synastry-full-report-20261018
Renumber the synastry records to BUG-1394 and BUG-1395 so they no longer collide with BUG-1393 from issue 84. Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+6
-2
@@ -1,16 +1,20 @@
|
||||
# 印度占星 Skill 更新日志
|
||||
|
||||
## 2026-10-19 — 完整数据版报告的 Niryaana Shoola 大运表改了
|
||||
|
||||
- 这张表不再和 Shoola 相同。起运从第二宫和第八宫里较强的星座开始;奇数星座顺行,偶数星座逆行;动、固、双星座分别是 7、8、9 年。只列主运,子运暂不列出。两宫分不出强弱时,这张表本轮不列出。合盘仍不用它做应期。
|
||||
|
||||
## 2026-10-19 — 完整合盘报告第二次打回后的修复
|
||||
|
||||
- 技术依据里两个人的婚恋结构改从完整星盘读取,不再整块显示「未标明」。
|
||||
- 侧栏的「婚恋合盘」默认先不出现,确认服务开着才显示。侧栏自己不再去问后台。
|
||||
- 同一对已完成的合盘仍然不能重复占一条;生成中删掉报告会退点。Skill 版本不变(BUG-1394)。
|
||||
- 同一对已完成的合盘仍然不能重复占一条;生成中删掉报告会退点。Skill 版本不变(BUG-1395)。
|
||||
|
||||
## 2026-10-19 — 完整合盘报告打回后的修复
|
||||
|
||||
- 关系画像每一节都改用证据包里对应的那一节,不再拿空对象去写。英文时间线的依据由程序生成。某一节写失败只重试这一节。
|
||||
- 选人页按钮改为「开始合盘」,资料不全的人不能选。阅读页标题用间隔号。产品开关关掉时,侧栏和合盘页都不出现。
|
||||
- 英文生成记用量,但不从已经扣过的中文报告里再扣一次。Skill 版本不变(BUG-1393)。
|
||||
- 英文生成记用量,但不从已经扣过的中文报告里再扣一次。Skill 版本不变(BUG-1394)。
|
||||
|
||||
## 2026-10-19 — 完整合盘报告
|
||||
|
||||
|
||||
+18
-3
@@ -36841,7 +36841,22 @@
|
||||
- 复发自:无。
|
||||
- 修复版本:分支 `codex/rectification-narrower-delivery-20261016`。
|
||||
|
||||
## BUG-1393 | 完整合盘报告有几节拿不到证据,英文任务会停住
|
||||
## BUG-1393 | Niryaana Shoola 和 Shoola 用了同一套算法,报告里两张表逐行相同
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现 / 最近更新:2026-10-18 / 2026-10-19
|
||||
- 来源:issue #84。任务书 `docs/tasks/TASK-niryaana-shoola-pvr-20261018.md`。检索「niryaana_shoola」「goel_nirayana_shoola_v1」,命中 BUG-1283。
|
||||
- 现象:完整数据版报告里的 Niryaana Shoola 与 Shoola 逐行相同。两张表都从上升和第七宫里取较强的星座起运,每宫 9 年,一律顺行。
|
||||
- 触发条件:打开带本命盘的完整数据版报告,或读取 full-reading 的 `niryaana_shoola_dasha`。
|
||||
- 根因:BUG-1283 把报告改走 `goel_nirayana_shoola_v1` 时,所选规则其实是 Shoola 的。当时没有和同文件里的 Shoola 对过输出。本条推翻该 profile。BUG-1283 的另一半不变:新路径仍不得调用旧的 `scripts/niryaana_shoola_dasha.py`。
|
||||
- 修复:在 `scripts/rashi_dashas_native.py` 内改成 `pvr_niryaana_shoola_v1`。起运比较第二宫和第八宫,依次看宫内九曜个数、星座相位数、有没有入旺行星;三层都相同则本轮不上表。奇数星座顺行,偶数星座逆行。动、固、双星座分别 7、8、9 年。只出主运。full-reading 的 `niryaana_shoola_dasha` 改填这张表。合盘校验器对这一行仍固定为不适用。
|
||||
- 验证:`tests/test_rashi_dashas_native.py` 在屏蔽 PyJHora 后 18 通过;`tests/test_full_reading_module_parity.py` 与 `tests/test_niryaana_shoola_dasha.py` 合计 10 通过。同一张样例盘上,Niryaana Shoola 与 Shoola 的首运或年数不再全同。语法树断言仍确认新路径不导入旧文件。
|
||||
- 防复发:上述测试。不得再把 Niryaana Shoola 接回 `goel_nirayana_shoola_v1` 或 `_nine_year`。平手不得默认挑一个星座。不得为这张表编造子运。
|
||||
- 相关记录:BUG-1283。
|
||||
- 复发自:无。这是推翻 BUG-1283 所选 profile,不是旧文件被重新调用。
|
||||
- 修复版本:分支 `codex/niryaana-shoola-pvr-20261018`。
|
||||
|
||||
## BUG-1394 | 完整合盘报告有几节拿不到证据,英文任务会停住
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-10-19
|
||||
@@ -36857,7 +36872,7 @@
|
||||
- 复发自:无。
|
||||
- 修复版本:分支 `codex/synastry-full-report-20261018`。
|
||||
|
||||
## BUG-1394 | 合盘技术依据全是未标明,侧栏自己去请求开关
|
||||
## BUG-1395 | 合盘技术依据全是未标明,侧栏自己去请求开关
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-10-19
|
||||
@@ -36869,6 +36884,6 @@
|
||||
- 修复:胶水层从两人的完整星盘补出这些结构字段,放进证据包的 `local_structures`。侧栏改为接收 `compatibilityEnabled`,默认不显示,由外壳在开关确认为开之后再传入。
|
||||
- 验证:`frontend/tests/synastry-full-report.test.ts` 用公开名人证据包核对第 ② 块不是整行未标明,并逐行核对第 ⑥ 块。`frontend/tests/sidebar-contract.test.ts` 核对侧栏源码不含 `fetch(`。`frontend/tests/database-synastry-full-report.test.ts` 核对同一对复用、指纹变化、删人级联,以及预留、结算、释放和生成中删除退点。
|
||||
- 防复发:不要从经度快照里读它没有的星座字段。不要在侧栏里发请求。
|
||||
- 相关记录:BUG-1393。issue #82。
|
||||
- 相关记录:BUG-1394。issue #82。
|
||||
- 复发自:无。
|
||||
- 修复版本:分支 `codex/synastry-full-report-20261018`。
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# 进度:Niryaana Shoola 改为 PVR 口径(2026-10-19)
|
||||
|
||||
- 基线:`origin/staging` `b827901c1`。分支 `codex/niryaana-shoola-pvr-20261018`。
|
||||
- 前置:#81 已在 staging(`900581068`),issue 标签为 `agent/待真机`。本单在其后开工。
|
||||
- BUG 编号:开工时最大号 BUG-1392,本单使用 BUG-1393。关联 BUG-1283,并写明推翻其所选 `goel_nirayana_shoola_v1`。旧文件仍不得被新路径调用。
|
||||
|
||||
## 做了什么
|
||||
|
||||
- `scripts/rashi_dashas_native.py`:Niryaana Shoola 改为 `pvr_niryaana_shoola_v1`。起运比较第二宫和第八宫(九曜个数、星座相位数、入旺行星);奇数星座顺行,偶数星座逆行;动 / 固 / 双星座 7 / 8 / 9 年。只出主运。三层平手则本轮不上表。没有搬上游文件,也没有 import 旧的 `niryaana_shoola_dasha.py`。
|
||||
- `scripts/jyotish_engine.py`:full-reading 的 `niryaana_shoola_dasha` 改填上面的结果,替换 #81 的占位。
|
||||
- `scripts/rashi_dasha_report.py`:该节列出主运,并写「子运规则未定,暂不列出。」
|
||||
- 对照文件里 Niryaana Shoola 的本站行和差异步骤按新表重记。PyJHora 行没有重跑。
|
||||
|
||||
## 断言三栏
|
||||
|
||||
| 位置 | 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| `test_rashi_dashas_native.py` 同盘 Niryaana | 首运 Virgo,十二段都是 9 年,有子运 | `pvr_niryaana_shoola_v1`,首运 Aries、顺行,年数从 7/8/9 起,无子运 | 推翻与 Shoola 相同的旧 profile |
|
||||
| `test_full_reading_module_parity.py` | `execution_status=blocked`,原因「口径更正中」 | `parameter_sensitive`,profile `pvr_niryaana_shoola_v1`,periods 为主运 | #84 用真结果替换占位 |
|
||||
| fixture `native.niryaana_shoola` 与 `diff_steps` | 与 Shoola 相同的 9 年顺行表 | PVR 主运星座和年数;差异步骤按新表重记 | 只改本站期望,PyJHora 行不动 |
|
||||
|
||||
## 命令
|
||||
|
||||
工作树没有 `.venv`。下面用已装 Swiss Ephemeris 的 Anaconda 3.11:`C:\Users\74082\anaconda3\python.exe`。
|
||||
|
||||
| 命令 | 结果 |
|
||||
| --- | --- |
|
||||
| `tests/test_rashi_dashas_native.py`(屏蔽 PyJHora 导入) | 18 通过,退出码 0 |
|
||||
| `tests/test_full_reading_module_parity.py` + `tests/test_niryaana_shoola_dasha.py` | 10 通过,退出码 0 |
|
||||
| 全量 pytest、`run_quality_gate.py --profile quick` | 未跑。Windows 分工把这两项留给 Claude 验收 |
|
||||
|
||||
## 偏离
|
||||
|
||||
- 没有整份搬上游 `niryaana_shoola_dasha.py`。力量比较、方向和年数按上游 `9f08b57b` / `258e08e3` 写在本仓文件里。
|
||||
- 两位公开名人用仓库里已有的公开盘:一张起射手并顺行,一张起水瓶并顺行,主运序列与该起运下的 7/8/9 年规则一致。
|
||||
- 平手样例(七曜都在巨蟹、白羊上升)结果为「本轮不上表」。节点占宫、节点星座相位、外行星不计数,与上游 `258e08e3` 的样例一致。
|
||||
@@ -505,6 +505,6 @@
|
||||
| `TASK-marriage-match-p1-fix-20261018.md` | #79 | 只存两个 profile 的配对结果;旧历史列表不读新行;并发重复写入改为重读 | 待验收 | `codex/marriage-match-p1-20261017` |
|
||||
| `TASK-marriage-match-p1-fix-20261018-2.md` | #79 | 胶水挪进子包,根目录文件数回到 446;调用清单改指向 marriage_match.py | 待验收 | `codex/marriage-match-p1-20261017` |
|
||||
| `TASK-full-reading-module-parity-20261018.md` | #81 | full-reading 补齐上游 5 个模块(西占层文件没搬致永远 blocked、Navamsha / Niryaana 用我们自己的实现填键、Lagna Vargottamamsa / Pushkara / Punarphoo)(BUG-1375 起) | 待执行(可与 #78 并行) | `codex/full-reading-module-parity-20261018` |
|
||||
| `TASK-niryaana-shoola-pvr-20261018.md` | #84 | Niryaana Shoola 改 PVR 口径(我们的表与 Shoola 逐行相同;推翻 BUG-1283 所选 profile;占星师 7a773d13) | 排队(等 #81) | `codex/niryaana-shoola-pvr-20261018` |
|
||||
| `TASK-niryaana-shoola-pvr-20261018.md` | `PROGRESS-niryaana-shoola-pvr-20261018.md` | #84 Niryaana Shoola 改 PVR 口径(我们的表与 Shoola 逐行相同;推翻 BUG-1283 所选 profile;占星师 7a773d13) | 待验收 | `codex/niryaana-shoola-pvr-20261018` |
|
||||
| `TASK-synastry-full-report-20261018.md` | #82 | 婚恋合盘:选人页 + 完整合盘报告(关系画像九节:结论+折叠的分析逻辑+关系时间线+技术依据摘要;英文按需;计费键 synastry.full) | 排队(等 #78、#79、#81) | `codex/synastry-full-report-20261018`;动表 |
|
||||
| `TASK-synastry-report-chat-20261018.md` | #83 | 从合盘报告某一节进入对话问依据(绑定报告与节、对话里可用术语、只依据已存计算结果并逐条核对、计费照普通对话) | 排队(等 #82) | `codex/synastry-report-chat-20261018`;动表 |
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# TASK · 完整合盘报告 · 第三份修复单(issue #82)
|
||||
|
||||
- 日期:2026-10-19
|
||||
- 被验提交:`362d903c31d579c834d36cd8209788864a363ae2`(分支 `codex/synastry-full-report-20261018`)
|
||||
- 前序:`TASK-synastry-full-report-fix-20261019.md`、`TASK-synastry-full-report-fix-20261019-2.md`(S1–S7)
|
||||
- 状态:第 3 次未过,按循环规则等产品负责人拍板(见 issue #82)
|
||||
- 编号:合并时本单两条已由 BUG-1393/1394 顺延为 **BUG-1394/1395**(1393 归 #84);Claude 合并提交 `b3c8eaf7` 在本地,未推送。新缺陷从开工时最大号 +1 起
|
||||
|
||||
## 验收结论(Claude,Linux / Node 22)
|
||||
|
||||
| 项 | 结论 |
|
||||
| --- | --- |
|
||||
| tsc / lint / build | 0 错;0 error;`/` 仍 Static,首屏 gzip 约 +0.3% |
|
||||
| 全量 pytest | 与 staging 基线一致(只差已知并行抖动 `test_vedastro_gateway`) |
|
||||
| 前端全量 | 5071 条,失败 26;比基线多 **1 条**(T1) |
|
||||
| S2 / S3 / S4 / S5 / S7 / 应该修 | 已修 |
|
||||
| S1 / S6 | 部分(T2、T3) |
|
||||
|
||||
## T1 · 报告列表翻页合同被打破(必做)
|
||||
|
||||
`frontend/tests/report-list-paging-20260930.test.tsx`「GET /api/reports pages by (created_at desc, id desc), fetches limit + 1 and returns nextCursor」失败。原因:为了让「缺人物时在扣点前拒绝」那条测试的 mock 能跑,`app/api/reports/route.ts` 的 GET 把一次 `.or()` 查询拆成了四次 `eq` 查询、在内存里合并。这样:
|
||||
|
||||
- 每次翻页要打四次数据库;
|
||||
- 四路各取 `limit + 1` 后再合并,`nextCursor` 的语义是否仍然正确没有任何测试守着,而原来守着它的合同测试现在红了。
|
||||
|
||||
修法:**恢复单次 `.or()` 查询与原有分页写法**,让翻页合同测试恢复绿;「缺人物」那条测试的问题改为给它的 mock 补上 `.or()`(这是测试替身缺方法,不是放宽断言;按 §7.3 写三栏说明)。若确有必要改分页实现,先在本单进度里写清新分页的正确性证明,并补一条「跨四路合并后的游标翻页不丢不重」的行为测试,再同步更新合同测试(三栏)。
|
||||
|
||||
## T2 · S1 剩余三处测试(必做)
|
||||
|
||||
1. 「两轮质检不过 → 退点」:现在只 mock 了一次 `report_guard_rejected`。改为让写作者两轮都被质检拒绝,断言第二轮后预留被释放、报告为失败。
|
||||
2. 英文 worker 行为测试:现在只检查租约常数和源码里有 `.catch`。补一条:领取 → 生成成功 → 写快照、任务 ready、记用量;一条:生成抛错 → 任务 failed、错误码、不卡 running。
|
||||
3. 技术依据第 ⑥ 块:现在拿同一个 `executionBasisLines()` 生成期望,等于自己验自己。改为直接读真实 `execution_receipt` 的行,逐行断言第 ⑥ 块里出现了对应的模块名与状态。
|
||||
|
||||
## T3 · S6 第 ② 块(必做)
|
||||
|
||||
- A7:引擎里在 `modules.jaimini.arudha_padas`,不是 `modules.arudha_padas`;按实际路径取。
|
||||
- `chara_karaka_standard` 在仓库里不存在,删掉这条死查找或换成真实键。
|
||||
- 测试:用真实 Einstein / Jobs 证据包断言 7 宫主、DK、UL、A7、D9 五项**都有值**(不是只断言「不全是未标明」);Python 侧 `relationship_structure()` 加一条单测。
|
||||
|
||||
## T4 · 外壳每次挂载都请求开关(必做,小)
|
||||
|
||||
`app/(app)/layout.tsx` 现在每次挂载外壳都 `fetch('/api/product-access')`。预热测试 `home-warm-return-lifecycle.test.tsx` 用的是自己的 `AppShell` 副本,没覆盖这个请求。修法二选一:复用已经在取的账户 / 产品数据(推荐),或把这个请求纳入预热测试的只读预取清单并更新该测试(三栏)。
|
||||
|
||||
## 交回前必跑
|
||||
|
||||
全量 `npm test`(与基线逐名比对,必须没有新增失败)、`npm run test:db`、`npm run build`、全量 `pytest`、快速门。上一轮又没跑全量,正是 T1 漏掉的原因。
|
||||
@@ -16124,18 +16124,20 @@ def cmd_full_reading(args):
|
||||
planet_lons,
|
||||
)
|
||||
report['modules']['navamsha_dasha'] = _public_secondary_dasha_module(secondary_dashas.get('navamsha'))
|
||||
report['modules']['niryaana_shoola_dasha'] = _public_secondary_dasha_module(
|
||||
secondary_dashas.get('niryaana_shoola')
|
||||
)
|
||||
except Exception as e:
|
||||
report['modules']['navamsha_dasha'] = {
|
||||
'status': 'blocked',
|
||||
'reason': f'navamsha_dasha_producer_failed:{e}',
|
||||
'periods': [],
|
||||
}
|
||||
# F2b: do not publish the current Niryaana Shoola table. It matches Shoola.
|
||||
# The synastry validator marks this row not applicable, so the pair task stays open.
|
||||
report['modules']['niryaana_shoola_dasha'] = {
|
||||
'execution_status': 'blocked',
|
||||
'reason': '口径更正中,见 TASK-niryaana-shoola-pvr-20261018',
|
||||
}
|
||||
report['modules']['niryaana_shoola_dasha'] = {
|
||||
'status': 'blocked',
|
||||
'reason': f'niryaana_shoola_dasha_producer_failed:{e}',
|
||||
'periods': [],
|
||||
}
|
||||
_record_stage_timing(
|
||||
stage_timings,
|
||||
'dasha_and_core_varga_stack',
|
||||
|
||||
@@ -140,6 +140,13 @@ def render_rashi_dasha_chapter(packet: Mapping) -> str:
|
||||
section = sections[key]
|
||||
title = title_en if language == "en" else title_zh
|
||||
lines.extend([f"### {title}", "", _method_line(section, language), ""])
|
||||
if key == "niryaana_shoola" and section.get("status") == "listed":
|
||||
lines.extend([
|
||||
"Child periods stay off this table: the cited book does not give a separate rule for them."
|
||||
if language == "en"
|
||||
else "子运规则未定,暂不列出。",
|
||||
"",
|
||||
])
|
||||
if key == "chara" and section.get("status") == "listed":
|
||||
lines.extend([
|
||||
"This section is the K.N. Rao Chara table. It is not the Narayana table."
|
||||
|
||||
@@ -38,6 +38,17 @@ except ImportError: # pragma: no cover - package import
|
||||
_MOVABLE = {0, 3, 6, 9}
|
||||
_FIXED = {1, 4, 7, 10}
|
||||
_DUAL = {2, 5, 8, 11}
|
||||
# Nine grahas only. Outer planets and upagrahas are ignored. Nodes are not weighted.
|
||||
_AYUR_RASI_STRENGTH_BODIES = (
|
||||
"Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu",
|
||||
)
|
||||
_EXALTED_SIGNS = {
|
||||
"Sun": {0}, "Moon": {1}, "Mars": {9}, "Mercury": {5},
|
||||
"Jupiter": {3}, "Venus": {11}, "Saturn": {6},
|
||||
}
|
||||
_NIRYAANA_SOURCE_ZH = "PVR《Vedic Astrology: An Integrated Approach》"
|
||||
_NIRYAANA_SOURCE_EN = "PVR, Vedic Astrology: An Integrated Approach"
|
||||
_NIRYAANA_PROFILE = "pvr_niryaana_shoola_v1"
|
||||
|
||||
|
||||
def _as_date(value: date | datetime | str) -> date:
|
||||
@@ -303,6 +314,171 @@ def _drig_children(md_sign: int, start: date, end: date, graha: Mapping, planet_
|
||||
return _equal_children(order, start, end)
|
||||
|
||||
|
||||
def niryaana_shoola_years(sign_idx: int) -> int:
|
||||
"""Movable, fixed, and dual signs last 7, 8, and 9 years."""
|
||||
sign_idx = int(sign_idx) % 12
|
||||
if sign_idx in _FIXED:
|
||||
return 8
|
||||
if sign_idx in _DUAL:
|
||||
return 9
|
||||
return 7
|
||||
|
||||
|
||||
def select_niryaana_shoola_start_sign(
|
||||
*,
|
||||
ascendant_longitude: float,
|
||||
planet_longitudes: Mapping[str, float],
|
||||
) -> dict:
|
||||
"""Pick the stronger of the second and eighth signs for a longevity dasha.
|
||||
|
||||
Compare occupant count, then every planet's Jaimini sign aspect (the sign
|
||||
lord is not weighted), then whether an occupant is exalted. A three-way
|
||||
tie stays off the table. Count only the nine grahas.
|
||||
"""
|
||||
lagna = sign_index(float(ascendant_longitude))
|
||||
second, eighth = (lagna + 1) % 12, (lagna + 7) % 12
|
||||
positions = {
|
||||
name: float(longitude)
|
||||
for name, longitude in planet_longitudes.items()
|
||||
if name in _AYUR_RASI_STRENGTH_BODIES and isinstance(longitude, (int, float))
|
||||
}
|
||||
candidates = {}
|
||||
for sign_idx in (second, eighth):
|
||||
occupants = sorted(
|
||||
name for name, longitude in positions.items()
|
||||
if sign_index(longitude) == sign_idx
|
||||
)
|
||||
aspecting = sorted(
|
||||
name for name, longitude in positions.items()
|
||||
if sign_index(longitude) != sign_idx and sign_idx in _aspecting_signs(sign_index(longitude))
|
||||
)
|
||||
exalted = sorted(
|
||||
name for name in occupants
|
||||
if sign_idx in _EXALTED_SIGNS.get(name, set())
|
||||
)
|
||||
candidates[sign_idx] = {
|
||||
"sign_index": sign_idx,
|
||||
"sign": SIGNS[sign_idx],
|
||||
"occupants": occupants,
|
||||
"occupant_count": len(occupants),
|
||||
"rasi_aspecting_planets": aspecting,
|
||||
"rasi_aspect_count": len(aspecting),
|
||||
"exalted_occupants": exalted,
|
||||
}
|
||||
second_score = (
|
||||
candidates[second]["occupant_count"],
|
||||
candidates[second]["rasi_aspect_count"],
|
||||
bool(candidates[second]["exalted_occupants"]),
|
||||
)
|
||||
eighth_score = (
|
||||
candidates[eighth]["occupant_count"],
|
||||
candidates[eighth]["rasi_aspect_count"],
|
||||
bool(candidates[eighth]["exalted_occupants"]),
|
||||
)
|
||||
payload = {
|
||||
"profile": "pvr_niryaana_shoola_ayur_rasi_strength_v1",
|
||||
"included_bodies": list(_AYUR_RASI_STRENGTH_BODIES),
|
||||
"ascendant_sign_index": lagna,
|
||||
"second_sign_index": second,
|
||||
"eighth_sign_index": eighth,
|
||||
"candidates": candidates,
|
||||
"selection_levels": [
|
||||
"occupant_count",
|
||||
"all_planet_rasi_aspect_count",
|
||||
"exalted_occupant_presence",
|
||||
],
|
||||
}
|
||||
if second_score == eighth_score:
|
||||
return {
|
||||
**payload,
|
||||
"status": "blocked",
|
||||
"reason": "niryaana_shoola_ayur_rasi_strength_tie_requires_source_rule",
|
||||
}
|
||||
selected = second if second_score > eighth_score else eighth
|
||||
return {
|
||||
**payload,
|
||||
"status": "selected",
|
||||
"selected_sign_idx": selected,
|
||||
"selected_sign": SIGNS[selected],
|
||||
}
|
||||
|
||||
|
||||
def niryaana_shoola_applicability(
|
||||
*,
|
||||
ascendant_longitude: float,
|
||||
second_strength: float,
|
||||
eighth_strength: float,
|
||||
) -> dict:
|
||||
"""Start from the stronger given second or eighth strength. Equal strength blocks."""
|
||||
lagna = sign_index(float(ascendant_longitude))
|
||||
second, eighth = (lagna + 1) % 12, (lagna + 7) % 12
|
||||
second_strength, eighth_strength = float(second_strength), float(eighth_strength)
|
||||
if second_strength == eighth_strength:
|
||||
return {
|
||||
"applicable": False,
|
||||
"execution_status": "blocked",
|
||||
"ascendant_sign_index": lagna,
|
||||
"second_sign_index": second,
|
||||
"eighth_sign_index": eighth,
|
||||
"second_strength": second_strength,
|
||||
"eighth_strength": eighth_strength,
|
||||
"rule_family": "pvr_niryaana_shoola_2nd_8th_modality",
|
||||
"reason": "The cited Niryaana Shoola rule does not provide a tie-break for equal second/eighth strength.",
|
||||
}
|
||||
start = second if second_strength > eighth_strength else eighth
|
||||
return {
|
||||
"applicable": True,
|
||||
"ascendant_sign_index": lagna,
|
||||
"second_sign_index": second,
|
||||
"eighth_sign_index": eighth,
|
||||
"second_strength": second_strength,
|
||||
"eighth_strength": eighth_strength,
|
||||
"starting_sign_index": start,
|
||||
"starting_sign": SIGNS[start],
|
||||
"direction": "forward" if start % 2 == 0 else "reverse",
|
||||
"rule_family": "pvr_niryaana_shoola_2nd_8th_modality",
|
||||
"reason": "Niryaana Shoola starts from the stronger second/eighth sign; odd seeds run forward and even seeds reverse.",
|
||||
}
|
||||
|
||||
|
||||
def niryaana_shoola_mahadashas(birth: date, start_sign: int) -> list[dict]:
|
||||
"""Twelve mahadashas only. Odd signs go forward; even signs go backward."""
|
||||
start_sign = int(start_sign) % 12
|
||||
order = _forward(start_sign) if start_sign % 2 == 0 else _reverse(start_sign)
|
||||
periods = []
|
||||
cursor = birth
|
||||
for sign_idx in order:
|
||||
years = niryaana_shoola_years(sign_idx)
|
||||
end = add_years(cursor, years)
|
||||
periods.append(_period(sign_idx, years, cursor, end))
|
||||
cursor = end
|
||||
return periods
|
||||
|
||||
|
||||
def _niryaana_shoola(birth: date, ascendant_longitude: float, planet_lons: Mapping[str, float]) -> dict:
|
||||
picked = select_niryaana_shoola_start_sign(
|
||||
ascendant_longitude=ascendant_longitude,
|
||||
planet_longitudes=planet_lons,
|
||||
)
|
||||
if picked.get("status") != "selected":
|
||||
return _omitted(
|
||||
_NIRYAANA_PROFILE,
|
||||
_NIRYAANA_SOURCE_ZH,
|
||||
_NIRYAANA_SOURCE_EN,
|
||||
"本轮不上表:二宫与八宫力量相同,原书没有给出进一步的裁决规则。",
|
||||
"This report does not include Niryaana Shoola: the second and eighth signs are equal, and the source gives no further tie-break.",
|
||||
)
|
||||
start_sign = int(picked["selected_sign_idx"])
|
||||
return _listed(
|
||||
_NIRYAANA_PROFILE,
|
||||
_NIRYAANA_SOURCE_ZH,
|
||||
_NIRYAANA_SOURCE_EN,
|
||||
niryaana_shoola_mahadashas(birth, start_sign),
|
||||
direction="forward" if start_sign % 2 == 0 else "reverse",
|
||||
strength_profile=picked["profile"],
|
||||
)
|
||||
|
||||
|
||||
def _nine_year(birth: date, lagna: int, planet_lons: Mapping[str, float], rashi: Mapping, profile: str, source_zh: str, source_en: str) -> dict:
|
||||
seventh = (lagna + 6) % 12
|
||||
picked = stronger_sign(lagna, seventh, rashi, planet_lons)
|
||||
@@ -397,15 +573,7 @@ def calculate_rashi_dashas(birth: date | datetime | str, ascendant_longitude: fl
|
||||
"Crux of Vedic Astrology, printed page 395",
|
||||
),
|
||||
"navamsha": _navamsha(birth_day, float(ascendant_longitude), longitudes),
|
||||
"niryaana_shoola": _nine_year(
|
||||
birth_day,
|
||||
lagna,
|
||||
longitudes,
|
||||
rashi,
|
||||
"goel_nirayana_shoola_v1",
|
||||
"Predicting through Jaimini Astrology,印页 162",
|
||||
"Predicting through Jaimini Astrology, printed page 162",
|
||||
),
|
||||
"niryaana_shoola": _niryaana_shoola(birth_day, float(ascendant_longitude), longitudes),
|
||||
"lagna_kendradi": _omitted(
|
||||
"",
|
||||
"",
|
||||
|
||||
+48
-77
@@ -564,32 +564,32 @@
|
||||
},
|
||||
"niryaana_shoola": {
|
||||
"sign_indexes": [
|
||||
10,
|
||||
11,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
10,
|
||||
9,
|
||||
8,
|
||||
9
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"years": [
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9
|
||||
8,
|
||||
7
|
||||
]
|
||||
},
|
||||
"lagna_kendradi": {
|
||||
@@ -647,7 +647,6 @@
|
||||
"subperiod"
|
||||
],
|
||||
"niryaana_shoola": [
|
||||
"start_sign",
|
||||
"direction",
|
||||
"years",
|
||||
"subperiod"
|
||||
@@ -1224,7 +1223,6 @@
|
||||
},
|
||||
"niryaana_shoola": {
|
||||
"sign_indexes": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
@@ -1235,21 +1233,22 @@
|
||||
11,
|
||||
0,
|
||||
1,
|
||||
2
|
||||
2,
|
||||
3
|
||||
],
|
||||
"years": [
|
||||
8,
|
||||
9,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9
|
||||
7
|
||||
]
|
||||
},
|
||||
"lagna_kendradi": {
|
||||
@@ -1308,7 +1307,6 @@
|
||||
"subperiod"
|
||||
],
|
||||
"niryaana_shoola": [
|
||||
"start_sign",
|
||||
"direction",
|
||||
"years",
|
||||
"subperiod"
|
||||
@@ -1909,32 +1907,32 @@
|
||||
},
|
||||
"niryaana_shoola": {
|
||||
"sign_indexes": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
8,
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
1
|
||||
11,
|
||||
10
|
||||
],
|
||||
"years": [
|
||||
7,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9
|
||||
8
|
||||
]
|
||||
},
|
||||
"lagna_kendradi": {
|
||||
@@ -1992,7 +1990,6 @@
|
||||
"subperiod"
|
||||
],
|
||||
"niryaana_shoola": [
|
||||
"start_sign",
|
||||
"direction",
|
||||
"years",
|
||||
"subperiod"
|
||||
@@ -2568,34 +2565,8 @@
|
||||
]
|
||||
},
|
||||
"niryaana_shoola": {
|
||||
"sign_indexes": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
0
|
||||
],
|
||||
"years": [
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9
|
||||
]
|
||||
"sign_indexes": [],
|
||||
"years": []
|
||||
},
|
||||
"lagna_kendradi": {
|
||||
"status": "omitted",
|
||||
|
||||
@@ -147,10 +147,15 @@ def test_secondary_dashas_use_native_profiles_and_match_goel_method_1(reports) -
|
||||
niryaana = modules["niryaana_shoola_dasha"]
|
||||
assert navamsha["status"] == "parameter_sensitive"
|
||||
assert navamsha["profile_id"] == "goel_method_1_v1"
|
||||
assert niryaana == {
|
||||
"execution_status": "blocked",
|
||||
"reason": "口径更正中,见 TASK-niryaana-shoola-pvr-20261018",
|
||||
}
|
||||
# 原值:niryaana == {execution_status: blocked, reason: 口径更正中,见 TASK-niryaana-shoola-pvr-20261018}
|
||||
# 新值:status parameter_sensitive,profile pvr_niryaana_shoola_v1,periods 是 PVR 主运
|
||||
# 原因:#84 用真结果换掉 #81 的占位。合盘校验器仍把这一行固定判为不适用。
|
||||
assert niryaana["status"] == "parameter_sensitive"
|
||||
assert niryaana["profile_id"] == "pvr_niryaana_shoola_v1"
|
||||
assert niryaana["periods"] == niryaana["native_result"]["periods"]
|
||||
assert niryaana["native_result"]["status"] == "listed"
|
||||
assert "antardasha" not in niryaana["periods"][0]
|
||||
assert sum(row["years"] for row in niryaana["periods"]) == 96
|
||||
assert navamsha["native_result"]["status"] == "listed"
|
||||
first = navamsha["periods"][0]
|
||||
d9 = modules["varga_full"]["D9_Navamsa"]
|
||||
|
||||
@@ -13,6 +13,9 @@ from scripts.rashi_dashas_native import (
|
||||
drig_aspect_sequence,
|
||||
navamsha_sign,
|
||||
navamsha_years,
|
||||
niryaana_shoola_applicability,
|
||||
niryaana_shoola_mahadashas,
|
||||
select_niryaana_shoola_start_sign,
|
||||
)
|
||||
from scripts.rashi_dasha_report import render_rashi_dasha_chapter
|
||||
|
||||
@@ -63,15 +66,23 @@ def test_goel_short_method_matches_the_printed_totals_and_direct_sthira() -> Non
|
||||
assert "pratyantar" not in aquarius
|
||||
|
||||
|
||||
def test_same_goel_chart_niryaana_uses_short_bala_not_the_printed_jaimini_start() -> None:
|
||||
# Printed Jaimini rashi bala on this chart starts Pisces. Short-method totals tie,
|
||||
# and the karaka rank of the lords selects Virgo. The report keeps the short method.
|
||||
def test_same_goel_chart_niryaana_uses_pvr_not_the_shoola_rule() -> None:
|
||||
# 原值:首运 Virgo,十二段都是 9 年,并有从 Virgo 起的子运。
|
||||
# 新值:profile pvr_niryaana_shoola_v1,首运 Aries、顺行,年数 7/8/9,没有子运。
|
||||
# 原因:产品 2026-10-18 推翻 BUG-1283 所选的 goel_nirayana_shoola_v1。那套和 Shoola 是同一张表。
|
||||
packet = calculate_rashi_dashas(date(2001, 1, 1), 5 * 30 + 1.55, GOEL_LONS)
|
||||
niryaana = packet["niryaana_shoola"]
|
||||
assert niryaana["periods"][0]["sign"] == "Virgo"
|
||||
assert [row["years"] for row in niryaana["periods"]] == [9] * 12
|
||||
assert niryaana["periods"][0]["antardasha"][0]["sign"] == "Virgo"
|
||||
assert niryaana["periods"][0]["antardasha"][0]["end"] == "2001-10-01"
|
||||
shoola = packet["shoola"]
|
||||
assert niryaana["profile_id"] == "pvr_niryaana_shoola_v1"
|
||||
assert niryaana["status"] == "listed"
|
||||
assert niryaana["direction"] == "forward"
|
||||
assert niryaana["periods"][0]["sign"] == "Aries"
|
||||
assert [row["years"] for row in niryaana["periods"][:3]] == [7, 8, 9]
|
||||
assert "antardasha" not in niryaana["periods"][0]
|
||||
assert (
|
||||
niryaana["periods"][0]["sign"] != shoola["periods"][0]["sign"]
|
||||
or [row["years"] for row in niryaana["periods"]] != [row["years"] for row in shoola["periods"]]
|
||||
)
|
||||
|
||||
|
||||
def test_drig_aspect_order_for_a_cancer_lagna_and_narayana_style_years() -> None:
|
||||
@@ -152,7 +163,128 @@ def test_report_modules_do_not_call_the_old_shoola_or_niryaana_helpers() -> None
|
||||
assert all(not item.split(".")[0] == "jhora" for item in imported)
|
||||
|
||||
|
||||
def test_niryaana_shoola_starts_from_the_stronger_second_or_eighth() -> None:
|
||||
taurus = select_niryaana_shoola_start_sign(ascendant_longitude=0.0, planet_longitudes={"Mars": 31.0})
|
||||
scorpio = select_niryaana_shoola_start_sign(ascendant_longitude=0.0, planet_longitudes={"Mars": 211.0})
|
||||
assert taurus["selected_sign"] == "Taurus"
|
||||
assert scorpio["selected_sign"] == "Scorpio"
|
||||
taurus_rows = niryaana_shoola_mahadashas(date(2000, 1, 1), taurus["selected_sign_idx"])
|
||||
scorpio_rows = niryaana_shoola_mahadashas(date(2000, 1, 1), scorpio["selected_sign_idx"])
|
||||
assert [row["sign"] for row in taurus_rows[:2]] == ["Taurus", "Aries"]
|
||||
assert [row["sign"] for row in scorpio_rows[:2]] == ["Scorpio", "Libra"]
|
||||
|
||||
|
||||
def test_niryaana_shoola_odd_signs_go_forward_and_even_signs_go_backward() -> None:
|
||||
even_seed = niryaana_shoola_applicability(ascendant_longitude=0.0, second_strength=2.0, eighth_strength=1.0)
|
||||
odd_seed = select_niryaana_shoola_start_sign(ascendant_longitude=45.0, planet_longitudes={"Mars": 61.0})
|
||||
assert even_seed["starting_sign"] == "Taurus"
|
||||
assert even_seed["direction"] == "reverse"
|
||||
assert odd_seed["selected_sign"] == "Gemini"
|
||||
odd_rows = niryaana_shoola_mahadashas(date(2000, 1, 1), odd_seed["selected_sign_idx"])
|
||||
assert [row["sign"] for row in odd_rows[:2]] == ["Gemini", "Cancer"]
|
||||
assert [row["years"] for row in odd_rows[:3]] == [9, 7, 8]
|
||||
|
||||
|
||||
def test_niryaana_shoola_given_strengths_match_the_upstream_mahadasha_golden() -> None:
|
||||
# Golden copied from upstream 9f08b57b tests/test_niryaana_shoola_dasha.py
|
||||
# test_niryaana_shoola_uses_modality_years_and_parity_direction.
|
||||
picked = niryaana_shoola_applicability(
|
||||
ascendant_longitude=15.0, second_strength=2.0, eighth_strength=1.0,
|
||||
)
|
||||
rows = niryaana_shoola_mahadashas(date(2000, 1, 1), picked["starting_sign_index"])
|
||||
assert [row["sign"] for row in rows[:3]] == ["Taurus", "Aries", "Pisces"]
|
||||
assert [row["years"] for row in rows[:3]] == [8, 7, 9]
|
||||
assert [row["sign"] for row in rows] == [
|
||||
"Taurus", "Aries", "Pisces", "Aquarius", "Capricorn", "Sagittarius",
|
||||
"Scorpio", "Libra", "Virgo", "Leo", "Cancer", "Gemini",
|
||||
]
|
||||
assert [row["years"] for row in rows] == [8, 7, 9, 8, 7, 9, 8, 7, 9, 8, 7, 9]
|
||||
assert sum(row["years"] for row in rows) == 96
|
||||
assert all("antardasha" not in row for row in rows)
|
||||
|
||||
|
||||
def test_two_public_charts_match_the_upstream_start_signs() -> None:
|
||||
# Claude 10-18, upstream 9f08b57b: one chart starts in Sagittarius, one in Aquarius.
|
||||
# These two public charts are the pair. Sequences are the PVR mahadashas from that start.
|
||||
expected = {
|
||||
"brad_pitt": (
|
||||
"Sagittarius",
|
||||
["Sagittarius", "Capricorn", "Aquarius", "Pisces", "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio"],
|
||||
[9, 7, 8, 9, 7, 8, 9, 7, 8, 9, 7, 8],
|
||||
),
|
||||
"jolie": (
|
||||
"Aquarius",
|
||||
["Aquarius", "Pisces", "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn"],
|
||||
[8, 9, 7, 8, 9, 7, 8, 9, 7, 8, 9, 7],
|
||||
),
|
||||
}
|
||||
sources = {
|
||||
"brad_pitt": ROOT / "tests/indastro_cases.json",
|
||||
"jolie": ROOT / "tests/celebrity_cases.json",
|
||||
}
|
||||
from scripts.jyotish_engine import compute_chart_data
|
||||
|
||||
for case_id, (start, signs, years) in expected.items():
|
||||
rows = json.loads(sources[case_id].read_text(encoding="utf-8"))
|
||||
birth = next(row for row in rows if row["id"] == case_id)
|
||||
chart, _asc, _jd, _ayanamsa = compute_chart_data(
|
||||
birth["year"], birth["month"], birth["day"], birth["hour"], birth["minute"],
|
||||
birth["lat"], birth["lon"], birth["tz"], second=birth.get("second") or 0,
|
||||
ayanamsa_name="lahiri",
|
||||
)
|
||||
longitudes = {
|
||||
name: float(row["degree_raw"])
|
||||
for name, row in chart["planets"].items()
|
||||
if isinstance(row, dict) and "degree_raw" in row
|
||||
}
|
||||
section = calculate_rashi_dashas(
|
||||
chart["birth_info"]["date"], float(chart["ascendant"]["degree_raw"]), longitudes,
|
||||
)["niryaana_shoola"]
|
||||
assert section["status"] == "listed"
|
||||
assert section["direction"] == "forward"
|
||||
assert [row["sign"] for row in section["periods"]] == signs
|
||||
assert section["periods"][0]["sign"] == start
|
||||
assert [row["years"] for row in section["periods"]] == years
|
||||
assert all("antardasha" not in row for row in section["periods"])
|
||||
|
||||
|
||||
def test_niryaana_shoola_tie_stays_off_the_table() -> None:
|
||||
tied = {name: 3 * 30 + 10.0 for name in ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn")}
|
||||
section = calculate_rashi_dashas(date(2001, 1, 1), 0.5, tied)["niryaana_shoola"]
|
||||
assert section["status"] == "omitted"
|
||||
assert section["periods"] == []
|
||||
assert "本轮不上表" in section["reason_zh"]
|
||||
assert "二宫与八宫力量相同" in section["reason_zh"]
|
||||
|
||||
|
||||
def test_niryaana_shoola_counts_both_nodes_and_ignores_outer_planets() -> None:
|
||||
# Same placements as upstream 258e08e3.
|
||||
occupancy = select_niryaana_shoola_start_sign(
|
||||
ascendant_longitude=0.0,
|
||||
planet_longitudes={"Rahu": 31.0, "Ketu": 211.0, "Pluto": 31.0},
|
||||
)
|
||||
aspects = select_niryaana_shoola_start_sign(
|
||||
ascendant_longitude=0.0,
|
||||
planet_longitudes={"Rahu": 1.0, "Ketu": 181.0},
|
||||
)
|
||||
assert occupancy["included_bodies"][-2:] == ["Rahu", "Ketu"]
|
||||
assert occupancy["candidates"][1]["occupants"] == ["Rahu"]
|
||||
assert occupancy["candidates"][7]["occupants"] == ["Ketu"]
|
||||
assert "Pluto" not in occupancy["candidates"][1]["occupants"]
|
||||
assert aspects["candidates"][1]["rasi_aspecting_planets"] == ["Ketu"]
|
||||
assert aspects["candidates"][7]["rasi_aspecting_planets"] == ["Rahu"]
|
||||
without_outer = select_niryaana_shoola_start_sign(ascendant_longitude=0.0, planet_longitudes={"Mars": 31.0})
|
||||
with_outer = select_niryaana_shoola_start_sign(
|
||||
ascendant_longitude=0.0, planet_longitudes={"Mars": 31.0, "Uranus": 211.0},
|
||||
)
|
||||
assert without_outer["selected_sign"] == with_outer["selected_sign"] == "Taurus"
|
||||
assert "Uranus" not in with_outer["candidates"][7]["occupants"]
|
||||
|
||||
|
||||
def test_pyjhora_fixture_diffs_stay_on_the_recorded_steps() -> None:
|
||||
# 原值:niryaana_shoola 的 native 行与 Shoola 一样,从上升或第七宫起、每段 9 年,并带子运差异。
|
||||
# 新值:同一键改为 PVR 主运的星座和年数;diff_steps 按新表相对 PyJHora 重记。
|
||||
# 原因:产品 2026-10-18 改口径。PyJHora 对照行没有重跑,只改我们自己的期望。
|
||||
payload = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
||||
assert payload["pyjhora_version"] == "4.8.7"
|
||||
assert payload["generator_python_has_pyjhora"] is True
|
||||
@@ -227,6 +359,10 @@ def test_jobs_chapter_names_the_method_and_leaves_kendradi_out() -> None:
|
||||
text = render_rashi_dasha_chapter(packet)
|
||||
for title in ("Sthira", "Niryaana Shoola", "Drig", "Navamsha", "Shoola", "Chara"):
|
||||
assert f"### {title}" in text
|
||||
niryaana = text.split("### Niryaana Shoola", 1)[1].split("### ", 1)[0]
|
||||
assert "pvr_niryaana_shoola_v1" in niryaana
|
||||
assert "子运规则未定,暂不列出。" in niryaana
|
||||
assert "| 序号 | 主运 | 子运 |" not in niryaana
|
||||
assert "本站选用的方法:" in text
|
||||
assert "未与外部软件逐一核对" in text
|
||||
assert "K.N. Rao" in text
|
||||
@@ -236,6 +372,9 @@ def test_jobs_chapter_names_the_method_and_leaves_kendradi_out() -> None:
|
||||
assert "| 序号 | 星座 |" not in kendradi
|
||||
english = render_rashi_dasha_chapter({**packet, "report_language": "en"})
|
||||
assert "Method used on this site:" in english
|
||||
english_niryaana = english.split("### Niryaana Shoola Dasha", 1)[1].split("### ", 1)[0]
|
||||
assert "pvr_niryaana_shoola_v1" in english_niryaana
|
||||
assert "Child periods stay off this table" in english_niryaana
|
||||
assert "This report does not include Lagna Kendradi" in english
|
||||
assert "not checked one-by-one against external software." in english
|
||||
leaked = [char for char in english if "\u4e00" <= char <= "\u9fff"]
|
||||
|
||||
Reference in New Issue
Block a user