Skip to content

Repository files navigation

Binder Base

Django + React application for tracking protein binder design experiments.


Table of Contents

  1. Development Quick Start
  2. Admin Dashboard
  3. Run Directory Convention & Import Command
  4. API Endpoints
  5. Production Deployment
  6. Backup

Development Quick Start

Prerequisites

  • Python 3.11+
  • Node.js 18+
  • PostgreSQL

Backend

cd binder_backend
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp binder_backend/.env.example binder_backend/.env
# Edit .env — set SECRET_KEY, DATABASE_*, MEDIA_ROOT, CSRF_TRUSTED_ORIGINS# Apply migrations and run
python manage.py migrate
python manage.py runserver

Frontend

cd binder_frontend
npm install
npm run dev # dev server at http://localhost:5173

The home page is served at http://localhost:5173.


Admin Dashboard

The Django admin interface is available at http://127.0.0.1:8000/admin. Log in with a superuser account (create one with python manage.py createsuperuser).

The following models are registered:

ModelList columnsFilters
ProteinUniProt ID, gene name, protein name, organism, lengthOrganism
BinderRunProtein, algorithm, run date, hardware, description, run dir, userProtein, algorithm, hardware, user
BinderRun, length, rank, quality score, ipTM, statusRun

Run Directory Convention & Import Command

The import_run command scans MEDIA_ROOT/runs/ for run directories not yet in the database and imports each one. This section documents exactly how a run directory is read so you can lay one out correctly.

Directory naming

Each run directory lives directly under MEDIA_ROOT/runs/ and is named:

<UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD>
PartMeaningExample
UNIPROT_IDLeading run of uppercase letters/digits. No lowercase, no hyphens.P12345
RUN_LABELFree-form label for the run. May itself contain hyphens — the date is anchored to the end of the name.tal1_screen
YYYYMMDDTrailing 8 digits.20260501

The directory name is validated against ^([A-Z0-9]+)-(.+)-(\d{8})$ and skips directories that don't match. If the UniProt ID or run date is different between the run directory name and the meta.json file (see below), a warning will be logged during importation. However, the importation will proceed with every imported value coming from meta.json.

Required directory structure

MEDIA_ROOT/runs/
P12345-tal1_screen-20260501/
meta.json # REQUIRED — run identity + metadata (see below)
*.cif # target structure — exactly one .cif in the run root
steps.yaml # optional pipeline config (see below)
config/ # optional configs referenced from steps.yaml
final_ranked_designs/ # REQUIRED
*.csv # binder table — first *.csv in this folder is used
*/rank001_<file_name> # per-design CIF files, matched by rank + filename

Target structure CIF (run root). The importer globs *.cif in the run directory root and uses it as the run's target structure, parsing the target sequence from its _entity_poly record. Keep exactly one.cif here:

  • None → the run still imports, but with no target structure and no target sequence.
  • More than one → the first match in glob order is used (order is not guaranteed), so the chosen file is effectively arbitrary. Always keep a single copy.

steps.yaml (optional). If present and valid, it is stored as the run's steps_config. Any nested config / config_file / config_path string values are replaced inline with the parsed contents of the referenced file (YAML or JSON), resolved relative to the run directory — this is what the config/ folder is for. A missing or unparseable steps.yaml is stored as null and does not stop the import.

meta.json (required). A JSON file in the run root that supplies the run's identity. Six keys are lifted into database columns; every other key is stored in the run's metadata field.

KeyDB fieldRequiredAccepted values
UniProt IDresolves/creates the ProteinYesAny non-empty string
Run daterun_datetimeNoYYYY-MM-DD, YYYY/MM/DD, YYYYMMDD, or a full ISO 8601 timestamp
AlgorithmalgorithmNoAny scalar (strings, numbers)
HardwarehardwareNoAny scalar (strings, numbers)
DescriptiondescriptionNoAny scalar (strings, numbers)
NotesnotesNoAny scalar (strings, numbers)
{
"UniProt ID": "P12345",
"Run date": "2026-05-01",
"Algorithm": "XXX v1.0",
"Hardware": "1x A100 80GB",
"Description": "TAL1-E2 binder screen",
"Notes": "rerun of the April batch with relaxed filters",
"Domain": "TAL1-E2",
"Hotspot residues": "31, 33, 38, 54, 58, 61, 63, 67, 68"
}

Here Domain and Hotspot residues become the run's metadata.

Key matching ignores case and any spaces, underscores or hyphens, so UniProt ID, uniprot_id and UNIPROT-ID are equivalent. Matching is on the whole key, so near-misses like Hardware config or Run dates are not absorbed — they stay in metadata.

If a recognized key's value can't be used — an unparseable Run date, an object where a scalar Algorithm was expected — the column is left null and the raw entry falls through to metadata, so nothing in the file is ever silently lost.

Note: import_run skips run directories it has already imported, so editing a meta.json afterwards will not update the corresponding run. Use import_meta_json to push those edits into runs that are already in the database.

final_ranked_designs/ (required). Must exist, or the directory is skipped (see below). The first*.csv in this folder is read as the binder table; per-design CIF files are located by globbing rank*_<file_name> recursively and matching the filename rank{final_rank}_{file_name} (the rank may be zero-padded to any width, e.g. rank1_, rank01_, rank001_).

The designs CSV

The CSV must contain at minimum a sequence column (rows without it are skipped). Recognized columns and their mapping to database fields:

CSV columnDB field
sequencebinder_sequence (its length → binder_length)
final_rankfinal_rank
quality_scorequality_score
design_to_target_iptmdesign_to_target_iptm
pass_filters (TRUE/FALSE)status (success / failed)
pass_*_filter (FALSE)contributes to failure_reason (which filters failed)
file_nameused (with final_rank) to locate the per-design CIF

Any additional columns are stored in the metrics JSON field.

When directories are skipped

A directory is skipped (logged, and the command continues to the next one) when:

  • It is already imported — its path matches an existing BinderRun.run_dir.
  • Its name does not match the <UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD> pattern.
  • It has no final_ranked_designs/ folder.
  • Its meta.json is missing, unparseable, or not a JSON object.
  • Its meta.json has no usable UniProt ID — without it the Protein can't be resolved.

BinderRun.run_dir also carries a database-level unique constraint, so a duplicate run cannot be created even if the in-memory check is bypassed. Two consequences:

Renaming a run directory makes it import again under the new path, producing a second BinderRun (and a second set of Binder rows) while the original row keeps pointing at a path that no longer exists. Renaming is not a supported way to re-import.

Running the import command

From binder_backend/:

python manage.py import_run

For each new run directory the command:

  1. Reads meta.json, taking UniProt ID, Run date, Algorithm, Hardware, Description and Notes from it and keeping the remaining keys as the run's metadata, then cross-checks the UniProt ID and date against the directory name. Note that if a discrepancy is detected, only a warning will be logged, and the import process will continue.
  2. Resolves the Protein by that UniProt ID. If it already exists it is reused; otherwise the AlphaFold API (sequence, gene name, organism, structure CIF, PAE JSON) and UniProt API (biological function) are queried, the CIF and PAE JSON are downloaded to MEDIA_ROOT/proteins/{uniprot_id}/, and the Protein is created.
  3. Creates a BinderRun (algorithm, date, target CIF path, target sequence, steps_config, metadata).
  4. Bulk-creates Binder records from the CSV, linking each to its per-design CIF.

A summary line reports the number of runs imported and skipped.

Re-reading meta.json for existing runs

import_run only ever looks at directories it hasn't imported, so editing a meta.json afterwards has no effect. To push those edits into runs that are already in the database, use import_meta_json:

python manage.py import_meta_json # every run in the database
python manage.py import_meta_json P12345-tal1_screen-20260501 # just this one
python manage.py import_meta_json --dry-run # preview, write nothing

This is a full resync, not a merge. The six meta.json-owned fields are overwritten from the file, and a key that is absent from the file sets its column back to null — so the database always mirrors the current contents of meta.json.

Because it can clear fields in bulk, run it with --dry-run first. Output is a per-field old -> new diff:

P12345-tal1_screen-20260501
algorithm OLD v0.1 -> XXX v1.0
hardware old hardware -> None
metadata {'stale': 'yes'} -> {'Domain': 'TAL1-E2'}
Done. updated: 1 unchanged: 0 failed: 0

Scope. The command never creates or deletes runs and never touches binders — it only updates the fields above on runs that already exist. Directories on disk that have never been imported are ignored; use import_run for those. Files other than meta.json (steps.yaml, the CIFs, the designs CSV) are not re-read either.

If UniProt ID changed, the run is reassigned to that protein and a warning is logged.

Failures leave data untouched. A run is reported and skipped without any change when its meta.json is missing, unparseable or not a JSON object, when it has no usable UniProt ID, or when the run directory itself is gone. A broken file is treated as a problem to fix, never as an instruction to clear every column.


API Endpoints

All endpoints are mounted at /api/. Interactive docs (OpenAPI/Swagger UI) are available at /api/docs.

GET /api/stats

Returns database-wide totals, for dashboard summary tiles.

Response:

FieldTypeDescription
protein_countintTotal proteins
run_countintTotal binder runs
binder_countintTotal binders across all runs
success_countintBinders with status exactly success

GET /api/proteins

Returns a list of all proteins.

Response — array of:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file

GET /api/proteins/{id}

Returns full detail for a single protein, including all binder runs and their ranked designs.

Response:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file
sequencestringFull amino acid sequence
created_atdatetimeRecord creation timestamp
updated_atdatetimeLast update timestamp
runsarrayList of BinderRun objects (see below)

BinderRun object:

FieldTypeDescription
idintDatabase ID
algorithmstringAlgorithm name and version, from the Algorithm key of meta.json
descriptionstringOptional description, from the Description key of meta.json
run_datetimedatetime | nullDate/time of the run, from the Run date key of meta.json. Null when that key is absent or unparseable
hardwarestringHardware used, from the Hardware key of meta.json
notesstringFree-text notes, from the Notes key of meta.json
run_dirstringRelative path to run directory under MEDIA_ROOT
cif_pathstringRelative path to the target structure CIF file
target_sequencestringTarget sequence parsed from the run's CIF file
steps_configobject | arrayParsed steps.yaml, with referenced config files inlined
metadataobjectRemaining keys of meta.json, after the six lifted keys are taken out (null if none)
userstringUsername who imported the run
binder_countintNumber of binders in the run

Binders are not inlined in this response — a protein with several runs of a few thousand designs each made it tens of MB. Fetch them a page at a time from GET /api/runs/{run_id}/binders instead.


GET /api/runs/{run_id}/binders

Returns one page of a run's binders.

Query parameters:

ParameterTypeDefaultDescription
pageint11-based page number
page_sizeint20Results per page (capped at 200)
sortstringrankOne of rank, quality, iptm, length, status
sort_dirstringascasc or desc; missing values always sort last
statusstringallall, or a status to filter by (case-insensitive)

Response:

FieldTypeDescription
itemsarrayList of Binder objects (see below)
totalintTotal binders matching the filter, across all pages
metric_keysarrayMetric column names available for this run

metric_keys is sampled from the leading rows of the run rather than scanned across every row, since binders within a run come from the same pipeline and carry the same keys.

Binder object:

FieldTypeDescription
idintDatabase ID
binder_sequencestringAmino acid sequence of the designed binder
binder_lengthintSequence length (aa)
statusstringDesign status (e.g. passed, failed)
failure_reasonstringReason for failure if applicable
final_rankintRank among designs in the run
quality_scorefloatOverall quality score
design_to_target_iptmfloatipTM score for binder–target interface
metricsobjectUnmapped CSV columns, kept as-is
cif_pathstringRelative path to the binder's CIF structure file

Production Deployment

The production stack is nginx → gunicorn → Django with a separately served React build.

Assumed install path: /opt/binder/binder-base/

1. Build the frontend

cd binder_frontend
npm install
npm run build # outputs to binder_frontend/dist/

2. Configure the backend environment

Create binder_backend/binder_backend/.env:

SECRET_KEY=<strong-random-key>
DEBUG=False
MEDIA_ROOT=/opt/binder/files/
DATABASE_ENGINE=django.db.backends.postgresql
DATABASE_NAME=binderbase
DATABASE_USER=<db-user>
DATABASE_PASSWORD=<db-password>
DATABASE_HOST=localhost
DATABASE_PORT=5432
CSRF_TRUSTED_ORIGINS=https://your-domain.example.com
DEFAULT_FROM_EMAIL=noreply@your-domain.example.com

