Load or backfill the ORD ORM Postgres database and verify it before cutover.

Apache-2.0Auto-check: notesDatabases

Install Orm Database Load

skills CLI
$ npx skills add open-reaction-database/ord-schema --skill orm-database-load -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install open-reaction-database/ord-schema orm-database-load --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/open-reaction-database/ord-schema.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/orm-database-load .claude/skills/orm-database-load && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
orm-database-load
GitHub stars
114
Token cost
~3.6k tokens
SKILL.md length
1,859 words
Files
3 (incl. scripts)
Skills in repo
1
Repo updated
First seen
Licence
Apache-2.0

At a glance

Load or backfill the ORD ORM Postgres database and verify it before cutover.

  • Running ordschema.orm.scripts.adddatasets
  • SKILL.md covers Where it runs, Full load, Backfill and Should you scale up the writer?, plus 4 more sections
  • Runs Python scripts from its folder; calls python, aws and psql
  • Populating a new ord<date search database

What it does

Orm Database Load is an agent skill from open-reaction-database/ord-schema. Load or backfill the ORD ORM Postgres database and verify it before cutover. Use when running ordschema.orm.scripts.adddatasets, populating a new ord<date search database, backfilling derived SMILES or RDKit links after a pipeline change, or checking a candidate database for parity with the live one.

Its SKILL.md is about 3.6k tokens, which your agent loads only when the skill is triggered. The skill folder holds 3 other files, including scripts (for example `scripts/global_link.py`).

It sits in Databases, covering Drug discovery and cheminformatics and ORMs and data access. It works with RDKit and PostgreSQL. The repository describes itself as: Schema for the Open Reaction Database. The licence is Apache-2.0.

When your agent uses it

  • Running ordschema.orm.scripts.adddatasets
  • Populating a new ord<date search database
  • Backfilling derived SMILES
  • RDKit links after a pipeline change

Example prompts

  • “/orm-database-load”

Requirements

  • Python 3

What it can do on your machine

Read from SKILL.md and the folder at commit 849f839. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships 2 files in scripts/ (Python), which the agent can run.

    Shell commands in SKILL.md call:

    • python
    • aws
    • psql

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use aws, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Orm Database Load loads about 3.6k tokens when it runs. Until then it costs about 81 tokens; SKILL.md has 1,859 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~81
When it runs · the whole SKILL.md, loaded when a task matches
~3.6k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check: notes

The automated check noted patterns worth knowing about, such as sudo or a known installer.

  • NoteRuns commands with sudoSKILL.md:25
    `sudo -u ubuntu -H bash -l`; and `fs.protected_regular` stops even root from reopening a

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.

SKILL.md

The full file from open-reaction-database/ord-schema at commit 849f839, republished under its Apache-2.0 licence (© open-reaction-database). 1,859 words, ~3,642 tokens.

