Skip to main content

Baseline Management & Script Maintenance

This guide explains how the CTMS provisioning scripts are structured, how to keep them up-to-date, and how to compare Frappe instances to ensure consistency.

Overview​

The CTMS platform uses four provisioning scripts to bring a new Frappe instance to production-ready state:

┌──────────────────────────────────────────────────────────────────────────────┐
│ New Frappe Instance Setup │
├──────────────┬────────────────┬──────────────┬───────────────────────────────┤
│ Stage 1 │ Stage 2 │ Stage 3 │ Stage 4 │
│ DocTypes │ Custom Fields │ RBAC (539) │ Master Data (88) │
│ (34) │ (15) │ ────────── │ ────────── │
│ ────────── │ ────────── │ 4 native │ 6 lookup tables (31) │
│ 4 phases │ 5 DocTypes │ 4 CTMS roles│ 1 medical dept │
│ dependency- │ 15 fields │ 24 resources│ 4 laboratory items │
│ ordered │ │ 8 actions │ drug catalogue (52) │
│ │ │ 488 perms │ │
│ │ │ 11 nav items│ │
└──────────────┴────────────────┴──────────────┴───────────────────────────────┘
ScriptPurposeRecords
frappe_setup_custom_doctypes.pyCreates 34 custom DocTypes in dependency order34 schemas
frappe_setup_custom_fields.pyCreates 15 custom fields on 5 built-in DocTypes15 fields
frappe_seed_rbac.pySeeds RBAC configuration (Frappe native roles, CTMS roles, resources, permissions, nav)539 records
frappe_seed_master_data.pySeeds study-design lookups, medical departments, laboratory items, and drug catalogue88 records

Source of Truth​

All provisioning data is embedded directly in the Python scripts as canonical dictionaries. This ensures:

  • Self-contained: No external file dependencies at runtime
  • Version-controlled: Every change is tracked via Git commits
  • Reproducible: Any instance can be provisioned from scratch
  • Auditabe: Full history of what changed and when

The external file scripts/frappe-migration/custom_doctypes_schema.json is a reference archive of all 34 DocType field schemas, but is not read by any script at runtime. It exists for documentation and comparison purposes.

Custom DocType Inventory (34 total)​

Phase 1 — Standalone (15 DocTypes)​

#DocTypeCategoryNaming
1CTMS RoleRBACfield: role_name
2CTMS ResourceRBACfield: resource_name
3CTMS ActionRBACfield: action_name
4Study StatusClinicalfield: study_status
5Study TypeClinicalfield: study_type_name
6Study PhaseClinicalfield: study_phase
7Event TypeClinicalfield: event_type_name
8Site StatusClinicalfield: site_status_name
9Intervention ModelStudy Designfield: intervention_model_name
10MaskingStudy Designfield: masking_name
11ControlStudy Designfield: control_name
12AllocationStudy Designfield: allocation_name
13ClassificationStudy Designfield: classification_name
14PurposeStudy Designfield: purpose_name
15zynomi_drug_masterDrug Masternaming_series: DRG-.####
#DocTypeCategory
16CTMS PermissionRBAC
17CTMS NavigationRBAC
18Study EventClinical
19Site LocationClinical
20StudyClinical
#DocTypeCategory
21CTMS Nav PermissionRBAC
22SiteClinical
23SubjectClinical
24ConsentClinical
25FamilyMedicalHistoryClinical
#DocTypeCategory
26Study SitesClinical
27study_personnelClinical
28PhysicalExaminationCRF
29AdverseEventsCRF
30CRF_FORM_DEMOGRAPHICSCRF
31CRF_FORM_VITAL_SIGNSCRF
32CRF_FORM_MEDICAL_HISTORYCRF
33Study CRFCRF
34CRF_FORM_MICRONUTRIENTS_ANTIOXIDANTS_HIV_INFECTIONCRF

Custom Fields Inventory (15 fields on 5 DocTypes)​

The custom fields script adds CTMS-specific fields to Frappe's built-in DocTypes:

Built-in DocTypeFieldsDescription
Healthcare Practitioner4education, experience, biography, surgeries
Vital Signs2study_event (Link → Study Event), study (Link → Study)
Patient2height, weight
Drug Prescription4send_reminders, snooze_after, start_date, end_date
Nursing Task1clinical_trial (Select)

Master Data Inventory (88 records)​

Study Design Lookups (31 records)​

DocTypeRecordsValues
Intervention Model4Crossover, Factorial, Parallel, Single Group
Masking5Double Blind, Open Label, Quadruple Blind, Single Blind, Triple Blind
Control5Active, Dose Comparison, Historical, No Treatment, Placebo
Allocation3Non-Randomized, Not Applicable, Randomized
Classification7Bioavailability, Bioequivalence, Efficacy, Pharmacodynamics, Pharmacokinetics, Safety, Safety/Efficacy
Purpose7Basic Science, Diagnostic, Health Services Research, Prevention, Screening, Supportive Care, Treatment

Drug Master Catalogue (52 records)​

The drug catalogue contains 52 pharmaceutical compounds with:

  • Drug Code (ATC classification or custom code)
  • Drug Name
  • Drug Status (Planning / Production)
  • Dosage Form (Tablet, Capsule, Injection, Cream)

Medical Department (1 record)​

RecordDescription
Clinical TrialDepartment used for clinical trial laboratory items

Laboratory Items (4 records)​

Laboratory items power the "Add Lab Test" dropdown in the Zynexa frontend. These are standard Frappe Item records with item_group = "Laboratory".