3. Collect static files and migrate

cd binder_backend
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input

4. Run gunicorn

cd binder_backend
gunicorn binder_backend.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4

Use a systemd service or supervisor to keep gunicorn running.

5. Configure nginx

Copy nginx.conf from the repo root to /etc/nginx/sites-available/binderbase (adjust server_name), then enable it:

ln -s /etc/nginx/sites-available/binderbase /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

The config serves:

  • / — React SPA from binder_frontend/dist/
  • /api/, /admin/ — proxied to gunicorn on port 8000
  • /media/ — files from MEDIA_ROOT (/opt/binder/files/)
  • /static/ — Django admin static files from binder_backend/static/

6. Create a superuser

cd binder_backend
python manage.py createsuperuser

Django admin is at https://your-domain.example.com/admin/.

7. Deploying an update

To ship new code to a running deployment, pull the changes, rebuild the frontend, apply backend changes, and restart gunicorn. Run from the repo root (/opt/binder/binder-base/):

git pull
# Frontend — reinstall deps and rebuild the SPAcd binder_frontend
npm install
npm run build
# Backend — update dependencies, apply database schema migrations and collect static files if neededcd ../binder_backend
source ../../venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input
# Restart the app server (adjust to your service name)
sudo systemctl restart gunicorn

nginx serves the new binder_frontend/dist/ build and static files directly, so no nginx reload is needed unless you changed nginx.conf.


Backup

Backup the database

pg_dump -U <DB_USER> -d <DB_NAME> -f binderbase-$(date +%Y%m%d).sql

Backing up the media files

MEDIA_ROOT holds every downloaded AlphaFold structure and every imported run directory. Snapshot it alongside the database dump.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - EpiGenomicsCode/binder-base · GitHub
Skip to content

Repository files navigation

Binder Base

Django + React application for tracking protein binder design experiments.


Table of Contents

  1. Development Quick Start
  2. Admin Dashboard
  3. Run Directory Convention & Import Command
  4. API Endpoints
  5. Production Deployment
  6. Backup

Development Quick Start

Prerequisites

  • Python 3.11+
  • Node.js 18+
  • PostgreSQL

Backend

cd binder_backend
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp binder_backend/.env.example binder_backend/.env
# Edit .env — set SECRET_KEY, DATABASE_*, MEDIA_ROOT, CSRF_TRUSTED_ORIGINS# Apply migrations and run
python manage.py migrate
python manage.py runserver

Frontend

cd binder_frontend
npm install
npm run dev # dev server at http://localhost:5173

The home page is served at http://localhost:5173.


Admin Dashboard

The Django admin interface is available at http://127.0.0.1:8000/admin. Log in with a superuser account (create one with python manage.py createsuperuser).

The following models are registered:

ModelList columnsFilters
ProteinUniProt ID, gene name, protein name, organism, lengthOrganism
BinderRunProtein, algorithm, run date, hardware, description, run dir, userProtein, algorithm, hardware, user
BinderRun, length, rank, quality score, ipTM, statusRun

Run Directory Convention & Import Command

The import_run command scans MEDIA_ROOT/runs/ for run directories not yet in the database and imports each one. This section documents exactly how a run directory is read so you can lay one out correctly.

Directory naming

Each run directory lives directly under MEDIA_ROOT/runs/ and is named:

<UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD>
PartMeaningExample
UNIPROT_IDLeading run of uppercase letters/digits. No lowercase, no hyphens.P12345
RUN_LABELFree-form label for the run. May itself contain hyphens — the date is anchored to the end of the name.tal1_screen
YYYYMMDDTrailing 8 digits.20260501

The directory name is validated against ^([A-Z0-9]+)-(.+)-(\d{8})$ and skips directories that don't match. If the UniProt ID or run date is different between the run directory name and the meta.json file (see below), a warning will be logged during importation. However, the importation will proceed with every imported value coming from meta.json.

Required directory structure

MEDIA_ROOT/runs/
P12345-tal1_screen-20260501/
meta.json # REQUIRED — run identity + metadata (see below)
*.cif # target structure — exactly one .cif in the run root
steps.yaml # optional pipeline config (see below)
config/ # optional configs referenced from steps.yaml
final_ranked_designs/ # REQUIRED
*.csv # binder table — first *.csv in this folder is used
*/rank001_<file_name> # per-design CIF files, matched by rank + filename

Target structure CIF (run root). The importer globs *.cif in the run directory root and uses it as the run's target structure, parsing the target sequence from its _entity_poly record. Keep exactly one.cif here:

  • None → the run still imports, but with no target structure and no target sequence.
  • More than one → the first match in glob order is used (order is not guaranteed), so the chosen file is effectively arbitrary. Always keep a single copy.

steps.yaml (optional). If present and valid, it is stored as the run's steps_config. Any nested config / config_file / config_path string values are replaced inline with the parsed contents of the referenced file (YAML or JSON), resolved relative to the run directory — this is what the config/ folder is for. A missing or unparseable steps.yaml is stored as null and does not stop the import.

meta.json (required). A JSON file in the run root that supplies the run's identity. Six keys are lifted into database columns; every other key is stored in the run's metadata field.

KeyDB fieldRequiredAccepted values
UniProt IDresolves/creates the ProteinYesAny non-empty string
Run daterun_datetimeNoYYYY-MM-DD, YYYY/MM/DD, YYYYMMDD, or a full ISO 8601 timestamp
AlgorithmalgorithmNoAny scalar (strings, numbers)
HardwarehardwareNoAny scalar (strings, numbers)
DescriptiondescriptionNoAny scalar (strings, numbers)
NotesnotesNoAny scalar (strings, numbers)
{
"UniProt ID": "P12345",
"Run date": "2026-05-01",
"Algorithm": "XXX v1.0",
"Hardware": "1x A100 80GB",
"Description": "TAL1-E2 binder screen",
"Notes": "rerun of the April batch with relaxed filters",
"Domain": "TAL1-E2",
"Hotspot residues": "31, 33, 38, 54, 58, 61, 63, 67, 68"
}

Here Domain and Hotspot residues become the run's metadata.

Key matching ignores case and any spaces, underscores or hyphens, so UniProt ID, uniprot_id and UNIPROT-ID are equivalent. Matching is on the whole key, so near-misses like Hardware config or Run dates are not absorbed — they stay in metadata.

If a recognized key's value can't be used — an unparseable Run date, an object where a scalar Algorithm was expected — the column is left null and the raw entry falls through to metadata, so nothing in the file is ever silently lost.

Note: import_run skips run directories it has already imported, so editing a meta.json afterwards will not update the corresponding run. Use import_meta_json to push those edits into runs that are already in the database.

final_ranked_designs/ (required). Must exist, or the directory is skipped (see below). The first*.csv in this folder is read as the binder table; per-design CIF files are located by globbing rank*_<file_name> recursively and matching the filename rank{final_rank}_{file_name} (the rank may be zero-padded to any width, e.g. rank1_, rank01_, rank001_).

The designs CSV

The CSV must contain at minimum a sequence column (rows without it are skipped). Recognized columns and their mapping to database fields:

CSV columnDB field
sequencebinder_sequence (its length → binder_length)
final_rankfinal_rank
quality_scorequality_score
design_to_target_iptmdesign_to_target_iptm
pass_filters (TRUE/FALSE)status (success / failed)
pass_*_filter (FALSE)contributes to failure_reason (which filters failed)
file_nameused (with final_rank) to locate the per-design CIF

Any additional columns are stored in the metrics JSON field.

When directories are skipped

A directory is skipped (logged, and the command continues to the next one) when:

  • It is already imported — its path matches an existing BinderRun.run_dir.
  • Its name does not match the <UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD> pattern.
  • It has no final_ranked_designs/ folder.
  • Its meta.json is missing, unparseable, or not a JSON object.
  • Its meta.json has no usable UniProt ID — without it the Protein can't be resolved.

BinderRun.run_dir also carries a database-level unique constraint, so a duplicate run cannot be created even if the in-memory check is bypassed. Two consequences:

Renaming a run directory makes it import again under the new path, producing a second BinderRun (and a second set of Binder rows) while the original row keeps pointing at a path that no longer exists. Renaming is not a supported way to re-import.

Running the import command

From binder_backend/:

python manage.py import_run

For each new run directory the command:

  1. Reads meta.json, taking UniProt ID, Run date, Algorithm, Hardware, Description and Notes from it and keeping the remaining keys as the run's metadata, then cross-checks the UniProt ID and date against the directory name. Note that if a discrepancy is detected, only a warning will be logged, and the import process will continue.
  2. Resolves the Protein by that UniProt ID. If it already exists it is reused; otherwise the AlphaFold API (sequence, gene name, organism, structure CIF, PAE JSON) and UniProt API (biological function) are queried, the CIF and PAE JSON are downloaded to MEDIA_ROOT/proteins/{uniprot_id}/, and the Protein is created.
  3. Creates a BinderRun (algorithm, date, target CIF path, target sequence, steps_config, metadata).
  4. Bulk-creates Binder records from the CSV, linking each to its per-design CIF.

A summary line reports the number of runs imported and skipped.

Re-reading meta.json for existing runs

import_run only ever looks at directories it hasn't imported, so editing a meta.json afterwards has no effect. To push those edits into runs that are already in the database, use import_meta_json:

python manage.py import_meta_json # every run in the database
python manage.py import_meta_json P12345-tal1_screen-20260501 # just this one
python manage.py import_meta_json --dry-run # preview, write nothing

This is a full resync, not a merge. The six meta.json-owned fields are overwritten from the file, and a key that is absent from the file sets its column back to null — so the database always mirrors the current contents of meta.json.

Because it can clear fields in bulk, run it with --dry-run first. Output is a per-field old -> new diff:

P12345-tal1_screen-20260501
algorithm OLD v0.1 -> XXX v1.0
hardware old hardware -> None
metadata {'stale': 'yes'} -> {'Domain': 'TAL1-E2'}
Done. updated: 1 unchanged: 0 failed: 0

Scope. The command never creates or deletes runs and never touches binders — it only updates the fields above on runs that already exist. Directories on disk that have never been imported are ignored; use import_run for those. Files other than meta.json (steps.yaml, the CIFs, the designs CSV) are not re-read either.

If UniProt ID changed, the run is reassigned to that protein and a warning is logged.

Failures leave data untouched. A run is reported and skipped without any change when its meta.json is missing, unparseable or not a JSON object, when it has no usable UniProt ID, or when the run directory itself is gone. A broken file is treated as a problem to fix, never as an instruction to clear every column.


API Endpoints

All endpoints are mounted at /api/. Interactive docs (OpenAPI/Swagger UI) are available at /api/docs.

GET /api/stats

Returns database-wide totals, for dashboard summary tiles.

Response:

FieldTypeDescription
protein_countintTotal proteins
run_countintTotal binder runs
binder_countintTotal binders across all runs
success_countintBinders with status exactly success

GET /api/proteins

Returns a list of all proteins.

Response — array of:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file

GET /api/proteins/{id}

Returns full detail for a single protein, including all binder runs and their ranked designs.

Response:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file
sequencestringFull amino acid sequence
created_atdatetimeRecord creation timestamp
updated_atdatetimeLast update timestamp
runsarrayList of BinderRun objects (see below)

BinderRun object:

FieldTypeDescription
idintDatabase ID
algorithmstringAlgorithm name and version, from the Algorithm key of meta.json
descriptionstringOptional description, from the Description key of meta.json
run_datetimedatetime | nullDate/time of the run, from the Run date key of meta.json. Null when that key is absent or unparseable
hardwarestringHardware used, from the Hardware key of meta.json
notesstringFree-text notes, from the Notes key of meta.json
run_dirstringRelative path to run directory under MEDIA_ROOT
cif_pathstringRelative path to the target structure CIF file
target_sequencestringTarget sequence parsed from the run's CIF file
steps_configobject | arrayParsed steps.yaml, with referenced config files inlined
metadataobjectRemaining keys of meta.json, after the six lifted keys are taken out (null if none)
userstringUsername who imported the run
binder_countintNumber of binders in the run

Binders are not inlined in this response — a protein with several runs of a few thousand designs each made it tens of MB. Fetch them a page at a time from GET /api/runs/{run_id}/binders instead.


GET /api/runs/{run_id}/binders

Returns one page of a run's binders.

Query parameters:

ParameterTypeDefaultDescription
pageint11-based page number
page_sizeint20Results per page (capped at 200)
sortstringrankOne of rank, quality, iptm, length, status
sort_dirstringascasc or desc; missing values always sort last
statusstringallall, or a status to filter by (case-insensitive)

