Plataforma de OSINT e investigação para cruzamento de dados (pessoas, empresas, património, dívidas).
atalaia/
│
├── backend/ # FastAPI + Python
│ ├── app/
│ │ ├── main.py # App FastAPI + lifespan (startup/shutdown)
│ │ ├── core/
│ │ │ ├── config.py # Settings via Pydantic (env vars)
│ │ │ └── security.py # JWT auth, get_current_user
│ │ ├── api/
│ │ │ └── endpoints/
│ │ │ ├── auth.py # POST /auth/login, /auth/refresh
│ │ │ ├── search.py # GET /search?q=...
│ │ │ ├── entity.py # GET /entity/{id}, /entity/{id}/timeline
│ │ │ └── graph.py # GET /graph/{id}, POST /graph/expand/{id}
│ │ ├── db/
│ │ │ ├── neo4j.py # Driver Neo4j (async)
│ │ │ └── elasticsearch.py # Cliente Elasticsearch (async)
│ │ ├── models/ # Modelos ORM/ODM (se necessário)
│ │ ├── schemas/
│ │ │ ├── entity.py # Pydantic: EntityProfile360, GraphData, ...
│ │ │ └── search.py # Pydantic: SearchResponse, SearchResult
│ │ └── services/
│ │ ├── search_service.py # Lógica: query ES → resultados unificados
│ │ ├── entity_service.py # Lógica: perfil 360º do Neo4j
│ │ └── graph_service.py # Lógica: grafo Neo4j → formato Cytoscape.js
│ ├── tests/
│ └── requirements.txt
│
├── frontend/ # React + TypeScript + Tailwind CSS
│ └── src/
│ ├── App.tsx # Router + Providers
│ ├── components/
│ │ ├── ui/
│ │ │ ├── AppLayout.tsx # Layout raiz: Omnibar + Sidebar + Outlet
│ │ │ ├── Sidebar.tsx # Navegação lateral
│ │ │ └── LoadingSpinner.tsx
│ │ ├── search/
│ │ │ └── Omnibar.tsx # Barra de pesquisa global (⌘K)
│ │ ├── entity/
│ │ │ ├── EntityHeader.tsx # Cabeçalho 360º com tags visuais
│ │ │ ├── PatrimonioCard.tsx # Card reutilizável Ativo/Histórico
│ │ │ ├── EmpresasCard.tsx # Rede empresarial
│ │ │ ├── DividasCard.tsx # Passivo / dívidas
│ │ │ ├── ContactosCard.tsx # Contactos e morada
│ │ │ └── EntityTimeline.tsx # Linha do tempo cronológica
│ │ └── graph/
│ │ ├── GraphLegend.tsx # Legenda lateral do grafo
│ │ ├── GraphToolbar.tsx # Filtros (hops, histórico)
│ │ └── NodeDetailPanel.tsx # Painel do nó selecionado
│ ├── hooks/
│ │ ├── useSearch.ts # React Query: pesquisa global
│ │ ├── useEntity.ts # React Query: perfil 360º
│ │ └── useGraph.ts # React Query: dados de grafo
│ ├── pages/
│ │ ├── LoginPage.tsx
│ │ ├── SearchPage.tsx # Página inicial / resultados
│ │ ├── EntityPage.tsx # Ficha 360º
│ │ └── GraphPage.tsx # Visão de grafo (ecrã inteiro)
│ ├── services/
│ │ └── api.ts # Axios client configurado
│ ├── store/
│ │ └── AuthContext.tsx # Contexto de autenticação
│ ├── types/
│ │ └── index.ts # TypeScript interfaces
│ └── utils/
│ ├── cn.ts # clsx helper
│ └── format.ts # formatDate, formatCurrency
│
├── infra/
│ └── neo4j_schema.cypher # Schema completo: nós, relações, índices
│
├── docker-compose.yml # Neo4j + Elasticsearch + Backend + Frontend
└── README.md
| Camada | Tecnologia | Versão |
|---|---|---|
| Base de dados | Neo4j Community | 5.18 |
| Motor de pesquisa | Elasticsearch | 8.13 |
| Backend | FastAPI + Python | 3.11+ |
| Frontend | React + TypeScript + Vite | 18 / 5 |
| Styling | Tailwind CSS | 3.x |
| Grafo UI | Cytoscape.js | 3.x |
| Estado servidor | TanStack Query (React Query) | 5.x |
| Autenticação | JWT (python-jose) | — |
# 1. Subir infra (Neo4j + Elasticsearch)
docker compose up neo4j elasticsearch -d
# 2. Aguardar healthchecks (~30s) e aplicar schema
docker exec atalaia-neo4j cypher-shell -u neo4j -p password -f /import/schema.cypher
# 3. Backend
cd backend
pip install -r requirements.txt
uvicorn app.main:app --reload
# 4. Frontend (outro terminal)
cd frontend
npm install
npm run devOu tudo via Docker:
docker compose up --buildURLs:
- Frontend: http://localhost:3000
- Backend API: http://localhost:8000/docs
- Neo4j Browser: http://localhost:7474
- Kibana (debug):
docker compose --profile debug up kibana
| Label | Identificador único | Campos chave |
|---|---|---|
| Pessoa | nif |
nome, data_nascimento, morada, pep, tags |
| Empresa | nipc |
nome, forma_juridica, cae, capital_social |
| Veiculo | matricula |
marca, modelo, ano, vin, valor_estimado |
| Imovel | artigo_matricial |
tipo, morada, area_m2, valor_patrimonial |
| Divida | id (UUID) |
tipo, credor, valor, estado |
| Relação | De | Para | Propriedades chave |
|---|---|---|---|
PROPRIETARIO |
Pessoa / Empresa | Veiculo | percentagem, titulo, estado, datas |
PROPRIETARIO_IMOVEL |
Pessoa / Empresa | Imovel | percentagem, valor_aquisicao, datas |
EXERCE_CARGO |
Pessoa | Empresa | cargo, percentagem_capital, datas |
SOCIO |
Pessoa / Empresa | Empresa | percentagem, valor_nominal, datas |
TEM_DIVIDA |
Pessoa / Empresa | Divida | responsabilidade, datas |
RELACIONADO_COM |
Pessoa | Pessoa | tipo (Cônjuge, Filho, ...), datas |
RELACIONADA_COM |
Empresa | Empresa | tipo (Subsidiária, Holding), datas |
Regra de ouro: Relações com estado: 'Historico' nunca são apagadas.
São filtradas na API e representadas visualmente com linhas tracejadas no grafo.
GET /search?q={query}&tipo={tipo}&page=1&size=20
GET /entity/{id}?incluir_historico=true
GET /entity/{id}/timeline
GET /graph/{id}?hops=1&incluir_historico=true&tipos_relacao=PROPRIETARIO,EXERCE_CARGO
POST /graph/expand/{id} body: ["uuid1", "uuid2", ...] (nós já no canvas)
- Módulo de importação de dados (PDFs, CSVs, APIs externas)
- Sistema de alertas e watchlists
- Exportação de relatórios (PDF/DOCX)
- Controlo de acesso granular (RBAC) por entidade
- Auditoria completa de acessos
- Análise de rede avançada (PageRank, betweenness centrality)
- Modo de comparação lado-a-lado de entidades