{
  "markdown": "# SmartMoney — 13F superinvestor tracker\n\nReconstructs hedge-fund / superinvestor portfolios straight from **SEC EDGAR**\n13F-HR filings, and diffs quarter-over-quarter to surface new positions, exits,\nadds and trims. Data is U.S.-government public domain — free to use and redistribute.\n\n**Guides:** [`TEST_LOCAL.md`](TEST_LOCAL.md) to run it on your machine in minutes ·\n[`INSTALL_SERVER.md`](INSTALL_SERVER.md) for production deployment · [`SECURITY.md`](SECURITY.md)\nfor the threat model and audit.\n\n## Setup\n```bash\npip install -r requirements.txt   # or: pip install requests defusedxml flask\nexport SEC_UA=\"SmartMoney/1.0 you@example.com\"   # SEC requires a contact email or 403s you\nexport OPENFIGI_APIKEY=\"...\"                       # optional, free; lifts FIGI rate limits\npython run.py --list                 # tracked funds\npython run.py --verify               # sanity-check seed CIKs against EDGAR\npython run.py --fund \"Berkshire Hathaway\" --top 15 --enrich   # --enrich adds tickers\n```\nOffline tests (no network):\n```bash\npip install pytest\npython -m pytest tests/ -q     # offline suites: parsing, figi, db, valuation, alerts, resolver, security, Pro API\n```\n\n## CUSIP resolution & the long tail\n13F rows carry only CUSIPs. `resolver.py` runs a confidence-ranked chain so the tail that\nOpenFIGI misses still gets resolved where possible, with provenance recorded:\n```\nmanual override (1.0) → OpenFIGI (0.95) → CUSIP issuer-prefix (0.65–0.85)\n→ SEC name match (0.60) → unresolved (0.0, issuer name kept)\n```\nThe CUSIP-prefix step reuses a confident sibling's ticker for a different share class/unit of\nthe same issuer; the SEC step matches `nameOfIssuer` against SEC `company_tickers.json`. Misses\nare cached with a timestamp and re-tried after a TTL (the tail shrinks as data improves), not\nforever. `--sync --enrich` uses the full chain.\n```bash\npython run.py --coverage                 # % of 13F value resolved, per fund + the worst tail\npython run.py --resolve-sweep            # re-run the chain over unresolved CUSIPs, back-fill\n```\nProvenance + confidence are stored per holding, so the dashboard's reconcile dot and the\nvaluation reconcile ratio can flag weak mappings. Optional `cusip_overrides.json` (`{\"<cusip>\":\"TKR\"}`)\nhard-maps stubborn names at top confidence.\n\n## Dashboard (web UI)\nA read-only Flask API exposes the store as JSON, and a single-file dashboard consumes it.\n```bash\npip install flask\npython -m smartmoney.api --db smartmoney.db            # serves http://localhost:5000\npython -m smartmoney.api --db smartmoney.db --value     # also enable live valuation (stooq)\n```\nOpen `http://localhost:5000`. The UI is branded **13FLOW** (dark editorial theme — emerald =\ninstitutions, amber = insiders, converging like the Confluence score) with five screens:\n**Consensus** (who's buying / most owned), **Funds** (holdings, current weights, implied P&L,\nconviction sparklines), **Compare** (overlap matrix across funds), **Alerts** (subscriptions +\nthe diff feed), and **Confluence** (13F accumulation × insider buying). A served **FAQ** page\n(`/faq`) explains the product, linked from the sidebar. Demo data is available only when\nexplicitly requested (`?demo=1` in the browser or `SMARTMONEY_CONFLUENCE_DEMO=1` for the\nConfluence API); production errors are shown instead of silently substituting samples. API endpoints:\n`/api/live-status`, `/api/funds`, `/api/fund/<cik>`,\n`/api/consensus/{buys,holdings}`, `/api/compare`,\n`/api/signals/confluence`, `/api/signals/confluence/history`,\n`/api/methodology/confluence-v1`, `/api/data-quality`, `/api/agent-stats`,\n`/api/openapi.json`, and the\nMCP Streamable HTTP endpoint `/api/mcp`. The Registry surface exposes only bounded, read-only\npublic tools. Optional Pro/x402 tools are disabled by default and are not advertised publicly.\nThe official Registry manifest is [`server.json`](server.json). Static,\ncrawler-friendly pages are\nserved at `/funds`, `/funds/<cik>`, `/stocks`, `/stocks/<ticker>`, `/signals`, and\n`/signals/<ticker>`, with SEC links where an accession or issuer search can be resolved.\n`/agents` and `/fr/agents` publish 7/30-day MCP activity from durable UTC aggregates. They\nmake no unique-user claim and retain no IP address, User-Agent, client version, raw client\nidentifier, arguments, prompts, responses or keys.\nThe separate operator-only `/stats/` surface is an optional password-protected GoAccess\nreport generated from Apache logs. It adds no browser tracking, strips query strings,\nanonymizes report IPs at level 2 and keeps a 90-day reporting window. Its dedicated CSP is\nscoped to the authenticated report so the public site's nonce policy remains unchanged.\nProduction also installs a first-in-order static `000-zen-default.conf` vhost. Unknown Host\nheaders receive an inert ZEN page and a separate minimal access log instead of reaching\n13FLOW or contaminating `13flow_access.log`; named vhosts continue to route normally.\n`/api/live-status` is the public,\nmachine-readable proof of live state: SHA, source (`SEC EDGAR`), latest quarter, row counts,\ndata-quality summary, and `uses_synthetic_data=false`. `/api/product-status` is the\nmachine-readable operational status surface: it states live data coverage, disabled\nclaims, and why full quantitative validation remains blocked until imported 2013-2026\nadjusted-price and normalized Form 4 transaction artifacts are available. See the\nCore V1 scope gate in [`docs/CORE_V1_BOUNDARY.md`](docs/CORE_V1_BOUNDARY.md).\nCurrent product and research boundaries are exposed at `/status`, `/readiness`,\n`/api/product-status` and `/api/research-readiness`. `/api/pro-offer` remains a retired\ncompatibility endpoint and is not exposed as a canonical MCP tool.\n\n## No browser accounts or checkout\nCore V1 deliberately has no browser account system, no public signup, no\nself-serve checkout and no Stripe billing flow. Pro API access is operator\nissued: create a scoped key, deliver the plaintext token once through an\napproved secure channel, verify `/api/pro/v1/status`, and keep audit/rotation\nin the Pro control plane.\n\n## Pro API\nThe Pro API is an explicit, versioned API-key surface for institutional and automated use.\nIt is off by default. Enable it with `SMARTMONEY_PRO_API=1` and store keys, counters, and\naudit events in a dedicated control-plane SQLite file via `SMARTMONEY_PRO_DB`.\n\nRecommended production split:\n```bash\n# /etc/13flow/13flow-web.env, used by 13flow.service on 127.0.0.1:8000\nSMARTMONEY_OPEN=1\nSMARTMONEY_DB_READONLY=1\nSMARTMONEY_DB=/var/lib/13flow/13flow.db\nSMARTMONEY_ADMIN_PANEL_USER=admin@toonux.com\nSMARTMONEY_ADMIN_SESSION_SECRET=<server-only-random-secret>\nSMARTMONEY_ADMIN_PASSWORD_PBKDF2=<pbkdf2-sha256$...>\nSMARTMONEY_ADMIN_SESSION_SECONDS=1800\nSMARTMONEY_ADMIN_TOTP_SECRET=<base32-secret>\nSMARTMONEY_ADMIN_TOTP_REQUIRED=1\n\n# /etc/13flow/13flow-pro.env, used by 13flow-pro.service on 127.0.0.1:8001\nSMARTMONEY_OPEN=1\nSMARTMONEY_DB_READONLY=1\nSMARTMONEY_DB=/var/lib/13flow/13flow.db\nSMARTMONEY_PRO_API=1\nSMARTMONEY_PRO_DB=/var/lib/13flow-pro/13flow-pro.db\nSMARTMONEY_PRO_KEY_PEPPER=<server-only-random-secret>\nSMARTMONEY_PRO_REQUIRE_KEY_PEPPER=1\nSMARTMONEY_PRO_ACCEPT_LEGACY_SHA256_KEYS=0\n```\n\nDo not grant `/var/lib/13flow-pro` write access to the public `13flow.service`. Apache\nshould route only `/api/pro/` to `13flow-pro.service`, while the public site and open JSON\nendpoints stay on the read-only service.\n\nAdmin panel access at `/pro/admin` is protected by a server-side admin session. Generate\nthe session secret and password hash on the server; the Pro API key used inside the panel\nis still separate and must carry `admin:read,admin:write`.\n\n```bash\nopenssl rand -hex 32\n/opt/13flow/.venv/bin/python /opt/13flow/run.py --hash-admin-password\n/opt/13flow/.venv/bin/python -c 'import base64,secrets; print(base64.b32encode(secrets.token_bytes(20)).decode().rstrip(\"=\"))'\n```\n\nCreate an API key offline as the operator. The plaintext token is shown exactly once. In\nproduction, only an HMAC-SHA256 hash derived with the server-only\n`SMARTMONEY_PRO_KEY_PEPPER` is stored, so tokens generated from a GitHub clone or another\ninstance cannot authenticate against `13flow.eu`. With\n`SMARTMONEY_PRO_REQUIRE_KEY_PEPPER=1`, legacy SHA-256 rows are rejected unless\n`SMARTMONEY_PRO_ACCEPT_LEGACY_SHA256_KEYS=1` is explicitly set for a short migration window.\n```bash\nsudo -u flowpro env SMARTMONEY_PRO_KEY_PEPPER=\"$SMARTMONEY_PRO_KEY_PEPPER\" \\\n  SMARTMONEY_PRO_REQUIRE_KEY_PEPPER=1 \\\n  /opt/13flow/.venv/bin/python /opt/13flow/run.py \\\n  --create-api-key \"Acme Asset Management\" \\\n  --pro-db /var/lib/13flow-pro/13flow-pro.db \\\n  --api-key-scopes funds:read,quality:read \\\n  --api-key-rate-per-min 120 \\\n  --api-key-rate-per-day 10000\n\npython run.py --list-api-keys --pro-db /var/lib/13flow-pro/13flow-pro.db\npython run.py --revoke-api-key <key_id> --pro-db /var/lib/13flow-pro/13flow-pro.db\npython run.py --prune-pro-audit-days 180 --pro-db /var/lib/13flow-pro/13flow-pro.db\n```\n\nUse `Authorization: Bearer <token>` or `X-13FLOW-Key: <token>`.\n```bash\ncurl -H \"Authorization: Bearer $TOKEN\" https://13flow.eu/api/pro/v1/status\ncurl -H \"Authorization: Bearer $TOKEN\" https://13flow.eu/api/pro/v1/funds\ncurl -H \"Authorization: Bearer $TOKEN\" https://13flow.eu/api/pro/v1/fund/0001067983\ncurl -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://13flow.eu/api/pro/v1/fund/0001067983?include_holds=0&limit_positions=20&limit_moves=50\"\ncurl -H \"Authorization: Bearer $TOKEN\" https://13flow.eu/api/pro/v1/data-quality\ncurl https://13flow.eu/api/pro/v1/openapi.json\ncurl https://13flow.eu/api/product-status\ncurl https://13flow.eu/api/research-readiness\n```\n\nSecurity properties: opaque high-entropy tokens, key hashes only at rest, scoped access\n(`funds:read`, `quality:read`), persistent per-minute/per-day rate limits, and an audit row\nfor every Pro request including denied and rate-limited calls. Pro responses are explicitly\nnon-cacheable (`private, no-store, max-age=0`) and vary on both supported key headers so\nreverse proxies cannot mix responses across credentials.\n\nOperational baseline:\n- one active key per institution or internal service;\n- revoke unused QA/bootstrap keys immediately;\n- rotate institutional keys on a fixed schedule and after personnel/vendor changes;\n- keep Pro audit rows long enough for incident response, then prune with\n  `--prune-pro-audit-days`;\n- back up `SMARTMONEY_PRO_DB` with encrypted backups only. See\n  [`deploy/PRO_API_SPLIT.md`](deploy/PRO_API_SPLIT.md) and `deploy/backup-pro-db.sh`.\n\nBefore issuing or renewing a Pro key, confirm\n`/api/pro/v1/admin/release-readiness` returns `go: true` with no blockers.\n\n`GET /api/pro/v1/fund/<cik>` is the institutional detail endpoint: it returns the selected\nfiling metadata, previous filing metadata, full holdings, share-count moves versus the\nprevious quarter, fund-scoped data-quality warnings, and the methodology block needed to\nreproduce the interpretation. For production integrations, use `include_holds=0`,\n`limit_positions`, and `limit_moves` to keep payloads bounded while retaining the same\ncalculation basis. Responses include `positions_total`/`positions_returned` and\n`changes_total`/`changes_returned` so clients can detect truncation deterministically.\n\n## Alerts — real delivery\nSubscribe to a fund and get the **diff** (not just \"a filing appeared\") delivered when a\nnew 13F lands. Channels: console (default), webhook, email.\n```bash\npython run.py --subscribe \"Berkshire Hathaway\"                       # console, primed\npython run.py --subscribe \"Scion Asset Mgmt\" --channel webhook --target https://hooks.you/x\npython run.py --list-subs\npython run.py --alerts-run          # sync subscribed funds from EDGAR, then deliver new ones\npython run.py --alerts-dispatch     # deliver pending from already-stored filings (offline)\n```\nEmail needs `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASS` / `SMTP_FROM` in the env.\nKey properties: idempotent (a crash/restart never re-sends or drops — the deliveries table is\nthe boundary), new subscribers are **primed** so they only get future filings, failed sends are\nrecorded and retried next run, and subscribing is gated to the paid tier. Drive `--alerts-run`\nfrom cron rather than the in-process `poll()` loop.\n\n## Confluence (13FLOW) — where 13F accumulation meets insider buying\nA separate screen ranks tickers where **superinvestor 13F accumulation** and **open-market\nForm 4 insider buying** coincide — a rare, hard-to-fake overlap. It reuses the existing EDGAR\netiquette (UA + rate limit), the `defusedxml` hardening, and the dashboard theme.\n```\nGET /api/signals/confluence?window=90&min_score=0\n→ { \"kpis\": {...}, \"signals\": [ {ticker, score, quadrant, breakdown{...}, institutional{...}, insider{...}}, ... ] }\nGET /api/methodology/confluence-v1\n→ frozen score version, parameters, universe, split, parameter_hash\nGET /api/signals/confluence/history?ticker=TSM&window=90\n→ append-only signal revisions from confluence-history.jsonl\n```\nThe 0–100 score is an **ordinal exploratory ranking**, not a probability, not a historical\nfrequency, and not an expected-return estimate. `FeatureParams` controls *what the signal\nmeasures* (recency half-life, buy-size curve, cluster window, seniority) and remains a set of\njudgement parameters until sensitivity tables are published. `Weights` controls *how the\npillars combine* (institutional breadth, insider conviction, recency-weighted dollars,\nagreement bonus, minus trim/sell penalties). The default weights are **heuristic**; the\ncurrent live build must be treated as **not calibrated on live historical outcomes** and not\nvalidated out-of-sample. Each signal exposes its per-pillar `breakdown`\nand a quadrant label (Conviction / Institutional bid / Insider conviction / Distribution /\nDivergent / Neutral), but the quadrant describes direction while the score describes heuristic\nintensity, so they can diverge.\n\nData-scope boundary: 13F filings are delayed long-US-securities disclosures, not complete\nfund portfolios. They omit shorts, most non-US holdings, bonds, full derivative books,\nintra-quarter trading and confidential-treatment omissions. The live Confluence rail also\nuses a bounded Form 4 issuer universe driven by tracked 13F activity; insider-only and\ndistribution quadrants are therefore not exhaustive. Current Form 4 parsing focuses on\nnormalized Table I transactions; Table II derivatives, 10b5-1 plan flags, multi-owner\nattribution and weighted-average price footnotes remain explicit limitations until modeled.\n\nThe quantitative proof boundary is public: `VALIDATION_PROTOCOL.md` defines the required\npoint-in-time dataset, train/validation/test split, baselines, neutralization, costs,\nconfidence intervals, permutation tests, and version log before any score can be called\nvalidated. Until then, the correct wording is: **backtest harness available; default weights\nare heuristic**. The offline price export, dataset builder and publication gate are:\n\n```bash\npython run.py \\\n  --build-validation-prices \\\n  --validation-tickers /var/lib/13flow/validation_tickers_sample25.txt \\\n  --validation-prices-out /var/lib/13flow/validation_prices_sample25.csv \\\n  --validation-price-provider massive \\\n  --validation-start 2013-01-01 \\\n  --validation-end 2026-07-02 \\\n  --validation-price-sleep-sec 15 \\\n  --validation-price-retry-attempts 8 \\\n  --validation-price-retry-base-sec 60 \\\n  --validation-price-retry-max-sec 900 \\\n  --validation-price-timeout-sec 10 \\\n  --validation-json\n\npython run.py --db /var/lib/13flow/13flow.db \\\n  --build-validation-dataset /var/lib/13flow/confluence_features.csv \\\n  --validation-prices /var/lib/13flow/validation_prices_sample25.csv \\\n  --validation-form4 /var/lib/13flow/validation_form4_sample25.csv \\\n  --validation-tickers /var/lib/13flow/validation_tickers_sample25.txt \\\n  --validation-code-commit \"$SHA\" \\\n  --validation-json\n\npython run.py --validation-dataset /path/to/confluence_features.csv --validation-json\n```\n\nThe price exporter writes a provider-neutral `ticker,date,adj_close` CSV and reuses\nalready exported ticker rows unless `--validation-price-force` is set. Massive requires\n`MASSIVE_API_KEY` in the process environment; `stooq` is available as a free fallback for\noperator smoke tests, and `yahoo` is a no-key research fallback when a vendor account cannot\nserve enough history. Any non-Massive fallback must be disclosed in the validation artifact\nas a research price source, not an institutional production feed. The exporter retries `429`\nand `5xx` responses with exponential backoff, honors `Retry-After`, deduplicates resumed rows\nand reports complete/partial history coverage per ticker. It checkpoints the CSV after each\nticker so interrupted runs remain resumable. Use `--validation-price-max-tickers 1` for first\ncontact with any new or fallback provider. Passing the same\n`--validation-tickers` file to the dataset builder filters the feature export to that priced\nuniverse; omit it only when the price CSV covers the full validation universe. The dataset\ngate returns the feature-table SHA256, split counts, schema gaps, version mismatches and rank\nmetrics for the score plus available baselines.\n\nImported vendor/bulk price files can be checked without touching any external API:\n\n```bash\npython run.py \\\n  --validate-price-csv /var/lib/13flow/validation_prices_full.csv \\\n  --validation-tickers /var/lib/13flow/validation_tickers_priceable.txt \\\n  --validation-start 2013-01-01 \\\n  --validation-end 2026-07-02 \\\n  --validation-json\n```\n\nThe validator reports required columns, positive-price failures, duplicate ticker/date rows,\nmissing tickers, partial histories and major calendar gaps before the file is used in a\nvalidation dataset.\n\nThe Form 4 validation artifact can be produced from SEC EDGAR with a conservative,\ncheckpointed exporter:\n\n```bash\nSEC_UA='13FLOW/1.0 contact@example.com' python run.py \\\n  --build-validation-form4 \\\n  --validation-tickers /var/lib/13flow/validation_tickers_sample25.txt \\\n  --validation-form4-out /var/lib/13flow/validation_form4_sample25.csv \\\n  --validation-start 2024-07-03 \\\n  --validation-end 2026-07-02 \\\n  --validation-form4-sleep-sec 2 \\\n  --validation-form4-max-tickers 1 \\\n  --validation-json\n```\n\nIncrease `--validation-form4-max-tickers` only after the one-ticker smoke succeeds. The\nexporter reuses existing ticker rows unless `--validation-form4-force` is set and caps each\nticker with `--validation-form4-max-filings-per-ticker`.\n\nFor the operator checklist after a long Form 4 export finishes, including the offline CSV\ngate and deployment order, see `docs/POST_RUN_FORM4_VALIDATION.md`.\n\nThe dataset builder joins the reviewed local Form 4 transaction file with\n`--validation-form4`. Accepted CSV/JSONL rows include `ticker`, `accession`,\n`filing_date`, `transaction_date`, owner identity/role fields, transaction code,\nacquired/disposed flag, shares, price and ownership-after fields. The join is point-in-time:\nonly Form 4 filings accepted by the dataset `as_of` date and transactions inside the trailing\nwindow enter the row. Without that file the builder still exports\n`feature_scope=13f_only_no_form4`; with it the scope becomes `13f_form4_joined`. Neither scope\nis a full validation claim until the imported price and Form 4 artifacts, coverage, costs and\nno-lookahead controls are reviewed. Non-priceable/common equity suspects are excluded by\ndefault; use `--validation-include-non-priceable` only for auditing noisy 13F rows.\n\nConfluence v1 is frozen as a machine-readable research contract in\n`docs/confluence_v1.json` and documented in `docs/CONFLUENCE_V1.md`. The append-only signal\nhistory is written to `confluence-history.jsonl` in `SMARTMONEY_CACHE_DIR`; corrections are\nnew revisions, not in-place edits.\n\nThe production live provider also has an explicit effective universe: to keep the public tier\noff abusive Form 4 fan-out, it scans insider filings only for tickers with at least\n`SMARTMONEY_CONFLUENCE_SCAN_MIN_FUNDS` tracked funds opening or adding in the latest 13F\nquarter (default 3). Trim/exits are computed more broadly, but insider-only, distribution,\nand divergent categories are therefore not exhaustive in this production path. This is\nexposed in `/api/signals/confluence` under `metadata.effective_universe`.\n\nThe screen lives as a fifth dashboard tab. Production must use either a precomputed\n`confluence-<window>.json` cache or the live provider. The live provider needs EDGAR access\nfor Form 4s:\n```bash\nSEC_UA=\"you@example.com\" SMARTMONEY_CONFLUENCE_LIVE=1 python -m smartmoney.api --db demo.db\n```\nWith no cache and no live provider, the endpoint returns `503 confluence_unavailable`. Use\n`SMARTMONEY_CONFLUENCE_DEMO=1` only for explicit local demos. Evaluate hypotheses or fit\nresearch weights with `python -m smartmoney.backtest` (synthetic demo only) — see\n`FORMS4_INTEGRATION.md` and `VALIDATION_PROTOCOL.md` for the feature write-up and validation\ncontract.\n\nOffline research/admin helpers:\n```bash\npython run.py --freeze-confluence-v1 docs/confluence_v1.json\npython run.py --append-signal-history --cache-dir /var/lib/13flow --confluence-windows 30,90,180\n```\n\n## Valuation — current weights & implied P&L\nA 13F reports value at *quarter-end*. To see what the book is worth *now* and the paper\nP&L since the filing, revalue stored holdings at live prices:\n```bash\npython run.py --value \"Berkshire Hathaway\"                       # stooq (free, default)\npython run.py --value \"Berkshire Hathaway\" --provider massive --fundamentals\npython run.py --value \"Berkshire Hathaway\" --basis 2024-09-30    # value a specific quarter\n```\nPrices are pluggable: **stooq** (free, no key) by default; **massive** (Massive Market Data,\nPolygon-shaped) with `MASSIVE_API_KEY` set, which also yields market cap and % of company owned.\nEach priced line shows a **reconcile ratio** (reported ÷ shares×quarter-end-close): ~1.00×\nmeans the CUSIP→ticker map is right; far from 1 flags a bad mapping. P&L is *paper* — it\nassumes holdings are unchanged since the filing, which the 45-day lag guarantees they're not.\n\n## Persistence & cross-fund screens\nSnapshots are stored in SQLite so diffs and multi-fund questions become queries.\n```bash\npython run.py --sync \"Berkshire Hathaway\" --enrich      # backfill one fund\npython run.py --sync-all --max-quarters 12 --enrich     # backfill everything\npython run.py --buys 2024-12-31 --min-funds 3           # who's BUYING (diff-based)\npython run.py --consensus 2024-12-31 --min-funds 3      # who's HOLDING (pure SQL)\npython run.py --quality --db smartmoney.db              # DB-only data-quality warnings\npython run.py --preflight --db smartmoney.db            # DB-only production readiness checks\npython run.py --timeline \"Berkshire Hathaway\" --cusip 037833100   # conviction over time\n```\n`--sync` only fetches filings not already stored, so re-runs are cheap and pick up just\nthe newest quarter. The `--buys`/`--consensus`/`--timeline` screens are pure DB reads —\nno SEC_UA needed once data is synced.\n\n## Operator preflight\n`run.py --preflight` is an offline release gate. It never calls EDGAR. It checks deploy SHA\ntraceability, opens the market DB read-only, verifies the `latest_filings` view has content,\nsummarizes data quality, verifies the Pro DB is writable, checks active Pro keys and recent\naudit rows, and, when a token is provided via environment, validates the Pro API contract\nin-process without putting the token in shell history. In rsync-style deployments without a\n`.git` directory, the CLI reads the deployed SHA from the systemd drop-in\n`/etc/systemd/system/13flow.service.d/version.conf`.\n\n```bash\nSHA=<deployed-git-sha>\n\nprintf \"API token: \"\nread -r -s SMARTMONEY_PRO_TOKEN\nprintf \"\\n\"\nexport SMARTMONEY_PRO_TOKEN\n\nsudo -E /opt/13flow/.venv/bin/python /opt/13flow/run.py --preflight \\\n  --db /var/lib/13flow/13flow.db \\\n  --pro-db /var/lib/13flow-pro/13flow-pro.db \\\n  --require-pro \\\n  --expected-sha \"$SHA\"\n\nunset SMARTMONEY_PRO_TOKEN\n\nsudo -E /opt/13flow/.venv/bin/python /opt/13flow/run.py --preflight --preflight-json ...\n```\n\n## Public smoke test\n`deploy/smoke-public.sh` is the crawler-visible release gate. It makes live HTTP calls to the\npublic site or staging, but never calls EDGAR. It fails if the root page regresses to\n`SAMPLE DATA`, auth/checkout copy appears in the open build, FAQ/Legal show legacy text,\nlegacy page aliases stop redirecting to canonical URLs, public JSON contracts break, MCP\ndisappears, or a private MCP tool is exposed on the default public surface.\n\n```bash\nEXPECTED_SHA=<deployed-git-sha> /opt/13flow/deploy/smoke-public.sh\nSITE=https://staging.13flow.eu EXPECTED_SHA=<sha> /opt/13flow/deploy/smoke-public.sh\n```\n\nThe public smoke now prints a timing summary and slow checks so a longer release gate can be\ndistinguished from slower production routes. For a focused latency guard, run the lightweight\npublic perf smoke after the functional smoke:\n\n```bash\n/opt/13flow/deploy/perf-smoke-public.sh\nSAMPLES=7 WARN_MS=1500 FAIL_MS=3000 /opt/13flow/deploy/perf-smoke-public.sh\n```\n\n## Open build (public, read-only — no auth, no Stripe, no browser alerts)\nThere is a first-class **open mode** for a public deployment that exposes only the read-only\nscreens (Consensus / Funds / Compare / Confluence) with no accounts and no payment.\n`--open` is kept for compatibility; Core V1 always registers only the public/Pro controlled\npilot surface:\n```bash\nSMARTMONEY_OPEN=1 SMARTMONEY_DB_READONLY=1 python -m smartmoney.api --db demo.db\n# or: python -m smartmoney.api --db demo.db --open --readonly\n```\nCore V1 does not register browser auth, billing or subscription routes\n(`/api/auth/*`, `/api/billing/*`, `/api/subscriptions`, `/api/alerts/*` return\n404, not 401). `SMARTMONEY_DB_READONLY=1` opens SQLite read-only so the public web process\ncannot write the market database, and the dashboard auto-detects the build via `/api/config`.\nThe Pro API is separate: run it in the dedicated `13flow-pro.service` with\n`SMARTMONEY_PRO_API=1`; `/api/pro/v1/*` writes only to `SMARTMONEY_PRO_DB`, not to the\nread-only 13F data DB, and the public `13flow.service` should keep no Pro DB write path. A complete\n**Debian + Apache** deployment kit (gunicorn systemd unit with a sandbox,\nApache TLS reverse-proxy vhost with a GET-only method allow-list + HSTS/CSP, an ingest user\nseparated from the web user, and a scheduled refresh) lives in [`deploy/`](deploy/) — see\n[`deploy/INSTALL_DEBIAN_APACHE.md`](deploy/INSTALL_DEBIAN_APACHE.md).\n\n## Architecture\n- `edgar.py` — rate-limited client (8 req/s, under SEC's 10/s ceiling), CIK resolution,\n  submissions feed, locates + downloads the holdings XML.\n- `parser.py` — namespace-agnostic info-table parser → raw holdings.\n- `portfolio.py` — aggregates rows to one line per (CUSIP, put/call), normalizes value units, weights.\n- `figi.py` — **CUSIP → ticker** via OpenFIGI v3: batched, rate-limited, 429-aware,\n  no-exchCode fallback, persistent disk cache.\n- `resolver.py` — long-tail resolver chain (OpenFIGI → CUSIP-prefix → SEC name → manual),\n  confidence + provenance, retryable cache, coverage reporting.\n- `diff.py` — classifies moves by **share count**: NEW / EXIT / ADD / TRIM / HOLD.\n- `db.py` — **SQLite store**: save/load portfolios, a `latest_filings` view so amendments\n  supersede, and SQL screens (consensus holdings, conviction timeline, holders, AUM timeline).\n- `analytics.py` — **consensus buys/sells** across funds (diff-based, the sharper screen).\n- `prices.py` — pluggable price/fundamentals providers: `StooqProvider` (free) + `MassiveProvider`.\n- `valuation.py` — revalue a stored portfolio at current prices: current weights, implied\n  P&L since quarter-end, reconcile check, % of company owned.\n- `registry.py` — superinvestor seed list (CIK is the stable key).\n- `tracker.py` — wires it together: `sync_fund` ingestion + freemium gating (free = 3 funds).\n- `channels.py` — delivery channels: console / webhook / email (+ callable for tests).\n- `alerts.py` — `AlertEngine`: diff-carrying alerts, persistent dedup, priming, paid-tier gate.\n- `api.py` — controlled-pilot Flask JSON API over the store; serves public pages and Pro API routes.\n- `netsec.py` — egress safety: SSRF guard for webhook URLs + email-recipient validation.\n- `pro.py` — Pro API keys, scopes, persistent rate limits, and request audit.\n- `forms4.py` — Form 4 discovery by issuer CIK + ownership-XML parser (open-market P/S), XXE-hardened.\n- `crosssignal.py` — Confluence engine: 13F accumulation × insider buying → scored, classified signal.\n- `backtest.py` — rank-IC / quantile-spread harness + coordinate-ascent research optimiser.\n- `api_signals.py` — read-only `GET /api/signals/confluence` blueprint (live + sample providers).\n- `dashboard.html` — single-file research app source; served at `/app`, with `/dashboard.html` redirecting there.\n- `faq.html` — branded FAQ / explainer source, served at `/faq`; `/faq.html` redirects there.\n\nSee **`SECURITY.md`** for the threat model, the audit findings, and deployment hardening.\n\n## Gotchas this code already handles (and the ones it doesn't)\n**Handled:**\n- Mandatory `User-Agent` + 10 req/s limit on EDGAR.\n- **Value units changed in 2023**: pre-2023-01-03 `<value>` is in *thousands*, after it's\n  *whole dollars*. Normalized by report date in `portfolio.py`.\n- Multiple rows per issuer aggregated; puts/calls kept distinct from long stock.\n- Amendments (13F-HR/A) skipped for the headline diff (they restate, not re-trade).\n- **CUSIP → ticker** via OpenFIGI: batch 100 jobs/req with key (5 without), v3 `warning`\n  = no-match handled, 429 backoff, results cached to disk so steady-state cost ≈ only new CUSIPs.\n\n**Not handled yet (the real work ahead):**\n- **No-match CUSIPs.** OpenFIGI resolves the vast majority of 13(f) securities, but expect a\n  long tail (some bonds, units, recently-issued names) to come back empty — they're cached as\n  misses and worth a periodic re-sweep.\n- **The 45-day lag.** 13F is filed up to 45 days after quarter-end, so no alert is ever\n  trade-fresh — the edge is being first to the *filing event* and to a clean diff, not to price.\n- Confidential-treatment requests can delay/omit positions; backfill when the amendment lands.\n- Pre-2013 filings are plain-text tables, not XML — this parser targets the XML era.\n\n## Roadmap toward the product\n1. ✅ CUSIP→ticker enrichment (OpenFIGI) — `figi.py`. Next: join price/market-cap.\n2. ✅ Persistence (SQLite) + cross-fund screens — `db.py` / `analytics.py`.\n3. ✅ Price / market-cap join — `prices.py` / `valuation.py` (current weights, implied P&L).\n4. ✅ Real alert delivery — `alerts.py` / `channels.py` (diff payload, dedup, channels, paywall).\n5. Freemium server-side: gating logic lives in `tracker.Tier` (fund limit + alerts flag).\n6. ✅ UX layer — `api.py` + `dashboard.html` (consensus, fund pages, compare, alert feed).\n\nWhen you outgrow SQLite, the swap to Postgres is mechanical: the schema uses standard\nwindow functions + one view, and the repository is plain SQL (no ORM lock-in).\n\n## License\n\n13FLOW is licensed under the **GNU Affero General Public License v3.0** (AGPL-3.0-or-later).\nIf you run a modified version on a network server, you must make the complete source\navailable to its users. See [LICENSE](LICENSE).\n\nData: SEC EDGAR (US public domain). 13FLOW is an analysis screen, not investment advice.\n",
  "bytes": 31340,
  "sha": "4da2a73c08060a33064cd97d96981e5b647b4e75369e22b51b62ec2303440863",
  "repo_slug": "bluetouff/13flow",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_bluetouff_13flow_5d99b98b/readme"
}