factcircuit
FactCircuit는 불변 출처와 단계별 검증, 제한된 검색, 재현 가능한 평가를 제공하는 감사 가능한 주장·증거 검증 루프입니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기SaaS de Licitação para o Fitness
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
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.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.