Skip to content

Commit 628aa10

Browse files
committed
Repair P0 baseline for benchmark study
1 parent 13ebe6d commit 628aa10

36 files changed

Lines changed: 1847 additions & 360 deletions

‎.github/workflows/ci.yml‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
correctness:
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
python-version: ["3.11", "3.12"]
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: actions/setup-python@v5
19+
with:
20+
python-version: ${{ matrix.python-version }}
21+
cache: pip
22+
- name: Install
23+
run: python -m pip install '.[dev]'
24+
- name: Lint
25+
run: ruff check .
26+
- name: Format check
27+
run: ruff format --check .
28+
- name: Tests
29+
run: pytest -q
30+
- name: Installed import and CLI
31+
working-directory: ${{ runner.temp }}
32+
run: |
33+
python -c "import fdp"
34+
fdp --help
35+
fdp run-all --help
36+
- name: Deterministic offline 1k smoke
37+
working-directory: ${{ runner.temp }}
38+
run: |
39+
fdp run-all --source synthetic --seed 20270916 --rows 1000 --output-dir "$RUNNER_TEMP/fdp-smoke"
40+
python -c "import json, os; p=os.path.join(os.environ['RUNNER_TEMP'], 'fdp-smoke', 'correctness.json'); d=json.load(open(p)); assert d['checksum_equal'] is True; assert d['database_final_row_count'] == 1000; assert d['integrity_check'] == 'ok'"

‎.gitignore‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.venv/
2+
.env
3+
__pycache__/
4+
*.py[cod]
5+
.pytest_cache/
6+
.ruff_cache/
7+
.coverage
8+
htmlcov/
9+
*.egg-info/
10+
build/
11+
dist/
12+
data/
13+
*.db
14+
*.sqlite
15+
*.sqlite3
16+
*-wal
17+
*-shm
18+
.repro/
19+
artifacts/tmp/
20+
benchmark-scratch/
21+
.DS_Store

‎Dockerfile‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
# Minimal Dockerfile (dev/demo)
2-
FROM python:3.11-slim
1+
FROM python:3.11-slim AS runtime
2+
3+
ENV PYTHONDONTWRITEBYTECODE=1 \
4+
PYTHONUNBUFFERED=1
35

46
WORKDIR /app
5-
COPY requirements.txt requirements-dev.txt ./
6-
RUN pip install --no-cache-dir -r requirements.txt -r requirements-dev.txt
7+
COPY pyproject.toml README.md LICENSE ./
8+
COPY src ./src
9+
RUN python -m pip install --no-cache-dir . \
10+
&& groupadd --system fdp \
11+
&& useradd --system --gid fdp --create-home fdp
12+
13+
RUN mkdir -p /work/output && chown -R fdp:fdp /work
14+
USER fdp
15+
WORKDIR /work
716

8-
COPY . .
9-
CMD ["python", "-m", "fdp.cli", "run-all"]
17+
VOLUME ["/work/output"]
18+
ENTRYPOINT ["fdp"]
19+
CMD ["run-all", "--source", "synthetic", "--seed", "20270916", "--rows", "1000", "--output-dir", "/work/output"]

‎Makefile‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
install:
2-
python -m pip install --upgrade pip
3-
pip install -r requirements.txt
1+
.PHONY: install dev lint format-check test smoke ci
42

5-
dev: install
6-
pip install -r requirements-dev.txt
7-
pre-commit install
3+
install:
4+
python -m pip install .
85

9-
format:
10-
black .
11-
isort .
6+
dev:
7+
python -m pip install -e '.[dev]'
128

139
lint:
14-
flake8 .
10+
ruff check .
11+
12+
format-check:
13+
ruff format --check .
1514

1615
test:
1716
pytest -q
1817

19-
run:
20-
python -m fdp.cli run-all
18+
smoke:
19+
fdp run-all --source synthetic --seed 20270916 --rows 1000 --output-dir .repro/smoke
20+
21+
ci: lint format-check test

‎README.md‎

Lines changed: 130 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,147 @@
1-
# Finance Data Pipelines — Pro (with Real Extractor)
1+
# Finance Data Pipelines
22

3-
A professional, interview-ready Python ETL template for **financial data** (stocks/indices/crypto).
4-
Includes **real data extraction via CoinGecko** (no API key), CLI, config, tests, CI, and Docker.
3+
Finance Data Pipelines is a small, correctness-first time-series ETL and SQLite loading
4+
project. The current release provides a deterministic offline generator, strict validation,
5+
an independent logical-state oracle, and **Strategy A: atomic full replacement**.
56

