A personalized lifestyle improvement tool powered by AI. Answer a few questions about your habits — get a tailored Lifestyle Report with a 7/15/30-day action plan and a daily schedule. No accounts, no local memory between runs.
- Overview
- Prerequisites
- Installation
- Running the app
- Configuration & controls
- Walkthrough
- Output files
- Privacy
- Troubleshooting
- Project structure
- Running tests
MetricPath runs a warm, conversational intake powered by the LLM you choose for that run. It collects 19 data points about your sleep, diet, exercise, stress, connection, hydration, sedentary time, goals, and constraints — then:
- Computes derived health metrics (sleep debt, activity score, diet score)
- Builds a personalized daily time-block schedule
- Generates a structured Lifestyle Report with actionable 7/15/30-day plans
- Lets you ask follow-up questions, save a branded PDF locally, or start a fresh run
Everything runs in your terminal. Each run starts fresh locally, and nothing is written to disk unless you explicitly choose to save.
| Requirement | Version |
|---|---|
| Python | 3.12+ |
| LLM access | Anthropic/OpenAI API key, or local Ollama for free mode |
# 1. Clone the repo
git clone https://github.com/kevinm126/metricpath.git
cd metricpath
# 2. Create and activate a Python 3.12+ virtual environment
/opt/homebrew/bin/python3.12 -m venv .venv
source .venv/bin/activate # macOS / Linux
# .venv\Scripts\activate # Windows
# 3. Install MetricPath and its runtime dependencies
python -m pip install -e .
# Runtime deps: rich, anthropic, openai, reportlab (no ML libraries needed at runtime)
# 3b. (Optional) install training-only deps — only needed to re-fit the risk model
# from raw NHANES data. Never required for normal use.
# python -m pip install pandas pyreadstat scikit-learn
# 4. (Optional) preload an LLM provider or free local endpoint
# You don't have to set anything before the first run. MetricPath will
# ask for a provider, prompt for an API key inline if one is missing,
# and offer to pull a free local Ollama model if you pick that route.
# Set values in .env only if you want to skip those prompts on later runs:
cp .env.example .env
# Paid providers (paste at runtime, or save to .env to skip the prompt):
# ANTHROPIC_API_KEY=sk-ant-...
# METRICPATH_LLM_PROVIDER=openai
# OPENAI_API_KEY=sk-...
#
# Free local provider (Ollama):
# METRICPATH_OLLAMA_BASE_URL=http://localhost:11434/v1/
# # install Ollama from https://ollama.com/download
# # then either let MetricPath pull a curated model for you on first run
# # (offers llama3.1:8b, qwen2.5:7b, phi3:14b, or gemma2:9b),
# # or pull one yourself ahead of time, e.g.:
# # ollama pull llama3.1:8bmetricpath # full pipeline (intake, report, follow-up)
metricpath onboard # alias for the full pipeline — the one-time setup
metricpath checkin # the daily loop: seven questions and a Day Score
metricpath reset # wipe ~/.metricpath/ after a typed DELETE confirmmetricpath and metricpath onboard run the full pipeline end-to-end in
your terminal. On each run, MetricPath asks which provider and model to
use before the intake begins.
metricpath checkin is the daily loop. Seven questions, under a minute,
a Day Score on the same 0–1000 scale as the full report, and up to two
research-anchored nudges when a domain drops against your rolling 7-day
average. It opts you into local history on first run with a plain-text
consent prompt and never writes anything without that yes.
During local development, python cli/main.py still works too.
MetricPath is a linear, interactive pipeline with a privacy-first finish. You steer it through a startup provider/model chooser, environment variables, and keyboard input during the session.
| Variable | Required | Default | What it does |
|---|---|---|---|
METRICPATH_LLM_PROVIDER |
no | anthropic |
Prefills the startup chooser with a provider. Supported values: anthropic, openai, ollama. |
ANTHROPIC_API_KEY |
no | — | Auth for Anthropic LLM calls. If unset when you pick Anthropic at startup, the CLI prompts you to paste the key inline for that run. Save it to .env to skip the prompt next run. |
OPENAI_API_KEY |
no | — | Auth for OpenAI LLM calls. Same inline-paste fallback as the Anthropic key. OpenAI API billing is separate from ChatGPT Plus/Pro subscriptions. |
METRICPATH_OLLAMA_BASE_URL |
no | http://localhost:11434/v1/ |
Override the OpenAI-compatible local Ollama endpoint used for free mode. |
METRICPATH_MODEL |
no | provider default | Prefills the startup chooser with a highlighted model choice. Defaults to claude-sonnet-4-6 for Anthropic, gpt-5.4-mini for OpenAI, and llama3.1:8b for Ollama. |
METRICPATH_POPULATION_SCORE |
no | 0 |
Set to 1, true, or yes to print the experimental Ding 2015 population-normed risk score. |
METRICPATH_POPULATION_PERCENTILE |
no | 0 |
Set to 1, true, or yes to print the trained-logistic risk percentile (see Population risk percentile). |
NO_COLOR |
no | unset | Set to any value to disable ANSI color and emoji highlighting. Useful in CI logs or basic terminals. |
ENVIRONMENT |
no | development |
production suppresses any echo of raw intake answers to stdout. |
Deprecated compatibility fallbacks: LIVEBETTER_MODEL and LIVEBETTER_POPULATION_SCORE are still read if the new METRICPATH_* variables are unset. LIVEBETTER_MODEL only applies to the Anthropic provider.
All of the above can live in .env next to .env.example — they are picked up automatically on startup. The CLI still asks you to choose a provider and model on every run; env vars only prefill the chooser.
The intake conversation uses a different system prompt depending on the provider and model you pick. Same prompt across all Anthropic models. Same prompt across all OpenAI models. One prompt per Ollama family — llama (covers llama3.1, llama3.2, llama4), qwen (qwen2.5, qwen3), phi (phi3, phi4), gemma (gemma2, gemma3) — plus a generic Ollama fallback for anything else you pull. Each prompt is researched against that provider or family's published prompt-engineering guide and tuned for how that family follows instructions: Anthropic gets XML-structured sections, OpenAI gets the CTCO pattern, Qwen gets terse direct instructions, Phi gets a compact prompt that fits its smaller context, and so on.
You never edit a file. The CLI loads the right prompt automatically based on the provider and model you chose at startup. Files live under cli/prompts/intake/.
Keyboard input during each phase of the session:
| Phase | Input | Effect |
|---|---|---|
| Startup | ↑ / ↓ + Enter |
choose anthropic, openai, free local ollama, or Exit and come back later |
| Startup (paid provider, no key) | paste an API key | sets the key for this run; the CLI suggests adding it to .env so the prompt doesn't fire next time. Press Enter on an empty prompt to go back to the provider chooser. |
| Startup | ↑ / ↓ + Enter |
choose a model for the selected provider from the menu; for Ollama, installed models are listed when available |
| Startup | pick Type a custom model name |
switches from the menu to free-form model input |
| Startup (Ollama, no models) | recovery menu | opens a curated picker (llama3.1:8b, qwen2.5:7b, phi3:14b, gemma2:9b — all free, all local) and pulls the one you choose. You can also type a custom model name, print the manual setup steps, or pick a different provider. |
| Startup (Ollama, model picker) | pick Pull a curated model |
opens the same curated picker without leaving the run, pulls your choice, and returns you to the model menu with the freshly pulled model added to the list. |
| Startup (Ollama unreachable) | Y at setup guidance prompt |
prints the exact download / ollama serve / ollama pull ... steps and returns you to the chooser |
| Any handled failure | retry menu / confirm prompt | shows a Rich error panel with what failed, short details, and the safe next action |
| Intake | any text | answer the current question |
| Intake | invalid numeric (e.g. abc for sleep hours) |
re-prompts in place with an error hint — does not restart the conversation |
| Intake | Ctrl-C |
aborts; no report is generated |
| Report failure | Y at retry prompt |
retries report generation without repeating intake |
| Post-report menu | ↑ / ↓ + Enter |
choose follow-up, local PDF save, start fresh, or finish |
| Save action | filename prompt | writes reports/<filename>.pdf only when explicitly chosen |
| Follow-up action | select it from the menu | enter follow-up Q&A primed with your full report |
| Follow-up chat | any text | sends your question with full context, then returns to the action menu when you exit |
| Follow-up failure | choose Retry this question, Skip this question, or Exit follow-up |
recovers cleanly without losing the session |
| Follow-up chat | done, quit, exit, q |
exits cleanly |
| Follow-up chat | empty line | ignored; reprompts |
| Launch | metricpath --population-score |
include the experimental Ding 2015 population-normed risk score |
| Launch | metricpath --population-percentile |
include the trained-logistic risk percentile (NHANES model) |
| Launch | metricpath --help |
print CLI help and exit |
| Any phase | Ctrl-C |
aborts |
| Code | Meaning |
|---|---|
0 |
success — report generated (and optional steps completed or skipped) |
1 |
missing dependency or unrecoverable runtime error |
130 |
interrupted with Ctrl-C |
A richer non-interactive CLI is on the roadmap (separate intake / report / export / ask subcommands with --session, --json, -o, etc.). The scaffolding lives in cli/errors.py for exit codes and the MP#### error catalog, but those subcommands are not wired into main.py yet — the current build intentionally ships one happy path end to end.
When you run metricpath, you're greeted with a welcome banner, choose a provider and model from the startup menus, and then the intake conversation begins.
MetricPath asks one question at a time — never a form. The conversation covers sleep, diet, exercise, stress level, goals, constraints, and schedule. Numeric answers (sleep hours, stress rating, exercise days) are validated before the LLM moves on.
The intake covers these 19 topics in a natural order:
| # | Topic | Example answer | Notes |
|---|---|---|---|
| 1 | Average nightly sleep hours | 6.5 |
validated: 0–24 |
| 2 | Sleep schedule consistency | Inconsistent — weekdays 7am, weekends 10am |
free text |
| 3 | Typical meals | Oatmeal, grab lunch out, home dinner |
free text |
| 4 | Dietary restrictions | Gluten-free (celiac) |
free text |
| 5 | Exercise days per week | 4 |
validated: 0–7 |
| 6 | Exercise type | Running |
free text |
| 7 | Average exercise duration | 30 min |
free text |
| 8 | Stress level (1–10) | 6 |
validated: 1–10 integer |
| 9 | Lifestyle goals | Sleep better, lose weight |
free text |
| 10 | Key constraints | Busy schedule, tight budget |
free text |
| 11 | Pre-existing conditions | (optional — skip if none) |
free text |
| 12 | Wake time | 7:00 AM |
free text |
| 13 | Work / school hours | 9–5 |
free text |
| 14 | Connection check-in (UCLA-3) | Hardly ever / some of the time / often |
3 Likert items, scored 3–9 |
| 15 | Hydration | 7 cups/day |
validated: 0–40 cups |
| 16 | Alcohol intake | 3 drinks/week |
validated: 0–100 drinks |
| 17 | Daily sitting hours | 8 |
validated: 0–24; used for sedentary-risk score |
| 18 | Age in years | 34 or skip |
optional; risk model defaults to 45 when absent |
| 19 | Biological sex | female or prefer not to say |
optional; risk model defaults to female reference when absent |
Once the intake is complete, MetricPath computes your health metrics and generates your personalized report in a single LLM call. This takes about 10–15 seconds.
Computed metrics injected into the report prompt:
| Metric | Formula |
|---|---|
| Sleep debt | max(0, (8 − avg_sleep_hrs) × 7) hours/week |
| Activity score | (exercise_days / 7) × 10 out of 10 |
| Diet score | Keyword heuristic on meal description, 0–10 |
| Stress level | Echoed from intake, 1–10 |
The report is printed to the terminal first and kept ephemeral by default. If you want a local artifact, choose Save report locally from the post-report action menu and MetricPath writes a branded PDF to reports/. Every recommendation is tied to your specific answers — not generic advice.
Report sections:
| Section | What's in it |
|---|---|
| Dashboard | Snapshot of your metrics — sleep debt, activity, diet, stress |
| Sleep Adjustments | 3–5 concrete changes with "why this works for you" explanations |
| Diet Adjustments | 3–5 adjustments based on your actual meals and restrictions |
| Exercise Adjustments | 3–5 suggestions factoring in your constraints and current routine |
| Stress & Recovery | 2–3 targeted recommendations tied to your stress level and goals |
| 7-Day Plan | Day-by-day checklist with progression built in |
| 15-Day Milestones | Measurable shifts to look for in weeks 2–3 |
| 30-Day Targets | Outcome-oriented goals tied to what you said you wanted |
| Closing Note | A personal, slightly playful send-off referencing something real you said |
A personalized time-block schedule is built from your wake time, work hours, and exercise habits, and injected into the report.
The schedule auto-places:
- Morning routine (wake → work)
- Exercise slot (pre-work if time allows, post-work otherwise — never double-booked)
- Lunch midpoint
- Dinner
- Wind-down routine (45 min before target bedtime)
- 8-hour sleep target block
After the report, you're dropped into a small action menu:
Save report locally — writes a branded PDF to reports/ using the filename you choose.
Follow-up Q&A — the LLM is primed with your full intake, metrics, schedule, and report. Ask anything:
- "Why did you recommend locking in a wake time?"
- "Can you swap the mid-week run for something lower impact?"
- "What should I focus on first given my tight schedule?"
Start fresh — jumps back to provider/model selection and rebuilds the whole session from empty state.
Type done or quit to leave follow-up and return to the action menu.
Pass --population-percentile (or set METRICPATH_POPULATION_PERCENTILE=1) to print an additional panel after the score:
╭─ Population percentile (logistic) ─────────────────────────╮
│ Population Risk Percentile: 41/100 │
│ (trained logistic, lower = lower predicted risk) │
│ log-odds: -3.459 p: 0.030 │
│ Top lifestyle contributors: │
│ · low_activity +0.485 │
│ · high_sitting +0.266 │
│ · short_sleep +0.051 │
╰─────────────────────────────────────────────────────────────╯
This is a logistic regression model trained on NHANES 2007–2008 microdata (n=6,219 adults) linked to the NCHS Public-Use Linked Mortality File through 2019. Test-fold AUC: 0.89. Features: age, sex (adjustment covariates), plus 8 lifestyle binary indicators. Coefficients live in cli/data/risk_logit_coefs.json alongside a percentile reference distribution computed over the same cohort.
The percentile is relative risk, not absolute probability — it shows where your lifestyle profile falls in the population distribution. Lower = lower predicted risk. It is never framed as a mortality estimate.
Re-training the model yourself (optional, for reproducibility):
# Install training-only dependencies (not needed for runtime):
.venv/bin/python -m pip install pandas pyreadstat scikit-learn
# Download NHANES 2007-2008 public microdata (free, no account needed):
mkdir -p ~/nhanes_2007_2008
cd ~/nhanes_2007_2008
for suffix in DEMO BMX SLQ PAQ DR1TOT ALQ SMQ DPQ; do
curl -O "https://wwwn.cdc.gov/Nchs/Data/Nhanes/Public/2007/DataFiles/${suffix}_E.xpt"
done
for f in *.xpt; do mv "$f" "$(echo "$f" | tr '[:lower:]' '[:upper:]')"; done
# Download the NCHS Linked Mortality File (2019 public-use release):
curl -o ~/NHANES_2007_2008_MORT_2019_PUBLIC.dat \
"https://ftp.cdc.gov/pub/Health_Statistics/NCHS/datalinkage/linked_mortality/NHANES_2007_2008_MORT_2019_PUBLIC.dat"
# Train (overwrites cli/data/*.json):
.venv/bin/python scripts/train_risk_model.py \
--nhanes-dir ~/nhanes_2007_2008 \
--lmf-file ~/NHANES_2007_2008_MORT_2019_PUBLIC.dat \
--cycle-suffix E \
--seed 42 \
--forceThe script validates data, fits the model, and writes two JSON files. Runtime never imports pandas, pyreadstat, or scikit-learn — those are training-only.
| File | Location | Description |
|---|---|---|
| Lifestyle Report | reports/<filename>.pdf |
Branded PDF report, written only when you choose Save report locally |
The reports/ directory is created automatically on first write. It is gitignored — local exports stay on your machine.
MetricPath is privacy-first. See PRIVACY.md for the full statement. Short version:
- No account required. Each run starts fresh.
- Opt-in persistence. The full pipeline never writes to disk unless
you ask. The daily check-in prompts once for consent before it starts
saving a local history file at
~/.metricpath/history.jsonl. - One-command wipe.
metricpath resetremoves~/.metricpath/after a typedDELETEconfirmation. - Ephemeral mode.
METRICPATH_EPHEMERAL=1disables every disk write, including PDF report exports. - No telemetry. Zero analytics, zero crash reporting, zero ping-home.
- PII filter on remote LLM calls. A deterministic filter redacts obvious email addresses and phone numbers from prompts sent to Anthropic or OpenAI. Ollama is local, so the filter does not run there.
- External calls depend on the provider you choose for that run: Anthropic API, OpenAI API, or your local Ollama endpoint. Anthropic/OpenAI receive prompt data during the run (after the PII filter); Ollama keeps prompts local.
| Symptom | Likely cause | Fix |
|---|---|---|
Environment Setup Problem panel at startup |
wrong Python version, stale env, or missing dependencies | activate a Python 3.12+ env, then run python -m pip install -e . |
ERROR: The 'rich' library is required. at startup |
rich not installed in the active env |
python -m pip install -e . |
ModuleNotFoundError: No module named 'anthropic' |
SDK missing | python -m pip install -e . |
AuthenticationError on the first intake turn |
the chosen paid provider key is unset, typo'd, or revoked | re-check .env, or choose a different provider on startup |
Ollama is not reachable at startup |
Ollama is not installed or not running locally | install from ollama.com/download, then start it or run ollama serve |
The Ollama model '…' is not installed locally. |
the chosen local model has not been pulled yet | run ollama pull <model> and start metricpath again |
LLM Request Problem panel |
rate limit, transient network issue, or local model hiccup | use the retry prompt first; if it keeps failing, switch models or check provider status |
Model Output Problem panel |
the model returned malformed intake/report output | retry the step; if it repeats, switch to a more reliable model |
| Hangs at "Generating your Lifestyle Report…" for > 60s | slow network, local model startup, or rate limit | Ctrl-C and retry; check your chosen provider and model status |
| Colors / emojis render as garbled escape codes | terminal doesn't advertise ANSI / UTF-8 | export NO_COLOR=1 and use a modern terminal (iTerm2, Windows Terminal, etc.) |
| Want to regenerate the CLI screenshots for the thesis | — | python ../thesis/cli_artifacts/make_screenshots.py (then npm run screenshots to render PNGs) |
metricpath/
├── cli/
│ ├── main.py ← Entry point — run this
│ ├── intake.py ← Multi-turn LLM intake (19 topics, validated)
│ ├── risk_model.py ← Logistic risk model runtime (pure Python, no ML deps)
│ ├── llm_client.py ← LLM provider wrapper (Anthropic / OpenAI / Ollama)
│ ├── metrics.py ← Derived health score computation
│ ├── population_score.py ← Ding 2015 population-normed risk score
│ ├── report_generator.py ← LLM report orchestration
│ ├── schedule_builder.py ← Daily time-block schedule
│ ├── data/
│ │ ├── risk_logit_coefs.json ← Trained model coefficients (NHANES 2007-2008)
│ │ └── risk_score_distribution.json ← Population percentile reference distribution
│ └── prompts/
│ ├── system.txt ← Base persona prompt
│ ├── intake/ ← Per-provider/family intake prompts (19 topics each)
│ │ ├── anthropic.txt
│ │ ├── openai.txt
│ │ └── ollama/ ← llama.txt, qwen.txt, phi.txt, gemma.txt, _default.txt
│ ├── report.txt ← Report generation prompt
│ └── followup.txt ← Follow-up Q&A prompt
├── scripts/
│ └── train_risk_model.py ← Offline trainer (run once; requires pandas/pyreadstat/sklearn)
├── tests/ ← Automated test suite (329 tests, no API key needed)
├── reports/ ← Opt-in local PDF exports (gitignored)
├── pyproject.toml ← Package metadata + `metricpath` command
├── playwright.config.ts ← Harness for rendering thesis screenshots (SVG→PNG)
├── package.json ← Pins @playwright/test for the screenshot harness
├── .env.example
└── AGENT.md ← Full architecture guide for contributors
source .venv/bin/activate
python -m pip install -e ".[dev]"
pytest tests/ -vAll tests run without an API key — the LLM client is injectable and the suite uses fake callables. The risk model tests load the shipped cli/data/*.json directly.
329 passed in 0.75s