Files
Jyotisha/frontend/src/app/api/synastry/route.ts
T

201 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextResponse } from "next/server";
import { isProductEnabled } from "@/lib/product-access";
import { createServerSupabaseClient } from "@/lib/supabase/server";
import {
synastryBirthPayload,
type GlobalBirthProfile as Profile,
} from "@/lib/global-birth-payloads";
type RelationshipType = "romance" | "business" | "family" | "general";
const apiBase = process.env.JYOTISH_API_BASE ?? "http://127.0.0.1:5200";
function moonLongitude(chart: Record<string, unknown>) {
const planets = chart.planets && typeof chart.planets === "object" ? chart.planets as Record<string, unknown> : {};
const moon = planets.Moon && typeof planets.Moon === "object" ? planets.Moon as Record<string, unknown> : {};
const value = moon.lon ?? moon.longitude ?? moon.degree;
const numeric = Number(value);
if (!Number.isFinite(numeric)) throw new Error("moon_longitude_missing");
return numeric;
}
function moonSummary(chart: Record<string, unknown>) {
const planets = chart.planets && typeof chart.planets === "object" ? chart.planets as Record<string, unknown> : {};
const moon = planets.Moon && typeof planets.Moon === "object" ? planets.Moon as Record<string, unknown> : {};
return {
sign: moon.sign,
nakshatra: moon.nakshatra,
pada: moon.nakshatra_pada,
lord: moon.nakshatra_lord,
longitude: moonLongitude(chart),
};
}
function d9Summary(varga: Record<string, unknown>) {
const result = varga.result && typeof varga.result === "object" ? varga.result as Record<string, unknown> : {};
const d9 = result.D9_Navamsa && typeof result.D9_Navamsa === "object" ? result.D9_Navamsa as Record<string, unknown> : {};
const ascendant = d9.ascendant && typeof d9.ascendant === "object" ? d9.ascendant as Record<string, unknown> : {};
const planets = d9.planets && typeof d9.planets === "object" ? d9.planets as Record<string, unknown> : {};
return {
ascendant,
moon: planets.Moon,
venus: planets.Venus,
mars: planets.Mars,
source: varga.source,
};
}
function planetSign(point: unknown) {
return point && typeof point === "object" && "sign" in point
? String((point as Record<string, unknown>).sign || "unknown")
: "unknown";
}
function relationshipReport(synastry: Record<string, unknown>, selfD9: Record<string, unknown>, partnerD9: Record<string, unknown>) {
const total = Number(synastry.total_score ?? 0);
const max = Number(synastry.max_score ?? 36);
const ratio = max > 0 ? total / max : 0;
const band = ratio >= 0.72 ? "supportive" : ratio >= 0.5 ? "mixed" : "challenging";
const self = d9Summary(selfD9);
const partner = d9Summary(partnerD9);
return {
status: "evidence_summary",
scoreBand: band,
headline: band === "supportive"
? "基础匹配度偏支持,但仍需结合现实互动与长期运势。"
: band === "mixed"
? "基础匹配度中等,适合重点观察沟通节奏、价值观与关系承诺。"
: "基础匹配度偏谨慎,需要先处理冲突模式与现实条件。",
strengths: [
`Ashtakoot ${total}/${max}`,
`本人 D9 Moon${planetSign(self.moon)}`,
`对方 D9 Moon${planetSign(partner.moon)}`,
],
risks: [
"这不是完整婚恋结论;尚未纳入双方 Dasha、UL/DK 与长期时机。",
`D9 Venus/Mars 需要继续解释:本人 ${planetSign(self.venus)}/${planetSign(self.mars)},对方 ${planetSign(partner.venus)}/${planetSign(partner.mars)}。`,
],
nextEvidence: ["双方 Dasha", "UL/DK", "D9 7宫/7主", "现实关系时间线"],
};
}
function vargaPlanetSign(varga: Record<string, unknown>, division: string, planet: string) {
const result = varga.result && typeof varga.result === "object" ? varga.result as Record<string, unknown> : {};
const chart = result[division] && typeof result[division] === "object" ? result[division] as Record<string, unknown> : {};
const planets = chart.planets && typeof chart.planets === "object" ? chart.planets as Record<string, unknown> : {};
return planetSign(planet === "Ascendant" ? chart.ascendant : planets[planet]);
}
function businessReport(selfVargas: Record<string, unknown>, partnerVargas: Record<string, unknown>) {
return {
status: "partial_evidence",
scoreBand: "not_scored",
headline: "已完成基础合作结构筛查;这不是合作成败、收益或契约保证。",
strengths: [
`D10 事业轴:本人 ${vargaPlanetSign(selfVargas, "D10_Dasamsa", "Ascendant")} / 对方 ${vargaPlanetSign(partnerVargas, "D10_Dasamsa", "Ascendant")}`,
`D2 财富轴:本人 Moon ${vargaPlanetSign(selfVargas, "D2_Hora", "Moon")} / 对方 Moon ${vargaPlanetSign(partnerVargas, "D2_Hora", "Moon")}`,
`D11 收益轴:本人 Sun ${vargaPlanetSign(selfVargas, "D11_Rudramsa", "Sun")} / 对方 Sun ${vargaPlanetSign(partnerVargas, "D11_Rudramsa", "Sun")}`,
],
risks: [
"尚未完成 A10、功能吉凶、双方 Vimshottari + Narayana、Shadbala/AV 与外部数值一致性,不得据此断言合作结果或精确时点。",
],
nextEvidence: ["A10", "功能吉凶", "双方 Vimshottari + Narayana", "D10/D2/D11 原始度数与外部校验"],
};
}
async function postPython(path: string, body: unknown) {
const response = await fetch(`${apiBase}${path}`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(45_000),
});
const data = await response.json().catch(() => null);
if (!response.ok || !data || typeof data !== "object") {
throw new Error(`jyotish_api_${response.status}`);
}
return data as Record<string, unknown>;
}
export async function POST(request: Request) {
try {
const supabase = await createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "请先登录" }, { status: 401 });
}
if (!await isProductEnabled("compatibility")) {
return NextResponse.json(
{ error: "合盘服务暂未开放", code: "compatibility_product_disabled" },
{ status: 503 },
);
}
const body = await request.json().catch(() => null) as { selfProfile?: Profile; partnerProfile?: Profile; relationshipType?: RelationshipType } | null;
if (!body?.selfProfile || !body.partnerProfile) {
return NextResponse.json({ error: "请提供双方星盘资料" }, { status: 400 });
}
const [selfPayload, partnerPayload] = await Promise.all([
synastryBirthPayload(body.selfProfile),
synastryBirthPayload(body.partnerProfile),
]);
const selfChart = await postPython("/api/chart", selfPayload);
const partnerChart = await postPython("/api/chart", partnerPayload);
const relationshipType = body.relationshipType === "business" || body.relationshipType === "family" || body.relationshipType === "general"
? body.relationshipType
: "romance";
if (relationshipType === "business") {
const [selfVargas, partnerVargas] = await Promise.all([
postPython("/api/varga_full", { ...selfPayload, planets: selfChart.planets, ascendant: selfChart.ascendant, divisions: ["D2", "D10", "D11"] }),
postPython("/api/varga_full", { ...partnerPayload, planets: partnerChart.planets, ascendant: partnerChart.ascendant, divisions: ["D2", "D10", "D11"] }),
]);
return NextResponse.json({
status: "ok",
relationshipType,
claimStatus: "partial",
method: "d2_d10_d11_business_screening_partial",
evidenceLayers: ["d2_hora", "d10_dashamsa", "d11_ekadashamsa"],
blockedLayers: ["A10", "functional_benefic_malefic", "vimshottari_narayana", "shadbala_ashtakavarga", "external_engine_parity"],
relationshipReport: businessReport(selfVargas, partnerVargas),
});
}
if (relationshipType !== "romance") {
return NextResponse.json({
status: "blocked",
relationshipType,
message: "该关系类型尚无可验证的专用合盘计算合同;已保留问题草稿。",
});
}
const selfD9 = await postPython("/api/varga_full", {
...selfPayload,
planets: selfChart.planets,
ascendant: selfChart.ascendant,
divisions: ["D9"],
});
const partnerD9 = await postPython("/api/varga_full", {
...partnerPayload,
planets: partnerChart.planets,
ascendant: partnerChart.ascendant,
divisions: ["D9"],
});
const synastry = await postPython("/api/synastry", {
male_moon: moonLongitude(selfChart),
female_moon: moonLongitude(partnerChart),
});
return NextResponse.json({
status: "ok",
method: "ashtakoot_plus_moon_nakshatra_d9",
evidenceLayers: ["ashtakoot", "moon_nakshatra", "d9_navamsa"],
selfChart: { moon: moonSummary(selfChart), d9: d9Summary(selfD9) },
partnerChart: { moon: moonSummary(partnerChart), d9: d9Summary(partnerD9) },
synastry,
relationshipReport: relationshipReport(synastry, selfD9, partnerD9),
});
} catch (error) {
return NextResponse.json({
status: "blocked",
error: error instanceof Error ? error.message : "synastry_unavailable",
message: "合盘计算暂时不可用;可先保留合盘问题草稿。",
}, { status: 503 });
}
}