6-
## Quickstart
7+
The repository is a baseline for a later controlled systems study. It does not yet contain
8+
Strategies B/C or performance results, and it is not presented as production-ready,
9+
high-performance, scalable, or research-grade software.
710

8-
### 1) Setup (Windows)
9-
```powershell
10-
py -m venv .venv
11-
.venv\Scripts\activate
12-
pip install -r requirements.txt
13-
pip install -r requirements-dev.txt
14-
copy .env.sample .env
15-
python -m fdp.cli run-all
11+
## Requirements
12+
13+
- Python 3.11 or newer
14+
- SQLite supplied by Python
15+
- No API key or network data source
16+
17+
## Installation
18+
19+
```bash
20+
python3 -m venv .venv
21+
source .venv/bin/activate
22+
python -m pip install --upgrade pip
23+
python -m pip install .
24+
fdp --help
1625
```
1726

18-
### 1) Setup (macOS/Linux)
27+
For development:
28+
29+
```bash
30+
python -m pip install -e '.[dev]'
31+
```
32+
33+
`pyproject.toml` is the authoritative dependency definition. `requirements*.txt` are
34+
compatibility wrappers only.
35+
36+
## Deterministic offline quickstart
37+
38+
Choose the output directory explicitly. All generated Parquet, JSON, SQLite, WAL, and SHM
39+
files remain under that directory unless `--db-path` explicitly selects another location.
40+
1941
```bash
20-
python3 -m venv .venv && source .venv/bin/activate
21-
pip install -r requirements.txt -r requirements-dev.txt
22-
cp .env.sample .env
23-
python -m fdp.cli run-all
42+
fdp run-all \
43+
--source synthetic \
44+
--seed 20270916 \
45+
--rows 1000 \
46+
--output-dir /tmp/fdp-run
2447
```
2548

26-
### 2) Switch data source
27-
Edit `config.yaml`:
28-
```yaml
29-
source: coingecko # options: coingecko | synthetic
30-
coingecko:
31-
coin_id: bitcoin
32-
vs_currency: usd
33-
days: 30
34-
35-
table:
36-
name: prices
49+
The command performs two identical atomic full replacements so exact-rerun idempotency is
50+
verified. It prints correctness fields as JSON and writes:
51+
52+
```text
53+
/tmp/fdp-run/
54+
raw/prices_raw.parquet
55+
normalized/prices_normalized.parquet
56+
dataset_manifest.json
57+
correctness.json
58+
warehouse.db
3759
```
38-
- `coingecko` uses the public API (no key).
39-
- `synthetic` generates demo data (offline; always works).
4060

41-
### 3) Outputs
42-
- `data/raw/prices_raw.parquet`
43-
- `data/clean/prices_clean.parquet`
44-
- `warehouse.db` (SQLite) → table: `prices`
61+
No throughput, latency, or comparative benchmark conclusion is produced.
62+
63+
## Optional configuration file
64+
65+
CLI values override a YAML file. Only `source`, `seed`, and `rows` are accepted:
4566

46-
## CLI Examples
4767
```bash
48-
python -m fdp.cli extract --source coingecko --coin-id bitcoin --vs usd --days 30
49-
python -m fdp.cli extract --source synthetic --days 60
50-
python -m fdp.cli transform
51-
python -m fdp.cli load --table prices
52-
python -m fdp.cli run-all
68+
fdp run-all --config config.yaml --output-dir /tmp/fdp-run
5369
```
5470