Response:

FieldTypeDescription
itemsarrayList of Binder objects (see below)
totalintTotal binders matching the filter, across all pages
metric_keysarrayMetric column names available for this run

metric_keys is sampled from the leading rows of the run rather than scanned across every row, since binders within a run come from the same pipeline and carry the same keys.

Binder object:

FieldTypeDescription
idintDatabase ID
binder_sequencestringAmino acid sequence of the designed binder
binder_lengthintSequence length (aa)
statusstringDesign status (e.g. passed, failed)
failure_reasonstringReason for failure if applicable
final_rankintRank among designs in the run
quality_scorefloatOverall quality score
design_to_target_iptmfloatipTM score for binder–target interface
metricsobjectUnmapped CSV columns, kept as-is
cif_pathstringRelative path to the binder's CIF structure file

Production Deployment

The production stack is nginx → gunicorn → Django with a separately served React build.

Assumed install path: /opt/binder/binder-base/

1. Build the frontend

cd binder_frontend
npm install
npm run build # outputs to binder_frontend/dist/

2. Configure the backend environment

Create binder_backend/binder_backend/.env:

SECRET_KEY=<strong-random-key>
DEBUG=False
MEDIA_ROOT=/opt/binder/files/
DATABASE_ENGINE=django.db.backends.postgresql
DATABASE_NAME=binderbase
DATABASE_USER=<db-user>
DATABASE_PASSWORD=<db-password>
DATABASE_HOST=localhost
DATABASE_PORT=5432
CSRF_TRUSTED_ORIGINS=https://your-domain.example.com
DEFAULT_FROM_EMAIL=noreply@your-domain.example.com

3. Collect static files and migrate

cd binder_backend
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input

4. Run gunicorn

cd binder_backend
gunicorn binder_backend.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4

Use a systemd service or supervisor to keep gunicorn running.

5. Configure nginx

Copy nginx.conf from the repo root to /etc/nginx/sites-available/binderbase (adjust server_name), then enable it:

ln -s /etc/nginx/sites-available/binderbase /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

The config serves:

  • / — React SPA from binder_frontend/dist/
  • /api/, /admin/ — proxied to gunicorn on port 8000
  • /media/ — files from MEDIA_ROOT (/opt/binder/files/)
  • /static/ — Django admin static files from binder_backend/static/

6. Create a superuser

cd binder_backend
python manage.py createsuperuser

Django admin is at https://your-domain.example.com/admin/.

7. Deploying an update

To ship new code to a running deployment, pull the changes, rebuild the frontend, apply backend changes, and restart gunicorn. Run from the repo root (/opt/binder/binder-base/):

git pull
# Frontend — reinstall deps and rebuild the SPAcd binder_frontend
npm install
npm run build
# Backend — update dependencies, apply database schema migrations and collect static files if neededcd ../binder_backend
source ../../venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input
# Restart the app server (adjust to your service name)
sudo systemctl restart gunicorn

nginx serves the new binder_frontend/dist/ build and static files directly, so no nginx reload is needed unless you changed nginx.conf.


Backup

Backup the database

pg_dump -U <DB_USER> -d <DB_NAME> -f binderbase-$(date +%Y%m%d).sql

Backing up the media files

MEDIA_ROOT holds every downloaded AlphaFold structure and every imported run directory. Snapshot it alongside the database dump.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - EpiGenomicsCode/binder-base · GitHub
Skip to content

Repository files navigation

Binder Base

Django + React application for tracking protein binder design experiments.


Table of Contents

  1. Development Quick Start
  2. Admin Dashboard
  3. Run Directory Convention & Import Command
  4. API Endpoints
  5. Production Deployment
  6. Backup

Development Quick Start

Prerequisites

  • Python 3.11+
  • Node.js 18+
  • PostgreSQL

Backend

cd binder_backend
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp binder_backend/.env.example binder_backend/.env
# Edit .env — set SECRET_KEY, DATABASE_*, MEDIA_ROOT, CSRF_TRUSTED_ORIGINS# Apply migrations and run
python manage.py migrate
python manage.py runserver

Frontend

cd binder_frontend
npm install
npm run dev # dev server at http://localhost:5173

The home page is served at http://localhost:5173.


Admin Dashboard

The Django admin interface is available at http://127.0.0.1:8000/admin. Log in with a superuser account (create one with python manage.py createsuperuser).

The following models are registered:

ModelList columnsFilters
ProteinUniProt ID, gene name, protein name, organism, lengthOrganism
BinderRunProtein, algorithm, run date, hardware, description, run dir, userProtein, algorithm, hardware, user
BinderRun, length, rank, quality score, ipTM, statusRun

Run Directory Convention & Import Command

The import_run command scans MEDIA_ROOT/runs/ for run directories not yet in the database and imports each one. This section documents exactly how a run directory is read so you can lay one out correctly.

Directory naming

Each run directory lives directly under MEDIA_ROOT/runs/ and is named:

<UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD>
PartMeaningExample
UNIPROT_IDLeading run of uppercase letters/digits. No lowercase, no hyphens.P12345
RUN_LABELFree-form label for the run. May itself contain hyphens — the date is anchored to the end of the name.tal1_screen
YYYYMMDDTrailing 8 digits.20260501

The directory name is validated against ^([A-Z0-9]+)-(.+)-(\d{8})$ and skips directories that don't match. If the UniProt ID or run date is different between the run directory name and the meta.json file (see below), a warning will be logged during importation. However, the importation will proceed with every imported value coming from meta.json.

Required directory structure

MEDIA_ROOT/runs/
P12345-tal1_screen-20260501/
meta.json # REQUIRED — run identity + metadata (see below)
*.cif # target structure — exactly one .cif in the run root
steps.yaml # optional pipeline config (see below)
config/ # optional configs referenced from steps.yaml
final_ranked_designs/ # REQUIRED
*.csv # binder table — first *.csv in this folder is used
*/rank001_<file_name> # per-design CIF files, matched by rank + filename

Target structure CIF (run root). The importer globs *.cif in the run directory root and uses it as the run's target structure, parsing the target sequence from its _entity_poly record. Keep exactly one.cif here:

  • None → the run still imports, but with no target structure and no target sequence.
  • More than one → the first match in glob order is used (order is not guaranteed), so the chosen file is effectively arbitrary. Always keep a single copy.

steps.yaml (optional). If present and valid, it is stored as the run's steps_config. Any nested config / config_file / config_path string values are replaced inline with the parsed contents of the referenced file (YAML or JSON), resolved relative to the run directory — this is what the config/ folder is for. A missing or unparseable steps.yaml is stored as null and does not stop the import.

meta.json (required). A JSON file in the run root that supplies the run's identity. Six keys are lifted into database columns; every other key is stored in the run's metadata field.

KeyDB fieldRequiredAccepted values
UniProt IDresolves/creates the ProteinYesAny non-empty string
Run daterun_datetimeNoYYYY-MM-DD, YYYY/MM/DD, YYYYMMDD, or a full ISO 8601 timestamp
AlgorithmalgorithmNoAny scalar (strings, numbers)
HardwarehardwareNoAny scalar (strings, numbers)
DescriptiondescriptionNoAny scalar (strings, numbers)
NotesnotesNoAny scalar (strings, numbers)
{
"UniProt ID": "P12345",
"Run date": "2026-05-01",
"Algorithm": "XXX v1.0",
"Hardware": "1x A100 80GB",
"Description": "TAL1-E2 binder screen",
"Notes": "rerun of the April batch with relaxed filters",
"Domain": "TAL1-E2",
"Hotspot residues": "31, 33, 38, 54, 58, 61, 63, 67, 68"
}

Here Domain and Hotspot residues become the run's metadata.

Key matching ignores case and any spaces, underscores or hyphens, so UniProt ID, uniprot_id and UNIPROT-ID are equivalent. Matching is on the whole key, so near-misses like Hardware config or Run dates are not absorbed — they stay in metadata.

If a recognized key's value can't be used — an unparseable Run date, an object where a scalar Algorithm was expected — the column is left null and the raw entry falls through to metadata, so nothing in the file is ever silently lost.

Note: import_run skips run directories it has already imported, so editing a meta.json afterwards will not update the corresponding run. Use import_meta_json to push those edits into runs that are already in the database.

final_ranked_designs/ (required). Must exist, or the directory is skipped (see below). The first*.csv in this folder is read as the binder table; per-design CIF files are located by globbing rank*_<file_name> recursively and matching the filename rank{final_rank}_{file_name} (the rank may be zero-padded to any width, e.g. rank1_, rank01_, rank001_).

The designs CSV

The CSV must contain at minimum a sequence column (rows without it are skipped). Recognized columns and their mapping to database fields:

CSV columnDB field
sequencebinder_sequence (its length → binder_length)
final_rankfinal_rank
quality_scorequality_score
design_to_target_iptmdesign_to_target_iptm
pass_filters (TRUE/FALSE)status (success / failed)
pass_*_filter (FALSE)contributes to failure_reason (which filters failed)
file_nameused (with final_rank) to locate the per-design CIF

Any additional columns are stored in the metrics JSON field.

When directories are skipped

A directory is skipped (logged, and the command continues to the next one) when:

  • It is already imported — its path matches an existing BinderRun.run_dir.
  • Its name does not match the <UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD> pattern.
  • It has no final_ranked_designs/ folder.
  • Its meta.json is missing, unparseable, or not a JSON object.
  • Its meta.json has no usable UniProt ID — without it the Protein can't be resolved.

BinderRun.run_dir also carries a database-level unique constraint, so a duplicate run cannot be created even if the in-memory check is bypassed. Two consequences:

Renaming a run directory makes it import again under the new path, producing a second BinderRun (and a second set of Binder rows) while the original row keeps pointing at a path that no longer exists. Renaming is not a supported way to re-import.

Running the import command

From binder_backend/:

python manage.py import_run

For each new run directory the command:

  1. Reads meta.json, taking UniProt ID, Run date, Algorithm, Hardware, Description and Notes from it and keeping the remaining keys as the run's metadata, then cross-checks the UniProt ID and date against the directory name. Note that if a discrepancy is detected, only a warning will be logged, and the import process will continue.
  2. Resolves the Protein by that UniProt ID. If it already exists it is reused; otherwise the AlphaFold API (sequence, gene name, organism, structure CIF, PAE JSON) and UniProt API (biological function) are queried, the CIF and PAE JSON are downloaded to MEDIA_ROOT/proteins/{uniprot_id}/, and the Protein is created.
  3. Creates a BinderRun (algorithm, date, target CIF path, target sequence, steps_config, metadata).
  4. Bulk-creates Binder records from the CSV, linking each to its per-design CIF.

A summary line reports the number of runs imported and skipped.

Re-reading meta.json for existing runs

import_run only ever looks at directories it hasn't imported, so editing a meta.json afterwards has no effect. To push those edits into runs that are already in the database, use import_meta_json:

python manage.py import_meta_json # every run in the database
python manage.py import_meta_json P12345-tal1_screen-20260501 # just this one
python manage.py import_meta_json --dry-run # preview, write nothing

This is a full resync, not a merge. The six meta.json-owned fields are overwritten from the file, and a key that is absent from the file sets its column back to null — so the database always mirrors the current contents of meta.json.

Because it can clear fields in bulk, run it with --dry-run first. Output is a per-field old -> new diff:

P12345-tal1_screen-20260501
algorithm OLD v0.1 -> XXX v1.0
hardware old hardware -> None
metadata {'stale': 'yes'} -> {'Domain': 'TAL1-E2'}
Done. updated: 1 unchanged: 0 failed: 0

Scope. The command never creates or deletes runs and never touches binders — it only updates the fields above on runs that already exist. Directories on disk that have never been imported are ignored; use import_run for those. Files other than meta.json (steps.yaml, the CIFs, the designs CSV) are not re-read either.

If UniProt ID changed, the run is reassigned to that protein and a warning is logged.

Failures leave data untouched. A run is reported and skipped without any change when its meta.json is missing, unparseable or not a JSON object, when it has no usable UniProt ID, or when the run directory itself is gone. A broken file is treated as a problem to fix, never as an instruction to clear every column.


API Endpoints

All endpoints are mounted at /api/. Interactive docs (OpenAPI/Swagger UI) are available at /api/docs.

GET /api/stats

Returns database-wide totals, for dashboard summary tiles.

Response:

