// BACKEND · 4 PAȘI

Arhitectura tehnică

FastAPI → Redis Streams → workeri → Triton (ViT) → PostgreSQL + audit on-chain.

Înlocuiește scan_store din memorie cu SQLAlchemy async + cache Redis pentru rezultate (TTL 1h).

models/db.py
from sqlalchemy import String, Float, DateTime, Text
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from datetime import datetime, timezone
import os

DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://elpida:elpida@localhost:5432/elpida")
engine = create_async_engine(DATABASE_URL, pool_size=20, max_overflow=40, pool_pre_ping=True)
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)

class Base(DeclarativeBase): pass

class ScanRecord(Base):
    __tablename__ = "scans"
    scan_id: Mapped[str] = mapped_column(String, primary_key=True)
    file_hash: Mapped[str] = mapped_column(String(64), index=True)
    verdict: Mapped[str] = mapped_column(String(16))
    confidence: Mapped[float] = mapped_column(Float)
    family: Mapped[str | None] = mapped_column(String, nullable=True)
    detection_methods: Mapped[str] = mapped_column(Text)   # JSON
    llm_explanation: Mapped[str | None] = mapped_column(Text, nullable=True)
    blockchain_tx: Mapped[str | None] = mapped_column(String(66), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True),
        default=lambda: datetime.now(timezone.utc))

async def init_db():
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
storage.py
import json, redis.asyncio as redis, os
from models.db import SessionLocal, ScanRecord

r = redis.from_url(os.getenv("REDIS_URL", "redis://localhost:6379/0"), decode_responses=True)

async def save_scan(result: dict):
    async with SessionLocal() as db:
        db.add(ScanRecord(**{**result,
            "detection_methods": json.dumps(result["detection_methods"])}))
        await db.commit()
    await r.set(f"scan:{result['scan_id']}", json.dumps(result, default=str), ex=3600)

async def get_scan(scan_id: str) -> dict | None:
    if cached := await r.get(f"scan:{scan_id}"):
        return json.loads(cached)
    async with SessionLocal() as db:
        rec = await db.get(ScanRecord, scan_id)
        if not rec: return None
        data = {c.name: getattr(rec, c.name) for c in rec.__table__.columns}
        data["detection_methods"] = json.loads(rec.detection_methods)
        data["created_at"] = rec.created_at.isoformat()
        await r.set(f"scan:{scan_id}", json.dumps(data), ex=3600)
        return data
main.py (extras)
@app.on_event("startup")
async def startup(): await init_db()

@app.get("/api/v1/scan/{scan_id}")
async def get_scan_result(scan_id: str):
    data = await get_scan(scan_id)
    if not data: raise HTTPException(404, "Scan not found")
    return data

Rulare completă

docker-compose.yml
services:
  api:      { build: ., command: uvicorn main:app --host 0.0.0.0, ports: ["8080:8000"], env_file: .env, depends_on: [db, redis, triton] }
  worker:   { build: ., command: python worker.py, env_file: .env, deploy: { replicas: 3 } }
  db:       { image: postgres:16, environment: { POSTGRES_USER: elpida, POSTGRES_PASSWORD: elpida, POSTGRES_DB: elpida } }
  redis:    { image: redis:7, command: redis-server --appendonly yes }
  hardhat:  { build: ./chain, command: npx hardhat node, ports: ["8545:8545"] }
  triton:   { image: nvcr.io/nvidia/tritonserver:24.08-py3, command: tritonserver --model-repository=/models, volumes: ["./model_repository:/models"] }