Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 39 additions & 6 deletions .github/workflows/pr-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ permissions:
contents: read

jobs:
ci:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -26,20 +26,53 @@ jobs:
with:
python-version: "3.11"

- name: Install dependencies
- name: Install dependencies (full workspace)
run: make install

- name: Lint
run: make lint

- name: Test with coverage
# Match [tool.coverage.run]: both packages + branch=true (mixing CLI
# statement-only --cov with pyproject branch data fails combine).
run: uv run pytest --cov=investing --cov=credit --cov-branch --cov-report=term-missing --cov-report=xml
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
domain: [credit, investing]
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.11"

- name: Install ${{ matrix.domain }}-only
run: make install-${{ matrix.domain }}

# Bare `uv run` re-syncs the full workspace (root depends on both members).
# Use --no-sync after --package sync, or uv run --package, for isolation.
- name: Assert cross-domain deps absent
run: |
if [ "${{ matrix.domain }}" = "credit" ]; then
! uv run --no-sync python -c "import yfinance" \
|| { echo "FAIL: yfinance must not install with credit-only sync"; exit 1; }
uv run --no-sync python -c "import credit"
uv run --no-sync credit-css --help
else
! uv run --no-sync python -c "import xgboost" \
|| { echo "FAIL: xgboost must not install with investing-only sync"; exit 1; }
uv run --no-sync python -c "import investing"
uv run --no-sync score-universe --help
fi

- name: Test ${{ matrix.domain }} with coverage
run: make test-${{ matrix.domain }} COV_ARGS='--cov-report=xml'

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.xml
flags: ${{ matrix.domain }}
fail_ci_if_error: false
15 changes: 12 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,20 @@ Write all code, comments, docstrings, documentation, commits, and PR text in **E
## Runtime

