Linha do Tempo
Este documento consolida as decisões arquiteturais (ADRs) tomadas durante o desenvolvimento do Opportunity Recommendation System (ORS), organizadas cronologicamente.
Arquitetura Orientada a Eventos (EDA)
Usar Arquitetura Orientada a Eventos (EDA) com RabbitMQ como broker de mensagens. Desacoplar a coleta (Scrapers) do processamento (Worker) e do consumo (API). RabbitMQ atua como buffer para absorver picos de carga.
Adoção da Ontologia ESCO
Usar a Ontologia ESCO (European Skills, Competences, Qualifications and Occupations) como espinha dorsal do Grafo de Conhecimento. Utilizar a taxonomia de Habilidades (Skills) da ESCO com suporte multilíngue, incluindo português brasileiro.
Uso do Padrão Template Method para Scrapers
Implementar o Padrão Template Method com uma classe base BaseScraper para garantir fluxo consistente de coleta com publicação incremental de oportunidades. Centraliza infraestrutura (publisher, logging, tratamento de erros) em classe base.
Deduplicação de Oportunidades via Hash
Calcular um hash SHA-256 no Publisher baseado em todos os campos da oportunidade exceto scraped_at. Adicionar content_hash como campo UNIQUE no banco de dados para evitar reprocessamento de duplicatas.
Selenium Standalone Container para Scrapers
Usar o container selenium/standalone-chrome como serviço dedicado no Docker Compose. Scrapers conectam via Remote WebDriver através da variável de ambiente SELENIUM_REMOTE_URL, eliminando a necessidade de instalar Chrome em cada container de scraper.
Adotar Stack PLG para Observabilidade Centralizada
Adotar a stack PLG (Promtail, Loki, Grafana) para centralização de logs, combinada com Loguru para padronização de logs estruturados em JSON nas aplicações Python. Promtail coleta de stdout/stderr, Loki armazena, Grafana visualiza.
Abordagem para Extração de Skills via NLP
Usar Ollama com modelo local (Gemma 3 1B) com JSON Mode para extração estruturada de skills de textos em português. Oferece capacidade zero-shot learning sem necessidade de fine-tuning ou dataset anotado, com performance moderada (~5-15s por documento com GPU).
Configuração Centralizada no Módulo Shared
Centralizar todas as configurações do projeto em src/shared/config.py usando pydantic_settings.BaseSettings. Garante single source of truth para configurações e facilita mudanças de ambiente (dev, prod).
Strategy Pattern para Mapeamento de Prompts por Source
Implementar Strategy Pattern com um Registry para mapeamento de prompts por source de dados. Cada source tem sua própria estratégia de mapeamento registrada, permitindo extensibilidade sem violar Open/Closed Principle.
Modular Docker Compose com Include
Separar docker-compose.yml em módulos usando a diretiva include (v2.20.3+). Cada domínio mantém seu compose isolado e autocontido. Comando docker compose up funciona normalmente com múltiplos arquivos.
Integração ESCO-LLM em 4 Estágios
Dividir o processo de integração ESCO-LLM em 4 estágios bem definidos: (1) Injeção de taxonomia ESCO no prompt, (2) Extração de skills candidatos pelo LLM, (3) Vinculação a URIs ESCO, (4) Validação contra ontologia.
RabbitMQ DLQ e Padrão de Retry
Implementar Manual ACK com limite de 3 retries e Dead Letter Queue (DLQ). ACK manual garante que mensagem só é reconhecida após sucesso. Limite de 3 tentativas evita loops infinitos. DLQ preserva mensagens com erro para investigação.
Histórico de Processamento para Fine-Tuning
Criar coleção separada processed_history para armazenar contexto completo de cada processamento (prompt text, modelo usado, timestamp). Separação de responsabilidades entre dados operacionais e histórico para análise.
Reestruturação de Diretórios do Projeto
Reestruturar projeto em 3 camadas claramente separadas: domain/ (lógica de negócio), infra/ (conectores de infraestrutura), workers/ (entrypoints e Dockerfiles). Elimina imports circulares e dependências confusas.
Skill Post-Processing Pipeline
Implementar SkillProcessor como Stage 2.5 do pipeline (entre extração LLM e linking). Responsabilidades: reorganizar por tipo correto, filtrar não-skills, extrair position/company, validar contra ESCO, injetar reference_labels para linking.
Arquitetura de Microserviços Orientada a Eventos
Implementar Event-Driven Architecture (EDA) com microserviços Docker independentes. Usar apenas Events ao invés de Commands. Event Bus (RabbitMQ) como backbone, Padrão Publisher-Subscriber para desacoplamento total.
Filas Únicas por Domínio com Dispatch por Event Type
Usar uma fila por domínio (opportunities e resumes), onde cada serviço registra handlers para tipos de evento que processa. EventDispatcher roteia mensagens para handler correto. Reduz complexidade de infraestrutura de N eventos para 2 filas principais.
EventRouter com Routing Key via Decorator
Usar o padrão Decorator @EventRouter.route() para registrar routing keys em registry centralizado. Validações: (1) chave qualificada module.classname, (2) erro se houver colisão de nome ou routing key, (3) formato domain.entity.action.
Retry com Republish e Incremento de Contador
Implementar retry via ACK + Republish com header incrementado. A mensagem é reconhecida (ACK) e uma nova é publicada com x-retry-count incrementado. Permite incrementar contador sem requeue infinito.
Bindings Específicos por Serviço
Usar bindings específicos por serviço consumidor ao invés de wildcard único. Cada serviço recebe apenas eventos que declarou interesse. Evita "Competing Consumers" indesejados.
Pipeline de Processamento de Currículos
Estender processors existentes com handlers para currículos ao invés de criar serviços duplicados. Mesmo skill-extractor, entity-linker e graph-populator processam ambos os domínios.
Estratégia de Testes com Testcontainers
Implementar suite de testes com Testcontainers para provisionar containers de dependências (MongoDB, RabbitMQ, Neo4j) em tempo de execução. Garante rede de segurança real contra regressões.
MVP do Sistema de Recomendação via Cypher
Criar serviço recommender-cypher que consome dados do Neo4j e fornece recomendações via RabbitMQ. Usa Índice de Jaccard como métrica de matching. Single Cypher Query sem plugins complexos.
Implementação da API para Front-end
Implementar API REST com HTTPS/TLS, JWT para autenticação, validação rigorosa de entrada, proteção contra injeções, rate limiting e logging centralizado.
Arquitetura de Connectors e Persistência Centralizada
Migrar para connectors centralizados (connector-graph e connector-mongodb) via RabbitMQ. Padrão RPC over Message Queue para operações síncronas. Connectors "donos" dos drivers e da integridade dos dados.
Collective Entity Linking — Modelo Duplo Diamante
Substituir o entity linking estático por um Collective Entity Linking (CEL) em dois estágios. Diamante de Expansão: KNN semântico maximiza o recall de candidatos via embeddings. Diamante de Convergência: Personalized PageRank no Neo4j valida coerência topológica e maximiza a precisão.
Logger Singleton com Filtragem Transparente de Módulos
Refatorar o pacote de logging para uma classe Logger Singleton que garante configuração única do Loguru em todo o serviço. InterceptHandler preserva o record.name original de cada biblioteca, permitindo silenciamento granular via variável de ambiente LOG_LEVELS.
Refatoração do Loader para Múltiplos Domínios
Renomear loader-resumes-kaggle para loader-kaggle com escopo ampliado. Passa a publicar tanto eventos ResumeRawLoaded (currículos) quanto OpportunityScraped (oportunidades) a partir de fontes CSV do Kaggle.
Prompt Engineering Estruturado para SLMs
Adotar técnicas de prompt engineering baseadas em assistentes estado-da-arte: Role/Persona Setting, estruturação por XML Tags, Negative Constraints e enforcement de JSON nativo. Prompts e lógica de mapeamento ficam exclusivamente no processor-prompt-generator.
Deduplicação no Neo4j via Versionamento de Nós
Implementar merge_node aprimorado com identidade estável por profile_id e versão volátil por id. Ao detectar mudança de versão, relações HAS_SKILL são removidas atomicamente antes da atualização, garantindo grafo sempre consistente.
Ingestão da ESCO e Índice Vetorial no Neo4j
Usar o Neo4j Vector Index como solução unificada para grafo e vetores. Embeddings dos nós (:Skill) gerados via embeddinggemma no Ollama e indexados com HNSW (cosine similarity, 768 dimensões). Novo serviço loader-esco orquestra a ingestão.
Métrica de Avaliação — Jaccard Médio Global
Adotar a média dos percentuais médios de Jaccard por usuário como métrica de avaliação do sistema de recomendação. Calcula a similaridade entre skills recomendadas e skills do perfil para todos os currículos e extrai a média global.
Estratégia de Prefixo Tipado para Embeddings
Adotar o formato Raw merged newlines como template canônico: preferred_label\n description\n alt_labels. Elimina prefixos textuais em inglês ou português que introduzem ruído lexical, permitindo que o modelo opere sobre conteúdo semântico puro.
Otimização de Performance do Ollama
Avaliar configurações do Ollama com benchmark dedicado (scripts/ollama_benchmark.py) testando Gemma 3 1B em múltiplas configurações. Configuração 2 alcançou redução de ~50% no p95 de latência em relação à baseline.
Prompt Multi-Role e Few-Shot via Chat History
Reestruturar prompts com mensagens multi-role (system, user, assistant) e 2 exemplos few-shot por tipo de entidade no histórico de chat. Prompts migram para arquivos físicos em resources/ (Markdown para instrução, JSON para resposta esperada). Contexto ampliado para 8.192 tokens.
Especialização do Entity Linking por Tipo de Skill
Implementar Fuzzy Match com fallback para Duplo Diamante diretamente no processor-entity-linker. Skills "hard" passam primeiro por fuzzy match em memória via RapidFuzz (threshold 0.9). Se o score for insuficiente, a pipeline KNN+PPR é executada. Skills "soft" e "knowledge" vão direto ao Duplo Diamante.
Refinamento do Personalized PageRank no Entity Linking
Adotar novo fluxo: KNN → threshold de similaridade → pesos uniformes no Top-K → PPR → normalização PPR/PR → multiplicação por cosine similarity → ranking final. Equilibra proximidade semântica (KNN) com validação estrutural na ontologia ESCO (PPR).