FieldTypeDescription
protein_countintTotal proteins
run_countintTotal binder runs
binder_countintTotal binders across all runs
success_countintBinders with status exactly success

GET /api/proteins

Returns a list of all proteins.

Response — array of:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file

GET /api/proteins/{id}

Returns full detail for a single protein, including all binder runs and their ranked designs.

Response:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file
sequencestringFull amino acid sequence
created_atdatetimeRecord creation timestamp
updated_atdatetimeLast update timestamp
runsarrayList of BinderRun objects (see below)

BinderRun object:

FieldTypeDescription
idintDatabase ID
algorithmstringAlgorithm name and version, from the Algorithm key of meta.json
descriptionstringOptional description, from the Description key of meta.json
run_datetimedatetime | nullDate/time of the run, from the Run date key of meta.json. Null when that key is absent or unparseable
hardwarestringHardware used, from the Hardware key of meta.json
notesstringFree-text notes, from the Notes key of meta.json
run_dirstringRelative path to run directory under MEDIA_ROOT
cif_pathstringRelative path to the target structure CIF file
target_sequencestringTarget sequence parsed from the run's CIF file
steps_configobject | arrayParsed steps.yaml, with referenced config files inlined
metadataobjectRemaining keys of meta.json, after the six lifted keys are taken out (null if none)
userstringUsername who imported the run
binder_countintNumber of binders in the run

Binders are not inlined in this response — a protein with several runs of a few thousand designs each made it tens of MB. Fetch them a page at a time from GET /api/runs/{run_id}/binders instead.


GET /api/runs/{run_id}/binders

Returns one page of a run's binders.

Query parameters:

ParameterTypeDefaultDescription
pageint11-based page number
page_sizeint20Results per page (capped at 200)
sortstringrankOne of rank, quality, iptm, length, status
sort_dirstringascasc or desc; missing values always sort last
statusstringallall, or a status to filter by (case-insensitive)

Response:

FieldTypeDescription
itemsarrayList of Binder objects (see below)
totalintTotal binders matching the filter, across all pages
metric_keysarrayMetric column names available for this run

metric_keys is sampled from the leading rows of the run rather than scanned across every row, since binders within a run come from the same pipeline and carry the same keys.

Binder object:

FieldTypeDescription
idintDatabase ID
binder_sequencestringAmino acid sequence of the designed binder
binder_lengthintSequence length (aa)
statusstringDesign status (e.g. passed, failed)
failure_reasonstringReason for failure if applicable
final_rankintRank among designs in the run
quality_scorefloatOverall quality score
design_to_target_iptmfloatipTM score for binder–target interface
metricsobjectUnmapped CSV columns, kept as-is
cif_pathstringRelative path to the binder's CIF structure file

Production Deployment

The production stack is nginx → gunicorn → Django with a separately served React build.

Assumed install path: /opt/binder/binder-base/

1. Build the frontend

cd binder_frontend
npm install
npm run build # outputs to binder_frontend/dist/

2. Configure the backend environment

Create binder_backend/binder_backend/.env:

SECRET_KEY=<strong-random-key>
DEBUG=False
MEDIA_ROOT=/opt/binder/files/
DATABASE_ENGINE=django.db.backends.postgresql
DATABASE_NAME=binderbase
DATABASE_USER=<db-user>
DATABASE_PASSWORD=<db-password>
DATABASE_HOST=localhost
DATABASE_PORT=5432
CSRF_TRUSTED_ORIGINS=https://your-domain.example.com
DEFAULT_FROM_EMAIL=noreply@your-domain.example.com

3. Collect static files and migrate

cd binder_backend
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input

4. Run gunicorn

cd binder_backend
gunicorn binder_backend.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4

Use a systemd service or supervisor to keep gunicorn running.

5. Configure nginx

Copy nginx.conf from the repo root to /etc/nginx/sites-available/binderbase (adjust server_name), then enable it:

ln -s /etc/nginx/sites-available/binderbase /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

The config serves:

  • / — React SPA from binder_frontend/dist/
  • /api/, /admin/ — proxied to gunicorn on port 8000
  • /media/ — files from MEDIA_ROOT (/opt/binder/files/)
  • /static/ — Django admin static files from binder_backend/static/

6. Create a superuser

cd binder_backend
python manage.py createsuperuser

Django admin is at https://your-domain.example.com/admin/.

7. Deploying an update

To ship new code to a running deployment, pull the changes, rebuild the frontend, apply backend changes, and restart gunicorn. Run from the repo root (/opt/binder/binder-base/):

git pull
# Frontend — reinstall deps and rebuild the SPAcd binder_frontend
npm install
npm run build
# Backend — update dependencies, apply database schema migrations and collect static files if neededcd ../binder_backend
source ../../venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input
# Restart the app server (adjust to your service name)
sudo systemctl restart gunicorn

nginx serves the new binder_frontend/dist/ build and static files directly, so no nginx reload is needed unless you changed nginx.conf.


Backup

Backup the database

pg_dump -U <DB_USER> -d <DB_NAME> -f binderbase-$(date +%Y%m%d).sql

Backing up the media files

MEDIA_ROOT holds every downloaded AlphaFold structure and every imported run directory. Snapshot it alongside the database dump.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - EpiGenomicsCode/binder-base · GitHub
Skip to content

Repository files navigation

Binder Base

Django + React application for tracking protein binder design experiments.


Table of Contents

  1. Development Quick Start
  2. Admin Dashboard
  3. Run Directory Convention & Import Command
  4. API Endpoints
  5. Production Deployment
  6. Backup

Development Quick Start

Prerequisites

  • Python 3.11+
  • Node.js 18+
  • PostgreSQL

Backend

cd binder_backend
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp binder_backend/.env.example binder_backend/.env
# Edit .env — set SECRET_KEY, DATABASE_*, MEDIA_ROOT, CSRF_TRUSTED_ORIGINS# Apply migrations and run
python manage.py migrate
python manage.py runserver

Frontend

cd binder_frontend
npm install
npm run dev # dev server at http://localhost:5173

The home page is served at http://localhost:5173.


Admin Dashboard

The Django admin interface is available at http://127.0.0.1:8000/admin. Log in with a superuser account (create one with python manage.py createsuperuser).

The following models are registered:

ModelList columnsFilters
ProteinUniProt ID, gene name, protein name, organism, lengthOrganism
BinderRunProtein, algorithm, run date, hardware, description, run dir, userProtein, algorithm, hardware, user
BinderRun, length, rank, quality score, ipTM, statusRun

Run Directory Convention & Import Command

The import_run command scans MEDIA_ROOT/runs/ for run directories not yet in the database and imports each one. This section documents exactly how a run directory is read so you can lay one out correctly.

Directory naming

Each run directory lives directly under MEDIA_ROOT/runs/ and is named:

<UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD>
PartMeaningExample
UNIPROT_IDLeading run of uppercase letters/digits. No lowercase, no hyphens.P12345
RUN_LABELFree-form label for the run. May itself contain hyphens — the date is anchored to the end of the name.tal1_screen
YYYYMMDDTrailing 8 digits.20260501

The directory name is validated against ^([A-Z0-9]+)-(.+)-(\d{8})$ and skips directories that don't match. If the UniProt ID or run date is different between the run directory name and the meta.json file (see below), a warning will be logged during importation. However, the importation will proceed with every imported value coming from meta.json.

Required directory structure

MEDIA_ROOT/runs/
P12345-tal1_screen-20260501/
meta.json # REQUIRED — run identity + metadata (see below)
*.cif # target structure — exactly one .cif in the run root
steps.yaml # optional pipeline config (see below)
config/ # optional configs referenced from steps.yaml
final_ranked_designs/ # REQUIRED
*.csv # binder table — first *.csv in this folder is used
*/rank001_<file_name> # per-design CIF files, matched by rank + filename

Target structure CIF (run root). The importer globs *.cif in the run directory root and uses it as the run's target structure, parsing the target sequence from its _entity_poly record. Keep exactly one.cif here:

  • None → the run still imports, but with no target structure and no target sequence.
  • More than one → the first match in glob order is used (order is not guaranteed), so the chosen file is effectively arbitrary. Always keep a single copy.

steps.yaml (optional). If present and valid, it is stored as the run's steps_config. Any nested config / config_file / config_path string values are replaced inline with the parsed contents of the referenced file (YAML or JSON), resolved relative to the run directory — this is what the config/ folder is for. A missing or unparseable steps.yaml is stored as null and does not stop the import.

meta.json (required). A JSON file in the run root that supplies the run's identity. Six keys are lifted into database columns; every other key is stored in the run's metadata field.

KeyDB fieldRequiredAccepted values
UniProt IDresolves/creates the ProteinYesAny non-empty string
Run daterun_datetimeNoYYYY-MM-DD, YYYY/MM/DD, YYYYMMDD, or a full ISO 8601 timestamp
AlgorithmalgorithmNoAny scalar (strings, numbers)
HardwarehardwareNoAny scalar (strings, numbers)
DescriptiondescriptionNoAny scalar (strings, numbers)
NotesnotesNoAny scalar (strings, numbers)
{
"UniProt ID": "P12345",
"Run date": "2026-05-01",
"Algorithm": "XXX v1.0",
"Hardware": "1x A100 80GB",
"Description": "TAL1-E2 binder screen",
"Notes": "rerun of the April batch with relaxed filters",
"Domain": "TAL1-E2",
"Hotspot residues": "31, 33, 38, 54, 58, 61, 63, 67, 68"
}

Here Domain and Hotspot residues become the run's metadata.

Key matching ignores case and any spaces, underscores or hyphens, so UniProt ID, uniprot_id and UNIPROT-ID are equivalent. Matching is on the whole key, so near-misses like Hardware config or Run dates are not absorbed — they stay in metadata.

If a recognized key's value can't be used — an unparseable Run date, an object where a scalar Algorithm was expected — the column is left null and the raw entry falls through to metadata, so nothing in the file is ever silently lost.

Note: import_run skips run directories it has already imported, so editing a meta.json afterwards will not update the corresponding run. Use import_meta_json to push those edits into runs that are already in the database.

final_ranked_designs/ (required). Must exist, or the directory is skipped (see below). The first*.csv in this folder is read as the binder table; per-design CIF files are located by globbing rank*_<file_name> recursively and matching the filename rank{final_rank}_{file_name} (the rank may be zero-padded to any width, e.g. rank1_, rank01_, rank001_).

The designs CSV

The CSV must contain at minimum a sequence column (rows without it are skipped). Recognized columns and their mapping to database fields:

CSV columnDB field
sequencebinder_sequence (its length → binder_length)
final_rankfinal_rank
quality_scorequality_score
design_to_target_iptmdesign_to_target_iptm
pass_filters (TRUE/FALSE)status (success / failed)
pass_*_filter (FALSE)contributes to failure_reason (which filters failed)
file_nameused (with final_rank) to locate the per-design CIF

Any additional columns are stored in the metrics JSON field.

When directories are skipped

A directory is skipped (logged, and the command continues to the next one) when:

  • It is already imported — its path matches an existing BinderRun.run_dir.
  • Its name does not match the <UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD> pattern.
  • It has no final_ranked_designs/ folder.
  • Its meta.json is missing, unparseable, or not a JSON object.
  • Its meta.json has no usable UniProt ID — without it the Protein can't be resolved.

BinderRun.run_dir also carries a database-level unique constraint, so a duplicate run cannot be created even if the in-memory check is bypassed. Two consequences:

Renaming a run directory makes it import again under the new path, producing a second BinderRun (and a second set of Binder rows) while the original row keeps pointing at a path that no longer exists. Renaming is not a supported way to re-import.

Running the import command

From binder_backend/:

python manage.py import_run

For each new run directory the command:

  1. Reads meta.json, taking UniProt ID, Run date, Algorithm, Hardware, Description and Notes from it and keeping the remaining keys as the run's metadata, then cross-checks the UniProt ID and date against the directory name. Note that if a discrepancy is detected, only a warning will be logged, and the import process will continue.
  2. Resolves the Protein by that UniProt ID. If it already exists it is reused; otherwise the AlphaFold API (sequence, gene name, organism, structure CIF, PAE JSON) and UniProt API (biological function) are queried, the CIF and PAE JSON are downloaded to MEDIA_ROOT/proteins/{uniprot_id}/, and the Protein is created.
  3. Creates a BinderRun (algorithm, date, target CIF path, target sequence, steps_config, metadata).
  4. Bulk-creates Binder records from the CSV, linking each to its per-design CIF.

