Round-7 option A is text-first, so Scorpio and Aquarius lords, the Ketu-only degree reversal, and node exaltation years now live in the Rath display layer. Scoring files stay unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
314 lines
19 KiB
Python
314 lines
19 KiB
Python
"""Sanjay Rath Narayana Dasha profile vs the book tables (BUG-1215 / BUG-1218).
|
||
|
||
The rule ledger and the book rows are ported verbatim from upstream
|
||
yinduzhanxing c7117f92 (`references/oracle/sanjay_rath_narayana_book_ledger_2026_08_28.json`,
|
||
Tables 15 / 17 / 18). Birth data are the published charts the book tables are
|
||
computed for (public figures / the book's own example chart):
|
||
|
||
- Table 15: Sri Aurobindo, 1872-08-15 05:00 LMT, Calcutta 22N34 88E22;
|
||
- Table 17: the book's "standard nativity", 1963-08-07 21:15 IST, Sambalpur 21N28 84E01;
|
||
- Table 18: Indira Gandhi, 1917-11-19 23:11 IST, Allahabad 25N27 81E51.
|
||
|
||
Lahiri ayanamsa. With the book's start sign, all 36 MD signs and years match.
|
||
Start sign by the round-5 seven-level rule (astrologer 2026-10-03): Table 15
|
||
Cancer (level 1) and Table 17 Pisces (level 2) reproduce; Table 18 does not —
|
||
the general rule decides Capricorn at level 5 (Cancer lord Moon 5.6 deg vs
|
||
Capricorn lord Saturn 21.8 deg). Red line 3 as revised by the product owner
|
||
(2026-10-03 evening, audit docs/research/shared_engine_reuse_audit_2026_10_03.md):
|
||
Table 18 is a book-internal contradiction (the hand example's reasoning reads
|
||
literally as Capricorn and the book's Table 27 software verdict is Capricorn),
|
||
reproduced only with the Cancer seed given explicitly and flagged
|
||
`book_example_exception`; the general rule's Capricorn is output unchanged.
|
||
Further acceptance evidence below: the book's five D1 software-verdict tables
|
||
(30 sign pairs, 28 match) and Table 25's 36 sub-period rows (24 match).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import json
|
||
import re
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from scripts.jyotish_engine import compute_chart_data
|
||
from scripts.narayana_rath import SIGNS, antardasha, calc_narayana_rath, md_order
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
LEDGER = ROOT / "references/oracle/sanjay_rath_narayana_book_ledger_2026_08_28.json"
|
||
UPSTREAM_BLOBS = { # c7117f92
|
||
"references/oracle/sanjay_rath_narayana_book_ledger_2026_08_28.json": "a52f181c22ba1b1e6a330b73711eb0e791785e56",
|
||
"references/oracle/chara_narayana_book_golden_cases_2026_08_28.json": "552c40448502c15a47035da064914dd76a4d919b",
|
||
"docs/research/sanjay_rath_narayana_book_ledger_2026_08_28.md": "8d18c2320d32dcd364784351ac52dc8bcb4ef053",
|
||
}
|
||
CHARTS = {
|
||
"Table 15": (1872, 8, 15, 5, 0, 22.5667, 88.3667, 88.3667 / 15),
|
||
"Table 17": (1963, 8, 7, 21, 15, 21.4667, 84.0167, 5.5),
|
||
"Table 18": (1917, 11, 19, 23, 11, 25.45, 81.85, 5.5),
|
||
}
|
||
|
||
|
||
def _book(table: str) -> dict:
|
||
return next(case for case in json.loads(LEDGER.read_text(encoding="utf-8"))["golden_cases"] if case["source_table"] == table)
|
||
|
||
|
||
def _chart(table: str):
|
||
chart, asc, *_ = compute_chart_data(*CHARTS[table], ayanamsa_name="lahiri")
|
||
return asc, {name: row["degree"] for name, row in chart["planets"].items()}
|
||
|
||
|
||
@pytest.mark.parametrize("path,blob", sorted(UPSTREAM_BLOBS.items()))
|
||
def test_ported_files_are_the_upstream_blobs(path: str, blob: str) -> None:
|
||
data = (ROOT / path).read_bytes()
|
||
assert hashlib.sha1(b"blob %d\0" % len(data) + data).hexdigest() == blob
|
||
|
||
|
||
@pytest.mark.parametrize("table", sorted(CHARTS))
|
||
def test_book_start_sign_reproduces_order_and_years(table: str) -> None:
|
||
book = _book(table)
|
||
asc, lons = _chart(table)
|
||
result = calc_narayana_rath(asc, lons, seed_sign_idx=SIGNS.index(book["start_sign"]), cycle_count=1)
|
||
assert [row["sign"] for row in result["mahadasha"]] == [row[0] for row in book["rows"]]
|
||
# 原值:年数逐行等于书印值
|
||
# 新值:Table 15 仍等于书印值;Table 17 / 18 见 NEW_EXPLICIT_YEARS(年数不再作通过线)
|
||
# 原因:BUG-1227 第七轮选 A,加上 BUG-1229 罗计旺陷 ±1
|
||
expected_years = NEW_EXPLICIT_YEARS.get(table, [row[1] for row in book["rows"]])
|
||
assert [row["years"] for row in result["mahadasha"]] == expected_years
|
||
|
||
|
||
def test_saturn_in_the_start_sign_gives_the_zodiacal_order() -> None:
|
||
asc, lons = _chart("Table 18") # Saturn in Cancer
|
||
assert md_order(SIGNS.index("Cancer"), lons)["direction"] == "table11_saturn_zodiacal"
|
||
asc, lons = _chart("Table 15")
|
||
assert md_order(SIGNS.index("Cancer"), lons)["direction"] == "table10_general"
|
||
|
||
|
||
def test_start_sign_by_the_seven_level_rule() -> None:
|
||
"""原值:现状钉住——Table 17 选出处女(原书双鱼),Table 18 平局(巨蟹 / 摩羯),只用前两级
|
||
新值:七级通则——Table 15 巨蟹(第 1 级)、Table 17 双鱼(第 2 级,三角色木星兼宫主记两次)、
|
||
Table 18 摩羯(第 5 级,宫主度数),与原书巨蟹不符;书内特例(AK)只在七级全平时可用,此盘够不着
|
||
原因:BUG-1218,占星师第五轮问 1、问 2"""
|
||
asc, lons = _chart("Table 15")
|
||
result = calc_narayana_rath(asc, lons, cycle_count=1)
|
||
assert (result["status"], result["seed_sign"], result["strength_level_decided"]) == ("partial_verified", "Cancer", 1)
|
||
assert [row["sign"] for row in result["mahadasha"]] == [row[0] for row in _book("Table 15")["rows"]]
|
||
asc, lons = _chart("Table 17")
|
||
result = calc_narayana_rath(asc, lons, cycle_count=1)
|
||
assert (result["status"], result["seed_sign"], result["strength_level_decided"]) == ("partial_verified", "Pisces", 2)
|
||
pisces = result["seed_selection"]["candidates"]["Pisces"]
|
||
assert [row["role"] for row in pisces["role_factors"]] == ["jupiter", "sign_lord"]
|
||
assert result["seed_selection"]["candidates"]["Virgo"]["aspect_factor_count"] == 1
|
||
assert [row["sign"] for row in result["mahadasha"]] == [row[0] for row in _book("Table 17")["rows"]]
|
||
# 原值:年数 12/12 等于 Table 17 书印值(天蝎 10、水瓶 8)
|
||
# 新值:10/12;天蝎计都 2(基本 1,入旺 +1),水瓶土星 1。Chart 3 水瓶印 9
|
||
# 原因:BUG-1227 第七轮选 A,年数不再作通过线;BUG-1229
|
||
assert [row["years"] for row in result["mahadasha"]] == NEW_EXPLICIT_YEARS["Table 17"]
|
||
asc, lons = _chart("Table 18")
|
||
for allow in (False, True):
|
||
result = calc_narayana_rath(asc, lons, cycle_count=1, allow_book_example=allow)
|
||
assert (result["seed_sign"], result["strength_level_decided"]) == ("Capricorn", 5)
|
||
assert result["book_example_exception"] is None
|
||
values = {sign: row["level_values"] for sign, row in result["seed_selection"]["candidates"].items()}
|
||
assert values["Cancer"][:4] == values["Capricorn"][:4] # occupants 1:1, roles 2:2, no dignity, both movable
|
||
assert values["Cancer"][4] < values["Capricorn"][4] # Moon 5.6 deg < Saturn 21.8 deg
|
||
assert _book("Table 18")["start_sign"] == "Cancer"
|
||
|
||
|
||
def test_antardasha_starts_from_the_lord_of_the_stronger_of_md_and_seventh() -> None:
|
||
"""原值:子运从大运星座本身起(Table 13),巨蟹大运 → 巨蟹、双子、金牛…
|
||
新值:比较大运星座与其第 7 宫,取较强者,从该宫宫主所在星座起,12 等分;Table 13 / 11 / 14 由大运星座里的土星 / 计都决定
|
||
原因:BUG-1218,占星师第五轮问 4(p.48 Arambha Rasi,p.50 说明 2)"""
|
||
asc, lons = _chart("Table 15")
|
||
md = calc_narayana_rath(asc, lons, cycle_count=1)["mahadasha"][0] # Cancer, 7 years; Mars and Jupiter there
|
||
ad = antardasha(md, lons)
|
||
rule = ad["antardasha_start_rule"]
|
||
assert (rule["stronger_sign"], rule["strength_level_decided"], rule["lord"], rule["start_sign"]) == ("Cancer", 1, "Moon", "Sagittarius")
|
||
assert rule["order_profile"] == "rath_table13_v1"
|
||
assert [row["sign"] for row in ad["rows"]][:3] == ["Sagittarius", "Capricorn", "Aquarius"] # Table 13, odd sign forward
|
||
assert all(abs(row["years"] - 7 / 12) < 1e-6 for row in ad["rows"])
|
||
asc, lons = _chart("Table 18")
|
||
md = calc_narayana_rath(asc, lons, seed_sign_idx=SIGNS.index("Cancer"), cycle_count=1)["mahadasha"][0]
|
||
ad = antardasha(md, lons)
|
||
rule = ad["antardasha_start_rule"]
|
||
# Cancer vs Capricorn: Capricorn at level 5; its lord Saturn sits in Cancer; Saturn in the MD sign -> Table 11.
|
||
assert (rule["stronger_sign"], rule["lord"], rule["start_sign"], rule["order_profile"]) == ("Capricorn", "Saturn", "Cancer", "rath_saturn_table11_v1")
|
||
assert [row["sign"] for row in ad["rows"]][:3] == ["Cancer", "Leo", "Virgo"]
|
||
|
||
|
||
def test_table18_only_with_the_explicit_cancer_seed_and_flagged() -> None:
|
||
asc, lons = _chart("Table 18")
|
||
book = _book("Table 18")
|
||
result = calc_narayana_rath(asc, lons, seed_sign_idx=SIGNS.index("Cancer"), cycle_count=1,
|
||
book_example="rath_p66_indira_ak")
|
||
assert result["status"] == "book_example_exception"
|
||
assert result["book_example_exception"] == "rath_p66_indira_ak"
|
||
assert result["book_example"]["book_internal_contradiction"] is True
|
||
assert [row["sign"] for row in result["mahadasha"]] == [row[0] for row in book["rows"]]
|
||
# 原值:显式巨蟹起运后年数等于书印值(水瓶 2)
|
||
# 新值:水瓶罗睺 1(基本 2,落陷 −1),其余 11 段不变
|
||
# 原因:BUG-1229,罗计按 Phalita 旺陷加减;顺序仍是 12/12
|
||
assert [row["years"] for row in result["mahadasha"]] == NEW_EXPLICIT_YEARS["Table 18"]
|
||
# The general rule's own verdict travels with it, unchanged.
|
||
assert (result["general_rule_selection"]["selected_sign"], result["general_rule_selection"]["strength_level_decided"]) == ("Capricorn", 5)
|
||
with pytest.raises(ValueError):
|
||
calc_narayana_rath(asc, lons, book_example="rath_p66_indira_ak")
|
||
|
||
|
||
# Book software verdicts ("Stronger planets and signs") for five D1 charts:
|
||
# Rath, Narayana Dasa, Tables 22 / 23 / 24 / 25 / 27 (book example charts
|
||
# 14, 15 = the standard nativity, 16, 17 = Krishnaraja Wadiyar IV, 22 = Indira
|
||
# Gandhi). Inputs and verdicts as transcribed in the audit (da95d92d).
|
||
def _dms(d: float, m: float) -> float:
|
||
return d + m / 60
|
||
|
||
|
||
AB = dict(Ar=0, Ta=1, Ge=2, Cn=3, Le=4, Vi=5, Li=6, Sc=7, Sg=8, Cp=9, Aq=10, Pi=11)
|
||
VERDICT_TABLES = {
|
||
"Table 22": ((1962, 3, 28, 6, 29, _dms(22, 6), _dms(85, 17), 5.5), ("Saturn", "Mars"), "Ar Sc Ge Cp Aq Pi"),
|
||
"Table 23": ((1963, 8, 7, 21, 15, _dms(21, 28), _dms(84, 1), 5.5), ("Saturn", "Mars"), "Li Ta Sg Cn Le Pi"),
|
||
"Table 24": ((1954, 12, 19, 0, 20, _dms(28, 36), _dms(77, 12), 5.5), ("Saturn", "Mars"), "Li Sc Sg Cn Aq Vi"),
|
||
"Table 25": ((1884, 6, 4, 10, 18, 12.0, _dms(76, 38), _dms(76, 38) / 15), ("Saturn", "Ketu"), "Li Ta Ge Cn Le Vi"),
|
||
"Table 27": ((1917, 11, 19, 23, 8, _dms(25, 28), _dms(81, 52), 5.5), ("Rahu", "Mars"), "Ar Sc Sg Cp Le Pi"),
|
||
}
|
||
# Expected misses, documented: Wadiyar Gemini/Sagittarius and Virgo/Pisces —
|
||
# the book takes Gemini and Virgo; the general rule decides at level 5
|
||
# (Mercury 1.3 deg vs Jupiter 10.0 deg) for Sagittarius and Pisces. Not
|
||
# sensitive to node mode or ayanamsa (audit). Round-6 ruling Q6b: the book's
|
||
# software used Rule 8 at the seventh level — a software special case; keep
|
||
# the general result, flagged `book_software_rule8_exception` (BUG-1226).
|
||
EXPECTED_SIGN_MISSES = {("Table 25", "Gemini", "Sagittarius"), ("Table 25", "Virgo", "Pisces")}
|
||
# Dual lords after round-7 option A (BUG-1227 resolved). 9/10: the one miss is
|
||
# Table 23 Scorpio (book Mars, p.43 (d) level 2 takes Ketu because Sagittarius
|
||
# has two Jupiter roles). Table 17 years are no longer a pass line:
|
||
# Scorpio book 10 -> Ketu 2 (base 1, exalted +1); Aquarius book 8 -> Saturn 1.
|
||
# Chart 3 prints Aquarius 9 (Rahu 8+1). Table 18 Aquarius book 2 -> Rahu 1
|
||
# (base 2, debilitated -1).
|
||
EXPECTED_CO_LORD_MISSES = {("Table 23", "Scorpio")}
|
||
# Explicit-seed years that no longer match the printed book rows. Order is unchanged.
|
||
NEW_EXPLICIT_YEARS = {
|
||
"Table 17": [12, 5, 2, 3, 5, 1, 1, 12, 2, 2, 9, 1],
|
||
"Table 18": [6, 9, 10, 2, 9, 5, 6, 1, 10, 4, 7, 5],
|
||
}
|
||
|
||
|
||
def test_book_software_verdicts_28_of_30_and_co_lords_9_of_10() -> None:
|
||
from scripts.narayana_rath import compare_signs, resolve_dual_lord
|
||
|
||
sign_misses, co_lord_misses, pairs = set(), set(), 0
|
||
decisions = []
|
||
for table, (birth, co_lords, stronger) in VERDICT_TABLES.items():
|
||
chart, _asc, *_ = compute_chart_data(*birth, ayanamsa_name="lahiri")
|
||
lons = {name: row["degree"] for name, row in chart["planets"].items()}
|
||
book = {AB[code] for code in stronger.split()}
|
||
for first in range(6):
|
||
second = first + 6
|
||
winner = first if first in book else second
|
||
result = compare_signs(first, second, lons)
|
||
pairs += 1
|
||
if result.get("selected_sign_idx") != winner:
|
||
assert result["strength_level_decided"] == 5
|
||
sign_misses.add((table, SIGNS[winner], SIGNS[second if winner == first else first]))
|
||
for sign_idx, want in zip((10, 7), co_lords):
|
||
decision = resolve_dual_lord(sign_idx, lons)
|
||
decisions.append((table, SIGNS[sign_idx], want, decision))
|
||
if decision.get("lord") != want:
|
||
co_lord_misses.add((table, SIGNS[sign_idx]))
|
||
assert pairs == 30
|
||
assert sign_misses == EXPECTED_SIGN_MISSES # 28/30, same two Wadiyar pairs
|
||
assert co_lord_misses == EXPECTED_CO_LORD_MISSES # 9/10
|
||
assert len(decisions) == 10
|
||
assert all(row[3]["rule"] == "d" and row[3]["status"] == "selected" for row in decisions)
|
||
scorpio = next(row[3] for row in decisions if row[0] == "Table 23" and row[1] == "Scorpio")
|
||
assert (scorpio["lord"], scorpio["strength_level_decided"]) == ("Ketu", 2)
|
||
|
||
|
||
# Table 25 (Wadiyar) sub-periods for the first three MDs, from upstream
|
||
# c7117f92 references/oracle/chara_narayana_rath_wadiyar_table25_replay_2026_09_07.json.
|
||
TABLE25_AD = {
|
||
"Cancer": ["Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces", "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo"],
|
||
"Gemini": ["Taurus", "Aries", "Pisces", "Aquarius", "Capricorn", "Sagittarius", "Scorpio", "Libra", "Virgo", "Leo", "Cancer", "Gemini"],
|
||
"Taurus": ["Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces", "Aries", "Taurus", "Gemini"],
|
||
}
|
||
|
||
|
||
def test_table25_sub_periods_24_of_36() -> None:
|
||
"""Cancer and Taurus (Saturn inside, Table 11) MDs: 12/12. Gemini MD: 0/12 —
|
||
the Gemini/Sagittarius comparison picks Sagittarius (level 5, the same
|
||
Wadiyar miss as above), so the ADs start from Jupiter's sign Cancer
|
||
instead of Mercury's sign Taurus. Current state, pinned."""
|
||
chart, asc, *_ = compute_chart_data(*VERDICT_TABLES["Table 25"][0], ayanamsa_name="lahiri")
|
||
lons = {name: row["degree"] for name, row in chart["planets"].items()}
|
||
mds = calc_narayana_rath(asc, lons, seed_sign_idx=SIGNS.index("Cancer"), cycle_count=1)["mahadasha"][:3]
|
||
assert [row["sign"] for row in mds] == ["Cancer", "Gemini", "Taurus"]
|
||
matched = {}
|
||
for md in mds:
|
||
rows = [row["sign"] for row in antardasha(md, lons)["rows"]]
|
||
matched[md["sign"]] = sum(a == b for a, b in zip(rows, TABLE25_AD[md["sign"]]))
|
||
assert matched == {"Cancer": 12, "Gemini": 0, "Taurus": 12}
|
||
gemini = antardasha(mds[1], lons)["antardasha_start_rule"]
|
||
assert (gemini["stronger_sign"], gemini["strength_level_decided"], gemini["start_sign"]) == ("Sagittarius", 5, "Cancer")
|
||
|
||
|
||
def test_rath_profile_is_display_only() -> None:
|
||
"""原值:没有任何脚本导入 narayana_rath(`users == []`)
|
||
新值:只有展示层 narayana_legacy_label 导入它;冻结的打分文件与 rectification/ 都不导入展示层或 Rath 模块
|
||
原因:BUG-1218 T4(产品负责人 2026-10-03 晚恢复 T4):Rath 版与旧算法并列显示,打分与时间轴结论仍用旧算法"""
|
||
pattern = re.compile(r"\bnarayana_rath\b")
|
||
users = sorted(path.name for path in (ROOT / "scripts").rglob("*.py")
|
||
if path.name != "narayana_rath.py" and pattern.search(path.read_text(encoding="utf-8")))
|
||
assert users == ["narayana_legacy_label.py"]
|
||
frozen = json.loads((ROOT / "references/rectification_sealed_holdout.v1.json").read_text(encoding="utf-8"))
|
||
scoring = set(_production_files(frozen)) | {str(p.relative_to(ROOT)) for p in (ROOT / "scripts/rectification").rglob("*.py")}
|
||
display = re.compile(r"\b(narayana_rath|narayana_legacy_label)\b")
|
||
assert sorted(path for path in scoring if display.search((ROOT / path).read_text(encoding="utf-8"))) == []
|
||
|
||
|
||
def _production_files(record) -> list:
|
||
found = []
|
||
if isinstance(record, dict):
|
||
for key, value in record.items():
|
||
if key == "production_scoring_files" and isinstance(value, list):
|
||
found.extend(value)
|
||
else:
|
||
found.extend(_production_files(value))
|
||
elif isinstance(record, list):
|
||
for value in record:
|
||
found.extend(_production_files(value))
|
||
return found
|
||
|
||
|
||
def test_table25_taurus_md_is_the_saturn_exception_and_carries_the_book_divergence() -> None:
|
||
"""Round-6 ruling Q7 / BUG-1225: Saturn in the Taurus MD sign -> ADs always
|
||
zodiacal (p.51), 12/12 with the book; the p.47 / J.S. 2.4.31-32 statements
|
||
are listed as a book divergence, not applied."""
|
||
chart, asc, *_ = compute_chart_data(*VERDICT_TABLES["Table 25"][0], ayanamsa_name="lahiri")
|
||
lons = {name: row["degree"] for name, row in chart["planets"].items()}
|
||
taurus = calc_narayana_rath(asc, lons, seed_sign_idx=SIGNS.index("Cancer"), cycle_count=1)["mahadasha"][2]
|
||
ad = antardasha(taurus, lons)
|
||
rule = ad["antardasha_start_rule"]
|
||
assert taurus["sign"] == "Taurus" and rule["order_profile"] == "rath_saturn_table11_v1"
|
||
assert [row["sign"] for row in ad["rows"]] == TABLE25_AD["Taurus"]
|
||
assert rule["direction"]["direction"] == "zodiacal"
|
||
p47, js = rule["direction"]["book_divergence"]
|
||
assert (p47["direction"], p47["differs"]) == ("reverse", True) # Taurus is even: p.47 would run the ADs backwards
|
||
|
||
|
||
def test_wadiyar_pairs_are_the_flagged_book_software_rule8_exception() -> None:
|
||
"""Round-6 ruling Q6b (BUG-1226): the two Table 25 misses are exactly the
|
||
registered `book_software_rule8_exception` pairs; the general result is
|
||
kept and the note travels with every Rath output."""
|
||
from scripts.narayana_rath import BOOK_SOFTWARE_DIFFERENCES, BOOK_SOFTWARE_RULE8, compare_signs
|
||
|
||
entry = BOOK_SOFTWARE_DIFFERENCES[BOOK_SOFTWARE_RULE8]
|
||
assert BOOK_SOFTWARE_RULE8 == "book_software_rule8_exception"
|
||
flagged = {(entry["book_table"], pair["book"], pair["general_rule"]) for pair in entry["pairs"]}
|
||
assert flagged == EXPECTED_SIGN_MISSES
|
||
assert entry["kept"] == "general_rule" and entry["note"] and entry["note_en"]
|
||
chart, asc, *_ = compute_chart_data(*VERDICT_TABLES["Table 25"][0], ayanamsa_name="lahiri")
|
||
lons = {name: row["degree"] for name, row in chart["planets"].items()}
|
||
for pair in entry["pairs"]:
|
||
result = compare_signs(SIGNS.index(pair["book"]), SIGNS.index(pair["general_rule"]), lons)
|
||
assert (result["selected_sign"], result["strength_level_decided"]) == (pair["general_rule"], pair["general_level"])
|
||
assert calc_narayana_rath(asc, lons, cycle_count=1)["known_book_differences"][BOOK_SOFTWARE_RULE8] == entry
|