factcircuit
FactCircuit: an auditable claim-and-evidence verification loop with immutable provenance, staged checks, bounded retrieval, and reproducible evaluation.
Explore PDMs e características de itens CATMAT para equipamentos fitness, com fonte e critérios visíveis.
Translation pending
DataPLpgSQL · TypeScriptGitHub @n4rch6pjt9-rgbOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
SaaS de Licitação para o Fitness
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Coletores LicitaGym (PNCP + SEST SENAT) → Supabase + RAG Versão no git: v15. A partir desta versão o coletor não é mais distribuído por ZIP. Código canônico: este repositório (coletor/, tests/, supabase/migrations/, monitor.sh). O detalhe da compra no PNCP passou para /api/consulta/v1/.... O caminho antigo /api/pncp/v1/.../compras/{ano}/{seq} responde 301 em JSON (sem Location útil). Itens, resultados e arquivos permanecem em /api/pncp/v1. Ver PNCP.detalhe_compra / PNCP.compra em coletor/pncp.py.Read the full README ↗
Versão no git: v15. A partir desta versão o coletor não é mais distribuído por ZIP.
Código canônico: este repositório (coletor/, tests/, supabase/migrations/, monitor.sh).
# primeira vez (Cloud Shell ou máquina local)
git clone https://github.com/n4rch6pjt9-rgb/LicitaGym.git
cd LicitaGym
git checkout main # após merge do PR v15; ou a branch feat/coletor-pncp-v15
pip install --user -r requirements.txt
# atualizar (substituí o unzip do zip)
cd ~/LicitaGym # ou o path do clone
git fetch origin
git checkout main
git pull --ff-only origin main
pip install --user -r requirements.txt # se requirements mudou
Monitor do job em andamento:
bash monitor.sh # padrão: processo_edital.log
bash monitor.sh pncp.log # outro log
Testes offline:
pip install --user -r requirements.txt pytest
python -m pytest tests/test_pncp.py tests/test_processo_edital.py -q
O detalhe da compra no PNCP passou para /api/consulta/v1/.... O caminho antigo
/api/pncp/v1/.../compras/{ano}/{seq} responde 301 em JSON (sem Location útil).
Itens, resultados e arquivos permanecem em /api/pncp/v1.
Ver PNCP.detalhe_compra / PNCP.compra em coletor/pncp.py.
O número do edital ("Pregão Eletrônico nº 001/2026") se repete entre órgãos e não identifica nada.
codigo_externo = número de controle PNCP (CNPJ-1-sequencial/ano).python3 -m coletor.buscar_processo "00007.20260204/0002-28" --cnpj 07.540.925/0001-74
python3 -m coletor.pncp --corrigir-processos # corrige compras gravadas até a v12
python3 -m coletor.processo_edital --dry-run # completa processos curtos ("4") lendo o edital
Busca nacional no PNCP por frase exata (por padrão os 160 termos de TERMOS_ESCOPO_COMPLETO; --termos-padrao = 12 termos), classifica cada compra
pelo objeto e pelos itens (coletor/escopo.py) e grava compra, itens, vencedores e arquivos.
Três modos. O modo só escolhe o filtro status da busca no PNCP; não define sozinho a prioridade gravada.
| Modo | Filtro PNCP | Prioridade |
|---|---|---|
leads (padrão) | recebendo_proposta | recebendo proposta |
monitorar | em_julgamento | em julgamento, suspensa, adjudicação ou recurso |
historico | encerradas | encerrada ou homologada |
Prioridade (owner, 02/10/2026): leads = recebendo proposta; monitorar = em julgamento, suspensa, adjudicação ou recurso; historico = encerrada ou homologada. Status desconhecido nunca vira lead. Falha de API ou documento ausente não pode gravar encerradas/historico nem promover a leads. Compra homologada não é lead.
Preço por item CATMAT, marca e mapa de fornecedor (revenda ou fabricante) saem só de certames homologados, ficam só no BI e são rastreáveis pelo certame. Revogada, anulada, deserta ou fracassada não alimentam esse mapa.
# schema: supabase/migrations/20260924100000_pncp_itens_resultados.sql (via supabase db push)
python3 -m coletor.pncp --dry-run --termos "borracha granulada" --tam 20 # teste rápido
python3 -m coletor.pncp # leads: recebendo proposta
python3 -m coletor.pncp --modo monitorar # em julgamento
python3 -m coletor.pncp --modo historico --paginas 10 --baixar-arquivos # encerradas
python3 -m coletor.indexador # arquivos baixados -> RAG
Reclassificar as linhas PNCP já gravadas (as antigas leads homologadas viram historico) — dry-run por padrão:
export SUPABASE_URL=... SUPABASE_SERVICE_ROLE_KEY=... # do ambiente, nunca no código
python3 -m coletor.backfill_prioridade_pncp # DRY-RUN: contagem por transição + amostra
python3 -m coletor.backfill_prioridade_pncp --consultar-pncp --limit 50 # DRY-RUN relendo o detalhe no PNCP (GET)
python3 -m coletor.reclassificar_escopo_pncp # DRY-RUN (prioridade + fase)
python3 -m coletor.reclassificar_escopo_pncp --apply # grava prioridade, fase e escopo
backfill_prioridade_pncp --apply está desativado (sai com código 2): ele decide só a prioridade, sem documentos
nem fase, e sobrescreveria fases documentais como "Suspensa (documento)". O dry-run dele fica como diagnóstico legado.
oportunidades_borracha lista primeiro os leads (certames abertos, ainda sem vencedor) e depois o resto, do
homologado mais recente para o mais antigo, com dias_desde_homologacao. Para oferecer raspa ao vencedor,
use as linhas historico com homologação recente (dias_desde_homologacao), não prioridade = 'leads'.
Volume observado (24/09/2026, frase exata): grama sintética 3.263 (85 abertas), academia ao ar livre 1.563, equipamentos de academia 1.023, piso emborrachado 986, campo society 794, borracha granulada 122. A busca do PNCP também acha termos dentro dos itens — decoração natalina, arbitragem e grama natural são descartadas pelo classificador.
Raspa de borracha para o vencedor: a view oportunidades_borracha junta compra, item e vencedor
(ex.: Carapicuíba/SP, 50 t de borracha granulada G3 homologadas a R$ 2.779,46/t para HG Comércio).
Coleta licitações encerradas do portal de compras do SEST SENAT
(compras.sestsenat.org.br, plataforma Paradigma) e registra no Supabase:
dados do processo, esclarecimentos/impugnações do fórum, notas da comissão e
todos os anexos (edital, propostas, lances, negociação, habilitação,
recursos, contrarrazões e pareceres). Os arquivos vão para o Cloud Storage.
O portal não tem API pública, mas a própria página usa um webservice JSON
interno (/portal/WebService/Servicos.asmx). O coletor chama esses mesmos
endpoints — não precisa de navegador headless.
Definido a partir da tabela pca_itens: 3.331 itens de PCA de 491 planos, ~R$ 146 mi,
100% na classe CATMAT 7830 — Equipamento para Ginástica e Recreação. PDMs mais planejados:
condicionamento físico (276), rede esporte (241), haltere (206), tatame (100), apito (71),
aparelho de ginástica (62), brinquedo inflável (53), bicicleta ergométrica (50), mesa de tênis de mesa (40).
coletor/escopo.py concentra: classes CATMAT, PDMs prioritários, termos de busca e o
classificador classificar(objeto, classes_catmat) → catmat | forte | fraco | None.
Também entram:
interesse_borracha(objeto) marca as licitações onde o vencedor vai precisar de raspa/granulado.
Serviço de evento, uniforme e troféu ficam fora. Um termo de busca sozinho não torna a linha forte.
Polia e espaldar são forte. Pilates, "aparelho para condicionamento físico" genérico e colchonete são fraco.
Puxador é positivo só na categoria acessórios.
O coletor usa --escopo fitness por padrão (--escopo tudo desliga o filtro).
O schema vem de supabase/migrations/20260923100000_licitacoes_externas.sql (aplicado por supabase db push;
em produção já existe e está registrado via supabase migration repair). Cria:
| Tabela | Conteúdo |
|---|---|
licitacoes_externas | 1 linha por processo (+ esclarecimentos e notas em JSON) |
licitacao_documentos | 1 linha por arquivo, com seção, fornecedor, lotes e status |
licitacao_chunks | trechos + embedding 768d (preenchida na próxima etapa) |
match_licitacao_chunks() | busca semântica com filtro por seção/fonte |
pip install -r requirements.txt
python -m coletor.main --ids 1,10,30,41 --dry-run # só lista, não grava
export SUPABASE_URL=https://<projeto>.supabase.co
export SUPABASE_SERVICE_ROLE_KEY=... # nunca no frontend
python -m coletor.main --ids 1,10 # grava; arquivos em ./dados
PROJECT=<seu-projeto-gcp>; REGION=southamerica-east1; BUCKET=licitagym-docs
gcloud config set project $PROJECT
gcloud services enable run.googleapis.com artifactregistry.googleapis.com \
cloudbuild.googleapis.com secretmanager.googleapis.com storage.googleapis.com
gcloud storage buckets create gs://$BUCKET --location=$REGION
printf '%s' "$SUPABASE_SERVICE_ROLE_KEY" | gcloud secrets create supabase-service-key --data-file=-
gcloud run jobs deploy coletor-sestsenat --source . --region $REGION \
--set-env-vars SUPABASE_URL=$SUPABASE_URL,GCS_BUCKET=$BUCKET,DELAY_SEGUNDOS=1.5 \
--set-secrets SUPABASE_SERVICE_ROLE_KEY=supabase-service-key:latest \
--args="--de,1,--ate,120" --task-timeout=6h --max-retries=1
gcloud run jobs execute coletor-sestsenat --region $REGION
A conta de serviço padrão do Cloud Run precisa de roles/storage.objectAdmin
no bucket e roles/secretmanager.secretAccessor no secret.
Para rodar toda semana: Cloud Scheduler → gcloud run jobs execute.
A coleta é idempotente (upsert + só baixa o que está pendente/erro).
| Variável | Padrão | Uso |
|---|---|---|
SECOES_DOWNLOAD | processo,recurso,contrarrazoes,parecer | Seções cujos arquivos são baixados. Metadados de todas as seções são sempre gravados. |
DELAY_SEGUNDOS | 1.5 | Pausa entre requisições (seja gentil com o portal). |
MAX_MB | 80 | Arquivos maiores ficam como ignorado. |
GCS_BUCKET | — | Sem ela, salva em ./dados. |
Flags: --ids, --de/--ate, --modulo (59 = pregão eletrônico), --todos
(inclui processos em andamento), --dry-run.
Um pregão de registro de preços grande (8453-4/2025) tem 32 arquivos no processo
mas ~1.000 arquivos únicos de fornecedores (proposta 32, lance 265, negociação 415,
habilitação 264), muitos em .zip/.rar. Por isso o padrão baixa só as seções
de maior valor para o RAG. Ligue proposta/negociacao quando quiser preços.
DELAY_SEGUNDOS ≥ 1 e rode fora do horário comercial.gcloud services enable aiplatform.googleapis.com
pip install --user -r requirements.txt
python3 -m coletor.indexador --limite 3 # teste com 3 arquivos
python3 -m coletor.indexador # todo o resto
python3 -m coletor.buscar "Por que a Freedom Motors recorreu e qual foi a decisão?"
.bin/sem extensão):
%PDF- → PDF; PK → ZIP, ou DOCX se tiver word/document.xml, ou XLSX se tiver xl/.pypdf; PDF escaneado → OCR pelo Gemini; ZIP é aberto (até 2 níveis, nomes
internos em cp850/UTF-8 sem flag corrigidos); DOCX lido direto; XLSX vira texto simples
(uma linha por linha da planilha, células com " | ", sem datas formatadas, até 200 mil caracteres);
.rar, .7z, .pptx, .doc/.xls antigos e imagens ficam em extracao.arquivos_ignorados.
Arquivo com nome .pdf que não é PDF (ex.: página HTML de erro) é ignorado, não vai para o OCR.licitacao_documentos.extracao (tipo, resumo,
fornecedores, decisão, motivos, fundamentos legais, valores, pontos-chave) e um trecho-resumo.text-multilingual-embedding-002 (lotes de até 30 trechos, 1 chamada a cada 12,5 s), 768 dimensões, RETRIEVAL_DOCUMENT (consulta usa RETRIEVAL_QUERY).
Cada chunk grava o modelo em licitacao_chunks.embedding_model (padrão da coluna: o mesmo modelo).GEN_MODEL (padrão gemini-3.8-flash, desde 22/10/2026: o gemini-2.5-flash foi descontinuado), GEN_LOCATION (padrão global, endpoint do 3.8 Flash) e GEN_THINKING (padrão deduzido do modelo: low no Gemini 3+, off no 2.5-flash; aceita off, low, medium, high ou padrao) são configuráveis. O embedding usa GOOGLE_CLOUD_LOCATION (padrão us-central1), separado da geração. O embedding do RAG permanece text-multilingual-embedding-002 com 768 dimensões; trocar o modelo exige um plano de reindex aprovado. Não apontar EMBED_MODEL para outro modelo sem esse plano.Cota do Vertex (projeto novo): o modelo de embeddings começa com 5 requisições/min
por região. O indexador já respeita isso (EMBED_INTERVALO=12.5). Para acelerar, peça aumento em
IAM e administrador → Cotas → "online prediction requests per base model … textembedding-gecko"
(us-central1) e reduza EMBED_INTERVALO. PDFs escaneados passam por OCR em partes de
OCR_PAGINAS=8 páginas.
Importante para o app: a pergunta do usuário precisa ser convertida em vetor com o
mesmo modelo (text-multilingual-embedding-002, 768d, RETRIEVAL_QUERY) antes de chamar
match_licitacao_chunks. Veja coletor/buscar.py como referência.