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:
co-authored by
Grok (agent loop)
Cursor
parent
52160a9cef
commit
cf5a62c684
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user