{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# MKT-015 \u2014 reproducibility notebook\n",
    "\n",
    "Runs top to bottom on the committed CSVs under `data/` with the Python\n",
    "standard library only (no network, no third-party imports). It verifies the\n",
    "freeze snapshot, recomputes the percentile / climatology checks on the\n",
    "committed reduced-resolution copies, and rebuilds the table behind each\n",
    "figure. Headline counts in the paper (3,288-day percentile and median; the\n",
    "868-window currency class) were computed on the full-resolution warehouse\n",
    "series; the checks here reproduce them to within the resolution loss, as\n",
    "stated in section 9 of the paper."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import csv, statistics, json\n",
    "from pathlib import Path\n",
    "\n",
    "DATA = Path('data')\n",
    "\n",
    "def load(name):\n",
    "    with open(DATA / name) as f:\n",
    "        return [(r['date'], float(r['value'])) for r in csv.DictReader(f)]\n",
    "\n",
    "series = {s: load(s + '.csv') for s in [\n",
    "    'br_cds_5y', 'br_ntnb_2029', 'br_ntnb_2035', 'br_ntnb_2045',\n",
    "    'usdbrl', 'br_ipca_yoy', 'br_selic_meta', 'br_pnad_desocupacao']}\n",
    "{k: (len(v), v[0][0], v[-1][0]) for k, v in series.items()}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The freeze snapshot (verifies section 4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cds = series['br_cds_5y']\n",
    "assert cds[-1] == ('2026-08-12', 123.58), cds[-1]\n",
    "curve = {s: series[s][-1] for s in ('br_ntnb_2029', 'br_ntnb_2035', 'br_ntnb_2045')}\n",
    "assert [v for _, v in curve.values()] == [8.04, 7.97, 7.45], curve\n",
    "slope = curve['br_ntnb_2045'][1] - curve['br_ntnb_2029'][1]\n",
    "ipca = series['br_ipca_yoy'][-1]\n",
    "assert ipca == ('2026-07-01', 4.44), ipca\n",
    "selic = series['br_selic_meta'][-1][1]\n",
    "pnad = series['br_pnad_desocupacao'][-1]\n",
    "print('CDS at freeze:', cds[-1])\n",
    "print('real curve:', {k: v for k, v in curve.items()}, '| slope 2045-2029: %.2f pp' % slope)\n",
    "print('IPCA yoy:', ipca, '| Selic:', selic, '| PNAD (stale):', pnad)\n",
    "print('last committed usdbrl close:', series['usdbrl'][-1],\n",
    "      '(the ledger freeze reference is the 2026-08-12 close, 5.1904;',\n",
    "      'the +10% threshold is 5.7094)')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Percentile and median checks (section 4)\n",
    "\n",
    "The paper's counts are on the full daily series: 3,288 closes, median 175.35,\n",
    "5.6 percent of days below the freeze. The committed copy is weekly; the same\n",
    "computation on it lands within the resolution loss."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vals = [v for _, v in cds]\n",
    "below = sum(1 for v in vals if v < 123.58)\n",
    "print('weekly copy: n=%d  median=%.2f  share below freeze=%.1f%%'\n",
    "      % (len(vals), statistics.median(vals), 100 * below / len(vals)))\n",
    "print('full-series counts (warehouse): n=3288  median=175.35  share below=5.6%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## IPCA climatology (sections 5.2 and 7.7)\n",
    "\n",
    "`br_ipca_yoy` is committed at full monthly resolution, so this check is exact:\n",
    "65.8 percent of the 240 reference months from 2006-08 through 2026-07 printed\n",
    "above the 4.5 percent ceiling."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "window = [(d, v) for d, v in series['br_ipca_yoy'] if '2006-08' <= d[:7] <= '2026-07']\n",
    "k = sum(1 for _, v in window if v > 4.5)\n",
    "print('%d of %d months above 4.5%% = %.1f%%' % (k, len(window), 100 * k / len(window)))\n",
    "assert (k, len(window)) == (158, 240), (k, len(window))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The seven calls against their base rates (Figure 4, Table 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(DATA / 'forecasts.csv') as f:\n",
    "    fc = list(csv.DictReader(f))\n",
    "w = max(len(r['label']) for r in fc)\n",
    "print('question'.ljust(w), 'ATOL   base   class (k/n)')\n",
    "for r in fc:\n",
    "    print(r['label'].ljust(w),\n",
    "          '%5.1f%% %5.1f%%  %s (%s/%s)' % (\n",
    "              100 * float(r['p_atol']), 100 * float(r['p_null']),\n",
    "              r['null_class'], r['null_k'], r['null_n']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Figure tables\n",
    "\n",
    "Each figure's underlying table, rebuilt from the committed copies. The\n",
    "rendered figures are the Vega-Lite specs under `figures/`, which read these\n",
    "same CSVs by URL; this cell confirms every spec's data file exists and prints\n",
    "the first and last row the chart draws."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "for spec_path in sorted(Path('figures').glob('*.vega.json')):\n",
    "    spec = json.load(open(spec_path))\n",
    "    url = spec.get('data', {}).get('url')\n",
    "    if url:\n",
    "        rows_ = list(csv.DictReader(open(url)))\n",
    "        print(spec_path.name, '->', url, ':', len(rows_), 'rows |',\n",
    "              dict(rows_[0]), '...', dict(rows_[-1]))\n",
    "    else:\n",
    "        print(spec_path.name, '-> inline/derived data')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}