{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Lab 11 Self-Driven Lab*\n",
    "\n",
    "# Capstone: End-to-End Architecture Review\n",
    "\n",
    "**Objective:** audit the deliberately flawed Regulatory Change Assistant, name one defect per exam domain, and commit to each repair before reading the annotated findings.\n",
    "\n",
    "## Challenge Outline\n",
    "\n",
    "Build a complete notebook that demonstrates the following outcomes:\n",
    "\n",
    "- **D1 Solution Design: agent vs. workflow:** state the Domain 1 finding, why an autonomous agent fails this brief, and the repair, defended in one line.\n",
    "- **D2 Prompting & Context: the cache can never hit:** identify both cache invalidators in the proposed call, then write the repaired call with the stable corpus first and prove the fix from usage fields.\n",
    "- **D3 Integration: tool bloat, confused deputy, wrong retrieval:** name all three integration defects in the tools/identity/retrieval rows and give the least-privilege and keyed-lookup repairs.\n",
    "- **D4 Evaluation: the wrong metrics and an uncalibrated judge:** reject the average-latency metric and the uncalibrated LLM judge, and specify the p95 / calibrated-judge / frozen-held-out-set repair.\n",
    "- **D5 Governance: the auto-filed 'no impact' verdict:** find the branch that most needs a human gate, and repair the PII handling and the unbounded retention against GDPR and residency.\n",
    "- **D6 Stakeholder & Lifecycle: the unhonourable SLA, and the handover:** explain why the 99% live-traffic accuracy promise cannot be measured or honoured, replace it with evidence-backed commitments, and list the artifact set the review hands over.\n",
    "- **D7 Developer Productivity: secrets in CLAUDE.md:** state why credentials in a committed, context-loaded file are an audit finding on their own, and where they belong instead.\n",
    "- **The cost model:** compute the uncached vs. cached daily cost of the repaired design and the cost per resolved task, and state which number the business funds.\n",
    "\n",
    "Your solution should include enough code, output, or written observations to prove each outcome worked. Keep the notebook focused on final behavior and evidence rather than a guided walkthrough.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Student Workspace\n",
    "\n",
    "Use the sections below to build your solution. Each section maps to one required outcome from the challenge outline.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 1: D1 Solution Design: agent vs. workflow\n",
    "\n",
    "state the Domain 1 finding, why an autonomous agent fails this brief, and the repair, defended in one line.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Part 1: D1 Solution Design: agent vs. workflow\n",
    "# Add your implementation, outputs, or notes here.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 2: D2 Prompting & Context: the cache can never hit\n",
    "\n",
    "identify both cache invalidators in the proposed call, then write the repaired call with the stable corpus first and prove the fix from usage fields.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Part 2: D2 Prompting & Context: the cache can never hit\n",
    "# Add your implementation, outputs, or notes here.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 3: D3 Integration: tool bloat, confused deputy, wrong retrieval\n",
    "\n",
    "name all three integration defects in the tools/identity/retrieval rows and give the least-privilege and keyed-lookup repairs.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Part 3: D3 Integration: tool bloat, confused deputy, wrong retrieval\n",
    "# Add your implementation, outputs, or notes here.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 4: D4 Evaluation: the wrong metrics and an uncalibrated judge\n",
    "\n",
    "reject the average-latency metric and the uncalibrated LLM judge, and specify the p95 / calibrated-judge / frozen-held-out-set repair.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Part 4: D4 Evaluation: the wrong metrics and an uncalibrated judge\n",
    "# Add your implementation, outputs, or notes here.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 5: D5 Governance: the auto-filed 'no impact' verdict\n",
    "\n",
    "find the branch that most needs a human gate, and repair the PII handling and the unbounded retention against GDPR and residency.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Part 5: D5 Governance: the auto-filed 'no impact' verdict\n",
    "# Add your implementation, outputs, or notes here.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 6: D6 Stakeholder & Lifecycle: the unhonourable SLA, and the handover\n",
    "\n",
    "explain why the 99% live-traffic accuracy promise cannot be measured or honoured, replace it with evidence-backed commitments, and list the artifact set the review hands over.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Part 6: D6 Stakeholder & Lifecycle: the unhonourable SLA, and the handover\n",
    "# Add your implementation, outputs, or notes here.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 7: D7 Developer Productivity: secrets in CLAUDE.md\n",
    "\n",
    "state why credentials in a committed, context-loaded file are an audit finding on their own, and where they belong instead.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Part 7: D7 Developer Productivity: secrets in CLAUDE.md\n",
    "# Add your implementation, outputs, or notes here.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 8: The cost model\n",
    "\n",
    "compute the uncached vs. cached daily cost of the repaired design and the cost per resolved task, and state which number the business funds.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Part 8: The cost model\n",
    "# Add your implementation, outputs, or notes here.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Verification Notes\n",
    "\n",
    "Summarize the evidence that each part worked. Capture API signals, validation outcomes, errors, recovery behavior, cost observations, or comparisons required by this lab.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Verification notes\n",
    "# Record the evidence that proves each lab outcome worked.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## Answer Key\n",
    "\n",
    "The cells below contain the completed reference implementation/content for this lab. Use this section only after attempting the self-driven lab.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Lab 11 Answer Key*\n",
    "\n",
    "# Capstone: End-to-End Architecture Review\n",
    "\n",
    "**Objective:** audit the deliberately flawed Regulatory Change Assistant, name one defect per exam domain, and commit to each repair before reading the annotated findings.\n",
    "\n",
    "The system under review: a global bank's Regulatory Change Assistant. Analysts track publications across 14 jurisdictions (~300 documents/day); every assertion in an impact memo must be traceable to a source; retrieval must resolve exact clauses like 'Article 17(2)'; GDPR and a data-residency mandate apply; p95 must be under 30 seconds; and a wrong 'no impact' verdict is a regulatory finding. The team proposes: an autonomous agent with a broad tool set (including delete_policy), one union-permission service account, dense-vector retrieval, auto-filed 'no impact' verdicts, unredacted PII, average-latency plus uncalibrated-LLM-judge evaluation with no held-out set, a 99% live-traffic accuracy SLA, and secrets in CLAUDE.md.\n",
    "\n",
    "This notebook is the review performed. Each part is one domain: the finding, why it fails against the brief's own requirements, and the repair. Write your own finding for each domain before reading the worked answer; score one point per defect found AND correctly repaired.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part 1: D1 Solution Design: agent vs. workflow\n",
    "\n",
    "The brief's own task description enumerates the steps: retrieve affected policies, draft a memo, route it. Review the control-flow decision against that fact before anything else.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Finding.** The design hands control flow to the model: an autonomous agent decides per turn which tools to call, on a path that never changes.\n",
    "\n",
    "**Why it fails.** The steps are fixed and enumerable (map change -> affected policies -> memo -> approval), which is the tell for a workflow. An agent here buys non-determinism, higher cost, higher latency, and an audit trail nobody can reconstruct, all to rediscover a sequence the team already wrote down. In a regulated process the un-reconstructable audit trail is the worst part: you cannot tell a regulator why a given memo was produced.\n",
    "\n",
    "**Repair.** A **workflow** whose classification step is a single augmented-LLM call over the retrieved policy terms, with code owning the sequence. One-line defense: when the path is known in advance, code owns the path and the model owns only the judgment inside each step. Reserve agents for the genuinely-unknown-path case, which this is not. (Labs 1 and 2.)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part 2: D2 Prompting & Context: the cache can never hit\n",
    "\n",
    "The proposed call puts a datetime.now() preamble at the very top and the varying publication before the static 60K-token corpus, with the cache breakpoint downstream of both. Caching is a prefix match: any byte change before a breakpoint invalidates everything after it.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Finding.** Two invalidators sit in front of the corpus: a per-request timestamp at the top of `system`, and the varying publication placed before the static corpus. The `cache_control` breakpoint is downstream of both, so it never gets reused: the bank re-processes 60K tokens at full price 300 times a day and blows the p95 SLA that a warm cache would have protected.\n",
    "\n",
    "**Repair.** Stable content physically first (render order is tools -> system -> messages): the corpus behind the breakpoint, and the date and today's publication in the user turn, after it. The repaired call also makes the regulator's traceability rule a schema, which D5 will rely on:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import anthropic\n",
    "\n",
    "client = anthropic.Anthropic()\n",
    "\n",
    "# Every assertion must carry a source. The schema makes that a hard contract:\n",
    "# an uncited claim cannot validate, so the rule is enforced by shape.\n",
    "IMPACT_SCHEMA = {\n",
    "    \"type\": \"json_schema\",\n",
    "    \"schema\": {\n",
    "        \"type\": \"object\",\n",
    "        \"properties\": {\n",
    "            \"verdict\": {\"type\": \"string\", \"enum\": [\"impact\", \"no_impact\"]},\n",
    "            \"affected_policies\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}},\n",
    "            \"cited_sources\": {\n",
    "                \"type\": \"array\",\n",
    "                \"items\": {\n",
    "                    \"type\": \"object\",\n",
    "                    \"properties\": {\n",
    "                        \"clause\": {\"type\": \"string\"},   # e.g. \"Article 17(2)\"\n",
    "                        \"quote\": {\"type\": \"string\"},\n",
    "                    },\n",
    "                    \"required\": [\"clause\", \"quote\"],\n",
    "                    \"additionalProperties\": False,\n",
    "                },\n",
    "            },\n",
    "        },\n",
    "        \"required\": [\"verdict\", \"affected_policies\", \"cited_sources\"],\n",
    "        \"additionalProperties\": False,\n",
    "    },\n",
    "}\n",
    "\n",
    "response = client.messages.create(\n",
    "    model=\"claude-opus-4-8\",\n",
    "    max_tokens=8000,\n",
    "    thinking={\"type\": \"adaptive\"},\n",
    "    output_config={\"effort\": \"high\", \"format\": IMPACT_SCHEMA},\n",
    "    system=[\n",
    "        # STABLE prefix first: the 60K corpus, cached across all 300 docs/day.\n",
    "        {\"type\": \"text\", \"text\": POLICY_CORPUS,\n",
    "         \"cache_control\": {\"type\": \"ephemeral\"}},\n",
    "    ],\n",
    "    messages=[\n",
    "        # Everything volatile AFTER the breakpoint: the date and today's doc.\n",
    "        {\"role\": \"user\",\n",
    "         \"content\": build_user_turn(today_iso, incoming_publication)},\n",
    "    ],\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Prove the fix rather than assume it: read the usage back. input_tokens is\n",
    "# only the uncached remainder; the corpus must show up under the cache fields.\n",
    "u = response.usage\n",
    "print(\"written to cache :\", u.cache_creation_input_tokens)  # ~1.25x, cold write\n",
    "print(\"served from cache:\", u.cache_read_input_tokens)      # ~0.1x, warm hits\n",
    "print(\"uncached remainder:\", u.input_tokens)                # today's doc only\n",
    "\n",
    "assert u.cache_read_input_tokens > 0, \"prefix still has a silent invalidator\"\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part 3: D3 Integration: tool bloat, confused deputy, wrong retrieval\n",
    "\n",
    "Three defects hide in one row of the component table: the tool set, the identity model, and the retrieval choice. Judge each against the brief's requirement that queries like 'Article 17(2)' resolve exactly.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Finding 1: capability bloat.** The agent holds a `delete_policy` tool it never needs. An unused destructive tool is pure blast radius: it can only ever hurt you. **Repair:** delete it from the schema. The model cannot call what it was never given.\n",
    "\n",
    "**Finding 2: confused deputy.** One service account holds the union of every jurisdiction's permissions. A prompt-injected publication could steer the assistant to act with authority far beyond the task, across jurisdictions it should never touch. **Repair:** scope credentials to the jurisdiction of the document being processed, so even a fully hijacked turn cannot reach beyond its own lane.\n",
    "\n",
    "**Finding 3: wrong retrieval.** Dense-vector similarity answers 'what is *similar* to this?', but 'Article 17(2)' is a key, not a concept. Embedding it is architecture theatre that silently returns near-miss clauses. **Repair:** an exact clause lookup (a keyed index) for citation queries, keeping semantic search only for the genuinely fuzzy 'what else might this touch?' pass. (Labs 4 and 5.)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part 4: D4 Evaluation: the wrong metrics and an uncalibrated judge\n",
    "\n",
    "Success is measured by average answer latency and an LLM-judge rubric never checked against human graders, with no held-out set. Review each instrument against what it is supposed to prove.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Finding.** Three evaluation defects compound:\n",
    "\n",
    "1. **Average latency hides the tail.** The SLA is p95 under 30 seconds; an average can look healthy while one memo in twenty times out.\n",
    "2. **The judge is uncalibrated.** A rubric scored by an LLM that was never checked against human graders is an opinion of unknown correlation with ground truth: a high score proves nothing about memo quality.\n",
    "3. **No held-out set.** Every prompt change is tuned against the same data it is scored on, so reported improvements are indistinguishable from overfitting.\n",
    "\n",
    "**Repair.** Track **p95**, not the mean. Calibrate the judge against a human-labelled sample and report the agreement rate before trusting it. Freeze a held-out regression set that no prompt change is allowed to see, and gate deploys on it. The citation requirement gives the evaluation seam for free: cited-span-exists is a deterministic check on every memo. (Labs 6 and 7.)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part 5: D5 Governance: the auto-filed 'no impact' verdict\n",
    "\n",
    "The brief spends its words on one fact: a wrong 'no impact' verdict is a regulatory finding. Check which branch the design chose to automate, then review the PII and retention rows.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Finding.** The design automates precisely the decision that most needs a human: 'no impact' verdicts auto-file with no gate, on the exact branch whose wrong answer is a regulatory finding. Alongside it, PII goes to the model unredacted (widening the GDPR surface for no benefit) and outputs are retained indefinitely (a standing liability under residency and right-to-erasure rules).\n",
    "\n",
    "**Repair.**\n",
    "\n",
    "- **A named human on the 'no impact' path.** Auto-file, if anything, only the low-risk 'impact found and routed' cases, which analysts will see anyway. The gate is a governance constraint, not a UX preference, and no confidence score moves it.\n",
    "- **Redact PII before the call** where it is not needed for the decision, so it never enters the trace at all.\n",
    "- **Bound retention** to the residency requirement, with a data-flow inventory covering traces, caches, and the eval set so an erasure request is honorable.\n",
    "- The D2 schema is also a governance control: a required `cited_sources` field means an uncited memo cannot validate, enforcing the regulator's traceability rule by shape rather than by a polite instruction. (Lab 8.)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part 6: D6 Stakeholder & Lifecycle: the unhonourable SLA, and the handover\n",
    "\n",
    "The team promised the regulator 99% accuracy on live traffic. Test the promise for measurability, then assemble the deliverable set a finished review hands over: the review is not done when the code is right.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Finding.** 'Accuracy on live traffic' has no denominator you control: the true label on a novel publication is unknown until an analyst rules on it, so the promised number cannot be measured in real time, let alone guaranteed. It also anchors the relationship to a figure one hard month will breach, converting a capability into a liability. A commitment made without an evaluation methodology that can support it is a lifecycle failure, not a marketing choice.\n",
    "\n",
    "**Repair.** Commit to what you can evidence: a measured accuracy on a frozen, human-labelled benchmark; a documented human gate on the high-risk branch; a monitored p95. Promise the process, not a live-traffic percentage. (Lab 9.)\n",
    "\n",
    "**The handover set.** The review is finished when the next engineer, the regulator, and the on-call responder can each do their job from what you leave behind. A missing row is a defect:\n",
    "\n",
    "| Artifact | Answers |\n",
    "|---|---|\n",
    "| Architecture diagram (five planes) | What the system does and where each control lives |\n",
    "| ADR log | Why the agent was rejected; why the keyed lookup replaced vectors |\n",
    "| Eval suite + frozen regression set | How we know a change did not regress accuracy |\n",
    "| Runbook | How on-call recovers when a memo fails to file |\n",
    "| Cost model (Part 8) | What 300 docs/day costs, cached vs. uncached |\n",
    "| Risk register | The residual risks after every repair, and who owns each |\n",
    "| On-call rotation | Who responds, and within what window |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part 7: D7 Developer Productivity: secrets in CLAUDE.md\n",
    "\n",
    "API keys and credentials are pasted into the project CLAUDE.md so the team shares one setup. Review that choice for a bank.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Finding.** `CLAUDE.md` is committed to the repository and loaded into context on every run; it is documentation, not a vault. Secrets there are exposed to everyone with repo access, leak into logs and traces on every request, and are near-impossible to rotate cleanly. For a bank this is an audit finding by itself, before any incident occurs.\n",
    "\n",
    "**Repair.** Move secrets to environment variables or a managed secret store, referenced from code and never from context. Keep `CLAUDE.md` for the operational knowledge it is good at: how to run the eval suite, where the runbook lives. (Lab 10.)\n",
    "\n",
    "**The through-line across all seven findings:** every repair *subtracts*. Delete the agent, delete the tool, narrow the credentials, gate the automated branch, drop the live-traffic promise, take the secrets out of context. When two options differ by a capability, the one that removes it is usually right.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part 8: The cost model\n",
    "\n",
    "Do the arithmetic, because 'caching saves money' is not a number and a CFO does not fund adjectives. Illustrative rates: Opus 4.8 at $5.00/M input and $25.00/M output, cache reads ~0.1x input, cold writes 1.25x (5-minute TTL).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Illustrative model, not a price quote. 60K-token corpus, 4K publication,\n",
    "# 2K memo, 300 docs/day; corpus re-warmed ~12x/day on a 5-minute TTL.\n",
    "OPUS_IN = 5.00 / 1_000_000\n",
    "OPUS_OUT = 25.00 / 1_000_000\n",
    "CORPUS, PUB, MEMO, DOCS = 60_000, 4_000, 2_000, 300\n",
    "\n",
    "uncached = CORPUS * OPUS_IN + PUB * OPUS_IN + MEMO * OPUS_OUT\n",
    "cached   = CORPUS * OPUS_IN * 0.10 + PUB * OPUS_IN + MEMO * OPUS_OUT\n",
    "warm_writes = 12 * CORPUS * OPUS_IN * 1.25\n",
    "\n",
    "daily_uncached = uncached * DOCS\n",
    "daily_cached = cached * DOCS + warm_writes\n",
    "\n",
    "print(f\"per request, uncached (proposed): ${uncached:.3f}\")\n",
    "print(f\"per request, cached  (repaired): ${cached:.3f}\")\n",
    "print(f\"daily, uncached: ${daily_uncached:.2f}\")\n",
    "print(f\"daily, cached  : ${daily_cached:.2f} (incl. ${warm_writes:.2f} cache writes)\")\n",
    "print(f\"the D2 ordering defect was costing ${daily_uncached - daily_cached:.2f}/day,\")\n",
    "print(f\"about ${(daily_uncached - daily_cached) * 365 / 1000:.0f}K/year, for nothing\")\n",
    "\n",
    "# Cost per request is not cost per RESOLVED task. With 20% of memos needing a\n",
    "# second pass, ~360 calls resolve 300 business tasks:\n",
    "calls = DOCS * 1.2\n",
    "per_resolved = cached * calls / DOCS\n",
    "print(f\"cost per request      : ${cached:.3f}\")\n",
    "print(f\"cost per resolved task: ${per_resolved:.3f}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Roughly a 3x reduction, driven almost entirely by the corpus moving from full price to a tenth of it, and the same repair protects the 30-second p95 because the warm prefix is faster to first token: caching is the rare lever that moves cost and the SLA in the same direction. The CFO cares about the last number, cost per **resolved** task, because it is the one tied to a completed unit of work; report that, not cost per request.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