Download SKILL.mdSave it as .claude/skills/orm-database-load/SKILL.md (or your agent's skills folder). This skill also uses 2 other files; get the full folder from GitHub.
name
orm-database-load
description
Load or backfill the ORD ORM Postgres database and verify it before cutover. Use when running ord_schema.orm.scripts.add_datasets, populating a new ord_<date> search database, backfilling derived SMILES or RDKit links after a pipeline change, or checking a candidate database for parity with the live one.

Loading and backfilling the ORM database

The ORM database is populated in two stages. ingest writes the ord.* search index and the public.* payload; derived writes derived.* SMILES, the rdkit.* structures and their links. Every pass is guarded by NOT EXISTS, so all of it is idempotent: safe to re-run, safe to kill and resume.

Where it runs

Not on a laptop. Loads run on a dev VM inside the database's VPC, because the Aurora cluster has no public endpoint and the job is chatty. The VM is normally stopped — start it, use it, stop it.

sh
aws ec2 describe-instances --filters "Name=tag:Name,Values=dev-vm" \
  --query 'Reservations[].Instances[].{Id:InstanceId,State:State.Name}'

Reach it with SSM (aws ssm send-command, document AWS-RunShellScript); there is no SSH. Two traps: SSM runs commands as root under dash, so wrap real work in sudo -u ubuntu -H bash -l; and fs.protected_regular stops even root from reopening a ubuntu-owned file in /tmp, so give each uploaded script a unique name.

The RDKit version must match the database

derived.*.smiles and rdkit.mols.smiles are written by Python RDKit, not by the Postgres cartridge, and the two do not agree on every molecule. The database stores [Br][Ag]; the cartridge canonicalizes that same structure to Br[Ag]. Load from a machine with a different RDKit and a few thousand organometallics will canonicalize differently, inserting duplicate structures under different strings.

Any SMILES already in rdkit.mols must be a fixed point of the writer you are about to use. Check before writing anything:

python
from rdkit import Chem
for smiles in ("[Br][Ag]", "[B]#[Ni]", "[Cl][Ti]([Cl])([Cl])[Cl]"):
    assert Chem.MolToSmiles(Chem.MolFromSmiles(smiles)) == smiles, smiles

Full load

Put PGHOST / PGPORT / PGUSER / PGPASSWORD / PGSSLMODE / PGDATABASE in the environment (the master password lives in Secrets Manager) and let the DSN inherit them:

sh
python -u -m ord_schema.orm.scripts.add_datasets \
  --pattern "$ORD_DATA/data/*/*.parquet" \
  --dsn "postgresql+psycopg://" \
  --n_jobs 16

Backfill

--stages derived fills in derived rows over already-ingested datasets. It runs the same per-dataset, per-shard RDKit passes as an end-to-end load, so nothing special is required:

sh
python -u -m ord_schema.orm.scripts.add_datasets \
  --pattern "$ORD_DATA/data/*/*.parquet" --stages derived \
  --dsn "postgresql+psycopg://" --n_jobs 16
Backfilling is not rebuilding

Every derived pass is guarded by NOT EXISTS. That is what makes the load idempotent, and it also means an existing row is never revisited: after a change to how SMILES are derived, the command above writes nothing and exits clean, leaving every stale row in place. Silence here is not success.

--rederive recomputes every row and updates the ones whose value changed:

sh
python -u -m ord_schema.orm.scripts.add_datasets \
  --pattern "$ORD_DATA/data/*/*.parquet" --stages derived --rederive \
  --dsn "postgresql+psycopg://" --n_jobs 4

Rows are updated in place, never deleted and reinserted. That matters because search inner-joins through derived.compound_smiles / derived.product_compound_smiles / derived.reaction_smiles into rdkit.* — a row that is absent even briefly is a reaction that briefly cannot be found, and emptying those tables would return zero results for every structure query until the rebuild finished.

The DO UPDATE is conditional on the value actually differing, so an unchanged row is not rewritten. That is not the same as writing nothing: the upsert still does per-row work, and a rederive whose values have changed is a full rewrite of the affected tables. Measured over the published corpus in August 2026, a rederive after #935 rewrote essentially every reaction SMILES — 10 compound updates against 2.4M reaction updates — because the database predated that change.

A rederive removes reactions from structure search for the length of the run. Every row whose SMILES changed has its rdkit_mol_id / rdkit_reaction_id cleared, and the linking pass that refills them runs serially after every dataset's SMILES stage — not per dataset. On the August 2026 run that put 1.98M of 2.43M reactions (81%) out of reaction-SMARTS search for hours. Component search is unaffected, since compound_smiles and product_compound_smiles relink almost immediately; only ReactionSmartsQuery joins rdkit.reactions. Plan the window accordingly, and do not start one expecting the "only changed rows are affected" reading — on a corpus that has drifted, changed rows are most of them.

Do not confuse it with --overwrite, which is an ingest flag meaning "re-ingest a dataset whose MD5 changed."

A row whose source stops yielding a SMILES is dropped, not left and not set to NULL. A row present always means a SMILES was derived, so a stale value can never stay joinable, and an entity that derives nothing is retried by any later pass rather than sitting behind the NOT EXISTS guard.

Size the parallelism for the largest dataset, not the corpus

--n_jobs 16 exhausted Aurora's local temp space on the RDKit stage of ord_dataset-1158e351… (the 2M-reaction USPTO set), failing with psycopg.errors.DiskFull: could not write to file "base/pgsql_tmp/…". FreeLocalStorage bottomed at 1.27 GB on a db.t4g.large. The other 52 datasets completed; only the largest one has shards big enough to spill concurrently.

Watch FreeLocalStorage alongside CPU, and prefer 4 workers for a corpus-wide rederive. The run is not much slower — the RDKit stage is serial anyway — and it is the stage that fails.

Resuming after a failed rederive

A failed dataset rolls back its shard, so the database stays consistent and the SMILES that did land are correct. What remains is the linking, which the ordinary derived pass already does: it fills rdkit_*_id wherever it is NULL, which is exactly the set a failure left behind, and the partial indexes on those tables exist to make that query cheap.

sh
python -u -m ord_schema.orm.scripts.add_datasets \
  --pattern "$ORD_DATA/data/*/*.parquet" --stages derived --prune_rdkit \
  --dsn "postgresql+psycopg://" --n_jobs 4

Without --rederive — the SMILES are already recomputed, so re-running the full rederive would redo 2.4M rows to fix a few hundred thousand links, and hit the same temp-space ceiling. Confirm first that the SMILES stage really finished, by checking that whatever the rebuild was meant to change has changed corpus-wide.

Collecting the structures a rebuild strands

rdkit.mols and rdkit.reactions are shared and deduplicated by structure, so a rederive rewrites the rows that point at them rather than touching them. A rebuild that changes a SMILES therefore links to a new structure and leaves the old one referenced by nothing. --prune_rdkit deletes those, once the RDKit pass has finished:

sh
python -u -m ord_schema.orm.scripts.add_datasets \
  --pattern "$ORD_DATA/data/*/*.parquet" --stages derived --rederive --prune_rdkit \
  --dsn "postgresql+psycopg://" --n_jobs 4

Whole-database by necessity: a structure is orphaned only if no dataset references it, so it cannot be scoped to one dataset or shard. Two consequences. Do not run it beside another load — _update_rdkit_mols inserts structures that _link_mol_ids links in a later statement, so a concurrent derive has a window where live rows look orphaned. And it is skipped automatically when any dataset failed, since an unfinished pass leaves exactly that window open; the log says so rather than staying quiet.

Should you scale up the writer?

Decide from metrics, not instinct.

  • The writer is a single instance with no reader. Changing its class restarts it: measured ~3.5 minutes of downtime, and you pay that twice (up, then back down). The outage hits the live search database, which shares the instance.
  • Check CPUCreditBalance first on burstable classes. Observed 2026-07-09: a 4.7M-row backfill ran at ~93% CPU and burned ~65–80 credits/hour against a full balance of 864 — hours of runway. The instance class was never the constraint.
  • If a job is projecting hours, suspect the query plan before the instance size. Doubling the CPU does not fix a quadratic scan. EXPLAIN the statement that pg_stat_activity shows.
  • Expect buffer-cache pollution regardless: the live database's index working set gets evicted, so search runs cold for a while afterwards. Prefer a low-traffic window.
Show full SKILL.md (730 more words)Show less

Monitoring

Committed row counts lag, because each shard's derive is a single transaction: count(*) sits flat and then jumps. A flat count is not a stall. Check pg_stat_activity (state, wait_event_type, oldest xact_start) and the tqdm bars in the log, which reflect in-flight progress. Watch CPUUtilization and CPUCreditBalance alongside.

Verification

Run scripts/verify_coverage.sql against the candidate database. Under psql -v ON_ERROR_STOP=1 a violated invariant exits non-zero, so a cutover wrapper can gate on process status. What it checks:

  • Coverage (enforced per dataset × scope): every compound carrying a structural identifier (SMILES/InChI/MolBlock) should have a derived SMILES row. Checked per (dataset, attachment scope) cell — ord.compound split by reaction input, workup input, and product measurement (three attachments easy to lose to a join that only follows reaction_input.reaction_id), plus ord.product_compound (reaction products), each grouped by the reaction's dataset. Two gates, both robust to the RDKit-unparseable residue every real load carries (organometallics, charged-N rings — a structural identifier the load-time RDKit cannot parse or reconstruct; ~3.2k across ~18.8M derived rows on a full load):

    • Omission (existence): _update_compound_smiles derives a dataset's compounds across all attachment paths in one unsegmented pass, so a cell it visited keeps at least one derived row (its parseable compounds) while a cell it skipped has exactly zero. Any cell holding ≥ min_scope_size structural compounds must have a derived row. No percentage tolerance — a healthy cell keeps its parseable derivations however many unparseable rows it also carries, and min_scope_size (default 50) absorbs a hypothetical tiny all-unparseable cell. Catches the single-dataset × single-path omission a corpus-wide or reaction-keyed count would dilute.
    • Failed shard (completeness, per shard-hash bucket): SMILES derivation is sharded — one hashtextextended(id) partition of a dataset's compound ids per worker, with num_shards = min(32, ceil(reactions / 50000)), so only datasets over ~50k reactions shard at all. The loader raises on a failed shard, but this gate does not trust that exit status. A failed shard leaves exactly one hash bucket wholly underived — parseable compounds included — while the unparseable residue spreads uniformly across every bucket (parseability is uncorrelated with the id hash) and so can never empty one. The same shard writes that dataset's reaction SMILES in the same transaction under the same hash, so its reaction bucket is empty too. The check recomputes each dataset's num_shards, buckets both tables by that hash, and judges an empty bucket against its sibling buckets: they measure how often this dataset's rows legitimately fail to derive, so the bucket is flagged when the siblings make "every one of mine missed by chance" implausible (combined probability < max_false_positive, default 1e-6). Using both tables is what keeps a sparse hole visible: sharding is chosen by reaction count, so every bucket of a sharded dataset holds thousands of reactions even when a name-heavy one leaves only a handful of structural compounds there — the reaction evidence is strongest exactly where the compound evidence is weakest, and no absolute floor over either table alone spans both. shard_size / shard_cap mirror the loader's constants — keep them in step if those change.

    Name-only compounds are excluded, so the printed compounds > derived gap does not count. Override any threshold with -v NAME=N.

  • Per-dataset reaction derivation (enforced): an independent guard on derived.reaction_smiles, the separate table the compound gate never touches. reaction_smiles carries the reaction's dataset_id, so it is a cheap hash aggregate over ord.reaction (no compound→reaction join) — flag any dataset with fewer than half its reactions derived. A dataset omitted from the derive pass has ~none; the reaction residual stays well under half.

  • No stray unlinked rows (enforced): the only unlinked derived rows are [Ti+5] structures, which _update_rdkit_mols deliberately keeps out of rdkit.mols. Any other unlinked row fails the script.

  • Payload parity (enforced): every ord.reaction has its public.reactions proto and the dataset counts agree.

Two further checks worth doing before a cutover:

  • Dataset freshness, using the loader's own hash: public.datasets.md5 must equal ord_schema.parquet.DatasetView(path).md5() for every Parquet file in ord-data.

  • Reaction-set equality against the database you are replacing — an order-independent fingerprint beats comparing counts alone:

    sql
    SELECT count(*), count(DISTINCT reaction_id), sum(hashtext(reaction_id)::bigint)
    FROM ord.reaction;

Cleanup

Stop the dev VM, close any SSM tunnels, and do not leave the master password on disk.

Gotchas

  • derived.compound_smiles legitimately has fewer rows than ord.compound. Compounds whose only identifier is a name have no derivable SMILES and get no row. That is not data loss.
  • rdkit.mols is deduplicated by SMILES string, not by structure, so two spellings of one molecule become two rows. This is why the writer's RDKit version matters.

© open-reaction-database, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 2 other files (scripts) in .claude/skills/orm-database-load of open-reaction-database/ord-schema.

  • SKILL.md
  • scripts/global_link.py
  • scripts/verify_coverage.sql

Open the folder on GitHubat commit 849f839

Compare with similar skills

Orm Database Load next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Orm Database Load compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Orm Database Load this skillopen-reaction-database/ord-schema114—~3.6kAutomated safety check: NotesApache-2.0
Better Drizzlealmeidazs/better-drizzle347—~1.7kAutomated safety check: PassApache-2.0
Prisma Database Setupcurvenote/curvenote1693 repos~1.4kAutomated safety check: PassMIT
Add Backendahpxex/open-dashboard146—~3.6kAutomated safety check: PassMIT
Releaserogerpadilla/uql125—~842Automated safety check: PassMIT
Database FundamentalsDanielPodolsky/ownyourcode2901 repos~1.6kAutomated safety check: PassMIT

Similar skills

  • Better Drizzle

    almeidazs/better-drizzle

    Write, review, and debug code that uses better-drizzle, the typed repository layer over Drizzle ORM 1.x (better(db), client.users.findMany, paginate, cursor, upsertMany, relation include/connect…

    347 GitHub stars~1.7k tokensUpdated 2 days ago
    DatabasesAuto-check passed
  • Prisma Database Setup

    curvenote/curvenote

    Guides for configuring Prisma with different database providers (PostgreSQL, MySQL, SQLite, MongoDB, etc.).

    169 GitHub starsUsed in 3 repos~1.4k tokens
    DatabasesAuto-check passed
  • Add Backend

    ahpxex/open-dashboard

    Everything about the data layer — pick one of six ready-to-run backend templates (TanStack Start + Drizzle + better-auth, Hono + Drizzle + better-auth, Hono + Prisma + better-auth, Hono + Drizzle +…

    146 GitHub stars~3.6k tokensUpdated 3 mo ago
    DatabasesAuto-check passed
  • Release

    rogerpadilla/uql

    Cut and publish a uql release - review the change, changelog entry, commit, version bump and tag, GitHub Release, npm publish, docs site.

    125 GitHub stars~842 tokensUpdated today
    DatabasesAuto-check passed
  • Database Fundamentals

    DanielPodolsky/ownyourcode

    Reviews schema design, SQL queries, ORM patterns. An agent skill from DanielPodolsky/ownyourcode.

    290 GitHub starsUsed in 1 repo~1.6k tokens
    DatabasesAuto-check passed
  • Database Expert

    cin12211/orca-q

    Database performance optimization, schema design, query analysis, and connection management across PostgreSQL, MySQL, MongoDB, and SQLite with ORM integration.

    223 GitHub stars~2.8k tokensUpdated 16 days ago
    DatabasesAuto-check passed

Works with

Questions about Orm Database Load

What does Orm Database Load do?

Load or backfill the ORD ORM Postgres database and verify it before cutover. Orm Database Load is an agent skill from open-reaction-database/ord-schema. Load or backfill the ORD ORM Postgres database and verify it before cutover.

When should I use Orm Database Load?

Orm Database Load fits situations like: running ordschema.orm.scripts.adddatasets; populating a new ord<date search database; backfilling derived SMILES; RDKit links after a pipeline change.

How do I install Orm Database Load in Claude Code?

Run `npx skills add open-reaction-database/ord-schema --skill orm-database-load -a claude-code`. Or copy the skill folder (.claude/skills/orm-database-load in open-reaction-database/ord-schema) into .claude/skills/orm-database-load in your project. Claude Code loads it when a task matches its description.

How do I install Orm Database Load in Codex?

Run `npx skills add open-reaction-database/ord-schema --skill orm-database-load -a codex`. Or copy the skill folder (.claude/skills/orm-database-load in open-reaction-database/ord-schema) into .agents/skills/orm-database-load in your project. Codex loads it when a task matches its description.

Can I use Orm Database Load in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add open-reaction-database/ord-schema --skill orm-database-load -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/orm-database-load, .gemini/skills/orm-database-load, .github/skills/orm-database-load and .opencode/skills/orm-database-load in your project.

What does Orm Database Load need to run?

Going by SKILL.md and its folder, Orm Database Load needs Python for the scripts in its folder and the command-line tools its instructions call (python, aws and psql). Our summary lists: Python 3.

Does Orm Database Load access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Orm Database Load safe to install?

Our automated static check of SKILL.md found notes only (runs commands with sudo), nothing it rates as a warning. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.

What licence does Orm Database Load use?

Orm Database Load is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Orm Database Load use?

About 3.6k tokens (SKILL.md is roughly 15k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Orm Database Load?

Skills that share tags, products or a category with Orm Database Load: Better Drizzle (almeidazs/better-drizzle, 347 stars), Prisma Database Setup (curvenote/curvenote, 169 stars), Add Backend (ahpxex/open-dashboard, 146 stars) and Release (rogerpadilla/uql, 125 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Orm Database Load?

open-reaction-database (a GitHub organization) maintains it in open-reaction-database/ord-schema, which has 114 GitHub stars. The repository was last updated on October 6, 2026.

Source: open-reaction-database/ord-schema on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.