A summary line reports the number of runs imported and skipped.

Re-reading meta.json for existing runs

import_run only ever looks at directories it hasn't imported, so editing a meta.json afterwards has no effect. To push those edits into runs that are already in the database, use import_meta_json:

python manage.py import_meta_json # every run in the database
python manage.py import_meta_json P12345-tal1_screen-20260501 # just this one
python manage.py import_meta_json --dry-run # preview, write nothing

This is a full resync, not a merge. The six meta.json-owned fields are overwritten from the file, and a key that is absent from the file sets its column back to null — so the database always mirrors the current contents of meta.json.

Because it can clear fields in bulk, run it with --dry-run first. Output is a per-field old -> new diff:

P12345-tal1_screen-20260501
algorithm OLD v0.1 -> XXX v1.0
hardware old hardware -> None
metadata {'stale': 'yes'} -> {'Domain': 'TAL1-E2'}
Done. updated: 1 unchanged: 0 failed: 0

Scope. The command never creates or deletes runs and never touches binders — it only updates the fields above on runs that already exist. Directories on disk that have never been imported are ignored; use import_run for those. Files other than meta.json (steps.yaml, the CIFs, the designs CSV) are not re-read either.

If UniProt ID changed, the run is reassigned to that protein and a warning is logged.

Failures leave data untouched. A run is reported and skipped without any change when its meta.json is missing, unparseable or not a JSON object, when it has no usable UniProt ID, or when the run directory itself is gone. A broken file is treated as a problem to fix, never as an instruction to clear every column.


API Endpoints

All endpoints are mounted at /api/. Interactive docs (OpenAPI/Swagger UI) are available at /api/docs.

GET /api/stats

Returns database-wide totals, for dashboard summary tiles.

Response:

FieldTypeDescription
protein_countintTotal proteins
run_countintTotal binder runs
binder_countintTotal binders across all runs
success_countintBinders with status exactly success

GET /api/proteins

Returns a list of all proteins.

Response — array of:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file

GET /api/proteins/{id}

Returns full detail for a single protein, including all binder runs and their ranked designs.

Response:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file
sequencestringFull amino acid sequence
created_atdatetimeRecord creation timestamp
updated_atdatetimeLast update timestamp
runsarrayList of BinderRun objects (see below)

BinderRun object:

FieldTypeDescription
idintDatabase ID
algorithmstringAlgorithm name and version, from the Algorithm key of meta.json
descriptionstringOptional description, from the Description key of meta.json
run_datetimedatetime | nullDate/time of the run, from the Run date key of meta.json. Null when that key is absent or unparseable
hardwarestringHardware used, from the Hardware key of meta.json
notesstringFree-text notes, from the Notes key of meta.json
run_dirstringRelative path to run directory under MEDIA_ROOT
cif_pathstringRelative path to the target structure CIF file
target_sequencestringTarget sequence parsed from the run's CIF file
steps_configobject | arrayParsed steps.yaml, with referenced config files inlined
metadataobjectRemaining keys of meta.json, after the six lifted keys are taken out (null if none)
userstringUsername who imported the run
binder_countintNumber of binders in the run

Binders are not inlined in this response — a protein with several runs of a few thousand designs each made it tens of MB. Fetch them a page at a time from GET /api/runs/{run_id}/binders instead.


GET /api/runs/{run_id}/binders

Returns one page of a run's binders.

Query parameters:

ParameterTypeDefaultDescription
pageint11-based page number
page_sizeint20Results per page (capped at 200)
sortstringrankOne of rank, quality, iptm, length, status
sort_dirstringascasc or desc; missing values always sort last
statusstringallall, or a status to filter by (case-insensitive)

Response:

FieldTypeDescription
itemsarrayList of Binder objects (see below)
totalintTotal binders matching the filter, across all pages
metric_keysarrayMetric column names available for this run

metric_keys is sampled from the leading rows of the run rather than scanned across every row, since binders within a run come from the same pipeline and carry the same keys.

Binder object:

FieldTypeDescription
idintDatabase ID
binder_sequencestringAmino acid sequence of the designed binder
binder_lengthintSequence length (aa)
statusstringDesign status (e.g. passed, failed)
failure_reasonstringReason for failure if applicable
final_rankintRank among designs in the run
quality_scorefloatOverall quality score
design_to_target_iptmfloatipTM score for binder–target interface
metricsobjectUnmapped CSV columns, kept as-is
cif_pathstringRelative path to the binder's CIF structure file

Production Deployment

The production stack is nginx → gunicorn → Django with a separately served React build.

Assumed install path: /opt/binder/binder-base/

1. Build the frontend

cd binder_frontend
npm install
npm run build # outputs to binder_frontend/dist/

2. Configure the backend environment

Create binder_backend/binder_backend/.env:

SECRET_KEY=<strong-random-key>
DEBUG=False
MEDIA_ROOT=/opt/binder/files/
DATABASE_ENGINE=django.db.backends.postgresql
DATABASE_NAME=binderbase
DATABASE_USER=<db-user>
DATABASE_PASSWORD=<db-password>
DATABASE_HOST=localhost
DATABASE_PORT=5432
CSRF_TRUSTED_ORIGINS=https://your-domain.example.com
DEFAULT_FROM_EMAIL=noreply@your-domain.example.com

3. Collect static files and migrate

cd binder_backend
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input

4. Run gunicorn

cd binder_backend
gunicorn binder_backend.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4

Use a systemd service or supervisor to keep gunicorn running.

5. Configure nginx

Copy nginx.conf from the repo root to /etc/nginx/sites-available/binderbase (adjust server_name), then enable it:

ln -s /etc/nginx/sites-available/binderbase /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

The config serves:

  • / — React SPA from binder_frontend/dist/
  • /api/, /admin/ — proxied to gunicorn on port 8000
  • /media/ — files from MEDIA_ROOT (/opt/binder/files/)
  • /static/ — Django admin static files from binder_backend/static/

6. Create a superuser

cd binder_backend
python manage.py createsuperuser

Django admin is at https://your-domain.example.com/admin/.

7. Deploying an update

To ship new code to a running deployment, pull the changes, rebuild the frontend, apply backend changes, and restart gunicorn. Run from the repo root (/opt/binder/binder-base/):

git pull
# Frontend — reinstall deps and rebuild the SPAcd binder_frontend
npm install
npm run build
# Backend — update dependencies, apply database schema migrations and collect static files if neededcd ../binder_backend
source ../../venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input
# Restart the app server (adjust to your service name)
sudo systemctl restart gunicorn

nginx serves the new binder_frontend/dist/ build and static files directly, so no nginx reload is needed unless you changed nginx.conf.


Backup

Backup the database

pg_dump -U <DB_USER> -d <DB_NAME> -f binderbase-$(date +%Y%m%d).sql

Backing up the media files

MEDIA_ROOT holds every downloaded AlphaFold structure and every imported run directory. Snapshot it alongside the database dump.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - EpiGenomicsCode/binder-base · GitHub
Skip to content

Repository files navigation

Binder Base

Django + React application for tracking protein binder design experiments.


Table of Contents

  1. Development Quick Start
  2. Admin Dashboard
  3. Run Directory Convention & Import Command
  4. API Endpoints
  5. Production Deployment
  6. Backup

Development Quick Start

Prerequisites

  • Python 3.11+
  • Node.js 18+
  • PostgreSQL

Backend

cd binder_backend
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp binder_backend/.env.example binder_backend/.env
# Edit .env — set SECRET_KEY, DATABASE_*, MEDIA_ROOT, CSRF_TRUSTED_ORIGINS# Apply migrations and run
python manage.py migrate
python manage.py runserver

Frontend

cd binder_frontend
npm install
npm run dev # dev server at http://localhost:5173

The home page is served at http://localhost:5173.


Admin Dashboard

The Django admin interface is available at http://127.0.0.1:8000/admin. Log in with a superuser account (create one with python manage.py createsuperuser).

The following models are registered:

ModelList columnsFilters
ProteinUniProt ID, gene name, protein name, organism, lengthOrganism
BinderRunProtein, algorithm, run date, hardware, description, run dir, userProtein, algorithm, hardware, user
BinderRun, length, rank, quality score, ipTM, statusRun

Run Directory Convention & Import Command

The import_run command scans MEDIA_ROOT/runs/ for run directories not yet in the database and imports each one. This section documents exactly how a run directory is read so you can lay one out correctly.

Directory naming

Each run directory lives directly under MEDIA_ROOT/runs/ and is named:

<UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD>
PartMeaningExample
UNIPROT_IDLeading run of uppercase letters/digits. No lowercase, no hyphens.P12345
RUN_LABELFree-form label for the run. May itself contain hyphens — the date is anchored to the end of the name.tal1_screen
YYYYMMDDTrailing 8 digits.20260501

The directory name is validated against ^([A-Z0-9]+)-(.+)-(\d{8})$ and skips directories that don't match. If the UniProt ID or run date is different between the run directory name and the meta.json file (see below), a warning will be logged during importation. However, the importation will proceed with every imported value coming from meta.json.

Required directory structure

MEDIA_ROOT/runs/
P12345-tal1_screen-20260501/
meta.json # REQUIRED — run identity + metadata (see below)
*.cif # target structure — exactly one .cif in the run root
steps.yaml # optional pipeline config (see below)
config/ # optional configs referenced from steps.yaml
final_ranked_designs/ # REQUIRED
*.csv # binder table — first *.csv in this folder is used
*/rank001_<file_name> # per-design CIF files, matched by rank + filename

Target structure CIF (run root). The importer globs *.cif in the run directory root and uses it as the run's target structure, parsing the target sequence from its _entity_poly record. Keep exactly one.cif here:

  • None → the run still imports, but with no target structure and no target sequence.
  • More than one → the first match in glob order is used (order is not guaranteed), so the chosen file is effectively arbitrary. Always keep a single copy.

steps.yaml (optional). If present and valid, it is stored as the run's steps_config. Any nested config / config_file / config_path string values are replaced inline with the parsed contents of the referenced file (YAML or JSON), resolved relative to the run directory — this is what the config/ folder is for. A missing or unparseable steps.yaml is stored as null and does not stop the import.

meta.json (required). A JSON file in the run root that supplies the run's identity. Six keys are lifted into database columns; every other key is stored in the run's metadata field.

KeyDB fieldRequiredAccepted values
UniProt IDresolves/creates the ProteinYesAny non-empty string
Run daterun_datetimeNoYYYY-MM-DD, YYYY/MM/DD, YYYYMMDD, or a full ISO 8601 timestamp
AlgorithmalgorithmNoAny scalar (strings, numbers)
HardwarehardwareNoAny scalar (strings, numbers)
DescriptiondescriptionNoAny scalar (strings, numbers)
NotesnotesNoAny scalar (strings, numbers)
{
"UniProt ID": "P12345",
"Run date": "2026-05-01",
"Algorithm": "XXX v1.0",
"Hardware": "1x A100 80GB",
"Description": "TAL1-E2 binder screen",
"Notes": "rerun of the April batch with relaxed filters",
"Domain": "TAL1-E2",
"Hotspot residues": "31, 33, 38, 54, 58, 61, 63, 67, 68"
}

Here Domain and Hotspot residues become the run's metadata.

Key matching ignores case and any spaces, underscores or hyphens, so UniProt ID, uniprot_id and UNIPROT-ID are equivalent. Matching is on the whole key, so near-misses like Hardware config or Run dates are not absorbed — they stay in metadata.

If a recognized key's value can't be used — an unparseable Run date, an object where a scalar Algorithm was expected — the column is left null and the raw entry falls through to metadata, so nothing in the file is ever silently lost.

Note: import_run skips run directories it has already imported, so editing a meta.json afterwards will not update the corresponding run. Use import_meta_json to push those edits into runs that are already in the database.

final_ranked_designs/ (required). Must exist, or the directory is skipped (see below). The first*.csv in this folder is read as the binder table; per-design CIF files are located by globbing rank*_<file_name> recursively and matching the filename rank{final_rank}_{file_name} (the rank may be zero-padded to any width, e.g. rank1_, rank01_, rank001_).

The designs CSV

The CSV must contain at minimum a sequence column (rows without it are skipped). Recognized columns and their mapping to database fields:

CSV columnDB field
sequencebinder_sequence (its length → binder_length)
final_rankfinal_rank
quality_scorequality_score
design_to_target_iptmdesign_to_target_iptm
pass_filters (TRUE/FALSE)status (success / failed)
pass_*_filter (FALSE)contributes to failure_reason (which filters failed)
file_nameused (with final_rank) to locate the per-design CIF

