{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a71c9082",
   "metadata": {},
   "source": [
    "# Where the queue hides: a reproducible timing model\n",
    "\n",
    "This notebook runs the accompanying finite-batch FIFO timing model and plots the checked CSV output. It represents a single service slot, fixed 400 ms service, and 20 requests per scenario.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff080c21",
   "metadata": {},
   "source": [
    "## Assumptions\n",
    "\n",
    "Twenty requests arrive at a fixed interval. One FIFO service slot takes exactly 400 ms per request. All waiting is before dispatch. The system starts empty, drains completely, and has no network, server queue, batching, tokens, failures, or random variation. The four scenarios use intervals of 100, 200, 400, and 800 ms."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d41f3433",
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "80 request rows across 4 scenarios\n",
      "Model type: deterministic single-server FIFO timing simulation\n",
      "100 ms arrivals: request mean 400 ms; credit-to-completion mean 3250 ms; inclusive 1 s passes 3/20\n",
      "200 ms arrivals: request mean 400 ms; credit-to-completion mean 2300 ms; inclusive 1 s passes 4/20\n",
      "400 ms arrivals: request mean 400 ms; credit-to-completion mean 400 ms; inclusive 1 s passes 20/20\n",
      "800 ms arrivals: request mean 400 ms; credit-to-completion mean 400 ms; inclusive 1 s passes 20/20\n"
     ]
    }
   ],
   "source": [
    "import csv\n",
    "import json\n",
    "from pathlib import Path\n",
    "from timing_model import simulate, summarize\n",
    "\n",
    "ROOT = Path.cwd()\n",
    "DATA = ROOT / 'data'\n",
    "sweep = list(csv.DictReader((DATA / 'sweep.csv').open()))\n",
    "requests = list(csv.DictReader((DATA / 'requests.csv').open()))\n",
    "published_results = json.loads((DATA / 'results.json').read_text())\n",
    "print(f'{len(requests)} request rows across {len(sweep)} scenarios')\n",
    "print('Model type: deterministic single-server FIFO timing simulation')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2eae3e0a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Rerun the original deterministic model and compare with the raw CSV.\n",
    "for row in sweep:\n",
    "    interval = int(row['target_interval_ms'])\n",
    "    result = summarize(simulate(n=20, interval_ms=interval, service_ms=400), interval)\n",
    "    assert result['request_mean_ms'] == float(row['request_mean_ms'])\n",
    "    assert result['effective_mean_ms'] == float(row['effective_mean_ms'])\n",
    "    assert result['effective_p95_ms'] == int(float(row['effective_p95_ms']))\n",
    "    print(f\"{interval:>3} ms arrivals: request mean {result['request_mean_ms']:.0f} ms; credit-to-completion mean {result['effective_mean_ms']:.0f} ms; inclusive 1 s passes {result['effective_slo_pass_count']}/20\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c1ade717",
   "metadata": {},
   "outputs": [
    {
     "output_type": "display_data",
     "data": {
      "image/svg+xml": [
       "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 820 450\" role=\"img\" aria-label=\"Grouped bars compare request and credit-inclusive mean latency across four arrival intervals in the deterministic 20-request model.\"><rect width=\"100%\" height=\"100%\" fill=\"#fff\"/><line x1=\"95\" y1=\"355.0\" x2=\"790\" y2=\"355.0\" stroke=\"#d9d5dc\"/><text x=\"83\" y=\"360.0\" text-anchor=\"end\" font-size=\"13\" fill=\"#49444b\">0</text><line x1=\"95\" y1=\"265.57142857142856\" x2=\"790\" y2=\"265.57142857142856\" stroke=\"#d9d5dc\"/><text x=\"83\" y=\"270.57142857142856\" text-anchor=\"end\" font-size=\"13\" fill=\"#49444b\">1,000</text><line x1=\"95\" y1=\"176.14285714285714\" x2=\"790\" y2=\"176.14285714285714\" stroke=\"#d9d5dc\"/><text x=\"83\" y=\"181.14285714285714\" text-anchor=\"end\" font-size=\"13\" fill=\"#49444b\">2,000</text><line x1=\"95\" y1=\"86.71428571428572\" x2=\"790\" y2=\"86.71428571428572\" stroke=\"#d9d5dc\"/><text x=\"83\" y=\"91.71428571428572\" text-anchor=\"end\" font-size=\"13\" fill=\"#49444b\">3,000</text><line x1=\"95\" y1=\"42\" x2=\"95\" y2=\"355\" stroke=\"#514b53\"/><line x1=\"95\" y1=\"355\" x2=\"790\" y2=\"355\" stroke=\"#514b53\"/><rect x=\"157.9\" y=\"319.2\" width=\"24\" height=\"35.8\" rx=\"3\" fill=\"#e98257\"/><rect x=\"181.9\" y=\"64.4\" width=\"24\" height=\"290.6\" rx=\"3\" fill=\"#683b73\"/><text x=\"181.875\" y=\"379\" text-anchor=\"middle\" font-size=\"14\" fill=\"#49444b\">100 ms</text><rect x=\"331.6\" y=\"319.2\" width=\"24\" height=\"35.8\" rx=\"3\" fill=\"#e98257\"/><rect x=\"355.6\" y=\"149.3\" width=\"24\" height=\"205.7\" rx=\"3\" fill=\"#683b73\"/><text x=\"355.625\" y=\"379\" text-anchor=\"middle\" font-size=\"14\" fill=\"#49444b\">200 ms</text><rect x=\"505.4\" y=\"319.2\" width=\"24\" height=\"35.8\" rx=\"3\" fill=\"#e98257\"/><rect x=\"529.4\" y=\"319.2\" width=\"24\" height=\"35.8\" rx=\"3\" fill=\"#683b73\"/><text x=\"529.375\" y=\"379\" text-anchor=\"middle\" font-size=\"14\" fill=\"#49444b\">400 ms</text><rect x=\"679.1\" y=\"319.2\" width=\"24\" height=\"35.8\" rx=\"3\" fill=\"#e98257\"/><rect x=\"703.1\" y=\"319.2\" width=\"24\" height=\"35.8\" rx=\"3\" fill=\"#683b73\"/><text x=\"703.125\" y=\"379\" text-anchor=\"middle\" font-size=\"14\" fill=\"#49444b\">800 ms</text><text x=\"24\" y=\"210\" transform=\"rotate(-90 24 210)\" text-anchor=\"middle\" font-size=\"14\" fill=\"#49444b\">Mean latency (ms)</text><text x=\"430\" y=\"412\" text-anchor=\"middle\" font-size=\"14\" fill=\"#49444b\">Scheduled interval between requests</text><rect x=\"155\" y=\"20\" width=\"14\" height=\"14\" fill=\"#e98257\"/><text x=\"176\" y=\"32\" font-size=\"13\" fill=\"#333\">Request: dispatch to completion</text><rect x=\"405\" y=\"20\" width=\"14\" height=\"14\" fill=\"#683b73\"/><text x=\"426\" y=\"32\" font-size=\"13\" fill=\"#333\">Effective: credit to completion</text><text x=\"430\" y=\"440\" text-anchor=\"middle\" font-size=\"12\" fill=\"#6b6670\">Finite batch of 20 requests; 400 ms constant service; 20-request FIFO model</text></svg>"
      ],
      "text/plain": [
       "<IPython.core.display.SVG object>"
      ]
     },
     "metadata": {}
    }
   ],
   "source": [
    "# Build a compact SVG directly from the raw scenario summaries (no plotting package).\n",
    "from IPython.display import SVG, display\n",
    "intervals = [int(r['target_interval_ms']) for r in sweep]\n",
    "request_means = [float(r['request_mean_ms']) for r in sweep]\n",
    "effective_means = [float(r['effective_mean_ms']) for r in sweep]\n",
    "width, height = 820, 450\n",
    "left, right, top, bottom = 95, 790, 42, 355\n",
    "max_value = 3500\n",
    "scale = (bottom - top) / max_value\n",
    "parts = [f'<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 {width} {height}\" role=\"img\" aria-label=\"Grouped bars compare request and credit-inclusive mean latency across four arrival intervals in the deterministic 20-request model.\">']\n",
    "parts.append('<rect width=\"100%\" height=\"100%\" fill=\"#fff\"/>')\n",
    "for value in (0, 1000, 2000, 3000):\n",
    "    y = bottom - value * scale\n",
    "    parts.append(f'<line x1=\"{left}\" y1=\"{y}\" x2=\"{right}\" y2=\"{y}\" stroke=\"#d9d5dc\"/>')\n",
    "    parts.append(f'<text x=\"{left-12}\" y=\"{y+5}\" text-anchor=\"end\" font-size=\"13\" fill=\"#49444b\">{value:,}</text>')\n",
    "parts.append(f'<line x1=\"{left}\" y1=\"{top}\" x2=\"{left}\" y2=\"{bottom}\" stroke=\"#514b53\"/>')\n",
    "parts.append(f'<line x1=\"{left}\" y1=\"{bottom}\" x2=\"{right}\" y2=\"{bottom}\" stroke=\"#514b53\"/>')\n",
    "group = (right - left) / len(intervals)\n",
    "bar_width = 24\n",
    "for i, interval in enumerate(intervals):\n",
    "    center = left + group * (i + 0.5)\n",
    "    for offset, value, color in [(-bar_width/2, request_means[i], '#e98257'), (bar_width/2, effective_means[i], '#683b73')]:\n",
    "        x = center + offset - bar_width/2\n",
    "        bar_height = value * scale\n",
    "        y = bottom - bar_height\n",
    "        parts.append(f'<rect x=\"{x:.1f}\" y=\"{y:.1f}\" width=\"{bar_width}\" height=\"{bar_height:.1f}\" rx=\"3\" fill=\"{color}\"/>')\n",
    "    parts.append(f'<text x=\"{center}\" y=\"{bottom+24}\" text-anchor=\"middle\" font-size=\"14\" fill=\"#49444b\">{interval} ms</text>')\n",
    "parts.append('<text x=\"24\" y=\"210\" transform=\"rotate(-90 24 210)\" text-anchor=\"middle\" font-size=\"14\" fill=\"#49444b\">Mean latency (ms)</text>')\n",
    "parts.append('<text x=\"430\" y=\"412\" text-anchor=\"middle\" font-size=\"14\" fill=\"#49444b\">Scheduled interval between requests</text>')\n",
    "parts.append('<rect x=\"155\" y=\"20\" width=\"14\" height=\"14\" fill=\"#e98257\"/><text x=\"176\" y=\"32\" font-size=\"13\" fill=\"#333\">Request: dispatch to completion</text>')\n",
    "parts.append('<rect x=\"405\" y=\"20\" width=\"14\" height=\"14\" fill=\"#683b73\"/><text x=\"426\" y=\"32\" font-size=\"13\" fill=\"#333\">Effective: credit to completion</text>')\n",
    "parts.append('<text x=\"430\" y=\"440\" text-anchor=\"middle\" font-size=\"12\" fill=\"#6b6670\">Finite batch of 20 requests; 400 ms constant service; 20-request FIFO model</text>')\n",
    "parts.append('</svg>')\n",
    "display(SVG(''.join(parts)))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "20 requests; dispatch mean 400 ms; credit-inclusive mean 3250 ms; max 6100 ms; 3/20 meet 1000 ms.\n"
     ]
    }
   ],
   "source": [
    "# Verify the 100 ms arrival scenario.\n",
    "scenario = [row for row in requests if int(row[\"target_interval_ms\"]) == 100]\n",
    "assert len(scenario) == 20\n",
    "assert sum(int(row[\"request_latency_ms\"]) for row in scenario) / 20 == 400\n",
    "assert sum(int(row[\"effective_latency_ms\"]) for row in scenario) / 20 == 3250\n",
    "assert max(int(row[\"effective_latency_ms\"]) for row in scenario) == 6100\n",
    "assert sum(int(row[\"effective_latency_ms\"]) <= 1000 for row in scenario) == 3\n",
    "print(\"20 requests; dispatch mean 400 ms; credit-inclusive mean 3250 ms; max 6100 ms; 3/20 meet 1000 ms.\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3417497a",
   "metadata": {},
   "source": [
    "## Read the result carefully\n",
    "\n",
    "At 100 ms arrivals, service remains exactly 400 ms per request in every row, while the modeled mean from credit issuance to completion is 3,250 ms. The difference comes from pre-dispatch FIFO waiting. The sample is a drained batch of 20 requests, not a steady-state estimate.\n",
    "\n",
    "Reproduce the unit suite from this folder with `python3 -m unittest -v test_timing_model.py`. Change `interval_ms` in the model cell to explore other deterministic inputs.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
