import { NextResponse } from "next/server"; import { refuseWhenAccountDeletionPending } from "@/lib/account-deletion-server"; import { z } from "zod"; import { isProductEnabled } from "@/lib/product-access"; import { consumeUserRequestRateLimit } from "@/lib/request-rate-limit"; import { globalBirthProfileFromAccountRow } from "@/lib/server-owned-birth-profile"; import { ConsultationSubjectError, loadSubjectBirth } from "@/lib/subject-birth"; import { createServerSupabaseClient } from "@/lib/supabase/server"; import { synastryBirthPayload } from "@/lib/global-birth-payloads"; const apiBase = process.env.JYOTISH_API_BASE ?? "http://127.0.0.1:5200"; const synastryRequestSchema = z.object({ partnerChartProfileId: z.string().uuid(), relationshipType: z.enum(["romance", "business", "family", "general"]).optional(), }).strict(); function moonLongitude(chart: Record) { const planets = chart.planets && typeof chart.planets === "object" ? chart.planets as Record : {}; const moon = planets.Moon && typeof planets.Moon === "object" ? planets.Moon as Record : {}; 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) { const planets = chart.planets && typeof chart.planets === "object" ? chart.planets as Record : {}; const moon = planets.Moon && typeof planets.Moon === "object" ? planets.Moon as Record : {}; return { sign: moon.sign, nakshatra: moon.nakshatra, pada: moon.nakshatra_pada, lord: moon.nakshatra_lord, longitude: moonLongitude(chart), }; } function d9Summary(varga: Record) { const result = varga.result && typeof varga.result === "object" ? varga.result as Record : {}; const d9 = result.D9_Navamsa && typeof result.D9_Navamsa === "object" ? result.D9_Navamsa as Record : {}; const ascendant = d9.ascendant && typeof d9.ascendant === "object" ? d9.ascendant as Record : {}; const planets = d9.planets && typeof d9.planets === "object" ? d9.planets as Record : {}; 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).sign || "unknown") : "unknown"; } function relationshipReport(synastry: Record, selfD9: Record, partnerD9: Record) { 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, division: string, planet: string) { const result = varga.result && typeof varga.result === "object" ? varga.result as Record : {}; const chart = result[division] && typeof result[division] === "object" ? result[division] as Record : {}; const planets = chart.planets && typeof chart.planets === "object" ? chart.planets as Record : {}; return planetSign(planet === "Ascendant" ? chart.ascendant : planets[planet]); } function businessReport(selfVargas: Record, partnerVargas: Record) { 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; } 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 }); } const frozen = await refuseWhenAccountDeletionPending(user.id); if (frozen) return frozen; if (!await isProductEnabled("compatibility")) { return NextResponse.json( { error: "合盘服务暂未开放", code: "compatibility_product_disabled" }, { status: 503 }, ); } const limited = consumeUserRequestRateLimit("synastry", user.id); if (!limited.ok) { return NextResponse.json({ error: "请求过于频繁", code: "rate_limited", retryAfterSeconds: limited.retryAfterSeconds, }, { status: 429, headers: { "Retry-After": String(limited.retryAfterSeconds) } }); } const parsed = synastryRequestSchema.safeParse(await request.json().catch(() => null)); if (!parsed.success) { return NextResponse.json({ error: "请选择已保存的对方星盘" }, { status: 400 }); } let selfResolved; try { selfResolved = await loadSubjectBirth(supabase, user.id, "self"); } catch (error) { if (error instanceof ConsultationSubjectError) { return NextResponse.json({ error: "请先完成本人出生资料" }, { status: 409 }); } throw error; } if (!selfResolved.chartable) { return NextResponse.json({ error: "请先完成本人出生资料" }, { status: 409 }); } let partnerResolved; try { partnerResolved = await loadSubjectBirth(supabase, user.id, parsed.data.partnerChartProfileId); } catch (error) { if (error instanceof ConsultationSubjectError && error.code === "subject_unavailable") { return NextResponse.json({ error: "暂时无法读取对方星盘" }, { status: 503 }); } if (error instanceof ConsultationSubjectError) { return NextResponse.json({ error: "请先把对方星盘保存到云端星盘库" }, { status: 404 }); } throw error; } if (!partnerResolved.chartable) { return NextResponse.json({ error: "请先把对方星盘保存到云端星盘库" }, { status: 404 }); } const [selfPayload, partnerPayload] = await Promise.all([ synastryBirthPayload(globalBirthProfileFromAccountRow(selfResolved.row)), synastryBirthPayload(globalBirthProfileFromAccountRow(partnerResolved.row)), ]); const selfChart = await postPython("/api/chart", selfPayload); const partnerChart = await postPython("/api/chart", partnerPayload); const relationshipType = parsed.data.relationshipType === "business" || parsed.data.relationshipType === "family" || parsed.data.relationshipType === "general" ? parsed.data.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 }); } }