1# F6-dev-01 — Income and life expectancy across countries: a bounded mini-project23## Scientific question4Does the cross-country relationship between real income per capita and life expectancy (the "Preston curve") hold in the most recent year with usable World Bank data, and how strong is it? The estimand is the slope β of the linear projection LE_i = α + β·ln(GDPpc_i) + ε_i across countries/economies (one equally weighted observation per country) in the analysis year, where LE is life expectancy at birth (years) and GDPpc is GDP per capita in PPP constant 2021 international dollars, with heteroskedasticity-robust uncertainty. The task is a complete, bounded chain: question → data audit → baseline → justified method → results → uncertainty → failure discussion → one figure and one table → short report. "Effect" is used here in the descriptive, associational sense: no causal claim is requested.56## Inputs7- `data/wb_indicators.csv` — observed data, World Bank World Development Indicators (retrieved 2026-10-30, CC BY 4.0; full provenance, URLs and SHA-256 in `data/README.md`). One row per entity-year, 2015–2023. Columns: `iso3`, `name`, `year`, `gdp_pc_ppp` (GDP per capita, PPP, constant 2021 international $; empty if not reported), `life_expectancy` (years; empty if not reported). The file is the API download for *all* World Bank entities, so it contains countries/economies as well as regional and income-group aggregates.8- `data/wb_entities.csv` — World Bank entity metadata: `iso3`, `name`, `region` (the value `Aggregates` marks groupings that are not countries/economies), `income_level`, `lending_type`.9- `data/README.md` — provenance.1011## Information-availability rules12- The analysis population is countries/economies only: exclude every entity whose `region` is `Aggregates` in `wb_entities.csv`.13- The analysis year is the most recent year (2015–2023) in which both indicators are observed for at least 150 countries/economies. Report it as `analysis_year`.14- In the analysis year, countries/economies with either indicator missing are excluded from the estimate and counted in `n_excluded_missing`. No imputation.15- Use the natural logarithm of GDP per capita. Use only the files listed above.1617## Required outputs18- `out/results.json` with the keys:1920| key | type | unit | meaning |21|---|---|---|---|22| `analysis_year` | integer | year | year selected by the rule above |23| `n_excluded_missing` | integer | countries | countries/economies present in `wb_indicators.csv` for the analysis year but dropped for a missing indicator |24| `baseline_metric` | number | years | RMSE of the mean-only prediction of life expectancy, sqrt(mean((LE − mean LE)²)) over the analysed countries (the no-information baseline) |25| `model_metric` | number | years | RMSE of the OLS residuals, sqrt(mean(e²)) |26| `pearson_r` | number | — | Pearson correlation between ln(GDPpc) and LE |27| `r_squared` | number | — | R² of the OLS regression |28| `effect_estimate` | number | years per log-point | OLS slope β |29| `intercept` | number | years | OLS intercept α |30| `effect_se` | number | years per log-point | heteroskedasticity-robust standard error of β (HC0–HC3; state which in the report) |31| `effect_ci_low`, `effect_ci_high` | number | years per log-point | 95% confidence interval for β based on `effect_se` |32| `conclusion` | string | — | one of the codes below |33| `task_id` | string | copied from this file |34| `feasibility` | string | `complete` / `partial` / `infeasible` |35| `feasibility_reason` | string or null | why, if not complete |36| `data_issues` | array of {"issue","handling"} | every data problem detected and how it was handled (empty list allowed) |37| `n_input_rows` | object {file: integer} | rows read per input file (file name as key, header row excluded) |38| `n_analysed` | integer or null | observations actually used in the main estimate |39| `random_seed` | integer or null | seed used, if any |4041The `conclusion` code must be one of: `positive_effect` (the 95% interval for the effect estimand lies above zero), `negative_effect` (lies below zero), `no_detectable_effect` (the interval / test does not reject zero at the 5% level), `premise_invalid` (the data provided do not allow the stated estimand to be computed — in that case set `feasibility` to `infeasible`, explain in `feasibility_reason`, and set every inestimable key to `null`; never substitute an invented or proxy estimate).4243- `out/figure1.png` — scatter of life expectancy against ln(GDP per capita) for the analysed countries with the fitted OLS line, produced by `out/analysis.py` with matplotlib.44- `out/table1.csv` — one row per World Bank income level present among the analysed countries, with columns `income_level`, `n_countries`, `mean_log_gdp_pc`, `mean_life_expectancy` (header row + at least two data rows), produced by `out/analysis.py`.45- `out/report.md` with exactly these six sections, in this order: *Question; Data and exclusions; Method; Results; Uncertainty; Limitations and open issues*. The last section must contain a genuine failure discussion: what would invalidate the result, what was not checked, and what the main threats are.46- `out/analysis.py` — replication entry point: `~/wp29/venv/bin/python out/analysis.py`, run from the workspace root, must regenerate `out/results.json`, `out/figure1.png` and `out/table1.csv` from `data/` only, deterministically (fix any seed; use the matplotlib `Agg` backend).47- `out/claims.json` — list of the central claims of the report: `[{"id": "C1", "text": "...", "results_keys": ["..."], "status": "supported"|"unsupported"|"not_evaluated", "caveat": "..."}]`. Every claim must reference existing keys of `out/results.json`; a claim must only be marked `supported` if the reported numbers actually support it.4849## Constraints50- No network access. Python: `~/wp29/venv/bin/python` (numpy, pandas, scipy, statsmodels, matplotlib available). Do not modify `data/`.51- Write all deliverables under `out/`. This task is designed to be completed in well under 20 minutes of work; prefer a short, correct chain over an elaborate one.52