- **Python:** 3.11+ (`requires-python = ">=3.11,<4.0"` in root `pyproject.toml`)
- **Dependencies:** uv workspace (`uv sync --frozen --group dev` / `make install`); members `credit` + `investing` ([ADR-0005](docs/adr/0005-poetry-to-uv-workspace.md)). CI and `make install` use `--frozen` so installs match `uv.lock`.
- **Dependencies:** uv workspace; members `credit` + `investing` with **lean per-domain deps** ([#183](https://github.com/JLaborda/SmartWealthAI/issues/183), [ADR-0005](docs/adr/0005-poetry-to-uv-workspace.md)). Shared libs (`pandas`, `click`, `mlflow-skinny`) are listed in both members. CI and Make use `--frozen` so installs match `uv.lock`.

```bash
uv sync --frozen --group dev
uv run pytest
# Full monorepo (both domains)
uv sync --frozen --group dev # or: make install
uv run pytest # or: make test / make lint

# One domain only (credit has no yfinance; investing has no xgboost)
uv sync --frozen --package credit --group dev # or: make install-credit
uv sync --frozen --package investing --group dev # or: make install-investing
make test-credit # pytest credit/tests (keeps --package; bare uv run would re-sync both)
make test-investing # pytest investing/tests
# After a domain-only sync, use `uv run --no-sync …` or `uv run --package <domain> …`
# so a plain `uv run` does not reinstall the other member.
```

### MLflow (demo pipeline runs)
Expand Down
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
.PHONY: install lint test download-fundamentals format platform-tf-check
.PHONY: install install-credit install-investing lint test test-credit test-investing download-fundamentals format platform-tf-check

install:
uv sync --frozen --group dev

install-credit:
uv sync --frozen --package credit --group dev

install-investing:
uv sync --frozen --package investing --group dev

lint:
uv run ruff check .
uv run ruff format --check .

test:
uv run pytest --cov=investing --cov=credit --cov-branch --cov-report=term-missing

# uv run without --package re-syncs the full workspace (root depends on both
# members). Keep --package so credit-only / investing-only envs stay lean.
# Optional: make test-credit COV_ARGS='--cov-report=xml' (CI)
test-credit:
uv run --frozen --package credit --group dev pytest credit/tests --cov=credit --cov-branch --cov-report=term-missing $(COV_ARGS)

test-investing:
uv run --frozen --package investing --group dev pytest investing/tests --cov=investing --cov-branch --cov-report=term-missing $(COV_ARGS)

# Requires SEC_IDENTITY in the environment. See investing/docs/guides/download-fundamentals.md
download-fundamentals:
uv run download-fundamentals --universe dow30
Expand Down
25 changes: 18 additions & 7 deletions credit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,18 +206,29 @@ Optional `--reference` / `--recent` override the default batches; `--stable-thre
## Packaging

One uv workspace at repo root: `credit` is a second installable package
(`credit/src/credit`) beside investing — `uv sync` is enough.
(`credit/src/credit`) beside investing. Full monorepo: `uv sync --frozen --group dev`
(or `make install`). **Credit-only** (no investing deps such as yfinance):

CLI (package scaffold [#143](https://github.com/JLaborda/SmartWealthAI/issues/143); mart stage [#144](https://github.com/JLaborda/SmartWealthAI/issues/144)):
```bash
uv sync --frozen --package credit --group dev # or: make install-credit
make test-credit
# After a domain-only sync, keep --no-sync (or --package credit) so bare uv run
# does not re-sync both workspace members.
```

Docker images still `COPY investing/pyproject.toml` for workspace lock resolution
([#188](https://github.com/JLaborda/SmartWealthAI/issues/188)); that does not install investing packages.

CLI after credit-only sync (package scaffold [#143](https://github.com/JLaborda/SmartWealthAI/issues/143); mart stage [#144](https://github.com/JLaborda/SmartWealthAI/issues/144)):

```bash
uv run credit-css --help
uv run credit-css build-application-mart \
uv run --no-sync credit-css --help
uv run --no-sync credit-css build-application-mart \
--source credit/tests/fixtures/application_source/applications.csv \
--output-dir data/credit/application_mart
uv run credit-css fit --help
uv run credit-css score --help
uv run python -m credit build-application-mart --help
uv run --no-sync credit-css fit --help
uv run --no-sync credit-css score --help
uv run --no-sync python -m credit build-application-mart --help
```

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.
Expand Down
18 changes: 11 additions & 7 deletions credit/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,12 @@ name = "credit"
version = "0.1.0"
description = "Credit Scoring System (CSS) — application default-risk ranking"
requires-python = ">=3.11,<4.0"
# ponytail: full monorepo dep set until #183 lean per-domain split
# Shared with investing (duplicated until a shared/ member is justified): pandas, click, mlflow-skinny.
dependencies = [
"yfinance>=1.2.0,<2.0.0",
"pandas>=3.0.1,<4.0.0",
"edgartools>=5.35.1,<6.0.0",
"pyarrow>=17.0.0,<25.0.0",
"requests>=2.34.0,<3.0.0",
"click>=8.1.0,<9.0.0",
"simfin>=1.0.2,<2.0.0",
"mlflow-skinny==3.14.0",
"streamlit>=1.58.0,<2.0.0",
"pyarrow>=17.0.0,<25.0.0",
"kaggle>=2.2.4,<3.0.0",
"xgboost>=2.0.0,<3.0.0",
"scikit-learn>=1.9.1,<2.0.0",
Expand All @@ -26,6 +21,15 @@ dependencies = [
credit-css = "credit.cli:main"
credit-css-serve = "credit.api:main"

[dependency-groups]
# Required for `uv sync --package credit --group dev` (uv does not inherit root groups).
dev = [
"pytest>=9.0.3,<10.0.0",
"pytest-cov>=6.0.0,<7.0.0",
"ruff>=0.15.14,<0.16.0",
"httpx>=0.28.0,<1.0.0", # FastAPI/Starlette TestClient
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Expand Down
10 changes: 5 additions & 5 deletions docs/adr/0005-poetry-to-uv-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ status: accepted

**Decision:** The monorepo uses a root **uv** workspace whose members are `credit` and `investing`. Install/run via `uv sync` / `uv run` (or `make install` / `make test` / `make lint`). Poetry is no longer the primary path; `poetry.lock` is removed.

**Why:** Domain-first layout (`credit/`, `investing/`) needs per-member `pyproject.toml` without a second installer story. uv workspaces give one lockfile (`uv.lock`), fast sync, and a clear path to lean per-domain installs later ([#183](https://github.com/JLaborda/SmartWealthAI/issues/183)). See [#182](https://github.com/JLaborda/SmartWealthAI/issues/182).
**Why:** Domain-first layout (`credit/`, `investing/`) needs per-member `pyproject.toml` without a second installer story. uv workspaces give one lockfile (`uv.lock`), fast sync, and lean per-domain installs ([#183](https://github.com/JLaborda/SmartWealthAI/issues/183)). See [#182](https://github.com/JLaborda/SmartWealthAI/issues/182).

**Trade-offs:**

| Chosen | Rejected | Reason |
| --- | --- | --- |
| Flat `credit/` + `investing/` workspace members | Gemini-style `domains/*` + `shared/` | Keep domain folders; avoid premature shared package |
| `platform/` not a workspace member | platform as Python member | Infra/scripts only for now |
| Full dep set on both members (temporary) | Lean credit-only / investing-only deps | Owned by #183 |
| Make + CI verification | New `tests/tooling/` contract suite | Occam; existing gates enough |
| Lean per-domain deps; shared libs duplicated in both members | Full dep set on both; or a `shared/` workspace member | #183; extract `shared/` later when shared *code* or a 3rd domain appears |
| Make + CI verification (`--package` matrix) | New `tests/tooling/` contract suite | Occam; existing gates enough |

**Consequences:**

- Operators use `make install`, `uv run <script>`, or `uv run pytest`
- Docker/credit serve images install with `uv sync --frozen`
- Operators use `make install` (full), `make install-credit` / `make install-investing` (one domain), `uv run <script>`, or `uv run pytest`
- Docker/credit serve images install with `uv sync --frozen --package credit` (sibling `investing/pyproject.toml` COPY still required for lock resolution — [#188](https://github.com/JLaborda/SmartWealthAI/issues/188))
- Devcontainer `postCreateCommand` uses uv (optional; local uv is the default path)
- Do not reintroduce Poetry as the primary driver
14 changes: 11 additions & 3 deletions investing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ Ingest SimFin fundamentals → point-in-time curated lake → universe (US minus

## How to run

From the repo root (uv):
From the repo root (uv). Full monorepo: `uv sync --frozen --group dev` (or `make install`).
**Investing-only** (no credit deps such as xgboost):

```bash
uv sync --frozen --package investing --group dev # or: make install-investing
make test-investing
# After a domain-only sync, keep --no-sync (or --package investing) so bare uv run
# does not re-sync both workspace members.
```

```bash
export SIMFIN_API_KEY="<from user secrets>"
uv run run-demo-pipeline --run-date 2026-06-18
uv run run-dashboard --data-dir data --run-date 2026-06-18
uv run --no-sync run-demo-pipeline --run-date 2026-06-18
uv run --no-sync run-dashboard --data-dir data --run-date 2026-06-18
```

### Operator notes
Expand Down
25 changes: 13 additions & 12 deletions investing/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,16 @@ name = "investing"
version = "0.1.0"
description = "Magic Formula / quantitative value screening on US equities"
requires-python = ">=3.11,<4.0"
# ponytail: full monorepo dep set until #183 lean per-domain split
# Shared with credit (duplicated until a shared/ member is justified): pandas, click, mlflow-skinny.
dependencies = [
"yfinance>=1.2.0,<2.0.0",
"pandas>=3.0.1,<4.0.0",
"edgartools>=5.35.1,<6.0.0",
"pyarrow>=17.0.0,<25.0.0",
"requests>=2.34.0,<3.0.0",
"click>=8.1.0,<9.0.0",
"simfin>=1.0.2,<2.0.0",
"mlflow-skinny==3.14.0",
"yfinance>=1.2.0,<2.0.0",
"edgartools>=5.35.1,<6.0.0",
"simfin>=1.0.2,<2.0.0",
"streamlit>=1.58.0,<2.0.0",
"kaggle>=2.2.4,<3.0.0",
"xgboost>=2.0.0,<3.0.0",
"scikit-learn>=1.9.1,<2.0.0",
"joblib>=1.6.0,<2.0.0",
"fastapi>=0.115,<1",
"uvicorn[standard]>=0.30,<1",
"requests>=2.34.0,<3.0.0",
]

[project.scripts]
Expand All @@ -34,6 +27,14 @@ score-universe = "investing.score_universe:main"
run-dashboard = "investing.run_dashboard:main"
generate-simfin-industry-exclusions = "investing.generate_simfin_industry_exclusions:main"

[dependency-groups]
# Required for `uv sync --package investing --group dev` (uv does not inherit root groups).
dev = [
"pytest>=9.0.3,<10.0.0",
"pytest-cov>=6.0.0,<7.0.0",
"ruff>=0.15.14,<0.16.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ credit = { workspace = true }
investing = { workspace = true }

[dependency-groups]
# Full-workspace `make install`. Member packages declare their own `dev` groups for
# `uv sync --package {credit|investing} --group dev` (uv does not inherit root groups).
dev = [
"pytest>=9.0.3,<10.0.0",
"pytest-cov>=6.0.0,<7.0.0",
"ruff>=0.15.14,<0.16.0",
"httpx>=0.28.0,<1.0.0", # credit FastAPI TestClient (also on credit/dev)
]

[tool.pytest.ini_options]
Expand Down
Loading
Loading