{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "aa11bb22",
   "metadata": {},
   "source": [
    "# Reviewing job results and device history with Boulder Opal\n",
    "\n",
    "**Retrieve job history, inspect execution summaries, and verify device state after running experiments and routines**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa11body",
   "metadata": {},
   "source": [
    "Every experiment and routine submitted through Boulder Opal is recorded as a job that captures the parameters used, the measurement results returned, and any resulting parameters or device state changes. Retrieving this history is essential when you want to confirm that calibrations ran as expected and see what data and parameters they returned.\n",
    "\n",
    "In this tutorial, you will learn how to list and filter past jobs across the device history, inspect job summaries and full result payloads, and review the current state of the underlying device model through both summary and detailed views.\n",
    "\n",
    "This tutorial assumes you have a Boulder Opal client session, a virtual device with at least one completed job, and the job IDs you want to inspect. If you are starting from scratch, complete the [getting started tutorial](https://docs.q-ctrl.com/boulder-opal/autocalibration/get-started-with-boulder-opal-autocalibration) first."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee55ff66",
   "metadata": {},
   "source": [
    "## 1. Set the device context\n",
    "\n",
    "Select the target device."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aa77bb88",
   "metadata": {},
   "outputs": [],
   "source": [
    "await client.set_current_device(\"<your-device-name>\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "698079df",
   "metadata": {},
   "source": [
    "## 2. List recent jobs\n",
    "\n",
    "Every job submitted through the client is stored on the platform with a unique ID, a status, and a creation timestamp. The `get_jobs()` method returns the most recent jobs for the current device. The `limit` argument controls how many results are returned in a single call, while `page` lets you paginate through older entries. Filtering by `device_name` or `job_name` is useful when you want to compare all runs of a specific experiment or routine across the device history."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cc99dd00",
   "metadata": {},
   "outputs": [],
   "source": [
    "job_history = await client.get_jobs(limit=5)\n",
    "client.display(job_history)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee11ff22",
   "metadata": {},
   "source": [
    "## 3. Inspect a specific job\n",
    "\n",
    "A job summary returns metadata without the full result payload, which is useful when you only need to check status or timing rather than inspect the underlying data. Key attributes include the job ID, the experiment or routine name, the current status, and the creation timestamp. Pass the summary to `client.display()` to render it as a formatted table you can scan quickly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aa33bb44",
   "metadata": {},
   "outputs": [],
   "source": [
    "summary = await client.get_job_summary(job_id=job_history[0].id)\n",
    "client.display(summary)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc55dd66",
   "metadata": {},
   "source": [
    "## 4. Retrieve measurement data\n",
    "\n",
    "In contrast to the lightweight summary above, `get_job_data()` returns the full result payload for a completed job. For experiments, the returned `JobData` object contains the swept parameter values, the measured populations or traces, and any fitted parameters extracted from the data, together with diagnostic plots. For routines, it includes results from each internal experiment. Passing the data to `client.display()` renders the plots and the numeric fit summary inline."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee77ff88",
   "metadata": {},
   "outputs": [],
   "source": [
    "job_data = await client.get_job_data(job_id=job_history[0].id)\n",
    "client.display(job_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc11dd22",
   "metadata": {},
   "source": [
    "## 5. View device summary\n",
    "\n",
    "Beyond individual job records, you can also confirm the current state of the virtual device itself. Routines and experiments configured with `update=\"auto\"` automatically write calibrated parameters back to the device when they complete, so reviewing the device summary is a quick way to verify that an automated update landed as expected. `get_device_summary()` returns high-level metadata including the device name and the timestamp of the last modification, which you can use to confirm when the device was last updated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee33ff44",
   "metadata": {},
   "outputs": [],
   "source": [
    "device_summary = await client.get_device_summary()\n",
    "client.display(device_summary)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa55bb66",
   "metadata": {},
   "source": [
    "## 6. View the device data sheet\n",
    "\n",
    "The device data sheet renders a formatted table of QPU component parameters drawn from the active device, such as drive frequencies, anharmonicities, and coherence times. Calling `display_device_data_sheet()` without arguments shows every component on the device, while passing the optional `node_name` argument focuses the view on a single qubit or resonator. The sheet reflects component parameter values but does not include gate calibrations, which are stored separately as defcals and inspected through the device data object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cc77dd88",
   "metadata": {},
   "outputs": [],
   "source": [
    "await client.display_device_data_sheet()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee99ff00",
   "metadata": {},
   "outputs": [],
   "source": [
    "await client.display_device_data_sheet(node_name=\"q0\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa12bb34",
   "metadata": {},
   "source": [
    "## 7. Command reference\n",
    "\n",
    "Summary of the methods covered in this tutorial.\n",
    "\n",
    "| Method                         | Returns         | Purpose                          |\n",
    "| ------------------------------ | --------------- | -------------------------------- |\n",
    "| `get_jobs()`                   | `list[JobSummary]` | List recent jobs with filters |\n",
    "| `get_job_summary(job_id)`      | `JobSummary`    | Job metadata and status          |\n",
    "| `get_job_data(job_id)`         | `JobData`       | Full results, fits, and plots    |\n",
    "| `get_device_summary()`         | `DeviceSummary` | Device metadata and timestamp    |\n",
    "| `display_device_data_sheet()`  | `None`          | Render component parameter table |\n",
    "| `display(data)`                | `None`          | Render any result in the notebook|"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc56dd78",
   "metadata": {},
   "source": [
    "## Next steps\n",
    "\n",
    "- [How to manually update device parameters](https://docs.q-ctrl.com/boulder-opal/autocalibration/apply/how-to-manually-update-device-parameters-with-boulder-opal): correct or override device parameter values directly on the virtual device.\n",
    "- [OPX+ user guide](https://docs.q-ctrl.com/boulder-opal/autocalibration/discover/setting-up-boulder-opal-with-quantummachines-opx-controllers) or [Qblox user guide](https://docs.q-ctrl.com/boulder-opal/autocalibration/discover/setting-up-boulder-opal-with-qblox-controllers): full routine walkthroughs for backend-specific calibration workflows.\n",
    "- [How to debug failed routines](https://docs.q-ctrl.com/boulder-opal/autocalibration/apply/how-to-debug-a-failed-routine-with-boulder-opal): debug failed jobs and resume calibration from the failing step."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.12.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
