{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Quickstart\n",
    "\n",
    "## Using the Block Model API Client\n",
    "\n",
    "Using the notebook utilities provided by `evo-sdk-common`, we can easily interact with the Block Model Service in a Jupyter notebook environment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.notebooks import ServiceManagerWidget\n",
    "\n",
    "manager = await ServiceManagerWidget.with_auth_code(\n",
    "    client_id=\"your-client-id\",\n",
    "    cache_location=\"./notebook-data\",\n",
    ").login()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## BlockModelAPIClient\n",
    "\n",
    "The `BlockModelAPIClient` wraps endpoint functionality to provide a cohesive interface to the underlying API implementation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.blockmodels import BlockModelAPIClient\n",
    "\n",
    "service_client = BlockModelAPIClient.from_context(manager)\n",
    "service_health = await service_client.get_service_health()\n",
    "\n",
    "print(f\"Block Model Service is {service_health.status.name.lower()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### List Block Models"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.blockmodels.data import SubBlockedGridDefinition\n",
    "\n",
    "# List the first page\n",
    "models = await service_client.list_block_models()\n",
    "print(f\"Found {len(models)} models. First model id: {models[0].id if len(models) > 0 else None}\")\n",
    "\n",
    "# Fetch all models across pages with a page size of 50\n",
    "all_models = await service_client.list_all_block_models(page_limit=5)\n",
    "print(f\"Total models returned: {len(all_models)}\")\n",
    "\n",
    "# Inspect a model\n",
    "if len(all_models) > 0:\n",
    "    block_model = all_models[0]\n",
    "    print(f\"First model name: {block_model.name}\")\n",
    "    if isinstance(block_model.grid_definition, SubBlockedGridDefinition):\n",
    "        print(f\"First model grid definition [nx, ny, nz]: {block_model.grid_definition.n_parent_blocks}\")\n",
    "    else:\n",
    "        print(f\"First model grid definition [nx, ny, nz]: {block_model.grid_definition.n_blocks}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### List Block Model Versions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# List the first page of versions\n",
    "versions = await service_client.list_versions(bm_id=block_model.id)\n",
    "print(f\"Found {len(versions)} versions\")\n",
    "\n",
    "# List all versions of a block model\n",
    "# Versions are ordered from newest to oldest\n",
    "all_versions = await service_client.list_all_versions(bm_id=block_model.id)\n",
    "\n",
    "print(f\"Total versions: {len(all_versions)}\")\n",
    "# Inspect a version\n",
    "if len(all_versions) > 0:\n",
    "    version = all_versions[0]\n",
    "    print(f\"Latest Version {version.version_id}: {version.version_uuid} (created: {version.created_at})\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create Block Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pyarrow\n",
    "\n",
    "from evo.blockmodels.data import RegularGridDefinition\n",
    "from evo.blockmodels.endpoints.models import RotationAxis\n",
    "\n",
    "block_grid = RegularGridDefinition(\n",
    "    model_origin=[0, 0, 0], rotations=[(RotationAxis.x, 20)], n_blocks=[10, 10, 10], block_size=[1, 1, 1]\n",
    ")\n",
    "initial_data = pyarrow.table(\n",
    "    {\"i\": [1, 2, 3], \"j\": [4, 5, 6], \"k\": [7, 8, 9], \"column_one\": [\"A\", \"D\", \"E\"], \"column_two\": [1.5, 1.3, 1.2]},\n",
    "    schema=pyarrow.schema(\n",
    "        {\n",
    "            \"i\": pyarrow.uint32(),\n",
    "            \"j\": pyarrow.uint32(),\n",
    "            \"k\": pyarrow.uint32(),\n",
    "            \"column_one\": pyarrow.string(),\n",
    "            \"column_two\": pyarrow.float32(),\n",
    "        }\n",
    "    ),\n",
    ")\n",
    "block_model, version = await service_client.create_block_model(\n",
    "    name=\"My Block Model\",\n",
    "    description=\"My Block Model\",\n",
    "    grid_definition=block_grid,\n",
    "    object_path=\"your/path\",\n",
    "    coordinate_reference_system=\"EPSG:3395\",\n",
    "    size_unit_id=\"m\",\n",
    "    initial_data=initial_data,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Add New Columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_cols = pyarrow.table(\n",
    "    {\"i\": [1, 1], \"j\": [4, 4], \"k\": [6, 7], \"column_three\": [\"C\", \"D\"], \"column_four\": [4.5, 5.3]},\n",
    "    schema=pyarrow.schema(\n",
    "        {\n",
    "            \"i\": pyarrow.uint32(),\n",
    "            \"j\": pyarrow.uint32(),\n",
    "            \"k\": pyarrow.uint32(),\n",
    "            \"column_three\": pyarrow.string(),\n",
    "            \"column_four\": pyarrow.float32(),\n",
    "        }\n",
    "    ),\n",
    ")\n",
    "\n",
    "version = await service_client.add_new_columns(\n",
    "    bm_id=block_model.id,\n",
    "    data=new_cols,\n",
    "    units={\"column_four\": \"g/t\"},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Query Block Model as Table"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.blockmodels.endpoints.models import BBox, IntRange\n",
    "\n",
    "# Select a bounding box to query\n",
    "# This example selects blocks where: 1 <= i <= 2\n",
    "bounding_box = BBox(\n",
    "    i_minmax=IntRange(min=1, max=2),\n",
    "    j_minmax=IntRange(min=1, max=9),\n",
    "    k_minmax=IntRange(min=1, max=9),\n",
    ")\n",
    "\n",
    "# Table will be a Pyarrow Table\n",
    "await service_client.query_block_model_as_table(\n",
    "    bm_id=block_model.id,\n",
    "    columns=[\"column_one\", \"column_four\"],\n",
    "    bbox=bounding_box,\n",
    "    version_uuid=version.version_uuid,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Update column units"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Update units for an existing column and add a comment describing the change\n",
    "# This example updates `column_two` to use percent (mass) and records a comment on the new version.\n",
    "version = await service_client.update_column_metadata(\n",
    "    bm_id=block_model.id,\n",
    "    column_updates={\"column_two\": \"%[mass]\"},\n",
    "    comment=\"Set column_two to percent mass for downstream analysis\",\n",
    ")\n",
    "\n",
    "print(f\"Updated version: id={version.version_id}, uuid={version.version_uuid}, comment={version.comment}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Rename Block Model Columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Rename existing columns in the block model\n",
    "# This example renames columns to more descriptive names\n",
    "version = await service_client.rename_block_model_columns(\n",
    "    bm_id=block_model.id,\n",
    "    column_renames={\"column_one\": \"geology_type\", \"column_two\": \"assay_value\"},\n",
    "    comment=\"Renamed columns for clarity and consistency\",\n",
    ")\n",
    "\n",
    "print(f\"Renamed version: id={version.version_id}, uuid={version.version_uuid}, comment={version.comment}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Delete Block Model Columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Delete columns from a block model\n",
    "# This example deletes two columns and records a comment on the new version.\n",
    "version_after_delete = await service_client.delete_block_model_columns(\n",
    "    bm_id=block_model.id,\n",
    "    column_titles=[\"column_three\", \"column_four\"],\n",
    "    comment=\"Remove temporary columns\",\n",
    ")\n",
    "\n",
    "print(\n",
    "    f\"After delete: id={version_after_delete.version_id}, uuid={version_after_delete.version_uuid}, comment={version_after_delete.comment}\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create Sub-blocked Model\n",
    "\n",
    "In addition to supporting regular block models, the Block Model API also supports sub-blocked models. For information on sub-blocked models, see the [Block Model API documentation](https://developer.seequent.com/docs/guides/blockmodel/geometry/sub-blocking).\n",
    "\n",
    "In this example, we will create a fully sub-blocked model, which means each parent block can either be undivided or divided into a fixed number of sub-blocks along each axis. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.blockmodels.data import FullySubBlockedGridDefinition\n",
    "\n",
    "# Similar to regular block models, sub-blocked models can be created with the create_block_model method, but uses a different\n",
    "# grid definition.\n",
    "block_grid = FullySubBlockedGridDefinition(\n",
    "    model_origin=[0, 0, 0],\n",
    "    rotations=[],\n",
    "    n_parent_blocks=[10, 10, 10],\n",
    "    parent_block_size=[2, 1, 1],\n",
    "    n_subblocks_per_parent=[5, 1, 2],\n",
    ")\n",
    "\n",
    "# As an alternative to specifying the index columns, data tables for sub-blocked models can also be defined by columns defining the block centroids (x, y, z) and sizes (dx, dy, dz).\n",
    "initial_data = pyarrow.table(\n",
    "    {\n",
    "        \"x\": [1.0, 2.6, 3.0],\n",
    "        \"y\": [1.5, 2.5, 2.5],\n",
    "        \"z\": [4.5, 2.25, 2.25],\n",
    "        \"dx\": [2.0, 0.4, 0.4],\n",
    "        \"dy\": [1.0, 1.0, 1.0],\n",
    "        \"dz\": [1.0, 0.5, 0.5],\n",
    "        \"column_one\": [\"A\", \"D\", \"E\"],\n",
    "        \"column_two\": [1.5, 1.3, 1.2],\n",
    "    },\n",
    "    schema=pyarrow.schema(\n",
    "        {\n",
    "            \"x\": pyarrow.float64(),\n",
    "            \"y\": pyarrow.float64(),\n",
    "            \"z\": pyarrow.float64(),\n",
    "            \"dx\": pyarrow.float64(),\n",
    "            \"dy\": pyarrow.float64(),\n",
    "            \"dz\": pyarrow.float64(),\n",
    "            \"column_one\": pyarrow.string(),\n",
    "            \"column_two\": pyarrow.float32(),\n",
    "        }\n",
    "    ),\n",
    ")\n",
    "block_model, version = await service_client.create_block_model(\n",
    "    name=\"My Sub-Blocked Model\",\n",
    "    description=\"My Sub-Blocked Model\",\n",
    "    grid_definition=block_grid,\n",
    "    object_path=\"your/path\",\n",
    "    coordinate_reference_system=\"EPSG:3395\",\n",
    "    size_unit_id=\"m\",\n",
    "    initial_data=initial_data,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Add New Sub-blocked Columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# For the add_new_subblocked_columns method, the sub-blocks within the data table must match existing sub-blocks defined in the model.\n",
    "new_cols = pyarrow.table(\n",
    "    {\n",
    "        \"i\": [0, 1],\n",
    "        \"j\": [1, 2],\n",
    "        \"k\": [4, 2],\n",
    "        # The sidx of 0 means that the sub-blocks is the full undivided parent block.\n",
    "        # The sidx of 3 means that the sub-block is the third sub-block within the divided grid, which maps to the (1, 0, 0) position in the sub-block grid.\n",
    "        # For more explanation of sidx, see the Block Model API documentation here: https://developer.seequent.com/docs/guides/blockmodel/geometry/sub-blocking#fully-sub-blocked-models-1\n",
    "        \"sidx\": [0, 3],\n",
    "        \"column_three\": [\"C\", \"D\"],\n",
    "        \"column_four\": [4.5, 5.3],\n",
    "    },\n",
    "    schema=pyarrow.schema(\n",
    "        {\n",
    "            \"i\": pyarrow.uint32(),\n",
    "            \"j\": pyarrow.uint32(),\n",
    "            \"k\": pyarrow.uint32(),\n",
    "            \"sidx\": pyarrow.uint32(),\n",
    "            \"column_three\": pyarrow.string(),\n",
    "            \"column_four\": pyarrow.float32(),\n",
    "        }\n",
    "    ),\n",
    ")\n",
    "version = await service_client.add_new_subblocked_columns(\n",
    "    bm_id=block_model.id,\n",
    "    data=new_cols,\n",
    "    units={\"column_four\": \"g/t\"},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Query Sub-blocked Model as Table"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This behaves the same as querying a regular block model, but it also returns additional columns like the sub-block sizes (dx, dy, dz).\n",
    "await service_client.query_block_model_as_table(\n",
    "    bm_id=block_model.id,\n",
    "    columns=[\"column_one\", \"column_four\"],\n",
    "    version_uuid=version.version_uuid,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Update Geometry of Sub-blocked Model\n",
    "\n",
    "To update the geometry of a sub-blocked model, we can use the `update_subblocked_columns` method, with the `geometry_change` parameter set to `True`. This requires all existing columns to be either updated or deleted, as part of this operation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_cols = pyarrow.table(\n",
    "    {\n",
    "        \"i\": [0, 1],\n",
    "        \"j\": [1, 2],\n",
    "        \"k\": [4, 2],\n",
    "        \"sidx\": [1, 0],\n",
    "        \"column_two\": [11.5, 12.3],\n",
    "        \"column_three\": [\"X\", \"Y\"],\n",
    "        \"column_five\": [14, -1],\n",
    "    },\n",
    "    schema=pyarrow.schema(\n",
    "        {\n",
    "            \"i\": pyarrow.uint32(),\n",
    "            \"j\": pyarrow.uint32(),\n",
    "            \"k\": pyarrow.uint32(),\n",
    "            \"sidx\": pyarrow.uint32(),\n",
    "            \"column_two\": pyarrow.float32(),\n",
    "            \"column_three\": pyarrow.string(),\n",
    "            \"column_five\": pyarrow.int32(),\n",
    "        }\n",
    "    ),\n",
    ")\n",
    "\n",
    "version = await service_client.update_subblocked_columns(\n",
    "    bm_id=block_model.id,\n",
    "    data=new_cols,\n",
    "    update_columns={\"column_two\", \"column_three\"},\n",
    "    delete_columns={\"column_one\", \"column_four\"},\n",
    "    new_columns=[\"column_five\"],\n",
    "    geometry_change=True,\n",
    ")\n",
    "await service_client.query_block_model_as_table(bm_id=block_model.id, version_uuid=version.version_uuid, columns=[\"*\"])"
   ]
  }
 ],
 "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.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
