A lightweight CRM for managing product experiments end-to-end — from hypothesis to decision.
Decision decay is real. Teams run experiments, get results, then forget why they shipped (or killed) a feature three months later. The learning evaporates. The next team repeats the same test.
Learning should compound, not decay. experiment-crm makes experiments first-class objects with an append-only decision log. Every observation, every status change, every judgement call gets recorded with a timestamp. Three sprints from now you can trace exactly how a decision was made — and what was learned along the way.
No database to set up. No API keys required. Fully deterministic. Just start it and go.
# Clone & install
git clone <repo-url> && cd experiment-crm
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Run the server
uvicorn app.main:app --reload
# In another terminal — run the full demo
bash examples/run_demo.shAPI docs are auto-generated at http://localhost:8000/docs.
{
"title": "Homepage CTA Color",
"hypothesis": "Changing CTA button from blue to green increases signups by 15%",
"primary_metric": "signup_rate",
"baseline": 3.2,
"target": 3.7
}After creating an experiment and appending notes via POST /experiments/{id}/notes:
"decision_log": [
{
"at": "2026-02-22T18:30:00.000Z",
"who": "alice",
"note": "Launched to 10% of users"
},
{
"at": "2026-02-22T18:45:00.000Z",
"who": "bob",
"note": "p-value dropped below 0.05 after 3 days"
}
]GET /experiments/export.csv returns flat CSV — one row per experiment:
id,title,hypothesis,primary_metric,baseline,target,status,confidence,decision,learning,decision_log
a1b2c3...,Homepage CTA Color,Changing CTA...,signup_rate,3.2,3.7,completed,96,ship,Green CTA outperformed...,"[{""at"":""2026-02-22T18:30:00Z"",""who"":""alice"",""note"":""Launched to 10%...""}]"
| Method | Path | Description |
|---|---|---|
| GET | /health |
Health check |
| POST | /experiments |
Create an experiment |
| GET | /experiments |
List all (filter by ?status=) |
| GET | /experiments/{id} |
Get one by ID |
| PATCH | /experiments/{id} |
Update fields |
| POST | /experiments/{id}/notes |
Append a decision-log note |
| GET | /experiments/export.csv |
Download all experiments as CSV |
curl -X POST http://localhost:8000/experiments/<id>/notes \
-H "Content-Type: application/json" \
-d '{"who": "alice", "note": "Launched to 10% of users"}'Each note is timestamped automatically by the server. The who field is optional.
curl http://localhost:8000/experiments/export.csv -o experiments.csvAll columns are flat; decision_log is serialized as a JSON string.
| Field | Type | Notes |
|---|---|---|
id |
UUID | Auto-generated |
title |
string | Required, max 200 chars |
hypothesis |
string | Required |
primary_metric |
string | Required |
baseline |
float | — |
target |
float | Must be > baseline |
status |
draft · running · completed · killed · shipped |
Default: draft |
confidence |
int 0–100 | Default: 0 |
decision |
ship · iterate · kill · inconclusive · null |
Required when status = completed |
learning |
string or null | Optional |
decision_log |
list of {at, who, note} |
Append-only via /notes |
pip install -r requirements.txt # includes pytest + httpx
pytest -vexperiment-crm/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app + routes
│ ├── schemas.py # Pydantic models + validation
│ └── store.py # In-memory storage
├── tests/
│ ├── __init__.py
│ ├── conftest.py # Test fixtures
│ └── test_experiments.py
├── examples/
│ └── run_demo.sh # End-to-end demo script
├── pyproject.toml
├── requirements.txt
└── README.md