From 89e60451160d53884d4785b1dba354166f6ad2c0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 14 Oct 2026 10:20:45 +0000 Subject: [PATCH] refactor(investing): migrate Magic Formula into investing/ (#181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror credit layout: investing/{CONTEXT.md,docs/,src/investing/,apps/,tests/}. Hard-rename package smartwealthai → investing (no shim). Move docs/mvp → investing/docs, dashboard → investing/apps/dashboard, and tests/credit → credit/tests. Update Poetry entry points, CI, AGENTS, CONTEXT-MAP, and cursor rules so the demo pipeline stays green under Poetry. Co-authored-by: jdlsicilia --- .cursor/rules/domain.md | 25 +++--- .cursor/rules/issue-tracker.md | 6 +- .cursor/rules/mvp-docs.mdc | 12 +-- .cursor/rules/notion-tasks.mdc | 4 +- .cursor/rules/project-context.mdc | 22 ++--- .cursor/rules/spec-driven-workflow.mdc | 4 +- .github/workflows/pr-ci.yml | 2 +- AGENTS.md | 34 ++++---- CONTEXT-MAP.md | 14 ++- Makefile | 4 +- README.md | 16 ++-- apps/dashboard/__init__.py | 0 credit/README.md | 18 ++-- credit/data/README.md | 2 +- credit/docs/README.md | 4 +- .../docs/features/aws-fargate-serve-demo.md | 4 +- .../features/css-chapter5-mart-and-scoring.md | 6 +- credit/docs/features/psi-data-drift.md | 6 +- credit/docs/guides/friday-demo.md | 12 +-- credit/docs/guides/serve-fastapi.md | 10 +-- credit/notebooks/eda_application_mart.ipynb | 2 +- credit/src/credit/demo_serve_artifact.py | 2 +- .../fixtures/amex_extract/train_data_tiny.csv | 0 .../application_source/amex_sample.pkl | Bin .../application_source/applications.csv | 0 .../competition_mart/amex/train_labels.csv | 0 .../home_credit/application_train.csv | 0 .../tests}/fixtures/psi_mart/applications.csv | 0 .../fixtures/scoring_mart/applications.csv | 0 .../fixtures/serving/drivers_request.json | 0 .../fixtures/serving/score_request.json | 0 .../tests}/test_amex_parquet_convert.py | 0 .../tests}/test_api_serving.py | 0 .../tests}/test_application_mart.py | 0 .../tests}/test_cli_competition_raw.py | 0 .../credit => credit/tests}/test_cli_stub.py | 8 +- .../tests}/test_competition_mart.py | 0 .../tests}/test_competition_raw_stage.py | 0 {tests/credit => credit/tests}/test_psi.py | 0 .../tests}/test_scoring_coverage.py | 0 .../tests}/test_scoring_pipeline.py | 0 .../tests}/test_serve_demo_bake.py | 0 data/reference/universes/README.md | 2 +- docs/README.md | 81 +++++------------- docs/adr/0002-june-demo-scope-cut.md | 2 +- docs/adr/0003-phase2-qv-cancelled.md | 8 +- CONTEXT.md => investing/CONTEXT.md | 30 +++---- investing/README.md | 28 +++--- {apps => investing/apps}/dashboard/Home.py | 4 +- .../apps/dashboard}/__init__.py | 0 {apps => investing/apps}/dashboard/common.py | 2 +- .../apps}/dashboard/pages/1_Ranking.py | 0 .../apps}/dashboard/pages/2_Portfolio.py | 0 {docs/mvp => investing/docs}/NOTION_SETUP.md | 6 +- .../docs}/architecture/architecture.md | 12 +-- .../mvp => investing/docs}/backlog/backlog.md | 0 {docs/mvp => investing/docs}/demo-slice.md | 12 +-- .../docs}/features/backtesting.md | 2 +- .../docs}/features/broker-execution.md | 2 +- .../docs}/features/cheap-stocks.md | 2 +- .../docs}/features/corroborative-signals.md | 0 .../docs}/features/dashboard-reporting.md | 4 +- .../docs}/features/etl-data-lake.md | 52 +++++------ .../docs}/features/high-quality-stocks.md | 2 +- .../docs}/features/permanent-loss-filter.md | 4 +- .../docs}/features/portfolio-evolution.md | 4 +- .../docs}/features/sell-watch.md | 2 +- .../docs}/features/universe-construction.md | 6 +- .../features/unstructured-financial-data.md | 0 .../docs}/guides/download-fundamentals.md | 18 ++-- .../docs}/guides/download-simfin.md | 8 +- .../docs}/prds/ci-cd/ci-cd-prd.md | 6 +- .../docs}/prds/devcontainer/prd.md | 4 +- .../mvp => investing/docs}/prds/phase2/prd.md | 12 +-- .../docs}/requirements/requirements.md | 2 +- .../src/investing}/__init__.py | 2 +- .../src/investing}/build_universe.py | 4 +- .../src/investing}/compute_metrics.py | 4 +- .../src/investing}/dashboard_data.py | 2 +- .../src/investing}/download_fundamentals.py | 16 ++-- .../src/investing}/download_prices.py | 4 +- .../src/investing}/download_simfin.py | 8 +- .../src/investing}/edgartools_client.py | 6 +- .../src/investing}/fixture_lake.py | 0 .../generate_simfin_industry_exclusions.py | 2 +- .../src/investing}/lake_paths.py | 2 +- .../src/investing}/magic_formula_metrics.py | 0 .../src/investing}/magic_formula_ranking.py | 8 +- .../src/investing}/mlflow_run_logging.py | 0 .../src/investing}/normalize_simfin.py | 6 +- .../src/investing}/pit_fundamentals.py | 4 +- .../src/investing}/price_ingest.py | 2 +- .../src/investing}/run_dashboard.py | 7 +- .../src/investing}/run_demo_pipeline.py | 12 +-- .../src/investing}/score_universe.py | 6 +- .../src/investing}/sec_client.py | 2 +- .../src/investing}/simfin_client.py | 0 .../investing}/simfin_industry_exclusions.py | 0 .../src/investing}/simfin_normalizer.py | 4 +- .../src/investing}/universe.py | 2 +- .../src/investing}/universe_builder.py | 4 +- .../tests}/fixtures/lake/MANIFEST.json | 0 .../tests}/fixtures/lake/README.md | 0 .../fixtures/lake/curated/fundamentals.csv | 0 .../as_of_date=2026-05-21/response.json | 0 .../as_of_date=2026-05-21/response.json | 0 .../us-balance-quarterly.csv | 0 .../as_of_date=2026-06-18/us-companies.csv | 0 .../as_of_date=2026-06-18/us-income-ttm.csv | 0 .../as_of_date=2026-06-18/industries.csv | 0 .../us-shareprices-latest.csv | 0 .../as_of_date=2026-05-21/history.json | 0 .../as_of_date=2026-05-21/history.json | 0 .../tests}/test_ci_baseline.py | 4 +- .../tests}/test_dashboard_data.py | 32 +++---- .../tests}/test_download_fundamentals.py | 8 +- .../tests}/test_download_prices.py | 12 +-- .../tests}/test_download_simfin.py | 16 ++-- .../tests}/test_magic_formula_metrics.py | 18 ++-- .../tests}/test_magic_formula_ranking.py | 28 +++--- .../tests}/test_mlflow_run_logging.py | 16 ++-- .../tests}/test_pit_fundamentals.py | 4 +- .../tests}/test_run_demo_pipeline.py | 24 +++--- .../tests}/test_simfin_industry_exclusions.py | 4 +- .../tests}/test_simfin_normalizer.py | 12 +-- .../tests}/test_universe_builder.py | 14 +-- platform/docs/ephemeral-css-serve-demo.md | 4 +- platform/scripts/demo-up.sh | 4 +- pyproject.toml | 36 ++++---- 129 files changed, 425 insertions(+), 452 deletions(-) delete mode 100644 apps/dashboard/__init__.py rename {tests/credit => credit/tests}/fixtures/amex_extract/train_data_tiny.csv (100%) rename {tests/credit => credit/tests}/fixtures/application_source/amex_sample.pkl (100%) rename {tests/credit => credit/tests}/fixtures/application_source/applications.csv (100%) rename {tests/credit => credit/tests}/fixtures/competition_mart/amex/train_labels.csv (100%) rename {tests/credit => credit/tests}/fixtures/competition_mart/home_credit/application_train.csv (100%) rename {tests/credit => credit/tests}/fixtures/psi_mart/applications.csv (100%) rename {tests/credit => credit/tests}/fixtures/scoring_mart/applications.csv (100%) rename {tests/credit => credit/tests}/fixtures/serving/drivers_request.json (100%) rename {tests/credit => credit/tests}/fixtures/serving/score_request.json (100%) rename {tests/credit => credit/tests}/test_amex_parquet_convert.py (100%) rename {tests/credit => credit/tests}/test_api_serving.py (100%) rename {tests/credit => credit/tests}/test_application_mart.py (100%) rename {tests/credit => credit/tests}/test_cli_competition_raw.py (100%) rename {tests/credit => credit/tests}/test_cli_stub.py (90%) rename {tests/credit => credit/tests}/test_competition_mart.py (100%) rename {tests/credit => credit/tests}/test_competition_raw_stage.py (100%) rename {tests/credit => credit/tests}/test_psi.py (100%) rename {tests/credit => credit/tests}/test_scoring_coverage.py (100%) rename {tests/credit => credit/tests}/test_scoring_pipeline.py (100%) rename {tests/credit => credit/tests}/test_serve_demo_bake.py (100%) rename CONTEXT.md => investing/CONTEXT.md (84%) rename {apps => investing/apps}/dashboard/Home.py (94%) rename {apps => investing/apps/dashboard}/__init__.py (100%) rename {apps => investing/apps}/dashboard/common.py (97%) rename {apps => investing/apps}/dashboard/pages/1_Ranking.py (100%) rename {apps => investing/apps}/dashboard/pages/2_Portfolio.py (100%) rename {docs/mvp => investing/docs}/NOTION_SETUP.md (83%) rename {docs/mvp => investing/docs}/architecture/architecture.md (97%) rename {docs/mvp => investing/docs}/backlog/backlog.md (100%) rename {docs/mvp => investing/docs}/demo-slice.md (86%) rename {docs/mvp => investing/docs}/features/backtesting.md (97%) rename {docs/mvp => investing/docs}/features/broker-execution.md (97%) rename {docs/mvp => investing/docs}/features/cheap-stocks.md (94%) rename {docs/mvp => investing/docs}/features/corroborative-signals.md (100%) rename {docs/mvp => investing/docs}/features/dashboard-reporting.md (96%) rename {docs/mvp => investing/docs}/features/etl-data-lake.md (91%) rename {docs/mvp => investing/docs}/features/high-quality-stocks.md (95%) rename {docs/mvp => investing/docs}/features/permanent-loss-filter.md (95%) rename {docs/mvp => investing/docs}/features/portfolio-evolution.md (97%) rename {docs/mvp => investing/docs}/features/sell-watch.md (97%) rename {docs/mvp => investing/docs}/features/universe-construction.md (97%) rename {docs/mvp => investing/docs}/features/unstructured-financial-data.md (100%) rename {docs/mvp => investing/docs}/guides/download-fundamentals.md (85%) rename {docs/mvp => investing/docs}/guides/download-simfin.md (88%) rename {docs/mvp => investing/docs}/prds/ci-cd/ci-cd-prd.md (97%) rename {docs/mvp => investing/docs}/prds/devcontainer/prd.md (94%) rename {docs/mvp => investing/docs}/prds/phase2/prd.md (98%) rename {docs/mvp => investing/docs}/requirements/requirements.md (94%) rename {src/smartwealthai => investing/src/investing}/__init__.py (85%) rename {src/smartwealthai => investing/src/investing}/build_universe.py (93%) rename {src/smartwealthai => investing/src/investing}/compute_metrics.py (95%) rename {src/smartwealthai => investing/src/investing}/dashboard_data.py (96%) rename {src/smartwealthai => investing/src/investing}/download_fundamentals.py (93%) rename {src/smartwealthai => investing/src/investing}/download_prices.py (96%) rename {src/smartwealthai => investing/src/investing}/download_simfin.py (95%) rename {src/smartwealthai => investing/src/investing}/edgartools_client.py (94%) rename {src/smartwealthai => investing/src/investing}/fixture_lake.py (100%) rename {src/smartwealthai => investing/src/investing}/generate_simfin_industry_exclusions.py (96%) rename {src/smartwealthai => investing/src/investing}/lake_paths.py (99%) rename {src/smartwealthai => investing/src/investing}/magic_formula_metrics.py (100%) rename {src/smartwealthai => investing/src/investing}/magic_formula_ranking.py (98%) rename {src/smartwealthai => investing/src/investing}/mlflow_run_logging.py (100%) rename {src/smartwealthai => investing/src/investing}/normalize_simfin.py (95%) rename {src/smartwealthai => investing/src/investing}/pit_fundamentals.py (98%) rename {src/smartwealthai => investing/src/investing}/price_ingest.py (99%) rename {src/smartwealthai => investing/src/investing}/run_dashboard.py (86%) rename {src/smartwealthai => investing/src/investing}/run_demo_pipeline.py (96%) rename {src/smartwealthai => investing/src/investing}/score_universe.py (93%) rename {src/smartwealthai => investing/src/investing}/sec_client.py (98%) rename {src/smartwealthai => investing/src/investing}/simfin_client.py (100%) rename {src/smartwealthai => investing/src/investing}/simfin_industry_exclusions.py (100%) rename {src/smartwealthai => investing/src/investing}/simfin_normalizer.py (99%) rename {src/smartwealthai => investing/src/investing}/universe.py (98%) rename {src/smartwealthai => investing/src/investing}/universe_builder.py (97%) rename {tests => investing/tests}/fixtures/lake/MANIFEST.json (100%) rename {tests => investing/tests}/fixtures/lake/README.md (100%) rename {tests => investing/tests}/fixtures/lake/curated/fundamentals.csv (100%) rename {tests => investing/tests}/fixtures/lake/raw/sec_edgar/cik=0000320193/endpoint=companyfacts/as_of_date=2026-05-21/response.json (100%) rename {tests => investing/tests}/fixtures/lake/raw/sec_edgar/cik=0000789019/endpoint=companyfacts/as_of_date=2026-05-21/response.json (100%) rename {tests => investing/tests}/fixtures/lake/raw/simfin/dataset=balance/variant=quarterly/market=us/as_of_date=2026-06-18/us-balance-quarterly.csv (100%) rename {tests => investing/tests}/fixtures/lake/raw/simfin/dataset=companies/variant=default/market=us/as_of_date=2026-06-18/us-companies.csv (100%) rename {tests => investing/tests}/fixtures/lake/raw/simfin/dataset=income/variant=ttm/market=us/as_of_date=2026-06-18/us-income-ttm.csv (100%) rename {tests => investing/tests}/fixtures/lake/raw/simfin/dataset=industries/variant=default/market=us/as_of_date=2026-06-18/industries.csv (100%) rename {tests => investing/tests}/fixtures/lake/raw/simfin/dataset=shareprices/variant=latest/market=us/as_of_date=2026-06-18/us-shareprices-latest.csv (100%) rename {tests => investing/tests}/fixtures/lake/raw/yfinance/ticker=AAPL/endpoint=history/as_of_date=2026-05-21/history.json (100%) rename {tests => investing/tests}/fixtures/lake/raw/yfinance/ticker=MSFT/endpoint=history/as_of_date=2026-05-21/history.json (100%) rename {tests => investing/tests}/test_ci_baseline.py (95%) rename {tests => investing/tests}/test_dashboard_data.py (86%) rename {tests => investing/tests}/test_download_fundamentals.py (95%) rename {tests => investing/tests}/test_download_prices.py (97%) rename {tests => investing/tests}/test_download_simfin.py (96%) rename {tests => investing/tests}/test_magic_formula_metrics.py (94%) rename {tests => investing/tests}/test_magic_formula_ranking.py (94%) rename {tests => investing/tests}/test_mlflow_run_logging.py (93%) rename {tests => investing/tests}/test_pit_fundamentals.py (98%) rename {tests => investing/tests}/test_run_demo_pipeline.py (90%) rename {tests => investing/tests}/test_simfin_industry_exclusions.py (95%) rename {tests => investing/tests}/test_simfin_normalizer.py (98%) rename {tests => investing/tests}/test_universe_builder.py (96%) diff --git a/.cursor/rules/domain.md b/.cursor/rules/domain.md index f343f9b..4d4375b 100644 --- a/.cursor/rules/domain.md +++ b/.cursor/rules/domain.md @@ -14,30 +14,35 @@ How the engineering skills should consume this repo's domain documentation when ## Before exploring, read these - **`CONTEXT-MAP.md`** — domains, relationships, delivery sequencing, Sources. -- **Domain glossary** — investing: `CONTEXT.md`; credit: `credit/CONTEXT.md`. +- **Domain glossary** — investing: `investing/CONTEXT.md`; credit: `credit/CONTEXT.md`. - **`docs/adr/`** — architectural decision records for cross-cutting choices (including ADR-0003 Phase 2 QV cancelled). -- **Investing:** `docs/mvp/architecture/architecture.md` (frozen historical vision) and `docs/mvp/features/.md` when changing investing. +- **Investing:** `investing/docs/architecture/architecture.md` (frozen historical vision) and `investing/docs/features/.md` when changing investing. - **Credit:** `credit/docs/` feature specs when present; `credit/README.md` for domain entry. If a CONTEXT file or `docs/adr/` does not exist yet, **proceed silently**. Do not flag their absence or suggest creating them upfront. -Do not treat `src/` or `notebooks/` as canonical architecture for new domains; see `AGENTS.md`. +Do not treat root `notebooks/` as canonical architecture for new domains; see `AGENTS.md`. ## File structure ``` / ├── CONTEXT-MAP.md ← portfolio domains + relationships -├── CONTEXT.md ← investing ubiquitous language -├── investing/README.md +├── investing/ +│ ├── CONTEXT.md ← investing ubiquitous language +│ ├── README.md +│ ├── docs/ ← investing demo specs +│ ├── src/investing/ ← investing package +│ ├── apps/dashboard/ +│ └── tests/ ├── credit/ │ ├── CONTEXT.md ← credit / CSS ubiquitous language │ ├── README.md -│ └── docs/ ← credit feature specs -├── docs/ -│ ├── adr/ ← system-wide ADRs -│ └── mvp/ ← investing demo specs (delivered / frozen vision) -└── src/ ← investing demo implementation (migrate later) +│ ├── docs/ ← credit feature specs +│ ├── src/credit/ +│ └── tests/ +└── docs/ + └── adr/ ← system-wide ADRs ``` ## Use CONTEXT vocabulary diff --git a/.cursor/rules/issue-tracker.md b/.cursor/rules/issue-tracker.md index 3e05588..238736d 100644 --- a/.cursor/rules/issue-tracker.md +++ b/.cursor/rules/issue-tracker.md @@ -22,12 +22,12 @@ Infer the repo from `git remote -v` — `gh` does this automatically when run in | Layer | Where | Role | | --- | --- | --- | -| **Investing specs (canonical)** | `docs/mvp/features/*.md`, `docs/mvp/architecture/architecture.md` | Investing product/engineering truth; update before closing investing work | +| **Investing specs (canonical)** | `investing/docs/features/*.md`, `investing/docs/architecture/architecture.md` | Investing product/engineering truth; update before closing investing work | | **Credit specs (canonical)** | `credit/docs/` (see `CONTEXT-MAP.md`) | Credit / CSS product/engineering truth; update before closing credit work | | **GitHub issues** | This repo's GitHub Issues | PRDs, vertical slices, and triage for Matt Pocock engineering skills (`to-issues`, `to-prd`, `triage`) | -| **Notion tasks** | Board `Cursor Agent Tasks` | Optional execution tracking via Notion MCP; see `AGENTS.md` and `docs/mvp/NOTION_SETUP.md` | +| **Notion tasks** | Board `Cursor Agent Tasks` | Optional execution tracking via Notion MCP; see `AGENTS.md` and `investing/docs/NOTION_SETUP.md` | -When a skill publishes to the issue tracker, create or update a **GitHub issue**. Link the relevant spec path (`docs/mvp/features/...` or `credit/docs/...`) in the issue body. Do not treat Notion as the issue tracker for those skills unless the user explicitly asks to sync there. +When a skill publishes to the issue tracker, create or update a **GitHub issue**. Link the relevant spec path (`investing/docs/features/...` or `credit/docs/...`) in the issue body. Do not treat Notion as the issue tracker for those skills unless the user explicitly asks to sync there. ## When a skill says "publish to the issue tracker" diff --git a/.cursor/rules/mvp-docs.mdc b/.cursor/rules/mvp-docs.mdc index 5c9ff20..d59b94e 100644 --- a/.cursor/rules/mvp-docs.mdc +++ b/.cursor/rules/mvp-docs.mdc @@ -1,16 +1,16 @@ --- -description: MVP Markdown spec structure and editing conventions -globs: docs/** +description: Investing Markdown spec structure and editing conventions +globs: investing/docs/** alwaysApply: false --- -# MVP documentation conventions +# Investing documentation conventions -When creating or editing specs under `docs/mvp/`: +When creating or editing specs under `investing/docs/`: ## Feature specs -Follow the structure used in existing features (e.g. `docs/mvp/features/cheap-stocks.md`): +Follow the structure used in existing features (e.g. `investing/docs/features/cheap-stocks.md`): - Objective - MVP scope / Out of MVP scope @@ -23,7 +23,7 @@ Add **Implementation status** when coding starts: `planned` | `in_progress` | `d ## Architecture spec -- Record closed decisions in tables (see "Decisions made so far" in `docs/mvp/architecture/architecture.md`). +- Record closed decisions in tables (see "Decisions made so far" in `investing/docs/architecture/architecture.md`). - Cross-link feature files from the module table. - Do not contradict a closed architecture decision without updating the architecture doc first. diff --git a/.cursor/rules/notion-tasks.mdc b/.cursor/rules/notion-tasks.mdc index 6770f86..766368e 100644 --- a/.cursor/rules/notion-tasks.mdc +++ b/.cursor/rules/notion-tasks.mdc @@ -5,14 +5,14 @@ alwaysApply: true # Notion task tracking -**Git specs are canonical** (`docs/mvp/` for investing; `credit/docs/` for credit). Notion is for tasks and status only. Portfolio map: [`CONTEXT-MAP.md`](../../CONTEXT-MAP.md). +**Git specs are canonical** (`investing/docs/` for investing; `credit/docs/` for credit). Notion is for tasks and status only. Portfolio map: [`CONTEXT-MAP.md`](../../CONTEXT-MAP.md). ## Task fields Each Notion task should include: - **Title:** e.g. `Feature: Cheap Stocks — implement EY rank` or `Credit: Application mart — EY-style WOE shortlist` -- **Spec path:** investing → `docs/mvp/features/.md`; credit → `credit/docs/…` (e.g. `credit/docs/features/css-chapter5-mart-and-scoring.md`) +- **Spec path:** investing → `investing/docs/features/.md`; credit → `credit/docs/…` (e.g. `credit/docs/features/css-chapter5-mart-and-scoring.md`) - **Notes:** copy acceptance criteria from the spec ## Sync discipline diff --git a/.cursor/rules/project-context.mdc b/.cursor/rules/project-context.mdc index c98fbcc..2dc115a 100644 --- a/.cursor/rules/project-context.mdc +++ b/.cursor/rules/project-context.mdc @@ -9,28 +9,28 @@ SmartWealthAI is a **portfolio monorepo** for finance engineering: **investing** ## Current phase -- **Investing:** June 30 demo slice **delivered** ([`demo-slice.md`](../../docs/mvp/demo-slice.md)). Specs remain under `docs/mvp/`; code under `src/smartwealthai` until a migrate-only PR. -- **Credit:** **Active next** — specs and new code under `credit/` ([`credit/CONTEXT.md`](../../credit/CONTEXT.md), `credit/docs/`). +- **Investing:** June 30 demo slice **delivered** ([`demo-slice.md`](../../investing/docs/demo-slice.md)). Code and specs under `investing/` (`investing/src/investing/`, `investing/docs/`, `investing/CONTEXT.md`). +- **Credit:** **Active** — specs and new code under `credit/` ([`credit/CONTEXT.md`](../../credit/CONTEXT.md), `credit/docs/`). - **Phase 2 Quantitative Value:** **cancelled** for active execution ([ADR-0003](../../docs/adr/0003-phase2-qv-cancelled.md)). Prefer updating specs over writing investing QV code. ## Canonical documentation - Portfolio map: `CONTEXT-MAP.md` -- Investing glossary: `CONTEXT.md` +- Investing glossary: `investing/CONTEXT.md` - Credit glossary: `credit/CONTEXT.md` - Domain READMEs: `investing/README.md`, `credit/README.md` - ADRs: `docs/adr/` -- Investing demo slice: `docs/mvp/demo-slice.md` -- Investing architecture (frozen vision): `docs/mvp/architecture/architecture.md` -- Investing features: `docs/mvp/features/*.md` -- Credit features (when present): `credit/docs/` -- Requirements (historical): `docs/mvp/requirements/requirements.md` -- Backlog: `docs/mvp/backlog/backlog.md` +- Investing demo slice: `investing/docs/demo-slice.md` +- Investing architecture (frozen vision): `investing/docs/architecture/architecture.md` +- Investing features: `investing/docs/features/*.md` +- Credit features: `credit/docs/` +- Requirements (historical): `investing/docs/requirements/requirements.md` +- Backlog: `investing/docs/backlog/backlog.md` ## Not canonical -Do not use `notebooks/` as reference architecture. Do not treat investing `src/` layout as the template for credit — new credit code lands under `credit/`. +Do not use root `notebooks/` as reference architecture. Domain packages live under `investing/src/investing/` and `credit/src/credit/`. ## Code changes -Investing implementation changes must trace to a feature spec under `docs/mvp/features/` or the demo slice. Credit changes must trace to specs under `credit/docs/` (create the spec first). Read `CONTEXT-MAP.md` and the matching domain CONTEXT before proposing design or code. +Investing implementation changes must trace to a feature spec under `investing/docs/features/` or the demo slice. Credit changes must trace to specs under `credit/docs/` (create the spec first). Read `CONTEXT-MAP.md` and the matching domain CONTEXT before proposing design or code. diff --git a/.cursor/rules/spec-driven-workflow.mdc b/.cursor/rules/spec-driven-workflow.mdc index 692beae..10d28df 100644 --- a/.cursor/rules/spec-driven-workflow.mdc +++ b/.cursor/rules/spec-driven-workflow.mdc @@ -8,10 +8,10 @@ alwaysApply: true ## Before coding 1. Name the feature spec file: - - Investing: `docs/mvp/features/.md` (e.g. `cheap-stocks.md`) + - Investing: `investing/docs/features/.md` (e.g. `cheap-stocks.md`) - Credit: under `credit/docs/` (e.g. `credit/docs/features/css-chapter5-mart-and-scoring.md`) 2. Read domain constraints: - - Investing: `docs/mvp/architecture/architecture.md` (frozen historical vision; point-in-time data, exclusions, MLflow, paper trading, universe rules) + - Investing: `investing/docs/architecture/architecture.md` (frozen historical vision; point-in-time data, exclusions, MLflow, paper trading, universe rules) - Credit: `credit/CONTEXT.md` + `CONTEXT-MAP.md` (+ the feature spec) 3. If scope is unclear, ask the user and record the answer in the spec (open questions or decisions tables). diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 315c531..43966b7 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -40,7 +40,7 @@ jobs: - name: Test with coverage # Match [tool.coverage.run]: both packages + branch=true (mixing CLI # statement-only --cov with pyproject branch data fails combine). - run: poetry run pytest --cov=smartwealthai --cov=credit --cov-branch --cov-report=term-missing --cov-report=xml + run: poetry run pytest --cov=investing --cov=credit --cov-branch --cov-report=term-missing --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 diff --git a/AGENTS.md b/AGENTS.md index 9a8e640..d7fbdc2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,31 +8,31 @@ SmartWealthAI is a **portfolio monorepo** (investing + credit; fraud later). See | Domain | Status | | --- | --- | -| **Investing** (Magic Formula demo) | **Delivered** (v0.1.0). Specs under `docs/mvp/`; code still under `src/smartwealthai` until a migrate-only PR. | +| **Investing** (Magic Formula demo) | **Delivered** (v0.1.0). Code and specs under `investing/` (`investing/src/investing/`, `investing/docs/`, `investing/CONTEXT.md`). | | **Credit** (CSS) | **Active next.** Specs and new code under `credit/` (`credit/docs/`, `credit/CONTEXT.md`). | | **Phase 2 Quantitative Value** | **Cancelled** for active execution ([ADR-0003](docs/adr/0003-phase2-qv-cancelled.md); branch `archive/phase2-qv`). | -Investing changes must align with a feature spec in `docs/mvp/features/` and [`docs/mvp/architecture/architecture.md`](docs/mvp/architecture/architecture.md) (frozen historical vision — not a live QV north star). Credit changes align with specs under `credit/docs/`. +Investing changes must align with a feature spec in `investing/docs/features/` and [`investing/docs/architecture/architecture.md`](investing/docs/architecture/architecture.md) (frozen historical vision — not a live QV north star). Credit changes align with specs under `credit/docs/`. -Do not treat `src/` or `notebooks/` as canonical architecture for new domains; investing code there is the demo delivery, not a template to copy for credit. +Do not treat root `notebooks/` as canonical architecture. Domain code lives under `investing/src/investing/` and `credit/src/credit/`. ## Documentation map (source of truth) | Path | Purpose | | --- | --- | | `CONTEXT-MAP.md` | Portfolio domain map, relationships, delivery sequencing | -| `CONTEXT.md` | Investing ubiquitous language (until moved to `investing/CONTEXT.md`) | +| `investing/CONTEXT.md` | Investing ubiquitous language | | `credit/CONTEXT.md` | Credit / CSS ubiquitous language | | `investing/README.md` | Investing domain entry (manager-facing) | | `credit/README.md` | Credit domain entry (manager-facing) | | `credit/docs/` | Credit feature specs (when present) | | `docs/adr/*.md` | Architecture Decision Records (hard-to-reverse choices) | -| `docs/mvp/demo-slice.md` | Investing June 30 demo slice (delivered) | -| `docs/mvp/architecture/architecture.md` | Investing MVP vision (frozen; see ADR-0003) | -| `docs/mvp/features/*.md` | Investing per-module specs | -| `docs/mvp/requirements/requirements.md` | Sprint 0 spike (historical) | -| `docs/mvp/backlog/backlog.md` | Informal ideas mapped to investing features | -| `docs/README.md` | Index of the `docs/` tree | +| `investing/docs/demo-slice.md` | Investing June 30 demo slice (delivered) | +| `investing/docs/architecture/architecture.md` | Investing MVP vision (frozen; see ADR-0003) | +| `investing/docs/features/*.md` | Investing per-module specs | +| `investing/docs/requirements/requirements.md` | Sprint 0 spike (historical) | +| `investing/docs/backlog/backlog.md` | Informal ideas mapped to investing features | +| `docs/README.md` | Index of root `docs/` (ADRs) and pointers to domain docs | See also `.cursor/rules/*.mdc` for persistent agent guidance. @@ -42,8 +42,8 @@ Write all code, comments, docstrings, documentation, commits, and PR text in **E ## Spec-driven workflow -1. Identify the feature spec: investing → `docs/mvp/features/.md`; credit → `credit/docs/` (when present). -2. Read the matching architecture/context: investing → `docs/mvp/architecture/architecture.md` + `CONTEXT.md`; credit → `credit/CONTEXT.md` + `CONTEXT-MAP.md`. +1. Identify the feature spec: investing → `investing/docs/features/.md`; credit → `credit/docs/` (when present). +2. Read the matching architecture/context: investing → `investing/docs/architecture/architecture.md` + `investing/CONTEXT.md`; credit → `credit/CONTEXT.md` + `CONTEXT-MAP.md`. 3. Resolve open questions in the spec; record decisions in the spec or ADR. 4. Implement only what the spec allows for the current phase. 5. After implementation, update the feature spec (implementation status, acceptance criteria, links to code when it exists). @@ -86,7 +86,7 @@ export SIMFIN_API_KEY="" poetry run download-simfin ``` -Spec: [`docs/mvp/features/etl-data-lake.md`](docs/mvp/features/etl-data-lake.md). Operator guide: [`docs/mvp/guides/download-simfin.md`](docs/mvp/guides/download-simfin.md). Delivery target: [`docs/mvp/demo-slice.md`](docs/mvp/demo-slice.md). +Spec: [`investing/docs/features/etl-data-lake.md`](investing/docs/features/etl-data-lake.md). Operator guide: [`investing/docs/guides/download-simfin.md`](investing/docs/guides/download-simfin.md). Delivery target: [`investing/docs/demo-slice.md`](investing/docs/demo-slice.md). ### Fundamentals — SEC spike (frozen) @@ -95,7 +95,7 @@ export SEC_IDENTITY="Your Name your@email.com" poetry run download-fundamentals --universe dow30 ``` -Guide: [`docs/mvp/guides/download-fundamentals.md`](docs/mvp/guides/download-fundamentals.md). +Guide: [`investing/docs/guides/download-fundamentals.md`](investing/docs/guides/download-fundamentals.md). ## Gotchas @@ -107,7 +107,7 @@ Guide: [`docs/mvp/guides/download-fundamentals.md`](docs/mvp/guides/download-fun ## Notion (task tracking) -Git specs are canonical (`docs/mvp/` for investing; `credit/docs/` for credit). Notion tracks execution tasks only. Setup: [`docs/mvp/NOTION_SETUP.md`](docs/mvp/NOTION_SETUP.md). +Git specs are canonical (`investing/docs/` for investing; `credit/docs/` for credit). Notion tracks execution tasks only. Setup: [`investing/docs/NOTION_SETUP.md`](investing/docs/NOTION_SETUP.md). ### Default task board @@ -119,7 +119,7 @@ Git specs are canonical (`docs/mvp/` for investing; `credit/docs/` for credit). Agents must use the Notion MCP server (authenticated in **Cursor → Settings → MCP**) to find and update this board. Search the workspace for `Cursor Agent Tasks` before creating or editing tasks. Do not ask the user to commit a Notion URL to the repository. -Each task should reference a spec path (e.g. `docs/mvp/features/universe-construction.md` or `credit/docs/…`) and copy acceptance criteria from that spec. When a task completes, update the Git spec first, then mark the Notion task done. +Each task should reference a spec path (e.g. `investing/docs/features/universe-construction.md` or `credit/docs/…`) and copy acceptance criteria from that spec. When a task completes, update the Git spec first, then mark the Notion task done. **Skills (after MCP auth):** `spec-to-implementation`, `create-task`, `tasks-build`, `tasks-explain-diff`. For `tasks-build`, the user supplies a single task URL in chat (not in this file). @@ -139,4 +139,4 @@ Default vocabulary: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for- ### Domain docs -Portfolio map: `CONTEXT-MAP.md`. Investing glossary: `CONTEXT.md`. Credit glossary: `credit/CONTEXT.md`. ADRs in `docs/adr/`. See [`.cursor/rules/domain.md`](.cursor/rules/domain.md). +Portfolio map: `CONTEXT-MAP.md`. Investing glossary: `investing/CONTEXT.md`. Credit glossary: `credit/CONTEXT.md`. ADRs in `docs/adr/`. See [`.cursor/rules/domain.md`](.cursor/rules/domain.md). diff --git a/CONTEXT-MAP.md b/CONTEXT-MAP.md index 2fb7ee1..0995632 100644 --- a/CONTEXT-MAP.md +++ b/CONTEXT-MAP.md @@ -2,26 +2,24 @@ SmartWealthAI is a **portfolio monorepo**: several financial domains in one repo so interviewers (including managers) can see end-to-end finance engineering at a glance. Domains share platform patterns only when useful; they do **not** share domain language. -## Repo shape (domain-first — D1, materialization B+) +## Repo shape (domain-first) Top-level folders are **products/domains**, not technical layers: | Path | Domain | | --- | --- | -| `investing/` | Value investing / Magic Formula — **README + docs skeleton now**; demo code may remain under `src/smartwealthai` / `apps/dashboard` / `docs/mvp` until a dedicated migrate-only PR | -| `credit/` | Credit scoring / scorecard — **active**; code and specs under `credit/` (`credit/docs/`, not `docs/mvp/features/`) | +| `investing/` | Value investing / Magic Formula — code, specs, dashboard, and tests live here (`investing/src/investing/`, `investing/docs/`, `investing/apps/dashboard/`, `investing/tests/`) | +| `credit/` | Credit scoring / scorecard — **active**; code and specs under `credit/` (`credit/docs/`, `credit/tests/`) | | `fraud/` | Fraud detection (future — create when that module starts) | | `platform/` | Shared infra (Terraform, AWS, shared API) — **only after** domain demos work locally | -**B+ rule:** do not big-bang-move investing code while starting credit. Root + domain READMEs carry the manager-facing story; migrate `investing/` code later in an isolated PR with tests green. - -**Packaging:** one Poetry project / one `poetry install`. Register `credit` as a second package alongside `src/smartwealthai` (PEP 621 scripts for credit CLIs). **Stay on Poetry for credit v1**; Poetry→uv is a later isolated tooling PR after the credit demo works. Do not put a second `pyproject.toml` under `credit/`. +**Packaging:** one Poetry project / one `poetry install`. Register `investing` and `credit` as packages (`investing/src`, `credit/src`) with PEP 621 scripts. Poetry→uv is a later isolated tooling PR (#182). Do not put a second `pyproject.toml` under domain folders in this phase. Do **not** put shared `ingest/`, `benchmarks/`, or `serving/` at the repo root as peers of domains. ## Contexts -- [Equity screening / investing](./CONTEXT.md) — quantitative value / Magic Formula on US equities. Glossary lives here until moved to `investing/CONTEXT.md`. +- [Equity screening / investing](./investing/CONTEXT.md) — quantitative value / Magic Formula on US equities. - [Credit risk — CSS](./credit/CONTEXT.md) — application credit scoring / scorecard (Elliot Taehun Kim, *Financial AI in Practice*, chapters 5–6). - Fraud — not started. @@ -34,7 +32,7 @@ Do **not** put shared `ingest/`, `benchmarks/`, or `serving/` at the repo root a ## Delivery sequencing (credit) -Feature specs land under [`credit/docs/`](credit/docs/) (index: [`credit/docs/README.md`](credit/docs/README.md)). Investing specs stay in `docs/mvp/features/` until a migrate-only PR. +Feature specs land under [`credit/docs/`](credit/docs/) (index: [`credit/docs/README.md`](credit/docs/README.md)). Investing specs live under [`investing/docs/`](investing/docs/). 1. **Book content first** (local), chapter order: ch.5 scratch → ch.6 OptBinning/monitoring/explainability. 2. **Chapter 5 delivery:** target **C** in **two PRs** — **PR1 = mart** (load/clean/validate); **PR2 = WOE/IV + XGBoost + probability → credit score (**book scaling params**; refine later) + CLI (**AUC + KS**); **rank-only** (cutoff/profit trade-off later)**. Chapter 6 is a later cut. Train/holdout: **time-based when a decision/application date exists, else stratified random** (document the limitation); fit WOE/model on develop only. diff --git a/Makefile b/Makefile index d7b0298..465f5f6 100644 --- a/Makefile +++ b/Makefile @@ -8,9 +8,9 @@ lint: poetry run ruff format --check . test: - poetry run pytest --cov=smartwealthai --cov=credit --cov-branch --cov-report=term-missing + poetry run pytest --cov=investing --cov=credit --cov-branch --cov-report=term-missing -# Requires SEC_IDENTITY in the environment. See docs/mvp/guides/download-fundamentals.md +# Requires SEC_IDENTITY in the environment. See investing/docs/guides/download-fundamentals.md download-fundamentals: poetry run download-fundamentals --universe dow30 diff --git a/README.md b/README.md index f504707..128f1bd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ | **Fraud** | `fraud/` | Not started | | **Platform** (AWS / Terraform) | `platform/` | After domain demos work locally | -Map & relationships: [`CONTEXT-MAP.md`](CONTEXT-MAP.md) · Investing glossary: [`CONTEXT.md`](CONTEXT.md) · Credit glossary: [`credit/CONTEXT.md`](credit/CONTEXT.md) +Map & relationships: [`CONTEXT-MAP.md`](CONTEXT-MAP.md) · Investing glossary: [`investing/CONTEXT.md`](investing/CONTEXT.md) · Credit glossary: [`credit/CONTEXT.md`](credit/CONTEXT.md) --- @@ -20,7 +20,7 @@ Map & relationships: [`CONTEXT-MAP.md`](CONTEXT-MAP.md) · Investing glossary: [ Greenblatt-style ranking on US equities: SimFin → PIT lake → ROC + EY → top-30 EW model portfolio → Streamlit + MLflow. -*Code today:* `src/smartwealthai/` + `apps/dashboard/` + [`docs/mvp/`](docs/mvp/) (migration into `investing/` is a later housekeeping PR). +*Code:* [`investing/src/investing/`](investing/src/investing/) · Dashboard: [`investing/apps/dashboard/`](investing/apps/dashboard/) · Specs: [`investing/docs/`](investing/docs/) ### Quickstart (investing demo) @@ -31,11 +31,11 @@ poetry run run-demo-pipeline --run-date 2026-06-18 poetry run run-dashboard --data-dir data --run-date 2026-06-18 ``` -See [`investing/README.md`](investing/) and [`docs/mvp/demo-slice.md`](docs/mvp/demo-slice.md). +See [`investing/README.md`](investing/) and [`investing/docs/demo-slice.md`](investing/docs/demo-slice.md). ### Tech stack (investing) -* **Language:** Python 3.11+ · **Deps:** Poetry (uv migration later, after credit demo works) +* **Language:** Python 3.11+ · **Deps:** Poetry (uv migration — #182) * **Data:** SimFin bulk · **Libs:** pandas, simfin, yfinance, streamlit, mlflow-skinny --- @@ -48,10 +48,10 @@ Application **CSS**: public demo data → application mart → scoring → rank ## Roadmap (portfolio) -1. **Credit PR1–PR2** — mart, then chapter 5 scratch model + score (local CLI). -2. **Credit chapter 6** — OptBinning scorecard, monitoring, explainability. +1. **Credit** — chapter 6 OptBinning scorecard, monitoring, explainability (as scheduled). +2. **Tooling** — Poetry → uv workspace (#182) + domain deps / CI matrix (#183). 3. **`platform/`** — Terraform / AWS; orchestrator choice open (Airflow vs EventBridge/ECS vs Prefect). -4. Optional: migrate investing code into `investing/`; Poetry → uv; fraud module. +4. Optional: Spec Kit (#178); fraud module. Phase 2 Quantitative Value (as production investing path) is **not** in active execution ([ADR-0003](docs/adr/0003-phase2-qv-cancelled.md); branch `archive/phase2-qv`). @@ -59,4 +59,4 @@ Phase 2 Quantitative Value (as production investing path) is **not** in active e ## How this repo is built -Specs in Git (`docs/mvp/`, `credit/docs/`, ADRs, CONTEXT) drive implementation — **spec-driven development** with Cursor agents. Decisions land in Markdown first; code follows the matching feature doc. See [`AGENTS.md`](AGENTS.md) and [`docs/README.md`](docs/README.md). +Specs in Git (`investing/docs/`, `credit/docs/`, ADRs, CONTEXT) drive implementation — **spec-driven development** with Cursor agents. Decisions land in Markdown first; code follows the matching feature doc. See [`AGENTS.md`](AGENTS.md) and [`docs/README.md`](docs/README.md). diff --git a/apps/dashboard/__init__.py b/apps/dashboard/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/credit/README.md b/credit/README.md index 919c72a..4d32e91 100644 --- a/credit/README.md +++ b/credit/README.md @@ -12,7 +12,7 @@ This is **not** fraud detection and **not** the investing screener. ## Data (demo only) -No live bank BFSI data. Book sample (AMEX-shaped) is in Git LFS at [`credit/data/train_df_sample.pkl`](data/train_df_sample.pkl) — see [`credit/data/README.md`](data/README.md) for schema, **target** → **bad**/**good**, and `git lfs pull`. **Priority:** official Kaggle **raw competition extracts** (AMEX + Home Credit) under gitignored `data/credit/raw/` (#151) — not committed; access-only (no submissions). FICO HELOC documented for later. Hermetic CI uses `tests/credit/fixtures/`. Always document the **target** → **bad**/**good** mapping on the mart README sidecar. +No live bank BFSI data. Book sample (AMEX-shaped) is in Git LFS at [`credit/data/train_df_sample.pkl`](data/train_df_sample.pkl) — see [`credit/data/README.md`](data/README.md) for schema, **target** → **bad**/**good**, and `git lfs pull`. **Priority:** official Kaggle **raw competition extracts** (AMEX + Home Credit) under gitignored `data/credit/raw/` (#151) — not committed; access-only (no submissions). FICO HELOC documented for later. Hermetic CI uses `credit/tests/fixtures/`. Always document the **target** → **bad**/**good** mapping on the mart README sidecar. ### Local mart + EDA (book sample) @@ -56,7 +56,7 @@ Mart parquet under `data/credit/` is gitignored. Fixture CSV path remains the he ```bash poetry run credit-css build-application-mart \ - --source tests/credit/fixtures/application_source/applications.csv \ + --source credit/tests/fixtures/application_source/applications.csv \ --output-dir data/credit/application_mart ``` @@ -66,7 +66,7 @@ Hermetic fixture (CI): ```bash poetry run credit-css build-application-mart \ - --source tests/credit/fixtures/scoring_mart/applications.csv \ + --source credit/tests/fixtures/scoring_mart/applications.csv \ --output-dir /tmp/credit_mart \ --application-id-column customer_ID \ --target-column target \ @@ -116,7 +116,7 @@ Operator path: **fit → set artifact env → serve → curl**. The API loads th ```bash # 1) Fit (hermetic fixture shown; any application mart works) poetry run credit-css build-application-mart \ - --source tests/credit/fixtures/scoring_mart/applications.csv \ + --source credit/tests/fixtures/scoring_mart/applications.csv \ --output-dir /tmp/credit_mart \ --application-id-column customer_ID \ --target-column target --bad-value 1 --good-value 0 @@ -137,10 +137,10 @@ poetry run credit-css-serve curl -s localhost:8000/health curl -s localhost:8000/score \ -H 'content-type: application/json' \ - -d @tests/credit/fixtures/serving/score_request.json + -d @credit/tests/fixtures/serving/score_request.json curl -s localhost:8000/drivers \ -H 'content-type: application/json' \ - -d @tests/credit/fixtures/serving/drivers_request.json + -d @credit/tests/fixtures/serving/drivers_request.json ``` Serving image (mount the fit output directory): @@ -163,7 +163,7 @@ Default path reproduces the same develop/holdout split as `fit`. Writes `psi_rep ```bash poetry run credit-css build-application-mart \ - --source tests/credit/fixtures/psi_mart/applications.csv \ + --source credit/tests/fixtures/psi_mart/applications.csv \ --output-dir /tmp/credit_mart_psi \ --application-id-column customer_ID \ --target-column target --bad-value 1 --good-value 0 @@ -213,14 +213,14 @@ CLI (package scaffold [#143](https://github.com/JLaborda/SmartWealthAI/issues/14 ```bash poetry run credit-css --help poetry run credit-css build-application-mart \ - --source tests/credit/fixtures/application_source/applications.csv \ + --source credit/tests/fixtures/application_source/applications.csv \ --output-dir data/credit/application_mart poetry run credit-css fit --help poetry run credit-css score --help poetry run python -m credit build-application-mart --help ``` -Hermetic CI uses fixtures under `tests/credit/fixtures/` (including `scoring_mart/` for #145). The mart README sidecar documents **target** → **bad**/**good** and the develop/holdout policy. +Hermetic CI uses fixtures under `credit/tests/fixtures/` (including `scoring_mart/` for #145). The mart README sidecar documents **target** → **bad**/**good** and the develop/holdout policy. Feature spec: [`docs/features/css-chapter5-mart-and-scoring.md`](docs/features/css-chapter5-mart-and-scoring.md) (parent [#142](https://github.com/JLaborda/SmartWealthAI/issues/142)). diff --git a/credit/data/README.md b/credit/data/README.md index 4557726..605b967 100644 --- a/credit/data/README.md +++ b/credit/data/README.md @@ -81,7 +81,7 @@ poetry run credit-css build-competition-mart \ Downstream: competition EDA (#153 / guided #159), then scratch scoring (#145). -Hermetic CI uses `tests/credit/fixtures/` (tiny AMEX statements + Home Credit application rows). +Hermetic CI uses `credit/tests/fixtures/` (tiny AMEX statements + Home Credit application rows). ## Mart regeneration (book sample) diff --git a/credit/docs/README.md b/credit/docs/README.md index 05a45b3..ec62240 100644 --- a/credit/docs/README.md +++ b/credit/docs/README.md @@ -1,8 +1,8 @@ # Credit documentation -Feature specs for the credit / CSS domain live **here** (`credit/docs/`), not under `docs/mvp/features/`. +Feature specs for the credit / CSS domain live **here** (`credit/docs/`), not under `investing/docs/features/`. -Investing Magic Formula specs remain in [`docs/mvp/`](../docs/mvp/) until a migrate-only PR. +Investing Magic Formula specs remain in [`investing/docs/`](../investing/docs/) until a migrate-only PR. ## Layout diff --git a/credit/docs/features/aws-fargate-serve-demo.md b/credit/docs/features/aws-fargate-serve-demo.md index df6f101..c20e25e 100644 --- a/credit/docs/features/aws-fargate-serve-demo.md +++ b/credit/docs/features/aws-fargate-serve-demo.md @@ -5,7 +5,7 @@ **Domain:** credit / CSS (demo surface) + shared `platform/` (IaC) **Glossary:** [`../../CONTEXT.md`](../../CONTEXT.md) (domain only — no AWS terms) · Map: [`../../../CONTEXT-MAP.md`](../../../CONTEXT-MAP.md) **Related:** chapter 5 [`css-chapter5-mart-and-scoring.md`](css-chapter5-mart-and-scoring.md) · PSI [`psi-data-drift.md`](psi-data-drift.md) · Friday MUST [`../guides/friday-demo.md`](../guides/friday-demo.md) · [ADR-0004](../../../docs/adr/0004-platform-ephemeral-fargate-serve.md) · bake guide [`../guides/serve-fastapi.md`](../guides/serve-fastapi.md) · operator [`../../../platform/docs/ephemeral-css-serve-demo.md`](../../../platform/docs/ephemeral-css-serve-demo.md) -**Code:** `credit/demo_serve_artifact.py`, `credit/Dockerfile.serve.demo`, `platform/scripts/prepare-serve-demo-artifact.sh`, `platform/terraform/credit-css-serve-demo/`, `platform/scripts/demo-up.sh`, `platform/scripts/demo-down.sh` · Tests: `tests/credit/test_serve_demo_bake.py` (API seam); live up/curl/down = HITL +**Code:** `credit/demo_serve_artifact.py`, `credit/Dockerfile.serve.demo`, `platform/scripts/prepare-serve-demo-artifact.sh`, `platform/terraform/credit-css-serve-demo/`, `platform/scripts/demo-up.sh`, `platform/scripts/demo-down.sh` · Tests: `credit/tests/test_serve_demo_bake.py` (API seam); live up/curl/down = HITL ## Problem Statement @@ -60,7 +60,7 @@ No S3 data lake, no auth, no GitHub CD in this cut. **Primary seam (one):** operator `demo-up` → HTTP `/health` + `/score` + `/drivers` with existing serving fixture JSON → `demo-down`. Assert external behaviour only (reachable URL, successful JSON responses, stack gone after destroy). -**Prior art:** `tests/credit/test_api_serving.py` and friday-demo / serve-fastapi guides cover the API contract hermetically. Do **not** re-test WOE/XGB in this feature. +**Prior art:** `credit/tests/test_api_serving.py` and friday-demo / serve-fastapi guides cover the API contract hermetically. Do **not** re-test WOE/XGB in this feature. **CI:** no live `terraform apply` on PR (credentials + cost). Optional later: `terraform fmt` / `validate` only if cheap to add; not required to call this slice done. diff --git a/credit/docs/features/css-chapter5-mart-and-scoring.md b/credit/docs/features/css-chapter5-mart-and-scoring.md index f1d27f8..6e4fd40 100644 --- a/credit/docs/features/css-chapter5-mart-and-scoring.md +++ b/credit/docs/features/css-chapter5-mart-and-scoring.md @@ -2,7 +2,7 @@ **Status:** in progress — book path + competition raw (#151) + competition mart (#152) + AMEX categoricals ([#157](https://github.com/JLaborda/SmartWealthAI/issues/157)) done; **scratch scoring + thin MLOps (#145 / #161)** done on develop; **FastAPI serving** (score + risk drivers, same `pipeline.joblib`) active this cut; local PSI done; Terraform stretch → [`aws-fargate-serve-demo.md`](aws-fargate-serve-demo.md); competition EDA (#153) remains parallel **GitHub:** parent [#142](https://github.com/JLaborda/SmartWealthAI/issues/142) · done: [#143](https://github.com/JLaborda/SmartWealthAI/issues/143) → [#144](https://github.com/JLaborda/SmartWealthAI/issues/144) → [#147](https://github.com/JLaborda/SmartWealthAI/issues/147) → [#151](https://github.com/JLaborda/SmartWealthAI/issues/151) → [#152](https://github.com/JLaborda/SmartWealthAI/issues/152) · [#157](https://github.com/JLaborda/SmartWealthAI/issues/157) · [#145](https://github.com/JLaborda/SmartWealthAI/issues/145)/[#161](https://github.com/JLaborda/SmartWealthAI/pull/161) · **active:** FastAPI serving (Friday demo week) · parallel: [#153](https://github.com/JLaborda/SmartWealthAI/issues/153), PSI CLI, [#162](https://github.com/JLaborda/SmartWealthAI/pull/162) glossary -**Code:** `credit/src/credit/application_mart.py`, `credit/src/credit/competition_mart.py`, `credit/src/credit/scoring_pipeline.py`, `credit/src/credit/api.py`, `credit/src/credit/binning.py`, `credit/src/credit/model.py`, `credit/src/credit/cli.py` · Notebook: `credit/notebooks/eda_application_mart.ipynb` (book-sample smoke; optional only) · Tests: `tests/credit/test_application_mart.py`, `tests/credit/test_competition_mart.py`, `tests/credit/test_scoring_pipeline.py`, `tests/credit/test_api_serving.py` · Sample: `credit/data/train_df_sample.pkl` (Git LFS) · Artifact: `pipeline.joblib` · Images: `credit/Dockerfile` (batch score), `credit/Dockerfile.serve` (FastAPI) +**Code:** `credit/src/credit/application_mart.py`, `credit/src/credit/competition_mart.py`, `credit/src/credit/scoring_pipeline.py`, `credit/src/credit/api.py`, `credit/src/credit/binning.py`, `credit/src/credit/model.py`, `credit/src/credit/cli.py` · Notebook: `credit/notebooks/eda_application_mart.ipynb` (book-sample smoke; optional only) · Tests: `credit/tests/test_application_mart.py`, `credit/tests/test_competition_mart.py`, `credit/tests/test_scoring_pipeline.py`, `credit/tests/test_api_serving.py` · Sample: `credit/data/train_df_sample.pkl` (Git LFS) · Artifact: `pipeline.joblib` · Images: `credit/Dockerfile` (batch score), `credit/Dockerfile.serve` (FastAPI) **Domain:** credit / CSS (Credit Scoring System) **Glossary:** [`../../CONTEXT.md`](../../CONTEXT.md) · Map: [`../../../CONTEXT-MAP.md`](../../../CONTEXT-MAP.md) **Book:** *Financial AI in Practice* chapters 5–6 (chapter 5 only in this spec) @@ -69,7 +69,7 @@ Deliver chapter 5 of the book as a real `credit` Poetry package and CLIs (local, - **FastAPI serving (Friday demo week, locked):** lives under `credit/` (not `platform/`). Loads the **same** `pipeline.joblib` via env `CREDIT_PIPELINE_ARTIFACT` (or `create_app(artifact_path=...)`). Startup **fails clearly** if the path is unset or the file is missing (`resolve_artifact_path` / `credit-css-serve` exit 2). Two POSTs: 1. **`POST /score`** — body = feature JSON (`application_id` + `features` map). Response: `pd`, `credit_score`, `rank` (single-application requests return `rank=1`; rank-only, no cutoff). 2. **`POST /drivers`** — same feature JSON + optional `top_k` (default 5). Response: top-k **risk drivers** ranked by **gain × |WOE|** for that row (XGBoost gain × absolute WOE of the applicant’s bin). **SHAP** on drivers = stretch / later. - Example payloads: `tests/credit/fixtures/serving/score_request.json` and `drivers_request.json` (from scoring fixture rows), never gitignored `data/credit/` marts. Operator guide: [`../guides/serve-fastapi.md`](../guides/serve-fastapi.md). Serving image: `credit/Dockerfile.serve` (uvicorn). Terraform / AWS = out of this slice. + Example payloads: `credit/tests/fixtures/serving/score_request.json` and `drivers_request.json` (from scoring fixture rows), never gitignored `data/credit/` marts. Operator guide: [`../guides/serve-fastapi.md`](../guides/serve-fastapi.md). Serving image: `credit/Dockerfile.serve` (uvicorn). Terraform / AWS = out of this slice. - **Score scaling params (locked):** book chapter 5 numbers (PDO=20, base_score=650, base_odds=20) with **PDO polarity** — odds = `(1−PD)/PD` (good:bad); +20 points ≈ doubles good-borrower odds; clip `[250, 1000]`. Higher `credit_score` = safer; rank 1 = highest score. (Notebook cell that used `PD/(1−PD)` inverted this; we follow the book PDO prose.) Tagged `scaling_source=book_chapter5_pdo_good_odds`. - **Mart schema change communication (owner decision):** green tests are not enough. For every credit stage that creates, drops, or renames mart columns / changes grain (raw → application), the change must (1) say so in the PR/agent summary in plain language, (2) update the mart README sidecar and `credit/data/README.md` in the same change, and (3) open or update a GitHub issue plus a line in this feature spec **before** merge. Example that failed this bar once: silent loss of AMEX categoricals before [#157](https://github.com/JLaborda/SmartWealthAI/issues/157). - **Delivery:** scaffold (#143) → mart (#144) → book EDA (#147) → competition raw (#151) → competition mart (#152) → fix AMEX cats (#157) → scratch scoring + thin MLOps (#145/#161) → FastAPI serving → local PSI → Terraform stretch ([`aws-fargate-serve-demo.md`](aws-fargate-serve-demo.md)) → competition-backed retrains if needed. @@ -80,7 +80,7 @@ Deliver chapter 5 of the book as a real `credit` Poetry package and CLIs (local, - Hermetic fixtures under the credit test tree; no network in unit/CI tests. - Prior art: investing SimFin normalizer tests (`normalize_simfin` result + written parquet assertions) — mirror that style for the mart. - PR2: assert CLI or pure function returns AUC/KS and produces ranked scores on a tiny fixture; do not assert internal WOE bin edges unless they are part of a documented public contract. -- FastAPI serving: hermetic `TestClient` tests against an app built with a fixture-fitted `pipeline.joblib`; assert `/score` returns `pd` / `credit_score` / `rank` and `/drivers` returns ordered top-k feature names with gain×|WOE| scores. Example request bodies are derived from `tests/credit/fixtures/scoring_mart/` rows. +- FastAPI serving: hermetic `TestClient` tests against an app built with a fixture-fitted `pipeline.joblib`; assert `/score` returns `pd` / `credit_score` / `rank` and `/drivers` returns ordered top-k feature names with gain×|WOE| scores. Example request bodies are derived from `credit/tests/fixtures/scoring_mart/` rows. ## Out of Scope diff --git a/credit/docs/features/psi-data-drift.md b/credit/docs/features/psi-data-drift.md index 099cad0..fd3bad1 100644 --- a/credit/docs/features/psi-data-drift.md +++ b/credit/docs/features/psi-data-drift.md @@ -2,7 +2,7 @@ **Status:** done — Friday MUST (local green demo); hermetic tests green **GitHub:** Friday demo week (monitoring grill 2026-10-06) · parent CSS [#142](https://github.com/JLaborda/SmartWealthAI/issues/142) -**Code:** `credit/src/credit/psi.py`, `credit/src/credit/cli.py` (`psi` command) · Tests: `tests/credit/test_psi.py` · Artifacts: `psi_report.md`, `psi_report.json` +**Code:** `credit/src/credit/psi.py`, `credit/src/credit/cli.py` (`psi` command) · Tests: `credit/tests/test_psi.py` · Artifacts: `psi_report.md`, `psi_report.json` **Domain:** credit / CSS (Credit Scoring System) **Glossary:** [`../../CONTEXT.md`](../../CONTEXT.md) · Map: [`../../../CONTEXT-MAP.md`](../../../CONTEXT-MAP.md) **Related:** chapter 5 scoring [`css-chapter5-mart-and-scoring.md`](css-chapter5-mart-and-scoring.md) · operator guide [`../guides/friday-demo.md`](../guides/friday-demo.md) @@ -58,14 +58,14 @@ Zero / empty bins use a small epsilon so $\ln$ stays defined. Overall report lab - [x] `credit-css psi --mart … --artifact … --output-dir …` writes both report files. - [x] Stable scenario (hermetic `psi_mart` fixture, no synthetic drift): monitored columns labeled `stable`. - [x] `--synthetic-drift`: shifted feature columns have PSI ≥ severe threshold (and higher than the same column in the stable run). -- [x] Hermetic tests under `tests/credit/`; no secrets; no committed `data/credit/` marts. +- [x] Hermetic tests under `credit/tests/`; no secrets; no committed `data/credit/` marts. - [x] Operator notes in `credit/README.md` and `credit/docs/guides/friday-demo.md`. - [x] Glossary: monitoring v1 = PSI file report; interactive visualization later (no library names). ## Testing Decisions - Public seam: `run_psi_check(...)` (or equivalent) + thin CLI wrapper. -- Fixture: `tests/credit/fixtures/scoring_mart/applications.csv` → build mart → fit → psi. +- Fixture: `credit/tests/fixtures/scoring_mart/applications.csv` → build mart → fit → psi. - Assert report JSON schema keys, per-column labels for stable path, and elevated PSI under `--synthetic-drift`. ## Out of Scope diff --git a/credit/docs/guides/friday-demo.md b/credit/docs/guides/friday-demo.md index 9e74057..77eec11 100644 --- a/credit/docs/guides/friday-demo.md +++ b/credit/docs/guides/friday-demo.md @@ -12,7 +12,7 @@ Hermetic fixture path (safe for git; do not commit `data/credit/` marts): ```bash poetry run credit-css build-application-mart \ - --source tests/credit/fixtures/scoring_mart/applications.csv \ + --source credit/tests/fixtures/scoring_mart/applications.csv \ --output-dir /tmp/credit_mart \ --application-id-column customer_ID \ --target-column target --bad-value 1 --good-value 0 @@ -33,7 +33,7 @@ For a PSI-stable develop/holdout story with 10 quantile bins, prefer the larger ```bash poetry run credit-css build-application-mart \ - --source tests/credit/fixtures/psi_mart/applications.csv \ + --source credit/tests/fixtures/psi_mart/applications.csv \ --output-dir /tmp/credit_mart_psi \ --application-id-column customer_ID \ --target-column target --bad-value 1 --good-value 0 @@ -56,17 +56,17 @@ poetry run credit-css-serve Example payloads (fixture-derived): -- `tests/credit/fixtures/serving/score_request.json` -- `tests/credit/fixtures/serving/drivers_request.json` +- `credit/tests/fixtures/serving/score_request.json` +- `credit/tests/fixtures/serving/drivers_request.json` ```bash curl -s localhost:8000/health curl -s localhost:8000/score \ -H 'content-type: application/json' \ - -d @tests/credit/fixtures/serving/score_request.json + -d @credit/tests/fixtures/serving/score_request.json curl -s localhost:8000/drivers \ -H 'content-type: application/json' \ - -d @tests/credit/fixtures/serving/drivers_request.json + -d @credit/tests/fixtures/serving/drivers_request.json ``` Show: `pd`, `credit_score`, `rank` (rank-only, no cutoff); then top-k drivers by **gain × |WOE|** (not SHAP unless stretch landed). diff --git a/credit/docs/guides/serve-fastapi.md b/credit/docs/guides/serve-fastapi.md index 70a05d0..19d8e34 100644 --- a/credit/docs/guides/serve-fastapi.md +++ b/credit/docs/guides/serve-fastapi.md @@ -11,7 +11,7 @@ Loads the same `pipeline.joblib` produced by `credit-css fit` (WOE/IV binner + X ```bash poetry run credit-css build-application-mart \ - --source tests/credit/fixtures/scoring_mart/applications.csv \ + --source credit/tests/fixtures/scoring_mart/applications.csv \ --output-dir /tmp/credit_mart \ --application-id-column customer_ID \ --target-column target --bad-value 1 --good-value 0 @@ -45,17 +45,17 @@ If `CREDIT_PIPELINE_ARTIFACT` is unset or the file is missing, `credit-css-serve Example payloads (fixture-derived, safe for git): -- `tests/credit/fixtures/serving/score_request.json` -- `tests/credit/fixtures/serving/drivers_request.json` +- `credit/tests/fixtures/serving/score_request.json` +- `credit/tests/fixtures/serving/drivers_request.json` ```bash curl -s localhost:8000/score \ -H 'content-type: application/json' \ - -d @tests/credit/fixtures/serving/score_request.json + -d @credit/tests/fixtures/serving/score_request.json curl -s localhost:8000/drivers \ -H 'content-type: application/json' \ - -d @tests/credit/fixtures/serving/drivers_request.json + -d @credit/tests/fixtures/serving/drivers_request.json ``` ## Docker (volume mount) diff --git a/credit/notebooks/eda_application_mart.ipynb b/credit/notebooks/eda_application_mart.ipynb index 4cae565..4618367 100644 --- a/credit/notebooks/eda_application_mart.ipynb +++ b/credit/notebooks/eda_application_mart.ipynb @@ -176,7 +176,7 @@ "\n", "- Mart grain: one `customer_ID` per row.\n", "- Full Kaggle AMEX + Home Credit dumps are out of this PR.\n", - "- Hermetic CI uses `tests/credit/fixtures/application_source/applications.csv`, not this notebook." + "- Hermetic CI uses `credit/tests/fixtures/application_source/applications.csv`, not this notebook." ] } ], diff --git a/credit/src/credit/demo_serve_artifact.py b/credit/src/credit/demo_serve_artifact.py index 3601ff2..e37e889 100644 --- a/credit/src/credit/demo_serve_artifact.py +++ b/credit/src/credit/demo_serve_artifact.py @@ -10,8 +10,8 @@ # Default relative to repo root when callers pass only output_dir via the script. DEFAULT_SCORING_FIXTURE = ( Path(__file__).resolve().parents[3] - / "tests" / "credit" + / "tests" / "fixtures" / "scoring_mart" / "applications.csv" diff --git a/tests/credit/fixtures/amex_extract/train_data_tiny.csv b/credit/tests/fixtures/amex_extract/train_data_tiny.csv similarity index 100% rename from tests/credit/fixtures/amex_extract/train_data_tiny.csv rename to credit/tests/fixtures/amex_extract/train_data_tiny.csv diff --git a/tests/credit/fixtures/application_source/amex_sample.pkl b/credit/tests/fixtures/application_source/amex_sample.pkl similarity index 100% rename from tests/credit/fixtures/application_source/amex_sample.pkl rename to credit/tests/fixtures/application_source/amex_sample.pkl diff --git a/tests/credit/fixtures/application_source/applications.csv b/credit/tests/fixtures/application_source/applications.csv similarity index 100% rename from tests/credit/fixtures/application_source/applications.csv rename to credit/tests/fixtures/application_source/applications.csv diff --git a/tests/credit/fixtures/competition_mart/amex/train_labels.csv b/credit/tests/fixtures/competition_mart/amex/train_labels.csv similarity index 100% rename from tests/credit/fixtures/competition_mart/amex/train_labels.csv rename to credit/tests/fixtures/competition_mart/amex/train_labels.csv diff --git a/tests/credit/fixtures/competition_mart/home_credit/application_train.csv b/credit/tests/fixtures/competition_mart/home_credit/application_train.csv similarity index 100% rename from tests/credit/fixtures/competition_mart/home_credit/application_train.csv rename to credit/tests/fixtures/competition_mart/home_credit/application_train.csv diff --git a/tests/credit/fixtures/psi_mart/applications.csv b/credit/tests/fixtures/psi_mart/applications.csv similarity index 100% rename from tests/credit/fixtures/psi_mart/applications.csv rename to credit/tests/fixtures/psi_mart/applications.csv diff --git a/tests/credit/fixtures/scoring_mart/applications.csv b/credit/tests/fixtures/scoring_mart/applications.csv similarity index 100% rename from tests/credit/fixtures/scoring_mart/applications.csv rename to credit/tests/fixtures/scoring_mart/applications.csv diff --git a/tests/credit/fixtures/serving/drivers_request.json b/credit/tests/fixtures/serving/drivers_request.json similarity index 100% rename from tests/credit/fixtures/serving/drivers_request.json rename to credit/tests/fixtures/serving/drivers_request.json diff --git a/tests/credit/fixtures/serving/score_request.json b/credit/tests/fixtures/serving/score_request.json similarity index 100% rename from tests/credit/fixtures/serving/score_request.json rename to credit/tests/fixtures/serving/score_request.json diff --git a/tests/credit/test_amex_parquet_convert.py b/credit/tests/test_amex_parquet_convert.py similarity index 100% rename from tests/credit/test_amex_parquet_convert.py rename to credit/tests/test_amex_parquet_convert.py diff --git a/tests/credit/test_api_serving.py b/credit/tests/test_api_serving.py similarity index 100% rename from tests/credit/test_api_serving.py rename to credit/tests/test_api_serving.py diff --git a/tests/credit/test_application_mart.py b/credit/tests/test_application_mart.py similarity index 100% rename from tests/credit/test_application_mart.py rename to credit/tests/test_application_mart.py diff --git a/tests/credit/test_cli_competition_raw.py b/credit/tests/test_cli_competition_raw.py similarity index 100% rename from tests/credit/test_cli_competition_raw.py rename to credit/tests/test_cli_competition_raw.py diff --git a/tests/credit/test_cli_stub.py b/credit/tests/test_cli_stub.py similarity index 90% rename from tests/credit/test_cli_stub.py rename to credit/tests/test_cli_stub.py index 34d7951..23334e6 100644 --- a/tests/credit/test_cli_stub.py +++ b/credit/tests/test_cli_stub.py @@ -78,15 +78,15 @@ def test_credit_package_exposes_version(tmp_path: Path) -> None: def test_credit_does_not_import_investing_package(tmp_path: Path) -> None: - """Importing credit must not load smartwealthai (no investing/SimFin coupling).""" + """Importing credit must not load investing (no investing/SimFin coupling).""" probe = "\n".join( [ "import sys", - "assert 'smartwealthai' not in sys.modules", + "assert 'investing' not in sys.modules", "import credit", "import credit.cli", - "leaked = sorted(k for k in sys.modules if k.startswith('smart'))", - "assert 'smartwealthai' not in sys.modules, leaked", + "leaked = [k for k in sys.modules if k.split('.', 1)[0] == 'investing']", + "assert 'investing' not in sys.modules, leaked", ] ) result = subprocess.run( diff --git a/tests/credit/test_competition_mart.py b/credit/tests/test_competition_mart.py similarity index 100% rename from tests/credit/test_competition_mart.py rename to credit/tests/test_competition_mart.py diff --git a/tests/credit/test_competition_raw_stage.py b/credit/tests/test_competition_raw_stage.py similarity index 100% rename from tests/credit/test_competition_raw_stage.py rename to credit/tests/test_competition_raw_stage.py diff --git a/tests/credit/test_psi.py b/credit/tests/test_psi.py similarity index 100% rename from tests/credit/test_psi.py rename to credit/tests/test_psi.py diff --git a/tests/credit/test_scoring_coverage.py b/credit/tests/test_scoring_coverage.py similarity index 100% rename from tests/credit/test_scoring_coverage.py rename to credit/tests/test_scoring_coverage.py diff --git a/tests/credit/test_scoring_pipeline.py b/credit/tests/test_scoring_pipeline.py similarity index 100% rename from tests/credit/test_scoring_pipeline.py rename to credit/tests/test_scoring_pipeline.py diff --git a/tests/credit/test_serve_demo_bake.py b/credit/tests/test_serve_demo_bake.py similarity index 100% rename from tests/credit/test_serve_demo_bake.py rename to credit/tests/test_serve_demo_bake.py diff --git a/data/reference/universes/README.md b/data/reference/universes/README.md index d3014b7..c9641bc 100644 --- a/data/reference/universes/README.md +++ b/data/reference/universes/README.md @@ -38,7 +38,7 @@ curl -H "User-Agent: $SEC_IDENTITY" https://www.sec.gov/files/company_tickers.js | --- | --- | --- | --- | | `dow30` | `dow30.csv` | 30 | Current Dow Jones Industrial Average components. | -Register new presets in `UNIVERSE_PRESETS` (`src/smartwealthai/universe.py`). +Register new presets in `UNIVERSE_PRESETS` (`investing/src/investing/universe.py`). ## Maintenance diff --git a/docs/README.md b/docs/README.md index a5ba494..2ac1be3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,13 +1,13 @@ # SmartWealthAI documentation -Portfolio monorepo: domain map and relationships live in [`CONTEXT-MAP.md`](../CONTEXT-MAP.md). This `docs/` tree is the **versioned source of truth for investing (Magic Formula demo)** ADRs, architecture, and feature specs. Credit specs live under [`credit/docs/`](../credit/docs/), not here. Notion is used only for task tracking (see [AGENTS.md](../AGENTS.md)). +Portfolio monorepo: domain map and relationships live in [`CONTEXT-MAP.md`](../CONTEXT-MAP.md). This root `docs/` tree holds **cross-cutting ADRs**. Investing specs live under [`investing/docs/`](../investing/docs/); credit specs under [`credit/docs/`](../credit/docs/). Notion is used only for task tracking (see [AGENTS.md](../AGENTS.md)). ## Ubiquitous language | Domain | Glossary | | --- | --- | | Portfolio / domains | [`CONTEXT-MAP.md`](../CONTEXT-MAP.md) | -| Investing | [`CONTEXT.md`](../CONTEXT.md) (until moved to `investing/CONTEXT.md`) | +| Investing | [`investing/CONTEXT.md`](../investing/CONTEXT.md) | | Credit (CSS) | [`credit/CONTEXT.md`](../credit/CONTEXT.md) | Extend glossaries with `/grill-with-docs`; agent consumption rules are in [`.cursor/rules/domain.md`](../.cursor/rules/domain.md). @@ -17,17 +17,20 @@ Extend glossaries with `/grill-with-docs`; agent consumption rules are in [`.cur ```text docs/ adr/ # Architecture Decision Records (why, not how) - mvp/ - demo-slice.md # Investing June 30 demo (delivered) - architecture/architecture.md # Investing MVP vision (frozen; ADR-0003) - features/ # Investing module specs - guides/ # Operator guides for investing slices - prds/ # PRDs (Phase 2 QV PRD cancelled — ADR-0003) - requirements/requirements.md # Sprint 0 spike (historical) - backlog/backlog.md # Informal ideas mapped to investing features + +investing/docs/ # Investing Magic Formula specs (moved from docs/mvp/) + demo-slice.md + architecture/architecture.md + features/ + guides/ + prds/ + requirements/ + backlog/ + +credit/docs/ # Credit / CSS feature specs ``` -Credit feature specs (when present): `credit/docs/`. Domain READMEs: [`investing/`](../investing/), [`credit/`](../credit/). +Domain READMEs: [`investing/`](../investing/), [`credit/`](../credit/). ## Architecture Decision Records (ADRs) @@ -38,56 +41,16 @@ Short notes on hard-to-reverse choices. See [`docs/adr/`](adr/). | [0001](adr/0001-simfin-fundamentals-mvp.md) | SimFin fundamentals for MVP; SEC ETL deferred | | [0002](adr/0002-june-demo-scope-cut.md) | June 30 demo slice scope cut | | [0003](adr/0003-phase2-qv-cancelled.md) | Phase 2 Quantitative Value cancelled for active execution | +| [0004](adr/0004-platform-ephemeral-fargate-serve.md) | Ephemeral Fargate CSS serve demo | -## Demo slice (investing) - -**Delivered:** [`demo-slice.md`](mvp/demo-slice.md) — SimFin → US universe → ROC/EY → top-30 portfolio → dashboard. - -## Operator guides +## Investing entry points -| Guide | When to use | +| Doc | Role | | --- | --- | -| [download-simfin.md](mvp/guides/download-simfin.md) | **Active investing demo path:** download SimFin bulk fundamentals and `shareprices/latest`, then build universe and run-date price snapshots. | -| [download-fundamentals.md](mvp/guides/download-fundamentals.md) | **Frozen SEC spike:** download `companyfacts` + `edgartools` for a universe. Active demo path uses SimFin — see [`demo-slice.md`](mvp/demo-slice.md) and [`etl-data-lake.md`](mvp/features/etl-data-lake.md). | +| [demo-slice.md](../investing/docs/demo-slice.md) | June 30 delivery (delivered) | +| [architecture.md](../investing/docs/architecture/architecture.md) | Frozen historical vision (ADR-0003) | +| [phase2/prd.md](../investing/docs/prds/phase2/prd.md) | **Cancelled** — QV funnel ([ADR-0003](adr/0003-phase2-qv-cancelled.md)) | -## PRDs +## Notion -| PRD | Scope | -| --- | --- | -| [devcontainer/prd.md](mvp/prds/devcontainer/prd.md) | Reproducible dev environment (Cursor / EC2) | -| [ci-cd/ci-cd-prd.md](mvp/prds/ci-cd/ci-cd-prd.md) | Phase 0 CI/CD, AWS integration, ECS deploy path | -| [phase2/prd.md](mvp/prds/phase2/prd.md) | **Cancelled** — Quantitative Value funnel, cloud, backtest, sell-watch ([ADR-0003](adr/0003-phase2-qv-cancelled.md); branch `archive/phase2-qv`) | - -## Feature specs (investing) - -| Spec | Module | -| --- | --- | -| [etl-data-lake.md](mvp/features/etl-data-lake.md) | ETL and data lake | -| [universe-construction.md](mvp/features/universe-construction.md) | Universe construction | -| [permanent-loss-filter.md](mvp/features/permanent-loss-filter.md) | Permanent loss filter | -| [high-quality-stocks.md](mvp/features/high-quality-stocks.md) | Quality scoring | -| [cheap-stocks.md](mvp/features/cheap-stocks.md) | Cheapness scoring | -| [corroborative-signals.md](mvp/features/corroborative-signals.md) | Corroborative signals | -| [unstructured-financial-data.md](mvp/features/unstructured-financial-data.md) | Unstructured data | -| [backtesting.md](mvp/features/backtesting.md) | Backtesting | -| [sell-watch.md](mvp/features/sell-watch.md) | Sell watch | -| [portfolio-evolution.md](mvp/features/portfolio-evolution.md) | Portfolio evolution (model vs personal vs benchmarks) | -| [dashboard-reporting.md](mvp/features/dashboard-reporting.md) | Dashboard and reporting | -| [broker-execution.md](mvp/features/broker-execution.md) | Broker execution (paper) | - -Start with [architecture.md](mvp/architecture/architecture.md) for investing constraints (frozen vision), then open the feature spec for the area you are changing. For credit work, use [`credit/docs/`](../credit/docs/) and [`credit/CONTEXT.md`](../credit/CONTEXT.md). - -## Spec-driven workflow - -1. Read or update the relevant feature spec (investing: here; credit: `credit/docs/`). -2. Align with architecture decisions and ADRs. -3. Implement only what the spec allows for the current phase. -4. Update the spec after implementation (status, acceptance criteria, code links). - -## Notion tasks - -- Duplicate the [Code with Notion board template](https://notion.notion.site/code-with-notion-board). -- Link each task to a spec path under `docs/mvp/features/` or `credit/docs/`. -- Task board: **`Cursor Agent Tasks`** in Notion (MCP OAuth; see [NOTION_SETUP.md](mvp/NOTION_SETUP.md) and [AGENTS.md](../AGENTS.md)). - -Cursor agent rules: `.cursor/rules/*.mdc`. +Link each task to a spec path under `investing/docs/features/` or `credit/docs/`. diff --git a/docs/adr/0002-june-demo-scope-cut.md b/docs/adr/0002-june-demo-scope-cut.md index 4770af6..a59aa66 100644 --- a/docs/adr/0002-june-demo-scope-cut.md +++ b/docs/adr/0002-june-demo-scope-cut.md @@ -12,4 +12,4 @@ The **June 30 deliverable** is a reduced vertical slice, not the full architectu **Considered:** Full MVP including 20-year walk-forward backtest (rejected for June); demo + minimal backtest (rejected—user chose fastest path); Zipline for backtests (rejected—incompatible with Python 3.11, unmaintained, poor fit for fundamental annual rebalance). -**Consequences:** Documented in [`docs/mvp/demo-slice.md`](../mvp/demo-slice.md). Deferred feature specs remain as a **design record**; they are **not** an active execution track — Phase 2 QV / full-MVP investing roadmap was later **cancelled for active execution** ([ADR-0003](0003-phase2-qv-cancelled.md)). MLflow logs **pipeline runs** in the demo; a `backtesting` experiment was planned for phase 2 and is likewise not scheduled. +**Consequences:** Documented in [`investing/docs/demo-slice.md`](../../investing/docs/demo-slice.md). Deferred feature specs remain as a **design record**; they are **not** an active execution track — Phase 2 QV / full-MVP investing roadmap was later **cancelled for active execution** ([ADR-0003](0003-phase2-qv-cancelled.md)). MLflow logs **pipeline runs** in the demo; a `backtesting` experiment was planned for phase 2 and is likewise not scheduled. diff --git a/docs/adr/0003-phase2-qv-cancelled.md b/docs/adr/0003-phase2-qv-cancelled.md index 95c61ea..01390c8 100644 --- a/docs/adr/0003-phase2-qv-cancelled.md +++ b/docs/adr/0003-phase2-qv-cancelled.md @@ -8,15 +8,15 @@ status: accepted **Why:** Portfolio focus moved to a multi-domain layout with **credit CSS** next ([`CONTEXT-MAP.md`](../../CONTEXT-MAP.md)). Continuing QV as the investing “north star” contradicts the manager-facing roadmap and splits execution capacity. -**What stays delivered:** The June 30 **Magic Formula** demo slice (SimFin → ROC/EY → top-30 EW model portfolio → Streamlit + MLflow) remains the investing delivery ([ADR-0002](0002-june-demo-scope-cut.md), [`demo-slice.md`](../mvp/demo-slice.md)). +**What stays delivered:** The June 30 **Magic Formula** demo slice (SimFin → ROC/EY → top-30 EW model portfolio → Streamlit + MLflow) remains the investing delivery ([ADR-0002](0002-june-demo-scope-cut.md), [`demo-slice.md`](../../investing/docs/demo-slice.md)). -**Where cancelled work lives:** Branch `archive/phase2-qv` and historical docs under `docs/mvp/` (architecture vision, Phase 2 PRD, deferred feature specs). Those documents are **frozen reference**, not a live implementation plan. +**Where cancelled work lives:** Branch `archive/phase2-qv` and historical docs under `investing/docs/` (architecture vision, Phase 2 PRD, deferred feature specs). Those documents are **frozen reference**, not a live implementation plan. **Cloud / platform:** Shared AWS / Terraform / orchestration returns later under `platform/`, after domain demos work locally — not as a daily QV job. **Consequences:** - Update agent and human entry points (`AGENTS.md`, root README, CONTEXT-MAP) so they do not treat Phase 2 QV as ready for implementation. -- [`docs/mvp/prds/phase2/prd.md`](../mvp/prds/phase2/prd.md) status is **Cancelled**. -- [`docs/mvp/architecture/architecture.md`](../mvp/architecture/architecture.md) is a **frozen historical vision** for the investing module, not an active QV north star. +- [`investing/docs/prds/phase2/prd.md`](../../investing/docs/prds/phase2/prd.md) status is **Cancelled**. +- [`investing/docs/architecture/architecture.md`](../../investing/docs/architecture/architecture.md) is a **frozen historical vision** for the investing module, not an active QV north star. - Gray & Carlisle *Quantitative Value* remains a cited Source for provenance only; do not implement the QV funnel unless this ADR is superseded. diff --git a/CONTEXT.md b/investing/CONTEXT.md similarity index 84% rename from CONTEXT.md rename to investing/CONTEXT.md index 1e7d450..ad2d144 100644 --- a/CONTEXT.md +++ b/investing/CONTEXT.md @@ -1,6 +1,6 @@ # Equity screening -Ubiquitous language for the quantitative value-investing / Magic Formula module. This is one context in a portfolio monorepo — see [`CONTEXT-MAP.md`](CONTEXT-MAP.md). Canonical formulas and acceptance criteria live in `docs/mvp/`; this file is the concise vocabulary agents and humans share. Extend via `/grill-with-docs` when terms are resolved. +Ubiquitous language for the quantitative value-investing / Magic Formula module. This is one context in a portfolio monorepo — see [`CONTEXT-MAP.md`](../CONTEXT-MAP.md). Canonical formulas and acceptance criteria live in `investing/docs/`; this file is the concise vocabulary agents and humans share. Extend via `/grill-with-docs` when terms are resolved. ## Language @@ -9,7 +9,7 @@ The pipeline decision date (e.g. daily batch). Scoring, universe, and filters ar _Avoid_: as-of date (reserved for filing availability), execution date **Run-date share price (demo)**: -Closing price on or before the run date from SimFin bulk `shareprices/latest`, joined to the universe by ticker. Used for market cap (`shares_outstanding × adj_close`) in ROC tie-break and EY. **`price_date` may lag `run_date` by up to ~30 days** on the SimFin free tier; acceptable for the June demo. Full daily history (`shareprices/daily`) and alternate vendors (e.g. yfinance) are **deferred / not scheduled** ([ADR-0003](docs/adr/0003-phase2-qv-cancelled.md)). +Closing price on or before the run date from SimFin bulk `shareprices/latest`, joined to the universe by ticker. Used for market cap (`shares_outstanding × adj_close`) in ROC tie-break and EY. **`price_date` may lag `run_date` by up to ~30 days** on the SimFin free tier; acceptable for the June demo. Full daily history (`shareprices/daily`) and alternate vendors (e.g. yfinance) are **deferred / not scheduled** ([ADR-0003](../docs/adr/0003-phase2-qv-cancelled.md)). _Avoid_: Yahoo as canonical price when the universe is SimFin; requiring same-day prices in the demo pipeline **As-of date**: @@ -29,12 +29,12 @@ Backtesting only companies that still exist today, overstating returns. Mitigate _Avoid_: living-universe backtest **Universe**: -Investable tickers for a run date. **June 30 demo:** all SimFin US companies minus banks/insurers/utilities (`IndustryId` exclusions + bank/insurance sanity check). **Deferred (historical):** historical S&P 500 constituents including delisted names — **not scheduled** ([ADR-0003](docs/adr/0003-phase2-qv-cancelled.md)). Spec: `docs/mvp/features/universe-construction.md`. +Investable tickers for a run date. **June 30 demo:** all SimFin US companies minus banks/insurers/utilities (`IndustryId` exclusions + bank/insurance sanity check). **Deferred (historical):** historical S&P 500 constituents including delisted names — **not scheduled** ([ADR-0003](../docs/adr/0003-phase2-qv-cancelled.md)). Spec: `docs/features/universe-construction.md`. _Avoid_: watchlist, portfolio, benchmark index today **Industry classification**: Provider-assigned industry code used to apply sector hard exclusions (banks, insurers, utilities). MVP source: SimFin `IndustryId` on the company record; exclusions maintained in `data/reference/simfin_industry_exclusions.csv` with optional sanity check against SimFin bank/insurance statement datasets. -_Avoid_: SIC code (deferred with SEC ETL; not scheduled — [ADR-0003](docs/adr/0003-phase2-qv-cancelled.md)), GICS, naive sector label from prices +_Avoid_: SIC code (deferred with SEC ETL; not scheduled — [ADR-0003](../docs/adr/0003-phase2-qv-cancelled.md)), GICS, naive sector label from prices **Sector hard exclusion (banks / insurers / utilities)**: Hard exclusion because their financial statements are not comparable to industrial companies under Greenblatt ROC and EY—different line items, balance-sheet economics, and (for utilities) regulated returns. Not because SimFin lacks data; SimFin uses separate templates for banks and insurers. @@ -45,7 +45,7 @@ A company removed entirely from ranking (not a score penalty). Permanent loss an _Avoid_: penalize, down-rank, soft filter **Permanent loss filter**: -Hard exclusion for fraud or bankruptcy/distress risk before any score. Spec: `docs/mvp/features/permanent-loss-filter.md`. +Hard exclusion for fraud or bankruptcy/distress risk before any score. Spec: `docs/features/permanent-loss-filter.md`. _Avoid_: risk score, stop-loss, drawdown rule **Review queue**: @@ -61,7 +61,7 @@ Income and cash-flow statements use SimFin **TTM**; balance sheet uses the lates _Avoid_: mixing balance-sheet TTM into ROC denominators, using annual income for ranking between rebalance dates **Return on capital (ROC)**: -`EBIT / (Net Working Capital + Net Fixed Assets)`. Quality factor; higher is better; cross-sectional **ROC rank** (1 = best). Spec: `docs/mvp/features/high-quality-stocks.md`. +`EBIT / (Net Working Capital + Net Fixed Assets)`. Quality factor; higher is better; cross-sectional **ROC rank** (1 = best). Spec: `docs/features/high-quality-stocks.md`. _Avoid_: ROE, ROIC (unless explicitly that metric) **Net working capital (NWC)**: @@ -69,7 +69,7 @@ _Avoid_: ROE, ROIC (unless explicitly that metric) _Avoid_: total working capital without the excess-cash adjustment **Earnings yield (EY)**: -`EBIT / Enterprise Value`. Cheapness factor; higher is cheaper; cross-sectional **EY rank** (1 = cheapest). Spec: `docs/mvp/features/cheap-stocks.md`. +`EBIT / Enterprise Value`. Cheapness factor; higher is cheaper; cross-sectional **EY rank** (1 = cheapest). Spec: `docs/features/cheap-stocks.md`. _Avoid_: dividend yield, earnings/price without EV **Enterprise value (EV)**: @@ -93,7 +93,7 @@ Version id for ROC, EY, or filter rules so runs and backtests stay reproducible. _Avoid_: “latest formula”, implicit default **Model portfolio**: -Target long-only holdings from the pipeline; **June 30 demo:** top 30 names by combined rank, equal-weight only, market-cap tie-break on ranks. No watchlist in demo slice. Paper trading was planned for full MVP (historical / not scheduled — [ADR-0003](docs/adr/0003-phase2-qv-cancelled.md)). +Target long-only holdings from the pipeline; **June 30 demo:** top 30 names by combined rank, equal-weight only, market-cap tie-break on ranks. No watchlist in demo slice. Paper trading was planned for full MVP (historical / not scheduled — [ADR-0003](../docs/adr/0003-phase2-qv-cancelled.md)). _Avoid_: personal portfolio, watchlist (demo slice) **Watchlist**: @@ -105,7 +105,7 @@ Simulated orders only; no real capital in the MVP. _Avoid_: live trading, shadow trading with real broker **Sell-watch**: -Daily monitor of model holdings for quality drop, fraud/bankruptcy, overvaluation, or opportunity cost; emits signals, no auto-execution. Spec: `docs/mvp/features/sell-watch.md`. +Daily monitor of model holdings for quality drop, fraud/bankruptcy, overvaluation, or opportunity cost; emits signals, no auto-execution. Spec: `docs/features/sell-watch.md`. _Avoid_: stop-loss, trailing stop (deferred) **Sell signal**: @@ -113,7 +113,7 @@ Recommendation to exit a holding; requires explicit user confirmation before ord _Avoid_: auto-sell, trim (deferred state) **Walk-forward backtest**: -Rolling train/validation windows (3–5 years) over 20+ years of PIT data; annual rebalance. Spec: `docs/mvp/features/backtesting.md`. **Deferred / not scheduled** ([ADR-0003](docs/adr/0003-phase2-qv-cancelled.md)); demo slice stops at ranked model portfolio + dashboard. +Rolling train/validation windows (3–5 years) over 20+ years of PIT data; annual rebalance. Spec: `docs/features/backtesting.md`. **Deferred / not scheduled** ([ADR-0003](../docs/adr/0003-phase2-qv-cancelled.md)); demo slice stops at ranked model portfolio + dashboard. _Avoid_: single in-sample fit, peeking at hold-out (when backtest ships) **Block bootstrap**: @@ -138,25 +138,25 @@ _Avoid_: ad-hoc snapshot without run id ## Flagged ambiguities -Resolved scope cuts (see ADRs and [`docs/mvp/demo-slice.md`](docs/mvp/demo-slice.md)): +Resolved scope cuts (see ADRs and [`docs/demo-slice.md`](docs/demo-slice.md)): - **June 30 demo MVP:** SimFin bulk US → raw → normalizer → **universe (US market)** → ROC/EY → combined rank → top-30 EW model portfolio → Streamlit dashboard. No permanent loss filter, backtest, sell-watch, or paper trading in this slice. - SEC ETL spike (`sec_client`, `edgartools_client`, `download-fundamentals`) is **frozen** in repo; demo pipeline uses SimFin bulk for fundamentals and run-date prices (`shareprices/latest`). -- **Phase 2 (Quantitative Value):** **cancelled** for active execution ([ADR-0003](docs/adr/0003-phase2-qv-cancelled.md); branch `archive/phase2-qv`). Multi-period fundamentals for a QV funnel are **not** a current delivery requirement; lake design may still keep the door open without blocking credit work. +- **Phase 2 (Quantitative Value):** **cancelled** for active execution ([ADR-0003](../docs/adr/0003-phase2-qv-cancelled.md); branch `archive/phase2-qv`). Multi-period fundamentals for a QV funnel are **not** a current delivery requirement; lake design may still keep the door open without blocking credit work. Terminology reminders: - “Cheap” means high **EY**, not low P/E—use **EY rank** in issues and code names. - “Quality” means high **ROC**, not ESG or subjective moat—use **ROC rank**. - “Value trap” in specs means negative EBIT routed to **review queue**, not a separate score. -- MVP specs in `docs/mvp/` remain canonical until an ADR or architecture decision supersedes them; update `CONTEXT.md` when `/grill-with-docs` resolves a term conflict. +- MVP specs in `investing/docs/` remain canonical until an ADR or architecture decision supersedes them; update `investing/CONTEXT.md` when `/grill-with-docs` resolves a term conflict. ## Sources -Investing provenance (full portfolio list: [`CONTEXT-MAP.md`](CONTEXT-MAP.md)): +Investing provenance (full portfolio list: [`CONTEXT-MAP.md`](../CONTEXT-MAP.md)): 1. **Joel Greenblatt (2010)** — *The Little Book That Still Beats the Market* Foundations of the Magic Formula: systematic ranking by return on capital (ROC) and earnings yield. 2. **Wesley R. Gray & Tobias E. Carlisle (2012)** — *Quantitative Value: A Practitioner's Guide to Automating Intelligent Investment and Eliminating Behavioral Errors* - Quantitative framework for accounting-manipulation screens (M-Score / F-Score), financial strength, competitive advantages (moats), and intrinsic valuation. **Not in active execution** ([ADR-0003](docs/adr/0003-phase2-qv-cancelled.md)). + Quantitative framework for accounting-manipulation screens (M-Score / F-Score), financial strength, competitive advantages (moats), and intrinsic valuation. **Not in active execution** ([ADR-0003](../docs/adr/0003-phase2-qv-cancelled.md)). diff --git a/investing/README.md b/investing/README.md index 5eed69a..092e781 100644 --- a/investing/README.md +++ b/investing/README.md @@ -2,13 +2,13 @@ **Domain:** quantitative value / Magic Formula screening on US equities. -**Status:** June 30 demo slice **delivered** (v0.1.0). Code still lives under `src/smartwealthai/`, `apps/dashboard/`, and `docs/mvp/` until a dedicated migrate-only PR moves it here (portfolio layout B+). +**Status:** June 30 demo slice **delivered** (v0.1.0). Layout mirrors `credit/`: package `investing`, specs under `docs/`, dashboard under `apps/dashboard/`, tests under `tests/`. ## What it does Ingest SimFin fundamentals → point-in-time curated lake → universe (US minus banks/insurers/utilities) → ROC + Earnings Yield ranks → combined rank → top-30 equal-weight **model portfolio** → Streamlit dashboard + MLflow run logging. -## How to run (today) +## How to run From the repo root (Poetry): @@ -25,11 +25,22 @@ make test # or: poetry run pytest ``` -- **Active fundamentals path:** [`docs/mvp/guides/download-simfin.md`](../docs/mvp/guides/download-simfin.md) -- **Frozen SEC spike:** [`docs/mvp/guides/download-fundamentals.md`](../docs/mvp/guides/download-fundamentals.md) +- **Active fundamentals path:** [`docs/guides/download-simfin.md`](docs/guides/download-simfin.md) +- **Frozen SEC spike:** [`docs/guides/download-fundamentals.md`](docs/guides/download-fundamentals.md) - More: [`AGENTS.md`](../AGENTS.md) -Docs: [`docs/mvp/demo-slice.md`](../docs/mvp/demo-slice.md) · Glossary: [`CONTEXT.md`](../CONTEXT.md) · Map: [`CONTEXT-MAP.md`](../CONTEXT-MAP.md) +Docs: [`docs/demo-slice.md`](docs/demo-slice.md) · Glossary: [`CONTEXT.md`](CONTEXT.md) · Map: [`CONTEXT-MAP.md`](../CONTEXT-MAP.md) + +## Layout + +```text +investing/ + CONTEXT.md # ubiquitous language + docs/ # demo slice, architecture, features, guides + src/investing/ # Python package (formerly smartwealthai) + apps/dashboard/ # Streamlit demo UI + tests/ # hermetic investing tests + lake fixtures +``` ## Not in active roadmap @@ -38,9 +49,4 @@ Phase 2 Quantitative Value (forensics, FS-Score, full backtest, sell-watch as pr ## Sources 1. **Joel Greenblatt (2010)** — *The Little Book That Still Beats the Market* - Foundations of the Magic Formula: systematic ranking by return on capital (ROC) and earnings yield. - -2. **Wesley R. Gray & Tobias E. Carlisle (2012)** — *Quantitative Value: A Practitioner's Guide to Automating Intelligent Investment and Eliminating Behavioral Errors* - QV framework provenance only — **not in active execution** ([ADR-0003](../docs/adr/0003-phase2-qv-cancelled.md)). - -Full portfolio Sources: [`CONTEXT-MAP.md`](../CONTEXT-MAP.md). +2. **Wesley R. Gray & Tobias E. Carlisle (2012)** — *Quantitative Value* (provenance for cancelled Phase 2 QV) diff --git a/apps/dashboard/Home.py b/investing/apps/dashboard/Home.py similarity index 94% rename from apps/dashboard/Home.py rename to investing/apps/dashboard/Home.py index 949099a..04dd285 100644 --- a/apps/dashboard/Home.py +++ b/investing/apps/dashboard/Home.py @@ -3,9 +3,9 @@ from __future__ import annotations import streamlit as st - from apps.dashboard.common import load_snapshot, render_empty_state, render_footer -from smartwealthai.dashboard_data import overview_headline + +from investing.dashboard_data import overview_headline st.set_page_config(page_title="SmartWealthAI", page_icon="📈", layout="wide") diff --git a/apps/__init__.py b/investing/apps/dashboard/__init__.py similarity index 100% rename from apps/__init__.py rename to investing/apps/dashboard/__init__.py diff --git a/apps/dashboard/common.py b/investing/apps/dashboard/common.py similarity index 97% rename from apps/dashboard/common.py rename to investing/apps/dashboard/common.py index 395b3ad..2422d84 100644 --- a/apps/dashboard/common.py +++ b/investing/apps/dashboard/common.py @@ -8,7 +8,7 @@ import streamlit as st -from smartwealthai.dashboard_data import ( +from investing.dashboard_data import ( DashboardSnapshot, discover_latest_run_date, load_dashboard_snapshot, diff --git a/apps/dashboard/pages/1_Ranking.py b/investing/apps/dashboard/pages/1_Ranking.py similarity index 100% rename from apps/dashboard/pages/1_Ranking.py rename to investing/apps/dashboard/pages/1_Ranking.py diff --git a/apps/dashboard/pages/2_Portfolio.py b/investing/apps/dashboard/pages/2_Portfolio.py similarity index 100% rename from apps/dashboard/pages/2_Portfolio.py rename to investing/apps/dashboard/pages/2_Portfolio.py diff --git a/docs/mvp/NOTION_SETUP.md b/investing/docs/NOTION_SETUP.md similarity index 83% rename from docs/mvp/NOTION_SETUP.md rename to investing/docs/NOTION_SETUP.md index 3faa90a..58ca388 100644 --- a/docs/mvp/NOTION_SETUP.md +++ b/investing/docs/NOTION_SETUP.md @@ -1,6 +1,6 @@ # Notion setup for SmartWealthAI -Git specs under `docs/mvp/` stay canonical. Notion tracks tasks only. +Git specs under `investing/docs/` stay canonical. Notion tracks tasks only. ## Default board @@ -27,7 +27,7 @@ Create a Notion page with: In Agent mode, after MCP auth: -- Ask to create tasks on **`Cursor Agent Tasks`** from e.g. `docs/mvp/features/universe-construction.md`, or +- Ask to create tasks on **`Cursor Agent Tasks`** from e.g. `investing/docs/features/universe-construction.md`, or - Ask to use **spec-to-implementation** (point the agent at the Git spec path; specs live in this repo, not in Notion). ## Task discipline @@ -35,5 +35,5 @@ In Agent mode, after MCP auth: | Step | Where | | --- | --- | | Change MVP decision | Update Git spec first | -| Track work | Notion task with `docs/mvp/features/...` path | +| Track work | Notion task with `investing/docs/features/...` path | | Finish work | Update Git spec (status + acceptance criteria), then mark Notion done | diff --git a/docs/mvp/architecture/architecture.md b/investing/docs/architecture/architecture.md similarity index 97% rename from docs/mvp/architecture/architecture.md rename to investing/docs/architecture/architecture.md index 9086b10..a81d91d 100644 --- a/docs/mvp/architecture/architecture.md +++ b/investing/docs/architecture/architecture.md @@ -1,20 +1,20 @@ # SmartWealthAI - MVP Architecture -> **Status: frozen historical vision (investing).** Phase 2 Quantitative Value and the broader “full MVP” roadmap below are **not** in active execution. See [ADR-0003](../../adr/0003-phase2-qv-cancelled.md) and [`CONTEXT-MAP.md`](../../../CONTEXT-MAP.md). The **delivered** investing path is the June 30 Magic Formula demo slice. Active portfolio work continues under `credit/`. +> **Status: frozen historical vision (investing).** Phase 2 Quantitative Value and the broader “full MVP” roadmap below are **not** in active execution. See [ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md) and [`CONTEXT-MAP.md`](../../../CONTEXT-MAP.md). The **delivered** investing path is the June 30 Magic Formula demo slice. Active portfolio work continues under `credit/`. -This document is a **frozen historical specification** for the investing (Magic Formula) domain — decisions, assumptions, and acceptance criteria captured during the original MVP design. It is retained so the planning trail stays visible; it is **not** a live north star for active delivery ([ADR-0003](../../adr/0003-phase2-qv-cancelled.md), [`CONTEXT-MAP.md`](../../../CONTEXT-MAP.md)). +This document is a **frozen historical specification** for the investing (Magic Formula) domain — decisions, assumptions, and acceptance criteria captured during the original MVP design. It is retained so the planning trail stays visible; it is **not** a live north star for active delivery ([ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md), [`CONTEXT-MAP.md`](../../../CONTEXT-MAP.md)). Originally written as a portfolio showcase of MLOps on a quantitative value investing system (automated agent, end-to-end). That investing demo shipped; active monorepo work continues under **credit/** next. ## June 30 demo slice (delivered) -The **first shippable vertical** is narrower than the full vision below. See [`demo-slice.md`](../demo-slice.md) and [ADR-0002](../../adr/0002-june-demo-scope-cut.md): SimFin ETL → US-market universe → ROC/EY → top-30 equal-weight portfolio → Streamlit dashboard. Modules labeled “phase 2” in this document were planned extensions; **Phase 2 QV as an active execution track is cancelled** ([ADR-0003](../../adr/0003-phase2-qv-cancelled.md)). The architecture text below is retained as **frozen reference**, not a live north star. +The **first shippable vertical** is narrower than the full vision below. See [`demo-slice.md`](../demo-slice.md) and [ADR-0002](../../../docs/adr/0002-june-demo-scope-cut.md): SimFin ETL → US-market universe → ROC/EY → top-30 equal-weight portfolio → Streamlit dashboard. Modules labeled “phase 2” in this document were planned extensions; **Phase 2 QV as an active execution track is cancelled** ([ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md)). The architecture text below is retained as **frozen reference**, not a live north star. ## MVP vision Build a modular quantitative value investing system that: -1. Retrieves financial data from **SimFin** (fundamentals, demo) and free price providers; SEC EDGAR deferred to phase 2 ([ADR-0001](../../adr/0001-simfin-fundamentals-mvp.md)). +1. Retrieves financial data from **SimFin** (fundamentals, demo) and free price providers; SEC EDGAR deferred to phase 2 ([ADR-0001](../../../docs/adr/0001-simfin-fundamentals-mvp.md)). 2. Stores raw and curated data in an AWS-based, incrementally refreshed data lake. 3. Filters out companies with high risk of permanent capital loss (fraud and bankruptcy). 4. Identifies high-quality companies. @@ -240,7 +240,7 @@ These items are now closed for the MVP. They can be reopened in later iterations | Share classes | Treat as the same company; keep the class with the highest average trading liquidity and drop the rest. Phase 2 for demo. | | Market cap floor | Optional parameter. Off in demo. | | Trading volume floor | Optional; off in demo. | -| Primary fundamentals source | **SimFin** (free tier, bulk download). [ADR-0001](../../adr/0001-simfin-fundamentals-mvp.md). | +| Primary fundamentals source | **SimFin** (free tier, bulk download). [ADR-0001](../../../docs/adr/0001-simfin-fundamentals-mvp.md). | | SEC ETL | Frozen spike in repo; phase 2 normalizer. | | Primary price source | **Demo:** SimFin bulk `shareprices/latest`. **Phase 2:** `yfinance`; free tiers of FMP, Alpha Vantage, and EODHD as redundancy / fallback. | | Data lake | S3 (raw + curated zones) + DuckDB as the analytical engine (`duckdb` reads parquet directly from S3, no Athena bill). | @@ -484,7 +484,7 @@ Order proposed for refining the feature specs (each spec follows the same templa ## Architecture acceptance criteria -- Every module has its own spec under `docs/mvp/features/`. +- Every module has its own spec under `investing/docs/features/`. - Every spec includes objective, scope, inputs, outputs, flow, Mermaid diagram, open questions, and acceptance criteria. - The architecture can run the pipeline end to end without a live broker. - The architecture separates raw data, normalized data, point-in-time queries, scoring, portfolio construction, monitoring, backtesting, and orders. diff --git a/docs/mvp/backlog/backlog.md b/investing/docs/backlog/backlog.md similarity index 100% rename from docs/mvp/backlog/backlog.md rename to investing/docs/backlog/backlog.md diff --git a/docs/mvp/demo-slice.md b/investing/docs/demo-slice.md similarity index 86% rename from docs/mvp/demo-slice.md rename to investing/docs/demo-slice.md index 4b522bb..9e814ab 100644 --- a/docs/mvp/demo-slice.md +++ b/investing/docs/demo-slice.md @@ -1,8 +1,8 @@ # June 30 demo slice — simplest Magic Formula -**Status:** **delivered** (investing domain) — see [ADR-0002](../adr/0002-june-demo-scope-cut.md) +**Status:** **delivered** (investing domain) — see [ADR-0002](../../docs/adr/0002-june-demo-scope-cut.md) **Target date:** 2026-06-30 -**Architecture reference:** [`architecture/architecture.md`](architecture/architecture.md) is a **frozen historical investing vision**, not a live north star ([ADR-0003](../adr/0003-phase2-qv-cancelled.md)). Active portfolio direction: multi-domain monorepo — [`CONTEXT-MAP.md`](../../CONTEXT-MAP.md); **credit CSS** next. +**Architecture reference:** [`architecture/architecture.md`](architecture/architecture.md) is a **frozen historical investing vision**, not a live north star ([ADR-0003](../../docs/adr/0003-phase2-qv-cancelled.md)). Active portfolio direction: multi-domain monorepo — [`CONTEXT-MAP.md`](../../CONTEXT-MAP.md); **credit CSS** next. ## Objective @@ -19,11 +19,11 @@ Deliver a working, explainable Greenblatt-style Magic Formula pipeline on real U | 5 | Ranking | Combined rank = ROC rank + EY rank (lower is better); tie-break ascending market cap | | 6 | Model portfolio | Top **30** names, **equal-weight** only | | 7 | Dashboard | [`dashboard-reporting.md`](features/dashboard-reporting.md) — ranking table, portfolio, per-name explainability | -| 8 | MLflow | Log each pipeline run (params, scoring metrics, portfolio artifact) — `src/smartwealthai/mlflow_run_logging.py`, wired in `score-universe` ([#61](https://github.com/JLaborda/SmartWealthAI/issues/61)) | +| 8 | MLflow | Log each pipeline run (params, scoring metrics, portfolio artifact) — `investing/src/investing/mlflow_run_logging.py`, wired in `score-universe` ([#61](https://github.com/JLaborda/SmartWealthAI/issues/61)) | ## Out of scope (deferred / historical — not scheduled) -Items below were cut from the June demo ([ADR-0002](../adr/0002-june-demo-scope-cut.md)). Further investing “phase 2” / full-MVP execution is **not scheduled** ([ADR-0003](../adr/0003-phase2-qv-cancelled.md)). Specs remain as design record. +Items below were cut from the June demo ([ADR-0002](../../docs/adr/0002-june-demo-scope-cut.md)). Further investing “phase 2” / full-MVP execution is **not scheduled** ([ADR-0003](../../docs/adr/0003-phase2-qv-cancelled.md)). Specs remain as design record. - Permanent loss filter ([`permanent-loss-filter.md`](features/permanent-loss-filter.md)) - Backtesting and crisis report ([`backtesting.md`](features/backtesting.md)) @@ -31,7 +31,7 @@ Items below were cut from the June demo ([ADR-0002](../adr/0002-june-demo-scope- - Paper trading / broker ([`broker-execution.md`](features/broker-execution.md)) - Watchlist (ranking table in dashboard is enough) - Corroborative signals, unstructured data, portfolio evolution (personal CSV) -- SEC EDGAR ETL (frozen spike remains in repo — [ADR-0001](../adr/0001-simfin-fundamentals-mvp.md)) +- SEC EDGAR ETL (frozen spike remains in repo — [ADR-0001](../../docs/adr/0001-simfin-fundamentals-mvp.md)) - Historical S&P 500 universe with delisted names - Score-weighted and risk-parity weighting @@ -92,7 +92,7 @@ flowchart LR ## Historical next steps (not scheduled) -> Investing Magic Formula demo is **delivered**. The list below is **historical planning — not scheduled**. Active portfolio direction: multi-domain monorepo; **credit CSS** next ([CONTEXT-MAP](../../CONTEXT-MAP.md), [ADR-0003](../adr/0003-phase2-qv-cancelled.md)). +> Investing Magic Formula demo is **delivered**. The list below is **historical planning — not scheduled**. Active portfolio direction: multi-domain monorepo; **credit CSS** next ([CONTEXT-MAP](../../CONTEXT-MAP.md), [ADR-0003](../../docs/adr/0003-phase2-qv-cancelled.md)). 1. Historical S&P 500 universe + permanent loss filter 2. Minimal backtest (annual rebalance, 20 years) — custom pandas/DuckDB loop, not Zipline diff --git a/docs/mvp/features/backtesting.md b/investing/docs/features/backtesting.md similarity index 97% rename from docs/mvp/features/backtesting.md rename to investing/docs/features/backtesting.md index 5c784f6..9cbcaf8 100644 --- a/docs/mvp/features/backtesting.md +++ b/investing/docs/features/backtesting.md @@ -2,7 +2,7 @@ ## Implementation status -**Deferred / historical — not scheduled** for the June 30 demo slice ([ADR-0002](../../adr/0002-june-demo-scope-cut.md)). Spec body preserved as design record; active Phase 2 / full-MVP investing track cancelled ([ADR-0003](../../adr/0003-phase2-qv-cancelled.md)). +**Deferred / historical — not scheduled** for the June 30 demo slice ([ADR-0002](../../../docs/adr/0002-june-demo-scope-cut.md)). Spec body preserved as design record; active Phase 2 / full-MVP investing track cancelled ([ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md)). ## Objective diff --git a/docs/mvp/features/broker-execution.md b/investing/docs/features/broker-execution.md similarity index 97% rename from docs/mvp/features/broker-execution.md rename to investing/docs/features/broker-execution.md index 27aba9d..939b7b0 100644 --- a/docs/mvp/features/broker-execution.md +++ b/investing/docs/features/broker-execution.md @@ -2,7 +2,7 @@ ## Implementation status -**Deferred / historical — not scheduled** for the June 30 demo slice ([ADR-0002](../../adr/0002-june-demo-scope-cut.md)). Spec body preserved as design record; active Phase 2 / full-MVP investing track cancelled ([ADR-0003](../../adr/0003-phase2-qv-cancelled.md)). +**Deferred / historical — not scheduled** for the June 30 demo slice ([ADR-0002](../../../docs/adr/0002-june-demo-scope-cut.md)). Spec body preserved as design record; active Phase 2 / full-MVP investing track cancelled ([ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md)). ## Objective diff --git a/docs/mvp/features/cheap-stocks.md b/investing/docs/features/cheap-stocks.md similarity index 94% rename from docs/mvp/features/cheap-stocks.md rename to investing/docs/features/cheap-stocks.md index 25fe902..d958e37 100644 --- a/docs/mvp/features/cheap-stocks.md +++ b/investing/docs/features/cheap-stocks.md @@ -2,7 +2,7 @@ ## Implementation status -done (demo cross-sectional slice) — EY scoring and ranks: `src/smartwealthai/magic_formula_ranking.py`, CLI `score-universe` ([#60](https://github.com/JLaborda/SmartWealthAI/issues/60)). Single-ticker tracer: `magic_formula_metrics.py`, `pit_fundamentals.py`, `compute-metrics` ([#44](https://github.com/JLaborda/SmartWealthAI/issues/44)). +done (demo cross-sectional slice) — EY scoring and ranks: `investing/src/investing/magic_formula_ranking.py`, CLI `score-universe` ([#60](https://github.com/JLaborda/SmartWealthAI/issues/60)). Single-ticker tracer: `magic_formula_metrics.py`, `pit_fundamentals.py`, `compute-metrics` ([#44](https://github.com/JLaborda/SmartWealthAI/issues/44)). ## Objective diff --git a/docs/mvp/features/corroborative-signals.md b/investing/docs/features/corroborative-signals.md similarity index 100% rename from docs/mvp/features/corroborative-signals.md rename to investing/docs/features/corroborative-signals.md diff --git a/docs/mvp/features/dashboard-reporting.md b/investing/docs/features/dashboard-reporting.md similarity index 96% rename from docs/mvp/features/dashboard-reporting.md rename to investing/docs/features/dashboard-reporting.md index 006e74e..f680c48 100644 --- a/docs/mvp/features/dashboard-reporting.md +++ b/investing/docs/features/dashboard-reporting.md @@ -2,7 +2,7 @@ ## Implementation status -**done** — demo slice three-page app ([#62](https://github.com/JLaborda/SmartWealthAI/issues/62)); full MVP pages **deferred / not scheduled** ([ADR-0003](../../adr/0003-phase2-qv-cancelled.md)). +**done** — demo slice three-page app ([#62](https://github.com/JLaborda/SmartWealthAI/issues/62)); full MVP pages **deferred / not scheduled** ([ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md)). ### Demo run @@ -13,7 +13,7 @@ poetry run run-dashboard --data-dir data --run-date 2026-06-18 Environment variables: `SMARTWEALTHAI_DATA_DIR` (lake root, default `data`), `SMARTWEALTHAI_RUN_DATE` (optional override). -Code: `apps/dashboard/` (Streamlit UI), `src/smartwealthai/dashboard_data.py` (parquet readers), `tests/test_dashboard_data.py`. +Code: `investing/apps/dashboard/` (Streamlit UI), `investing/src/investing/dashboard_data.py` (parquet readers), `investing/tests/test_dashboard_data.py`. ## Objective diff --git a/docs/mvp/features/etl-data-lake.md b/investing/docs/features/etl-data-lake.md similarity index 91% rename from docs/mvp/features/etl-data-lake.md rename to investing/docs/features/etl-data-lake.md index 61cb155..2f0cdbf 100644 --- a/docs/mvp/features/etl-data-lake.md +++ b/investing/docs/features/etl-data-lake.md @@ -2,7 +2,7 @@ ## Implementation status -**done** (demo slice) — SimFin connector + normalizer + shareprices snapshot + end-to-end orchestrator `run-demo-pipeline` ([#63](https://github.com/JLaborda/SmartWealthAI/issues/63)). SEC spike frozen ([ADR-0001](../../adr/0001-simfin-fundamentals-mvp.md)); resume **not scheduled** ([ADR-0003](../../adr/0003-phase2-qv-cancelled.md)). +**done** (demo slice) — SimFin connector + normalizer + shareprices snapshot + end-to-end orchestrator `run-demo-pipeline` ([#63](https://github.com/JLaborda/SmartWealthAI/issues/63)). SEC spike frozen ([ADR-0001](../../../docs/adr/0001-simfin-fundamentals-mvp.md)); resume **not scheduled** ([ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md)). ## Objective @@ -162,28 +162,28 @@ Phase 2 yfinance cache semantics: ### Progress notes -- SimFin bulk connector implemented in `src/smartwealthai/download_simfin.py`, +- SimFin bulk connector implemented in `investing/src/investing/download_simfin.py`, `simfin_client.py`, and `lake_paths.simfin_bulk_path`. Operator guide: - [`docs/mvp/guides/download-simfin.md`](../guides/download-simfin.md). -- SimFin fundamentals normalizer in `src/smartwealthai/simfin_normalizer.py` and + [`investing/docs/guides/download-simfin.md`](../guides/download-simfin.md). +- SimFin fundamentals normalizer in `investing/src/investing/simfin_normalizer.py` and `normalize_simfin.py` CLI; mapping at `config/fundamentals/simfin_mapping_v1.yaml`. - Hermetic tests in `tests/test_simfin_normalizer.py` with fixtures under - `tests/fixtures/lake/raw/simfin/`. -- Hermetic tests in `tests/test_download_simfin.py` (path layout, skip/force, + Hermetic tests in `investing/tests/test_simfin_normalizer.py` with fixtures under + `investing/tests/fixtures/lake/raw/simfin/`. +- Hermetic tests in `investing/tests/test_download_simfin.py` (path layout, skip/force, mocked download, per-dataset failure handling). - A hermetic fixture lake contract is implemented for CI in - `tests/fixtures/lake/README.md` with raw SEC, raw SimFin, and raw yfinance + `investing/tests/fixtures/lake/README.md` with raw SEC, raw SimFin, and raw yfinance snapshots, curated derived fundamentals, and a provenance manifest with checksums. - Point-in-time selection and raw fixture loading behavior are covered by tests in - `tests/test_ci_baseline.py` via `smartwealthai.fixture_lake`. -- Fundamentals download spike modules are implemented under `src/smartwealthai/` + `investing/tests/test_ci_baseline.py` via `investing.fixture_lake`. +- Fundamentals download spike modules are implemented under `investing/src/investing/` (`sec_client`, `edgartools_client`, `download_fundamentals`) — **frozen** for phase 2. - SimFin connector + normalizer are the active demo path ([ADR-0001](../../adr/0001-simfin-fundamentals-mvp.md)). + SimFin connector + normalizer are the active demo path ([ADR-0001](../../../docs/adr/0001-simfin-fundamentals-mvp.md)). - SimFin shareprices snapshot in `price_ingest.py` and `download_prices.py` CLI (`poetry run download-prices`). Requires `shareprices/latest` from `download-simfin`. Writes `curated/prices/run_date=/prices.parquet`. Hermetic tests in - `tests/test_download_prices.py`. + `investing/tests/test_download_prices.py`. **Operator sequence (demo pipeline):** @@ -218,7 +218,7 @@ poetry run score-universe --run-date 2026-06-19 ## SimFin bulk connector (demo) Downloads US fundamentals via the `simfin` Python package into `raw/simfin/`. -Operator guide: [`docs/mvp/guides/download-simfin.md`](../guides/download-simfin.md). +Operator guide: [`investing/docs/guides/download-simfin.md`](../guides/download-simfin.md). ### CLI @@ -232,9 +232,9 @@ poetry run download-simfin --refresh-days 7 --force | Module | Role | | --- | --- | -| `smartwealthai.simfin_client` | API key config, safe bulk download (zip-slip guarded), cache CSV path. | -| `smartwealthai.download_simfin` | CLI orchestration, skip/force by `refresh_days`, run summary. | -| `smartwealthai.lake_paths` | `simfin_bulk_path`, `simfin_errors_path`. | +| `investing.simfin_client` | API key config, safe bulk download (zip-slip guarded), cache CSV path. | +| `investing.download_simfin` | CLI orchestration, skip/force by `refresh_days`, run summary. | +| `investing.lake_paths` | `simfin_bulk_path`, `simfin_errors_path`. | ### Acceptance criteria (SimFin connector) @@ -277,10 +277,10 @@ Transforms SimFin bulk statements into curated canonical parquet. Joins income T | Module | Role | | --- | --- | -| `smartwealthai.simfin_normalizer` | Raw SimFin CSV join + PIT stamping + curated parquet writer. | -| `smartwealthai.normalize_simfin` | CLI entry point (`poetry run normalize-simfin`). | +| `investing.simfin_normalizer` | Raw SimFin CSV join + PIT stamping + curated parquet writer. | +| `investing.normalize_simfin` | CLI entry point (`poetry run normalize-simfin`). | | `config/fundamentals/simfin_mapping_v1.yaml` | SimFin column → canonical field mapping. | -| `smartwealthai.lake_paths` | `curated_fundamentals_path`, `curated_issues_path`, `fiscal_period_label`. | +| `investing.lake_paths` | `curated_fundamentals_path`, `curated_issues_path`, `fiscal_period_label`. | ```bash poetry run normalize-simfin --snapshot-date 2026-06-18 --universe-run-date 2026-06-18 @@ -361,7 +361,7 @@ notebook exploration and cross-checks — **not** for the production normalizer. No `submissions` ingest, no curated parquet normalizer in this slice (normalizer: [#52](https://github.com/JLaborda/SmartWealthAI/issues/52)), and no S3 upload. -**Operator guide:** [`docs/mvp/guides/download-fundamentals.md`](../guides/download-fundamentals.md) +**Operator guide:** [`investing/docs/guides/download-fundamentals.md`](../guides/download-fundamentals.md) ### Scope @@ -410,18 +410,18 @@ No `submissions` ingest, no curated parquet normalizer in this slice (normalizer ```bash export SEC_IDENTITY="Your Name your@email.com" -python -m smartwealthai.download_fundamentals --universe dow30 -python -m smartwealthai.download_fundamentals --universe dow30 --periods 16 --force +python -m investing.download_fundamentals --universe dow30 +python -m investing.download_fundamentals --universe dow30 --periods 16 --force ``` ### Module map | Module | Role | | --- | --- | -| `smartwealthai.sec_client` | SEC REST client (throttle, retry, `companyfacts` download). | -| `smartwealthai.edgartools_client` | Optional `get_facts()` statement extraction to parquet (dev/QC). | -| `smartwealthai.download_fundamentals` | CLI orchestration, universe loading, run summary. | -| `smartwealthai.normalize_fundamentals` (planned) | `companyfacts` → curated canonical parquet ([#52](https://github.com/JLaborda/SmartWealthAI/issues/52)). | +| `investing.sec_client` | SEC REST client (throttle, retry, `companyfacts` download). | +| `investing.edgartools_client` | Optional `get_facts()` statement extraction to parquet (dev/QC). | +| `investing.download_fundamentals` | CLI orchestration, universe loading, run summary. | +| `investing.normalize_fundamentals` (planned) | `companyfacts` → curated canonical parquet ([#52](https://github.com/JLaborda/SmartWealthAI/issues/52)). | ### Acceptance criteria (spike) diff --git a/docs/mvp/features/high-quality-stocks.md b/investing/docs/features/high-quality-stocks.md similarity index 95% rename from docs/mvp/features/high-quality-stocks.md rename to investing/docs/features/high-quality-stocks.md index aeaf903..80450d6 100644 --- a/docs/mvp/features/high-quality-stocks.md +++ b/investing/docs/features/high-quality-stocks.md @@ -2,7 +2,7 @@ ## Implementation status -done (demo cross-sectional slice) — ROC scoring and ranks: `src/smartwealthai/magic_formula_ranking.py`, CLI `score-universe` ([#60](https://github.com/JLaborda/SmartWealthAI/issues/60)). Single-ticker tracer: `magic_formula_metrics.py`, `pit_fundamentals.py`, `compute-metrics` ([#44](https://github.com/JLaborda/SmartWealthAI/issues/44)). +done (demo cross-sectional slice) — ROC scoring and ranks: `investing/src/investing/magic_formula_ranking.py`, CLI `score-universe` ([#60](https://github.com/JLaborda/SmartWealthAI/issues/60)). Single-ticker tracer: `magic_formula_metrics.py`, `pit_fundamentals.py`, `compute-metrics` ([#44](https://github.com/JLaborda/SmartWealthAI/issues/44)). ## Objective diff --git a/docs/mvp/features/permanent-loss-filter.md b/investing/docs/features/permanent-loss-filter.md similarity index 95% rename from docs/mvp/features/permanent-loss-filter.md rename to investing/docs/features/permanent-loss-filter.md index 4805627..cdea9c0 100644 --- a/docs/mvp/features/permanent-loss-filter.md +++ b/investing/docs/features/permanent-loss-filter.md @@ -2,7 +2,7 @@ ## Implementation status -**Deferred / historical — not scheduled** for the June 30 demo slice ([ADR-0002](../../adr/0002-june-demo-scope-cut.md)). Spec body preserved as design record; active Phase 2 / full-MVP investing track cancelled ([ADR-0003](../../adr/0003-phase2-qv-cancelled.md)). +**Deferred / historical — not scheduled** for the June 30 demo slice ([ADR-0002](../../../docs/adr/0002-june-demo-scope-cut.md)). Spec body preserved as design record; active Phase 2 / full-MVP investing track cancelled ([ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md)). ## Objective @@ -32,7 +32,7 @@ Identify companies in the investable universe with elevated risk of permanent ca | Adjusted prices and corporate actions | `curated/prices` | | SEC filing index (form type, accession, acceptance datetime) | `curated/sec_edgar/submissions` | | Auditor information (when extracted) | `curated/sec_edgar/auditor` (future) | -| Regression test fixtures | `tests/fixtures/permanent_loss/` (CIK + as_of_date + expected `exclude`) | +| Regression test fixtures | `investing/tests/fixtures/permanent_loss/` (CIK + as_of_date + expected `exclude`) | | Run date | Pipeline parameter | ## Outputs diff --git a/docs/mvp/features/portfolio-evolution.md b/investing/docs/features/portfolio-evolution.md similarity index 97% rename from docs/mvp/features/portfolio-evolution.md rename to investing/docs/features/portfolio-evolution.md index 27cf29a..6ec88f6 100644 --- a/docs/mvp/features/portfolio-evolution.md +++ b/investing/docs/features/portfolio-evolution.md @@ -12,7 +12,7 @@ The user must be able to answer the questions "have my own decisions been worth ## MVP scope - Consume the consolidated personal-operations CSV (already in EUR) as the single source of truth for the personal portfolio. -- Use the closed schema documented in `docs/mvp/architecture/architecture.md` (Date, Symbol, Type, Volume, Price, Value, Commission, Currency). +- Use the closed schema documented in `investing/docs/architecture/architecture.md` (Date, Symbol, Type, Volume, Price, Value, Commission, Currency). - Reconstruct daily NAV in EUR using adjusted prices from the curated price store. - Apply the broker-to-yfinance ticker mapping (`data/reference/ticker_mapping.csv`). - Treat bankrupt or delisted holdings systematically: write a closing price of zero on the delisting date instead of dropping the position. @@ -52,7 +52,7 @@ The user must be able to answer the questions "have my own decisions been worth | Model holdings over time | `curated/portfolio_evolution/model/holdings.parquet` (already produced upstream) | | Comparison metrics | `curated/portfolio_evolution/comparison/metrics.parquet` (cumulative return, drawdown, rolling Sharpe, alpha vs each benchmark) | | Attribution | `curated/portfolio_evolution/attribution/contribution.parquet` (per-ticker contribution to total return) | -| Streamlit page | `apps/dashboard/pages/portfolio_evolution.py` | +| Streamlit page | `investing/apps/dashboard/pages/portfolio_evolution.py` | | MLflow artifacts | NAV plots, drawdown plot, rolling Sharpe plot | ## Mermaid diagram diff --git a/docs/mvp/features/sell-watch.md b/investing/docs/features/sell-watch.md similarity index 97% rename from docs/mvp/features/sell-watch.md rename to investing/docs/features/sell-watch.md index b80623e..8441d9b 100644 --- a/docs/mvp/features/sell-watch.md +++ b/investing/docs/features/sell-watch.md @@ -2,7 +2,7 @@ ## Implementation status -**Deferred / historical — not scheduled** for the June 30 demo slice ([ADR-0002](../../adr/0002-june-demo-scope-cut.md)). Spec body preserved as design record; active Phase 2 / full-MVP investing track cancelled ([ADR-0003](../../adr/0003-phase2-qv-cancelled.md)). +**Deferred / historical — not scheduled** for the June 30 demo slice ([ADR-0002](../../../docs/adr/0002-june-demo-scope-cut.md)). Spec body preserved as design record; active Phase 2 / full-MVP investing track cancelled ([ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md)). ## Objective diff --git a/docs/mvp/features/universe-construction.md b/investing/docs/features/universe-construction.md similarity index 97% rename from docs/mvp/features/universe-construction.md rename to investing/docs/features/universe-construction.md index b76fb20..7e829d4 100644 --- a/docs/mvp/features/universe-construction.md +++ b/investing/docs/features/universe-construction.md @@ -2,7 +2,7 @@ ## Implementation status -**done** (demo slice) — universe builder ([#58](https://github.com/JLaborda/SmartWealthAI/issues/58)); industry exclusions reference CSV ([#56](https://github.com/JLaborda/SmartWealthAI/issues/56)). Full S&P 500 historical mode is **deferred / not scheduled** ([ADR-0003](../../adr/0003-phase2-qv-cancelled.md)). +**done** (demo slice) — universe builder ([#58](https://github.com/JLaborda/SmartWealthAI/issues/58)); industry exclusions reference CSV ([#56](https://github.com/JLaborda/SmartWealthAI/issues/56)). Full S&P 500 historical mode is **deferred / not scheduled** ([ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md)). ## Objective @@ -14,7 +14,7 @@ Produce the investable universe of US common stocks for each decision date. This - Seed universe: all SimFin US companies (`load_companies(market='us')`). - Exclude banks, insurers, and utilities via `data/reference/simfin_industry_exclusions.csv` (`IndustryId` list built from `load_industries()`). -- **Regeneration rules** (applied by `build_exclusions` in `src/smartwealthai/simfin_industry_exclusions.py`): +- **Regeneration rules** (applied by `build_exclusions` in `investing/src/investing/simfin_industry_exclusions.py`): - `bank`: SimFin industry name exactly `Banks` - `insurer`: industry name contains `Insurance` - `utility`: SimFin sector exactly `Utilities` @@ -140,7 +140,7 @@ flowchart TD - [x] No bank/insurance sanity-check ticker appears in the universe. - [x] Module consumes only curated/raw SimFin snapshots (no network). -**Code:** `src/smartwealthai/universe_builder.py`, CLI `poetry run build-universe`. +**Code:** `investing/src/investing/universe_builder.py`, CLI `poetry run build-universe`. ### Full MVP (phase 2) (historical) diff --git a/docs/mvp/features/unstructured-financial-data.md b/investing/docs/features/unstructured-financial-data.md similarity index 100% rename from docs/mvp/features/unstructured-financial-data.md rename to investing/docs/features/unstructured-financial-data.md diff --git a/docs/mvp/guides/download-fundamentals.md b/investing/docs/guides/download-fundamentals.md similarity index 85% rename from docs/mvp/guides/download-fundamentals.md rename to investing/docs/guides/download-fundamentals.md index ae1f0d6..a49397e 100644 --- a/docs/mvp/guides/download-fundamentals.md +++ b/investing/docs/guides/download-fundamentals.md @@ -1,6 +1,6 @@ # Guide: Download fundamentals (local spike — frozen) -> **Status:** This guide documents the **frozen SEC ETL spike** ([ADR-0001](../../adr/0001-simfin-fundamentals-mvp.md)). The June 30 demo pipeline uses **SimFin** instead — see [`demo-slice.md`](../demo-slice.md) and [`etl-data-lake.md`](../features/etl-data-lake.md). Do not delete this spike; resuming it under investing “phase 2” is **not scheduled** ([ADR-0003](../../adr/0003-phase2-qv-cancelled.md)). +> **Status:** This guide documents the **frozen SEC ETL spike** ([ADR-0001](../../../docs/adr/0001-simfin-fundamentals-mvp.md)). The June 30 demo pipeline uses **SimFin** instead — see [`demo-slice.md`](../demo-slice.md) and [`etl-data-lake.md`](../features/etl-data-lake.md). Do not delete this spike; resuming it under investing “phase 2” is **not scheduled** ([ADR-0003](../../../docs/adr/0003-phase2-qv-cancelled.md)). Operator guide for the first ETL vertical slice: download raw SEC `companyfacts` and standardized annual statements from `edgartools` for a parameterized universe. @@ -37,7 +37,7 @@ poetry run download-fundamentals --universe dow30 Equivalent module invocation: ```bash -poetry run python -m smartwealthai.download_fundamentals --universe dow30 +poetry run python -m investing.download_fundamentals --universe dow30 ``` A full Dow 30 run downloads **120 artifacts** (4 per CIK: 1 JSON + 3 parquet files) and @@ -145,18 +145,18 @@ flowchart TD | Module | Responsibility | | --- | --- | -| `smartwealthai.download_fundamentals` | CLI orchestration and run summary. | -| `smartwealthai.universe` | Preset resolution and CSV loading. | -| `smartwealthai.lake_paths` | Path builders for the local raw zone. | -| `smartwealthai.sec_client` | SEC REST client (throttle, retry, `companyfacts`). | -| `smartwealthai.edgartools_client` | `get_facts()` statement extraction to parquet. | +| `investing.download_fundamentals` | CLI orchestration and run summary. | +| `investing.universe` | Preset resolution and CSV loading. | +| `investing.lake_paths` | Path builders for the local raw zone. | +| `investing.sec_client` | SEC REST client (throttle, retry, `companyfacts`). | +| `investing.edgartools_client` | `get_facts()` statement extraction to parquet. | ## Tests Hermetic unit tests (no network): ```bash -poetry run pytest tests/test_download_fundamentals.py -q +poetry run pytest investing/tests/test_download_fundamentals.py -q ``` Integration smoke test (requires `SEC_IDENTITY` and network) is intentionally **not** part @@ -165,7 +165,7 @@ of PR CI. Run locally on a small CSV when validating credentials. ## Expanding universes 1. Add `data/reference/universes/.csv` with `ticker,cik` rows. -2. Register the preset in `UNIVERSE_PRESETS` inside `src/smartwealthai/universe.py`. +2. Register the preset in `UNIVERSE_PRESETS` inside `investing/src/investing/universe.py`. 3. Run: `poetry run download-fundamentals --universe `. Planned expansions: S&P 500, Russell 3000, Nasdaq — same CSV + preset pattern. diff --git a/docs/mvp/guides/download-simfin.md b/investing/docs/guides/download-simfin.md similarity index 88% rename from docs/mvp/guides/download-simfin.md rename to investing/docs/guides/download-simfin.md index d94cff1..0ba912a 100644 --- a/docs/mvp/guides/download-simfin.md +++ b/investing/docs/guides/download-simfin.md @@ -71,10 +71,10 @@ Pass `--ticker` to limit the normalize step to specific names (intersect univers | Module | Role | | --- | --- | -| `smartwealthai.simfin_client` | Configure API key, safe bulk download (zip-slip guarded), resolve cache CSV path. | -| `smartwealthai.download_simfin` | CLI orchestration, skip/force logic, run summary. | -| `smartwealthai.lake_paths` | Raw lake path builders for `raw/simfin/`. | +| `investing.simfin_client` | Configure API key, safe bulk download (zip-slip guarded), resolve cache CSV path. | +| `investing.download_simfin` | CLI orchestration, skip/force logic, run summary. | +| `investing.lake_paths` | Raw lake path builders for `raw/simfin/`. | ## Tests -Hermetic tests live in `tests/test_download_simfin.py`. They mock SimFin network calls; PR CI does not require a live API key. +Hermetic tests live in `investing/tests/test_download_simfin.py`. They mock SimFin network calls; PR CI does not require a live API key. diff --git a/docs/mvp/prds/ci-cd/ci-cd-prd.md b/investing/docs/prds/ci-cd/ci-cd-prd.md similarity index 97% rename from docs/mvp/prds/ci-cd/ci-cd-prd.md rename to investing/docs/prds/ci-cd/ci-cd-prd.md index 5951ba3..7a0d802 100644 --- a/docs/mvp/prds/ci-cd/ci-cd-prd.md +++ b/investing/docs/prds/ci-cd/ci-cd-prd.md @@ -1,7 +1,7 @@ # PRD: CI/CD and MLOps Infrastructure (Phase 0) **Status:** Ready for implementation -**Canonical architecture:** `docs/mvp/architecture/architecture.md` +**Canonical architecture:** `investing/docs/architecture/architecture.md` **Related specs:** ETL + data lake, permanent loss filter, backtesting, sell-watch (pipeline vertical slice) --- @@ -224,8 +224,8 @@ Makefile targets wrap Poetry commands so CI and humans share entrypoints: instal ## Further Notes -- This PRD captures decisions from the CI/CD design session (grill-me). It should be reflected in a future feature spec under `docs/mvp/features/` (e.g. `cicd-infrastructure.md`) and cross-linked from `docs/mvp/architecture/architecture.md` when implementation starts — per spec-driven workflow, the feature spec becomes canonical for acceptance criteria and implementation status. +- This PRD captures decisions from the CI/CD design session (grill-me). It should be reflected in a future feature spec under `investing/docs/features/` (e.g. `cicd-infrastructure.md`) and cross-linked from `investing/docs/architecture/architecture.md` when implementation starts — per spec-driven workflow, the feature spec becomes canonical for acceptance criteria and implementation status. - The core product vertical slice remains: ingest → permanent loss filter → quality (ROC) → cheapness (EY) → combined rank → model portfolio (~30 names) → backtest → sell-watch. CI/CD serves that slice, not the reverse. - Cost awareness: dev integration and ECS tasks should use minimal resource sizes and Spot where acceptable; align with the architecture soft budget (~low single-digit USD/month for control plane before storage growth). - When implementation begins, update `docs/README.md` to index this PRD under an MVP PRDs section alongside the devcontainer PRD. -- GitHub issue creation with label `ready-for-agent` is recommended for tracking vertical implementation slices (`to-issues`), but this document is the saved PRD artifact at `docs/mvp/prds/ci-cd/ci-cd-prd.md` as requested. +- GitHub issue creation with label `ready-for-agent` is recommended for tracking vertical implementation slices (`to-issues`), but this document is the saved PRD artifact at `investing/docs/prds/ci-cd/ci-cd-prd.md` as requested. diff --git a/docs/mvp/prds/devcontainer/prd.md b/investing/docs/prds/devcontainer/prd.md similarity index 94% rename from docs/mvp/prds/devcontainer/prd.md rename to investing/docs/prds/devcontainer/prd.md index 8b8ee9b..ba2b3d5 100644 --- a/docs/mvp/prds/devcontainer/prd.md +++ b/investing/docs/prds/devcontainer/prd.md @@ -119,8 +119,8 @@ This is an infrastructure/tooling PRD, not a feature module. There is no applica ## Further Notes - The devcontainer config is fully version-controlled under `.devcontainer/` and evolves with the project. Any team member (or the developer on a new machine) gets the same environment by opening the repo. -- The architecture doc (`docs/mvp/architecture/architecture.md`) references GitHub Actions + ECR for Docker image builds. The devcontainer's Docker-in-Docker feature allows local testing of those images before pushing. -- This PRD does not create a feature spec under `docs/mvp/features/` because the devcontainer is developer tooling, not an MVP feature module. It is tracked as a standalone PRD. +- The architecture doc (`investing/docs/architecture/architecture.md`) references GitHub Actions + ECR for Docker image builds. The devcontainer's Docker-in-Docker feature allows local testing of those images before pushing. +- This PRD does not create a feature spec under `investing/docs/features/` because the devcontainer is developer tooling, not an MVP feature module. It is tracked as a standalone PRD. ## Files to Create or Modify diff --git a/docs/mvp/prds/phase2/prd.md b/investing/docs/prds/phase2/prd.md similarity index 98% rename from docs/mvp/prds/phase2/prd.md rename to investing/docs/prds/phase2/prd.md index b27007d..afc59b4 100644 --- a/docs/mvp/prds/phase2/prd.md +++ b/investing/docs/prds/phase2/prd.md @@ -1,10 +1,10 @@ # PRD: MVP Phase 2 — Quantitative Value, Cloud, Backtest, Sell-Watch -**Status:** Cancelled — not in active execution ([ADR-0003](../../../adr/0003-phase2-qv-cancelled.md); branch `archive/phase2-qv`) -**Canonical architecture:** `docs/mvp/architecture/architecture.md` (frozen historical vision) -**Prior delivery:** June 30 demo slice (`docs/mvp/demo-slice.md`, ADR-0002) +**Status:** Cancelled — not in active execution ([ADR-0003](../../../../docs/adr/0003-phase2-qv-cancelled.md); branch `archive/phase2-qv`) +**Canonical architecture:** `investing/docs/architecture/architecture.md` (frozen historical vision) +**Prior delivery:** June 30 demo slice (`investing/docs/demo-slice.md`, ADR-0002) **Related specs:** ETL + data lake, permanent loss filter, backtesting, sell-watch, universe construction, dashboard reporting -**Related PRDs:** CI/CD (`docs/mvp/prds/ci-cd/ci-cd-prd.md`) +**Related PRDs:** CI/CD (`investing/docs/prds/ci-cd/ci-cd-prd.md`) **Capacity assumption:** Solo developer, ~10–15 hours per week **Estimated calendar:** Phase 2a ~13–16 weeks; Phase 2b ~8–12 weeks (~5–7 months total) — **superseded; do not schedule** @@ -274,7 +274,7 @@ When implementation starts, resolve: - `fixture_lake.py` and `point_in_time_fundamentals()` for PIT fixture queries. - `magic_formula_ranking.py` tests (if present) for rank assignment and portfolio selection patterns. - CI/CD PRD user story 7: smoke test on fixture vertical slice — **update smoke to QV funnel** once forensic + funnel exist; keep MF benchmark unit tests separate. -- Permanent loss spec: Enron / Lehman / WorldCom regression cases under `tests/fixtures/permanent_loss/`. +- Permanent loss spec: Enron / Lehman / WorldCom regression cases under `investing/tests/fixtures/permanent_loss/`. ### CI vs integration @@ -313,7 +313,7 @@ When implementation starts, resolve: ### Relationship to other documents -- This PRD does **not** replace per-module feature specs; it coordinates them. Each module keeps acceptance criteria in `docs/mvp/features/`. +- This PRD does **not** replace per-module feature specs; it coordinates them. Each module keeps acceptance criteria in `investing/docs/features/`. - Implementers should read ADR-0001 (SimFin fundamentals), ADR-0002 (demo scope cut / no Zipline), and the CI/CD PRD before cloud work. - After Phase 2a ships, update `demo-slice.md` "After the demo" ordering and `architecture.md` functional flow to state QV as production scoring. diff --git a/docs/mvp/requirements/requirements.md b/investing/docs/requirements/requirements.md similarity index 94% rename from docs/mvp/requirements/requirements.md rename to investing/docs/requirements/requirements.md index f5fc26f..6d490b3 100644 --- a/docs/mvp/requirements/requirements.md +++ b/investing/docs/requirements/requirements.md @@ -36,4 +36,4 @@ Build a minimal Python pipeline that downloads financial data for a very small s - Databases, GUI, Docker, machine learning - Downloading thousands of tickers -Those belong to later MVP modules documented under `docs/mvp/features/`. +Those belong to later MVP modules documented under `investing/docs/features/`. diff --git a/src/smartwealthai/__init__.py b/investing/src/investing/__init__.py similarity index 85% rename from src/smartwealthai/__init__.py rename to investing/src/investing/__init__.py index 2ba6f9c..d9b1985 100644 --- a/src/smartwealthai/__init__.py +++ b/investing/src/investing/__init__.py @@ -1,4 +1,4 @@ -"""SmartWealthAI application package (MVP pipeline). +"""Investing (Magic Formula) application package (MVP pipeline). Submodules ---------- diff --git a/src/smartwealthai/build_universe.py b/investing/src/investing/build_universe.py similarity index 93% rename from src/smartwealthai/build_universe.py rename to investing/src/investing/build_universe.py index d22d110..aa629a4 100644 --- a/src/smartwealthai/build_universe.py +++ b/investing/src/investing/build_universe.py @@ -8,8 +8,8 @@ import click from click.testing import CliRunner -from smartwealthai.simfin_industry_exclusions import REFERENCE_PATH -from smartwealthai.universe_builder import build_universe +from investing.simfin_industry_exclusions import REFERENCE_PATH +from investing.universe_builder import build_universe @click.command(context_settings={"help_option_names": ["-h", "--help"]}) diff --git a/src/smartwealthai/compute_metrics.py b/investing/src/investing/compute_metrics.py similarity index 95% rename from src/smartwealthai/compute_metrics.py rename to investing/src/investing/compute_metrics.py index 16a9a58..e97bbfd 100644 --- a/src/smartwealthai/compute_metrics.py +++ b/investing/src/investing/compute_metrics.py @@ -8,8 +8,8 @@ import click from click.testing import CliRunner -from smartwealthai.magic_formula_metrics import MetricsResult -from smartwealthai.pit_fundamentals import MetricsInputError, compute_metrics_for_ticker +from investing.magic_formula_metrics import MetricsResult +from investing.pit_fundamentals import MetricsInputError, compute_metrics_for_ticker @click.command(context_settings={"help_option_names": ["-h", "--help"]}) diff --git a/src/smartwealthai/dashboard_data.py b/investing/src/investing/dashboard_data.py similarity index 96% rename from src/smartwealthai/dashboard_data.py rename to investing/src/investing/dashboard_data.py index 1d6f397..555e397 100644 --- a/src/smartwealthai/dashboard_data.py +++ b/investing/src/investing/dashboard_data.py @@ -10,7 +10,7 @@ import pandas as pd -from smartwealthai.lake_paths import curated_combined_ranking_path, curated_portfolio_path +from investing.lake_paths import curated_combined_ranking_path, curated_portfolio_path _RUN_DATE_RE = re.compile(r"run_date=(\d{4}-\d{2}-\d{2})") diff --git a/src/smartwealthai/download_fundamentals.py b/investing/src/investing/download_fundamentals.py similarity index 93% rename from src/smartwealthai/download_fundamentals.py rename to investing/src/investing/download_fundamentals.py index b027cd9..f8d7dc6 100644 --- a/src/smartwealthai/download_fundamentals.py +++ b/investing/src/investing/download_fundamentals.py @@ -1,7 +1,7 @@ """CLI to download raw SEC companyfacts and edgartools statements for a universe. Orchestrates the fundamentals download spike documented in -``docs/mvp/features/etl-data-lake.md``. For each ``(ticker, cik)`` in the universe: +``investing/docs/features/etl-data-lake.md``. For each ``(ticker, cik)`` in the universe: 1. SEC REST — verbatim ``companyfacts`` JSON. 2. edgartools — annual income, balance, and cash-flow statements as parquet. @@ -11,7 +11,7 @@ export SEC_IDENTITY="Your Name your@email.com" poetry run download-fundamentals --universe dow30 -See ``docs/mvp/guides/download-fundamentals.md`` for the full operator guide. +See ``investing/docs/guides/download-fundamentals.md`` for the full operator guide. """ from __future__ import annotations @@ -24,15 +24,15 @@ import click -from smartwealthai.edgartools_client import EdgartoolsClientError, download_statements -from smartwealthai.lake_paths import ( +from investing.edgartools_client import EdgartoolsClientError, download_statements +from investing.lake_paths import ( STATEMENT_NAMES, companyfacts_path, edgartools_statement_path, errors_path, ) -from smartwealthai.sec_client import SecClientError, fetch_companyfacts -from smartwealthai.universe import UniverseEntry, load_universe +from investing.sec_client import SecClientError, fetch_companyfacts +from investing.universe import UniverseEntry, load_universe logger = logging.getLogger(__name__) @@ -64,7 +64,7 @@ def download_entry( ) -> DownloadResult: """Download all raw artifacts for one universe entry. - Produces up to four files under ``data_dir/raw/`` (see :mod:`smartwealthai.lake_paths`). + Produces up to four files under ``data_dir/raw/`` (see :mod:`investing.lake_paths`). Failures are captured in the result rather than raised, so the CLI can continue with the remaining issuers. @@ -181,7 +181,7 @@ def run_download( @click.command( context_settings={"help_option_names": ["-h", "--help"]}, - epilog="Guide: docs/mvp/guides/download-fundamentals.md", + epilog="Guide: investing/docs/guides/download-fundamentals.md", ) @click.option("--universe", help="Universe preset name (e.g. dow30).") @click.option( diff --git a/src/smartwealthai/download_prices.py b/investing/src/investing/download_prices.py similarity index 96% rename from src/smartwealthai/download_prices.py rename to investing/src/investing/download_prices.py index c023e82..ac3b1d6 100644 --- a/src/smartwealthai/download_prices.py +++ b/investing/src/investing/download_prices.py @@ -10,8 +10,8 @@ import click from click.testing import CliRunner -from smartwealthai.lake_paths import price_ingest_errors_path -from smartwealthai.price_ingest import run_price_ingest +from investing.lake_paths import price_ingest_errors_path +from investing.price_ingest import run_price_ingest logger = logging.getLogger(__name__) diff --git a/src/smartwealthai/download_simfin.py b/investing/src/investing/download_simfin.py similarity index 95% rename from src/smartwealthai/download_simfin.py rename to investing/src/investing/download_simfin.py index 96d065d..065b57e 100644 --- a/src/smartwealthai/download_simfin.py +++ b/investing/src/investing/download_simfin.py @@ -1,7 +1,7 @@ """CLI to download SimFin bulk US fundamentals into the raw data lake. Orchestrates the demo SimFin connector documented in -``docs/mvp/features/etl-data-lake.md``. Downloads ``companies``, ``industries``, +``investing/docs/features/etl-data-lake.md``. Downloads ``companies``, ``industries``, ``income`` (TTM), ``balance`` (quarterly), ``cashflow`` (TTM), and ``shareprices`` (latest) for ``market=us``. @@ -10,7 +10,7 @@ export SIMFIN_API_KEY="" poetry run download-simfin -See ``docs/mvp/guides/download-simfin.md`` for the operator guide. +See ``investing/docs/guides/download-simfin.md`` for the operator guide. """ from __future__ import annotations @@ -28,8 +28,8 @@ import click from click.testing import CliRunner -from smartwealthai.lake_paths import simfin_bulk_path, simfin_errors_path -from smartwealthai.simfin_client import configure_simfin, fetch_dataset_csv +from investing.lake_paths import simfin_bulk_path, simfin_errors_path +from investing.simfin_client import configure_simfin, fetch_dataset_csv logger = logging.getLogger(__name__) diff --git a/src/smartwealthai/edgartools_client.py b/investing/src/investing/edgartools_client.py similarity index 94% rename from src/smartwealthai/edgartools_client.py rename to investing/src/investing/edgartools_client.py index c2ed9ff..9e491e9 100644 --- a/src/smartwealthai/edgartools_client.py +++ b/investing/src/investing/edgartools_client.py @@ -7,7 +7,7 @@ Output parquets preserve the edgartools dataframe shape (``concept``, ``label``, ``section``, ``FY 20xx`` columns). -Operator guide: ``docs/mvp/guides/download-fundamentals.md``. +Operator guide: ``investing/docs/guides/download-fundamentals.md``. """ from __future__ import annotations @@ -17,8 +17,8 @@ import pandas as pd from edgar import Company, set_identity -from smartwealthai.lake_paths import STATEMENT_NAMES -from smartwealthai.sec_client import get_sec_identity +from investing.lake_paths import STATEMENT_NAMES +from investing.sec_client import get_sec_identity class EdgartoolsClientError(Exception): diff --git a/src/smartwealthai/fixture_lake.py b/investing/src/investing/fixture_lake.py similarity index 100% rename from src/smartwealthai/fixture_lake.py rename to investing/src/investing/fixture_lake.py diff --git a/src/smartwealthai/generate_simfin_industry_exclusions.py b/investing/src/investing/generate_simfin_industry_exclusions.py similarity index 96% rename from src/smartwealthai/generate_simfin_industry_exclusions.py rename to investing/src/investing/generate_simfin_industry_exclusions.py index 06c8f3c..eb3b264 100644 --- a/src/smartwealthai/generate_simfin_industry_exclusions.py +++ b/investing/src/investing/generate_simfin_industry_exclusions.py @@ -8,7 +8,7 @@ import pandas as pd from click.testing import CliRunner -from smartwealthai.simfin_industry_exclusions import ( +from investing.simfin_industry_exclusions import ( REFERENCE_PATH, build_exclusions, write_exclusions, diff --git a/src/smartwealthai/lake_paths.py b/investing/src/investing/lake_paths.py similarity index 99% rename from src/smartwealthai/lake_paths.py rename to investing/src/investing/lake_paths.py index c5064fa..0ceb3c1 100644 --- a/src/smartwealthai/lake_paths.py +++ b/investing/src/investing/lake_paths.py @@ -1,6 +1,6 @@ """Path builders for the local data lake raw zone. -Paths follow the layout documented in ``docs/mvp/features/etl-data-lake.md`` (Fundamentals +Paths follow the layout documented in ``investing/docs/features/etl-data-lake.md`` (Fundamentals download spike) so local runs can move to S3 without renaming partitions. Example:: diff --git a/src/smartwealthai/magic_formula_metrics.py b/investing/src/investing/magic_formula_metrics.py similarity index 100% rename from src/smartwealthai/magic_formula_metrics.py rename to investing/src/investing/magic_formula_metrics.py diff --git a/src/smartwealthai/magic_formula_ranking.py b/investing/src/investing/magic_formula_ranking.py similarity index 98% rename from src/smartwealthai/magic_formula_ranking.py rename to investing/src/investing/magic_formula_ranking.py index 2a9462c..67545ab 100644 --- a/src/smartwealthai/magic_formula_ranking.py +++ b/investing/src/investing/magic_formula_ranking.py @@ -8,7 +8,7 @@ import pandas as pd -from smartwealthai.lake_paths import ( +from investing.lake_paths import ( curated_cheap_issues_path, curated_cheap_scores_path, curated_combined_ranking_path, @@ -18,8 +18,8 @@ curated_universe_path, pad_cik, ) -from smartwealthai.magic_formula_metrics import FORMULA_VERSION, MetricsResult -from smartwealthai.pit_fundamentals import compute_metrics_for_tickers +from investing.magic_formula_metrics import FORMULA_VERSION, MetricsResult +from investing.pit_fundamentals import compute_metrics_for_tickers DEFAULT_PORTFOLIO_SIZE = 30 @@ -447,7 +447,7 @@ def score_universe( mlflow_run_id: str | None = None if not skip_mlflow: - from smartwealthai.mlflow_run_logging import log_demo_pipeline_run + from investing.mlflow_run_logging import log_demo_pipeline_run roc_values = [ result.roc for result in partitioned.quality_scorable if result.roc is not None diff --git a/src/smartwealthai/mlflow_run_logging.py b/investing/src/investing/mlflow_run_logging.py similarity index 100% rename from src/smartwealthai/mlflow_run_logging.py rename to investing/src/investing/mlflow_run_logging.py diff --git a/src/smartwealthai/normalize_simfin.py b/investing/src/investing/normalize_simfin.py similarity index 95% rename from src/smartwealthai/normalize_simfin.py rename to investing/src/investing/normalize_simfin.py index 4190e3e..337ede7 100644 --- a/src/smartwealthai/normalize_simfin.py +++ b/investing/src/investing/normalize_simfin.py @@ -9,8 +9,8 @@ import pandas as pd from click.testing import CliRunner -from smartwealthai.lake_paths import curated_universe_path -from smartwealthai.simfin_normalizer import DEFAULT_MAPPING_PATH, normalize_simfin +from investing.lake_paths import curated_universe_path +from investing.simfin_normalizer import DEFAULT_MAPPING_PATH, normalize_simfin def resolve_show_progress(*, progress: bool, quiet: bool) -> bool | None: @@ -54,7 +54,7 @@ def resolve_normalize_tickers( if ticker_set is None: msg = ( "Pass --universe-run-date (after build-universe) or --ticker to limit scope. " - "See docs/mvp/guides/download-simfin.md." + "See investing/docs/guides/download-simfin.md." ) raise click.ClickException(msg) return ticker_set diff --git a/src/smartwealthai/pit_fundamentals.py b/investing/src/investing/pit_fundamentals.py similarity index 98% rename from src/smartwealthai/pit_fundamentals.py rename to investing/src/investing/pit_fundamentals.py index 4f26bb1..81dd34a 100644 --- a/src/smartwealthai/pit_fundamentals.py +++ b/investing/src/investing/pit_fundamentals.py @@ -11,12 +11,12 @@ import click import pandas as pd -from smartwealthai.lake_paths import ( +from investing.lake_paths import ( curated_prices_snapshot_path, curated_universe_path, pad_cik, ) -from smartwealthai.magic_formula_metrics import MetricsResult, build_metrics +from investing.magic_formula_metrics import MetricsResult, build_metrics # ponytail: I/O-bound cap; raise if profiling shows disk saturation _READ_WORKERS = min(8, (os.cpu_count() or 4) + 2) diff --git a/src/smartwealthai/price_ingest.py b/investing/src/investing/price_ingest.py similarity index 99% rename from src/smartwealthai/price_ingest.py rename to investing/src/investing/price_ingest.py index a47481a..e7c59a5 100644 --- a/src/smartwealthai/price_ingest.py +++ b/investing/src/investing/price_ingest.py @@ -8,7 +8,7 @@ import pandas as pd -from smartwealthai.lake_paths import ( +from investing.lake_paths import ( curated_prices_snapshot_path, curated_universe_path, simfin_bulk_path, diff --git a/src/smartwealthai/run_dashboard.py b/investing/src/investing/run_dashboard.py similarity index 86% rename from src/smartwealthai/run_dashboard.py rename to investing/src/investing/run_dashboard.py index 19c8493..d82f024 100644 --- a/src/smartwealthai/run_dashboard.py +++ b/investing/src/investing/run_dashboard.py @@ -11,7 +11,7 @@ def _repo_root() -> Path: - return Path(__file__).resolve().parents[2] + return Path(__file__).resolve().parents[3] @click.command(context_settings={"help_option_names": ["-h", "--help"]}) @@ -36,12 +36,13 @@ def main(data_dir: Path | None, run_date: click.DateTime | None) -> None: env["SMARTWEALTHAI_RUN_DATE"] = run_date.strftime("%Y-%m-%d") root = _repo_root() - pythonpath = str(root) + # investing/ on PYTHONPATH so Streamlit pages can `import apps.dashboard.common` + pythonpath = str(root / "investing") if existing := env.get("PYTHONPATH"): pythonpath = f"{pythonpath}{os.pathsep}{existing}" env["PYTHONPATH"] = pythonpath - home = root / "apps" / "dashboard" / "Home.py" + home = root / "investing" / "apps" / "dashboard" / "Home.py" subprocess.run( [ sys.executable, diff --git a/src/smartwealthai/run_demo_pipeline.py b/investing/src/investing/run_demo_pipeline.py similarity index 96% rename from src/smartwealthai/run_demo_pipeline.py rename to investing/src/investing/run_demo_pipeline.py index a0772c9..0c6cdf8 100644 --- a/src/smartwealthai/run_demo_pipeline.py +++ b/investing/src/investing/run_demo_pipeline.py @@ -11,12 +11,12 @@ import click from click.testing import CliRunner -from smartwealthai.download_simfin import run_download -from smartwealthai.magic_formula_ranking import ScoringResult, score_universe -from smartwealthai.normalize_simfin import resolve_normalize_tickers -from smartwealthai.price_ingest import run_price_ingest -from smartwealthai.simfin_normalizer import normalize_simfin -from smartwealthai.universe_builder import build_universe +from investing.download_simfin import run_download +from investing.magic_formula_ranking import ScoringResult, score_universe +from investing.normalize_simfin import resolve_normalize_tickers +from investing.price_ingest import run_price_ingest +from investing.simfin_normalizer import normalize_simfin +from investing.universe_builder import build_universe logger = logging.getLogger(__name__) diff --git a/src/smartwealthai/score_universe.py b/investing/src/investing/score_universe.py similarity index 93% rename from src/smartwealthai/score_universe.py rename to investing/src/investing/score_universe.py index 7551dc8..8b530ee 100644 --- a/src/smartwealthai/score_universe.py +++ b/investing/src/investing/score_universe.py @@ -8,13 +8,13 @@ import click from click.testing import CliRunner -from smartwealthai.magic_formula_ranking import score_universe -from smartwealthai.normalize_simfin import resolve_show_progress +from investing.magic_formula_ranking import score_universe +from investing.normalize_simfin import resolve_show_progress def format_scoring_summary(result: object) -> str: """Render a human-readable scoring run summary.""" - from smartwealthai.magic_formula_ranking import ScoringResult + from investing.magic_formula_ranking import ScoringResult if not isinstance(result, ScoringResult): msg = "expected ScoringResult" diff --git a/src/smartwealthai/sec_client.py b/investing/src/investing/sec_client.py similarity index 98% rename from src/smartwealthai/sec_client.py rename to investing/src/investing/sec_client.py index d2bb8c6..c60ce60 100644 --- a/src/smartwealthai/sec_client.py +++ b/investing/src/investing/sec_client.py @@ -6,7 +6,7 @@ - Request throttling (~8 req/s). - Retries with exponential backoff on transient failures. -Operator guide: ``docs/mvp/guides/download-fundamentals.md``. +Operator guide: ``investing/docs/guides/download-fundamentals.md``. """ from __future__ import annotations diff --git a/src/smartwealthai/simfin_client.py b/investing/src/investing/simfin_client.py similarity index 100% rename from src/smartwealthai/simfin_client.py rename to investing/src/investing/simfin_client.py diff --git a/src/smartwealthai/simfin_industry_exclusions.py b/investing/src/investing/simfin_industry_exclusions.py similarity index 100% rename from src/smartwealthai/simfin_industry_exclusions.py rename to investing/src/investing/simfin_industry_exclusions.py diff --git a/src/smartwealthai/simfin_normalizer.py b/investing/src/investing/simfin_normalizer.py similarity index 99% rename from src/smartwealthai/simfin_normalizer.py rename to investing/src/investing/simfin_normalizer.py index 6dc11c9..b3a3d13 100644 --- a/src/smartwealthai/simfin_normalizer.py +++ b/investing/src/investing/simfin_normalizer.py @@ -13,7 +13,7 @@ import click import pandas as pd -from smartwealthai.lake_paths import ( +from investing.lake_paths import ( curated_fundamentals_path, curated_issues_path, fiscal_period_label, @@ -24,7 +24,7 @@ ) DEFAULT_MAPPING_PATH = ( - Path(__file__).resolve().parents[2] / "config" / "fundamentals" / "simfin_mapping_v1.yaml" + Path(__file__).resolve().parents[3] / "config" / "fundamentals" / "simfin_mapping_v1.yaml" ) CANONICAL_VALUE_FIELDS: tuple[str, ...] = ( diff --git a/src/smartwealthai/universe.py b/investing/src/investing/universe.py similarity index 98% rename from src/smartwealthai/universe.py rename to investing/src/investing/universe.py index 6812c64..723fdf8 100644 --- a/src/smartwealthai/universe.py +++ b/investing/src/investing/universe.py @@ -13,7 +13,7 @@ import pandas as pd -REPO_ROOT = Path(__file__).resolve().parents[2] +REPO_ROOT = Path(__file__).resolve().parents[3] UNIVERSE_PRESETS: dict[str, Path] = { "dow30": REPO_ROOT / "data" / "reference" / "universes" / "dow30.csv", diff --git a/src/smartwealthai/universe_builder.py b/investing/src/investing/universe_builder.py similarity index 97% rename from src/smartwealthai/universe_builder.py rename to investing/src/investing/universe_builder.py index f771b50..056b53c 100644 --- a/src/smartwealthai/universe_builder.py +++ b/investing/src/investing/universe_builder.py @@ -8,13 +8,13 @@ import pandas as pd -from smartwealthai.lake_paths import ( +from investing.lake_paths import ( curated_exclusions_path, curated_universe_path, pad_cik, simfin_bulk_path, ) -from smartwealthai.simfin_industry_exclusions import REFERENCE_PATH, load_exclusions +from investing.simfin_industry_exclusions import REFERENCE_PATH, load_exclusions UNIVERSE_COLUMNS: tuple[str, ...] = ( "run_date", diff --git a/tests/fixtures/lake/MANIFEST.json b/investing/tests/fixtures/lake/MANIFEST.json similarity index 100% rename from tests/fixtures/lake/MANIFEST.json rename to investing/tests/fixtures/lake/MANIFEST.json diff --git a/tests/fixtures/lake/README.md b/investing/tests/fixtures/lake/README.md similarity index 100% rename from tests/fixtures/lake/README.md rename to investing/tests/fixtures/lake/README.md diff --git a/tests/fixtures/lake/curated/fundamentals.csv b/investing/tests/fixtures/lake/curated/fundamentals.csv similarity index 100% rename from tests/fixtures/lake/curated/fundamentals.csv rename to investing/tests/fixtures/lake/curated/fundamentals.csv diff --git a/tests/fixtures/lake/raw/sec_edgar/cik=0000320193/endpoint=companyfacts/as_of_date=2026-05-21/response.json b/investing/tests/fixtures/lake/raw/sec_edgar/cik=0000320193/endpoint=companyfacts/as_of_date=2026-05-21/response.json similarity index 100% rename from tests/fixtures/lake/raw/sec_edgar/cik=0000320193/endpoint=companyfacts/as_of_date=2026-05-21/response.json rename to investing/tests/fixtures/lake/raw/sec_edgar/cik=0000320193/endpoint=companyfacts/as_of_date=2026-05-21/response.json diff --git a/tests/fixtures/lake/raw/sec_edgar/cik=0000789019/endpoint=companyfacts/as_of_date=2026-05-21/response.json b/investing/tests/fixtures/lake/raw/sec_edgar/cik=0000789019/endpoint=companyfacts/as_of_date=2026-05-21/response.json similarity index 100% rename from tests/fixtures/lake/raw/sec_edgar/cik=0000789019/endpoint=companyfacts/as_of_date=2026-05-21/response.json rename to investing/tests/fixtures/lake/raw/sec_edgar/cik=0000789019/endpoint=companyfacts/as_of_date=2026-05-21/response.json diff --git a/tests/fixtures/lake/raw/simfin/dataset=balance/variant=quarterly/market=us/as_of_date=2026-06-18/us-balance-quarterly.csv b/investing/tests/fixtures/lake/raw/simfin/dataset=balance/variant=quarterly/market=us/as_of_date=2026-06-18/us-balance-quarterly.csv similarity index 100% rename from tests/fixtures/lake/raw/simfin/dataset=balance/variant=quarterly/market=us/as_of_date=2026-06-18/us-balance-quarterly.csv rename to investing/tests/fixtures/lake/raw/simfin/dataset=balance/variant=quarterly/market=us/as_of_date=2026-06-18/us-balance-quarterly.csv diff --git a/tests/fixtures/lake/raw/simfin/dataset=companies/variant=default/market=us/as_of_date=2026-06-18/us-companies.csv b/investing/tests/fixtures/lake/raw/simfin/dataset=companies/variant=default/market=us/as_of_date=2026-06-18/us-companies.csv similarity index 100% rename from tests/fixtures/lake/raw/simfin/dataset=companies/variant=default/market=us/as_of_date=2026-06-18/us-companies.csv rename to investing/tests/fixtures/lake/raw/simfin/dataset=companies/variant=default/market=us/as_of_date=2026-06-18/us-companies.csv diff --git a/tests/fixtures/lake/raw/simfin/dataset=income/variant=ttm/market=us/as_of_date=2026-06-18/us-income-ttm.csv b/investing/tests/fixtures/lake/raw/simfin/dataset=income/variant=ttm/market=us/as_of_date=2026-06-18/us-income-ttm.csv similarity index 100% rename from tests/fixtures/lake/raw/simfin/dataset=income/variant=ttm/market=us/as_of_date=2026-06-18/us-income-ttm.csv rename to investing/tests/fixtures/lake/raw/simfin/dataset=income/variant=ttm/market=us/as_of_date=2026-06-18/us-income-ttm.csv diff --git a/tests/fixtures/lake/raw/simfin/dataset=industries/variant=default/market=us/as_of_date=2026-06-18/industries.csv b/investing/tests/fixtures/lake/raw/simfin/dataset=industries/variant=default/market=us/as_of_date=2026-06-18/industries.csv similarity index 100% rename from tests/fixtures/lake/raw/simfin/dataset=industries/variant=default/market=us/as_of_date=2026-06-18/industries.csv rename to investing/tests/fixtures/lake/raw/simfin/dataset=industries/variant=default/market=us/as_of_date=2026-06-18/industries.csv diff --git a/tests/fixtures/lake/raw/simfin/dataset=shareprices/variant=latest/market=us/as_of_date=2026-06-18/us-shareprices-latest.csv b/investing/tests/fixtures/lake/raw/simfin/dataset=shareprices/variant=latest/market=us/as_of_date=2026-06-18/us-shareprices-latest.csv similarity index 100% rename from tests/fixtures/lake/raw/simfin/dataset=shareprices/variant=latest/market=us/as_of_date=2026-06-18/us-shareprices-latest.csv rename to investing/tests/fixtures/lake/raw/simfin/dataset=shareprices/variant=latest/market=us/as_of_date=2026-06-18/us-shareprices-latest.csv diff --git a/tests/fixtures/lake/raw/yfinance/ticker=AAPL/endpoint=history/as_of_date=2026-05-21/history.json b/investing/tests/fixtures/lake/raw/yfinance/ticker=AAPL/endpoint=history/as_of_date=2026-05-21/history.json similarity index 100% rename from tests/fixtures/lake/raw/yfinance/ticker=AAPL/endpoint=history/as_of_date=2026-05-21/history.json rename to investing/tests/fixtures/lake/raw/yfinance/ticker=AAPL/endpoint=history/as_of_date=2026-05-21/history.json diff --git a/tests/fixtures/lake/raw/yfinance/ticker=MSFT/endpoint=history/as_of_date=2026-05-21/history.json b/investing/tests/fixtures/lake/raw/yfinance/ticker=MSFT/endpoint=history/as_of_date=2026-05-21/history.json similarity index 100% rename from tests/fixtures/lake/raw/yfinance/ticker=MSFT/endpoint=history/as_of_date=2026-05-21/history.json rename to investing/tests/fixtures/lake/raw/yfinance/ticker=MSFT/endpoint=history/as_of_date=2026-05-21/history.json diff --git a/tests/test_ci_baseline.py b/investing/tests/test_ci_baseline.py similarity index 95% rename from tests/test_ci_baseline.py rename to investing/tests/test_ci_baseline.py index 47222df..51ca108 100644 --- a/tests/test_ci_baseline.py +++ b/investing/tests/test_ci_baseline.py @@ -2,8 +2,8 @@ from datetime import date -from smartwealthai import __version__ -from smartwealthai.fixture_lake import ( +from investing import __version__ +from investing.fixture_lake import ( load_fixture_fundamentals, load_fixture_sec_companyfacts, load_fixture_yfinance_history, diff --git a/tests/test_dashboard_data.py b/investing/tests/test_dashboard_data.py similarity index 86% rename from tests/test_dashboard_data.py rename to investing/tests/test_dashboard_data.py index 4a73e97..4f24829 100644 --- a/tests/test_dashboard_data.py +++ b/investing/tests/test_dashboard_data.py @@ -13,21 +13,21 @@ import pytest from click.testing import CliRunner -from smartwealthai.dashboard_data import ( +from investing.dashboard_data import ( DashboardSnapshot, discover_latest_run_date, load_dashboard_snapshot, overview_headline, resolve_data_dir, ) -from smartwealthai.magic_formula_ranking import score_universe -from smartwealthai.normalize_simfin import cli_run as normalize_cli_run -from smartwealthai.price_ingest import run_price_ingest -from smartwealthai.run_dashboard import _repo_root -from smartwealthai.run_dashboard import main as run_dashboard_main -from smartwealthai.universe_builder import build_universe - -FIXTURE_LAKE = Path("tests/fixtures/lake") +from investing.magic_formula_ranking import score_universe +from investing.normalize_simfin import cli_run as normalize_cli_run +from investing.price_ingest import run_price_ingest +from investing.run_dashboard import _repo_root +from investing.run_dashboard import main as run_dashboard_main +from investing.universe_builder import build_universe + +FIXTURE_LAKE = Path("investing/tests/fixtures/lake") RUN_DATE = date(2026, 6, 18) SNAPSHOT_DATE = date(2026, 6, 18) @@ -140,7 +140,7 @@ def test_resolve_data_dir_honors_env(monkeypatch: pytest.MonkeyPatch) -> None: def test_run_dashboard_entry_point_registered() -> None: result = subprocess.run( - [sys.executable, "-m", "smartwealthai.run_dashboard", "--help"], + [sys.executable, "-m", "investing.run_dashboard", "--help"], capture_output=True, text=True, check=False, @@ -151,7 +151,7 @@ def test_run_dashboard_entry_point_registered() -> None: def test_run_dashboard_sets_env_and_launches_streamlit() -> None: runner = CliRunner() - with patch("smartwealthai.run_dashboard.subprocess.run") as mock_run: + with patch("investing.run_dashboard.subprocess.run") as mock_run: result = runner.invoke( run_dashboard_main, ["--data-dir", "/tmp/lake", "--run-date", "2026-06-18"], @@ -166,27 +166,27 @@ def test_run_dashboard_sets_env_and_launches_streamlit() -> None: env = kwargs["env"] assert env["SMARTWEALTHAI_DATA_DIR"] == "/tmp/lake" assert env["SMARTWEALTHAI_RUN_DATE"] == "2026-06-18" - assert env["PYTHONPATH"].startswith(str(_repo_root())) + assert env["PYTHONPATH"].startswith(str(_repo_root() / "investing")) assert env["PYTHONPATH"].endswith("/existing") def test_run_dashboard_launches_without_optional_flags() -> None: runner = CliRunner() - with patch("smartwealthai.run_dashboard.subprocess.run") as mock_run: + with patch("investing.run_dashboard.subprocess.run") as mock_run: result = runner.invoke(run_dashboard_main, []) assert result.exit_code == 0 env = mock_run.call_args.kwargs["env"] assert "SMARTWEALTHAI_DATA_DIR" not in env assert "SMARTWEALTHAI_RUN_DATE" not in env - assert env["PYTHONPATH"] == str(_repo_root()) + assert env["PYTHONPATH"] == str(_repo_root() / "investing") def test_run_dashboard_main_module_entrypoint(monkeypatch: pytest.MonkeyPatch) -> None: import runpy monkeypatch.setattr(sys, "argv", ["run-dashboard"]) - with patch("smartwealthai.run_dashboard.subprocess.run"): + with patch("investing.run_dashboard.subprocess.run"): with pytest.raises(SystemExit) as exc_info: - runpy.run_module("smartwealthai.run_dashboard", run_name="__main__") + runpy.run_module("investing.run_dashboard", run_name="__main__") assert exc_info.value.code == 0 diff --git a/tests/test_download_fundamentals.py b/investing/tests/test_download_fundamentals.py similarity index 95% rename from tests/test_download_fundamentals.py rename to investing/tests/test_download_fundamentals.py index 6c75b52..ee0690e 100644 --- a/tests/test_download_fundamentals.py +++ b/investing/tests/test_download_fundamentals.py @@ -2,7 +2,7 @@ Covers universe loading, raw-zone path layout, and skip/force cache semantics. Network integration tests are intentionally excluded from PR CI; see -``docs/mvp/guides/download-fundamentals.md``. +``investing/docs/guides/download-fundamentals.md``. """ from __future__ import annotations @@ -12,8 +12,8 @@ import pytest -from smartwealthai.download_fundamentals import should_skip -from smartwealthai.lake_paths import ( +from investing.download_fundamentals import should_skip +from investing.lake_paths import ( artifact_paths, companyfacts_path, curated_fundamentals_path, @@ -23,7 +23,7 @@ is_valid_fiscal_period, pad_cik, ) -from smartwealthai.universe import load_universe, resolve_universe_file +from investing.universe import load_universe, resolve_universe_file def test_pad_cik_zero_fills_to_ten_digits() -> None: diff --git a/tests/test_download_prices.py b/investing/tests/test_download_prices.py similarity index 97% rename from tests/test_download_prices.py rename to investing/tests/test_download_prices.py index c875d3f..46c751e 100644 --- a/tests/test_download_prices.py +++ b/investing/tests/test_download_prices.py @@ -12,8 +12,8 @@ import pandas as pd import pytest -from smartwealthai.download_prices import cli_run -from smartwealthai.lake_paths import ( +from investing.download_prices import cli_run +from investing.lake_paths import ( curated_prices_path, curated_prices_snapshot_path, curated_universe_path, @@ -22,7 +22,7 @@ yfinance_errors_path, yfinance_raw_path, ) -from smartwealthai.price_ingest import ( +from investing.price_ingest import ( CURATED_PRICE_COLUMNS, build_price_rows, load_raw_shareprices, @@ -32,9 +32,9 @@ should_skip_artifact, write_curated_prices_snapshot, ) -from smartwealthai.universe_builder import build_universe +from investing.universe_builder import build_universe -FIXTURE_LAKE = Path("tests/fixtures/lake") +FIXTURE_LAKE = Path("investing/tests/fixtures/lake") RUN_DATE = date(2026, 6, 18) SNAPSHOT_DATE = date(2026, 6, 18) PRICE_DATE = date(2026, 6, 17) @@ -294,7 +294,7 @@ def test_main_module_entrypoint(lake: Path) -> None: [ sys.executable, "-m", - "smartwealthai.download_prices", + "investing.download_prices", "--data-dir", str(lake), "--run-date", diff --git a/tests/test_download_simfin.py b/investing/tests/test_download_simfin.py similarity index 96% rename from tests/test_download_simfin.py rename to investing/tests/test_download_simfin.py index 3039b32..291e7b7 100644 --- a/tests/test_download_simfin.py +++ b/investing/tests/test_download_simfin.py @@ -15,7 +15,7 @@ import pytest -from smartwealthai.download_simfin import ( +from investing.download_simfin import ( DEMO_DATASETS, DatasetResult, cli_run, @@ -23,12 +23,12 @@ run_download, should_skip_dataset, ) -from smartwealthai.lake_paths import ( +from investing.lake_paths import ( simfin_bulk_path, simfin_errors_path, simfin_source_filename, ) -from smartwealthai.simfin_client import ( +from investing.simfin_client import ( configure_simfin, dataset_cache_path, fetch_dataset_csv, @@ -237,7 +237,7 @@ def test_configure_simfin_sets_api_key_and_cache_dir( monkeypatch: pytest.MonkeyPatch, ) -> None: mock_sf = MagicMock() - monkeypatch.setattr("smartwealthai.simfin_client.sf", mock_sf) + monkeypatch.setattr("investing.simfin_client.sf", mock_sf) cache_dir = tmp_path / "cache" configure_simfin(api_key="test-key", cache_dir=cache_dir) @@ -391,8 +391,8 @@ def mock_fetch( path.write_text("csv\n") return path - monkeypatch.setattr("smartwealthai.download_simfin.configure_simfin", mock_configure) - monkeypatch.setattr("smartwealthai.download_simfin.fetch_dataset_csv", mock_fetch) + monkeypatch.setattr("investing.download_simfin.configure_simfin", mock_configure) + monkeypatch.setattr("investing.download_simfin.fetch_dataset_csv", mock_fetch) exit_code = run_download( data_dir=tmp_path / "lake", @@ -481,7 +481,7 @@ def mock_run_download(**kwargs: object) -> int: seen.update(kwargs) return 0 - monkeypatch.setattr("smartwealthai.download_simfin.run_download", mock_run_download) + monkeypatch.setattr("investing.download_simfin.run_download", mock_run_download) assert cli_run(["--data-dir", str(tmp_path), "--force"]) == 0 assert seen["data_dir"] == tmp_path @@ -498,7 +498,7 @@ def mock_run_download(**kwargs: object) -> int: seen.update(kwargs) return 0 - monkeypatch.setattr("smartwealthai.download_simfin.run_download", mock_run_download) + monkeypatch.setattr("investing.download_simfin.run_download", mock_run_download) assert cli_run(["--as-of-date", "2026-06-18"]) == 0 assert seen["as_of_date"] == date(2026, 6, 18) diff --git a/tests/test_magic_formula_metrics.py b/investing/tests/test_magic_formula_metrics.py similarity index 94% rename from tests/test_magic_formula_metrics.py rename to investing/tests/test_magic_formula_metrics.py index e0f6e32..5245872 100644 --- a/tests/test_magic_formula_metrics.py +++ b/investing/tests/test_magic_formula_metrics.py @@ -11,8 +11,8 @@ import pandas as pd import pytest -from smartwealthai.compute_metrics import cli_run, format_metrics_table -from smartwealthai.magic_formula_metrics import ( +from investing.compute_metrics import cli_run, format_metrics_table +from investing.magic_formula_metrics import ( FORMULA_VERSION, build_metrics, compute_ev, @@ -22,16 +22,16 @@ compute_roc, compute_total_debt, ) -from smartwealthai.normalize_simfin import cli_run as normalize_cli_run -from smartwealthai.pit_fundamentals import MetricsInputError, compute_metrics_for_ticker -from smartwealthai.price_ingest import run_price_ingest -from smartwealthai.universe_builder import build_universe +from investing.normalize_simfin import cli_run as normalize_cli_run +from investing.pit_fundamentals import MetricsInputError, compute_metrics_for_ticker +from investing.price_ingest import run_price_ingest +from investing.universe_builder import build_universe -FIXTURE_LAKE = Path("tests/fixtures/lake") +FIXTURE_LAKE = Path("investing/tests/fixtures/lake") RUN_DATE = date(2026, 6, 18) SNAPSHOT_DATE = date(2026, 6, 18) -# AAPL fixture values (SimFin bulk snapshot in tests/fixtures/lake). +# AAPL fixture values (SimFin bulk snapshot in investing/tests/fixtures/lake). AAPL_EBIT = 123_216_000_000.0 AAPL_CURRENT_ASSETS = 152_987_000_000.0 AAPL_CURRENT_LIABILITIES = 176_000_000_000.0 @@ -360,7 +360,7 @@ def test_cli_exits_nonzero_when_ticker_missing(lake: Path) -> None: def test_compute_metrics_entry_point_registered() -> None: result = subprocess.run( - [sys.executable, "-m", "smartwealthai.compute_metrics", "--help"], + [sys.executable, "-m", "investing.compute_metrics", "--help"], capture_output=True, text=True, check=False, diff --git a/tests/test_magic_formula_ranking.py b/investing/tests/test_magic_formula_ranking.py similarity index 94% rename from tests/test_magic_formula_ranking.py rename to investing/tests/test_magic_formula_ranking.py index 3f0d891..38630b3 100644 --- a/tests/test_magic_formula_ranking.py +++ b/investing/tests/test_magic_formula_ranking.py @@ -13,15 +13,15 @@ import pytest from click.testing import CliRunner -from smartwealthai.lake_paths import ( +from investing.lake_paths import ( curated_cheap_scores_path, curated_combined_ranking_path, curated_portfolio_path, curated_quality_scores_path, curated_universe_path, ) -from smartwealthai.magic_formula_metrics import build_metrics -from smartwealthai.magic_formula_ranking import ( +from investing.magic_formula_metrics import build_metrics +from investing.magic_formula_ranking import ( CombinedRankingRow, RankInput, ScoringResult, @@ -32,23 +32,23 @@ partition_metrics, score_universe, ) -from smartwealthai.normalize_simfin import cli_run as normalize_cli_run -from smartwealthai.pit_fundamentals import ( +from investing.normalize_simfin import cli_run as normalize_cli_run +from investing.pit_fundamentals import ( MetricsInputError, compute_metrics_for_ticker, compute_metrics_for_tickers, ) -from smartwealthai.price_ingest import run_price_ingest -from smartwealthai.score_universe import cli_run as score_cli_run -from smartwealthai.score_universe import format_scoring_summary -from smartwealthai.score_universe import main as score_main -from smartwealthai.universe_builder import build_universe +from investing.price_ingest import run_price_ingest +from investing.score_universe import cli_run as score_cli_run +from investing.score_universe import format_scoring_summary +from investing.score_universe import main as score_main +from investing.universe_builder import build_universe -FIXTURE_LAKE = Path("tests/fixtures/lake") +FIXTURE_LAKE = Path("investing/tests/fixtures/lake") RUN_DATE = date(2026, 6, 18) SNAPSHOT_DATE = date(2026, 6, 18) -# Synthetic fixture tickers (see tests/fixtures/lake/raw/simfin/). +# Synthetic fixture tickers (see investing/tests/fixtures/lake/raw/simfin/). MSFT_ROC = 200_000_000.0 / 120_000_000.0 MSFT_EY = 200_000_000.0 / 1_090_000_000.0 MSFT_MARKET_CAP = 20_000_000.0 * 50.0 @@ -392,7 +392,7 @@ def test_cli_score_universe_succeeds_on_fixture_lake(lake: Path) -> None: def test_score_universe_entry_point_registered() -> None: result = subprocess.run( - [sys.executable, "-m", "smartwealthai.score_universe", "--help"], + [sys.executable, "-m", "investing.score_universe", "--help"], capture_output=True, text=True, check=False, @@ -408,7 +408,7 @@ def _universe_tickers(lake: Path) -> list[str]: def _metrics_result_key(result: object) -> tuple: - from smartwealthai.magic_formula_metrics import MetricsResult + from investing.magic_formula_metrics import MetricsResult assert isinstance(result, MetricsResult) return ( diff --git a/tests/test_mlflow_run_logging.py b/investing/tests/test_mlflow_run_logging.py similarity index 93% rename from tests/test_mlflow_run_logging.py rename to investing/tests/test_mlflow_run_logging.py index c814084..a985099 100644 --- a/tests/test_mlflow_run_logging.py +++ b/investing/tests/test_mlflow_run_logging.py @@ -12,9 +12,9 @@ import pytest from mlflow import MlflowClient -from smartwealthai.magic_formula_metrics import FORMULA_VERSION -from smartwealthai.magic_formula_ranking import score_universe -from smartwealthai.mlflow_run_logging import ( +from investing.magic_formula_metrics import FORMULA_VERSION +from investing.magic_formula_ranking import score_universe +from investing.mlflow_run_logging import ( DEMO_PIPELINE_EXPERIMENT, _prepare_file_store, log_demo_pipeline_run, @@ -22,11 +22,11 @@ resolve_tracking_uri, summarize_metric_distribution, ) -from smartwealthai.normalize_simfin import cli_run as normalize_cli_run -from smartwealthai.price_ingest import run_price_ingest -from smartwealthai.universe_builder import build_universe +from investing.normalize_simfin import cli_run as normalize_cli_run +from investing.price_ingest import run_price_ingest +from investing.universe_builder import build_universe -FIXTURE_LAKE = Path("tests/fixtures/lake") +FIXTURE_LAKE = Path("investing/tests/fixtures/lake") RUN_DATE = date(2026, 6, 18) SNAPSHOT_DATE = date(2026, 6, 18) @@ -180,7 +180,7 @@ def test_resolve_git_sha_returns_unknown_when_git_fails(monkeypatch: pytest.Monk def fail_git(*_args: object, **_kwargs: object) -> subprocess.CompletedProcess[str]: raise subprocess.CalledProcessError(1, "git") - monkeypatch.setattr("smartwealthai.mlflow_run_logging.subprocess.run", fail_git) + monkeypatch.setattr("investing.mlflow_run_logging.subprocess.run", fail_git) assert resolve_git_sha() == "unknown" diff --git a/tests/test_pit_fundamentals.py b/investing/tests/test_pit_fundamentals.py similarity index 98% rename from tests/test_pit_fundamentals.py rename to investing/tests/test_pit_fundamentals.py index 0631aa8..ac46fe6 100644 --- a/tests/test_pit_fundamentals.py +++ b/investing/tests/test_pit_fundamentals.py @@ -9,8 +9,8 @@ import pandas as pd import pytest -from smartwealthai.lake_paths import curated_prices_snapshot_path, curated_universe_path, pad_cik -from smartwealthai.pit_fundamentals import ( +from investing.lake_paths import curated_prices_snapshot_path, curated_universe_path, pad_cik +from investing.pit_fundamentals import ( MetricsInputError, _cik_from_partition_path, _fundamentals_paths_for_ciks, diff --git a/tests/test_run_demo_pipeline.py b/investing/tests/test_run_demo_pipeline.py similarity index 90% rename from tests/test_run_demo_pipeline.py rename to investing/tests/test_run_demo_pipeline.py index 575633a..0895fb2 100644 --- a/tests/test_run_demo_pipeline.py +++ b/investing/tests/test_run_demo_pipeline.py @@ -13,10 +13,10 @@ import pytest from click.testing import CliRunner -from smartwealthai.lake_paths import curated_portfolio_path -from smartwealthai.magic_formula_ranking import ScoringResult -from smartwealthai.price_ingest import PriceIngestRun -from smartwealthai.run_demo_pipeline import ( +from investing.lake_paths import curated_portfolio_path +from investing.magic_formula_ranking import ScoringResult +from investing.price_ingest import PriceIngestRun +from investing.run_demo_pipeline import ( PipelineResult, PipelineStepResult, cli_run, @@ -25,7 +25,7 @@ run_demo_pipeline, ) -FIXTURE_LAKE = Path("tests/fixtures/lake") +FIXTURE_LAKE = Path("investing/tests/fixtures/lake") RUN_DATE = date(2026, 6, 18) SNAPSHOT_DATE = date(2026, 6, 18) @@ -54,7 +54,7 @@ def test_pipeline_result_ok_false_when_scoring_step_failed() -> None: assert not result.ok -@patch("smartwealthai.run_demo_pipeline.run_download", return_value=0) +@patch("investing.run_demo_pipeline.run_download", return_value=0) def test_run_demo_pipeline_runs_download_when_not_skipped( mock_download: object, lake: Path, @@ -71,7 +71,7 @@ def test_run_demo_pipeline_runs_download_when_not_skipped( assert result.steps[0].ok -@patch("smartwealthai.run_demo_pipeline.run_download", return_value=1) +@patch("investing.run_demo_pipeline.run_download", return_value=1) def test_run_demo_pipeline_aborts_on_download_failure( mock_download: object, lake: Path, @@ -89,7 +89,7 @@ def test_run_demo_pipeline_aborts_on_download_failure( @patch( - "smartwealthai.run_demo_pipeline.run_price_ingest", + "investing.run_demo_pipeline.run_price_ingest", side_effect=FileNotFoundError("shareprices missing"), ) def test_run_demo_pipeline_aborts_on_price_ingest_error( @@ -109,7 +109,7 @@ def test_run_demo_pipeline_aborts_on_price_ingest_error( @patch( - "smartwealthai.run_demo_pipeline.run_price_ingest", + "investing.run_demo_pipeline.run_price_ingest", return_value=PriceIngestRun(included=0, run_skipped=False), ) def test_run_demo_pipeline_aborts_when_no_prices( @@ -208,7 +208,7 @@ def test_run_demo_pipeline_scores_universe_and_writes_portfolio(lake: Path) -> N assert result.scoring.portfolio_count > 0 -@patch("smartwealthai.run_demo_pipeline.run_download", return_value=1) +@patch("investing.run_demo_pipeline.run_download", return_value=1) def test_cli_run_exits_nonzero_on_pipeline_failure( mock_download: object, lake: Path, @@ -230,7 +230,7 @@ def test_cli_run_exits_nonzero_on_pipeline_failure( def test_cli_main_handles_click_exception(lake: Path) -> None: runner = CliRunner() with patch( - "smartwealthai.run_demo_pipeline.run_demo_pipeline", + "investing.run_demo_pipeline.run_demo_pipeline", side_effect=click.ClickException("pipeline scope error"), ): result = runner.invoke( @@ -266,7 +266,7 @@ def test_cli_run_demo_pipeline_with_fixture_lake(lake: Path) -> None: def test_run_demo_pipeline_module_main_help() -> None: result = subprocess.run( - [sys.executable, "-m", "smartwealthai.run_demo_pipeline", "--help"], + [sys.executable, "-m", "investing.run_demo_pipeline", "--help"], capture_output=True, text=True, check=False, diff --git a/tests/test_simfin_industry_exclusions.py b/investing/tests/test_simfin_industry_exclusions.py similarity index 95% rename from tests/test_simfin_industry_exclusions.py rename to investing/tests/test_simfin_industry_exclusions.py index dacd5f3..4f259aa 100644 --- a/tests/test_simfin_industry_exclusions.py +++ b/investing/tests/test_simfin_industry_exclusions.py @@ -6,8 +6,8 @@ import pandas as pd -from smartwealthai.generate_simfin_industry_exclusions import cli_run -from smartwealthai.simfin_industry_exclusions import ( +from investing.generate_simfin_industry_exclusions import cli_run +from investing.simfin_industry_exclusions import ( EXCLUSION_COLUMNS, build_exclusions, load_exclusions, diff --git a/tests/test_simfin_normalizer.py b/investing/tests/test_simfin_normalizer.py similarity index 98% rename from tests/test_simfin_normalizer.py rename to investing/tests/test_simfin_normalizer.py index 6098900..17cf83f 100644 --- a/tests/test_simfin_normalizer.py +++ b/investing/tests/test_simfin_normalizer.py @@ -11,19 +11,19 @@ import pandas as pd import pytest -from smartwealthai.fixture_lake import FIXTURES_DIR -from smartwealthai.lake_paths import ( +from investing.fixture_lake import FIXTURES_DIR +from investing.lake_paths import ( curated_fundamentals_path, curated_issues_path, fiscal_period_label, simfin_bulk_path, ) -from smartwealthai.normalize_simfin import ( +from investing.normalize_simfin import ( cli_run, load_universe_tickers, resolve_show_progress, ) -from smartwealthai.simfin_normalizer import ( +from investing.simfin_normalizer import ( DEFAULT_MAPPING_PATH, _index_balance_by_ticker, _latest_balance_row, @@ -34,7 +34,7 @@ load_simfin_mapping, normalize_simfin, ) -from smartwealthai.universe_builder import build_universe +from investing.universe_builder import build_universe SIMFIN_FIXTURE_DATE = date(2026, 6, 18) @@ -169,7 +169,7 @@ def test_work_tickers_logs_skipped_scoped_tickers( ) -> None: income = _read_simfin_csv(_raw_paths(lake_with_simfin, SIMFIN_FIXTURE_DATE)["income"]) - with caplog.at_level(logging.INFO, logger="smartwealthai.simfin_normalizer"): + with caplog.at_level(logging.INFO, logger="investing.simfin_normalizer"): _work_tickers(income, ticker_col="Ticker", tickers={"AAPL", "GHOST", "MISSING"}) assert "scoped ticker(s) have no income rows" in caplog.text diff --git a/tests/test_universe_builder.py b/investing/tests/test_universe_builder.py similarity index 96% rename from tests/test_universe_builder.py rename to investing/tests/test_universe_builder.py index cfdb2c4..5f1dc44 100644 --- a/tests/test_universe_builder.py +++ b/investing/tests/test_universe_builder.py @@ -9,11 +9,11 @@ import pandas as pd import pytest -from smartwealthai.lake_paths import simfin_bulk_path -from smartwealthai.simfin_industry_exclusions import REFERENCE_PATH -from smartwealthai.universe_builder import build_universe +from investing.lake_paths import simfin_bulk_path +from investing.simfin_industry_exclusions import REFERENCE_PATH +from investing.universe_builder import build_universe -FIXTURE_LAKE = Path("tests/fixtures/lake") +FIXTURE_LAKE = Path("investing/tests/fixtures/lake") SNAPSHOT_DATE = date(2026, 6, 18) RUN_DATE = date(2026, 6, 18) BANK_INDUSTRY_ID = 104_002 @@ -192,7 +192,7 @@ def test_same_run_date_produces_identical_universe(lake: Path) -> None: def test_cli_writes_universe_under_data_dir(lake: Path) -> None: - from smartwealthai.build_universe import cli_run + from investing.build_universe import cli_run assert ( cli_run( @@ -215,7 +215,7 @@ def test_cli_writes_universe_under_data_dir(lake: Path) -> None: def test_cli_defaults_snapshot_date_to_run_date(lake: Path) -> None: - from smartwealthai.build_universe import cli_run + from investing.build_universe import cli_run assert cli_run(["--data-dir", str(lake), "--run-date", RUN_DATE.isoformat()]) == 0 @@ -228,7 +228,7 @@ def test_build_universe_module_main(lake: Path) -> None: [ sys.executable, "-m", - "smartwealthai.build_universe", + "investing.build_universe", "--data-dir", str(lake), "--run-date", diff --git a/platform/docs/ephemeral-css-serve-demo.md b/platform/docs/ephemeral-css-serve-demo.md index 1cf3b65..4c867e3 100644 --- a/platform/docs/ephemeral-css-serve-demo.md +++ b/platform/docs/ephemeral-css-serve-demo.md @@ -39,10 +39,10 @@ Example (after up prints `http://A.B.C.D:8000`): curl -s http://A.B.C.D:8000/health curl -s http://A.B.C.D:8000/score \ -H 'content-type: application/json' \ - -d @tests/credit/fixtures/serving/score_request.json + -d @credit/tests/fixtures/serving/score_request.json curl -s http://A.B.C.D:8000/drivers \ -H 'content-type: application/json' \ - -d @tests/credit/fixtures/serving/drivers_request.json + -d @credit/tests/fixtures/serving/drivers_request.json ``` ## Down diff --git a/platform/scripts/demo-up.sh b/platform/scripts/demo-up.sh index 0f91381..2d7d013 100755 --- a/platform/scripts/demo-up.sh +++ b/platform/scripts/demo-up.sh @@ -110,8 +110,8 @@ echo echo "Ephemeral demo URL (not production): $BASE" echo "Try:" echo " curl -s $BASE/health" -echo " curl -s $BASE/score -H 'content-type: application/json' -d @$ROOT/tests/credit/fixtures/serving/score_request.json" -echo " curl -s $BASE/drivers -H 'content-type: application/json' -d @$ROOT/tests/credit/fixtures/serving/drivers_request.json" +echo " curl -s $BASE/score -H 'content-type: application/json' -d @$ROOT/credit/tests/fixtures/serving/score_request.json" +echo " curl -s $BASE/drivers -H 'content-type: application/json' -d @$ROOT/credit/tests/fixtures/serving/drivers_request.json" echo echo "Tear down when done: AWS_PROFILE=$AWS_PROFILE AWS_REGION=$AWS_REGION ./platform/scripts/demo-down.sh" echo "$BASE" >"$TF_DIR/.last_demo_url" diff --git a/pyproject.toml b/pyproject.toml index 19800ef..3d79c6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] package-mode = true packages = [ - { include = "smartwealthai", from = "src" }, + { include = "investing", from = "investing/src" }, { include = "credit", from = "credit/src" }, ] @@ -33,16 +33,16 @@ dependencies = [ ] [project.scripts] -download-fundamentals = "smartwealthai.download_fundamentals:main" -download-simfin = "smartwealthai.download_simfin:main" -normalize-simfin = "smartwealthai.normalize_simfin:main" -build-universe = "smartwealthai.build_universe:main" -download-prices = "smartwealthai.download_prices:main" -compute-metrics = "smartwealthai.compute_metrics:main" -run-demo-pipeline = "smartwealthai.run_demo_pipeline:main" -score-universe = "smartwealthai.score_universe:main" -run-dashboard = "smartwealthai.run_dashboard:main" -generate-simfin-industry-exclusions = "smartwealthai.generate_simfin_industry_exclusions:main" +download-fundamentals = "investing.download_fundamentals:main" +download-simfin = "investing.download_simfin:main" +normalize-simfin = "investing.normalize_simfin:main" +build-universe = "investing.build_universe:main" +download-prices = "investing.download_prices:main" +compute-metrics = "investing.compute_metrics:main" +run-demo-pipeline = "investing.run_demo_pipeline:main" +score-universe = "investing.score_universe:main" +run-dashboard = "investing.run_dashboard:main" +generate-simfin-industry-exclusions = "investing.generate_simfin_industry_exclusions:main" credit-css = "credit.cli:main" credit-css-serve = "credit.api:main" @@ -58,8 +58,8 @@ dev = [ ] [tool.pytest.ini_options] -testpaths = ["tests"] -pythonpath = ["src", "credit/src"] +testpaths = ["investing/tests", "credit/tests"] +pythonpath = ["investing/src", "credit/src"] markers = [ "integration: requires network or AWS (excluded from PR CI)", ] @@ -67,19 +67,19 @@ markers = [ [tool.ruff] target-version = "py311" line-length = 100 -src = ["src", "credit/src", "tests"] +src = ["investing/src", "credit/src", "investing/tests", "credit/tests"] [tool.ruff.lint] select = ["E", "F", "I", "UP", "B"] [tool.coverage.run] -source = ["smartwealthai", "credit"] +source = ["investing", "credit"] branch = true # Frozen SEC ETL spike (phase 2) — excluded from demo coverage metrics. omit = [ - "src/smartwealthai/sec_client.py", - "src/smartwealthai/edgartools_client.py", - "src/smartwealthai/download_fundamentals.py", + "investing/src/investing/sec_client.py", + "investing/src/investing/edgartools_client.py", + "investing/src/investing/download_fundamentals.py", ] [tool.coverage.report]