mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 07:44:42 +02:00
feat(finlab): import déterministe d'un relevé de compte Revolut → positions (#72)
Procédure de mise à jour du portefeuille « à chaque trade » à partir des relevés Revolut, sans IA (exact). - finlab/revolut.py : reconstruit les positions depuis le « trading account statement » (toutes transactions) par COÛT MOYEN (achats − ventes, PRU pondéré), mappe les tickers Revolut→Yahoo (ASME→ASML.AS, TOTB→TTE.PA, BNP→BNP.PA, AXA→CS.PA, FTE→ORA.PA, AIR1→AI.PA) et vérifie les cours. Idempotent, ne touche qu'un compte, préserve les autres comptes/cash/commentaires (round-trip ruamel.yaml) - CLI : python -m finlab.cli import-revolut <csv> [--write] [--account] - Dashboard : le bouton « Importer un relevé » accepte les CSV → endpoints /api/revolut/preview (aperçu) + /api/revolut/apply (écrit), avec garde anti-évasion de chemin ; le front affiche les positions + bouton « Appliquer » - portfolio.yaml : compte Revolut reconstruit depuis l'historique complet → ajoute AMAT (achetée le 29/06, manquait) ; valeurs alignées au centime - requirements : + ruamel.yaml ; persona console + admin/ia/finlab.md documentés Validé sur l'historique réel : 11 positions reconstruites = portfolio.yaml existant (cours vérifiés, 0 warning) + AMAT. Flux dashboard upload→preview→apply testé (TestClient), JS OK. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1c4a9d647e
commit
9d55cc803d
8 changed files with 324 additions and 29 deletions
|
|
@ -108,9 +108,9 @@
|
|||
<div>
|
||||
<div class="panel">
|
||||
<h3>Mes comptes <span class="muted" id="pfCount"></span>
|
||||
<button class="btn perbtn" style="font-size:11px" title="Importer une capture de relevé (la Console IA la scanne)" onclick="document.getElementById('importFile').click()">📥 Importer un relevé</button>
|
||||
<button class="btn perbtn" style="font-size:11px" title="Image (scannée par l'IA) ou CSV Revolut (import déterministe)" onclick="document.getElementById('importFile').click()">📥 Importer un relevé</button>
|
||||
</h3>
|
||||
<input type="file" id="importFile" accept="image/*" style="display:none" onchange="uploadImport(this.files[0])">
|
||||
<input type="file" id="importFile" accept="image/*,.csv" style="display:none" onchange="uploadImport(this.files[0])">
|
||||
<div class="acct-tabs" id="acctTabs"></div>
|
||||
<div id="pfList"><div class="loading">Chargement…</div></div>
|
||||
</div>
|
||||
|
|
@ -439,7 +439,8 @@ async function uploadImport(f){
|
|||
const r=await fetch('/api/import',{method:'POST',body:fd});
|
||||
const j=await r.json();
|
||||
if(j.error) throw new Error(j.error);
|
||||
out.innerHTML = `<p style="color:var(--up)">✅ Relevé enregistré : <code>${j.rel}</code></p>
|
||||
if(j.kind==='csv') return revolutPreview(j.rel); // relevé Revolut → import déterministe
|
||||
out.innerHTML = `<p style="color:var(--up)">✅ Capture enregistrée : <code>${j.rel}</code></p>
|
||||
<p>Ouvre la <b>Console IA</b> et demande :</p>
|
||||
<pre>importe le relevé ${j.rel} et mets à jour mon portefeuille</pre>
|
||||
<p class="muted" style="font-size:12px">Je lirai l'image, je mapperai chaque ligne vers son ticker Yahoo (en vérifiant le cours), puis je mettrai à jour <code>portfolio.yaml</code> <b>après ta confirmation</b>.</p>
|
||||
|
|
@ -447,6 +448,33 @@ async function uploadImport(f){
|
|||
}catch(e){ out.innerHTML='<div class="err">Erreur: '+e.message+'</div>'; }
|
||||
finally{ inp.value=''; }
|
||||
}
|
||||
// Relevé de compte Revolut (CSV) : aperçu déterministe des positions, puis « Appliquer ».
|
||||
async function revolutPreview(rel){
|
||||
const out=document.getElementById('importOut');
|
||||
out.innerHTML='<div class="loading">Reconstruction des positions Revolut…</div>';
|
||||
try{
|
||||
const d=await api('/api/revolut/preview?file='+encodeURIComponent(rel));
|
||||
out.innerHTML = `<p style="color:var(--up)">📄 Relevé Revolut — <b>${d.positions.length} positions</b> reconstruites (compte « ${d.account} ») :</p>`
|
||||
+ '<table><thead><tr><th>Ticker</th><th>Quantité</th><th>PRU</th><th>Dev</th></tr></thead><tbody>'
|
||||
+ d.positions.map(p=>`<tr><td class="t">${p.ticker}</td><td class="mono">${p.shares}</td><td class="mono">${p.avg_price}</td><td>${p.avg_currency}</td></tr>`).join('')
|
||||
+ '</tbody></table>'
|
||||
+ (d.warnings.length?`<div class="err" style="margin-top:6px">⚠ ${d.warnings.join('<br>⚠ ')}</div>`:'')
|
||||
+ `<div class="controls" style="margin-top:12px">
|
||||
<button class="btn acc" onclick="revolutApply('${rel}')">✅ Appliquer au compte Revolut</button>
|
||||
</div>
|
||||
<p class="muted" style="font-size:11px">Remplace les positions du compte Revolut dans portfolio.yaml (cash + autres comptes préservés). Coût moyen. Re-déposer un relevé à jour à chaque trade.</p>`;
|
||||
}catch(e){ out.innerHTML='<div class="err">Erreur: '+e.message+'</div>'; }
|
||||
}
|
||||
async function revolutApply(rel){
|
||||
const out=document.getElementById('importOut');
|
||||
out.innerHTML='<div class="loading">Mise à jour de portfolio.yaml…</div>';
|
||||
try{
|
||||
const r=await fetch('/api/revolut/apply?file='+encodeURIComponent(rel),{method:'POST'});
|
||||
const d=await r.json(); if(d.error) throw new Error(d.error);
|
||||
out.innerHTML=`<p style="color:var(--up)">✅ Compte Revolut mis à jour (${d.positions.length} positions). Le dashboard se recharge…</p>`;
|
||||
setTimeout(()=>{ document.getElementById('importModal').classList.remove('on'); loadPortfolio(); }, 1200);
|
||||
}catch(e){ out.innerHTML='<div class="err">Erreur: '+e.message+'</div>'; }
|
||||
}
|
||||
|
||||
// ── Actions rapides (finlab pur, one-click) ────────────────
|
||||
const escapeHtml = s => (s||'').replace(/[&<>]/g,c=>({'&':'&','<':'<','>':'>'}[c]));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue