Files
Jyotisha/jyotish-app/jyotish-export-modules.js
T
732642856 2457e23271 feat: Jyotish Web App v4.3 完整印度占星前端应用
技术栈: Vite + 原生HTML/JS/CSS + swisseph-wasm
功能模块:
- 排盘引擎: 行星/宫位/Dasha/Yoga/相位/燃烧检测/130+城市
- 高级计算: Karaka/Arudha/16分盘/Ashtakavarga/Shadbala/Panchanga/Chara Dasha
- 73个Yoga识别 (Pancha Mahapurusha + 扩展A/B)
- 生时校正v2.0: K.N.Rao方法论, 多维评分
- Transit v2.0: 日期选择/全行星/AV评分/Sade Sati
- 深度分析7子模块 + 扩展分析5子模块
- 导出v2.0: JSON 100%覆盖Python 13模块 + SVG/PNG
- i18n中英双语 + 点读机95词条 + AI聊天面板
- 认证+订阅 (JWT/Apple Sign-In/IAP)
- 浅色极简主题 (fatetell.com风格)
2026-04-30 03:51:30 +08:00

222 lines
7.5 KiB
JavaScript

/**
* Jyotish Export Helper Modules v1.0
* - Validation (数据完整性校验 R1-R10)
* - Audit (行星审计 P1-P12)
* - Actionable Context (宫位/行星映射, 仓库耦合)
*/
import { SIGNS, SIGN_LORDS, DASHA_ORDER, DASHA_YEARS, NAKSHATRA_LIST } from './jyotish-engine.js';
// ========== Validation R1-R10 ==========
export function computeValidation(planets, ascendant, ashtakavarga, dasha) {
const results = [];
let passed = 0, failed = 0;
function check(rule, name, ok, detail) {
results.push({ rule, name, passed: ok, detail });
ok ? passed++ : failed++;
}
// R1: SAV总和=337
if (ashtakavarga?.sav) {
const total = ashtakavarga.sav.reduce((a, b) => a + b, 0);
check('R1', 'SAV总和=337', total === 337, `SAV total = ${total}, expected 337`);
} else {
check('R1', 'SAV总和=337', false, 'SAV data unavailable');
}
// R2: BAV行常数校验
if (ashtakavarga?.bav_raw) {
const bav = ashtakavarga.bav_raw;
const bavNames = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Lagna'];
const bavConsts = [48,49,39,54,56,52,39,49];
let r2Detail = [];
let r2Ok = true;
for (let i = 0; i < bavNames.length; i++) {
const row = bav[bavNames[i]];
if (!row) { r2Ok = false; r2Detail.push(`${bavNames[i]}=N/A`); continue; }
const sum = row.reduce((a, b) => a + b, 0);
const ok = sum === bavConsts[i];
if (!ok) r2Ok = false;
r2Detail.push(`${bavNames[i]}=${sum}/${bavConsts[i]}`);
}
check('R2', 'BAV行常数校验', r2Ok, r2Detail.join('; '));
}
// R2b: BAV列→SAV列校验
if (ashtakavarga?.bav_raw && ashtakavarga?.sav) {
const bav = ashtakavarga.bav_raw;
const bavNames = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn'];
let r2bOk = true;
let r2bDetail = [];
for (let col = 0; col < 12; col++) {
let colSum = 0;
for (const pn of bavNames) colSum += (bav[pn]?.[col] || 0);
const savVal = ashtakavarga.sav[col];
if (colSum !== savVal) r2bOk = false;
}
check('R2b', 'BAV列→SAV列校验', r2bOk, r2bOk ? '所有12星座 BAV列和 与 SAV分数一致 ✅' : 'BAV列和与SAV不一致');
}
// R5: Rahu-Ketu 180°对冲
if (planets.Rahu?.degree != null && planets.Ketu?.degree != null) {
const diff = Math.abs(planets.Rahu.degree - planets.Ketu.degree);
const deviation = Math.abs(diff - 180);
check('R5', 'Rahu-Ketu 180°对冲', deviation < 0.01,
`Rahu=${planets.Rahu.degree}°, Ketu=${planets.Ketu.degree}°, 偏差=${deviation.toFixed(4)}°`);
}
// R7: Dasha年限总和=120
const dashaSum = DASHA_ORDER.reduce((s, p) => s + (DASHA_YEARS[p] || 0), 0);
check('R7', 'Dasha年限总和=120', dashaSum === 120, `Dasha year chain sum = ${dashaSum}`);
// R8: 行星完整性
const requiredPlanets = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu'];
const haveAll = requiredPlanets.every(p => planets[p] && !planets[p].error);
check('R8', '行星完整性', haveAll, haveAll ? '完整:9行星 + Ascendant' : '缺少行星');
// R9: 星座度数范围
let r9Ok = true;
for (const pn of requiredPlanets) {
const p = planets[pn];
if (!p || p.error) continue;
if (p.degree_in_sign < 0 || p.degree_in_sign >= 30) r9Ok = false;
}
check('R9', '星座度数范围', r9Ok, r9Ok ? '所有行星度数在合法范围' : '存在度数越界');
// R10: 宫位连续性
let r10Ok = true;
const houses = new Set();
for (const pn of requiredPlanets) {
const p = planets[pn];
if (p?.house) houses.add(p.house);
}
if (ascendant?.sign) houses.add(1);
check('R10', '宫位连续性', houses.size >= 10, `1-12宫完整(${houses.size}宫)`);
return {
valid: failed === 0,
total_checks: results.length,
checked: results.length,
passed, failed,
skipped: 0,
results,
};
}
// ========== Audit (P1-P12) ==========
export function computeAudit(planets, ascendant, ashtakavarga, validation) {
const ascIdx = SIGNS.indexOf(ascendant?.sign || 'Aries');
const ascLord = SIGN_LORDS[ascendant?.sign || 'Aries'];
// P1: Identity
const ascLordPlanet = planets[ascLord];
const p1 = {
asc_sign: ascendant?.sign,
asc_lord: ascLord,
lord_sign: ascLordPlanet?.sign || '',
lord_house: ascLordPlanet?.house || 0,
lord_status: ascLordPlanet?.status || '',
};
// P2: Health houses (6, 8, 12)
const p2 = { houses: {} };
for (const h of [6, 8, 12]) {
const sIdx = (ascIdx + h - 1) % 12;
p2.houses[`house_${h}`] = { sign: SIGNS[sIdx], lord: SIGN_LORDS[SIGNS[sIdx]] };
}
p2.sun_status = planets.Sun?.status || '';
p2.sun_house = planets.Sun?.house || 0;
// P3: Warehouse Coupling
const p3 = {};
const lordToHouses = {};
for (let h = 1; h <= 12; h++) {
const sIdx = (ascIdx + h - 1) % 12;
const lord = SIGN_LORDS[SIGNS[sIdx]];
if (!lordToHouses[lord]) lordToHouses[lord] = [];
lordToHouses[lord].push(h);
}
const kendraTrikona = new Set([1,4,5,7,9,10]);
for (const [lord, hs] of Object.entries(lordToHouses)) {
if (hs.length < 2) continue;
const hasK = hs.some(h => kendraTrikona.has(h));
const allK = hs.every(h => kendraTrikona.has(h));
let quality = '中性 — 标准互动';
if (allK) quality = '吉庆型 — 自然流畅的支持';
else if (!hasK) quality = '压力型 — 通过努力获取成就';
else quality = '凶吉混合 — 挑战与成长并存';
p3[lord] = {
houses: hs,
meaning: `${lord}同时掌管${hs.join('宫和')}宫,事务捆绑`,
conjunction_quality: quality,
};
}
// P8: Age Status (degree-based life stage)
const p8 = {};
for (const pn of ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn']) {
const p = planets[pn];
if (!p || p.error) continue;
const deg = p.degree_in_sign;
let phase, quality;
if (deg < 10) { phase = '婴幼(0-10°)'; quality = '辅助型 — 能量尚未完全展开'; }
else if (deg < 20) { phase = '青壮(10-20°)'; quality = '主动型 — 能量最活跃,主导性强'; }
else { phase = '成熟(20-30°)'; quality = '成熟型 — 稳定输出,经验丰富'; }
p8[pn] = { degree_in_sign: Math.round(deg * 100) / 100, phase, quality };
}
return {
version: '3.5',
birth_info_audit: {
ascendant: ascendant?.sign,
degree: ascendant?.degree,
},
audit: { P1_identity: p1, P2_health: p2, P3_warehouse_coupling: p3, P8_age_status: p8 },
validation,
conflict_arbitration: [],
};
}
// ========== Actionable Context ==========
export function computeActionableContext(planets, ascendant) {
const ascIdx = SIGNS.indexOf(ascendant?.sign || 'Aries');
// House Lord Map
const houseLordMap = {};
for (let h = 1; h <= 12; h++) {
const sIdx = (ascIdx + h - 1) % 12;
const sign = SIGNS[sIdx];
houseLordMap[h] = { sign, lord: SIGN_LORDS[sign] };
}
// Planet House Map
const planetHouseMap = {};
const planetNames = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu'];
for (const pn of planetNames) {
const p = planets[pn];
if (!p || p.error) continue;
planetHouseMap[pn] = {
sign: p.sign,
house: p.house,
degree_in_sign: p.degree_in_sign,
};
}
// Key House Activations
const keyActivations = {};
for (let h = 1; h <= 12; h++) {
const planetsInHouse = planetNames.filter(pn => planets[pn]?.house === h);
if (planetsInHouse.length > 0) {
keyActivations[h] = { planets: planetsInHouse, count: planetsInHouse.length };
}
}
return {
house_lord_map: houseLordMap,
planet_house_map: planetHouseMap,
key_house_activations: keyActivations,
};
}