# mutual-funds-personal A local, private mutual-fund portfolio tracker for Indian investors. Pulls your CAS (Consolidated Account Statement) from CAMS by email, parses it, and serves a Streamlit dashboard with holdings, gains, allocation, and tax info โ€” entirely on your machine, no data ever leaves. --- > ### ๐Ÿงช Try the live demo > > **[finance.twounderthesky.com](https://finance.twounderthesky.com)** โ€” login `demo@example.com` / `demo1234` > > All data shown is fictional. The demo serves a hand-crafted sample CAS for investor "Baburao Ganpatrao Apte" with 6 schemes across Equity / Debt / Multi-Asset / Foreign, two active SIPs across two PPFAS folios, an HDFC Liquid โ†’ HDFC Mid Cap STP, and a current-FY LTCG redemption โ€” enough to exercise every dashboard surface (filters, allocation donut, scheme detail pages, redemption calculator, SIPs & STPs tab). The Refresh-CAS / Process-inbox buttons are simulated on the demo path; no real CAMS or Gmail traffic is generated. --- ## What you get - Auto-fetch the latest CAS PDF from your Gmail (encrypted at rest on disk) - Holdings, current value, and unrealised gains using live AMFI NAV - **XIRR** per scheme and combined portfolio XIRR - Allocation donut by asset class / sub-category - **Equity LTCG FY tracker** โ€” realised gain this FY + room left under the โ‚น1.25L exemption - Per-scheme redemption calculator (LTCG / STCG split, lot-by-lot FIFO breakdown) - Per-folio holder names, scheme detail pages with persistent URLs - **Multi-user**: admin invites others by token; each user owns one CAS account - **View multiple accounts in one place** โ€” switch between your own and any linked accounts (e.g. spouse, parents) from a sidebar dropdown; unlink anytime - Everything encrypted under each user's login password โ€” disk theft alone gets you nothing ## Setup Requires Python 3.9+, a Gmail account, and a Gmail [App Password](https://myaccount.google.com/apppasswords). ```bash git clone https://github.com//mutual-funds-personal.git cd mutual-funds-personal python3 -m venv .venv && source .venv/bin/activate pip install -r requirements.txt playwright install chromium # ~150 MB; needed for the CAS request flow cp config.example.yaml config.yaml # edit config.yaml โ€” set admin_email to your Gmail ``` ## Running You need **both** processes running side-by-side โ€” the auth gateway can't render the dashboard without the Streamlit backend, and Streamlit can't authenticate users without the gateway. ```bash # terminal 1: Streamlit dashboard backend (port 8501) streamlit run ui/app.py # terminal 2: FastAPI auth gateway (port 8000 โ€” this is what you'll visit) uvicorn auth_server.main:app --host 0.0.0.0 --port 8000 ``` Then open **http://localhost:8000** (use `0.0.0.0` so phones/laptops on the same Wi-Fi can reach it too). On first launch you'll be guided through: 1. **Admin signup** โ€” the email you set as `admin_email` in `config.yaml` becomes the only account that can invite others. 2. **Setup** โ€” Gmail App Password + a CAS PDF password. 3. **First CAS fetch** โ€” the form is submitted to CAMS automatically and the dashboard polls Gmail until the statement lands. To invite a second user (e.g. spouse), open **โš™๏ธ Settings โ†’ ๐Ÿ‘ฅ Invite a user** in the sidebar โ€” share the one-time link out of band. > You never visit `:8501` directly. The dashboard reads its session from a signed header the gateway injects on every proxied request; direct access shows an error. Streamlit's port / XSRF / CORS settings are baked into `.streamlit/config.toml` so the command stays simple. ### Run with Docker Instead of the venv + two-terminal setup above, you can run everything in one container โ€” it bundles Python + Chromium, so no separate `pip install` / `playwright install` step: ```bash cp config.example.yaml config.yaml # set admin_email docker compose up --build ``` Open **http://localhost:8000**. The entrypoint launches both the Streamlit backend and the auth gateway behind an Xvfb virtual display, so the headful CAMS scrape still clears reCAPTCHA. `data/` and `config.yaml` are mounted from the host and survive rebuilds; debug captures go to a named volume to keep the repo clean. ### Seeding a local demo account If you're publicly hosting your instance and want a "kick the tyres" account visitors can use without seeing real data, run: ```bash python scripts/seed_demo_account.py ``` This creates `demo@example.com` / `demo1234` (non-admin), writes a hand-crafted dummy CAS to `data/accounts/demo_example_com/`, and the dashboard automatically shows a "this is fictional" banner whenever the demo is the active account. The Refresh-CAS / Process-inbox buttons are stubbed on this path so visitors don't trigger real CAMS or Gmail traffic. Idempotent โ€” re-run any time to refresh the sample data. ## How it works | Layer | What it does | |---|---| | `auth_server/` | FastAPI auth gateway: login, signup, invite, setup, change-password, delete-account, migration. HTTP-only signed cookies. Reverse-proxies authenticated requests to Streamlit | | `ui/` | Streamlit dashboard split into focused modules: `app.py` (orchestrator), `sidebar.py`, `scheme_card.py`, `scheme_detail.py`, `cas_workflow.py`, `donut.py`, `format.py`, `query.py`, `auth_glue.py` | | `ingest/cams_request.py` | Fills the CAMS Mailback form via Playwright | | `ingest/gmail_fetch.py` | Pulls the CAS PDF reply from Gmail over IMAP and writes it encrypted to disk | | `analytics/auth.py` | Argon2id login, KEK derivation, invite + linking, account CRUD | | `analytics/crypto.py` | Argon2id KDF, Fernet wrap/unwrap for data keys + CAS PDFs + parse cache | | `analytics/db.py` | SQLite store: users, cas_accounts, account_access, invites | | `analytics/portfolio.py` | Parses the CAS PDF, computes positions, XIRR, AMFI NAV overlay | | `analytics/tax.py` | FIFO lot tracking, LTCG / STCG bucketing, FY exemption math | All artefacts (encrypted PDFs, parsed data, SQLite store, server secret) live under `data/` โ€” gitignored, never synced. Debug captures under `debug/` are also gitignored. ## License Copyright ยฉ 2026 Kesha Shah. Licensed under the [PolyForm Noncommercial License 1.0.0](LICENSE). **TL;DR:** - โœ… Run it for yourself, your family, your hobby project - โœ… Fork it, modify it, send pull requests, redistribute changes - โœ… Use it for education, research, or in a non-profit / charity / government context - โŒ Use it in any product, service, or workflow tied to revenue โ€” commercial use of any kind requires a separate license The full canonical text is in [LICENSE](LICENSE); see [polyformproject.org/licenses/noncommercial/1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0/) for the project's plain-English explanation.