feat: scaffold FastAPI intelligence-api with ClickHouse client, company/contact search endpoints

This commit is contained in:
Claude 2026-07-20 22:45:02 +02:00
parent c6c6de4787
commit 0b8a10481b
14 changed files with 152 additions and 0 deletions

12
.env.example Normal file
View file

@ -0,0 +1,12 @@
CLICKHOUSE_URL=
CLICKHOUSE_USER=default
CLICKHOUSE_PASSWORD=
CLICKHOUSE_DATABASE=intelligence
REDIS_URL=
RAW_DATA_PATH=/data/raw
PROCESSED_DATA_PATH=/data/processed
QDRANT_URL=
QDRANT_API_KEY=

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
__pycache__/
*.pyc
.venv/
.env
*.log

7
Dockerfile Normal file
View file

@ -0,0 +1,7 @@
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

View file

@ -1,2 +1,17 @@
# intelligence-api
FastAPI pentru Data-Intelligence: cautare companii/persoane, matching contacte, scoring, statistici — peste ClickHouse.
## Stack
FastAPI, Uvicorn, Pydantic, clickhouse-connect, Polars, DuckDB, PyArrow, psycopg, Redis, rapidfuzz, phonenumbers, email-validator, orjson.
## Dezvoltare
```bash
pip install -r requirements.txt
cp .env.example .env
uvicorn app.main:app --reload
```
`GET /health` returneaza statusul serviciului. `GET /docs` expune Swagger UI (automat din FastAPI).

0
app/__init__.py Normal file
View file

12
app/clickhouse.py Normal file
View file

@ -0,0 +1,12 @@
import clickhouse_connect
from app.config import settings
def get_client():
return clickhouse_connect.get_client(
dsn=settings.clickhouse_url,
username=settings.clickhouse_user,
password=settings.clickhouse_password,
database=settings.clickhouse_database,
)

15
app/config.py Normal file
View file

@ -0,0 +1,15 @@
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
clickhouse_url: str = ""
clickhouse_user: str = "default"
clickhouse_password: str = ""
clickhouse_database: str = "intelligence"
redis_url: str = ""
class Config:
env_file = ".env"
settings = Settings()

10
app/main.py Normal file
View file

@ -0,0 +1,10 @@
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
from app.routers import companies, contacts, health
app = FastAPI(title="Intelligence API", version="0.1.0", default_response_class=ORJSONResponse)
app.include_router(health.router)
app.include_router(companies.router)
app.include_router(contacts.router)

0
app/routers/__init__.py Normal file
View file

16
app/routers/companies.py Normal file
View file

@ -0,0 +1,16 @@
from fastapi import APIRouter
from app.clickhouse import get_client
from app.schemas import CompanySearchQuery
router = APIRouter(prefix="/companies", tags=["companies"])
@router.post("/search")
def search_companies(payload: CompanySearchQuery):
client = get_client()
result = client.query(
"SELECT name, domain, country FROM companies WHERE name ILIKE {q:String} LIMIT {limit:UInt32}",
parameters={"q": f"%{payload.query}%", "limit": payload.limit},
)
return {"results": result.result_rows}

25
app/routers/contacts.py Normal file
View file

@ -0,0 +1,25 @@
from fastapi import APIRouter
from rapidfuzz import fuzz
from app.clickhouse import get_client
from app.schemas import ContactMatchQuery
router = APIRouter(prefix="/contacts", tags=["contacts"])
@router.post("/match")
def match_contact(payload: ContactMatchQuery):
"""Placeholder pentru entity resolution: cauta dupa email/telefon normalizat,
apoi scoreaza candidatii cu rapidfuzz pentru potriviri apropiate."""
client = get_client()
if payload.email:
result = client.query(
"SELECT * FROM contacts WHERE email = {email:String} LIMIT 5",
parameters={"email": payload.email},
)
return {"matches": result.result_rows}
return {"matches": []}
def score_name_similarity(a: str, b: str) -> float:
return fuzz.token_sort_ratio(a, b)

10
app/routers/health.py Normal file
View file

@ -0,0 +1,10 @@
from datetime import datetime, timezone
from fastapi import APIRouter
router = APIRouter()
@router.get("/health")
def health():
return {"status": "ok", "service": "intelligence-api", "timestamp": datetime.now(timezone.utc).isoformat()}

11
app/schemas.py Normal file
View file

@ -0,0 +1,11 @@
from pydantic import BaseModel, EmailStr
class CompanySearchQuery(BaseModel):
query: str
limit: int = 20
class ContactMatchQuery(BaseModel):
email: EmailStr | None = None
phone: str | None = None

14
requirements.txt Normal file
View file

@ -0,0 +1,14 @@
fastapi==0.115.5
uvicorn[standard]==0.32.1
pydantic==2.10.2
pydantic-settings==2.6.1
clickhouse-connect==0.8.7
polars==1.15.0
duckdb==1.1.3
pyarrow==18.0.0
psycopg[binary]==3.2.3
redis==5.2.0
rapidfuzz==3.10.1
phonenumbers==8.13.50
email-validator==2.2.0
orjson==3.10.12