{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 911 Dispatch Triage — Walkthrough Notebook\n", "\n", "This notebook walks through all three difficulty levels of the environment.\n", "Run each cell in order after starting the server (`uv run server`)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from dispatch_triage.client import DispatchEnv\n", "from dispatch_triage.models import DispatchAction\n", "\n", "env = DispatchEnv(server_url='ws://localhost:8000')\n", "print('Connected.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Helper — pretty-print observation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def show(obs):\n", " print('\\n' + '='*60)\n", " print(obs.message)\n", " print()\n", " print('INCIDENTS')\n", " for inc in obs.incidents:\n", " status = f'→ unit {inc.assigned_unit_id} (rank {inc.dispatch_rank})' if inc.resolved else 'WAITING'\n", " deps = f' [depends on: {inc.depends_on}]' if inc.depends_on else ''\n", " print(f' [{inc.id}] {inc.type:<16} sev={inc.severity} {inc.location:<12} {status}{deps}')\n", " print()\n", " print('UNITS')\n", " for u in obs.units:\n", " avail = 'available' if u.available else 'deployed'\n", " print(f' [{u.id}] {u.type:<12} {avail}')\n", " print()\n", " print(f'Score so far: {obs.score_so_far:.3f} Done: {obs.done}')\n", " print('='*60)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## EASY — 3 incidents, 3 units\n", "\n", "Optimal strategy: dispatch by descending severity with matching unit types." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "obs = env.reset(difficulty='easy')\n", "show(obs)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Step 1: cardiac_arrest (sev=9, id=0) → ambulance (id=0)\n", "result = env.step(DispatchAction(incident_id=0, unit_id=0))\n", "show(result.observation)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Step 2: car_crash (sev=5, id=1) → police (id=1)\n", "result = env.step(DispatchAction(incident_id=1, unit_id=1))\n", "show(result.observation)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Step 3: fire (sev=3, id=2) → fire_truck (id=2)\n", "result = env.step(DispatchAction(incident_id=2, unit_id=2))\n", "show(result.observation)\n", "print(f'\\nFINAL SCORE (easy, optimal): {result.observation.score_so_far:.3f}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## MEDIUM — 5 incidents, 3 units\n", "\n", "Only 3 units available for 5 incidents. \n", "Optimal: cover the 3 highest-severity incidents with matching units." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "obs = env.reset(difficulty='medium')\n", "show(obs)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Step 1: cardiac_arrest (sev=9, id=0) → ambulance (id=0)\n", "result = env.step(DispatchAction(incident_id=0, unit_id=0))\n", "show(result.observation)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Step 2: fire (sev=8, id=1) → fire_truck (id=1)\n", "result = env.step(DispatchAction(incident_id=1, unit_id=1))\n", "show(result.observation)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Step 3: gas_leak (sev=7, id=2) → police (id=2) [mismatch! fire_truck is correct]\n", "# This illustrates the penalty; ideally you'd have a second fire_truck.\n", "result = env.step(DispatchAction(incident_id=2, unit_id=2))\n", "show(result.observation)\n", "print(f'\\nFINAL SCORE (medium, with one mismatch): {result.observation.score_so_far:.3f}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## HARD — 7 incidents, 3 units + cascade dependency\n", "\n", "**Key insight:** Incident 0 (cardiac_arrest, sev=7) spikes to sev=11 if the adjacent \n", "gas leak (incident 1, sev=6) is not resolved first. \n", "Greedy ordering (dispatch fire at sev=8 first) is sub-optimal here." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "obs = env.reset(difficulty='hard')\n", "show(obs)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Step 1: gas_leak (sev=6, id=1) → fire_truck (id=1) — PREVENT the cascade first\n", "result = env.step(DispatchAction(incident_id=1, unit_id=1))\n", "show(result.observation)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Step 2: cardiac_arrest (sev=7, id=0) → ambulance (id=0) — no cascade now\n", "result = env.step(DispatchAction(incident_id=0, unit_id=0))\n", "show(result.observation)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Step 3: fire (sev=8, id=2) → police (id=2) — only unit left, mismatch penalty\n", "result = env.step(DispatchAction(incident_id=2, unit_id=2))\n", "show(result.observation)\n", "print(f'\\nFINAL SCORE (hard, cascade-aware): {result.observation.score_so_far:.3f}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Score Summary\n", "\n", "| Difficulty | Optimal approach | Expected score |\n", "|---|---|---|\n", "| easy | Greedy by severity + correct units | ~1.0 |\n", "| medium | Greedy top-3 severity + correct units | ~0.85 |\n", "| hard | Resolve gas leak first, then greedy | ~0.80 |" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.11.0" } }, "nbformat": 4, "nbformat_minor": 5 }