vccs
Vue 3용 차트 컴포넌트 모음, Recharts에서 포팅
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Painel de inteligência da produção acadêmica da UFSC
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Plataforma de cientometria, análise de redes sociais (SNA) e mineração de textos que mapeia a produção acadêmica da UFSC — teses, dissertações e trabalhos de conclusão — para revelar a estrutura latente do conhecimento: redes de colaboração, genealogia acadêmica, temas emergentes e o papel topológico de pesquisadores e conceitos.README 전문 보기 ↗
Plataforma de cientometria, análise de redes sociais (SNA) e mineração de textos que mapeia a produção acadêmica da UFSC — teses, dissertações e trabalhos de conclusão — para revelar a estrutura latente do conhecimento: redes de colaboração, genealogia acadêmica, temas emergentes e o papel topológico de pesquisadores e conceitos.
Os dados vêm do Repositório Institucional da UFSC, que roda DSpace. O EcoGrad não produz dado novo: coleta, normaliza, indexa e reorganiza o que já está depositado lá, e todo trabalho preserva o link para a página original. O acervo atual tem 92.331 registros, 85.567 trabalhos distintos (a mesma obra pode estar catalogada em mais de uma coleção) e 254 coleções; 80.415 registros têm resumo utilizável.
Em produção: https://ecograd.netlify.app
A aplicação é a versão web, em ecograd-web/ — React 18 + Vite + TypeScript +
Tailwind, com Netlify Functions. Rode com cd ecograd-web && npm install && npm run netlify:dev;
detalhes no README da aplicação.
A raiz guarda o que alimenta e valida essa aplicação:
| Arquivo | Papel |
|---|---|
backend.py (+ app_config.py, gemini_utils.py) | Referência numérica do Python original, importada por npm run verify:parity |
pipeline_ufsc.py | Enriquecimento da base: macrotemas (NMF + Gemini), normalização, consolidação |
coleta/ | Coleta semanal do Repositório Institucional, executada pelo GitHub Actions |
base_*.json.gz, programas_ufsc.json, mapa_colecoes_tcc.json | Bases copiadas para ecograd-web/public/data/ no sync:data |
base_ppgegc.json | Base de referência da comparação de paridade |
ecograd-mcp/ | Servidor MCP do acervo, publicado no npm — o índice como ferramenta de assistente |
A versão web é uma migração de um app Streamlit. Toda a matemática de redes complexas, foresight
e memética foi transcrita e verificada numericamente contra o backend.py original — veja
Paridade. A interface Streamlit em si (Principal.py
e pages/) foi removida do repositório em favor da versão web; ela continua acessível no
histórico do git, até o commit cad47e3.
O projeto vai além da contagem de publicações. Usando teoria de sistemas complexos e grafos, mapeia a "ecologia do conhecimento" de ecossistemas acadêmicos para:
O painel lateral recolhe para uma faixa de ícones, e o estado fica salvo na sessão. O botão Sobre, na tela inicial, explica a procedência dos dados e como a plataforma funciona por baixo.
De qualquer página, Exportar relatório monta um arquivo com as seções do Dashboard e os dossiês visitados que você marcar. Tudo acontece no navegador; nada é enviado para servidor. Dois formatos:
secoes, com
indicadores e tabelas) e, além dele, os registros completos da análise em documentos:
título, ano, nível, autoria, orientação, palavras-chave, macrotema, coleção, link, resumo,
artefatos da ontologia e PDFs, com os mesmos nomes de campo da base de origem. Campos vazios não
aparecem, como na base. Os resumos podem ser desmarcados quando o arquivo precisa ser menor — a
tela estima o tamanho antes. Gráficos não atravessam: viram o nome da figura omitida, já que os
dados que os sustentam estão nas tabelas.Os dois carregam coleções, período, versão da base, data de geração e as ressalvas sobre os limites do recorte. Elas não são desmarcáveis: um arquivo circula longe da tela que as explica.
KPIs da base, comparativo entre PPGs quando há mais de um, ficha oficial da CAPES (cruzada com a Plataforma Sucupira por fuzzy matching, que resolve programas renomeados) e uma síntese do perfil epistemológico escrita por IA.
Todo nome exibido é clicável — chips de destaque e as barras dos Top 10 — e leva direto ao dossiê da entidade no Motor de Busca.
Nas nuvens de palavras, em qualquer tela, clicar num termo abre um convite em vez de agir: o modal diz quantas coleções seriam baixadas e quanto pesam, oferece explorar o termo no recorte já carregado quando ele aparece nele, e avisa quando o termo só existe dentro de títulos e resumos — sem ser palavra-chave, macrotema, pessoa ou título — e portanto não tem o que abrir.
Busca unificada por Documento, Autor, Orientador, Co-orientador, Palavra-chave e Macrotema. Cada entidade abre um dossiê com:
Uma página com quatro abas, agrupadas por pergunta de pesquisa. Só a aba aberta é montada, e os cálculos mais pesados têm botão próprio — eles seguem em segundo plano se você trocar de aba.
Temas e conceitos — o que existe na seleção.
Tempo e tendências — como o vocabulário mudou.
Estrutura da rede — como tudo se conecta.
echarts-gl): arraste para
girar, roda para aproximar, botão direito para deslocar. Cor pela comunidade do Louvain e escala
dos eixos linear (a do modelo original) ou logarítmica.Especialização e dados — comparar níveis e levar embora.
O UFSCão fala em duas superfícies, com a mesma persona, a mesma configuração e os mesmos limites:
| Onde | Sobre o quê | Como recupera |
|---|---|---|
| Conversar, na tela inicial | o acervo inteiro | índice Postgres: SQL para quem/quanto/quando, busca textual e vetorial para tema |
| Botão flutuante, nas páginas | as coleções carregadas | dossiê montado por pergunta a partir do recorte em memória |
A conversa iniciada na tela inicial acompanha você para o botão flutuante, visível e citada, com o recorte de cada resposta declarado — as duas telas respondem sobre corpora diferentes, e o prompt diz isso ao modelo em vez de misturar os números em silêncio.
BYOK. A chave de API é sua, fica no seu navegador e vai direto ao provedor escolhido (OpenAI, Anthropic, Google, Groq, OpenRouter ou qualquer serviço compatível com a API de chat da OpenAI). O EcoGrad não recebe nem guarda a conversa. Configure em Configurações ou dentro do próprio chat. A única chamada a modelo que o servidor faz é o embedding da pergunta, para a busca por significado.
O modelo só escreve. Toda contagem sai de SQL; a busca por similaridade escolhe o que ler, e
nunca responde "quantos". Cada afirmação sobre um trabalho leva a citação [n] da obra que a
sustenta, e cada [n] é um botão que abre a obra no Motor de Busca. Citação a fonte que não foi
enviada é apontada na tela.
Duas profundidades de leitura. A resposta padrão lê uma amostra representativa de 15 a 20 obras, espalhada por coleção. Aprofundar lê todas as obras do tema com resumo utilizável, em lotes — mostrando chamadas, tokens e tempo estimados antes de gastar a sua chave.
Guardrails (src/lib/guardrails.ts) valem nas duas telas,
num arquivo só para não divergirem:
SELECT, com teto de tempo e de linhas; não existe tabela de escrita alcançável pela chave
publicada; e a chave do modelo é do próprio usuário.Responder "como a UFSC trata tal tema?" sem baixar nada exige um lugar onde o acervo inteiro já
esteja indexado. Esse lugar é um Postgres (Supabase) com o texto dos resumos em FTS e um vetor
por obra em pgvector. Ele é derivado e reconstruível: os .json.gz da raiz continuam sendo
a fonte, e indice_meta guarda o sha256 das bases que o geraram — a aplicação compara com o
manifesto publicado e se recusa a responder sobre uma base que não é mais a da tela.
| Camada | O que faz |
|---|---|
buscar_texto, tesauro, consulta_expandida | recuperação léxica, com expansão de vocabulário tirada do próprio acervo |
documento_embedding (pgvector, HNSW) | busca por significado, para o tema escrito com outro vocabulário |
panorama_tematico | panorama exato do tema em SQL mais uma amostra representativa, com cota por coleção |
obras_do_tema, resumos_das_obras | a lista inteira do tema e os resumos em páginas, para o "Aprofundar" |
consulta.* + consultar() | views comentadas e execução somente leitura, que é como o modelo escreve SQL (NL2SQL) |
pessoa, pessoa_fusao, perfis, rede_metrica | pessoas unificadas automaticamente, perfis e métricas de rede pré-calculadas |
Contagem nunca sai de similaridade. O vetor escolhe o que ler; quem conta é SQL. Obra achada só por significado vai ao modelo marcada como aproximação e fora de qualquer total.
cd ecograd-web
npm run indice:derivar # gera os arquivos do índice a partir das bases
npm run indice:carregar # carrega por COPY (porta 5432)
npm run indice:carregar:https # ou por HTTPS, onde a 5432 está bloqueada
npm run indice:embeddings # vetores das obras (custo único, ~US$ 8)
indice/schema.sql é a definição canônica: aplicá-lo num Postgres vazio e rodar os comandos acima
reconstrói o índice do zero. Não há migração incremental de propósito — divergência silenciosa
entre base e índice é o modo de falha que o projeto quer evitar, e reconstruir custa minutos.
O índice já é uma API pública: o PostgREST do Supabase expõe cada função com grant … to anon
em /rest/v1/rpc/<função>, e a chave publicável é pública por desenho — é a mesma que o navegador
recebe ao abrir o site. Não há serviço próprio no meio, nem custo além do banco que já existe.
docs/API.md — endereço, chave, cada endpoint com argumentos e retorno, as views
consultáveis por SQL e os limites de uso.ecograd-mcp/ — os mesmos endpoints como servidor MCP, para usar o acervo dentro
de um assistente: claude mcp add ecograd -- npx -y ecograd-mcp. Roda na máquina de quem usa, por
stdio, sem nada para hospedar.Duas rotinas no GitHub Actions mantêm tudo em dia, e a segunda depende da primeira:
| Rotina | Quando | O que faz |
|---|---|---|
coleta-ufsc.yml | segundas, 03:00 (Brasília) | coleta os depósitos novos do repositório e commita um lote em coletas/ |
indice-supabase.yml | logo após a coleta, mais rede de segurança nos dias 1 e 15 | deriva o índice das bases mais os lotes, carrega no Supabase, enriquece e calcula os vetores das obras novas |
A segunda roda por workflow_run, e não num horário próprio: o índice tem que ser derivado das
mesmas bases que o site publicou, e amarrar pelo relógio deixaria as duas metades fora de fase na
semana em que a coleta demorasse. Sem lote novo não há commit, o workflow_run não dispara, e o
índice fica como está — que é o correto, porque nada mudou.
O último passo é indice:conferir, que falha o job se o
carimbo não bater com as contagens, se o enriquecimento não tiver entrado, se a cobertura de vetor
cair abaixo de 90% ou se panorama_tematico e obras_do_tema discordarem. Numa rotina automática,
falhar alto é melhor do que servir meio acervo em silêncio.
Segredos necessários no repositório: SUPABASE_DB_URL (pooler em modo sessão, porta 5432) e
GEMINI_API_KEY.
Por que os embeddings não custam US$ 8 por semana. documento_embedding referencia documento
com on delete cascade, e a recarga do índice levaria os 85 mil vetores junto — obrigando a
recalcular tudo a cada execução. Duas coisas evitam isso: a carga preserva os vetores dentro da
própria transação, devolvendo os que ainda têm obra correspondente; e indice:embeddings --incremental lê do banco o que já foi pago, em vez do cache local, que num runner começa
sempre vazio. Só obra nova ou com resumo alterado vai ao modelo.
Runner e qualidade do resumo. Em runner hospedado do GitHub, a coleta fica fora da RedeUFSC: o DSpace pede verificação anti-robô e ela cai no Oasisbr/IBICT, sem resumo. Como o resumo é o que alimenta a busca textual e os vetores, colher pelo DSpace exige um runner self-hosted na rede da UFSC (ou com VPN), apontado pela variável de repositório
COLETA_RUNNER.
O índice e o UFSCão foram construídos por etapas com porta de medição, e o plano mudou duas vezes porque a medição desmentiu a aposta inicial. Os ADRs registram isso, e o mais recente vale sobre os anteriores:
| Documento | O que decide |
|---|---|
| ADR 001 | o plano original do chat semântico, em cinco etapas |
| ADR 002 | recorte por item, e não por coleção inteira |
| ADR 003 | a lacuna era de indexação, não de semântica: indexar o resumo levou a revocação de 46% para 79%, sem vetor nenhum |
| ADR 004 | o UFSCão sobre o acervo inteiro, em quatro fases (A0 a C) |
| Aferição | as 25 perguntas, os gabaritos e todo número medido |
Recuperação medida contra a triagem humana assinada, em k = 25 (revocação / precisão):
| Método | "empreendedorismo feminino" | "mulheres empreendedoras" | Sobreposição |
|---|---|---|---|
| Busca por rótulo (o app antes do índice) | 46% / 100% | — | — |
Texto (buscar_texto) | 79% / 76% | 63% / 83% | 64% |
| Vetor | 79% / 95% | 79% / 100% | 96% |
| Híbrido (RRF) — o que vai à tela | 83% / 87% | 75% / 82% | 80% |
Limite declarado: há um único tema com gabarito humano assinado. Uma amostra cega de 8 temas
sorteados está congelada em docs/evidencias/afericao/, aguardando triagem — é ela que dirá se os
números acima generalizam.
npm run afericao # roda o conjunto de aferição contra os gabaritos
npm run afericao:vetor # compara texto, vetor e híbrido
npm run afericao:amostra-cega # sorteia temas para medir generalização
netlify.toml # na raiz, com base = "ecograd-web"
docs/ # ADRs e aferição: o porquê de cada decisão, com os números
ecograd-web/
├── indice/schema.sql # definição canônica do índice Postgres
├── netlify/functions/ # DeepSeek (síntese, ontologia), Gemini (embedding), proxy CAPES, Neo4j
├── scripts/ # derivação e carga do índice, aferição, paridade
├── src/lib/ # motores analíticos, prompts e guardrails, em TypeScript puro
├── src/workers/ # ingestão e redes complexas fora da main thread
└── src/components/ # UI React
Front-end — React 18, Vite, Tailwind, Zustand (estado), TanStack Query (cache), Radix UI, ECharts (gráficos e nuvem de palavras) e react-force-graph (grafos).
Motores analíticos — kernels próprios sobre estrutura CSR: Brandes (BFS e Dijkstra, com amostragem de pivôs), closeness com correção Wasserman-Faust, clustering simples e ponderado, PageRank, autovetor, constraint de Burt, rich-club, assortatividade e Louvain (graphology).
Web Workers — a ingestão descomprime .json.gz de 62 MB e filtra a seleção; o worker de rede
roda SNA, bootstrap, grid search e a rede memética. Sem isso a página congelaria por minutos.
Backend serverless — Netlify Functions em TypeScript. As chaves secretas vivem só no runtime das
funções, nunca no bundle. O neo4j-query não aceita Cypher do cliente: só o nome de uma consulta
pré-registrada e parâmetros, que viajam como binds do driver. O embedding-consulta é a única
chamada a modelo que o servidor faz por conta própria — o vetor da pergunta, para a busca por
significado — e é cercada: só aceita a própria origem, texto curto e 30 chamadas por minuto por IP.
O que o EcoGrad não faz — não guarda conversa, não recebe a sua chave de API, não registra quem perguntou o quê. A conversa do UFSCão vai do seu navegador direto ao provedor que você escolheu; o que fica no EcoGrad é só o que você vê na sessão, e some ao fechar a aba.
O backend.py já aproximava o betweenness acima de 1500 nós; o mesmo princípio foi estendido às
demais métricas O(n·m), porque a rede completa da UFSC passa de 100 mil nós. Abaixo dos limiares o
resultado é idêntico ao NetworkX. A tabela completa está no
README da aplicação.
cd ecograd-web && npm run verify:parity
O harness roda os dois motores sobre a mesma base e compara os resultados. Cobertura:
| Bloco | O que é comparado |
|---|---|
| Topologia | degree, betweenness, closeness e clustering — diferença relativa máxima 2e-16 |
| Métricas complexas | densidade, eficiência global, entropia, PageRank, autovetor, Burt |
| Rede de coocorrência | rich-club por grau (153 valores), clustering ponderado, assortatividade, γ |
| Maturidade | assortatividade, rich-club, γ por regressão log-log e por estimador MLE |
| Radar de Foresight | os 43 termos, com Momentum e Novidade — diferença exatamente zero |
| Backtest | as 89 linhas, distribuição por quadrante e os oito vereditos |
| Memética | memes, mortalidade, sobreviventes, longevidade, meia-vida |
| QL e Jaccard | valores idênticos |
Duas diferenças conhecidas, ambas sem efeito sobre os números: Louvain é estocástico (a comparação é por modularidade) e empates de contagem na memética têm ordem arbitrária no pandas, que usa quicksort instável — o TypeScript desempata por nome, o que torna o Top-N reprodutível.
O harness já pagou por si: foi ele que expôs o autovetor iterando A·x em vez do x + A·x do
NetworkX, e a varredura do rich-club ordenando as arestas pelo maior grau em vez do menor — erro
que produzia coeficientes acima de 1.
cd ecograd-web && npm install
Crie ecograd-web/.env a partir de .env.example e rode tudo (front-end + funções) em
http://localhost:8888:
npm run netlify:dev
Para trabalhar só na interface, sem as funções, use npm run dev. As bases da raiz são copiadas
para public/data/ automaticamente. Detalhes, comandos e notas de deploy no
README da aplicação.
| Variável | Onde vale | Para quê |
|---|---|---|
VITE_INDICE_URL, VITE_INDICE_CHAVE | build | endereço e chave publicável do índice. O Vite as grava no JS que vai ao navegador |
DEEPSEEK_API_KEY | runtime das funções | síntese, ontologia e a cortesia do UFSCão |
GEMINI_API_KEY | runtime das funções | o embedding da pergunta (o índice foi vetorizado com o gemini-embedding-2) |
SUPABASE_DB_URL ou SUPABASE_SERVICE_ROLE_KEY | só na carga, na sua máquina | reconstruir o índice. Nunca precisam existir no ambiente de build |
As duas VITE_* são públicas por desenho — o prefixo significa gravar o valor no bundle, e a
chave é a publicável (anon), com RLS somente leitura e nenhuma tabela de escrita alcançável. Por
isso o netlify.toml traz SECRETS_SCAN_OMIT_KEYS para as duas: sem isso o scanner de segredos da
Netlify reprova o build ao encontrá-las no próprio bundle que acabou de gerar. O scanner segue
ligado para todo o resto.
Por serem de build, mudá-las exige recompilar — republicar o mesmo artefato não adianta.
npm run test:all # suíte completa
npm run typecheck # tsc --noEmit
npm run build # sync de dados + typecheck + build de produção
npm run verify:parity # paridade numérica com o backend Python
A interface Streamlit foi removida; o que ficou na raiz é a referência numérica usada pela comparação de paridade e o pipeline de enriquecimento da base.
pip install -r requirements.txt
As credenciais são lidas de variáveis de ambiente (ou de .streamlit/secrets.toml, que
app_config.py ainda aceita):
GEMINI_API_KEY=...
NEO4J_URI=...
NEO4J_USERNAME=...
NEO4J_PASSWORD=...
A migração do app Streamlit (cujo último estado vive no histórico do git, até o commit cad47e3)
está concluída, com uma exceção declarada.
| Módulo original | Onde estava | Onde está agora | Status |
|---|---|---|---|
| Espaço Topológico 3D (Grau × Betweenness × Closeness) | Exploração Global | Análise Avançada · Estrutura da rede | ✅ portado |
| Mapa Temático (quadrantes de Callon / Bibliometrix) | Principal | Análise Avançada · Temas e conceitos | ✅ portado |
| Sankey Temporal de palavras-chave | Fluxos | Análise Avançada · Tempo e tendências | ✅ portado |
| Boxplot de Especialização (QL por nível) | Exploração Global | Análise Avançada · Especialização e dados | ✅ portado |
| Furos Estruturais de Burt (seção dedicada) | Exploração Global | Análise Avançada · Estrutura da rede | ✅ portado |
| Exportação da rede (GEXF / GraphML / JSON) | Exploração Global | Análise Avançada · Especialização e dados | ✅ portado |
| Tabela geral da base com métricas SNA | Principal | Análise Avançada · Especialização e dados | ✅ portado |
| Métricas de redes complexas do grafo global | Exploração Global | Análise Avançada · Estrutura da rede | ✅ portado |
| Órbita via Neo4j (Cypher) | Motor de Busca | — | ⛔ fora de escopo, por decisão de projeto |
Três diferenças deliberadas em relação ao original, todas declaradas na própria interface:
echarts-gl, com a mesma órbita livre do scatter_3d original.
A tela do WebGL é uma superfície própria e o download de imagem não a alcança: ali a vista em
tabela é o caminho para levar os dados embora, e também o caminho pelo teclado. Ele oferece
escala logarítmica além da linear do original, porque as três métricas têm cauda longa e a linear
empilha a maioria dos nós num canto do cubo; o nome do eixo declara quando a escala está em uso.#/exploracao, #/foresight e #/memetica continuam abrindo, cada um na aba que guarda o
conteúdo que prometia.A órbita via Neo4j é a única funcionalidade que não será portada: a função existe no histórico, mas o EcoGrad usa o grafo em memória e o JSON como fonte, sem banco de grafos. Ver docs/ARMAZENAMENTO-E-VERSIONAMENTO.md.
Ver LICENSE.
Desenvolvido por Gustavo Simas — github.com/GSimas