Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/lib/dsm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,17 @@ for (const diagnosis of dsmDiagnoses) {
}
}

// Plain clinical names a clinician types that never appear in the catalogue text. Search has
// no stemming, so "depression" cannot reach "depressive" and Major depressive disorder was
// missing from a search for "depression" while incidental mentions filled the list.
const DSM_CLINICAL_SEARCH_ALIASES: Readonly<Record<string, readonly string[]>> = {
"major-depressive-disorder": ["depression", "major depression", "clinical depression"],
"persistent-depressive-disorder-dysthymia": ["chronic depression"],
Comment thread
BigSimmo marked this conversation as resolved.
Resolved
};

function dsmDiagnosisAliases(diagnosis: DsmDiagnosis) {
const aliases = new Set<string>();
for (const alias of DSM_CLINICAL_SEARCH_ALIASES[diagnosis.slug] ?? []) aliases.add(normalizeSearchText(alias));
const parenthetical = PARENTHETICAL_ABBREV_RE.exec(diagnosis.title)?.[1];
if (parenthetical) aliases.add(normalizeSearchText(parenthetical));
const initialism = dsmTitleInitialism(diagnosis.title);
Expand Down
28 changes: 22 additions & 6 deletions src/lib/rag/rag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ function asReviewOnlyCitations(citations: readonly Citation[]): Citation[] {
return citations.map((citation) => ({ ...citation, provenance: "review_only" as const }));
}

// Search deadline for the admin client. See searchChunksWithTiming for why it exceeds the shared 30 s default.
const SEARCH_SUPABASE_TIMEOUT_MS = 90_000;

const confidenceOrder = {
unsupported: 0,
low: 1,
Expand Down Expand Up @@ -1148,11 +1151,12 @@ async function searchChunksWithTiming(
recordSearchScoreTelemetry(telemetry, []);
return finishSearch(searchTiming, { results: [] as SearchResult[], telemetry });
}
// No client-side deadline here. PR #3454 (11 Oct 2026) gave the shared admin client a
// 30 s default, and the keyword RPC can take 30-48 s on the live corpus, so it was
// aborted and returned nothing for 9 of the 36 golden cases (eval canaries from 11 Oct).
// Search keeps the caller's own abort signal, as it did before that change.
const supabase = createAdminClient({ timeoutMs: null });
// Longer deadline than the shared 30 s default. PR #3454 (11 Oct 2026) gave the shared
// admin client that default, and the keyword RPC can take 30-48 s on the live corpus, so it
// was aborted and returned nothing for 9 of the 36 golden cases (eval canaries from 11 Oct).
// 90 s clears the slowest observed keyword call while still bounding a stalled request,
// including the query-correction RPC.
const supabase = createAdminClient({ timeoutMs: SEARCH_SUPABASE_TIMEOUT_MS });
Comment thread
BigSimmo marked this conversation as resolved.
Resolved
// Source-only retrieval never calls embeddings.
const sourceOnlyRetrieval = isSourceOnlyMode();
if (args.forceEmbedding && sourceOnlyRetrieval) {
Expand Down Expand Up @@ -1336,11 +1340,22 @@ async function searchChunksWithTiming(
shouldApplyUnsupportedSearchShortCircuit(retrievalQuery, queryAnalysis, ragAliasExpansions)
) {
// RC6: correct a would-be unsupported typo once before retrieval short-circuits.
let correctionFailed = false;
if (!args.typoCorrected && !sourceOnlyRetrieval) {
const { data: corrected } = await supabase.rpc("correct_clinical_query_terms", {
// Carry the caller's abort signal so a cancelled search does not leave this call running
// until the 90 s client deadline.
let correctionQuery = supabase.rpc("correct_clinical_query_terms", {
input_query: retrievalQuery,
min_sim: 0.45,
});
if (args.signal) correctionQuery = correctionQuery.abortSignal(args.signal);
const { data: corrected, error: correctionError } = await correctionQuery;
// A cancelled call resolves with an error rather than throwing; stop before the empty
// short-circuit result below is written to the search cache.
throwIfAborted(args.signal);
// A failed or timed-out correction is not proof the query has no match, so the empty
// result below must not be cached.
if (correctionError) correctionFailed = true;
if (typeof corrected === "string" && corrected && corrected.toLowerCase() !== retrievalQuery.toLowerCase()) {
const correctedSearch = await searchChunksWithTelemetry({
...args,
Expand All @@ -1360,6 +1375,7 @@ async function searchChunksWithTiming(
recordSearchScoreTelemetry(telemetry, []);
// Skip only when a reachable classifier could have produced a nondeterministic soft-tail zero.
if (
!correctionFailed &&
!shouldSkipUnsupportedSoftTailCacheWrite(retrievalQuery, queryAnalysis, {
openAiApiKeyPresent: Boolean(env.OPENAI_API_KEY),
})
Expand Down
5 changes: 3 additions & 2 deletions supabase/applied-migration-hashes.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"note": "sha256 of every migration this repository has shipped, with newlines normalised. An entry whose hash changes means an applied migration was edited, which cannot reach the live database. See scripts/check-migration-immutability.mjs.",
"generator": "scripts/check-migration-immutability.mjs",
"sealed_at": "2026-10-15T15:57:11.538Z",
"sealed_at": "2026-10-15T17:36:28.021Z",
"versions": {
"20260527000000_bulk_ingestion.sql": "813e8e314064a6dcbaaa55e2765a8ce901d5783b304b4a7af7bb7417a07daf7c",
"20260527001000_documents_policy_cleanup.sql": "559c26f3172bf75e545a41adef75538b2bf648ea7d53c8eb3d3ea7c244c65a48",
Expand Down Expand Up @@ -313,6 +313,7 @@
"20261013200007_managed_native_pdf_terminal_append.sql": "94ac297bad4a1f9d28fcb13d018237df081aa5b3da328073be8c9e17774bd292",
"20261014164220_validate_managed_ingestion_historical_reconciliation.sql": "8ffd75292620e82065300eb1e1d008c229545dde37826862820bfd6ea1314587",
"20261015160000_document_child_policies_check_parent_owner.sql": "71210bbc5aecdca74bee2ae32181ba2861e65017ca1a5e87995aa8e557946094",
"20261015174500_roster_open_post_once.sql": "a4edfe07497e3b833c4bedecf1b41d0d7b9fb0b73a798a71b3c17bccb9f851ff"
"20261015174500_roster_open_post_once.sql": "a4edfe07497e3b833c4bedecf1b41d0d7b9fb0b73a798a71b3c17bccb9f851ff",
"20261015180000_prefilter_text_only_governed_retrieval.sql": "8ff1f445269ad6413d5f00b304fc169dd2521f0d01e215415bbc4f573d2a926a"
}
}
8 changes: 4 additions & 4 deletions supabase/drift-manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"generated_at": "2026-10-15T15:56:45.785Z",
"generated_at": "2026-10-15T23:31:52.942Z",
"generator": "scripts/generate-drift-manifest.ts",
"postgres_image": "supabase/postgres:17.6.1.127@sha256:be60aee15997daca475b710b734bc6bfe52cd544dcd7e9fd2ff58210b6747d83",
"schema_sha256": "e561ea277c34ddec08d6b676ebcb8603c0e98b04e0ca311c9fbf4a4f49246895",
"replay_seconds": 38,
"schema_sha256": "067f617ef440b12532d63d7a4e89054919da9258e403c1beaace99a7578033f2",
"replay_seconds": 34,
"snapshot": {
"views": [
{
Expand Down Expand Up @@ -19441,7 +19441,7 @@
"acl": [
"postgres=X/postgres"
],
"def_hash": "92bf14fee49b501305bd81ac583a43cd",
"def_hash": "090e9afd33a764ce6ec81c632abd83b4",
"signature": "public.match_governed_candidate_chunks_v3(extensions.vector,text,integer,double precision,uuid[],uuid,boolean,text[],uuid,text,bigint,text[])"
},
{
Expand Down
Loading
Loading