From aff994bd058f22592972090eb6571dec79d83657 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 03:33:29 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/lib/email-matching.server.ts | 147 +++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 src/lib/email-matching.server.ts diff --git a/src/lib/email-matching.server.ts b/src/lib/email-matching.server.ts new file mode 100644 index 0000000..804343e --- /dev/null +++ b/src/lib/email-matching.server.ts @@ -0,0 +1,147 @@ +// Server-side helper that loads all the lookup data needed for matching and +// runs `matchEmailToCases` against an array of incoming_emails rows. Updates +// each row in place with the resulting status, case_id, suggestions, etc. + +import { supabaseAdmin } from "@/integrations/supabase/client.server"; +import { + matchEmailToCases, + type CaseLookup, + type ContactCaseLink, + type ClientCaseLink, + type HomeownerCaseLink, + type EmailLike, + type PriorEmail, +} from "@/lib/email-matching"; + +interface IncomingEmailRow extends EmailLike { + id: string; +} + +async function loadLookups() { + const [casesRes, contactsRes, clientsRes, homeownersRes, priorRes] = + await Promise.all([ + supabaseAdmin + .from("cases") + .select( + "id, case_number, court_case_number, case_caption, title, client_id, opposing_counsel_email, status, updated_at", + ) + .is("archived_at", null), + supabaseAdmin + .from("case_contacts") + .select("case_id, contact_id, contact:contacts(email)"), + supabaseAdmin + .from("clients") + .select("id, primary_contact_email") + .is("archived_at", null), + supabaseAdmin + .from("homeowners") + .select("id, email, client_id") + .is("archived_at", null), + supabaseAdmin + .from("incoming_emails") + .select("message_id, case_id") + .not("case_id", "is", null) + .not("message_id", "is", null) + .limit(5000), + ]); + + const cases: CaseLookup[] = (casesRes.data ?? []) as any; + + const contactLinks: ContactCaseLink[] = ((contactsRes.data ?? []) as any[]) + .map((row: any) => ({ + case_id: row.case_id, + contact_id: row.contact_id, + email: row.contact?.email ?? null, + })) + .filter((r) => r.email); + + // Build case_ids per client/homeowner + const casesByClient = new Map(); + const clientByCases = new Map(); + for (const c of cases) { + clientByCases.set(c.id, c.client_id); + if (c.client_id) { + const arr = casesByClient.get(c.client_id) ?? []; + arr.push(c.id); + casesByClient.set(c.client_id, arr); + } + } + + const clientLinks: ClientCaseLink[] = ((clientsRes.data ?? []) as any[]) + .filter((r) => r.primary_contact_email) + .map((r) => ({ + client_id: r.id, + primary_contact_email: r.primary_contact_email, + case_ids: casesByClient.get(r.id) ?? [], + })) + .filter((r) => r.case_ids.length > 0); + + const homeownerLinks: HomeownerCaseLink[] = ((homeownersRes.data ?? []) as any[]) + .filter((r) => r.email && r.client_id) + .map((r) => ({ + homeowner_id: r.id, + email: r.email, + case_ids: casesByClient.get(r.client_id) ?? [], + })) + .filter((r) => r.case_ids.length > 0); + + const priorEmails: PriorEmail[] = (priorRes.data ?? []) as any; + + return { cases, contactLinks, clientLinks, homeownerLinks, priorEmails }; +} + +export async function matchAndUpdateEmails(emailIds: string[]): Promise<{ + matched: number; + suggested: number; + unmatched: number; +}> { + if (emailIds.length === 0) return { matched: 0, suggested: 0, unmatched: 0 }; + + const { data: rowsRaw, error } = await supabaseAdmin + .from("incoming_emails") + .select( + "id, message_id, in_reply_to, email_references, from_address, to_addresses, cc_addresses, subject, body_text", + ) + .in("id", emailIds); + + if (error) throw new Error(`Failed to load emails for matching: ${error.message}`); + + const rows = (rowsRaw ?? []) as IncomingEmailRow[]; + if (rows.length === 0) return { matched: 0, suggested: 0, unmatched: 0 }; + + const lookups = await loadLookups(); + + let matched = 0, + suggested = 0, + unmatched = 0; + + for (const row of rows) { + const result = matchEmailToCases( + row, + lookups.cases, + lookups.contactLinks, + lookups.clientLinks, + lookups.homeownerLinks, + lookups.priorEmails, + ); + + const update: Record = { + match_status: result.status, + match_suggestions: result.suggestions, + match_reason: result.reason, + }; + if (result.status === "matched" && result.case_id) { + update.case_id = result.case_id; + update.matched_at = new Date().toISOString(); + matched++; + } else if (result.status === "suggested") { + suggested++; + } else { + unmatched++; + } + + await supabaseAdmin.from("incoming_emails").update(update).eq("id", row.id); + } + + return { matched, suggested, unmatched }; +} \ No newline at end of file