Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/agents/merge-ready-lessons.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Notes from past merge-ready cycles. Read before starting; append after success.

## 2026-10-05 — PR #655 — DDOS-7931 create metadata kwargs

Copilot correctly required a runnable Entities-layer demo for new public create kwargs — extend the existing `entity-updates` notebook rather than inventing a new one. Reinforced: notebook-only pushes skip `Test Python Code` (`**/*.py` path filter), so touch a `.py` file before waiting on required CI.

## 2026-10-05 — PR #654 — DDOS-7952 Pose origin normalize

`Test Python Code` path-filters on `**/*.py`, so a docs-only push leaves required formatting/functionality checks stuck or cancelled — touch a `.py` file to retrigger. `gh pr checks --watch --fail-fast` also exits early on non-required SonarCloud failures; watch required Ubuntu checks only, and treat matrix `cancel` as not-green (fail-fast can cancel queued Ubuntu jobs while Windows already passed). Copilot’s docs nit was real: pose origin docs must say known values + unknown strings preserved, not an exhaustive four-value list.
Expand Down
61 changes: 59 additions & 2 deletions docs/notebooks/clean/entity-updates.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"source": [
"## Setup\n",
"\n",
"Credentials come from **`~/.deeporigin/`** (written when [authenticating](../../how-to/auth.md)). We use `DeepOriginClient.from_disk()` so a repo `.env` or `DO_*` variables in the kernel do not override disk config."
"Credentials come from **`~/.deeporigin/`** (run `deeporigin login` once). We use `DeepOriginClient.from_disk()` so a repo `.env` or `DO_*` variables in the kernel do not override disk config."
]
},
{
Expand All @@ -48,7 +48,7 @@
"from deeporigin.drug_discovery import BRD_DATA_DIR, Ligand, Protein\n",
"from deeporigin.platform import DeepOriginClient\n",
"\n",
"# Disk config only (~/.deeporigin/ after authenticating).\n",
"# Disk config only (~/.deeporigin/ after `deeporigin login`).\n",
"# Do not use DeepOriginClient() here — it prefers DO_* env vars over disk.\n",
"client = DeepOriginClient.from_disk(\"dev\")\n",
"client"
Expand Down Expand Up @@ -133,6 +133,63 @@
"print(f\"file_path={protein_row['file_path']!r}\")"
]
},
{
"cell_type": "markdown",
"id": "3d619a5b",
"metadata": {},
"source": [
"## Entities layer — create with metadata and origin\n",
"\n",
"`create_protein` accepts write-once prep metadata (`state`, `preparation`, `structure_hash`) and\n",
"an `origin` mapping (`kind`, `entity_type`, `entity_id`). `create_ligand` accepts `origin` only.\n",
"The client maps `origin` to platform `origin_*` columns; `origin_entity_display_id` is server-managed.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "89acc135",
"metadata": {},
"outputs": [],
"source": [
"meta_tag = f\"meta-{uuid.uuid4().hex[:10]}\"\n",
"meta_pdb = f\"entities/proteins/meta-{meta_tag}.pdb\"\n",
"client.files.upload(BRD_DATA_DIR / \"brd.pdb\", meta_pdb)\n",
"\n",
"prepared = client.entities.create_protein(\n",
" file_path=meta_pdb,\n",
" state=\"prepared\",\n",
" preparation={\"chains\": [{\"id\": \"chain:A\", \"decision\": \"keep\"}]},\n",
" structure_hash=f\"hash-{meta_tag}\",\n",
" origin={\n",
" \"kind\": \"prepared\",\n",
" \"entity_type\": \"protein\",\n",
" \"entity_id\": protein_id, # prior cell's create_protein id\n",
" },\n",
")\n",
"prep_row = prepared[\"data\"]\n",
"print(\n",
" f\"Prepared protein {prep_row['id']}: state={prep_row.get('state')!r}, \"\n",
" f\"hash={prep_row.get('structure_hash')!r}, origin_kind={prep_row.get('origin_kind')!r}\"\n",
")\n",
"\n",
"extracted = client.entities.create_ligand(\n",
" smiles=\"CCO\",\n",
" name=f\"extracted-{meta_tag}\",\n",
" variant_name_tag=meta_tag,\n",
" origin={\n",
" \"kind\": \"crystal_extraction\",\n",
" \"entity_type\": \"protein\",\n",
" \"entity_id\": prep_row[\"id\"],\n",
" },\n",
")\n",
"lig_row = extracted[\"data\"]\n",
"print(\n",
" f\"Ligand {lig_row['id']}: origin_kind={lig_row.get('origin_kind')!r}, \"\n",
" f\"origin_entity_id={lig_row.get('origin_entity_id')!r}\"\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down
66 changes: 65 additions & 1 deletion src/platform/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Literal, TypedDict

from deeporigin.platform.tags import merge_entity_tags, stamp_batch_row_tags
from deeporigin.utils.constants import (
Expand All @@ -14,6 +14,38 @@
if TYPE_CHECKING:
from deeporigin.platform.client import DeepOriginClient

ProteinState = Literal["unprocessed", "prepared"]


class EntityOrigin(TypedDict, total=False):
"""Write-once provenance supplied on entity create.

Maps to platform columns ``origin_kind``, ``origin_entity_type``, and
``origin_entity_id``. ``origin_entity_display_id`` is server-stamped.
"""

kind: str
entity_type: str
entity_id: str


def _apply_entity_origin(
set_dict: dict[str, Any],
origin: EntityOrigin | None,
) -> None:
"""Translate public ``origin`` mapping into platform ``origin_*`` set fields."""
if origin is None:
return
kind = origin.get("kind")
if kind is not None:
set_dict["origin_kind"] = kind
entity_type = origin.get("entity_type")
if entity_type is not None:
set_dict["origin_entity_type"] = entity_type
entity_id = origin.get("entity_id")
if entity_id is not None:
set_dict["origin_entity_id"] = entity_id


def _writable_ligand_set_fields(set_dict: dict[str, Any]) -> dict[str, Any]:
"""Return a ligand create/update ``set`` payload without molprops fields.
Expand Down Expand Up @@ -54,6 +86,10 @@ def _writable_ligand_set_fields(set_dict: dict[str, Any]) -> dict[str, Any]:
"molecular_weight",
"structure_key",
"tags",
"origin_kind",
"origin_entity_type",
"origin_entity_id",
"origin_entity_display_id",
]

PROTEIN_RETURNING_FIELDS = [
Expand Down Expand Up @@ -85,6 +121,13 @@ def _writable_ligand_set_fields(set_dict: dict[str, Any]) -> dict[str, Any]:
"protein_family",
"ligandability_score",
"protein_length",
"state",
"preparation",
"structure_hash",
"origin_kind",
"origin_entity_type",
"origin_entity_id",
"origin_entity_display_id",
]


Expand Down Expand Up @@ -525,6 +568,7 @@ def create_ligand(
hbond_acceptor_count: int | None = None,
rotatable_bond_count: int | None = None,
tpsa: float | None = None,
origin: EntityOrigin | None = None,
) -> dict:
"""Create a new ligand.

Expand All @@ -536,6 +580,8 @@ def create_ligand(
variant_name_tag: Variant name tag. Defaults to empty string.
tags: Data-platform metadata tags (jsonb object). Provenance
``app`` / ``session`` are merged from the client automatically.
origin: Write-once provenance (``kind``, ``entity_type``, ``entity_id``).
Create-only; not accepted on :meth:`update_ligand`.
molecular_weight: Deprecated. Server-computed from SMILES; ignored.
formal_charge: Deprecated. Server-computed from SMILES; ignored.
hbond_donor_count: Deprecated. Server-computed from SMILES; ignored.
Expand All @@ -559,6 +605,7 @@ def create_ligand(
if mol_file is not None:
set_dict["mol_file"] = mol_file
set_dict["tags"] = merge_entity_tags(self._c, tags, always=True)
_apply_entity_origin(set_dict, origin)

body: dict[str, Any] = {
"set": _writable_ligand_set_fields(set_dict),
Expand Down Expand Up @@ -777,6 +824,10 @@ def create_protein(
protein_length: int | None = None,
project_id: str | None = None,
tags: dict[str, Any] | None = None,
state: ProteinState | None = None,
preparation: dict[str, Any] | None = None,
origin: EntityOrigin | None = None,
structure_hash: str | None = None,
Comment thread
sg-s marked this conversation as resolved.
) -> dict:
"""Create a new protein.

Expand All @@ -791,6 +842,11 @@ def create_protein(
project_id: Project ID for the protein.
tags: Data-platform metadata tags (jsonb object). When provided,
provenance ``app`` / ``session`` are merged from the client.
state: ``unprocessed`` or ``prepared`` (Protein Prep signal).
preparation: JSON summary of what preparation did.
origin: Write-once provenance (``kind``, ``entity_type``, ``entity_id``).
Create-only; not accepted on :meth:`update_protein`.
structure_hash: Tool-supplied structure fingerprint for dedupe.

Returns:
Dictionary containing the created protein data.
Expand All @@ -799,6 +855,13 @@ def create_protein(
"file_path": file_path,
}

if state is not None:
set_dict["state"] = state
if preparation is not None:
set_dict["preparation"] = preparation
if structure_hash is not None:
set_dict["structure_hash"] = structure_hash

if project_id is not None:
set_dict["project_id"] = project_id
if gene_symbol is not None:
Expand All @@ -814,6 +877,7 @@ def create_protein(
if protein_length is not None:
set_dict["protein_length"] = protein_length
set_dict["tags"] = merge_entity_tags(self._c, tags, always=True)
_apply_entity_origin(set_dict, origin)

body: dict[str, Any] = {
"set": set_dict,
Expand Down
Loading
Loading