fix(synastry): store only the two marriage-match profiles

The saved row was the whole dossier twice. Keep the default and Raman match results, drop a concurrent duplicate insert back to the first row, and hide the new rows from the old history list.

Checked service tests 9/9, tsc 0, lint 0 errors, and test:db 73 pass / 13 fail matching the Windows known list.

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jesse-ux
2026-10-18 19:14:31 +08:00
co-authored by Grok (agent loop) Cursor
parent 52160a9cef
commit cf5a62c684
10 changed files with 206 additions and 18 deletions
@@ -29,6 +29,7 @@ export async function GET() {
.from("synastry_reports")
.select("id, partner_name, report, created_at")
.eq("user_id", user.id)
.is("rules_version", null)
.order("created_at", { ascending: false })
.limit(10);
+55 -7
View File
@@ -44,10 +44,18 @@ export type MarriageMatchPacket = {
raman_marriage_match: unknown;
};
/** Stored pair result. `default` is dossier.marriage_match; `raman` is the Raman profile. */
export type MarriageMatchStructured = {
rules_version: string;
default: unknown;
raman: unknown;
};
export type MarriageMatchRead = {
rulesVersion: string;
cached: boolean;
result: MarriageMatchPacket;
structured: MarriageMatchStructured;
dossier?: unknown;
};
type BirthBody = {
@@ -145,11 +153,41 @@ function isPacket(value: unknown): value is MarriageMatchPacket {
return typeof record.rules_version === "string" && "dossier" in record && "raman_marriage_match" in record;
}
function isStructured(value: unknown): value is MarriageMatchStructured {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const record = value as Record<string, unknown>;
return typeof record.rules_version === "string"
&& "default" in record
&& "raman" in record
&& !("dossier" in record)
&& !("chart_snapshots" in record);
}
function structuredFromPacket(packet: MarriageMatchPacket): MarriageMatchStructured {
const dossier = packet.dossier;
if (!dossier || typeof dossier !== "object" || Array.isArray(dossier)) {
throw new MarriageMatchServiceError("engine_failed");
}
const marriageMatch = (dossier as { marriage_match?: unknown }).marriage_match;
if (marriageMatch == null || packet.raman_marriage_match == null) {
throw new MarriageMatchServiceError("engine_failed");
}
return {
rules_version: packet.rules_version,
default: marriageMatch,
raman: packet.raman_marriage_match,
};
}
function isUniqueViolation(error: unknown): boolean {
return Boolean(error && typeof error === "object" && (error as { code?: unknown }).code === "23505");
}
async function findSaved(
db: MarriageMatchDb,
userId: string,
pair: ReturnType<typeof canonicalPair>,
): Promise<MarriageMatchPacket | null> {
): Promise<MarriageMatchStructured | null> {
const { data, error } = await db
.from("synastry_reports")
.select("structured_result")
@@ -163,7 +201,7 @@ async function findSaved(
if (error) throw new MarriageMatchServiceError("store_failed");
if (!data || typeof data !== "object") return null;
const stored = (data as { structured_result?: unknown }).structured_result;
return isPacket(stored) ? stored : null;
return isStructured(stored) ? stored : null;
}
export async function readOrComputeMarriageMatch(input: {
@@ -208,7 +246,7 @@ async function readResolvedPair(input: {
);
const saved = await findSaved(input.supabase, input.userId, pair);
if (saved) {
return { rulesVersion: MARRIAGE_MATCH_RULES_VERSION, cached: true, result: saved };
return { rulesVersion: MARRIAGE_MATCH_RULES_VERSION, cached: true, structured: saved };
}
let computed: unknown;
@@ -224,18 +262,28 @@ async function readResolvedPair(input: {
if (!isPacket(computed) || computed.rules_version !== MARRIAGE_MATCH_RULES_VERSION) {
throw new MarriageMatchServiceError("engine_failed");
}
const structured = structuredFromPacket(computed);
const inserted = await input.supabase.from("synastry_reports").insert({
user_id: input.userId,
partner_name: "婚恋合盘",
report: computed,
report: structured,
subject_low: pair.subjectLow,
subject_high: pair.subjectHigh,
fingerprint_low: pair.fingerprintLow,
fingerprint_high: pair.fingerprintHigh,
rules_version: MARRIAGE_MATCH_RULES_VERSION,
structured_result: computed,
structured_result: structured,
});
if (isUniqueViolation(inserted.error)) {
const raced = await findSaved(input.supabase, input.userId, pair);
if (raced) return { rulesVersion: MARRIAGE_MATCH_RULES_VERSION, cached: true, structured: raced };
}
if (inserted.error) throw new MarriageMatchServiceError("store_failed");
return { rulesVersion: MARRIAGE_MATCH_RULES_VERSION, cached: false, result: computed };
return {
rulesVersion: MARRIAGE_MATCH_RULES_VERSION,
cached: false,
structured,
dossier: computed.dossier,
};
}