Any additional columns are stored in the metrics JSON field.

When directories are skipped

A directory is skipped (logged, and the command continues to the next one) when:

  • It is already imported — its path matches an existing BinderRun.run_dir.
  • Its name does not match the <UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD> pattern.
  • It has no final_ranked_designs/ folder.
  • Its meta.json is missing, unparseable, or not a JSON object.
  • Its meta.json has no usable UniProt ID — without it the Protein can't be resolved.

BinderRun.run_dir also carries a database-level unique constraint, so a duplicate run cannot be created even if the in-memory check is bypassed. Two consequences:

Renaming a run directory makes it import again under the new path, producing a second BinderRun (and a second set of Binder rows) while the original row keeps pointing at a path that no longer exists. Renaming is not a supported way to re-import.

Running the import command

From binder_backend/:

python manage.py import_run

For each new run directory the command:

  1. Reads meta.json, taking UniProt ID, Run date, Algorithm, Hardware, Description and Notes from it and keeping the remaining keys as the run's metadata, then cross-checks the UniProt ID and date against the directory name. Note that if a discrepancy is detected, only a warning will be logged, and the import process will continue.
  2. Resolves the Protein by that UniProt ID. If it already exists it is reused; otherwise the AlphaFold API (sequence, gene name, organism, structure CIF, PAE JSON) and UniProt API (biological function) are queried, the CIF and PAE JSON are downloaded to MEDIA_ROOT/proteins/{uniprot_id}/, and the Protein is created.
  3. Creates a BinderRun (algorithm, date, target CIF path, target sequence, steps_config, metadata).
  4. Bulk-creates Binder records from the CSV, linking each to its per-design CIF.

A summary line reports the number of runs imported and skipped.

Re-reading meta.json for existing runs

import_run only ever looks at directories it hasn't imported, so editing a meta.json afterwards has no effect. To push those edits into runs that are already in the database, use import_meta_json:

python manage.py import_meta_json # every run in the database
python manage.py import_meta_json P12345-tal1_screen-20260501 # just this one
python manage.py import_meta_json --dry-run # preview, write nothing

This is a full resync, not a merge. The six meta.json-owned fields are overwritten from the file, and a key that is absent from the file sets its column back to null — so the database always mirrors the current contents of meta.json.

Because it can clear fields in bulk, run it with --dry-run first. Output is a per-field old -> new diff:

P12345-tal1_screen-20260501
algorithm OLD v0.1 -> XXX v1.0
hardware old hardware -> None
metadata {'stale': 'yes'} -> {'Domain': 'TAL1-E2'}
Done. updated: 1 unchanged: 0 failed: 0

Scope. The command never creates or deletes runs and never touches binders — it only updates the fields above on runs that already exist. Directories on disk that have never been imported are ignored; use import_run for those. Files other than meta.json (steps.yaml, the CIFs, the designs CSV) are not re-read either.

If UniProt ID changed, the run is reassigned to that protein and a warning is logged.

Failures leave data untouched. A run is reported and skipped without any change when its meta.json is missing, unparseable or not a JSON object, when it has no usable UniProt ID, or when the run directory itself is gone. A broken file is treated as a problem to fix, never as an instruction to clear every column.


API Endpoints

All endpoints are mounted at /api/. Interactive docs (OpenAPI/Swagger UI) are available at /api/docs.

GET /api/stats

Returns database-wide totals, for dashboard summary tiles.

Response:

FieldTypeDescription
protein_countintTotal proteins
run_countintTotal binder runs
binder_countintTotal binders across all runs
success_countintBinders with status exactly success

GET /api/proteins

Returns a list of all proteins.

Response — array of:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file

GET /api/proteins/{id}

Returns full detail for a single protein, including all binder runs and their ranked designs.

Response:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file
sequencestringFull amino acid sequence
created_atdatetimeRecord creation timestamp
updated_atdatetimeLast update timestamp
runsarrayList of BinderRun objects (see below)

BinderRun object:

FieldTypeDescription
idintDatabase ID
algorithmstringAlgorithm name and version, from the Algorithm key of meta.json
descriptionstringOptional description, from the Description key of meta.json
run_datetimedatetime | nullDate/time of the run, from the Run date key of meta.json. Null when that key is absent or unparseable
hardwarestringHardware used, from the Hardware key of meta.json
notesstringFree-text notes, from the Notes key of meta.json
run_dirstringRelative path to run directory under MEDIA_ROOT
cif_pathstringRelative path to the target structure CIF file
target_sequencestringTarget sequence parsed from the run's CIF file
steps_configobject | arrayParsed steps.yaml, with referenced config files inlined
metadataobjectRemaining keys of meta.json, after the six lifted keys are taken out (null if none)
userstringUsername who imported the run
binder_countintNumber of binders in the run

Binders are not inlined in this response — a protein with several runs of a few thousand designs each made it tens of MB. Fetch them a page at a time from GET /api/runs/{run_id}/binders instead.


GET /api/runs/{run_id}/binders

Returns one page of a run's binders.

Query parameters:

ParameterTypeDefaultDescription
pageint11-based page number
page_sizeint20Results per page (capped at 200)
sortstringrankOne of rank, quality, iptm, length, status
sort_dirstringascasc or desc; missing values always sort last
statusstringallall, or a status to filter by (case-insensitive)

Response:

FieldTypeDescription
itemsarrayList of Binder objects (see below)
totalintTotal binders matching the filter, across all pages
metric_keysarrayMetric column names available for this run

metric_keys is sampled from the leading rows of the run rather than scanned across every row, since binders within a run come from the same pipeline and carry the same keys.

Binder object:

FieldTypeDescription
idintDatabase ID
binder_sequencestringAmino acid sequence of the designed binder
binder_lengthintSequence length (aa)
statusstringDesign status (e.g. passed, failed)
failure_reasonstringReason for failure if applicable
final_rankintRank among designs in the run
quality_scorefloatOverall quality score
design_to_target_iptmfloatipTM score for binder–target interface
metricsobjectUnmapped CSV columns, kept as-is
cif_pathstringRelative path to the binder's CIF structure file

Production Deployment

The production stack is nginx → gunicorn → Django with a separately served React build.

Assumed install path: /opt/binder/binder-base/

1. Build the frontend

cd binder_frontend
npm install
npm run build # outputs to binder_frontend/dist/

2. Configure the backend environment

Create binder_backend/binder_backend/.env:

SECRET_KEY=<strong-random-key>
DEBUG=False
MEDIA_ROOT=/opt/binder/files/
DATABASE_ENGINE=django.db.backends.postgresql
DATABASE_NAME=binderbase
DATABASE_USER=<db-user>
DATABASE_PASSWORD=<db-password>
DATABASE_HOST=localhost
DATABASE_PORT=5432
CSRF_TRUSTED_ORIGINS=https://your-domain.example.com
DEFAULT_FROM_EMAIL=noreply@your-domain.example.com

3. Collect static files and migrate

cd binder_backend
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input

4. Run gunicorn

cd binder_backend
gunicorn binder_backend.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4

Use a systemd service or supervisor to keep gunicorn running.

5. Configure nginx

Copy nginx.conf from the repo root to /etc/nginx/sites-available/binderbase (adjust server_name), then enable it:

ln -s /etc/nginx/sites-available/binderbase /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

The config serves:

  • / — React SPA from binder_frontend/dist/
  • /api/, /admin/ — proxied to gunicorn on port 8000
  • /media/ — files from MEDIA_ROOT (/opt/binder/files/)
  • /static/ — Django admin static files from binder_backend/static/

6. Create a superuser

cd binder_backend
python manage.py createsuperuser

Django admin is at https://your-domain.example.com/admin/.

7. Deploying an update

To ship new code to a running deployment, pull the changes, rebuild the frontend, apply backend changes, and restart gunicorn. Run from the repo root (/opt/binder/binder-base/):

git pull
# Frontend — reinstall deps and rebuild the SPAcd binder_frontend
npm install
npm run build
# Backend — update dependencies, apply database schema migrations and collect static files if neededcd ../binder_backend
source ../../venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input
# Restart the app server (adjust to your service name)
sudo systemctl restart gunicorn

nginx serves the new binder_frontend/dist/ build and static files directly, so no nginx reload is needed unless you changed nginx.conf.


Backup

Backup the database

pg_dump -U <DB_USER> -d <DB_NAME> -f binderbase-$(date +%Y%m%d).sql

Backing up the media files

MEDIA_ROOT holds every downloaded AlphaFold structure and every imported run directory. Snapshot it alongside the database dump.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - EpiGenomicsCode/binder-base · GitHub
Skip to content

Repository files navigation

Binder Base

Django + React application for tracking protein binder design experiments.


Table of Contents

  1. Development Quick Start
  2. Admin Dashboard
  3. Run Directory Convention & Import Command
  4. API Endpoints
  5. Production Deployment
  6. Backup

Development Quick Start

Prerequisites

  • Python 3.11+
  • Node.js 18+
  • PostgreSQL

Backend

cd binder_backend
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp binder_backend/.env.example binder_backend/.env
# Edit .env — set SECRET_KEY, DATABASE_*, MEDIA_ROOT, CSRF_TRUSTED_ORIGINS# Apply migrations and run
python manage.py migrate
python manage.py runserver

Frontend

cd binder_frontend
npm install
npm run dev # dev server at http://localhost:5173

The home page is served at http://localhost:5173.


Admin Dashboard

The Django admin interface is available at http://127.0.0.1:8000/admin. Log in with a superuser account (create one with python manage.py createsuperuser).

The following models are registered:

ModelList columnsFilters
ProteinUniProt ID, gene name, protein name, organism, lengthOrganism
BinderRunProtein, algorithm, run date, hardware, description, run dir, userProtein, algorithm, hardware, user
BinderRun, length, rank, quality score, ipTM, statusRun

Run Directory Convention & Import Command

The import_run command scans MEDIA_ROOT/runs/ for run directories not yet in the database and imports each one. This section documents exactly how a run directory is read so you can lay one out correctly.

Directory naming

Each run directory lives directly under MEDIA_ROOT/runs/ and is named:

<UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD>
PartMeaningExample
UNIPROT_IDLeading run of uppercase letters/digits. No lowercase, no hyphens.P12345
RUN_LABELFree-form label for the run. May itself contain hyphens — the date is anchored to the end of the name.tal1_screen
YYYYMMDDTrailing 8 digits.20260501

The directory name is validated against ^([A-Z0-9]+)-(.+)-(\d{8})$ and skips directories that don't match. If the UniProt ID or run date is different between the run directory name and the meta.json file (see below), a warning will be logged during importation. However, the importation will proceed with every imported value coming from meta.json.

Required directory structure

MEDIA_ROOT/runs/
P12345-tal1_screen-20260501/
meta.json # REQUIRED — run identity + metadata (see below)
*.cif # target structure — exactly one .cif in the run root
steps.yaml # optional pipeline config (see below)
config/ # optional configs referenced from steps.yaml
final_ranked_designs/ # REQUIRED
*.csv # binder table — first *.csv in this folder is used
*/rank001_<file_name> # per-design CIF files, matched by rank + filename

Target structure CIF (run root). The importer globs *.cif in the run directory root and uses it as the run's target structure, parsing the target sequence from its _entity_poly record. Keep exactly one.cif here:

  • None → the run still imports, but with no target structure and no target sequence.
  • More than one → the first match in glob order is used (order is not guaranteed), so the chosen file is effectively arbitrary. Always keep a single copy.

steps.yaml (optional). If present and valid, it is stored as the run's steps_config. Any nested config / config_file / config_path string values are replaced inline with the parsed contents of the referenced file (YAML or JSON), resolved relative to the run directory — this is what the config/ folder is for. A missing or unparseable steps.yaml is stored as null and does not stop the import.

meta.json (required). A JSON file in the run root that supplies the run's identity. Six keys are lifted into database columns; every other key is stored in the run's metadata field.

KeyDB fieldRequiredAccepted values
UniProt IDresolves/creates the ProteinYesAny non-empty string
Run daterun_datetimeNoYYYY-MM-DD, YYYY/MM/DD, YYYYMMDD, or a full ISO 8601 timestamp
AlgorithmalgorithmNoAny scalar (strings, numbers)
HardwarehardwareNoAny scalar (strings, numbers)
DescriptiondescriptionNoAny scalar (strings, numbers)
NotesnotesNoAny scalar (strings, numbers)
{
"UniProt ID": "P12345",
"Run date": "2026-05-01",
"Algorithm": "XXX v1.0",
"Hardware": "1x A100 80GB",
"Description": "TAL1-E2 binder screen",
"Notes": "rerun of the April batch with relaxed filters",
"Domain": "TAL1-E2",
"Hotspot residues": "31, 33, 38, 54, 58, 61, 63, 67, 68"
}

