vitacodex
Backend · NutriNest v5 · análise técnica

Score composto 40%. O motor está certo. O roteamento está quebrado.

O stack FAISS + BM25 + Azure embeddings é a arquitetura correta. O problema é que o intent router detecta o tipo de consulta mas não tem caminho de resposta dedicado — então rsID, gene e categoria retornam 0% de precisão mesmo existindo no índice.

Medicina preventiva
52%
v5 hybrid (+2.6pp vs lexicon)
Precisão por keyword
18%
RSID=0% — falha crítica
Aplicações clínicas
46%
Performance = 20%
Score composto
40%
Meta para produção: ≥ 75%

Recuperação semântica (queries livres)

o que funciona bem
Tolerância à lactose
80%
Metabolismo cafeína
80%
Sensibilidade insulina
65%
Tendência a peso
40%
Risco cardiovascular
0%
Metabolismo álcool
0%

Precisão por keyword

onde está a falha real
rsID lookup
0%
Cromossomo
0%
Categoria
0%
Gene
20%
Genótipo (ex: CC)
40%
Nível de confiança
50%
Root cause
O hybrid_search aplica exact_boost (+0.2 rsID, +0.1 gene) depois da fusão RRF — boosteando documentos errados que o BM25 já recuperou mal. O token rs4988235 não é recuperado pelo BM25 porque o texto do documento é narrativo, não estruturado para lookup exato.

Stack atual

Dados
nutrinest_snps_v3.jsonl33 SNPs · 132 docslexicon por SNPconfidence_weight
Ingest (v5 hybrid)
expand_snps_jsonl_to_docs()lexicon injetado no texto1 parent + N genotype docs
Índice
FAISS IndexFlatIPAzure text-embedding-3-largeBM25 (rank_bm25)snps_v5_lex.pkl
Busca
detect_intent() routerhybrid_search() RRFexact_boost pós-RRFconfidence gating

O que está faltando

Faltando: lookup index
rsID → doc indicesgene → rsID listcategory → rsID listchromosome → rsID list
Faltando: cluster layer
cluster_definitions.jsonSNP → cluster membershipcluster scorerbridge para UI
Faltando: modelo descritivo
estado de genótipo por usuárioaggregação de cluster scoreranker de prioridaderenderer de linguagem

Fixes prioritizados

por impacto e custo
1
Lookup index dedicado (rsID / gene / categoria / cromossomo)
Dict construído no ingest, early-exit no hybrid_search antes de FAISS/BM25. Resolve 0% rsID → ~100%. Uma tarde de trabalho.
# early-exit antes do FAISS/BM25 if intent in ("rsid", "gene", "chrom", "category"): hits = lookup_idx[intent].get(key, []) if hits: return [metas[i] for i in hits]
Crítico
2
Separar texto de embedding do texto BM25
Léxico saí do embed_text, vai apenas para bm25_text. Texto clínico limpo para semântica; sinônimos + aliases para busca léxica.
# Dois campos, não um embed_text = f"SNP {rsid} em {gene} — {trait}. {effect}." bm25_text = f"{embed_text} {' '.join(synonyms)} {' '.join(related)}"
Crítico
3
Category alias map — linguagem do usuário para categoria interna
"risco cardiovascular" → lipidios_triglicerides. "ansiedade" → neurociencia. Resolve Performance (20%) e Cardiovascular sem novo SNP.
Alto
4
Two-tier confidence return (primary / exploratory)
Penalidade atual (s -= 0.25) ainda deixa SNPs de baixa confiança aparecer. Primary (conf ≥ 0.6) e Exploratory (hipótese) separados — conecta ao UI de incerteza do Vitacodex.
Alto
5
IndexFlatIP → IndexHNSWFlat
Irrelevante agora (132 docs), necessário antes de escalar para 300+ SNPs. Troca de uma linha, custo zero, recall <1% de degradação.
Médio

Sequência de build

dependência e impacto
✓ Feito
FAISS + Azure embeddings
BM25 hybrid layer (v5)
Lexicon no JSONL
Intent router
confidence_weight field
33 SNPs · 6 áreas
🔥 Agora
snp_lookup.py
Split embed / BM25 text
Category alias map
Two-tier result return
Esperado: keyword 18% → 70%+
→ Próximo
cluster_definitions.json
SNP → cluster membership
Cluster scorer
Bridge para DNA Explorer UI
→ Depois
Lab-genetics fusion
Weekly priority ranker
LLM renderer
Expansão de SNPs