gitbuyer_

bitfent/cryptocuz

privatemain

README

CryptoCuz — 8h crypto digest API

Stato: pre-alpha 0.0.0.1 — prima implementazione della SPEC (SPEC_crypto_8h_digest_api.md) e dei piani BUILD_01–07. Un endpoint a chiamata singola che, dato un argomento crypto, restituisce un briefing delle ultime 8 ore: cosa è successo, chi lo sta dicendo, quanto conta, con fonti e livello di confidenza.

Avvio in locale

uv venv --python 3.12 .venv
uv pip install -e '.[dev]'
cp .env.example .env          # compilare le chiavi che si hanno
.venv/bin/uvicorn app.main:app --port 8000

Senza alcuna chiave il servizio parte comunque (V10): x_social disabilitato e dichiarato, store in-memory (solo dev), e la sintesi risponde 503 onesto finché manca DEEPSEEK_API_KEY.

curl -s localhost:8000/health
curl -s -X POST localhost:8000/v1/digest -H 'Content-Type: application/json' \
     -d '{"topic":"hyperliquid","depth":"standard"}'

Test e lint:

.venv/bin/python -m pytest        # 239 test, offline, niente rete (i @live sono esclusi)
.venv/bin/ruff check .

Deploy su Render (web service nativo, senza Docker e senza blueprint)

Il servizio si deploya come Web Service con runtime Python, configurato a mano in dashboard. Niente render.yaml: il deploy resta un gesto esplicito.

Campo Valore
Runtime Python 3
Build command pip install -r requirements.txt
Start command uvicorn app.main:app --host 0.0.0.0 --port $PORT
Health check path /health

Due file nel repo servono al deploy e a nient'altro:

  • .python-version (3.12) — Render dal 11/02/2026 crea i servizi con Python 3.14; lo stack è validato su 3.12 e il pin evita che l'interprete cambi sotto i piedi al primo deploy. Alzarlo è una modifica deliberata, con suite verde.
  • requirements.txt — lock delle versioni esatte con cui la suite gira verde. pyproject.toml resta la fonte dei range ammessi; qui c'è la combinazione effettivamente provata. Il pacchetto non serve installarlo: uvicorn mette la working dir in testa a sys.path, quindi app/, config/ e registry/ si risolvono dal checkout.

Variabili d'ambiente (i segreti si compilano in dashboard, mai nel repo):

ENV=prod
RELAY_SHARED_SECRET=<segreto condiviso col relay>   # oppure API_KEYS
SUPABASE_DB_URL=<connection string del pooler, porta 6543>
DEEPSEEK_API_KEY=<chiave>
SYNTH_MODEL=deepseek-v4-flash
X_BEARER_TOKEN=<opzionale: vuoto = collector X spento e costo social azzerato>
X_DAILY_CAP_USD=<soffitto giornaliero, default 15>
SCHEDULER_ENABLED=false

Tre cose che valgono la pena sapere prima di accendere:

  1. ENV=prod è fail-closed, e va tenuto così. Senza RELAY_SHARED_SECRET (o API_KEYS) il servizio si rifiuta di partire (app/auth.py), e lo stesso senza SUPABASE_DB_URL (app/storage.py). Non aggirare l'errore mettendo ENV=dev: in quel caso l'open_mode autentica chiunque, e ogni chiamata all'endpoint costa lettura X reale.
  2. SCHEDULER_ENABLED=false finché l'advisory lock non è sistemato. PostgresStorage.run_lock assume che pg_try_advisory_lock sia di sessione, ma il pooler Supabase in transaction mode (6543) multiplexa più connessioni sullo stesso backend: due chiamanti ottengono entrambi il lock (è rientrante) e l'unlock singolo lascia il refcount a 1. L'esclusione «una sola esecuzione per orario» non vale (vedi BUILD_06 P4/D-INF-7, da rifare come lease su tabella).
  3. La connection string deve essere quella del pooler (6543). L'host diretto db.<ref>.supabase.co:5432 è solo IPv6 e da Render non si risolve. statement_cache_size=0 è già gestito nel codice (D-INF-2).

Le migration si applicano prima del primo avvio, da locale, puntando allo stesso DB: python scripts/migrate.py (idempotente).

Architettura (SPEC §4)

POST /v1/digest → API layer (auth X-Relay-Key · rate limit · cache 300s)
               → Orchestrator: plan → collect → normalize → dedupe → score
                              → select → synthesize (DeepSeek) → verify → assemble
               → DigestResponse (contratto congelato SPEC §5)
  • app/ — API layer (BUILD_01): contratto Pydantic, vocabolario errori chiuso {200, 401, 422, 429, 503, 504}, cache, token bucket, request log.
  • agent/ — pipeline (BUILD_02): 9 step puri e testabili; verify() con i 5 guardrail (citazioni, URL, advice, finestra, numeri); confidence deterministica.
  • collectors/ — adapter fonti (BUILD_03): news_rss, coingecko, defillama attivi; x_social dietro provider astratto, disabilitato senza X_BEARER_TOKEN (assunzione aperta SPEC §17.2); MockCollector con i 6 scenari d'eval.
  • llm/ — client DeepSeek via SDK openai (BUILD_04): validated_call (1 retry trasporto + 1 retry semantico, tetto 3 richieste), parsing difensivo, contabilità token/costo.
  • registry/ — aliases.yaml (risoluzione progetti→handle/coingecko/DeFiLlama, config di repo) e RUBRIC.md (criteri). Il seed del registry NON è più qui: vive su Supabase (seed_accounts + registry_features.vote), popolato dall'app di seeding e letto con SeedRegistry.from_storage. La curation resta lavoro umano (quali handle + provenance).
  • db/migrations/ — schema Supabase/Postgres (BUILD_06 §3.1) + funzione take_token.

