feat(report): replace the reader edition with the full-data report

Generate the full-data edition (pl9_personal_long_report.v4) and remove the reader edition. English still contains Han, so the API withholds it (BUG-1272, investigating). This commit is not pushed to staging.
This commit is contained in:
jesse-ux
2026-10-08 17:59:48 +08:00
parent 1a8c97f64a
commit 2a43fc9598
32 changed files with 4039 additions and 133 deletions
+530
View File
@@ -0,0 +1,530 @@
"""English glossary for the PL9 bilingual AI-density export.
The yoga/dosha engines emit `effects` as Chinese short phrases and `combination`
as mixed Chinese/English strings. The English renderer must translate these
instead of deleting CJK and emitting a placeholder. This module holds the
Chinese -> English vocabulary used for that translation.
词汇来源:references/yoga_rules.json 的 effects 字段词频统计(高频词优先),
覆盖 yoga/dosha 解释层最常出现的短句;低频词若未命中则按省略处理,不输出占位符。
"""
from __future__ import annotations
# 瑜伽/宫位 effects 中文短句 -> 英文。按词频从高到低整理。
EFFECTS_EN: dict[str, str] = {
"领域受限": "domain constrained",
"需要额外努力": "requires additional effort",
"充足": "adequate",
"转化困境为机遇": "turns hardship into opportunity",
"不讨喜": "not widely liked",
"事业通过智慧实现": "career achieved through wisdom",
"享受丰富": "enjoys abundance",
"人缘好": "well liked",
"命运力量在角宫": "destiny power in angular houses",
"命运助力明显": "strong support from destiny",
"善于辞令": "eloquent speech",
"基础发展稳定": "stable foundational development",
"多元才华": "versatile talents",
"子女受其庇护": "children receive protection",
"待补充": "to be supplemented",
"心地善良": "kind-hearted",
"心智有背景支持": "mind supported by background conditions",
"心智稳定性增强": "increased mental stability",
"性情愉快": "pleasant temperament",
"性格坚定": "steadfast character",
"持续收入": "steady income",
"政治才华": "political talent",
"月亮前后均有支撑": "Moon supported on both sides",
"物质充裕": "material abundance",
"独处中成长": "growth through solitude",
"生活舒适": "comfortable life",
"盈利丰厚": "substantial profits",
"真诚待人": "sincere dealings",
"祖先福报影响子女": "ancestral blessings affect children",
"经济稳定": "financial stability",
"自我、财富、努力、根基彼此支撑": "self, wealth, effort, and foundations support one another",
"自我表达": "self-expression",
"言辞刻薄": "harsh speech",
"贵人带来社会地位": "benefactors bring social status",
"贵人或隐性资源": "benefactors or hidden resources",
"资源与适应力较强": "strong resources and adaptability",
# 才能 / 事业 / 地位(高频)
"卓越才能": "exceptional talent",
"领域领军": "leading in one's field",
"人格魅力": "personal magnetism",
"受人尊敬": "widely respected",
"命运眷顾": "favored by destiny",
"智慧": "wisdom",
"事业成功": "career success",
"长寿": "longevity",
"财富": "wealth",
"艺术才华": "artistic talent",
"社会地位": "social standing",
"品德高尚": "noble character",
"逆境崛起": "rises from adversity",
"因祸得福": "turns misfortune into fortune",
"权力": "power",
"权力地位": "power and status",
"社会地位高": "high social status",
"智慧卓越": "outstanding wisdom",
"财富积累": "wealth accumulation",
"物质成功": "material success",
"财富充裕": "abundant wealth",
"自力更生": "self-reliant",
"学识渊博": "profound learning",
"婚姻美满": "happy marriage",
"审美卓越": "refined aesthetic sense",
"健康需注意": "watch health",
"稳定": "stability",
"领导力": "leadership",
"贵人运强": "strong benefactor luck",
"口才出众": "eloquent",
"受人爱戴": "beloved",
"精力充沛": "energetic",
"困境中成长": "grows through hardship",
"受人敬仰": "admired",
"精神修养": "spiritual cultivation",
"领导能力": "leadership ability",
"精神力量": "spiritual strength",
"财务困难": "financial difficulty",
"行动力": "initiative",
"权威地位": "authority",
"精神成就": "spiritual attainment",
"物质享受": "material comfort",
"婚姻幸福": "marital happiness",
"学术成就": "academic achievement",
"创造力": "creativity",
"高位": "high position",
"身体健康": "good health",
"社会影响力": "social influence",
"精神导师": "spiritual mentor",
"克服困难": "overcomes obstacles",
"转化能力": "transformative ability",
"逆境中崛起": "rises in adversity",
"投资收益": "investment returns",
"财运亨通": "thriving fortune",
"生活幸福": "happy life",
"生活富足": "prosperous life",
"名声良好": "good reputation",
"精神挑战": "spiritual challenge",
"行动力强": "strong initiative",
"商业头脑": "business acumen",
"意志坚定": "strong will",
"领导才能": "leadership talent",
"领导气质": "leadership quality",
"事业辉煌": "brilliant career",
"智慧与行动力兼备": "wisdom with drive",
"正义感强": "strong sense of justice",
"智力超群": "exceptional intelligence",
"智慧通达": "penetrating wisdom",
"多才多艺": "versatile",
"生活稳定": "stable life",
"有依靠": "well-supported",
"精神成长": "spiritual growth",
"家庭幸福": "happy family",
"影响力": "influence",
"自我实现": "self-realization",
"子女运好": "good fortune with children",
"王者气质": "regal bearing",
"艺术气质": "artistic temperament",
"家庭问题": "family issues",
"精神压力": "mental stress",
"精神领导力": "spiritual leadership",
"情感丰富": "emotionally rich",
"全面成功": "all-round success",
"子女有成就": "accomplished children",
"子女智慧": "wise children",
"婚姻和谐": "harmonious marriage",
"配偶有魅力": "attractive spouse",
"婚姻延迟": "delayed marriage",
"名声显赫": "great renown",
"名声": "reputation",
"社交网络强大": "strong social network",
"政治权力": "political power",
"战胜对手": "overcomes rivals",
"祖先庇佑": "ancestral blessings",
"宗教权威": "religious authority",
"收入不稳定": "unstable income",
"家庭和谐": "family harmony",
"子孙兴旺": "flourishing descendants",
"后代繁荣": "prosperous descendants",
"后代有成就": "accomplished descendants",
"家族延续": "family continuity",
"学识": "learning",
"内在平静": "inner peace",
"慈悲": "compassion",
"保护": "protection",
"保守": "conservative",
"旅行": "travel",
"变动": "change",
"海外": "overseas",
"不稳定": "instability",
"传统": "tradition",
"孤独": "solitude",
"人际关系差": "poor relationships",
"兄弟和睦": "harmony with siblings",
"勇气": "courage",
"竞争力": "competitiveness",
# 事业 / 财富 补充
"事业有成": "career achievement",
"事业顺利": "smooth career",
"事业巅峰": "career peak",
"事业成就": "career accomplishment",
"职业使命感强": "strong sense of vocation",
"政府高层职位": "senior government post",
"人脉资源强": "strong network resources",
"收入与社会地位同步提升": "rising income and status",
"慢而稳的事业成功": "slow but steady career success",
"专业化": "specialization",
"军事/体育/工程领域成功": "success in military, sports or engineering",
"竞争意识强": "strong competitive drive",
"敌人变朋友": "enemies become friends",
"竞争中获利": "profits through competition",
"愿望实现": "wishes fulfilled",
"愿望通过智慧实现": "wishes fulfilled through wisdom",
"愿望通过地位实现": "wishes fulfilled through status",
"才华变事业": "talent turned into career",
"创造力变现": "creativity monetized",
"商业成功": "business success",
"物质丰裕": "material abundance",
"收入稳定": "stable income",
"家庭资产": "family assets",
"财富自主": "financial independence",
"自力更生致富": "self-made wealth",
"独立创业": "independent entrepreneurship",
"财富增长": "wealth growth",
"财富丰厚": "substantial wealth",
"稳定财富": "stable wealth",
"巨量财富": "great wealth",
"持续财富积累": "sustained wealth accumulation",
"投资回报": "investment returns",
"投资成功": "successful investment",
"通过才华/运气致富": "wealth through talent or luck",
"祖先财富": "ancestral wealth",
"储蓄丰厚": "ample savings",
"理财能力": "financial acumen",
"谨慎理财": "prudent money management",
# 婚姻 / 家庭
"婚姻稳定": "stable marriage",
"优质婚姻": "quality marriage",
"婚姻显赫": "prestigious marriage",
"婚姻挑战": "marital challenges",
"配偶杰出": "outstanding spouse",
"配偶贤良": "virtuous spouse",
"配偶支持": "supportive spouse",
"配偶互相支持": "mutually supportive spouses",
"配偶有成就": "accomplished spouse",
"配偶有社会地位": "spouse with social standing",
"配偶带来财富": "spouse brings wealth",
"配偶带来好运": "spouse brings good fortune",
"配偶关系紧张": "tense spousal relationship",
"家庭和睦": "family harmony",
"后代昌盛": "prosperous descendants",
"子女有出息": "promising children",
"子女缘厚": "strong bond with children",
"多子多福": "many children, many blessings",
"子女贤良": "virtuous children",
"子女众多": "many children",
"独生子女": "only child",
"子女少但优秀": "few but accomplished children",
"子女带来财富": "children bring wealth",
"子女带来名声": "children bring renown",
# 健康 / 身心
"健康良好": "good health",
"健康问题": "health concerns",
"慢性疾病": "chronic illness",
"慢性病倾向": "tendency to chronic illness",
"少病少灾": "few illnesses",
"活力充沛": "full of vitality",
"生命力强": "strong vitality",
"体质较弱": "weak constitution",
"疾病": "illness",
"延寿": "extended longevity",
"长寿健康": "long and healthy life",
"智慧长寿": "wisdom and longevity",
"寿命稳定": "stable lifespan",
"疾病抵抗力": "disease resistance",
"免疫系统": "immune system",
# 心智 / 灵性
"智慧超群": "outstanding wisdom",
"辩才无碍": "unimpeded eloquence",
"沟通能力": "communication ability",
"学业有成": "academic success",
"知识渊博": "vast knowledge",
"教育成功": "educational success",
"意志力": "willpower",
"苦行": "austerity",
"精神修行": "spiritual practice",
"内在力量": "inner strength",
"家庭导向": "family-oriented",
"安全感": "sense of security",
"勤劳": "diligent",
"节俭": "frugal",
"积累": "accumulation",
"务实": "pragmatic",
"家庭": "family",
"自由": "freedom",
"社交": "social",
"起伏": "ups and downs",
"财务波动": "financial fluctuation",
"严厉": "strict",
"批评": "criticism",
"精神痛苦": "mental anguish",
"宗教": "religion",
"祭祀": "ritual",
"精神追求": "spiritual pursuit",
"灵性成长": "spiritual growth",
"灵性觉醒": "spiritual awakening",
"灵性智慧": "spiritual wisdom",
"终极解脱": "final liberation",
"解脱": "liberation",
"解脱智慧": "wisdom of liberation",
"超越生死": "beyond birth and death",
"灵性洞察": "spiritual insight",
"精神探索": "spiritual exploration",
"精神圆满": "spiritual fulfillment",
"宇宙和谐": "cosmic harmony",
"终极智慧": "ultimate wisdom",
"天才级智力": "genius-level intellect",
"超强记忆力": "exceptional memory",
"高等教育": "higher education",
"哲学洞察": "philosophical insight",
"艺术天赋": "artistic gift",
"艺术创造力": "artistic creativity",
"音乐天赋": "musical gift",
"浪漫魅力": "romantic charm",
"美貌": "beauty",
"非传统思想": "unconventional thinking",
# 负向 / 挑战(少量关键词,避免占位)
"欺诈": "deceit",
"不诚实": "dishonesty",
"阴谋": "intrigue",
"信任危机": "trust issues",
"债务问题": "debt problems",
"财务不稳定": "financial instability",
"储蓄困难": "difficulty saving",
"人脉关系淡薄": "weak personal network",
"健康挑战": "health challenges",
"情感孤立": "emotional isolation",
"社交困难": "social difficulty",
"名声受损": "reputation damaged",
"意外事故": "accidents",
"身体虚弱": "physical frailty",
"视力问题": "vision problems",
"判断失误": "errors of judgment",
"需要努力": "requires effort",
"需要努力突破": "requires effort to break through",
"需要韧性": "requires resilience",
"需要节约": "requires thrift",
"需节俭": "needs thrift",
"挑战多": "many challenges",
"人生有挑战": "life has challenges",
"人生挑战": "life challenges",
"不安定": "unsettled",
"漂泊": "wandering",
"不安于室": "restless",
# 婚恋 / 家庭 / 健康补充(2026-09 全量导出缺词补齐)
"性格坚毅": "resolute character",
"口才与财富兼备": "eloquence with wealth",
"充满活力": "full of vigor",
"权力权威": "power and authority",
"个人魅力": "personal charm",
"需努力积累": "requires diligent accumulation",
"财务压力": "financial pressure",
"家庭纠纷": "family disputes",
"需节俭持家": "needs frugal household management",
"人生顺遂": "smooth and favorable life",
"灵性提升": "spiritual elevation",
"财富地位": "wealth and status",
"极高地位": "extremely high status",
"名声远扬": "far-reaching renown",
"道德致富": "wealth through integrity",
"慷慨大方": "generous",
"艺术财富": "artistic wealth",
"奢侈品": "luxury goods",
"财富波动": "wealth fluctuation",
"严重财务困难": "severe financial difficulty",
"需要极度节俭": "requires extreme thrift",
"需要匹配化解": "requires matching remedies",
"月亮受保护": "Moon protected",
"情感稳定": "emotional stability",
"全方位成功": "all-around success",
"四通八达": "well-connected in all directions",
"坚不可摧的成功": "unshakeable success",
"强大意志力": "strong willpower",
"星盘力量翻倍": "doubled chart strength",
"命运高度一致": "highly aligned destiny",
"宗教政治地位": "religious and political standing",
"太阳后方有行星支持": "planets following the Sun",
"表达力与行动力增强": "enhanced expression and drive",
"自我驱动力较强": "strong self-drive",
"太阳前方有行星铺垫": "planets preceding the Sun",
"内在准备力强": "strong inner preparation",
"重视计划与隐性资源": "values planning and hidden resources",
"太阳前后均有行星支撑": "planets supporting the Sun on both sides",
"自我表达较完整": "relatively complete self-expression",
"行动前后资源较足": "sufficient resources before and after action",
"资源积累能力": "resource accumulation ability",
"心智有后续支撑": "mind has follow-up support",
"配偶品德高尚": "virtuous spouse",
"经常旅行": "frequent travel",
"被亲属遗弃": "abandoned by relatives",
"缺乏家庭支持": "lack of family support",
"家庭关系疏远": "distant family relations",
"母亲健康不佳": "poor maternal health",
"与母亲缘薄": "weak bond with mother",
"母亲早逝": "early loss of mother",
"心动/感情激活观察": "romantic-activation observation",
"承诺掂量": "commitment weighing",
"不等于法律婚姻": "does not equal legal marriage",
}
# 英文星座名集合(用于 combination 黏连修复,避免与 dignity 词黏连)。
SIGNS_EN: frozenset[str] = frozenset({
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
})
# 英文行星名集合。
PLANETS_EN: frozenset[str] = frozenset({
"Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu",
})
# dignity / 强度词(用于黏连修复)。
DIGNITY_EN: frozenset[str] = frozenset({
"exalted", "debilitated", "strong", "weak", "moderate", "medium",
"own", "moolatrikona", "friendly", "neutral", "enemy", "combust",
})
# 中文行星名 / 星座名 / 结构词 -> 英文。用于 combination 混排串的逐词替换,
# 使删 CJK 后不产生黏连或残片。
CN_TERMS_EN: dict[str, str] = {
"自我": "self", "表现为": "expressed as", "节律": "rhythm", "学习": "learning",
"写作": "writing", "沟通": "communication", "移动": "movement", "自主行动": "independent action",
"居住": "residence", "家庭": "family", "内在安全感": "inner security", "资产基础": "asset foundation",
"先稳住根基再发挥": "stabilize foundations before expression", "祖先祭拜": "ancestral rites",
"捐赠黑芝麻和铁器": "donate black sesame and iron items", "供养婆罗门": "support Brahmins",
"相邻": "adjacent", "指标": "indicators", "内在": "inner", "星": "planet",
"1-4宫主形成命宫基础链": "1st-4th house lords form the ascendant foundational chain",
"4宫/4宫主受凶星、Rahu、Maandi/Mandi 等严重影响": "house 4/its lord severely affected by malefics, Rahu, and Maandi/Mandi",
"Dharidhra:财帛/收益主落凶宫或Lagna/Maraka/Dusthana组合": "Dharidhra: wealth/gains lord in a difficult house or Lagna/Maraka/Dusthana combination",
"Rahu在5且非土星Navamsa,或7宫主关联行星的Navamsa主落1/2/5": "Rahu in house 5 and not Saturn Navamsa, or Navamsa lord of a planet associated with the 7th lord falls in 1/2/5",
"月亮受凶星夹制/合相/相位,或4宫主Navamsa链最终落6/8/12": "Moon hemmed/conjunct/aspected by malefics, or the 4th lord's Navamsa chain ultimately falls in 6/8/12",
"第4宫有天然吉星/强星/吉性星座,或上升主入4宫且受吉星影响": "house 4 has natural benefic/strong planets or a benefic sign, or the ascendant lord enters house 4 and is influenced by benefics",
"行星集中在角宫(Chakra 轮盘)": "planets concentrated in angular houses (Chakra wheel)",
"中强": "medium-strong",
"基础链": "foundational chain", "形成": "forms", "分布在": "distributed across",
"六个星座": "six signs", "位于": "located in", "入": "enters",
"受凶星": "affected by malefics", "凶星": "malefic", "天然吉星": "natural benefic",
"吉性星座": "benefic sign", "关联": "associated with", "相位": "aspected",
"合相": "conjunct", "夹制": "hemmed", "最终落": "ultimately falls in",
"处于": "is in", "不利状态": "an unfavorable state", "严重影响": "severely affected",
"非": "not ", "角宫主": "angular house lord", "三方宫主": "trinal house lord",
"财帛": "wealth", "收益": "gains", "基础": "foundation", "自定义条件满足": "custom condition met",
"检测器未返回组合文本": "detector returned no combination text", "未返回": "not returned",
"候选组合": "Candidate combination",
"条件未齐,暂不作结果判断。": "Required conditions are incomplete; no outcome inference is made.",
"缺少条件输入": "Missing condition inputs",
"待评估": "pending evaluation", "七曜": "seven planets", "星座": "sign",
"行星": "planet", "不利": "unfavorable", "影响": "influence", "的": "'s",
"上升主": "ascendant lord", "宫主": "house lord", "互为": "mutual",
"合于": "conjunct", "变动星座": "mutable sign", "且": "and", "或": "or",
"强": "strong", "弱": "weak", "第": "house ", "宫": "",
"火星在第4宫(从月亮的2/12宫)": "Mars in house 4 (2nd/12th from Moon)",
"充足": "adequate",
"转化困境为机遇": "turns hardship into opportunity",
"不讨喜": "not widely liked", "事业通过智慧实现": "career achieved through wisdom",
"享受丰富": "enjoys abundance", "人缘好": "well liked",
"命运力量在角宫": "destiny power in angular houses", "命运助力明显": "strong support from destiny",
"善于辞令": "eloquent speech", "基础发展稳定": "stable foundational development",
"多元才华": "versatile talents", "子女受其庇护": "children receive protection",
"待补充": "to be supplemented", "心地善良": "kind-hearted",
"心智有背景支持": "mind supported by background conditions", "心智稳定性增强": "increased mental stability",
"性情愉快": "pleasant temperament", "性格坚定": "steadfast character",
"持续收入": "steady income", "政治才华": "political talent",
"月亮前后均有支撑": "Moon supported on both sides", "物质充裕": "material abundance",
"独处中成长": "growth through solitude", "生活舒适": "comfortable life",
"盈利丰厚": "substantial profits", "真诚待人": "sincere dealings",
"祖先福报影响子女": "ancestral blessings affect children", "经济稳定": "financial stability",
"自我、财富、努力、根基彼此支撑": "self, wealth, effort, and foundations support one another",
"自我表达": "self-expression", "言辞刻薄": "harsh speech",
"贵人带来社会地位": "benefactors bring social status", "贵人或隐性资源": "benefactors or hidden resources",
"资源与适应力较强": "strong resources and adaptability",
"太阳": "Sun",
"月亮": "Moon",
"水星": "Mercury",
"金星": "Venus",
"火星": "Mars",
"木星": "Jupiter",
"土星": "Saturn",
"罗睺": "Rahu",
"计都": "Ketu",
"白羊座": "Aries",
"金牛座": "Taurus",
"双子座": "Gemini",
"巨蟹座": "Cancer",
"狮子座": "Leo",
"处女座": "Virgo",
"天秤座": "Libra",
"天蝎座": "Scorpio",
"射手座": "Sagittarius",
"摩羯座": "Capricorn",
"宝瓶座": "Aquarius",
"水瓶座": "Aquarius",
"双鱼座": "Pisces",
"与": " and ",
"同在第": " together in house ",
"同处": " together in ",
"在第": " in house ",
"第": " house ",
"宫": " ",
"两侧均有行星": " planets on both sides ",
"一侧有行星": " planet on one side ",
"前后均有行星": " planets on both sides ",
# 结构词(宫主 / 落点 / 相位等),供 combination 逐词替换;按长度降序替换避免互相覆盖。
"宫主": " house lord",
"上升主": "lagna lord",
"命主": "lagna lord",
"凶宫": " dusthana",
"角宫": " kendra",
"吉星": " benefic",
"凶星": " malefic",
"落入": " in ",
"落陷": " debilitated",
"北交点": " Rahu",
"南交点": " Ketu",
"变动星座": " mutable sign",
"奇数星座": " odd signs",
"本命盘": " natal chart",
"合相": " conjunct",
"相位": " aspect",
"互照": " mutual aspect",
"夹制": " hemmed",
"关联": " aspected by ",
"有力": " strong",
"主": " lord",
"在": " in ",
"或": " or ",
"被": " by ",
"受": " by ",
}
# 组合条件整句 -> 英文(在逐词替换之前做全串精确匹配,避免整句被逐字拆坏)。
# 仅收录不含盘主专属行星/星座名的通用句,跨盘可复用。
COMBINATION_EN: dict[str, str] = {
"有Pitra Dosha迹象:Sun(1宫)与Rahu(2宫)合相/相邻": "Pitra Dosha indication: Sun (house 1) and Rahu (house 2) conjunct/adjacent",
"中性(Neutral)": "neutral (Neutral)",
"1-4宫主形成命宫基础链": "1st-4th house lords form the ascendant foundational chain",
"4宫/4宫主受凶星、Rahu、Maandi/Mandi 等严重影响": "house 4/its lord severely affected by malefics, Rahu, and Maandi/Mandi",
"Dharidhra:财帛/收益主落凶宫或Lagna/Maraka/Dusthana组合": "Dharidhra: wealth/gains lord in a difficult house or Lagna/Maraka/Dusthana combination",
"Rahu在5且非土星Navamsa,或7宫主关联行星的Navamsa主落1/2/5": "Rahu in house 5 and not Saturn Navamsa, or Navamsa lord of a planet associated with the 7th lord falls in 1/2/5",
"月亮受凶星夹制/合相/相位,或4宫主Navamsa链最终落6/8/12": "Moon hemmed/conjunct/aspected by malefics, or the 4th lord's Navamsa chain ultimately falls in 6/8/12",
"第4宫有天然吉星/强星/吉性星座,或上升主入4宫且受吉星影响": "house 4 has natural benefic/strong planets or a benefic sign, or the ascendant lord enters house 4 and is influenced by benefics",
"行星集中在角宫(Chakra 轮盘)": "planets concentrated in angular houses (Chakra wheel)",
"自定义条件满足": "custom condition satisfied",
"落陷取消格局": "cancellation of debilitation",
"上升主星有力+其他行星有力": "strong lagna lord and other strong planets",
"凶宫主在角宫": "dusthana lord in a kendra",
"命主在本命盘强(分盘瑜伽近似判断)": "lagna lord strong in natal chart (approximate varga-yoga judgment)",
}
File diff suppressed because it is too large Load Diff
+669
View File
@@ -0,0 +1,669 @@
#!/usr/bin/env python3
"""PL9 report language normalization and leakage gates.
The term policy is based on this repository's StarTrack language-bridge
boundary and the Jyotish wording guidance in references/modern-language-guide.md.
It only changes reader-facing labels; it does not alter calculation payloads.
"""
from __future__ import annotations
import re
from typing import Any
ZH_TERM_REPLACEMENTS: tuple[tuple[str, str], ...] = (
("Natural friends", "天然友星"),
("Natural enemies", "天然敌星"),
("Temporary relation", "临时关系"),
("Planet 1", "行星1"),
("Planet 2", "行星2"),
("Planet", "行星"),
("planet", "行星"),
("sign", "星座"),
("longitude", "黄经"),
("field", "字段"),
("value", "数值"),
("title", "标题"),
("target", "目标"),
("anchor", "依据"),
("yoga", "瑜伽"),
("dt_ut", "UTC 时间"),
("dt_local", "本地时间"),
("sun_lon", "太阳黄经"),
("year_lord", "年主星"),
("field_status", "字段状态"),
("muntha_house", "Muntha 宫位"),
("muntha_sign", "Muntha 星座"),
("muntha_lord", "Muntha 主星"),
("munthesh", "Muntha 宫主"),
("Nakshatra Scheme", "星宿身体对应表"),
("Opinion 1", "说法一"),
("Opinion 2", "说法二"),
("Graha Avastha", "Graha Avastha(行星状态)"),
("Jagradadi", "醒睡状态"),
("Baladi", "年龄状态"),
("Lajjitadi", "羞惭等状态"),
("Deeptadi", "明亮等状态"),
("Shyanadi", "卧姿等状态"),
("Shayanadi", "卧姿等状态"),
("Degree", "落座度数"),
("Declination", "赤纬"),
("Speed", "速度"),
("House", "宫位"),
("Sign", "星座"),
("Lord", "宫主"),
("Score", "分数"),
("Bala", "年龄状态"),
("Jagrat", "醒睡状态"),
("Aspect", "相位"),
("Exact degree", "精确角度"),
("Actual difference", "实际角距"),
("Orb", "容许度"),
("Applying", "入相"),
("From house", "起始宫位"),
("Target house", "目标宫位"),
("Aspect type", "相位类型"),
("Special", "特殊相位"),
("Category", "类别"),
("Strength", "强度"),
("Combination", "组合条件"),
("Effects / notes", "作用说明"),
("friends:", "友星:"),
("enemies:", "敌星:"),
("True", "是"),
("False", "否"),
("conjunction", "合相"),
("opposition", "对冲"),
("special", "特殊组合"),
("solar_yoga", "太阳瑜伽"),
("lunar_yoga", "月亮瑜伽"),
("kalatra", "婚恋"),
("durbhaga", "不利组合"),
("moderate", "中等"),
("common", "常见"),
("strong", "强"),
("weak", "弱"),
("not separately listed", "未单独列出"),
("ownership", "守护宫"),
("dignity", "尊贵状态"),
("house placement", "落宫"),
("natural significations", "自然象征"),
("functional role", "功能角色"),
("Graha Avasthas - Planets and their Moods", "Graha Avastha(行星状态与情绪)"),
("Mangala / Sade Sati / Dosha Results", "火星婚姻煞 / Sade Sati / Dosha 结果"),
("Mangal / Kuja Dosha", "火星婚姻煞(Mangala/Kuja Dosha)"),
("Poorvashadha", "前阿沙陀"),
("Poorva Ashadha", "前阿沙陀"),
("Uttara Phalg.", "后破伽"),
("Uttara Phalguni", "后破伽"),
("Uttarashadha", "后阿沙陀"),
("Uttara Ashadha", "后阿沙陀"),
("Uttarabhadra", "后跋陀罗"),
("Uttara Bhadrapada", "后跋陀罗"),
("Moola", "Mula(根、本源)"),
("Mula", "Mula(根、本源)"),
("Both thighs", "双侧大腿"),
("Both feet", "双脚"),
("Private parts", "私密部位"),
("Sides of body", "身体两侧"),
("Back", "背部"),
("Left side", "左侧"),
("Left hand", "左手"),
("Waist", "腰部"),
("Waiste", "腰部"),
("Shins", "小腿胫部"),
("Swapna", "梦眠"),
("Dreamful", "多梦"),
("Mrita", "死寂"),
("State of death", "死寂状态"),
("Sushupti", "熟睡"),
("State of sleep", "睡眠状态"),
("Jagrad", "觉醒"),
("Wakefulness", "清醒"),
("Kumaravastha", "少年期"),
("Adolescence", "青春期"),
("Balavastha", "童年期"),
("Childhood", "童年"),
("Vriddha", "老年期"),
("Old age", "老年"),
("Mudit Kshobit", "喜悦中带扰动"),
("Trushit Mudit", "渴求中带喜悦"),
("Kshudit Trushit", "饥渴不安"),
("Mudit", "喜悦"),
("Khala", "粗劣"),
("Mudita", "愉悦"),
("Delighted", "愉悦"),
("Shanta", "平静"),
("Quiescent", "安静"),
("Deena", "匮乏"),
("Deficient", "不足"),
("Swastha", "稳定"),
("Stable", "稳定"),
("Nidra", "睡眠"),
("Sleep", "睡眠"),
("Gamenecchha", "欲行"),
("Eager to go", "急于行动"),
("Shayana", "卧躺"),
("Recumbent", "卧躺"),
("Sabhayam Vasti", "集会中"),
("in an assembly", "在集会中"),
("Infant", "婴幼期"),
("Young", "青年期"),
("Youth", "壮年期"),
("Dead", "死寂"),
("Awake", "觉醒"),
("Dreaming", "梦眠"),
("Trushita", "渴求"),
("Kshobhita", "扰动"),
("Vikala", "失衡"),
("Kautuka", "好奇"),
("Agama", "学习/趋近"),
("Panapara", "续宫"),
("Apoklima", "果宫"),
("Kendra", "角宫"),
("Benefic", "自然吉星"),
("Malefic", "自然凶星"),
)
ZH_ALLOWED_LATIN_TOKENS = {
"AI", "AD", "PD", "MD", "PL9", "KP", "BPHS", "PVR", "D1", "D2", "D3", "D4",
"D5", "D6", "D7", "D8", "D9", "D10", "D11", "D12", "D16", "D20", "D24",
"D27", "D30", "D40", "D45", "D60", "Rashi", "Navamsha", "Bhava", "Sripati",
"Sudarshan", "Sudarshana", "Upagraha", "Lagna", "Arudha", "Upapada", "Pada",
"Dasha", "Vimshottari", "Ashtottari", "Yogini", "Kala", "Chakra", "Jaimini",
"Narayana", "Sthira", "Drig", "Shoola", "Tribhagi", "Sade", "Sati", "Dhayya",
"Kantaka", "Varshaphala", "Tajika", "Mudda", "Patyayini", "Saham", "Sahams",
"Shadbala", "Ashtakavarga", "BAV", "SAV", "Pinda", "Avastha", "Vimsopaka",
"Paravatamsa", "Simhasanamsa", "Rahu", "Ketu", "Sun", "Moon", "Mars",
"Mercury", "Jupiter", "Venus", "Saturn", "Ashwini", "Bharani", "Krittika",
"Virupa", "Sthana", "Dig", "Drik", "Chesta",
"Rohini", "Mrigashira", "Ardra", "Punarvasu", "Pushya", "Ashlesha", "Magha",
"Purva", "Uttara", "Phalguni", "Hasta", "Chitra", "Swati", "Vishakha",
"Anuradha", "Jyeshtha", "Mula", "Ashadha", "Shravana", "Dhanishta",
"Shatabhisha", "Bhadrapada", "Revati",
"benefic", "malefic", "debilitated", "Neecha", "Bhanga", "and",
"functional_benefic", "functional_malefic", "functional_neutral",
"natural_benefic", "natural_malefic", "natural_role_not_returned",
"functional_role_not_returned",
}
ZH_FORBIDDEN_MIXED_PHRASES = (
"General effects during",
"General effects which are felt",
"Interpretation of the",
"Effects of",
"Interpretations based on the condition",
"Supported houses gain",
"The immediate focus is",
"brings house",
"is read from house",
"Maha 大运",
"Antar 大运",
"Pratyantar 大运",
"现实场域中阅读",
"这一现实场域",
)
EN_FORBIDDEN_CHARS = (":", ";", ",", "。", "、", "(", ")", "——")
_ZH_REGEX_REPLACEMENTS = tuple(
(raw, public)
for raw, public in ZH_TERM_REPLACEMENTS
if re.fullmatch(r"[A-Za-z0-9_ /:-]+", raw)
)
_ZH_LITERAL_REPLACEMENTS = tuple(
(raw, public)
for raw, public in ZH_TERM_REPLACEMENTS
if not re.fullmatch(r"[A-Za-z0-9_ /:-]+", raw)
)
_ZH_REGEX_REPLACEMENT_MAP = {raw: public for raw, public in _ZH_REGEX_REPLACEMENTS}
_ZH_REGEX_REPLACEMENT_PATTERN = re.compile(
r"(?<![A-Za-z])("
+ "|".join(re.escape(raw) for raw, _public in sorted(_ZH_REGEX_REPLACEMENTS, key=lambda item: len(item[0]), reverse=True))
+ r")(?![A-Za-z])"
)
_ZH_NAKSHATRA_GLOSSES = {
"前阿沙陀": "Purva Ashadha",
"后阿沙陀": "Uttara Ashadha",
"后破伽": "Uttara Phalguni",
"后跋陀罗": "Uttara Bhadrapada",
"阿湿毗尼": "Ashwini",
"巴拉尼": "Bharani",
"基利提卡": "Krittika",
"罗希尼": "Rohini",
"鹿首": "Mrigashira",
"阿尔德拉": "Ardra",
"普那婆苏": "Punarvasu",
"普沙": "Pushya",
"阿什列沙": "Ashlesha",
"摩伽": "Magha",
"前破伽": "Purva Phalguni",
"哈斯塔": "Hasta",
"吉多罗": "Chitra",
"斯瓦蒂": "Swati",
"毗舍佉": "Vishakha",
"阿奴罗陀": "Anuradha",
"杰耶什塔": "Jyeshtha",
"室罗伐那": "Shravana",
"陀尼湿陀": "Dhanishta",
"百药宿": "Shatabhisha",
"前跋陀罗": "Purva Bhadrapada",
"雷瓦蒂": "Revati",
}
_ZH_NAKSHATRA_GLOSS_PATTERN = re.compile(
"("
+ "|".join(re.escape(raw) for raw in sorted(_ZH_NAKSHATRA_GLOSSES, key=len, reverse=True))
+ r")(?![((])"
)
def _replace_zh_regex_terms(markdown: str) -> str:
return _ZH_REGEX_REPLACEMENT_PATTERN.sub(
lambda match: _ZH_REGEX_REPLACEMENT_MAP[match.group(1)],
markdown,
)
def normalize_zh_markdown_terms(markdown: str) -> str:
text = _replace_zh_regex_terms(markdown)
for raw, public in _ZH_LITERAL_REPLACEMENTS:
text = text.replace(raw, public)
text = text.replace("e特殊组合ly", "especially")
# Repair field-label substitutions that crossed token boundaries in the
# previous customer pin. These are presentation-only fixes; chart values
# and source evidence remain unchanged.
text = text.replace("强est_第", "最强宫")
text = text.replace("弱est_第", "最弱宫")
text = text.replace("strongest_house", "最强宫")
text = text.replace("weakest_house", "最弱宫")
text = text.replace("7 visible planets occupy exactly 5 distinct signs", "七颗可见行星恰好分布在五个不同星座")
text = text.replace("Many friends, talkative, skilled in various arts", "人际接触较多,善于表达,具多样艺术或技能倾向")
text = text.replace("Father died before birth, ancestral curse", "传统规则提示:涉及父系与家族议题,不能据此判断现实经历")
text = text.replace("Skilled in fine arts, music, dance; cultured and wealthy", "擅长艺术、音乐或舞蹈;重视文化修养与资源积累")
text = text.replace("Ridiculed by others, mocked, subject to derision", "可能面临误解、嘲讽或评价压力;需结合现实处境核验")
text = text.replace("Deception, distrust, household/family complications", "信任、家庭关系或居住事务可能较复杂;需结合现实处境核验")
text = text.replace("rare", "罕见")
text = text.replace("relationship_observation", "关系观察")
text = text.replace("solar 瑜伽", "太阳瑜伽")
text = text.replace("lunar 瑜伽", "月亮瑜伽")
text = re.sub(r"(\d+)\s+第from\s+月亮", r"从月亮起第\1宫", text)
text = re.sub(r"\bin\s+(从月亮起第\d+宫)", r"位于\1", text)
text = text.replace("_aspect_", "宫相位_")
text = _normalize_zh_bhava_factor_text(text)
text = text.replace("Bhava 年龄状态 十二宫力量", "Bhava Bala 十二宫力量")
text = text.replace("Shadbala / Bhava 年龄状态", "Shadbala / Bhava Bala")
text = text.replace("星宿 Scheme", "星宿身体对应表")
text = _normalize_mula_gloss(_restore_zh_nakshatra_glosses(_collapse_duplicate_zh_parentheses(text)))
text = text.replace("### 是 Solar Return", "### 真实太阳返照")
text = re.sub(
r"General effects during the 主大运 of (.+?) are read from the 行星's 自然象征, 落宫, owned houses, 尊贵状态, 星宿, and 功能角色\.",
r"\1主大运的总体作用,需要从该行星的自然象征、落宫、守护宫、尊贵状态、星宿与功能角色一起阅读。",
text,
)
text = text.replace(
"Interpretations based on the condition of the 行星 in the birth chart and divisional charts are as follows:",
"结合本命盘与分盘条件后,可按以下方式细读:",
)
text = re.sub(
r"(.+?)落在(.+?),首先把(.+?)放到(.+?)这一现实场域中阅读。",
r"\1落在\2,表示\3会主要通过\4来表现。",
text,
)
text = re.sub(r"第([1-4])足", r"第\1 Pada(星宿四分区)", text)
text = text.replace("Pada | 速度", "Pada(星宿四分区) | 速度")
text = _normalize_zh_dasha_prose(text)
text = _thicken_zh_dasha_density(text)
text = _normalize_mula_gloss(_restore_zh_nakshatra_glosses(_collapse_duplicate_zh_parentheses(text)))
return text
def _normalize_zh_bhava_factor_text(markdown: str) -> str:
"""Localize compact Bhava Bala factor formulas in Chinese reports."""
text = markdown
body = r"(?:Sun|Moon|Mars|Mercury|Jupiter|Venus|Saturn|Rahu|Ketu|太阳|月亮|火星|水星|木星|金星|土星|北交点|南交点)"
text = re.sub(rf"({body})\s+in\s+H(\d+)", r"\1位于第\2宫", text)
text = re.sub(rf"({body})\s+in\s+(角宫|续宫|果宫)\s+\(H(\d+)\)", r"\1位于\2第\3宫", text)
text = re.sub(rf"({body})\s+aspects\s+H(\d+)\s+\((\d+)(?:st|nd|rd|th),\s+(自然吉星|自然凶星)\)", r"\1以\3宫相位照第\2宫(\4)", text)
text = re.sub(rf"({body})\s+aspects\s+H(\d+)\s+\((\d+)(?:st|nd|rd|th)\)", r"\1以\3宫相位照第\2宫", text)
text = re.sub(r"\((自然吉星|自然凶星)\)", r"(\1)", text)
text = text.replace("火星宫相位_4", "火星特殊4宫相位")
text = text.replace("火星宫相位_8", "火星特殊8宫相位")
text = text.replace("木星宫相位_5", "木星特殊5宫相位")
text = text.replace("木星宫相位_9", "木星特殊9宫相位")
text = text.replace("土星宫相位_3", "土星特殊3宫相位")
text = text.replace("土星宫相位_10", "土星特殊10宫相位")
return text
def _normalize_mula_gloss(markdown: str) -> str:
"""Make the Mula gloss idempotent across repeated language passes."""
text = markdown
root_gloss = r"根[,、/]本源"
text = re.sub(rf"Mula(?:[((]{root_gloss}[))])+", "Mula(根、本源)", text)
text = re.sub(rf"根宿[((]Mula[((]{root_gloss}[))][))]", "Mula(根、本源)", text)
text = re.sub(rf"根宿[((](Mula(?:[((]{root_gloss}[))])+)[))]", "Mula(根、本源)", text)
return text
def _collapse_duplicate_zh_parentheses(markdown: str) -> str:
"""Remove duplicated Chinese glosses such as 后破伽(后破伽)."""
return re.sub(r"([\u4e00-\u9fff][\u4e00-\u9fff·/-]{0,12})[((]\1[))]", r"\1", markdown)
def _restore_zh_nakshatra_glosses(markdown: str) -> str:
"""Keep the original Sanskrit/English nakshatra label in Chinese reports."""
return _ZH_NAKSHATRA_GLOSS_PATTERN.sub(
lambda match: f"{match.group(1)}({_ZH_NAKSHATRA_GLOSSES[match.group(1)]})",
markdown,
)
def _thicken_zh_dasha_density(markdown: str) -> str:
"""Restore reader-facing density for translated AD/PD prose.
This adds reading instructions tied to already-present fields. It does not
introduce new predictions or alter dates.
"""
lines = markdown.splitlines()
out: list[str] = []
for index, line in enumerate(lines):
out.append(line)
lookahead = "\n".join(lines[index + 1:index + 6])
if "这一子运不是单独结论" in lookahead or "这个次子运只承担短周期细化" in lookahead:
continue
ad_match = re.search(
r"(.+?)主大运中的(.+?)子运,会把(.+?)的自然主题带入当前阶段。",
line,
)
if ad_match:
md_lord, ad_lord, theme_lord = ad_match.groups()
out.extend([
"",
(
f"这一子运不是单独结论,而是把{theme_lord}的自然象征放进{md_lord}主大运的背景里筛选。"
f"阅读时先看{ad_lord}本身的落宫、守护宫、尊贵状态与星宿,再看它和主大运星之间是否互相支持。"
),
"",
(
"若同一宫位或同一行星在分盘、Ashtakavarga、年度盘和行运中重复出现,"
"该主题的可见度会提高;若这些层彼此冲突,则应把结果视为阶段性倾向,而不是孤立断语。"
),
])
continue
pd_match = re.search(
r"(.+?)子运中的(.+?)次子运,会在(.+?)背景下呈现(.+?)的短周期结果。",
line,
)
if pd_match:
ad_lord, pd_lord, md_lord, theme_lord = pd_match.groups()
out.extend([
"",
(
f"这个次子运只承担短周期细化:{pd_lord}会把{theme_lord}的具体征象带入{ad_lord}子运,"
f"并受{md_lord}主背景限制。"
),
"",
(
"因此这里优先用于判断事情推进的节奏、触发点和轻重缓急;实际领域仍需回到本命落宫、"
"分盘重复、年度盘和当前行运共同核对。"
),
])
continue
return "\n".join(out)
def _normalize_zh_dasha_prose(markdown: str) -> str:
text = markdown
replacements = {
"mind, mother, water, residence, 迁移与出行, public mood, nourishment, fertility, trade, learning, and changeable fortune become active.": "心智、母亲、居住、水象事务、迁移与出行、公众情绪、滋养、生育、贸易、学习与变化中的运势会被启动。",
"There can be interest in mantra, teachers, sacred learning, art, hospitality, garments, ornaments, land, and watery products.": "这一阶段可能增加对咒语、师长、神圣知识、艺术、服务接待、衣物、饰品、土地与水相关事务的兴趣。",
"The mind can become lively, sensitive, restless, affectionate, and responsive to family or social approval.": "心绪可能更活跃、敏感、容易波动,也更重视家庭回应与社会认可。",
"When supported, it brings comfort from home, spouse, children, servants, conveyances, food, clothing, education, fame, and fulfilled desires.": "条件良好时,可带来家庭、伴侣、子女、协助者、交通工具、饮食衣物、教育、名声与愿望满足方面的支持。",
"New places, cultivation, trade, and public-facing work may become profitable.": "新地点、耕作/经营、贸易与面向公众的工作可能带来收益。",
"When afflicted, it can show fear, anger, wavering judgment, family strain, maternal concern, contaminated food, 迁移与出行 pressure, and loss of old security.": "受克时,可能表现为恐惧、怒气、判断摇摆、家庭压力、母亲相关担忧、饮食不洁、迁移压力,以及旧有安全感减弱。",
"Friends may not give full support, and emotional decisions need steadier review.": "朋友支持可能不足,情绪化决定需要更稳妥地复核。",
"absolute Virupas; precise Sthana/Dig/Kala/Drik; bounded BPHS Chesta": "以绝对 Virupa 计分,包含 Sthana、Dig、Kala、Drik 与受限 BPHS Chesta",
"fear, anger, wavering judgment, family strain, maternal concern, contaminated food, 迁移与出行 pressure, and loss of old security": "恐惧、怒气、判断摇摆、家庭压力、母亲相关担忧、饮食不洁、迁移压力,以及旧有安全感减弱",
"unusual openings, foreign benefit, technical or political leverage, and gains through nontraditional channels": "特殊机会、海外或异地收益、技术或权力杠杆,以及非传统渠道带来的收获",
"fear, bondage, deception, sudden reversals, illness, scandals, and trouble from authorities or hidden enemies": "恐惧、束缚、欺骗、突发反转、疾病、名誉风波,以及来自权威或暗中对手的麻烦",
"promotion, respect, wealth, grains, clothes, gold, children, good counsel, and fulfillment of aims": "晋升、尊重、财富、物资、衣物、贵金属、子女、良好建议与目标达成",
"trouble to spouse or children, loss through indulgence, legal worries, fever, or grief from family obligations": "伴侣或子女方面的麻烦、因放纵带来的损耗、法律忧虑、发热或家庭责任带来的忧伤",
"durable property, work stability, public recognition, gain through labor, and capacity to endure responsibility": "稳定资产、工作稳定、公众认可、辛勤劳动带来的收益,以及承担责任的耐力",
"pain, quarrels, obstruction, loss of wealth, separation from parents, danger through low morale, and heavy duties": "痛苦、争执、阻碍、财富损耗、与父母分离、士气低落带来的风险,以及沉重职责",
"knowledge, wealth, garments, jewels, name, ritual merit, useful alliances, and clever problem solving": "知识、财富、衣物、珠宝、名声、仪式功德、有用联盟与灵活的问题解决能力",
"poor judgment, fever, anxiety, argument, loss through paperwork, and instability in business": "判断不稳、发热、焦虑、争论、文书损耗与商业不稳定",
"renunciation, spiritual practice, diagnostic skill, hidden knowledge, and the ability to cut through confusion": "放下执着、灵性修持、诊断能力、隐秘知识,以及切断混乱的能力",
"fear from enemies, loss of reasoning, sudden obstruction, disease, grief, and restless 迁移与出行": "来自对手的恐惧、理性受损、突发阻碍、疾病、忧伤与不安定的迁移出行",
"spouse happiness, property, conveyance, ornaments, fine food, friendship, and pleasant company": "伴侣愉悦、资产、交通工具、饰品、美食、友谊与愉快陪伴",
"indulgence, waste, disputes with women or partners, loss through luxury, and ailments connected with reproductive or urinary balance": "放纵、浪费、与女性或伴侣的争执、奢侈带来的损耗,以及生殖或泌尿平衡相关不适",
"recognition, administrative help, confidence, vehicles, ornaments, and respected duties": "认可、行政助力、自信、交通工具、饰品与受尊重的职责",
"pressure from government, rivals, fire, theft, father-related strain, feverish conditions, and restlessness": "来自政府/权威、竞争者、火灾、盗损、父亲相关压力、发热状态与内在不安的压力",
"courage, stamina, property gains, productive labor, victory over enemies, and technical execution": "勇气、体力、资产收益、有效劳动、战胜对手与技术执行力",
"harsh speech, weapon or fire trouble, stomach ailments, injuries, litigation, and hot-tempered separations": "言语尖锐、武器或火相关麻烦、胃部不适、伤损、诉讼,以及急躁导致的分离",
"stamina, property, gains, productive labor, victory over enemies": "体力、资产、收益、有效劳动和战胜对手",
"foreign places, outsiders, ambition, unusual gains, fear, poisons, snakes, politics, sudden turns, obsession, and unconventional paths become prominent.": "异地、外来者、野心、特殊收益、恐惧、毒性/中毒象征、蛇象、政治、突发转折、执念与非传统路径会变得突出。",
"The period can bring success in ventures, conveyances, garments, distant direction gains, and contact with powerful or foreign circles.": "这一时期可能带来事业尝试、交通工具、衣物、远方收益,以及与有权势或海外/异地圈层接触方面的机会。",
"If afflicted, it brings defamation, business loss, fever, enemies, anxiety, spouse or child distress, and loss of reputation.": "若受克,可能带来名誉受损、事业损失、发热、对手、焦虑、伴侣或子女方面的压力,以及声望下降。",
"It favors strategic risk only when facts and ethics are kept clear.": "只有事实清楚、边界正当时,策略性冒险才更有利。",
"北交点/罗睺-related mantra, restraint, and charity are traditional remedial themes.": "与北交点/罗睺相关的咒语、克制与布施,是传统辅助主题。",
"labor, delay, servants, masses, endurance, old people, land, minerals, iron, chronic pressure, discipline, and separation themes become prominent.": "劳动、延迟、服务者、大众、耐力、长者、土地、矿物、铁器、长期压力、纪律与分离主题会变得突出。",
"The period can give property, recognition, steady gains, service authority, and patient achievement after effort.": "这一时期可能带来资产、认可、稳定收益、服务型权责,以及努力之后的耐心成果。",
"If afflicted, it brings loss of friends, disputes with relatives, fear, confinement, fatigue, rheumatic or digestive trouble, and wandering.": "若受克,可能带来朋友损失、亲属争执、恐惧、受限感、疲惫、风湿或消化问题,以及漂泊。",
"The middle of the period can be more productive than its beginning or end.": "这一时期的中段往往比开始和结束阶段更容易产生成果。",
"Traditional balancing themes include service, discipline, humility, and 土星-related charity.": "传统平衡主题包括服务、纪律、谦逊,以及与土星相关的布施。",
"The period can detach the person from stale ambitions and force a simpler, more inward path.": "这一时期可能使人脱离旧有野心,被迫走向更简单、更内向的路径。",
"If afflicted, business disturbance, loss of wealth, stomach or eye trouble, fear, conflict, and news of death or separation can appear.": "若受克,可能出现事业扰动、财富损耗、胃部或眼部问题、恐惧、冲突,以及死亡或分离相关消息。",
"It can produce intermittent gains after pressure or 迁移与出行.": "它可能在压力或迁移之后带来间歇性收益。",
"Durga worship, protective mantra, and charity are traditional balancing themes.": "杜尔迦崇拜、保护性咒语与布施,是传统平衡主题。",
"authority, government, 状态, father, medicine, land, fire, command, visibility, and public responsibility become more prominent.": "权威、政府、地位、父亲、医疗、土地、火象事务、指挥权、能见度与公共责任会更突出。",
"spiritual discipline, mantra, ritual, leadership, and contact with officials or influential people can increase.": "灵性纪律、咒语、仪式、领导力,以及与官员或有影响力人士的接触可能增加。",
"anxiety, heat, separation from close relatives, disputes with authority, eye, teeth, abdomen, or vitality concerns can also need attention.": "焦虑、热性问题、与近亲分离、和权威争执,以及眼睛、牙齿、腹部或生命力相关议题也需要留意。",
"It can push philanthropic action, construction, public work, or a clearer place in society.": "它可能推动公益行动、建设事务、公共工作,或让个人在社会中取得更明确的位置。",
"The person may feel proud, isolated, or forced to leave a familiar place for work or duty.": "当事人可能感到自尊增强但也更孤立,或因工作与职责被迫离开熟悉环境。",
"energy, courage, land, siblings, weapons, competition, surgery, heat, property disputes, technical action, and decisive breaks become prominent.": "能量、勇气、土地、手足、武器/工具、竞争、手术、热性事务、地产争议、技术行动与果断切割会更突出。",
"The period increases initiative and can bring land, honors, official recognition, or success through bold effort.": "这一时期会增强主动性,也可能通过大胆行动带来土地、荣誉、官方认可或成功。",
"teachers, children, wisdom, religion, wealth, counsel, learning, law, protection, ceremonies, and honorable expansion become prominent.": "师长、子女、智慧、宗教、财富、建议、学习、法律、保护、仪式与体面的扩展会更突出。",
"If afflicted, body pain, family strain, displeasure of authority, missed goals, or loss through poor counsel can appear.": "若受克,可能出现身体疼痛、家庭压力、权威不满、目标落空,或因建议不当导致损失。",
"education, speech, business, writing, analysis, accounts, friends, trade, negotiation, youth, and skills become prominent.": "教育、言语、商业、写作、分析、账务、朋友、贸易、谈判、年轻人事务与技能会更突出。",
"separation, austerity, sharp insight, wandering, spiritual pressure, enemies, sudden loss, animals, cuts, and hidden causes become prominent.": "分离、苦修、锐利洞察、漂泊、灵性压力、对手、突发损失、动物、切割伤与隐秘原因会更突出。",
"comforts, spouse, relationships, ornaments, vehicles, art, luxury, pleasures, agreements, water products, and refined company become prominent.": "舒适、伴侣、关系、饰品、交通工具、艺术、享受、愉悦、协议、水产品与雅致社交会更突出。",
"The period can bring beauty, garments, jewels, enjoyment, hospitality, marriage themes, and social pleasures.": "这一时期可能带来美感、衣物、珠宝、享受、接待、婚姻主题与社交愉悦。",
"If afflicted, comforts are disturbed by excess expense, sensual distraction, household conflict, fever, headaches, or relationship strain.": "若受克,舒适感可能被过度开销、感官分心、家庭冲突、发热、头痛或关系压力扰动。",
}
if replacements:
pattern = re.compile("|".join(re.escape(raw) for raw in sorted(replacements, key=len, reverse=True)))
text = pattern.sub(lambda match: replacements[match.group(0)], text)
text = re.sub(
r"Supported houses gain expression through (.+?); strained houses require steadier handling, especially where the same houses repeat in (?:大运|主大运), divisional charts, Ashtakavarga, or the annual chart\.",
r"受支持的宫位会通过\1获得表达;若同一宫位在大运、分盘、Ashtakavarga 或年度盘中反复承压,则需要更稳妥地处理。",
text,
)
text = re.sub(
r"When supported, (.+?) (?:gives|brings) (.+?)\.",
r"条件良好时,\1会带来\2。",
text,
)
text = re.sub(
r"When strained, (?:it |)(?:can |may |)(?:show|bring) (.+?)\.",
r"受压时,可能表现为\1。",
text,
)
text = re.sub(
r"When afflicted, (?:it |)(?:can |may |)(?:show|bring) (.+?)\.",
r"受克时,可能表现为\1。",
text,
)
text = re.sub(
r"In the (.+?), (.+?)'s significations give the short-period result inside the (.+?) background\.子运中的(.+?)次子运",
r"\1子运中的\4次子运,会在\3背景下呈现\2的短周期结果。",
text,
)
text = re.sub(
r"Consideration: (.+?) is checked from (.+?) against the classical marriage-sensitive houses\. In this chart (.+?) is listed in house (.+?) in (.+?); the detector severity is (.+?)\. Classical reference family: (.+?)\.",
r"判定方式:\1会从\2出发,检查传统婚恋敏感宫位。本盘中,\3位于第\4宫、\5,检测强度为\6。参考文献族:\7。",
text,
)
text = text.replace(
"Consideration: this Dosha row is shown only when the local detector returns a chart-specific condition. Classical reference family is attached in the source map.",
"判定方式:只有本盘检测到对应条件时才列出该 Dosha;对应古典参考族已在资料层登记。",
)
text = text.replace(
"Result: when present, Kuja/Mangala Dosha is read as heat, impatience, conflict, or pressure around partnership handling; when absent or cancelled, the report does not promote it as a dominant relationship obstacle. The result must be read with the seventh house, 金星, Upapada, Navamsha, current 大运, and partner-chart comparison where available.",
"结果:若火星婚姻煞成立,通常表示关系处理中的热度、急躁、冲突或压力;若不存在或有抵消条件,则不应把它提升为主导关系障碍。该项需要与第七宫、金星、Upapada、Navamsha、当前大运以及可用的伴侣盘一起阅读。",
)
text = text.replace(
"Result: this row contributes a supporting condition and is not promoted over the core chart, 大运, and divisional evidence.",
"结果:该项只作为辅助条件,不高于本命盘、大运和分盘证据。",
)
text = text.replace(
"Cancellation: no explicit cancellation reasons were returned by the local detector; partner-chart matching remains a separate comparison step.",
"抵消条件:本地检测器未返回明确抵消原因;伴侣盘匹配仍属于独立比较步骤。",
)
text = text.replace(
"本节按传统大运的总体作用与具体命盘条件两层结构展开,参考:Light on Life An Introduction to the Astrology of India; Predict Effectively through Yogini 大运。",
"本节按传统大运的总体作用与具体命盘条件两层结构展开,参考《印度占星生命之光》和《Yogini Dasha 实战预测》的结构。",
)
text = text.replace(
"When supported in the chart, this period gives 认可、行政助力、自信、交通工具、饰品与受尊重的职责.",
"命盘条件良好时,这一阶段可带来认可、行政助力、自信、交通工具、饰品与受尊重的职责。",
)
text = text.replace(
"When supported in the chart, this period gives 体力、资产、收益、有效劳动和战胜对手.",
"命盘条件良好时,这一阶段可带来体力、资产、收益、有效劳动和战胜对手。",
)
text = re.sub(
r"When supported in the chart, this period gives (.+?)\.",
r"命盘条件良好时,这一阶段可带来\1。",
text,
)
text = re.sub(
r"When afflicted, it brings (.+?)\.",
r"受克时,可能带来\1。",
text,
)
text = re.sub(
r"If afflicted, (.+?)\.",
r"若受克,\1。",
text,
)
text = text.replace("Remedies / Supportive 修持s", "传统辅助建议")
return text
def normalize_en_markdown_terms(markdown: str) -> str:
text = markdown.replace("(", "(").replace(")", ")").replace("、", ", ")
text = (
text.replace("dt_ut", "UTC time")
.replace("dt_local", "Local time")
.replace("sun_lon", "Sun longitude")
.replace("year_lord", "Year lord")
.replace("field_status", "Field status")
.replace("muntha_house", "Muntha house")
.replace("muntha_sign", "Muntha sign")
.replace("muntha_lord", "Muntha lord")
)
text = re.sub(
r"\b(Mars|Jupiter|Venus|Saturn|Mercury|Sun|Moon|Rahu|Ketu)(\d{1,2})\b",
r"\1 \2",
text,
)
return _thicken_en_dasha_density(text)
def _thicken_en_dasha_density(markdown: str) -> str:
"""Add chapter-level AD context without repeating boilerplate on every PD row."""
lines = markdown.splitlines()
out: list[str] = []
current_md = None
ad_context_added = False
for index, line in enumerate(lines):
md_heading = re.match(r"^#{2,4}\s+(.+?\bMaha Dasha\b.*)$", line)
if md_heading:
next_md = md_heading.group(1)
if next_md != current_md:
current_md = next_md
ad_context_added = False
out.append(line)
lookahead = "\n".join(lines[index + 1:index + 6])
if (
"This Antar Dasha is not read as a standalone verdict" in lookahead
or "This Pratyantar Dasha is the short-cycle refinement" in lookahead
):
continue
ad_match = re.search(
r"The Antar Dasha of (.+?) activates (.+?)'s natural themes inside the broader Maha Dasha of (.+?)\.",
line,
)
if ad_match and not ad_context_added:
ad_lord, theme_lord, md_lord = ad_match.groups()
out.extend([
"",
(
f"This Antar Dasha is not read as a standalone verdict. It places {theme_lord}'s "
f"natural significations inside the background of the {md_lord} Maha Dasha, then "
f"filters them through {ad_lord}'s house placement, house ownership, dignity, "
"nakshatra lord, conjunctions, and divisional-chart repetition."
),
"",
(
"When the same planet or house repeats through divisional charts, Ashtakavarga, "
"annual factors, and transits, the theme becomes more visible. When those layers "
"conflict, the result should be read as a staged tendency rather than an isolated statement."
),
])
ad_context_added = True
continue
pd_match = re.search(
r"In the Pratyantar Dasha of (.+?) in the Antar Dasha of (.+?), (.+?)'s significations give the short-period result inside the (.+?) background\.",
line,
)
if pd_match:
# The row already contains the concrete PD lord and timing context.
# Keep it, but do not append the same methodology paragraph per node.
continue
return "\n".join(out)
def language_quality_receipt(markdown: str, *, language: str) -> dict[str, Any]:
violations: list[str] = []
if language == "en":
chinese_chars = len(re.findall(r"[\u4e00-\u9fff]", markdown))
if chinese_chars:
violations.append(f"chinese_characters:{chinese_chars}")
for char in EN_FORBIDDEN_CHARS:
if char in markdown:
violations.append(f"fullwidth_punctuation:{char}")
else:
for phrase in ZH_FORBIDDEN_MIXED_PHRASES:
if phrase in markdown:
violations.append(f"mixed_template_phrase:{phrase}")
suspicious_lines: list[str] = []
for line_no, line in enumerate(markdown.splitlines(), 1):
if line.lstrip().startswith("|"):
continue
if not re.search(r"[\u4e00-\u9fff]", line):
continue
words = re.findall(r"\b[A-Za-z][A-Za-z/_-]{2,}\b", line)
non_allowed = [word for word in words if word not in ZH_ALLOWED_LATIN_TOKENS]
if len(non_allowed) >= 5:
suspicious_lines.append(f"{line_no}:{','.join(non_allowed[:8])}")
if len(suspicious_lines) >= 20:
break
if suspicious_lines:
violations.append("mixed_english_dense_lines:" + ";".join(suspicious_lines))
return {
"schema": "pl9.language_quality_receipt.v1",
"language": language,
"status": "pass" if not violations else "fail",
"violations": violations,
}
+26 -15
View File
@@ -8,10 +8,9 @@ rewritten in place (concession 1). Algorithm text is unchanged except ``_bind_en
``render_pl9_parity_markdown`` reads ``SIGNS`` / ``SIGNS_CN`` through ``globals()``, which only
works in the engine module.
Upstream ``_pl9_export_markdown_for_edition`` also serves ``pl9_ai_density``. That branch needs
``_render_pl9_ai_density_raw_data_markdown_en`` and the English density sanitizers, which are
outside the ``reader_main`` closure and are not ported. ``reader_main`` and the reference
default match the upstream branches.
``full_data`` (upstream ``pl9_ai_density``) is rendered by ``scripts/pl9_full_data_export.py``
on top of ``render_pl9_parity_markdown``. The reader edition ``reader_main`` is no longer
dispatched. The reference default is unchanged.
BUG-1028 / 2026-09-25 local reader binding correction: annual lord, Muntha and
annual ascendant are read from their own authoritative pack fields. Upstream
@@ -53,21 +52,18 @@ def _reference_markdown(packet: dict) -> str:
def _pl9_export_markdown_for_edition(packet: dict, edition: str) -> str:
"""Dispatch reader_main vs the reference renderer.
"""Dispatch full_data vs the reference renderer.
Upstream ``23b9609e`` ``scripts/jyotish_engine.py`` lines 13099-13145.
``pl9_ai_density`` is not ported; see the module docstring.
``full_data`` is the upstream ``pl9_ai_density`` branch. ``reader_main`` is removed.
"""
_bind_engine_symbols()
normalized = str(edition or "").strip()
if normalized == "reader_main":
if _pl9_report_language(packet) == 'en':
try:
from scripts.pl9_reader_english import render_pl9_user_markdown_en
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution
from pl9_reader_english import render_pl9_user_markdown_en
return render_pl9_user_markdown_en(packet)
return _render_pl9_user_markdown(packet)
if normalized == "full_data":
try:
from scripts.pl9_full_data_export import render_full_data_markdown
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution
from pl9_full_data_export import render_full_data_markdown
return render_full_data_markdown(packet)
if normalized in {"pl9_parity", "pl9_fixed_204"}:
return render_pl9_parity_markdown(packet)
return _reference_markdown(packet)
@@ -1736,6 +1732,21 @@ def render_pl9_parity_markdown(packet: dict) -> str:
f"{_period_end(child) or _cell(child_raw.get('end_date') or child_raw.get('end'))} |"
)
lines.append('')
# BUG-1215 / BUG-1218: the full-data edition keeps the legacy label and the
# Rath table already printed by the reference renderer. Display only.
narayana_packet = _dict(timing.get('narayana_dasha'))
if narayana_packet:
try:
from narayana_legacy_label import rath_report_lines_zh as _rath_lines
except ImportError:
from scripts.narayana_legacy_label import rath_report_lines_zh as _rath_lines
legacy_line = f'- 算法:{LEGACY_NOTE_ZH}'
if legacy_line not in lines:
lines.extend(['### Narayana Dasha', '', legacy_line, ''])
rath_lines = [line for line in _rath_lines(narayana_packet.get('rath')) if line]
if rath_lines and not any(str(line).startswith('- 并列:') for line in lines):
lines.extend(rath_lines)
lines.append('')
source_dasha_pages = _list(source_visible.get('dasha_pages'))
if source_dasha_pages:
source_titles = {
+24 -20
View File
@@ -14,10 +14,20 @@ from types import SimpleNamespace
from typing import Any
try:
from scripts.pl9_full_data_export import (
FULL_DATA_EDITION,
REPORT_VERSION_FULL,
render_full_data_markdown,
)
from scripts.reader_appendix_language import clean_reader_appendix_markdown
from scripts.reader_dasha_applicability import render_dasha_applicability
from scripts.report_density_packet import report_density_packet
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution path
from pl9_full_data_export import (
FULL_DATA_EDITION,
REPORT_VERSION_FULL,
render_full_data_markdown,
)
from reader_appendix_language import clean_reader_appendix_markdown
from reader_dasha_applicability import render_dasha_applicability
from report_density_packet import report_density_packet
@@ -27,9 +37,7 @@ class ProfessionalReportReferenceInputError(ValueError):
"""The professional-reference request is outside the public contract."""
READER_MAIN_EDITION = "reader_main"
REFERENCE_EDITION = "reference"
REPORT_VERSION_READER = "pl9_personal_long_report.v3"
def _normalize_edition(value: Any) -> str:
@@ -40,9 +48,11 @@ def _normalize_edition(value: Any) -> str:
normalized = value.strip()
if normalized in {"", REFERENCE_EDITION, "professional_reference"}:
return REFERENCE_EDITION
if normalized == READER_MAIN_EDITION:
return READER_MAIN_EDITION
raise ProfessionalReportReferenceInputError("edition must be reference or reader_main")
if normalized == "reader_main":
raise ProfessionalReportReferenceInputError("edition reader_main has been removed; use full_data")
if normalized == FULL_DATA_EDITION:
return FULL_DATA_EDITION
raise ProfessionalReportReferenceInputError("edition must be reference or full_data")
def _normalize_format(value: Any) -> str:
@@ -102,14 +112,12 @@ def _english_edition(packet: dict, include_fact_tables: bool) -> dict[str, Any]:
"""
try:
from scripts.pl9_reader_english import HAN, _public_wording_en
from scripts.pl9_reader_export import _pl9_export_markdown_for_edition
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution path
from pl9_reader_english import HAN, _public_wording_en
from pl9_reader_export import _pl9_export_markdown_for_edition
try:
english = copy.deepcopy(packet)
english["report_language"] = "en"
markdown = _pl9_export_markdown_for_edition(english, READER_MAIN_EDITION)
markdown = render_full_data_markdown(english)
applicability = (
_public_wording_en(render_dasha_applicability(copy.deepcopy(packet), language="en"))
if include_fact_tables else None
@@ -170,8 +178,8 @@ def build_professional_report_reference(handler, body: dict[str, Any], *, engine
edition = _normalize_edition(body.get("edition"))
packs = _normalize_packs(body.get("packs"))
languages = _normalize_languages(body.get("languages"))
if languages and (output_format != "markdown" or edition != READER_MAIN_EDITION):
raise ProfessionalReportReferenceInputError("languages requires format markdown and edition reader_main")
if languages and (output_format != "markdown" or edition != FULL_DATA_EDITION):
raise ProfessionalReportReferenceInputError("languages requires format markdown and edition full_data")
birth = handler._high_rigor_birth_payload(body)
full_reading = handler._compute_full_reading_for_thematic(birth)
@@ -185,30 +193,26 @@ def build_professional_report_reference(handler, body: dict[str, Any], *, engine
except ValueError as exc:
raise ProfessionalReportReferenceInputError(str(exc)) from exc
if edition == READER_MAIN_EDITION and isinstance(packet, dict):
if edition == FULL_DATA_EDITION and isinstance(packet, dict):
packet = dict(packet)
packet["report_version"] = REPORT_VERSION_READER
packet["report_version"] = REPORT_VERSION_FULL
if output_format == "markdown":
if edition == READER_MAIN_EDITION:
try:
from scripts.pl9_reader_export import _pl9_export_markdown_for_edition
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution path
from pl9_reader_export import _pl9_export_markdown_for_edition
if edition == FULL_DATA_EDITION:
# English renders from its own deep copy first, so nothing the Chinese
# renderer does to the packet can reach it (and vice versa).
english = _english_edition(packet, body.get("include_fact_tables") is True) if "en" in languages else {}
markdown = _pl9_export_markdown_for_edition(packet, READER_MAIN_EDITION)
markdown = render_full_data_markdown(packet)
else:
markdown = resolved_engine.render_pl9_markdown(packet)
return {
"format": "markdown",
"edition": edition,
"report_version": REPORT_VERSION_READER if edition == READER_MAIN_EDITION else packet.get("report_version"),
"report_version": REPORT_VERSION_FULL if edition == FULL_DATA_EDITION else packet.get("report_version"),
"markdown": markdown,
**({"fact_table_packet": report_density_packet(packet),
"reader_dasha_applicability": clean_reader_appendix_markdown(render_dasha_applicability(packet))}
if body.get("include_fact_tables") is True else {}),
**(english if edition == READER_MAIN_EDITION and "en" in languages else {}),
**(english if edition == FULL_DATA_EDITION and "en" in languages else {}),
}
return {
"format": "json",