phase B (2/2): CSV upload endpoint + drag-drop UI
Completes Phase B. The full alternative-onboarding flow is now end-to-end: drop a T212 pie CSV → parser → InstrumentMap resolver → PortfolioSnapshot + Position rows, all without ever asking the user for broker credentials. - persist_pie() in app/services/csv_import.py: takes a ParsedPie, resolves each Slice via InstrumentMap, writes Portfolio + Snapshot + Position rows. Unmapped slices are still persisted using their CSV values and surfaced in the response for the UI to warn about. - POST /api/portfolios/upload: multipart endpoint accepting CSV file + optional portfolio_name + currency. 2 MiB cap. Returns import summary. - /upload page with drag-drop dropzone, file input fallback, and inline result panel showing invested/value/result + unmapped-slice warnings. - New "Import" link in the header nav. Verified end-to-end against the real T212 export: all 13 positions land with correct T212 tickers (incl. FPp_EQ for the Paris TotalEnergies listing the heuristic resolver picks), zero unmapped slices, totals reconcile to the penny. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
16e9f5f0cc
commit
8a155ef157
6 changed files with 439 additions and 12 deletions
|
|
@ -33,6 +33,12 @@ async def news_page(request: Request):
|
|||
return templates.TemplateResponse(request, "news.html", {})
|
||||
|
||||
|
||||
@router.get("/upload", response_class=HTMLResponse)
|
||||
async def upload_page(request: Request):
|
||||
"""Drag-drop CSV import. Posts to /api/portfolios/upload."""
|
||||
return templates.TemplateResponse(request, "upload.html", {})
|
||||
|
||||
|
||||
async def _resolve_log_date(session: AsyncSession, day: str | None) -> date:
|
||||
"""If `day` is YYYY-MM-DD use it; else fall back to the date of the most
|
||||
recent generated log; else today."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue