A factsheet engine for Indian mutual funds — live NAV, returns, and risk metrics assembled from three external data sources and two Postgres databases into a single-page poster.
Status at a glance:
One Express service fronts everything. It talks live to two paid vendor APIs and an AI provider, reads two separately-owned Postgres databases, and falls back to a legacy scraper that no longer works in production. A standalone batch job feeds the one database this project actually owns.
Legend: solid edges are live, verified paths; the dashed edge into Legacy fallback is dead weight in production (no Python runtime in the deploy target, and the underlying library ships broken anyway).
Graded on what's actually been tested this cycle, not what the code intends to do.
Rate limiting, sanitized error responses, no secrets in the repo going forward, render.yaml declares every required env var as sync: false.
Outstanding: the Investwell password that leaked in an early commit needs rotating on Investwell's side — deleting the file didn't undo the exposure, and it's still sitting in Miran's original repo's history too.
Fund lookup, live factsheet data, NAV history, and risk ratios (Sharpe, Info Ratio, Upside/Downside Capture, Rolling Returns) all verified end-to-end — 1,727 of 1,746 funds compute cleanly. Debt and Hybrid funds get full benchmark-mode ratios for the first time, sourced from official NSE monthly data rather than a live scrape.
batch_compute_risk_metrics.js is a script someone has to remember to re-run — category averages will quietly drift stale otherwise. Needs a weekly cron, not a person's memory.
bharatfintrack.js, benchmark_scraper.py, and the .venv reference still ship in the repo. They only work for 4 pre-baked static caches and fail silently everywhere else — no Python runtime exists in the deploy target at all. Worth removing outright now that nifty_pipeline covers what this was for.
Every verification this cycle was a manual curl call or a browser click-through. No unit tests, no integration suite, nothing that runs automatically on a change.
console.log is the entire logging strategy. No structured logs, no error tracking, no alerting — a production incident is invisible until a person notices the site is wrong.
The fund cache lives in process memory. Fine for one instance; the moment this runs behind a load balancer with more than one instance, each holds a different cache and users see inconsistent data.
render.yaml is complete and correct, but the real target is AWS — that configuration doesn't exist yet.
Roughly the order it's worth doing them in.
batch_compute_risk_metrics.js keeps category averages from silently going stale.child_process.exec call that fails on every request outside the 4 cached indices, adding latency and log noise for nothing.render.yaml's env-var list translates directly; the work is standing up the actual AWS side.Growthvine has a separate reference document — Growthvine Capital SEBI-Ready System Architecture v1.0 (2026-07-01) — defining a target architecture across five products: Portfolio Review, Financial Planning, Fund Analysis Dashboard, Fund One Pager, and Content Automation. This project is exactly one of those five, and the doc scopes it explicitly (§4): Data Sensitivity: Low to Medium, Criticality: Medium, Primary Risks: public API abuse, stale fund facts, cache poisoning.
That scoping matters more than counting checklist boxes. Most of the doc's 24 sections govern the other four products' CAS ingestion, consent workflows, S3/PDF generation, and internal-user MFA — none of which this codebase touches at all. Grading this repo against the whole document would manufacture false-positive gaps. What follows is scoped to what actually applies to Fund One Pager, with every claim checked against the current code rather than asserted from memory.
/api/fund/:isin returns a generic message on failure; the real error only reaches console.error, never the client.CREATE TABLE, and executing the full batch_compute_risk_metrics.js batch against all 1,746 real funds — used the same credentials from the one .env file that render.yaml also points at in production. There is no dev/staging database, scoped access, or separation of any kind. This already happened; it isn't a future risk.batch_compute_risk_metrics.js does none of this: upsertFundRiskMetrics (server/nifty_pipeline.js:115) runs a raw INSERT ... ON CONFLICT (isin) DO UPDATE straight into the live fund_risk_metrics table on every run. If a bad run computed wrong numbers for a subset of funds, there's no staging step to catch it, no immutable record of what the run found or changed, and no way to diff against the prior run before it overwrote the numbers everyone sees.isin. §13.4 names "resource exhaustion" and "cache poisoning" as this product's own primary risks. server/index.js:89 reads isin straight from req.params with no format check before it triggers real Investwell/GrowthVine API calls and an unbounded NodeCache write (new NodeCache({ stdTTL: 43200 }), server/index.js:70 — no maxKeys limit configured). A flood of garbage-ISIN requests burns paid-API quota and grows the cache without bound; there's nothing here to stop it.event_id, event_type, actor_id, subject_id, correlation_id, created_at, …). The entire logging strategy today is console.log — unstructured, ephemeral, gone on process restart, not queryable.correlationId/correlation_id/x-request-id). §7.1, §11, and §14 all require one, specifically so a single factsheet request can be traced across the API call, cache check, and the three external vendor calls it fans out to. Right now that's impossible to reconstruct after the fact..github, no pre-commit hooks, confirmed. This is precisely the control that would have caught the leaked Investwell password before it was ever committed, and it still doesn't exist to catch the next one.NodeCache, once it runs on more than one instance.VENDORS.md), just doesn't exist yet.fund_risk_metricsisin input — the exact risk the reference doc names for this productFlagged during this audit cycle, not designed or implemented yet. Each needs its own design pass when building resumes.
Vite + a separate Express backend (Vite serves the SPA in dev with a proxy to the API, Express serves the built dist/ in production) is a standard, production-proven pattern for this shape of app and noticeably faster to develop against than Create React App or a hand-rolled Webpack config. The only real alternative is Next.js, and that's only worth it if server-side rendering for SEO becomes a goal (e.g., individual fund pages ranking on Google) — that's a rewrite, not a swap, and nothing about the app needs it today.
BharatFinTrack's Python/NSE scraper (bharatfintrack.js, benchmark_scraper.py, the .venv reference) and any yfinance-style free-data-source approach share the same failure mode: no Python runtime exists in the deploy target, and even where one could be added, these sources are unofficial scrapes with no uptime guarantee and a real history of getting blocked by the source site. All of this is already dead weight now that nifty_pipeline covers the same need through an official, pre-stored data source instead of live scraping (see §1). Confirmed for removal; not yet deleted, since we're auditing this cycle, not building.
Right now, risk_calculator.js's methodology (Sharpe, Info Ratio, Upside/Downside Capture, Rolling Returns, Max Drawdown) and batch_compute_risk_metrics.js live inside the Fund One Pager Express app itself. Growthvine's own target architecture (§7.1 "Instance 1: Data API and Cache") already separates a dedicated data-serving layer from product-specific backend workloads — this project doesn't yet reflect that split. Since Portfolio Review, Financial Planning, and the Fund Analysis Dashboard would plausibly want the same risk metrics, keeping the calculation logic siloed inside this one product's repo means duplicating it (or worse, silently diverging it) the moment another product needs the same numbers. Worth a real design pass on: what the boundary looks like (shared package vs. its own deployed service), who owns it, and how the four other products would consume it — not something to decide inside this audit.
Current behavior: the frontend renders the poster from the live JSON response, and "Export Factsheet (PNG)" runs html-to-image client-side (canvas rasterization in the browser) — there is no server-side headless-browser step anywhere in this app today. The concern raised is about any future server-side rendering path reaching for something like Playwright/headless Chromium to generate exports — correctly ruled out as too heavy for this deploy target, and notably the same tool Growthvine's own target architecture isolates into its own sandboxed instance for Content Automation specifically because of that weight (§7.3).
The direction worth designing later: treat the HTML render as the cached, shareable artifact (fast to serve, matches the target architecture's own "pre-generated fund one-pager, 7-day TTL, rebuilt after data sync" caching tier, §12.2) and generate a PNG only on demand from that — keeping PNG conversion client-side (as it already is) rather than introducing a heavy server-side rendering dependency. This decouples "compute + render the factsheet" (expensive, changes only when the underlying data syncs) from "convert to an image" (cheap, done per user action). Not designed in detail yet — needs its own pass once the data-layer question above is settled, since where computation lives affects what this caching tier actually looks like.