From 8bbfe88a304d2ea3e5a6d87ad9918bf7d506617f Mon Sep 17 00:00:00 2001 From: ALI YESILKAYA Date: Tue, 30 Jun 2026 11:00:29 +0200 Subject: [PATCH] =?UTF-8?q?feat(finlab):=20import=20de=20relev=C3=A9=20par?= =?UTF-8?q?=20image=20(upload=20dashboard=20=E2=86=92=20scan=20Console=20I?= =?UTF-8?q?A)=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Process dédié pour actualiser le portefeuille à partir d'une capture de relevé : - Dashboard : bouton « 📥 Importer un relevé » (panneau Mes comptes) → upload de l'image vers le workspace (imports/) via POST /api/import ; une modale donne la phrase exacte à dire à la console + un lien direct - Backend : endpoints /api/import (UploadFile, refuse les non-images) et /api/imports (listing), dossier imports/ sous FINLAB_HOME (workspace, persistant) - Persona console (CLAUDE.md) : workflow « Import d'un relevé » — lire l'image, extraire compte/positions, mapper vers tickers Yahoo en VÉRIFIANT les cours, confirmer, puis mettre à jour portfolio.yaml Le scan vision se fait dans la Console IA (abonnement, pas de clé API). Le dashboard n'est que le pont d'upload. Testé : POST image→200 + listing OK, non-image→400 ; JS node --check OK. Co-authored-by: Claude Opus 4.8 --- tools/finlab/dashboard/server.py | 31 +++++++++++++++++++- tools/finlab/dashboard/static/index.html | 36 +++++++++++++++++++++++- tools/finlab/workspace/CLAUDE.md | 24 ++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/tools/finlab/dashboard/server.py b/tools/finlab/dashboard/server.py index 06706c8..955a366 100644 --- a/tools/finlab/dashboard/server.py +++ b/tools/finlab/dashboard/server.py @@ -8,12 +8,13 @@ Lancement : uvicorn dashboard.server:app --host 0.0.0.0 --port 8800 """ from __future__ import annotations +import datetime as dt import math from pathlib import Path import pandas as pd import yaml -from fastapi import FastAPI, Request +from fastapi import FastAPI, File, Request, UploadFile from fastapi.responses import FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles @@ -167,6 +168,34 @@ def api_plan(ticker: str, capital: float = 1427.0): return _clean({"plan": p, "render": plan_mod.render(p)}) +# ── Import de relevés (image → analysée par la Console IA) ───────────────────── +IMPORTS_DIR = data.ROOT / "imports" +_IMG_EXT = {".png", ".jpg", ".jpeg", ".webp", ".gif"} + + +@app.post("/api/import") +async def api_import(file: UploadFile = File(...)): + """Enregistre une capture de relevé dans le workspace (imports/) ; la Console IA la scanne + ensuite pour mettre à jour portfolio.yaml (vision côté abonnement, pas de clé API ici).""" + ext = Path(file.filename or "").suffix.lower() + if ext not in _IMG_EXT: + return JSONResponse(status_code=400, content={"error": f"format image non supporté: {ext or '?'}"}) + IMPORTS_DIR.mkdir(parents=True, exist_ok=True) + ts = dt.datetime.now().strftime("%Y%m%d-%H%M%S") + name = f"releve-{ts}{ext}" + (IMPORTS_DIR / name).write_bytes(await file.read()) + return {"saved": name, "rel": f"imports/{name}"} + + +@app.get("/api/imports") +def api_imports(): + if not IMPORTS_DIR.exists(): + return {"imports": []} + files = sorted((f for f in IMPORTS_DIR.iterdir() if f.suffix.lower() in _IMG_EXT), + key=lambda f: f.stat().st_mtime, reverse=True) + return {"imports": [f.name for f in files]} + + # ── Statique ────────────────────────────────────────────────────────────────── @app.get("/") def index(): diff --git a/tools/finlab/dashboard/static/index.html b/tools/finlab/dashboard/static/index.html index 4d1342b..84d4ea6 100644 --- a/tools/finlab/dashboard/static/index.html +++ b/tools/finlab/dashboard/static/index.html @@ -101,7 +101,10 @@
-

Mes comptes

+

Mes comptes + +

+
Chargement…
@@ -190,6 +193,17 @@
+ + +