55-
## Project Layout
71+
The package never searches the source checkout for configuration. It does not use `.env`, so
72+
no `.env.example` is required.
73+
74+
## Logical model
75+
76+
The logical key is `(series_id, event_ts_utc)` and exact event identity is
77+
`(series_id, event_ts_utc, revision)`. Timestamps are UTC Unix seconds aligned to one minute.
78+
Prices and optional volume use fixed-point integers with scale `10^-6`.
79+
80+
Duplicate/update rules:
81+
82+
- an exact duplicate event is a safe no-op;
83+
- different payloads for the same key and revision reject the entire snapshot;
84+
- the highest revision is current;
85+
- a lower revision cannot regress the current state;
86+
- an empty replacement is rejected by default.
87+
88+
The SQLite current-state table has a composite primary key, required checks, and an
89+
`event_ts_utc` index. Loads use WAL, `synchronous=FULL`, foreign keys, a 5-second busy timeout,
90+
and one explicit transaction. The staging table is validated against the independent oracle
91+
before publication. A pre-commit failure rolls back to the prior committed table.
92+
93+
## Tests and local CI-equivalent checks
94+
95+
```bash
96+
ruff check .
97+
ruff format --check .
98+
pytest -q
99+
100+
tmp_dir="$(mktemp -d)"
101+
fdp run-all --source synthetic --seed 20270916 --rows 1000 \
102+
--output-dir "$tmp_dir/output"
56103
```
57-
src/fdp/
58-
cli.py # click-based CLI (with flags)
59-
config.py # Pydantic settings + YAML config
60-
extract.py # real extractor (CoinGecko) + synthetic fallback
61-
transform.py # cleaning + returns
62-
load.py # SQLAlchemy load into SQLite
63-
utils/
64-
io.py
65-
logging.py
66-
tests/
67-
test_flow.py
68-
test_extract_synthetic.py
69-
test_extract_coingecko_stub.py
70-
.github/workflows/python-ci.yml
71-
.pre-commit-config.yaml
72-
pyproject.toml
73-
requirements.txt
74-
requirements-dev.txt
75-
Makefile
76-
Dockerfile
77-
.env.sample
78-
config.yaml
79-
LICENSE
80-
README.md
81-
docs/DATA_SOURCES.md
104+
105+
Tests use temporary directories and make no live network calls.
106+
107+
## Docker
108+
109+
The image installs the package, runs as a non-root user, and defaults to the deterministic
110+
offline 1,000-row flow:
111+
112+
```bash
113+
docker build -t finance-data-pipelines:phase3d .
114+
docker run --rm -v "$PWD/docker-output:/work/output" \
115+
finance-data-pipelines:phase3d
82116
```
83117

84-
> Tip: Replace the CoinGecko extractor with your preferred exchange/stock API if needed,
85-
and keep credentials in `.env` (never commit secrets).
118+
Docker support is part of the baseline, but a particular release should be called verified
119+
only when build and run commands have actually executed in that release environment.
120+
121+
## Current maturity and limitations
122+
123+
- Strategy A only; Strategies B/C belong to the next phase.
124+
- Single-process, single-writer SQLite baseline.
125+
- Synthetic correctness fixture only; no market-behavior claims.
126+
- No concurrency or distributed-system evaluation.
127+
- No performance measurements or rankings.
128+
- Deterministic exception injection tests transaction rollback; they are not a claim that
129+
every OS/power-loss mode has been tested.
130+
131+
## Project layout
132+
133+
```text
134+
src/fdp/
135+
cli.py installed Click interface
136+
pipeline.py ordinary Python orchestration
137+
extract.py deterministic synthetic generator
138+
transform.py strict normalization
139+
validation.py loader-side validation and revision resolution
140+
oracle.py independent current-state oracle
141+
load.py atomic SQLite full replacement
142+
encoding.py canonical binary checksum encoding
143+
manifest.py deterministic JSON manifests
144+
parquet_io.py frozen Parquet schema
145+
tests/ isolated correctness and CLI tests
146+
.github/workflows/ci.yml
147+
```

‎config.yaml‎

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
# Choose data source: coingecko | synthetic
2-
source: coingecko
3-
4-
coingecko:
5-
coin_id: bitcoin
6-
vs_currency: usd
7-
days: 30
8-
9-
table:
10-
name: prices
1+
# Optional CLI defaults. Generated paths must still be selected with --output-dir.
2+
source: synthetic
3+
seed: 20270916
4+
rows: 1000

‎docs/DATA_SOURCES.md‎

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
# Data Sources
1+
# Data sources
22

3-
## CoinGecko (default)
4-
- Public API (no key).
5-
- Endpoint used: `/coins/{coin_id}/market_chart?vs_currency={vs}&days={days}`
6-
- Rate limits apply; keep requests modest (e.g., 1–2 per minute).
3+
## Synthetic baseline
74

8-
## Synthetic
9-
- Offline demo data for development & tests.
5+
Phase 3D uses only the deterministic offline generator in `fdp.extract`.
6+
7+
- generator version: recorded in `dataset_manifest.json`;
8+
- seed and exact requested row count: explicit;
9+
- timestamp unit: UTC Unix seconds;
10+
- timestamp granularity: 60 seconds;
11+
- values: fixed-point integers at scale `10^-6`;
12+
- live network dependency: none.
13+
14+
The generated values exist to test data-system correctness. They are not intended to model or
15+
support conclusions about financial markets.
16+
17+
## Future real-data fixture
18+
19+
A fixed, attributable ECB reference-rate snapshot may be added in Phase 3E after its source,
20+
retrieval date, reuse conditions, raw hash, transformation, and normalized hash are recorded.
21+
It is not part of this P0 baseline.

0 commit comments

Comments
 (0)