Pipeline pessoal de mineração de dados astronômicos que caça anãs marrons e estrelas frias próximas ao Sol ainda não catalogadas, procurando por movimento próprio anômalo no catálogo público CatWISE2020.
Ilustração artística de uma anã marrom (Luhman 16B), parte do terceiro sistema mais próximo do Sol. Crédito: ESO/I. Crossfield/N. Risinger (skysurvey.org), CC BY 4.0.
A tela de revisão: para cada candidato, o recorte infravermelho, o óptico e a animação do movimento por época, com a pergunta de cada imagem e o progresso do trabalho no topo.
Experimente o treino online: casos de resposta conhecida, direto no navegador, sem instalar nada. As suas respostas ficam só na sua sessão.
Rastro is a personal data-mining pipeline that hunts for uncatalogued brown dwarfs and cool stars near the Sun through high proper motion in the public CatWISE2020 catalogue (queried via IRSA TAP). Five automated phases share state through SQLite: a sanity check that recovers Luhman 16 and compares its measured proper motion, a tiled field download with server-side cuts, quality filtering (proper-motion significance, W2 SNR, contamination flags, pmcode), removal of known objects by cross-match with SIMBAD and 11 VizieR lists of ultracool dwarfs, and an LLM triage that only ranks, never decides. Two manual phases follow: a local Streamlit app for visual review (unWISE and Legacy Surveys cutouts, Gaia/Pan-STARRS counterparts, and a consistency test of the motion against AllWISE positions) and an HTML report for a second opinion. Recall against 949 known L/T/Y dwarfs from SIMBAD is about 76%. A 25 deg² scan (RA 148-153, Dec +18 to +23) left 237 candidates under review. The best one turned out to be already published as a photometric L5 candidate (Karpov et al. 2025), found independently; no discovery is claimed. Code and docs are in Portuguese; config.py holds every parameter. Run with python pipeline.py; offline tests with python tests/test_units.py.
A pergunta: existem objetos frios e escuros perto do Sol que ninguém descobriu ainda?
O que se procura: as anãs marrons, "estrelas fracassadas". Têm massa demais pra serem planetas e de menos pra acender como uma estrela. Quase não emitem luz visível, mas emitem calor (infravermelho), então um telescópio infravermelho consegue enxergá-las.
Como achar: as estrelas parecem paradas no céu, mas as que estão perto se mexem devagar, ano após ano. É como olhar a paisagem pela janela do carro: o poste ao lado passa rápido, a montanha ao longe parece parada. Esse deslocamento no céu se chama movimento próprio. Um objeto que se move muito pra um ponto tão apagado é um forte suspeito de estar perto de nós.
Quase tudo no fundo (cinza) fica no lugar. A WISE 0855-0714 (laranja), a anã marrom mais fria que se conhece, anda cerca de 80 segundos de arco em 10 anos. É esse tipo de movimento que o projeto procura.
Onde estão os dados: o telescópio espacial WISE fotografou o céu todo várias vezes ao longo de 8 anos. Astrônomos já transformaram essas fotos numa enorme tabela pública, o CatWISE2020, com 1,89 bilhão de linhas. Cada linha é um ponto de luz, com sua posição, seu brilho e o quanto ele se mexeu. O projeto não olha fotos nem usa telescópio: ele lê essa tabela e filtra.
O que o programa faz, passo a passo. Pense numa peneira com várias malhas, cada uma deixando passar menos gente:
- Confere as ferramentas. Procura dois objetos famosos que já se conhece. Se não os achar, algo está errado e o programa para.
- Baixa um pedaço do céu. Hoje, 25 graus quadrados (quadrados de 1 grau por 1 grau, cada um com cerca de 5 vezes a área da Lua cheia), só com pontos que se movem rápido.
- Joga fora medidas duvidosas. Defeitos do instrumento, pontos colados em vizinhos e movimentos pequenos demais pra distinguir de erro de medida.
- Joga fora o que já é conhecido. Compara com o SIMBAD, o grande cadastro de objetos astronômicos, e com listas publicadas de anãs frias que o SIMBAD ainda não absorveu.
- Pede a nota de uma IA. Uma IA lê os números de cada sobrevivente e dá uma nota de prioridade. Ela só ajuda a ordenar, nunca decide.
- Olhar humano. As imagens de cada suspeito são conferidas à mão, num app local (
streamlit run app/revisao.py) que mostra os recortes, os números e a nota da IA, e guarda a sua decisão. - Relatório pra um astrônomo. Os candidatos que você confirmou saem numa página só, com imagens e números, prontos pra pedir uma segunda opinião. A opinião em si ainda está por fazer.
O que esperar: quase tudo que passa pela peneira é falso alarme ou já era conhecido, e isso é normal até em projetos profissionais. No campo de teste, o programa reencontrou sozinho uma anã marrom que já estava catalogada, sinal de que os filtros funcionam. Na varredura de 25 graus quadrados sobraram 237 candidatos, e ainda não há lista curta e defensável: falta a confirmação visual. O melhor suspeito confirmado a olho já tinha sido publicado em 2025 como candidata a anã L5 (Karpov et al.), achado de forma independente: os filtros apontam pro lugar certo, mas a concorrência é real. Uma descoberta nova é o horizonte, não a promessa.
O que alimenta o programa
- Uma região do céu, dada por coordenadas (RA e Dec) em
config.py, e os limiares dos filtros (quão rápido o ponto tem de se mover, quão nítida a medida precisa ser). - Dois bancos de dados públicos, baixados da internet na hora: a tabela CatWISE2020 (NASA/IPAC) e o cadastro SIMBAD (CDS, Estrasburgo).
- Uma chave grátis de IA (Groq), só pra nota da última fase.
O que ele devolve
Uma tabela, candidatos_fase5, no arquivo data/rastro.db. Cada linha é um suspeito: código da fonte, posição no céu, brilho e cor no infravermelho, movimento próprio (e quantas vezes ele é maior que o erro de medida), a nota da IA (alta, media, baixa ou incerta) e um parágrafo curto com o motivo da nota. Pra ver a lista, ordenada pelos que mais se movem:
python -c "import sqlite3, pandas as pd; print(pd.read_sql('SELECT source_id, ra, dec, pm_total, llm_triage_score FROM candidatos_fase5 ORDER BY pm_total DESC', sqlite3.connect('data/rastro.db')))"O que fazer com o resultado
A lista é de suspeitos, não de descobertas. O caminho depois dela:
- Olhar as imagens dos suspeitos, começando pelos que o AllWISE mostra se movendo (a nota da IA é só um comentário). O app
app/revisao.py(veja Como usar) mostra os recortes de cada suspeito e grava sua decisão. Os recortes mostram se há um objeto real ali; o movimento aparece na animação "Movimento" (quadros do W2 por época, do WiseView) e no teste do AllWISE. - Descartar os falsos alarmes. Na prática, quase todos caem aqui: ruído, defeito do instrumento ou ponto mal medido.
- Levar os que sobrarem a um astrônomo, que pode confirmar com outras observações (um espectro, por exemplo) e, se for novo de verdade, publicar.
Como decidir na revisão visual
| O que você vê | Decisão |
|---|---|
| Nada no centro das imagens de infravermelho, só ruído | Descartar |
| Mancha larga e difusa (galáxia), ou colada numa estrela brilhante, ou no meio dos "raios" de uma estrela | Descartar |
| No óptico, uma estrela clara (branca, amarela ou azul) no centro | Descartar (estrela comum) |
| Ponto nítido no infravermelho, laranja ou avermelhado, e nada no óptico | Confirmar (compatível com anã T ou Y) |
| Ponto nítido no infravermelho e um ponto vermelho fraco no óptico | Confirmar (compatível com anã L), mas com cor W1-W2 abaixo de ~0,3 pode ser só uma anã M comum, então prefira Dúvida |
| Ponto no infravermelho, mas fraco, colado numa vizinha, ou você não conseguiu conferir o movimento | Dúvida |
Detalhes que ajudam
- O objeto se move. No óptico, ele pode estar um pouco deslocado da mira, na direção do movimento (movimento próprio x anos de diferença entre as imagens). Dentro do espaço vazio da mira (cerca de 6") ainda conta como no lugar.
- No unWISE, cor avermelhada ou alaranjada é o que se espera de uma anã fria. Azul ou branco é estrela comum.
- O app compara cada candidato com o AllWISE (~2010): "se moveu (confere)" reforça o caso, "não se moveu" pesa contra, e "sem fonte" não diz nada (o candidato é fraco demais pra aparecer lá).
- Confirmar não é descoberta: quer dizer "passa no olho". O teste decisivo é a animação "Movimento" ao lado dos recortes: o ponto tem de andar sempre na mesma direção, ano após ano. O link do WiseView abre a mesma animação em tamanho maior.
- Descartar não apaga nada: a decisão fica gravada com a nota e dá pra rever no filtro "Situação".
Checklist, animação e modo treino
- Checklist. Abaixo dos números, o app marca com ✅,
⚠️ ou ❌ cinco testes (movimento, AllWISE, Gaia, cor W1-W2 e óptico) e fecha com "Sugestão do pipeline". A sugestão é só um resumo dos números: ela só vale se os dois testes humanos passarem, um ponto nítido no infravermelho dentro da mira e, na animação, o ponto andando sempre na mesma direção. - Animação. A terceira imagem mostra o movimento: quadros do W2 do unWISE por época, montados pelo servidor do WiseView, com a mira no lugar e o ano escrito em cada quadro. Se o servidor falhar, a página mostra um aviso e continua funcionando.
- Modo treino. A página
treino, na barra lateral do app, sorteia casos já resolvidos, o mesmo número para cada resposta: anãs L/T/Y conhecidas que passariam pelos mesmos filtros da fila (uma anã fraca demais para ver nas imagens não entra), estrelas comuns de alto movimento do SIMBAD e candidatos rejeitados pela Fase 3. Os casos são cruzados com Gaia, Pan-STARRS e AllWISE uma vez, como a fila, então o checklist e a sugestão funcionam igual à revisão. O caso vem sem a resposta; depois do clique, o app mostra a resposta, a explicação e se você acertou. Dúvida vale meio ponto. Cada resposta fica gravada na tabelatreino_historico, e o quadro "Seu histórico" mostra o acerto geral, o das últimas 20 respostas e o acerto separado por resposta certa: diferença grande entre eles indica viés, como confirmar quase tudo. No topo do treino aparece o nível (Aprendiz, Revisor ou Revisor confiável, pelas últimas 20 respostas), e a revisão mostra a trilha do trabalho: treino em dia, fila revisada, um confirmado com AllWISE consistente, segunda opinião pedida e descoberta publicada (as duas últimas você marca). Nada dá ponto por confirmar candidato. Antes do primeiro treino, rode uma vezpython tools/recall_conhecidas.py(usa rede, ~8 min): ele gravadata/recall_conhecidas.pkl, de onde saem as anãs do treino.
Casos de referência
Confirmar
Em cada linha: infravermelho (unWISE NEO7) e óptico (Legacy Surveys). As três primeiras linhas desta folha são exemplos de confirmar.
- Anã L5 conhecida. Bolha nítida no infravermelho e um ponto vermelho no óptico, os dois na mira.
- Anã T7 conhecida. Bolha alaranjada no infravermelho, na borda da mira (o objeto andou desde 2015) e nada no óptico naquela posição. O ponto vermelho à direita, no óptico, é outra fonte.
- Anã L2 conhecida, do próprio campo varrido. Bolha nítida no infravermelho e um pontinho vermelho no óptico, os dois logo abaixo da mira: a posição do catálogo é de 2000 e o objeto andou desde então. Um suspeito com essa cara vale levar ao WiseView.
Dúvida
- Anã Y0 conhecida. Um borrão marrom fraquíssimo no infravermelho, nada no óptico, vizinhas brilhantes por perto. Uma anã Y real parece isso.
- Três suspeitos do campo com a mesma cara: fraco no infravermelho, nada no óptico. Sem ver o movimento no WiseView não dá pra distingui-los de ruído, e é por isso que o certo é dúvida (e não descartar).
Descartar
- Nada no infravermelho. Só um rastro difuso e sem forma, sem a bolha compacta de um objeto real.
- Estrela clara no óptico. Ponto brilhante, com núcleo estourado, bem na mira.
- Galáxia. No óptico aparece uma galáxia grande e amarela, com estrutura.
- Estrela brilhante e saturada. O "estouro" azul-lilás é típico de estrela forte demais pra câmera.
Cada linha tem 2 recortes de 60" x 60": infravermelho (unWISE NEO7) e óptico (Legacy Surveys). Embaixo, a anã L2 já catalogada 2MASS J09585080+2001525: nítida no infravermelho e vermelha no óptico. Em cima, um suspeito que a IA chegou a marcar como alta: quase nada aparece no centro, sinal de falso alarme. Imagens: unWISE (NASA/WISE) e Legacy Surveys (DESI), via visualizador do Legacy Surveys.
O método não é novo. Kevin Luhman descobriu a WISE 0855-0714 (a anã marrom mais fria conhecida, a ~2,3 parsecs, com movimento próprio de ~8"/ano) comparando imagens WISE de épocas diferentes. Voluntários de ciência cidadã, como os do projeto Backyard Worlds, também já acharam anãs ultrafrias vasculhando dados públicos: um deles, Frank Kiwy, achou sozinho dezenas de sistemas binários.
O que muda aqui é o catálogo. O CatWISE2020 (1,89 bilhão de fontes, combinando WISE e NEOWISE, 12 épocas em 8 anos) já traz o movimento próprio calculado por fonte, com precisão muito superior à de gerações anteriores. Não é preciso cruzar dois catálogos de épocas diferentes na mão: o ajuste já está feito, e melhor. Este projeto minera esse campo pronto.
O CatWISE2020 é um catálogo tabular: cada linha é uma fonte detectada no infravermelho (um ponto no céu), não uma foto. As colunas são medições já reduzidas:
| Coluna | O que é |
|---|---|
ra, dec |
posição no céu (graus) |
w1mpro, w2mpro |
brilho nas bandas infravermelhas W1 (3,4 µm) e W2 (4,6 µm), em magnitudes |
w1snr, w2snr |
razão sinal-ruído da medida em cada banda |
pmra, pmdec |
movimento próprio já calculado pelo catálogo (arcsec/ano) |
pmcode, ka, km |
blend da fonte (pmcode = ABCCC, A = nº de componentes) e uso de astrometria/movimento |
w1flg, w2flg, cc_flags, ab_flags, n_aw |
flags de qualidade/contaminação (artefato, confusão de fontes) |
O pipeline não processa imagens. A Fase 2 é uma consulta SELECT ... WHERE ... em ADQL (uma linguagem tipo SQL), rodada no servidor do IRSA via TAP, um protocolo de consulta a catálogos astronômicos. Cada candidato que avança é uma linha dessa tabela. Imagens só entram no fim, na inspeção humana (Fase 6).
| Nível | O que é | Prometido? |
|---|---|---|
| Mínimo | Pipeline rodando ponta a ponta e redescobrindo um objeto conhecido (o teste de sanidade) | Sim, é o critério de aceite |
| Bom | Lista curta e defensável de candidatos não catalogados numa região real, sem artefatos óbvios | Sim, com esforço razoável |
| Moonshot | Um candidato genuinamente novo, validado por segunda opinião | Não, é o horizonte |
O valor entregue está no pipeline funcionar corretamente, não no achado em si.
O que o projeto encontrou até agora, inclusive o que não deu certo:
| Achado | Estágio | O que ensinou |
|---|---|---|
| Teste de sanidade: a Luhman 16, anã marrom a 2 parsecs, é redescoberta com o movimento próprio certo | Feito a cada execução | A cadeia de dados (IRSA, SIMBAD) funciona de ponta a ponta |
| Os cortes deixam passar 76% de 949 anãs L, T e Y já conhecidas | Medido | Dois cortes que pareciam óbvios derrubavam o recall e foram removidos |
| Primeira varredura (RA 148° a 153°, Dec 18° a 23°): 22.065 fontes, 333 depois dos filtros, 237 candidatos novos | Em revisão visual | A maior parte do trabalho é descartar falsos positivos |
| O melhor candidato, com o movimento confirmado pelo AllWISE, já estava publicado como CWISE J100116.36+184013.6, candidata L5 em Karpov et al. (2025) | Redescoberta independente | O método chega ao mesmo objeto que uma equipe profissional, mas há concorrência: antes de chamar algo de novo, consultar o VizieR inteiro |
Fases sequenciais. Cada uma lê a tabela produzida pela anterior no SQLite e grava a sua, então elas se comunicam pelo banco, não por memória.
| Fase | O que faz | Status |
|---|---|---|
| 1 | Teste de sanidade: redescobre Luhman 16 (PM ~2,7"/ano) e WISE 0855 (PM ~8"/ano) via CatWISE2020 + SIMBAD. Se Luhman 16 falhar, o pipeline aborta, porque não faz sentido caçar objetos novos com uma stack que não acha os conhecidos. WISE 0855 só gera aviso | ✅ |
| 2 | Query ao CatWISE2020 (ADQL/TAP) numa região do céu, varrida em ladrilhos de 1 grau (um ladrilho que estoura o limite de linhas é dividido em 4), filtrando no servidor por movimento próprio, astrometria (ka=km=3) e qualidade de W2 (SNR e flag). Sem corte em W1: anãs Y são fracas nessa banda | ✅ |
| 3 | Filtragem fina de qualidade: remove contaminação/artefatos por cc_flags/ab_flags, troca do componente principal (pmcode B=N), confusão de fontes (n_aw) e movimento próprio pouco significativo (PM/σ < 8) |
✅ |
| 4 | Cross-match contra SIMBAD (via CDS XMatch em lote): descarta o que já é catalogado. O SIMBAD usa posições em J2000 e o CatWISE mede em ~2015, então a posição é recuada pelo movimento próprio antes do match. Sem isso, fontes com PM acima de ~0,3"/ano escapam como "novas". Depois, os que sobram são cruzados com 11 listas de anãs frias do VizieR (CATALOGOS_ANAS em config.py, como Karpov+2025, Kirkpatrick+2021/2024 e Best+2018), nas duas épocas, porque o SIMBAD demora a absorver catálogos novos |
✅ |
| 5 | Triagem assistida por LLM: prioriza os candidatos por consistência com uma anã L, T ou Y. A LLM ordena esforço humano, nunca dá o veredito final. O score varia entre rodadas (cerca de 1/3 dos candidatos mudam), então vale só como prioridade | ✅ |
| 6 | Inspeção visual humana (app Streamlit): recortes unWISE e óptico com mira na posição, animação do W2 por época, checklist com sugestão do pipeline, números, link pro WiseView, teste de movimento contra o AllWISE, busca por código e filtro por situação, decisão confirmar/dúvida/descartar gravada em revisao_humana. O notebook inspecao_visual.ipynb é a versão antiga, só com links |
✅ |
| 7 | Relatório dos confirmados (HTML com imagens embutidas), gerado por botão no app ou por python src/relatorio.py (que também grava um CSV), pra enviar a um astrônomo e pedir uma segunda opinião. Manual, como a Fase 6 |
✅ |
Quanto os cortes deixam passar. Os limiares foram medidos contra 949 anãs L/T/Y já conhecidas (python tools/recall_conhecidas.py, usa rede, ~8 min). Com os cortes atuais passam 76% delas (L 80%, T 70%, Y 77%). Duas medições mudaram o desenho: filtrar o primeiro dígito do pmcode (A=1) derrubava o recall de 76% pra 25%, porque ele mede vizinhança e não qualidade; e cortar por SNR em W1 perdia metade das anãs Y. As conhecidas são as mais brilhantes, então o recall pra anãs ainda não descobertas tende a ser menor.
O uso tem duas partes, uma no terminal e outra no navegador:
| Parte | Onde | Comando | Para quê |
|---|---|---|---|
| Fases 1 a 5 | Terminal | python pipeline.py |
Baixar o céu, filtrar e montar a lista de candidatos. Leva um bom tempo e roda sozinho |
| Fases 6 e 7 | Navegador | python iniciar.py |
Olhar as imagens, decidir e baixar o relatório dos confirmados |
O pipeline é um lote longo, sem interação, e o terminal serve bem. A revisão precisa de imagens e de decisão humana, e por isso vive no navegador. O pipeline só precisa ser rodado de novo para varrer outra região ou refazer os cortes.
A partir da raiz do repositório:
python3 -m venv venv && source venv/bin/activate # Linux e macOS, se ainda nao existir
pip install -r requirements.txt
python -u pipeline.py # roda as fases 1 a 5 em sequencia
python -u pipeline.py --proximo # escolhe sozinho o próximo bloco do céu e roda tudoNo Windows (PowerShell), crie e ative o ambiente assim; o resto dos comandos é igual:
py -m venv venv; venv\Scripts\Activate.ps1-u faz o Python escrever cada print na hora. No terminal isso já acontece, mas com a saída redirecionada (> log.txt, | tee log.txt, nohup) o Python junta tudo em blocos e o log fica parado por minutos, parecendo travado, nas fases longas.
As fases 1 a 4 não precisam de chave. A Fase 5 precisa de uma chave grátis de LLM (veja Triagem por LLM). Sem chave, ela falha e as tabelas das fases anteriores continuam no banco.
Revisar os candidatos (Fases 6 e 7), depois que a Fase 4 tiver rodado:
python iniciar.py # sobe o app e abre o navegador; usa o venv do projeto sozinhoÉ o mesmo que streamlit run app/revisao.py, sem precisar ativar o venv. python iniciar.py --demo abre só o treino, como na demo online, e --porta 8600 escolhe a porta.
O app abre no navegador com a fila de candidatos pendentes e sem contraparte no Gaia, com os que o AllWISE mostra se movendo primeiro e depois do mais ao menos significativo. Na primeira abertura ele cruza a lista com Gaia, Pan-STARRS e AllWISE (alguns segundos). As páginas ficam em app/paginas/ (fila.py para a revisão e treino.py para o treino), e o tema escuro e os componentes visuais vêm de .streamlit/config.toml e app/estilo.py. O guia "O que procurar em cada imagem" explica como ler as três imagens e o que decide. Quando a fila acabar, o botão "Buscar mais candidatas" (com confirmação, progresso e cancelamento) escolhe sozinho o próximo bloco do céu e roda o pipeline; o mesmo pelo terminal é python -u pipeline.py --proximo.
Na barra lateral:
- Situação: pendentes, confirmados, dúvidas, descartados ou todos, cada um com a contagem. É por aqui que se revê o que já foi classificado.
- Buscar por código: acha qualquer candidato pelo código (ou pelo final dele), em qualquer situação.
- Filtros de Gaia, Pan-STARRS, movimento no AllWISE, PM/σ mínimo e nota da IA, e a ordenação.
- Já foi publicado? Um botão consulta o VizieR inteiro em volta do candidato e destaca os catálogos de anãs ou de movimento próprio, com a distância. A Fase 4 só cruza as listas conhecidas, e é assim que se acha um catálogo novo que ainda não entrou nelas. Use antes de confirmar. O relatório da Fase 7 faz a mesma busca para cada candidato.
- Relatório (Fase 7): no corpo da página, o botão "Gerar relatório" monta uma página HTML com os confirmados (ou confirmados e dúvidas, se marcar a caixa) e o botão "Baixar relatório" entrega o arquivo. Leva alguns segundos por candidato, para baixar os recortes.
- No corpo da página, o quadro "Fila completa" lista a fila, e um clique numa linha abre aquele candidato.
O teste do AllWISE. O AllWISE mediu o céu uns 5 anos antes do CatWISE. Se o movimento próprio de um candidato for real, a fonte tem de aparecer lá na posição recuada pelo movimento, e não onde ela está hoje. O app marca cada candidato como se moveu (confere), não se moveu, ambíguo, sem fonte ou PM baixo demais pra testar. O AllWISE é mais raso que o CatWISE, então um candidato fraco quase sempre cai em "sem fonte", e isso não prova nada.
As decisões ficam na tabela revisao_humana do mesmo banco e não são apagadas quando o pipeline roda de novo.
O relatório (Fase 7). É uma página com recortes, números, resultado do AllWISE, a sua nota e links. Abre offline e dá pra anexar num e-mail. A página diz o que a lista não afirma: são candidatos, não descobertas. Sem o app, o mesmo relatório sai pelo terminal, junto com um candidatos.csv, em data/relatorio/:
python src/relatorio.py # só os confirmados
python src/relatorio.py --duvidas # confirmados e dúvidasRodar uma fase isolada (cada módulo expõe run(); precisa da tabela da fase anterior no banco):
python -c "from src.llm_triage import run; run()"Há dois níveis de teste, do mais rápido ao mais completo:
1. Testes de unidade (offline, sem rede, instantâneos). Cobrem a lógica pura que quebra calada se alguém mexer: derivação de movimento próprio e de sua significância (pm_signif), filtro de pmcode, recuo de posição pra J2000 no cross-match, parsing do score da LLM (incluindo a regra "alta só via veredito explícito") e a montagem da query da Fase 2.
python tests/test_units.py # runner embutido, sem dependencia
# ou, se tiver pytest: python -m pytest tests/2. Teste de sanidade / integração (Fase 1, usa rede). É o verdadeiro teste de aceite do sistema: redescobre objetos reais conhecidos (Luhman 16 e WISE 0855) no CatWISE2020 e confere se o movimento próprio medido bate com a referência. O IRSA devolve 504 de vez em quando; a Fase 1 tenta de novo até 3 vezes. Se o Luhman 16 não bater com a referência, ela chama sys.exit(1) e para tudo: a stack de dados (IRSA/SIMBAD) está com problema e não adianta seguir.
python -c "from src.sanity_check import run; run()"Não há mocks das APIs externas (IRSA/SIMBAD/LLM): a filosofia aqui é validar contra os serviços reais via Fase 1, já que a corretude depende justamente de os catálogos responderem o esperado.
Esta é a única fase que precisa de chave. É provider-agnóstico: fala o formato OpenAI (/chat/completions) via httpx, sem SDK. Trocar de provider é só mudar três coisas em config.py: LLM_BASE_URL, LLM_MODEL e de onde vem a chave. Há exemplos comentados pra Gemini, OpenRouter, Zhipu e Ollama.
- Default: Groq free (
openai/gpt-oss-20b, modo texto comreasoning_effortbaixo). Chave grátis, sem cartão, em console.groq.com. Cota (tokens/min e tokens/dia) é por modelo, então dá pra alternar de modelo se um esgotar. - Alternativa sem cota diária (pra iterar bastante): Zhipu GLM-4.7-Flash, perpetuamente free (exemplo pronto no
config.py). - Zero cota / offline: Ollama local.
-
Crie uma conta em console.groq.com (entra com Google ou GitHub, sem cartão).
-
No menu API Keys, clique em Create API Key e copie a chave (começa com
gsk_). Ela só aparece uma vez; se perder, crie outra. -
Guarde a chave numa variável de ambiente, que é de onde o projeto a lê. Para valer em todo terminal novo:
# Linux (bash); no macOS troque ~/.bashrc por ~/.zshrc echo 'export GROQ_API_KEY="gsk_cole_a_sua_chave_aqui"' >> ~/.bashrc source ~/.bashrc
# Windows: grava para os próximos terminais; feche e abra o terminal depois setx GROQ_API_KEY "gsk_cole_a_sua_chave_aqui"
Não coloque a chave em
config.pynem em nenhum arquivo do repositório: ela iria parar no git. -
Teste a chave (o mesmo comando nos três sistemas, com o ambiente ativado). Se aparecer uma lista de modelos, está certa;
Invalid API Keyquer dizer chave errada ou incompleta:python -c "import os, httpx; print(httpx.get('https://api.groq.com/openai/v1/models', headers={'Authorization': 'Bearer ' + os.environ['GROQ_API_KEY']}).text[:300])" -
Rode só a Fase 5 (as fases 1 a 4 precisam já ter rodado):
python -u -c "from src.llm_triage import run; run()"
Quando a cota acaba. A Fase 5 tria os LLM_BATCH_SIZE (60) candidatos de maior movimento próprio, com uma pausa de LLM_REQUEST_DELAY_S (8 s) entre chamadas para respeitar o limite por minuto. Se o limite por minuto estourar, ela espera e tenta de novo sozinha. Se a cota diária acabar, cada chamada restante falha na hora com a mensagem "cota diaria do free tier esgotada" e o candidato fica com score erro. Aí há três saídas: esperar o reset (no dia seguinte, no fuso do Groq), trocar LLM_MODEL por outro modelo do Groq (a cota é separada por modelo) ou usar um dos outros provedores abaixo.
Outro provedor. Mude em config.py as linhas LLM_BASE_URL, LLM_MODEL e LLM_API_KEY_ENV (há exemplos prontos comentados para Gemini, OpenRouter, Zhipu e Ollama) e exporte a chave com o nome que você pôs em LLM_API_KEY_ENV. Com Ollama local, use LLM_API_KEY_ENV = None: não há chave.
A chave é lida por load_api_key() da variável em LLM_API_KEY_ENV. Se a variável não existir, há um segundo lugar, conveniente para quem usa o OpenCode: o arquivo ~/.local/share/opencode/auth.json (campo <provider>.key), caminho do Linux e do macOS. No Windows, use a variável. O projeto não lê arquivo .env.
A triagem pode usar saída estruturada (response_format json_schema, LLM_STRUCTURED=True), mas o default é o modo texto: o gpt-oss-20b no Groq falha o JSON em boa parte das chamadas. Se o modelo rejeitar o formato, cai no modo texto e lê o veredito da resposta. Resposta vazia vira erro, nunca um score. Regra do score: alta só sai de um veredito explícito do modelo; heurística por palavra-chave só rebaixa, nunca promove, pra não gerar falso positivo.
Tudo em data/rastro.db (SQLite). Tabelas, em ordem de fase:
campo_bruto: resultado da query TAP (Fase 2)candidatos_fase3: após filtro de qualidadecandidatos_fase4: após cross-match SIMBAD e listas de anãs (comstatus,simbad_match_idecatalogo_match)candidatos_fase5: após triagem LLM (comllm_triage_scoreellm_triage_notes)revisao_humana: suas decisões da Fase 6 (confirmado,duvidaoudescartado, com nota). Tabela própria, que o pipeline não recria
Tudo centralizado em config.py: região do céu, limiares de PM/SNR/flags, alvos de sanidade, provider/modelo da LLM, caminhos. Mudar comportamento do pipeline é mexer aqui, não nos módulos.
.
├── pipeline.py # entrypoint (roda as fases 1-5)
├── config.py # toda a parametrizacao
├── requirements.txt
├── README.md
├── src/
│ ├── sanity_check.py # Fase 1
│ ├── fetch_field.py # Fase 2
│ ├── quality_filter.py # Fase 3
│ ├── known_catalog_match.py # Fase 4
│ ├── llm_triage.py # Fase 5
│ ├── relatorio.py # Fase 7: relatorio dos confirmados (HTML, e CSV pela linha de comando)
│ ├── campos.py # proximo bloco 5x5 do ceu para o --proximo
│ ├── util.py # prazo de relogio nas chamadas de rede
│ └── proper_motion.py # util (pm_total, cor_w1w2)
├── tools/
│ ├── recall_conhecidas.py # mede o recall dos cortes contra anas conhecidas
│ ├── gerar_referencias.py # folhas de referencia da revisao (docs/img/ref_*.jpg)
│ └── gerar_funil.py # grafico do funil (docs/img/funil_fases.png), lido do banco
├── app/
│ ├── revisao.py # Fase 6: router do app Streamlit de revisao visual
│ ├── estilo.py # tema escuro, cabecalho, ficha e checklist (CSS/HTML)
│ ├── dados.py # leitura/gravacao das revisoes, URLs e checklist
│ ├── imagens.py # recortes e animacao do W2, compartilhados pelas paginas
│ ├── guia.py # como ler as imagens, exemplos bom x ruim e pista do treino
│ ├── cruzamento.py # Gaia, Pan-STARRS e AllWISE, cruzados uma vez e guardados
│ ├── execucao.py # botao Buscar mais candidatas (pipeline em segundo plano)
│ └── paginas/
│ ├── fila.py # revisao visual: fila, imagens e decisao
│ └── treino.py # modo treino: casos com resposta
├── .streamlit/
│ └── config.toml # tema escuro e barra de ferramentas minima
├── tests/
│ └── test_units.py # testes de unidade offline
├── docs/img/ # figuras usadas neste README
├── notebooks/
│ └── inspecao_visual.ipynb # versao antiga da Fase 6 (so links)
└── data/
└── rastro.db # resultados (gitignored)
- Catálogo de movimento próprio: CatWISE2020 (Marocco et al. 2021) e AllWISE, ambos do IRSA (NASA/IPAC).
- Objetos conhecidos: SIMBAD, VizieR e o serviço XMatch, do CDS (Estrasburgo).
- Contrapartes ópticas: Gaia DR3 (ESA/DPAC) e Pan-STARRS DR1, via VizieR.
- Imagens: unWISE (NASA/WISE) e DR10 do Legacy Surveys (DESI); animação por época do WiseView (Backyard Worlds).
Este projeto usou o banco de dados SIMBAD e o serviço VizieR, operados no CDS, Estrasburgo, França.
- Eisenhardt et al. (2020, ApJS 247, 69) e Marocco et al. (2021, ApJS 253, 8): definição do CatWISE2020, colunas, flags e precisão do movimento próprio.
- Luhman (2014, ApJ 786, L18): descoberta da WISE 0855-0714 por comparação de épocas do WISE.
- Backyard Worlds: Planet 9 (Zooniverse): ciência cidadã que já achou centenas de anãs frias em dados públicos do WISE; um voluntário, Frank Kiwy, achou sozinho dezenas de binárias ultrafrias.
- Karpov et al. (2025, A&A 695, A195): lista de candidatas fotométricas que já continha o melhor candidato achado aqui (CWISE J100116.36+184013.6).
Código sob a licença MIT. Imagens de terceiros mantêm a licença indicada na legenda.







