From d6f558ca9130a9a4f3748ee4837229687cbab271 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:17:51 +0800 Subject: [PATCH] chore: remove accidentally committed scratch/ debugging files; gitignore scratch/ The 12 files under scratch/ (one-off SQL and TS debugging scripts) landed on main via the 'Save Codex local changes' commit (0aa8f5d7e) and were never meant to be tracked. Remove them and gitignore scratch/ so local debugging files cannot be committed again. The tsconfig 'scratch/**' exclude from PR #139 stays: tsc does not respect .gitignore, so the exclude keeps untracked local scratch files out of typecheck. Files remain recoverable from git history if any turn out to matter. Co-Authored-By: Claude Fable 5 --- .gitignore | 2 + scratch/apply-fast-schema.sql | 575 ------------------------------ scratch/audit-guidelines.ts | 107 ------ scratch/check-indexes.ts | 22 -- scratch/check-queued-docs.ts | 22 -- scratch/count-files.ts | 59 ---- scratch/count-status.ts | 54 --- scratch/create-hnsw-indexes.sql | 21 -- scratch/fix-indexes.ts | 15 - scratch/queue-queued-docs.ts | 57 --- scratch/run-migrations-safe.sql | 603 -------------------------------- scratch/show-samples.ts | 22 -- scratch/update-health-check.sql | 132 ------- 13 files changed, 2 insertions(+), 1689 deletions(-) delete mode 100644 scratch/apply-fast-schema.sql delete mode 100644 scratch/audit-guidelines.ts delete mode 100644 scratch/check-indexes.ts delete mode 100644 scratch/check-queued-docs.ts delete mode 100644 scratch/count-files.ts delete mode 100644 scratch/count-status.ts delete mode 100644 scratch/create-hnsw-indexes.sql delete mode 100644 scratch/fix-indexes.ts delete mode 100644 scratch/queue-queued-docs.ts delete mode 100644 scratch/run-migrations-safe.sql delete mode 100644 scratch/show-samples.ts delete mode 100644 scratch/update-health-check.sql diff --git a/.gitignore b/.gitignore index d6b6f2e68..25e923da9 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,8 @@ next-env.d.ts # agent/QA artifacts .codex-screenshots/ +# Local debugging scratch space — never commit (accidentally landed once via 'Save Codex local changes') +scratch/ .qa-smoke/ *.pid tmp_output.txt diff --git a/scratch/apply-fast-schema.sql b/scratch/apply-fast-schema.sql deleted file mode 100644 index e76dbc9a4..000000000 --- a/scratch/apply-fast-schema.sql +++ /dev/null @@ -1,575 +0,0 @@ -SET search_path = public, extensions; - --- ============================================================ --- 1. RAG RETRIEVAL LOGS TABLE --- Per-query diagnostics: candidate scores, latency breakdown, --- miss detection. --- ============================================================ - -create table if not exists public.rag_retrieval_logs ( - id uuid primary key default gen_random_uuid(), - owner_id uuid references auth.users(id) on delete set null, - query text not null, - normalized_query text, - query_class text, - retrieval_strategy text, - - -- Candidate scores - candidate_count integer not null default 0, - top_similarity double precision, - top_text_rank double precision, - top_hybrid_score double precision, - top_rrf_score double precision, - mean_hybrid_score double precision, - - -- Selected citations - selected_chunk_ids uuid[] not null default '{}', - selected_document_ids uuid[] not null default '{}', - selected_count integer not null default 0, - - -- Latency breakdown - embedding_latency_ms integer, - rpc_latency_ms integer, - rerank_latency_ms integer, - total_latency_ms integer, - - -- Source counts - vector_candidate_count integer, - text_candidate_count integer, - memory_card_count integer, - index_unit_count integer, - embedding_field_count integer, - - -- Miss detection - is_miss boolean not null default false, - miss_reason text, - - -- Metadata - embedding_cache_hit boolean, - metadata jsonb not null default '{}'::jsonb, - created_at timestamptz not null default now() -); - -create index if not exists rag_retrieval_logs_owner_created_idx - on public.rag_retrieval_logs(owner_id, created_at desc); -create index if not exists rag_retrieval_logs_miss_idx - on public.rag_retrieval_logs(is_miss, created_at desc) - where is_miss = true; -create index if not exists rag_retrieval_logs_strategy_idx - on public.rag_retrieval_logs(retrieval_strategy, created_at desc); - -alter table public.rag_retrieval_logs enable row level security; -grant select, insert, update, delete on table public.rag_retrieval_logs to service_role; -grant select on table public.rag_retrieval_logs to authenticated; - -create policy "rag retrieval logs owner read" on public.rag_retrieval_logs - for select to authenticated using (owner_id = (select auth.uid())); - --- ============================================================ --- 2. CONTENT_HASH DEDUP ON DOCUMENT_EMBEDDING_FIELDS --- Replace the problematic unique(document_id, source_chunk_id, --- field_type, content) constraint that indexes full text. --- ============================================================ - -alter table public.document_embedding_fields - add column if not exists content_hash text; - --- Populate for existing rows -update public.document_embedding_fields - set content_hash = md5(content) - where content_hash is null; - --- Drop the old constraint. -alter table public.document_embedding_fields - drop constraint if exists document_embedding_fields_document_id_source_chunk_id_field_key; -alter table public.document_embedding_fields - drop constraint if exists document_embedding_fields_document_id_source_chunk_id_fiel_key; - --- Create hash-based unique index -create unique index if not exists document_embedding_fields_dedup_idx - on public.document_embedding_fields(document_id, source_chunk_id, field_type, content_hash); - --- ============================================================ --- 3. DROP DUPLICATE/REDUNDANT INDEXES --- These single-column indexes are covered by composite indexes --- with the same leading column. --- ============================================================ - -drop index if exists document_chunks_document_id_idx; -drop index if exists document_sections_document_id_idx; -drop index if exists document_memory_cards_document_id_idx; -drop index if exists document_images_document_id_idx; -drop index if exists document_labels_document_id_idx; -drop index if exists document_embedding_fields_document_id_idx; -drop index if exists document_table_facts_document_id_idx; -drop index if exists document_index_quality_document_id_idx; - --- ============================================================ --- 4. OTHER EXPECTED SCHEMA INDEXES FROM OTHER MIGRATIONS --- ============================================================ -create index if not exists ingestion_jobs_status_next_run_idx - on public.ingestion_jobs(status, next_run_at asc) - where status in ('pending', 'processing'); - -create index if not exists ingestion_jobs_document_status_idx - on public.ingestion_jobs(document_id, status); - -create index if not exists import_batches_status_created_idx - on public.import_batches(status, created_at desc); - -create index if not exists storage_cleanup_jobs_status_created_idx - on public.storage_cleanup_jobs(status, created_at desc); - --- ============================================================ --- 5. ENHANCED HYBRID SCORING --- ============================================================ - -create or replace function public.match_document_chunks_hybrid( - query_embedding extensions.vector(1536), - query_text text, - match_count integer default 12, - min_similarity double precision default 0.12, - document_filters uuid[] default null, - owner_filter uuid default null -) -returns table ( - id uuid, - document_id uuid, - title text, - file_name text, - page_number integer, - chunk_index integer, - section_heading text, - content text, - retrieval_synopsis text, - image_ids uuid[], - source_metadata jsonb, - similarity double precision, - text_rank double precision, - hybrid_score double precision, - rrf_score double precision, - images jsonb -) -language sql -stable -set search_path = public, extensions, pg_temp -as $$ - with query as ( - select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq - ), - vector_ranked as ( - select - c.id, - c.document_id, - c.page_number, - c.chunk_index, - c.section_heading, - c.content, - c.retrieval_synopsis, - c.image_ids, - 1 - (c.embedding <=> query_embedding) as similarity, - ( - ts_rank_cd(c.search_tsv, query.tsq) + - (ts_rank_cd(d.title_search_tsv, query.tsq) * 3.0) - )::double precision as text_rank, - row_number() over (order by c.embedding <=> query_embedding) as vector_rank, - null::bigint as text_match_rank, - coalesce((d.metadata->'rag_indexing_version') is not null, false) as has_deep_index, - d.updated_at as doc_updated_at, - coalesce( - (select q.quality_score from public.document_index_quality q where q.document_id = c.document_id), - 0.7 - ) as quality_score - from public.document_chunks c - join public.documents d on d.id = c.document_id - cross join query - where (document_filters is null or c.document_id = any(document_filters)) - and (owner_filter is null or d.owner_id = owner_filter) - and d.status = 'indexed' - and 1 - (c.embedding <=> query_embedding) >= min_similarity - order by c.embedding <=> query_embedding - limit greatest(match_count * 6, 48) - ), - text_ranked as ( - select - c.id, - c.document_id, - c.page_number, - c.chunk_index, - c.section_heading, - c.content, - c.retrieval_synopsis, - c.image_ids, - 1 - (c.embedding <=> query_embedding) as similarity, - ( - ts_rank_cd(c.search_tsv, query.tsq) + - (ts_rank_cd(d.title_search_tsv, query.tsq) * 3.0) - )::double precision as text_rank, - null::bigint as vector_rank, - row_number() over ( - order by - ( - ts_rank_cd(c.search_tsv, query.tsq) + - (ts_rank_cd(d.title_search_tsv, query.tsq) * 3.0) - ) desc, - c.embedding <=> query_embedding - ) as text_match_rank, - coalesce((d.metadata->'rag_indexing_version') is not null, false) as has_deep_index, - d.updated_at as doc_updated_at, - coalesce( - (select q.quality_score from public.document_index_quality q where q.document_id = c.document_id), - 0.7 - ) as quality_score - from public.document_chunks c - join public.documents d on d.id = c.document_id - cross join query - where (document_filters is null or c.document_id = any(document_filters)) - and (owner_filter is null or d.owner_id = owner_filter) - and d.status = 'indexed' - and (c.search_tsv @@ query.tsq or d.title_search_tsv @@ query.tsq) - order by ( - ts_rank_cd(c.search_tsv, query.tsq) + - (ts_rank_cd(d.title_search_tsv, query.tsq) * 3.0) - ) desc - limit greatest(match_count * 6, 48) - ), - combined as ( - select * from vector_ranked - union all - select * from text_ranked - ), - scored as ( - select - id, - document_id, - page_number, - chunk_index, - section_heading, - content, - retrieval_synopsis, - image_ids, - max(similarity)::double precision as similarity, - max(text_rank)::double precision as text_rank, - min(vector_rank) as vector_rank, - min(text_match_rank) as text_match_rank, - max(quality_score)::double precision as quality_score, - bool_or(has_deep_index) as has_deep_index, - max(doc_updated_at) as doc_updated_at - from combined - group by id, document_id, page_number, chunk_index, section_heading, content, retrieval_synopsis, image_ids - ), - scored_metrics as ( - select - scored.*, - ( - (scored.similarity * 0.62) - + (least(scored.text_rank, 1) * 0.22) - + (scored.quality_score * 0.10) - + (case when scored.doc_updated_at > now() - interval '90 days' then 0.06 else 0 end) - )::double precision as hybrid_score, - ( - coalesce(1.0 / (60 + scored.vector_rank), 0) + - coalesce(1.0 / (60 + scored.text_match_rank), 0) - )::double precision as rrf_score - from scored - ), - hybrid_candidates as ( - select id - from scored_metrics - order by hybrid_score desc, similarity desc, text_rank desc - limit match_count - ), - vector_candidates as ( - select id - from scored_metrics - order by similarity desc, hybrid_score desc - limit match_count - ), - text_candidates as ( - select id - from scored_metrics - order by text_rank desc, hybrid_score desc - limit match_count - ), - rrf_candidates as ( - select id - from scored_metrics - order by rrf_score desc, hybrid_score desc - limit match_count - ), - candidate_ids as ( - select id from hybrid_candidates - union - select id from vector_candidates - union - select id from text_candidates - union - select id from rrf_candidates - ) - select - c.id, - c.document_id, - d.title, - d.file_name, - c.page_number, - c.chunk_index, - c.section_heading, - c.content, - c.retrieval_synopsis, - c.image_ids, - d.metadata as source_metadata, - c.similarity, - c.text_rank, - c.hybrid_score, - c.rrf_score, - public.chunk_image_metadata(c.image_ids) as images - from scored_metrics c - join candidate_ids candidates on candidates.id = c.id - join public.documents d on d.id = c.document_id - order by c.hybrid_score desc, c.rrf_score desc, c.similarity desc, c.text_rank desc - limit match_count; -$$; - -create or replace function public.match_document_index_units_hybrid( - query_embedding extensions.vector(1536), - query_text text, - match_count integer default 24, - min_similarity double precision default 0.1, - document_filters uuid[] default null, - owner_filter uuid default null -) -returns table ( - id uuid, - document_id uuid, - source_chunk_id uuid, - source_image_id uuid, - unit_type text, - title text, - content text, - page_start integer, - page_end integer, - heading_path text[], - normalized_terms text[], - source_span jsonb, - quality_score real, - extraction_mode text, - similarity double precision, - text_rank double precision, - hybrid_score double precision, - metadata jsonb -) -language sql -stable -set search_path = public, extensions, pg_temp -as $$ - with query as ( - select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq, - regexp_split_to_array(lower(coalesce(query_text, '')), '\s+') as terms - ), - ranked as ( - select u.id, u.document_id, u.source_chunk_id, u.source_image_id, u.unit_type, u.title, u.content, u.page_start, - u.page_end, u.heading_path, u.normalized_terms, u.source_span, u.quality_score, u.extraction_mode, - (1 - (u.embedding <=> query_embedding))::double precision as similarity, - (ts_rank_cd(u.search_tsv, query.tsq) - + case when u.normalized_terms && query.terms then 0.25 else 0 end - + case when u.unit_type in ('askable_question', 'table_fact', 'clinical_fact') then 0.06 - when u.unit_type = 'section_summary' then 0.03 - else 0 end - )::double precision as text_rank, - u.metadata - from public.document_index_units u - join public.documents d on d.id = u.document_id - cross join query - where d.status = 'indexed' - and (document_filters is null or u.document_id = any(document_filters)) - and (owner_filter is null or d.owner_id = owner_filter) - and u.source_chunk_id is not null - and (u.search_tsv @@ query.tsq or u.normalized_terms && query.terms) - order by text_rank desc, similarity desc - limit greatest(match_count * 3, 48) - ) - select id, document_id, source_chunk_id, source_image_id, unit_type, title, content, page_start, page_end, heading_path, - normalized_terms, source_span, quality_score, extraction_mode, similarity, text_rank, - ( - (similarity * 0.52) - + (least(text_rank, 1) * 0.28) - + (quality_score * 0.12) - + (case when extraction_mode in ('model_heavy', 'hybrid') then 0.04 else 0 end) - + (case when unit_type = 'askable_question' then 0.04 else 0 end) - )::double precision as hybrid_score, - metadata - from ranked - order by hybrid_score desc, similarity desc, text_rank desc - limit match_count; -$$; - -create or replace function public.analyze_rag_tables() -returns void -language plpgsql -set search_path = public, extensions, pg_temp -as $$ -begin - analyze public.document_chunks; - analyze public.document_memory_cards; - analyze public.document_index_units; - analyze public.document_embedding_fields; - analyze public.document_table_facts; - analyze public.documents; -end; -$$; - -revoke execute on function public.analyze_rag_tables() from public, anon, authenticated; -grant execute on function public.analyze_rag_tables() to service_role; - -create or replace function public.reset_document_index(p_document_id uuid) -returns void -language plpgsql -set search_path = public, extensions, pg_temp -as $$ -begin - perform set_config('statement_timeout', '180000', true); - delete from public.document_index_units where document_id = p_document_id; - delete from public.document_memory_cards where document_id = p_document_id; - delete from public.document_sections where document_id = p_document_id; - delete from public.document_table_facts where document_id = p_document_id; - delete from public.document_embedding_fields where document_id = p_document_id; - delete from public.document_index_quality where document_id = p_document_id; - delete from public.document_chunks where document_id = p_document_id; - delete from public.document_images where document_id = p_document_id; - delete from public.document_pages where document_id = p_document_id; -end; -$$; - --- Update health check -create or replace function public.search_schema_health() -returns jsonb -language plpgsql -stable -set search_path = public, extensions, pg_catalog, pg_temp -as $$ -declare - missing text[] := array[]::text[]; - vector_type_oid oid; - vector_schema text; - index_name text; -begin - select t.oid, n.nspname - into vector_type_oid, vector_schema - from pg_type t - join pg_namespace n on n.oid = t.typnamespace - where t.typname = 'vector' - and n.nspname = 'extensions' - limit 1; - - if vector_type_oid is null then - missing := array_append(missing, 'extensions.vector_type'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_chunks' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_chunks.extensions_vector_signature'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_chunks_hybrid' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_chunks_hybrid.extensions_vector_signature'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_memory_cards_hybrid' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_memory_cards_hybrid.extensions_vector_signature'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_index_units_hybrid' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_index_units_hybrid.extensions_vector_signature'); - end if; - - if to_regclass('public.document_index_units') is null then - missing := array_append(missing, 'document_index_units.table'); - end if; - if to_regclass('public.rag_retrieval_logs') is null then - missing := array_append(missing, 'rag_retrieval_logs.table'); - end if; - if not exists (select 1 from pg_class where relname = 'documents_title_trgm_idx') then - missing := array_append(missing, 'documents_title_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_chunks_content_trgm_idx') then - missing := array_append(missing, 'document_chunks_content_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_labels_label_trgm_idx') then - missing := array_append(missing, 'document_labels_label_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_summaries_summary_trgm_idx') then - missing := array_append(missing, 'document_summaries_summary_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_index_units_embedding_hnsw_idx') then - missing := array_append(missing, 'document_index_units_embedding_hnsw_idx'); - end if; - if not exists (select 1 from pg_class where relname in ('document_embedding_fields_owner_idx', 'document_embedding_fields_owner_id_idx')) then - missing := array_append(missing, 'document_embedding_fields_owner_idx'); - end if; - if not exists (select 1 from pg_class where relname in ('document_table_facts_owner_idx', 'document_table_facts_owner_id_idx')) then - missing := array_append(missing, 'document_table_facts_owner_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_table_facts_source_image_idx') then - missing := array_append(missing, 'document_table_facts_source_image_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_embedding_fields_dedup_idx') then - missing := array_append(missing, 'document_embedding_fields_dedup_idx'); - end if; - foreach index_name in array array[ - 'document_pages_document_idx', - 'document_images_document_idx', - 'document_sections_document_idx', - 'document_memory_cards_document_idx', - 'document_chunks_document_idx', - 'document_table_facts_document_idx', - 'document_embedding_fields_document_idx', - 'document_index_units_document_idx', - 'ingestion_jobs_status_next_run_idx', - 'ingestion_jobs_document_status_idx', - 'documents_owner_status_idx', - 'import_batches_status_created_idx', - 'storage_cleanup_jobs_status_created_idx' - ] loop - if not exists (select 1 from pg_class where relname = index_name) then - missing := array_append(missing, index_name); - end if; - end loop; - - return jsonb_build_object( - 'ok', cardinality(missing) = 0, - 'missing', missing, - 'vector_extension_schema', vector_schema, - 'checked_at', now() - ); -end; -$$; - -revoke execute on function public.search_schema_health() from public, anon, authenticated; -grant execute on function public.search_schema_health() to service_role; diff --git a/scratch/audit-guidelines.ts b/scratch/audit-guidelines.ts deleted file mode 100644 index 985389e68..000000000 --- a/scratch/audit-guidelines.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { loadEnvConfig } from "@next/env"; -import * as fs from "fs"; -import * as path from "path"; - -loadEnvConfig(process.cwd()); - -function getFilesRecursively(dir: string): string[] { - let results: string[] = []; - if (!fs.existsSync(dir)) return []; - const list = fs.readdirSync(dir); - for (const file of list) { - const filePath = path.join(dir, file); - const stat = fs.statSync(filePath); - if (stat && stat.isDirectory()) { - results = results.concat(getFilesRecursively(filePath)); - } else { - if (/\.(pdf|docx|doc)$/i.test(file)) { - results.push(filePath); - } - } - } - return results; -} - -async function main() { - const { createAdminClient } = await import("../src/lib/supabase/admin"); - const supabase = createAdminClient(); - - // 1. Scan the local directory - const rootDir = "C:\\Users\\joshs\\OneDrive\\Medicine\\Guidelines"; - console.log(`Scanning local files in ${rootDir}...`); - const localFiles = getFilesRecursively(rootDir); - console.log(`Found ${localFiles.length} local guideline files (PDF/DOC/DOCX).`); - - // 2. Fetch all documents from the database - console.log("Fetching documents from Supabase..."); - const { data: dbDocs, error } = await supabase - .from("documents") - .select("id, title, status, metadata"); - - if (error) { - console.error("Error fetching database documents:", error); - return; - } - - console.log(`Found ${dbDocs?.length || 0} documents in Supabase.`); - - // Create a map of lowercased source path -> db doc - const dbDocsByPath = new Map(); - for (const doc of dbDocs || []) { - const meta = doc.metadata && typeof doc.metadata === "object" ? (doc.metadata as any) : {}; - if (meta.source_path) { - dbDocsByPath.set(meta.source_path.toLowerCase(), doc); - } - } - - // 3. Match them up - const categories = ["BMJ", "EMHS", "KEMH", "NMHS", "PHC", "RKPG", "SMHS"]; - const stats: Record = {}; - - for (const cat of categories) { - stats[cat] = { total: 0, indexed: 0, processing: 0, queued: 0, missing: 0, missingFiles: [] }; - } - - for (const file of localFiles) { - // Determine category from path - const relative = path.relative(rootDir, file); - const topFolder = relative.split(path.sep)[0]; - - if (stats[topFolder]) { - stats[topFolder].total++; - const matched = dbDocsByPath.get(file.toLowerCase()); - if (matched) { - if (matched.status === "indexed") { - stats[topFolder].indexed++; - } else if (matched.status === "processing") { - stats[topFolder].processing++; - } else if (matched.status === "queued") { - stats[topFolder].queued++; - } else { - stats[topFolder].missing++; - stats[topFolder].missingFiles.push(relative); - } - } else { - stats[topFolder].missing++; - stats[topFolder].missingFiles.push(relative); - } - } - } - - console.log("=== Auditing Summary by Directory ==="); - for (const [cat, info] of Object.entries(stats)) { - console.log(`\nDirectory: ${cat}`); - console.log(` Total Local Files: ${info.total}`); - console.log(` Indexed (Done): ${info.indexed}`); - console.log(` Processing: ${info.processing}`); - console.log(` Queued: ${info.queued}`); - console.log(` Not in Database: ${info.missing}`); - if (info.missing > 0 && info.missing <= 5) { - console.log(` Missing files:`, info.missingFiles); - } else if (info.missing > 5) { - console.log(` Missing files (first 5):`, info.missingFiles.slice(0, 5)); - } - } -} - -main().catch(console.error); diff --git a/scratch/check-indexes.ts b/scratch/check-indexes.ts deleted file mode 100644 index b31ad49b0..000000000 --- a/scratch/check-indexes.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { loadEnvConfig } from "@next/env"; -loadEnvConfig(process.cwd()); - -async function main() { - const { createAdminClient } = await import("../src/lib/supabase/admin"); - const supabase = createAdminClient(); - - // We can query pg_indexes view using Supabase client if it is exposed, or we can use RPC - // Wait, let's see if we can query from a system catalog or check what indexes exist. - // Actually, pg_indexes might not be directly exposed. Let's see if we can do a select from pg_indexes. - const { data, error } = await supabase - .from("pg_indexes") // this might fail if not in public schema, but system catalogs aren't exposed in PostgREST by default. - .select("*"); - - if (error) { - console.error("System query error (expected if pg_indexes is not exposed):", error.message); - } else { - console.log(data); - } -} - -main().catch(console.error); diff --git a/scratch/check-queued-docs.ts b/scratch/check-queued-docs.ts deleted file mode 100644 index a0b962b9a..000000000 --- a/scratch/check-queued-docs.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { loadEnvConfig } from "@next/env"; -loadEnvConfig(process.cwd()); - -async function main() { - const { createAdminClient } = await import("../src/lib/supabase/admin"); - const supabase = createAdminClient(); - - const { data: jobs, error } = await supabase - .from("ingestion_jobs") - .select("id, status, error_message, attempt_count, locked_at, document_id, documents(title)") - .in("status", ["pending", "processing"]); - - if (error) { - console.error(error); - return; - } - - console.log("=== Active Ingestion Jobs ==="); - console.log(JSON.stringify(jobs, null, 2)); -} - -main().catch(console.error); diff --git a/scratch/count-files.ts b/scratch/count-files.ts deleted file mode 100644 index a8a73dc94..000000000 --- a/scratch/count-files.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { readdir, stat } from "node:fs/promises"; -import path from "node:path"; - -const paths = [ - 'C:\\Users\\joshs\\OneDrive\\Medicine\\Guidelines\\NMHS', - 'C:\\Users\\joshs\\OneDrive\\Medicine\\Guidelines\\PHC', - 'C:\\Users\\joshs\\OneDrive\\Medicine\\Guidelines\\RKPG', - 'C:\\Users\\joshs\\OneDrive\\Medicine\\Guidelines\\SMHS' -]; - -const extensions = [".pdf", ".docx", ".xlsx", ".txt"]; - -async function countFiles(dir: string): Promise<{ count: number; bytes: number }> { - let count = 0; - let bytes = 0; - - async function visit(current: string) { - let entries; - try { - entries = await readdir(current, { withFileTypes: true }); - } catch { - return; - } - for (const entry of entries) { - const fullPath = path.join(current, entry.name); - if (entry.isDirectory()) { - await visit(fullPath); - } else if (entry.isFile()) { - const ext = path.extname(entry.name).toLowerCase(); - if (extensions.includes(ext)) { - count++; - try { - const s = await stat(fullPath); - bytes += s.size; - } catch {} - } - } - } - } - - await visit(dir); - return { count, bytes }; -} - -async function main() { - let grandTotalCount = 0; - let grandTotalBytes = 0; - for (const dir of paths) { - const { count, bytes } = await countFiles(dir); - const mb = (bytes / (1024 * 1024)).toFixed(2); - console.log(`${dir}: ${count} files (${mb} MB)`); - grandTotalCount += count; - grandTotalBytes += bytes; - } - const grandTotalMB = (grandTotalBytes / (1024 * 1024)).toFixed(2); - console.log(`Grand Total: ${grandTotalCount} files (${grandTotalMB} MB)`); -} - -main().catch(console.error); diff --git a/scratch/count-status.ts b/scratch/count-status.ts deleted file mode 100644 index e4265b86c..000000000 --- a/scratch/count-status.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { loadEnvConfig } from "@next/env"; -loadEnvConfig(process.cwd()); - -async function main() { - const { createAdminClient } = await import("../src/lib/supabase/admin"); - const supabase = createAdminClient(); - - // Total in documents table - const { data: docCounts, error: docError } = await supabase - .from("documents") - .select("status, metadata"); - - if (docError) { - console.error("Error fetching documents:", docError); - return; - } - - const statusCounts: Record = {}; - let enrichmentPending = 0; - - for (const doc of docCounts || []) { - statusCounts[doc.status] = (statusCounts[doc.status] || 0) + 1; - const meta = doc.metadata && typeof doc.metadata === "object" ? (doc.metadata as any) : {}; - if (meta.enrichment_status === "pending") { - enrichmentPending++; - } - } - - console.log("=== Document Table Status ==="); - console.log("Total rows in documents table:", docCounts?.length); - console.log("Breakdown by status:", statusCounts); - console.log("Documents with metadata.enrichment_status === 'pending':", enrichmentPending); - - // Total in ingestion_jobs - const { data: jobCounts, error: jobError } = await supabase - .from("ingestion_jobs") - .select("status"); - - if (jobError) { - console.error("Error fetching jobs:", jobError); - return; - } - - const jobStatusCounts: Record = {}; - for (const job of jobCounts || []) { - jobStatusCounts[job.status] = (jobStatusCounts[job.status] || 0) + 1; - } - - console.log("=== Ingestion Jobs Status ==="); - console.log("Total jobs:", jobCounts?.length); - console.log("Breakdown by status:", jobStatusCounts); -} - -main().catch(console.error); diff --git a/scratch/create-hnsw-indexes.sql b/scratch/create-hnsw-indexes.sql deleted file mode 100644 index 0c478aa82..000000000 --- a/scratch/create-hnsw-indexes.sql +++ /dev/null @@ -1,21 +0,0 @@ --- SQL script to recreate the missing HNSW vector indexes. --- Run this script in the Supabase Dashboard SQL Editor for your project: --- https://supabase.com/dashboard/project/sjrfecxgysukkwxsowpy/sql/new - -SET statement_timeout = 0; -- disable statement timeout for index creation - --- 1. Create HNSW index on document_chunks (69,000+ rows) --- This is a large build and must be run without session timeout. -CREATE INDEX IF NOT EXISTS document_chunks_embedding_hnsw_idx - ON public.document_chunks USING hnsw (embedding vector_cosine_ops) - WITH (m = 24, ef_construction = 128); - --- 2. Create HNSW index on document_memory_cards (10,000+ rows) -CREATE INDEX IF NOT EXISTS document_memory_cards_embedding_hnsw_idx - ON public.document_memory_cards USING hnsw (embedding vector_cosine_ops) - WITH (m = 24, ef_construction = 128); - --- 3. Create HNSW index on document_embedding_fields (3,000+ rows) -CREATE INDEX IF NOT EXISTS document_embedding_fields_embedding_hnsw_idx - ON public.document_embedding_fields USING hnsw (embedding vector_cosine_ops) - WITH (m = 24, ef_construction = 128); diff --git a/scratch/fix-indexes.ts b/scratch/fix-indexes.ts deleted file mode 100644 index 348f296cb..000000000 --- a/scratch/fix-indexes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { loadEnvConfig } from "@next/env"; -loadEnvConfig(process.cwd()); - -async function main() { - const { createAdminClient } = await import("../src/lib/supabase/admin"); - const supabase = createAdminClient(); - - console.log("Applying missing schema indexes..."); - - // 1. Create document_embedding_fields_owner_idx - const { error: error1 } = await supabase.rpc("search_schema_health"); // Just a test, but we can execute raw SQL if we have a way. - // Wait, does Supabase JS client support raw SQL execution? No, unless we use RPC or run it via schema migration. - // Ah, wait! Is there a function we can use, or does the migration schema script apply them? - // Wait, let's see how the test suite applies migrations or runs SQL. -} diff --git a/scratch/queue-queued-docs.ts b/scratch/queue-queued-docs.ts deleted file mode 100644 index 3b389ca8c..000000000 --- a/scratch/queue-queued-docs.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { loadEnvConfig } from "@next/env"; -loadEnvConfig(process.cwd()); - -async function main() { - const { createAdminClient } = await import("@/lib/supabase/admin"); - const { env } = await import("@/lib/env"); - const supabase = createAdminClient(); - - const { data: docs, error } = await supabase - .from("documents") - .select("id, metadata") - .eq("status", "queued"); - - if (error) { - console.error("Error fetching queued documents:", error); - return; - } - - if (!docs || docs.length === 0) { - console.log("No queued documents found to create jobs for."); - return; - } - - for (const doc of docs) { - const metadata = doc.metadata as Record; - const batchId = metadata?.import_batch_id || null; - - console.log(`Creating ingestion job for document ${doc.id} (batch: ${batchId})`); - - const { data: existingJobs } = await supabase - .from("ingestion_jobs") - .select("id") - .eq("document_id", doc.id); - - if (existingJobs && existingJobs.length > 0) { - console.log(`Job already exists for document ${doc.id}, skipping.`); - continue; - } - - const { error: insertError } = await supabase.from("ingestion_jobs").insert({ - document_id: doc.id, - batch_id: batchId, - status: "pending", - stage: "queued", - progress: 0, - max_attempts: env.WORKER_MAX_ATTEMPTS || 3, - }); - - if (insertError) { - console.error(`Error inserting job for document ${doc.id}:`, insertError); - } else { - console.log(`Successfully created ingestion job for document ${doc.id}`); - } - } -} - -main().catch(console.error); diff --git a/scratch/run-migrations-safe.sql b/scratch/run-migrations-safe.sql deleted file mode 100644 index 1096201c4..000000000 --- a/scratch/run-migrations-safe.sql +++ /dev/null @@ -1,603 +0,0 @@ --- Set no timeout for the session -SET statement_timeout = 0; -SET search_path = public, extensions; - --- ============================================================ --- 1. HNSW INDEX TUNING --- Rebuild with m=24 (graph connectivity) and ef_construction=128 --- (build-time recall). Default was m=16, ef_construction=64. --- ============================================================ - -drop index if exists document_chunks_embedding_hnsw_idx; -create index document_chunks_embedding_hnsw_idx - on public.document_chunks using hnsw (embedding vector_cosine_ops) - with (m = 24, ef_construction = 128); - -drop index if exists document_memory_cards_embedding_hnsw_idx; -create index document_memory_cards_embedding_hnsw_idx - on public.document_memory_cards using hnsw (embedding vector_cosine_ops) - with (m = 24, ef_construction = 128); - -drop index if exists document_index_units_embedding_hnsw_idx; -create index document_index_units_embedding_hnsw_idx - on public.document_index_units using hnsw (embedding vector_cosine_ops) - with (m = 24, ef_construction = 128); - -drop index if exists document_embedding_fields_embedding_hnsw_idx; -create index document_embedding_fields_embedding_hnsw_idx - on public.document_embedding_fields using hnsw (embedding vector_cosine_ops) - with (m = 24, ef_construction = 128); - --- ============================================================ --- 2. RAG RETRIEVAL LOGS TABLE --- Per-query diagnostics: candidate scores, latency breakdown, --- miss detection. --- ============================================================ - -create table if not exists public.rag_retrieval_logs ( - id uuid primary key default gen_random_uuid(), - owner_id uuid references auth.users(id) on delete set null, - query text not null, - normalized_query text, - query_class text, - retrieval_strategy text, - - -- Candidate scores - candidate_count integer not null default 0, - top_similarity double precision, - top_text_rank double precision, - top_hybrid_score double precision, - top_rrf_score double precision, - mean_hybrid_score double precision, - - -- Selected citations - selected_chunk_ids uuid[] not null default '{}', - selected_document_ids uuid[] not null default '{}', - selected_count integer not null default 0, - - -- Latency breakdown - embedding_latency_ms integer, - rpc_latency_ms integer, - rerank_latency_ms integer, - total_latency_ms integer, - - -- Source counts - vector_candidate_count integer, - text_candidate_count integer, - memory_card_count integer, - index_unit_count integer, - embedding_field_count integer, - - -- Miss detection - is_miss boolean not null default false, - miss_reason text, - - -- Metadata - embedding_cache_hit boolean, - metadata jsonb not null default '{}'::jsonb, - created_at timestamptz not null default now() -); - -create index if not exists rag_retrieval_logs_owner_created_idx - on public.rag_retrieval_logs(owner_id, created_at desc); -create index if not exists rag_retrieval_logs_miss_idx - on public.rag_retrieval_logs(is_miss, created_at desc) - where is_miss = true; -create index if not exists rag_retrieval_logs_strategy_idx - on public.rag_retrieval_logs(retrieval_strategy, created_at desc); - -alter table public.rag_retrieval_logs enable row level security; -grant select, insert, update, delete on table public.rag_retrieval_logs to service_role; -grant select on table public.rag_retrieval_logs to authenticated; - -create policy "rag retrieval logs owner read" on public.rag_retrieval_logs - for select to authenticated using (owner_id = (select auth.uid())); - --- ============================================================ --- 3. CONTENT_HASH DEDUP ON DOCUMENT_EMBEDDING_FIELDS --- Replace the problematic unique(document_id, source_chunk_id, --- field_type, content) constraint that indexes full text. --- ============================================================ - -alter table public.document_embedding_fields - add column if not exists content_hash text; - --- Populate for existing rows -update public.document_embedding_fields - set content_hash = md5(content) - where content_hash is null; - --- Drop the old constraint. -alter table public.document_embedding_fields - drop constraint if exists document_embedding_fields_document_id_source_chunk_id_field_key; -alter table public.document_embedding_fields - drop constraint if exists document_embedding_fields_document_id_source_chunk_id_fiel_key; - --- Create hash-based unique index -create unique index if not exists document_embedding_fields_dedup_idx - on public.document_embedding_fields(document_id, source_chunk_id, field_type, content_hash); - --- ============================================================ --- 4. DROP DUPLICATE/REDUNDANT INDEXES --- These single-column indexes are covered by composite indexes --- with the same leading column. --- ============================================================ - -drop index if exists document_chunks_document_id_idx; -drop index if exists document_sections_document_id_idx; -drop index if exists document_memory_cards_document_id_idx; -drop index if exists document_images_document_id_idx; -drop index if exists document_labels_document_id_idx; -drop index if exists document_embedding_fields_document_id_idx; -drop index if exists document_table_facts_document_id_idx; -drop index if exists document_index_quality_document_id_idx; - --- ============================================================ --- 5. OTHER EXPECTED SCHEMA INDEXES FROM OTHER MIGRATIONS --- ============================================================ -create index if not exists ingestion_jobs_status_next_run_idx - on public.ingestion_jobs(status, next_run_at asc) - where status in ('pending', 'processing'); - -create index if not exists ingestion_jobs_document_status_idx - on public.ingestion_jobs(document_id, status); - -create index if not exists import_batches_status_created_idx - on public.import_batches(status, created_at desc); - -create index if not exists storage_cleanup_jobs_status_created_idx - on public.storage_cleanup_jobs(status, created_at desc); - --- ============================================================ --- 6. ENHANCED HYBRID SCORING --- ============================================================ - -create or replace function public.match_document_chunks_hybrid( - query_embedding extensions.vector(1536), - query_text text, - match_count integer default 12, - min_similarity double precision default 0.12, - document_filters uuid[] default null, - owner_filter uuid default null -) -returns table ( - id uuid, - document_id uuid, - title text, - file_name text, - page_number integer, - chunk_index integer, - section_heading text, - content text, - retrieval_synopsis text, - image_ids uuid[], - source_metadata jsonb, - similarity double precision, - text_rank double precision, - hybrid_score double precision, - rrf_score double precision, - images jsonb -) -language sql -stable -set search_path = public, extensions, pg_temp -as $$ - with query as ( - select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq - ), - vector_ranked as ( - select - c.id, - c.document_id, - c.page_number, - c.chunk_index, - c.section_heading, - c.content, - c.retrieval_synopsis, - c.image_ids, - 1 - (c.embedding <=> query_embedding) as similarity, - ( - ts_rank_cd(c.search_tsv, query.tsq) + - (ts_rank_cd(d.title_search_tsv, query.tsq) * 3.0) - )::double precision as text_rank, - row_number() over (order by c.embedding <=> query_embedding) as vector_rank, - null::bigint as text_match_rank, - coalesce((d.metadata->'rag_indexing_version') is not null, false) as has_deep_index, - d.updated_at as doc_updated_at, - coalesce( - (select q.quality_score from public.document_index_quality q where q.document_id = c.document_id), - 0.7 - ) as quality_score - from public.document_chunks c - join public.documents d on d.id = c.document_id - cross join query - where (document_filters is null or c.document_id = any(document_filters)) - and (owner_filter is null or d.owner_id = owner_filter) - and d.status = 'indexed' - and 1 - (c.embedding <=> query_embedding) >= min_similarity - order by c.embedding <=> query_embedding - limit greatest(match_count * 6, 48) - ), - text_ranked as ( - select - c.id, - c.document_id, - c.page_number, - c.chunk_index, - c.section_heading, - c.content, - c.retrieval_synopsis, - c.image_ids, - 1 - (c.embedding <=> query_embedding) as similarity, - ( - ts_rank_cd(c.search_tsv, query.tsq) + - (ts_rank_cd(d.title_search_tsv, query.tsq) * 3.0) - )::double precision as text_rank, - null::bigint as vector_rank, - row_number() over ( - order by - ( - ts_rank_cd(c.search_tsv, query.tsq) + - (ts_rank_cd(d.title_search_tsv, query.tsq) * 3.0) - ) desc, - c.embedding <=> query_embedding - ) as text_match_rank, - coalesce((d.metadata->'rag_indexing_version') is not null, false) as has_deep_index, - d.updated_at as doc_updated_at, - coalesce( - (select q.quality_score from public.document_index_quality q where q.document_id = c.document_id), - 0.7 - ) as quality_score - from public.document_chunks c - join public.documents d on d.id = c.document_id - cross join query - where (document_filters is null or c.document_id = any(document_filters)) - and (owner_filter is null or d.owner_id = owner_filter) - and d.status = 'indexed' - and (c.search_tsv @@ query.tsq or d.title_search_tsv @@ query.tsq) - order by ( - ts_rank_cd(c.search_tsv, query.tsq) + - (ts_rank_cd(d.title_search_tsv, query.tsq) * 3.0) - ) desc - limit greatest(match_count * 6, 48) - ), - combined as ( - select * from vector_ranked - union all - select * from text_ranked - ), - scored as ( - select - id, - document_id, - page_number, - chunk_index, - section_heading, - content, - retrieval_synopsis, - image_ids, - max(similarity)::double precision as similarity, - max(text_rank)::double precision as text_rank, - min(vector_rank) as vector_rank, - min(text_match_rank) as text_match_rank, - max(quality_score)::double precision as quality_score, - bool_or(has_deep_index) as has_deep_index, - max(doc_updated_at) as doc_updated_at - from combined - group by id, document_id, page_number, chunk_index, section_heading, content, retrieval_synopsis, image_ids - ), - scored_metrics as ( - select - scored.*, - ( - (scored.similarity * 0.62) - + (least(scored.text_rank, 1) * 0.22) - + (scored.quality_score * 0.10) - + (case when scored.doc_updated_at > now() - interval '90 days' then 0.06 else 0 end) - )::double precision as hybrid_score, - ( - coalesce(1.0 / (60 + scored.vector_rank), 0) + - coalesce(1.0 / (60 + scored.text_match_rank), 0) - )::double precision as rrf_score - from scored - ), - hybrid_candidates as ( - select id - from scored_metrics - order by hybrid_score desc, similarity desc, text_rank desc - limit match_count - ), - vector_candidates as ( - select id - from scored_metrics - order by similarity desc, hybrid_score desc - limit match_count - ), - text_candidates as ( - select id - from scored_metrics - order by text_rank desc, hybrid_score desc - limit match_count - ), - rrf_candidates as ( - select id - from scored_metrics - order by rrf_score desc, hybrid_score desc - limit match_count - ), - candidate_ids as ( - select id from hybrid_candidates - union - select id from vector_candidates - union - select id from text_candidates - union - select id from rrf_candidates - ) - select - c.id, - c.document_id, - d.title, - d.file_name, - c.page_number, - c.chunk_index, - c.section_heading, - c.content, - c.retrieval_synopsis, - c.image_ids, - d.metadata as source_metadata, - c.similarity, - c.text_rank, - c.hybrid_score, - c.rrf_score, - public.chunk_image_metadata(c.image_ids) as images - from scored_metrics c - join candidate_ids candidates on candidates.id = c.id - join public.documents d on d.id = c.document_id - order by c.hybrid_score desc, c.rrf_score desc, c.similarity desc, c.text_rank desc - limit match_count; -$$; - -create or replace function public.match_document_index_units_hybrid( - query_embedding extensions.vector(1536), - query_text text, - match_count integer default 24, - min_similarity double precision default 0.1, - document_filters uuid[] default null, - owner_filter uuid default null -) -returns table ( - id uuid, - document_id uuid, - source_chunk_id uuid, - source_image_id uuid, - unit_type text, - title text, - content text, - page_start integer, - page_end integer, - heading_path text[], - normalized_terms text[], - source_span jsonb, - quality_score real, - extraction_mode text, - similarity double precision, - text_rank double precision, - hybrid_score double precision, - metadata jsonb -) -language sql -stable -set search_path = public, extensions, pg_temp -as $$ - with query as ( - select websearch_to_tsquery('english', coalesce(query_text, '')) as tsq, - regexp_split_to_array(lower(coalesce(query_text, '')), '\s+') as terms - ), - ranked as ( - select u.id, u.document_id, u.source_chunk_id, u.source_image_id, u.unit_type, u.title, u.content, u.page_start, - u.page_end, u.heading_path, u.normalized_terms, u.source_span, u.quality_score, u.extraction_mode, - (1 - (u.embedding <=> query_embedding))::double precision as similarity, - (ts_rank_cd(u.search_tsv, query.tsq) - + case when u.normalized_terms && query.terms then 0.25 else 0 end - + case when u.unit_type in ('askable_question', 'table_fact', 'clinical_fact') then 0.06 - when u.unit_type = 'section_summary' then 0.03 - else 0 end - )::double precision as text_rank, - u.metadata - from public.document_index_units u - join public.documents d on d.id = u.document_id - cross join query - where d.status = 'indexed' - and (document_filters is null or u.document_id = any(document_filters)) - and (owner_filter is null or d.owner_id = owner_filter) - and u.source_chunk_id is not null - and (u.search_tsv @@ query.tsq or u.normalized_terms && query.terms) - order by text_rank desc, similarity desc - limit greatest(match_count * 3, 48) - ) - select id, document_id, source_chunk_id, source_image_id, unit_type, title, content, page_start, page_end, heading_path, - normalized_terms, source_span, quality_score, extraction_mode, similarity, text_rank, - ( - (similarity * 0.52) - + (least(text_rank, 1) * 0.28) - + (quality_score * 0.12) - + (case when extraction_mode in ('model_heavy', 'hybrid') then 0.04 else 0 end) - + (case when unit_type = 'askable_question' then 0.04 else 0 end) - )::double precision as hybrid_score, - metadata - from ranked - order by hybrid_score desc, similarity desc, text_rank desc - limit match_count; -$$; - -create or replace function public.analyze_rag_tables() -returns void -language plpgsql -set search_path = public, extensions, pg_temp -as $$ -begin - analyze public.document_chunks; - analyze public.document_memory_cards; - analyze public.document_index_units; - analyze public.document_embedding_fields; - analyze public.document_table_facts; - analyze public.documents; -end; -$$; - -revoke execute on function public.analyze_rag_tables() from public, anon, authenticated; -grant execute on function public.analyze_rag_tables() to service_role; - -create or replace function public.reset_document_index(p_document_id uuid) -returns void -language plpgsql -set search_path = public, extensions, pg_temp -as $$ -begin - perform set_config('statement_timeout', '180000', true); - delete from public.document_index_units where document_id = p_document_id; - delete from public.document_memory_cards where document_id = p_document_id; - delete from public.document_sections where document_id = p_document_id; - delete from public.document_table_facts where document_id = p_document_id; - delete from public.document_embedding_fields where document_id = p_document_id; - delete from public.document_index_quality where document_id = p_document_id; - delete from public.document_chunks where document_id = p_document_id; - delete from public.document_images where document_id = p_document_id; - delete from public.document_pages where document_id = p_document_id; -end; -$$; - --- Update health check with correct indexes -create or replace function public.search_schema_health() -returns jsonb -language plpgsql -stable -set search_path = public, extensions, pg_catalog, pg_temp -as $$ -declare - missing text[] := array[]::text[]; - vector_type_oid oid; - vector_schema text; - index_name text; -begin - select t.oid, n.nspname - into vector_type_oid, vector_schema - from pg_type t - join pg_namespace n on n.oid = t.typnamespace - where t.typname = 'vector' - and n.nspname = 'extensions' - limit 1; - - if vector_type_oid is null then - missing := array_append(missing, 'extensions.vector_type'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_chunks' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_chunks.extensions_vector_signature'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_chunks_hybrid' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_chunks_hybrid.extensions_vector_signature'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_memory_cards_hybrid' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_memory_cards_hybrid.extensions_vector_signature'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_index_units_hybrid' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_index_units_hybrid.extensions_vector_signature'); - end if; - - if to_regclass('public.document_index_units') is null then - missing := array_append(missing, 'document_index_units.table'); - end if; - if to_regclass('public.rag_retrieval_logs') is null then - missing := array_append(missing, 'rag_retrieval_logs.table'); - end if; - if not exists (select 1 from pg_class where relname = 'documents_title_trgm_idx') then - missing := array_append(missing, 'documents_title_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_chunks_content_trgm_idx') then - missing := array_append(missing, 'document_chunks_content_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_labels_label_trgm_idx') then - missing := array_append(missing, 'document_labels_label_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_summaries_summary_trgm_idx') then - missing := array_append(missing, 'document_summaries_summary_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_index_units_embedding_hnsw_idx') then - missing := array_append(missing, 'document_index_units_embedding_hnsw_idx'); - end if; - if not exists (select 1 from pg_class where relname in ('document_embedding_fields_owner_idx', 'document_embedding_fields_owner_id_idx')) then - missing := array_append(missing, 'document_embedding_fields_owner_idx'); - end if; - if not exists (select 1 from pg_class where relname in ('document_table_facts_owner_idx', 'document_table_facts_owner_id_idx')) then - missing := array_append(missing, 'document_table_facts_owner_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_table_facts_source_image_idx') then - missing := array_append(missing, 'document_table_facts_source_image_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_embedding_fields_dedup_idx') then - missing := array_append(missing, 'document_embedding_fields_dedup_idx'); - end if; - foreach index_name in array array[ - 'document_pages_document_idx', - 'document_images_document_idx', - 'document_sections_document_idx', - 'document_memory_cards_document_idx', - 'document_chunks_document_idx', - 'document_table_facts_document_idx', - 'document_embedding_fields_document_idx', - 'document_index_units_document_idx', - 'ingestion_jobs_status_next_run_idx', - 'ingestion_jobs_document_status_idx', - 'documents_owner_status_idx', - 'import_batches_status_created_idx', - 'storage_cleanup_jobs_status_created_idx' - ] loop - if not exists (select 1 from pg_class where relname = index_name) then - missing := array_append(missing, index_name); - end if; - end loop; - - return jsonb_build_object( - 'ok', cardinality(missing) = 0, - 'missing', missing, - 'vector_extension_schema', vector_schema, - 'checked_at', now() - ); -end; -$$; - -revoke execute on function public.search_schema_health() from public, anon, authenticated; -grant execute on function public.search_schema_health() to service_role; diff --git a/scratch/show-samples.ts b/scratch/show-samples.ts deleted file mode 100644 index 830a9bff5..000000000 --- a/scratch/show-samples.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { loadEnvConfig } from "@next/env"; -loadEnvConfig(process.cwd()); - -async function main() { - const { createAdminClient } = await import("../src/lib/supabase/admin"); - const supabase = createAdminClient(); - - const { data: sampleDocs, error } = await supabase - .from("documents") - .select("id, title, status, metadata") - .limit(10); - - if (error) { - console.error(error); - return; - } - - console.log("=== Sample Documents ==="); - console.log(JSON.stringify(sampleDocs, null, 2)); -} - -main().catch(console.error); diff --git a/scratch/update-health-check.sql b/scratch/update-health-check.sql deleted file mode 100644 index 6da2830c0..000000000 --- a/scratch/update-health-check.sql +++ /dev/null @@ -1,132 +0,0 @@ -create or replace function public.search_schema_health() -returns jsonb -language plpgsql -stable -set search_path = public, extensions, pg_catalog, pg_temp -as $$ -declare - missing text[] := array[]::text[]; - vector_type_oid oid; - vector_schema text; - index_name text; -begin - select t.oid, n.nspname - into vector_type_oid, vector_schema - from pg_type t - join pg_namespace n on n.oid = t.typnamespace - where t.typname = 'vector' - and n.nspname = 'extensions' - limit 1; - - if vector_type_oid is null then - missing := array_append(missing, 'extensions.vector_type'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_chunks' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_chunks.extensions_vector_signature'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_chunks_hybrid' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_chunks_hybrid.extensions_vector_signature'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_memory_cards_hybrid' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_memory_cards_hybrid.extensions_vector_signature'); - end if; - - if vector_type_oid is not null and not exists ( - select 1 - from pg_proc p - join pg_namespace n on n.oid = p.pronamespace - where n.nspname = 'public' - and p.proname = 'match_document_index_units_hybrid' - and p.proargtypes[0] = vector_type_oid - ) then - missing := array_append(missing, 'match_document_index_units_hybrid.extensions_vector_signature'); - end if; - - if to_regclass('public.document_index_units') is null then - missing := array_append(missing, 'document_index_units.table'); - end if; - if to_regclass('public.rag_retrieval_logs') is null then - missing := array_append(missing, 'rag_retrieval_logs.table'); - end if; - if not exists (select 1 from pg_class where relname = 'documents_title_trgm_idx') then - missing := array_append(missing, 'documents_title_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_chunks_content_trgm_idx') then - missing := array_append(missing, 'document_chunks_content_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_labels_label_trgm_idx') then - missing := array_append(missing, 'document_labels_label_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_summaries_summary_trgm_idx') then - missing := array_append(missing, 'document_summaries_summary_trgm_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_index_units_embedding_hnsw_idx') then - missing := array_append(missing, 'document_index_units_embedding_hnsw_idx'); - end if; - if not exists (select 1 from pg_class where relname in ('document_embedding_fields_owner_idx', 'document_embedding_fields_owner_id_idx')) then - missing := array_append(missing, 'document_embedding_fields_owner_idx'); - end if; - if not exists (select 1 from pg_class where relname in ('document_table_facts_owner_idx', 'document_table_facts_owner_id_idx')) then - missing := array_append(missing, 'document_table_facts_owner_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_table_facts_source_image_idx') then - missing := array_append(missing, 'document_table_facts_source_image_idx'); - end if; - if not exists (select 1 from pg_class where relname = 'document_embedding_fields_dedup_idx') then - missing := array_append(missing, 'document_embedding_fields_dedup_idx'); - end if; - foreach index_name in array array[ - 'document_pages_document_idx', - 'document_images_document_idx', - 'document_sections_document_idx', - 'document_memory_cards_document_idx', - 'document_chunks_document_idx', - 'document_table_facts_document_idx', - 'document_embedding_fields_document_idx', - 'document_index_units_document_idx', - 'ingestion_jobs_status_next_run_idx', - 'ingestion_jobs_document_status_idx', - 'documents_owner_status_idx', - 'import_batches_status_created_idx', - 'storage_cleanup_jobs_status_created_idx' - ] loop - if not exists (select 1 from pg_class where relname = index_name) then - missing := array_append(missing, index_name); - end if; - end loop; - - return jsonb_build_object( - 'ok', cardinality(missing) = 0, - 'missing', missing, - 'vector_extension_schema', vector_schema, - 'checked_at', now() - ); -end; -$$; - -revoke execute on function public.search_schema_health() from public, anon, authenticated; -grant execute on function public.search_schema_health() to service_role;