Item CodeItem NameDescription
LaboratoryLaboratoryLaboratory
ImagingImagingImaging
USG AbdomenUltrasound AbdomenUltrasound Abdomen
X-Ray ChestChest X-Ray (PA View)Chest X-Ray (PA View)

Adding a New Custom DocType​

When a new custom DocType needs to be added to the provisioning scripts:

1. Determine the Phase​

Check if the new DocType has Link fields referencing other custom DocTypes:

DependencyPhase
No custom DocType linksPhase 1
Links to Phase 1 DocTypesPhase 2
Links to Phase 2 DocTypesPhase 3
Links to Phase 3 DocTypesPhase 4

2. Fetch the Schema from Zynomi​

import requests

url = "https://your-site.frappe.cloud"
headers = {"Authorization": "token <key>:<secret>"}

# Get the DocType schema
r = requests.get(f"{url}/api/doctype/YourDocType", headers=headers)
schema = r.json()["data"]
print(schema["fields"]) # Field definitions

3. Add to the Setup Script​

Add the DocType definition to the appropriate PHASE_N list in frappe_setup_custom_doctypes.py:

PHASE_1.append({
"name": "Your DocType",
"module": "Healthcare",
"custom": 1,
"naming_rule": "By fieldname",
"autoname": "field:your_field",
"fields": [
{"fieldname": "your_field", "fieldtype": "Data", "label": "Your Field", "reqd": 1},
# ... more fields
],
})

4. Update Counts​

Update the docstring header in the script to reflect new totals:

  • Total DocType count
  • Phase-specific count
  • Category breakdown (RBAC / Clinical / Study Design)

5. Update the External Schema​

Add the DocType to scripts/frappe-migration/custom_doctypes_schema.json for reference:

# Fetch and append to the JSON file
python3 -c "
import requests, json
# ... fetch schema and update JSON
"

6. Test​

# Dry run to verify
python frappe_setup_custom_doctypes.py --dry-run --env ../../.env.example

# Create on target instance
python frappe_setup_custom_doctypes.py --env ../../.env.example

Comparing Instances​

To verify that two Frappe instances have identical custom DocTypes:

import requests

INSTANCES = {
"instance-a": ("https://your-site.frappe.cloud", "key1:secret1"),
"instance-b": ("https://your-other-site.frappe.cloud", "key2:secret2"),
}

for name, (url, token) in INSTANCES.items():
headers = {"Authorization": f"token {token}"}
r = requests.get(f"{url}/api/resource/DocType",
params={"filters": '[["custom","=",1]]', "limit_page_length": 0},
headers=headers)
doctypes = sorted(d["name"] for d in r.json()["data"])
print(f"{name}: {len(doctypes)} custom DocTypes")
for dt in doctypes:
print(f" - {dt}")

Expected Output​

Both instances should report 34 custom DocTypes with identical names, plus 15 custom fields on 5 built-in DocTypes.

Keeping Scripts Up-to-Date​

When to Update Scripts​

TriggerAction
New DocType created in zynomiAdd to frappe_setup_custom_doctypes.py + schema JSON
New custom field on built-in DocTypeAdd to frappe_setup_custom_fields.py
New lookup values addedAdd to frappe_seed_master_data.py
New drugs added to catalogueAdd to frappe_seed_master_data.py
New laboratory items neededAdd to frappe_seed_master_data.py (LABORATORY_ITEM_DATA)
New medical departments neededAdd to frappe_seed_master_data.py (MEDICAL_DEPARTMENT_DATA)
RBAC roles/permissions changedUpdate frappe_seed_rbac.py
Field added/removed on DocTypeUpdate schema in setup script + JSON

Versioning Checklist​

When making changes to provisioning scripts:

  • Update the script's docstring header (counts, dates)
  • Update custom_doctypes_schema.json if DocType fields changed
  • Update this documentation page
  • Update the Initial Setup Guide
  • Run --dry-run against a test instance
  • Verify on both both target instances
  • Commit with a descriptive message
  • Push to main branch

Git History​

All provisioning script changes are tracked in the ctms.devops repository:

# View recent changes to provisioning scripts
git log --oneline scripts/frappe-seed/

# View what changed in a specific commit
git show <commit-hash> -- scripts/frappe-seed/

Troubleshooting​

"DocType already exists"​

This is normal — the setup script is idempotent and skips existing DocTypes. You'll see skipped=N in the output.

"Record already exists"​

Same as above — seed scripts skip existing records. This is the expected behavior for re-runs.

Instance Drift​

If instances have diverged (e.g., manual changes in Frappe UI):

  1. Run the comparison script above to identify differences
  2. Fetch the correct schema from zynomi (source of truth)
  3. Update the provisioning script
  4. Re-run the script against the drifted instance

If you get a "LinkValidationError" when seeding data, ensure the linked DocTypes and records exist:

  1. Run Stage 1 first (DocTypes)
  2. Then Stage 2 (Custom Fields)
  3. Then Stage 3 (RBAC — including Frappe native roles)
  4. Then Stage 4 (Master data)
  5. Then Stage 5 (Practitioner)

The stages must be run in order.

User Signup Fails with "Could not find Role"​

If user signup via the API returns LinkValidationError: Could not find Row #1: Role: Platform Administrator, it means the Frappe native Role entries are missing. Stage 3 creates both:

  • Frappe native Role entries (required for User role assignment via Link fields)
  • CTMS Role entries (used by the CTMS permission matrix)

Re-run Stage 3 to create both:

CTMS_INIT_STAGES=3 docker compose --env-file .env.local --profile init run --rm ctms-init