Here Domain and Hotspot residues become the run's metadata.

Key matching ignores case and any spaces, underscores or hyphens, so UniProt ID, uniprot_id and UNIPROT-ID are equivalent. Matching is on the whole key, so near-misses like Hardware config or Run dates are not absorbed — they stay in metadata.

If a recognized key's value can't be used — an unparseable Run date, an object where a scalar Algorithm was expected — the column is left null and the raw entry falls through to metadata, so nothing in the file is ever silently lost.

Note: import_run skips run directories it has already imported, so editing a meta.json afterwards will not update the corresponding run. Use import_meta_json to push those edits into runs that are already in the database.

final_ranked_designs/ (required). Must exist, or the directory is skipped (see below). The first*.csv in this folder is read as the binder table; per-design CIF files are located by globbing rank*_<file_name> recursively and matching the filename rank{final_rank}_{file_name} (the rank may be zero-padded to any width, e.g. rank1_, rank01_, rank001_).

The designs CSV

The CSV must contain at minimum a sequence column (rows without it are skipped). Recognized columns and their mapping to database fields:

CSV columnDB field
sequencebinder_sequence (its length → binder_length)
final_rankfinal_rank
quality_scorequality_score
design_to_target_iptmdesign_to_target_iptm
pass_filters (TRUE/FALSE)status (success / failed)
pass_*_filter (FALSE)contributes to failure_reason (which filters failed)
file_nameused (with final_rank) to locate the per-design CIF

Any additional columns are stored in the metrics JSON field.

When directories are skipped

A directory is skipped (logged, and the command continues to the next one) when:

  • It is already imported — its path matches an existing BinderRun.run_dir.
  • Its name does not match the <UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD> pattern.
  • It has no final_ranked_designs/ folder.
  • Its meta.json is missing, unparseable, or not a JSON object.
  • Its meta.json has no usable UniProt ID — without it the Protein can't be resolved.

BinderRun.run_dir also carries a database-level unique constraint, so a duplicate run cannot be created even if the in-memory check is bypassed. Two consequences:

Renaming a run directory makes it import again under the new path, producing a second BinderRun (and a second set of Binder rows) while the original row keeps pointing at a path that no longer exists. Renaming is not a supported way to re-import.

Running the import command

From binder_backend/:

python manage.py import_run

For each new run directory the command:

  1. Reads meta.json, taking UniProt ID, Run date, Algorithm, Hardware, Description and Notes from it and keeping the remaining keys as the run's metadata, then cross-checks the UniProt ID and date against the directory name. Note that if a discrepancy is detected, only a warning will be logged, and the import process will continue.
  2. Resolves the Protein by that UniProt ID. If it already exists it is reused; otherwise the AlphaFold API (sequence, gene name, organism, structure CIF, PAE JSON) and UniProt API (biological function) are queried, the CIF and PAE JSON are downloaded to MEDIA_ROOT/proteins/{uniprot_id}/, and the Protein is created.
  3. Creates a BinderRun (algorithm, date, target CIF path, target sequence, steps_config, metadata).
  4. Bulk-creates Binder records from the CSV, linking each to its per-design CIF.

A summary line reports the number of runs imported and skipped.

Re-reading meta.json for existing runs

import_run only ever looks at directories it hasn't imported, so editing a meta.json afterwards has no effect. To push those edits into runs that are already in the database, use import_meta_json:

python manage.py import_meta_json # every run in the database
python manage.py import_meta_json P12345-tal1_screen-20260501 # just this one
python manage.py import_meta_json --dry-run # preview, write nothing

This is a full resync, not a merge. The six meta.json-owned fields are overwritten from the file, and a key that is absent from the file sets its column back to null — so the database always mirrors the current contents of meta.json.

Because it can clear fields in bulk, run it with --dry-run first. Output is a per-field old -> new diff:

P12345-tal1_screen-20260501
algorithm OLD v0.1 -> XXX v1.0
hardware old hardware -> None
metadata {'stale': 'yes'} -> {'Domain': 'TAL1-E2'}
Done. updated: 1 unchanged: 0 failed: 0

Scope. The command never creates or deletes runs and never touches binders — it only updates the fields above on runs that already exist. Directories on disk that have never been imported are ignored; use import_run for those. Files other than meta.json (steps.yaml, the CIFs, the designs CSV) are not re-read either.

If UniProt ID changed, the run is reassigned to that protein and a warning is logged.

Failures leave data untouched. A run is reported and skipped without any change when its meta.json is missing, unparseable or not a JSON object, when it has no usable UniProt ID, or when the run directory itself is gone. A broken file is treated as a problem to fix, never as an instruction to clear every column.


API Endpoints

All endpoints are mounted at /api/. Interactive docs (OpenAPI/Swagger UI) are available at /api/docs.

GET /api/stats

Returns database-wide totals, for dashboard summary tiles.

Response:

FieldTypeDescription
protein_countintTotal proteins
run_countintTotal binder runs
binder_countintTotal binders across all runs
success_countintBinders with status exactly success

GET /api/proteins

Returns a list of all proteins.

Response — array of:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file

GET /api/proteins/{id}

Returns full detail for a single protein, including all binder runs and their ranked designs.

Response:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file
sequencestringFull amino acid sequence
created_atdatetimeRecord creation timestamp
updated_atdatetimeLast update timestamp
runsarrayList of BinderRun objects (see below)

BinderRun object:

FieldTypeDescription
idintDatabase ID
algorithmstringAlgorithm name and version, from the Algorithm key of meta.json
descriptionstringOptional description, from the Description key of meta.json
run_datetimedatetime | nullDate/time of the run, from the Run date key of meta.json. Null when that key is absent or unparseable
hardwarestringHardware used, from the Hardware key of meta.json
notesstringFree-text notes, from the Notes key of meta.json
run_dirstringRelative path to run directory under MEDIA_ROOT
cif_pathstringRelative path to the target structure CIF file
target_sequencestringTarget sequence parsed from the run's CIF file
steps_configobject | arrayParsed steps.yaml, with referenced config files inlined
metadataobjectRemaining keys of meta.json, after the six lifted keys are taken out (null if none)
userstringUsername who imported the run
binder_countintNumber of binders in the run

Binders are not inlined in this response — a protein with several runs of a few thousand designs each made it tens of MB. Fetch them a page at a time from GET /api/runs/{run_id}/binders instead.


GET /api/runs/{run_id}/binders

Returns one page of a run's binders.

Query parameters:

ParameterTypeDefaultDescription
pageint11-based page number
page_sizeint20Results per page (capped at 200)
sortstringrankOne of rank, quality, iptm, length, status
sort_dirstringascasc or desc; missing values always sort last
statusstringallall, or a status to filter by (case-insensitive)

Response:

FieldTypeDescription
itemsarrayList of Binder objects (see below)
totalintTotal binders matching the filter, across all pages
metric_keysarrayMetric column names available for this run

metric_keys is sampled from the leading rows of the run rather than scanned across every row, since binders within a run come from the same pipeline and carry the same keys.

Binder object:

FieldTypeDescription
idintDatabase ID
binder_sequencestringAmino acid sequence of the designed binder
binder_lengthintSequence length (aa)
statusstringDesign status (e.g. passed, failed)
failure_reasonstringReason for failure if applicable
final_rankintRank among designs in the run
quality_scorefloatOverall quality score
design_to_target_iptmfloatipTM score for binder–target interface
metricsobjectUnmapped CSV columns, kept as-is
cif_pathstringRelative path to the binder's CIF structure file

Production Deployment

The production stack is nginx → gunicorn → Django with a separately served React build.

Assumed install path: /opt/binder/binder-base/

1. Build the frontend

cd binder_frontend
npm install
npm run build # outputs to binder_frontend/dist/

2. Configure the backend environment

Create binder_backend/binder_backend/.env:

SECRET_KEY=<strong-random-key>
DEBUG=False
MEDIA_ROOT=/opt/binder/files/
DATABASE_ENGINE=django.db.backends.postgresql
DATABASE_NAME=binderbase
DATABASE_USER=<db-user>
DATABASE_PASSWORD=<db-password>
DATABASE_HOST=localhost
DATABASE_PORT=5432
CSRF_TRUSTED_ORIGINS=https://your-domain.example.com
DEFAULT_FROM_EMAIL=noreply@your-domain.example.com

3. Collect static files and migrate

cd binder_backend
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input

4. Run gunicorn

cd binder_backend
gunicorn binder_backend.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4

Use a systemd service or supervisor to keep gunicorn running.

5. Configure nginx

Copy nginx.conf from the repo root to /etc/nginx/sites-available/binderbase (adjust server_name), then enable it:

ln -s /etc/nginx/sites-available/binderbase /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

The config serves:

  • / — React SPA from binder_frontend/dist/
  • /api/, /admin/ — proxied to gunicorn on port 8000
  • /media/ — files from MEDIA_ROOT (/opt/binder/files/)
  • /static/ — Django admin static files from binder_backend/static/

6. Create a superuser

cd binder_backend
python manage.py createsuperuser

Django admin is at https://your-domain.example.com/admin/.

7. Deploying an update

To ship new code to a running deployment, pull the changes, rebuild the frontend, apply backend changes, and restart gunicorn. Run from the repo root (/opt/binder/binder-base/):

git pull
# Frontend — reinstall deps and rebuild the SPAcd binder_frontend
npm install
npm run build
# Backend — update dependencies, apply database schema migrations and collect static files if neededcd ../binder_backend
source ../../venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input
# Restart the app server (adjust to your service name)
sudo systemctl restart gunicorn

nginx serves the new binder_frontend/dist/ build and static files directly, so no nginx reload is needed unless you changed nginx.conf.


Backup

Backup the database

pg_dump -U <DB_USER> -d <DB_NAME> -f binderbase-$(date +%Y%m%d).sql

Backing up the media files

MEDIA_ROOT holds every downloaded AlphaFold structure and every imported run directory. Snapshot it alongside the database dump.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - EpiGenomicsCode/binder-base · GitHub
Skip to content

Repository files navigation

Binder Base

Django + React application for tracking protein binder design experiments.


Table of Contents

  1. Development Quick Start
  2. Admin Dashboard
  3. Run Directory Convention & Import Command
  4. API Endpoints
  5. Production Deployment
  6. Backup

Development Quick Start

Prerequisites

  • Python 3.11+
  • Node.js 18+
  • PostgreSQL

Backend

cd binder_backend
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp binder_backend/.env.example binder_backend/.env
# Edit .env — set SECRET_KEY, DATABASE_*, MEDIA_ROOT, CSRF_TRUSTED_ORIGINS# Apply migrations and run
python manage.py migrate
python manage.py runserver

Frontend

cd binder_frontend
npm install
npm run dev # dev server at http://localhost:5173

The home page is served at http://localhost:5173.


Admin Dashboard

The Django admin interface is available at http://127.0.0.1:8000/admin. Log in with a superuser account (create one with python manage.py createsuperuser).

The following models are registered:

ModelList columnsFilters
ProteinUniProt ID, gene name, protein name, organism, lengthOrganism
BinderRunProtein, algorithm, run date, hardware, description, run dir, userProtein, algorithm, hardware, user
BinderRun, length, rank, quality score, ipTM, statusRun

Run Directory Convention & Import Command

The import_run command scans MEDIA_ROOT/runs/ for run directories not yet in the database and imports each one. This section documents exactly how a run directory is read so you can lay one out correctly.

Directory naming

Each run directory lives directly under MEDIA_ROOT/runs/ and is named:

<UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD>
PartMeaningExample
UNIPROT_IDLeading run of uppercase letters/digits. No lowercase, no hyphens.P12345
RUN_LABELFree-form label for the run. May itself contain hyphens — the date is anchored to the end of the name.tal1_screen
YYYYMMDDTrailing 8 digits.20260501

The directory name is validated against ^([A-Z0-9]+)-(.+)-(\d{8})$ and skips directories that don't match. If the UniProt ID or run date is different between the run directory name and the meta.json file (see below), a warning will be logged during importation. However, the importation will proceed with every imported value coming from meta.json.

Required directory structure