Come aggiungere un collector

  1. Nuovo modulo in collectors/ sullo stampo di base.BaseCollector (enabled(), _build_request, _parse, fetch, ≤ max_upstream_calls).
  2. Sezione in config/sources.yaml (+ depth_map).
  3. Registrarlo in collectors.base.build_collectors.
  4. Test: dialetto + mapping su risposta registrata + invarianti di categoria (lista vuota ≠ errore; mai timestamp inventati).

Come aggiornare il registry

Il seed vive su Supabase (non più in un YAML di repo): la curation la fa l'app di seeding (Doc B), il servizio lo legge a ogni avvio con SeedRegistry.from_storage e la meta.registry_version diventa db-<n>. Sanity check del seed caricato: python scripts/registry_tool.py lint. I criteri sono in registry/RUBRIC.md.

Dove si costruisce e si mantiene il registry (due flussi, due repo)

Il Notability Registry vive in un DB Supabase condiviso e si costruisce in due flussi distinti:

  • Aggiornamento incrementale — questo repo. A ogni chiamata all'API di X il registro si aggiorna con dati più freschi e registra i nuovi candidati; il servizio ne aggiorna score e tier a regime. Guida operativa: docs/registry_A_aggiornamento_incrementale.md.
  • Seeding iniziale — repo separato di data-prep. Popolamento della prima versione delle tabelle da una lista di handle, arricchita coi dati dell'API di X. Documento autosufficiente: docs/registry_B_seeding_iniziale.md.

Il voto di notabilità si costruisce col profiling ricco (7 segnali da analista, con un passo LLM di classificazione): il seeding ne calcola la parte da profilo (voto iniziale), questo repo lo fa evolvere coi segnali che richiedono storia. Disegno completo nei due doc sopra.

Cosa NON c'è ancora in questa pre-alpha (dichiarato)

  • Backend Postgres: la suite di default gira sul fallback in-memory, ma il percorso asyncpg è ora esercitato anche contro Supabase reale — schema applicato (scripts/migrate.py, migrazioni 001–004), seed dei 675 account letto da seed_accounts+registry_features, e volano (registry_record_run) verificato end-to-end. Suite di integrazione opt-in: pytest -m integration.
  • Layer appreso del registry (BUILD_05 A3–A6): motore di consolidamento e scheduler esistono e le tabelle sono su Supabase, ma non hanno ancora accumulato storia reale da run live.
  • Collector dex_activity e governance/github (differibili per D-COL-7/8).
  • Eval harness completo (evals/, BUILD_02 P10): i casi bloccanti (topic morto, injection, astroturfing, fonte giù) sono già test CI in tests/.
  • Probe di latenza DeepSeek (BUILD_04 P6) e worksheet dei costi (BUILD_07): richiedono chiave reale e vanno fatti prima del listing.
  • Metriche Prometheus: per ora contatori JSON su /internal/metrics dietro flag.
  • Listing su datapoint.market (BUILD_07 P5): richiede stop umano esplicito, per design.

Assunzioni aperte (SPEC §17 — decidere prima del listing)

Wallet Base (pay_to) · prezzo e budget mensile · dominio HTTPS dell'origin (immutabile dopo il listing, BUILD_07 D6).

Chiuse il 19/07/2026 — fonte dati X: tier pay-per-use attivo ($0,005/post letto, cap 2M/mese; Basic e Pro sono chiusi ai nuovi signup). Costo per digest: $0,15 quick · $0,30 standard · $0,50 deep. Campi della scheda account verificati con chiamata reale (scripts/smoke_x_fields.py): affiliation disponibile, verified_followers_count non servito dal tier — rimosso da USER_FIELDS, segnale anti-bot ricostruito da authenticity_proxy() (vedi registry/RUBRIC.md). Dettaglio in docs/TDAT-001-attivazione-x-api.md; piano aggiornato in cryptocuz-piano-produzione-v2.md.

Target del registry — chiuso il 19/07/2026: 150 account seed curati, di cui 50 eval_split: holdout mai usati per la taratura del motore. Non è un numero tondo ma il dimensionamento dell'eval set (BUILD_05 D-REG-10): sotto 100 etichette il gate G-reg-3 non discrimina un motore all'80% da uno al 70%. Propagato in SPEC §16, DESIGN, BUILD_05 ×5, BUILD_07 ×2, README, registry/RUBRIC.md. Contraddizione V4/D-REG-2 risolta.

✅ Residuo tecnico chiuso (19/07/2026): agent/notability.py legge e valida eval_split (obbligatorio, {tune|holdout}, chiave mancante = errore duro), AccountInfo lo espone e SeedRegistry.accounts_by_split() dà l'accesso per split. L'assegnazione è ancorata alla funzione canonica eval_split_for(handle) (hash sha1 mod 3, ~1/3 holdout) — non a discrezione di chi cura: from_storage DERIVA lo split dall'hash a runtime, a prova

… README truncated here; the full file ships with the clone.

119 files

file contents open where the seller allows a preview; the rest is what you are buying

agent/20
app/14
collectors/8
config/7
db/6
docs/2
llm/8
out/2
registry/2
scripts/6
tests/25