MEDIA_ROOT/runs/
P12345-tal1_screen-20260501/
meta.json # REQUIRED — run identity + metadata (see below)
*.cif # target structure — exactly one .cif in the run root
steps.yaml # optional pipeline config (see below)
config/ # optional configs referenced from steps.yaml
final_ranked_designs/ # REQUIRED
*.csv # binder table — first *.csv in this folder is used
*/rank001_<file_name> # per-design CIF files, matched by rank + filename

Target structure CIF (run root). The importer globs *.cif in the run directory root and uses it as the run's target structure, parsing the target sequence from its _entity_poly record. Keep exactly one.cif here:

  • None → the run still imports, but with no target structure and no target sequence.
  • More than one → the first match in glob order is used (order is not guaranteed), so the chosen file is effectively arbitrary. Always keep a single copy.

steps.yaml (optional). If present and valid, it is stored as the run's steps_config. Any nested config / config_file / config_path string values are replaced inline with the parsed contents of the referenced file (YAML or JSON), resolved relative to the run directory — this is what the config/ folder is for. A missing or unparseable steps.yaml is stored as null and does not stop the import.

meta.json (required). A JSON file in the run root that supplies the run's identity. Six keys are lifted into database columns; every other key is stored in the run's metadata field.

KeyDB fieldRequiredAccepted values
UniProt IDresolves/creates the ProteinYesAny non-empty string
Run daterun_datetimeNoYYYY-MM-DD, YYYY/MM/DD, YYYYMMDD, or a full ISO 8601 timestamp
AlgorithmalgorithmNoAny scalar (strings, numbers)
HardwarehardwareNoAny scalar (strings, numbers)
DescriptiondescriptionNoAny scalar (strings, numbers)
NotesnotesNoAny scalar (strings, numbers)
{
"UniProt ID": "P12345",
"Run date": "2026-05-01",
"Algorithm": "XXX v1.0",
"Hardware": "1x A100 80GB",
"Description": "TAL1-E2 binder screen",
"Notes": "rerun of the April batch with relaxed filters",
"Domain": "TAL1-E2",
"Hotspot residues": "31, 33, 38, 54, 58, 61, 63, 67, 68"
}

Here Domain and Hotspot residues become the run's metadata.

Key matching ignores case and any spaces, underscores or hyphens, so UniProt ID, uniprot_id and UNIPROT-ID are equivalent. Matching is on the whole key, so near-misses like Hardware config or Run dates are not absorbed — they stay in metadata.

If a recognized key's value can't be used — an unparseable Run date, an object where a scalar Algorithm was expected — the column is left null and the raw entry falls through to metadata, so nothing in the file is ever silently lost.

Note: import_run skips run directories it has already imported, so editing a meta.json afterwards will not update the corresponding run. Use import_meta_json to push those edits into runs that are already in the database.

final_ranked_designs/ (required). Must exist, or the directory is skipped (see below). The first*.csv in this folder is read as the binder table; per-design CIF files are located by globbing rank*_<file_name> recursively and matching the filename rank{final_rank}_{file_name} (the rank may be zero-padded to any width, e.g. rank1_, rank01_, rank001_).

The designs CSV

The CSV must contain at minimum a sequence column (rows without it are skipped). Recognized columns and their mapping to database fields:

CSV columnDB field
sequencebinder_sequence (its length → binder_length)
final_rankfinal_rank
quality_scorequality_score
design_to_target_iptmdesign_to_target_iptm
pass_filters (TRUE/FALSE)status (success / failed)
pass_*_filter (FALSE)contributes to failure_reason (which filters failed)
file_nameused (with final_rank) to locate the per-design CIF

Any additional columns are stored in the metrics JSON field.

When directories are skipped

A directory is skipped (logged, and the command continues to the next one) when:

  • It is already imported — its path matches an existing BinderRun.run_dir.
  • Its name does not match the <UNIPROT_ID>-<RUN_LABEL>-<YYYYMMDD> pattern.
  • It has no final_ranked_designs/ folder.
  • Its meta.json is missing, unparseable, or not a JSON object.
  • Its meta.json has no usable UniProt ID — without it the Protein can't be resolved.

BinderRun.run_dir also carries a database-level unique constraint, so a duplicate run cannot be created even if the in-memory check is bypassed. Two consequences:

Renaming a run directory makes it import again under the new path, producing a second BinderRun (and a second set of Binder rows) while the original row keeps pointing at a path that no longer exists. Renaming is not a supported way to re-import.

Running the import command

From binder_backend/:

python manage.py import_run

For each new run directory the command:

  1. Reads meta.json, taking UniProt ID, Run date, Algorithm, Hardware, Description and Notes from it and keeping the remaining keys as the run's metadata, then cross-checks the UniProt ID and date against the directory name. Note that if a discrepancy is detected, only a warning will be logged, and the import process will continue.
  2. Resolves the Protein by that UniProt ID. If it already exists it is reused; otherwise the AlphaFold API (sequence, gene name, organism, structure CIF, PAE JSON) and UniProt API (biological function) are queried, the CIF and PAE JSON are downloaded to MEDIA_ROOT/proteins/{uniprot_id}/, and the Protein is created.
  3. Creates a BinderRun (algorithm, date, target CIF path, target sequence, steps_config, metadata).
  4. Bulk-creates Binder records from the CSV, linking each to its per-design CIF.

A summary line reports the number of runs imported and skipped.

Re-reading meta.json for existing runs

import_run only ever looks at directories it hasn't imported, so editing a meta.json afterwards has no effect. To push those edits into runs that are already in the database, use import_meta_json:

python manage.py import_meta_json # every run in the database
python manage.py import_meta_json P12345-tal1_screen-20260501 # just this one
python manage.py import_meta_json --dry-run # preview, write nothing

This is a full resync, not a merge. The six meta.json-owned fields are overwritten from the file, and a key that is absent from the file sets its column back to null — so the database always mirrors the current contents of meta.json.

Because it can clear fields in bulk, run it with --dry-run first. Output is a per-field old -> new diff:

P12345-tal1_screen-20260501
algorithm OLD v0.1 -> XXX v1.0
hardware old hardware -> None
metadata {'stale': 'yes'} -> {'Domain': 'TAL1-E2'}
Done. updated: 1 unchanged: 0 failed: 0

Scope. The command never creates or deletes runs and never touches binders — it only updates the fields above on runs that already exist. Directories on disk that have never been imported are ignored; use import_run for those. Files other than meta.json (steps.yaml, the CIFs, the designs CSV) are not re-read either.

If UniProt ID changed, the run is reassigned to that protein and a warning is logged.

Failures leave data untouched. A run is reported and skipped without any change when its meta.json is missing, unparseable or not a JSON object, when it has no usable UniProt ID, or when the run directory itself is gone. A broken file is treated as a problem to fix, never as an instruction to clear every column.


API Endpoints

All endpoints are mounted at /api/. Interactive docs (OpenAPI/Swagger UI) are available at /api/docs.

GET /api/stats

Returns database-wide totals, for dashboard summary tiles.

Response:

FieldTypeDescription
protein_countintTotal proteins
run_countintTotal binder runs
binder_countintTotal binders across all runs
success_countintBinders with status exactly success

GET /api/proteins

Returns a list of all proteins.

Response — array of:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file

GET /api/proteins/{id}

Returns full detail for a single protein, including all binder runs and their ranked designs.

Response:

FieldTypeDescription
idintDatabase ID
uniprot_idstringUniProt accession
gene_namestringGene name
protein_namestringFull protein name
organismstringScientific organism name
lengthintSequence length (aa)
biological_functionstringBiological function description (from UniProt)
cif_pathstringRelative path to the AlphaFold predicted structure CIF file
pae_json_pathstringRelative path to the AlphaFold PAE JSON file
sequencestringFull amino acid sequence
created_atdatetimeRecord creation timestamp
updated_atdatetimeLast update timestamp
runsarrayList of BinderRun objects (see below)

BinderRun object:

FieldTypeDescription
idintDatabase ID
algorithmstringAlgorithm name and version, from the Algorithm key of meta.json
descriptionstringOptional description, from the Description key of meta.json
run_datetimedatetime | nullDate/time of the run, from the Run date key of meta.json. Null when that key is absent or unparseable
hardwarestringHardware used, from the Hardware key of meta.json
notesstringFree-text notes, from the Notes key of meta.json
run_dirstringRelative path to run directory under MEDIA_ROOT
cif_pathstringRelative path to the target structure CIF file
target_sequencestringTarget sequence parsed from the run's CIF file
steps_configobject | arrayParsed steps.yaml, with referenced config files inlined
metadataobjectRemaining keys of meta.json, after the six lifted keys are taken out (null if none)
userstringUsername who imported the run
binder_countintNumber of binders in the run

Binders are not inlined in this response — a protein with several runs of a few thousand designs each made it tens of MB. Fetch them a page at a time from GET /api/runs/{run_id}/binders instead.


GET /api/runs/{run_id}/binders

Returns one page of a run's binders.

Query parameters:

ParameterTypeDefaultDescription
pageint11-based page number
page_sizeint20Results per page (capped at 200)
sortstringrankOne of rank, quality, iptm, length, status
sort_dirstringascasc or desc; missing values always sort last
statusstringallall, or a status to filter by (case-insensitive)

Response:

FieldTypeDescription
itemsarrayList of Binder objects (see below)
totalintTotal binders matching the filter, across all pages
metric_keysarrayMetric column names available for this run

metric_keys is sampled from the leading rows of the run rather than scanned across every row, since binders within a run come from the same pipeline and carry the same keys.

Binder object:

FieldTypeDescription
idintDatabase ID
binder_sequencestringAmino acid sequence of the designed binder
binder_lengthintSequence length (aa)
statusstringDesign status (e.g. passed, failed)
failure_reasonstringReason for failure if applicable
final_rankintRank among designs in the run
quality_scorefloatOverall quality score
design_to_target_iptmfloatipTM score for binder–target interface
metricsobjectUnmapped CSV columns, kept as-is
cif_pathstringRelative path to the binder's CIF structure file

Production Deployment

The production stack is nginx → gunicorn → Django with a separately served React build.

Assumed install path: /opt/binder/binder-base/

1. Build the frontend

cd binder_frontend
npm install
npm run build # outputs to binder_frontend/dist/

2. Configure the backend environment

Create binder_backend/binder_backend/.env:

SECRET_KEY=<strong-random-key>
DEBUG=False
MEDIA_ROOT=/opt/binder/files/
DATABASE_ENGINE=django.db.backends.postgresql
DATABASE_NAME=binderbase
DATABASE_USER=<db-user>
DATABASE_PASSWORD=<db-password>
DATABASE_HOST=localhost
DATABASE_PORT=5432
CSRF_TRUSTED_ORIGINS=https://your-domain.example.com
DEFAULT_FROM_EMAIL=noreply@your-domain.example.com

3. Collect static files and migrate

cd binder_backend
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input

4. Run gunicorn

cd binder_backend
gunicorn binder_backend.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4

Use a systemd service or supervisor to keep gunicorn running.

5. Configure nginx

Copy nginx.conf from the repo root to /etc/nginx/sites-available/binderbase (adjust server_name), then enable it:

ln -s /etc/nginx/sites-available/binderbase /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

The config serves:

  • / — React SPA from binder_frontend/dist/
  • /api/, /admin/ — proxied to gunicorn on port 8000
  • /media/ — files from MEDIA_ROOT (/opt/binder/files/)
  • /static/ — Django admin static files from binder_backend/static/

6. Create a superuser

cd binder_backend
python manage.py createsuperuser

Django admin is at https://your-domain.example.com/admin/.

7. Deploying an update

To ship new code to a running deployment, pull the changes, rebuild the frontend, apply backend changes, and restart gunicorn. Run from the repo root (/opt/binder/binder-base/):

git pull
# Frontend — reinstall deps and rebuild the SPAcd binder_frontend
npm install
npm run build
# Backend — update dependencies, apply database schema migrations and collect static files if neededcd ../binder_backend
source ../../venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --no-input
# Restart the app server (adjust to your service name)
sudo systemctl restart gunicorn

nginx serves the new binder_frontend/dist/ build and static files directly, so no nginx reload is needed unless you changed nginx.conf.


Backup

Backup the database

pg_dump -U <DB_USER> -d <DB_NAME> -f binderbase-$(date +%Y%m%d).sql

Backing up the media files

MEDIA_ROOT holds every downloaded AlphaFold structure and every imported run directory. Snapshot it alongside the database dump.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages