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│ │
└──────────────┴────────────────┴──────────────┴───────────────────────────────┘
| Script | Purpose | Records |
|---|---|---|
frappe_setup_custom_doctypes.py | Creates 34 custom DocTypes in dependency order | 34 schemas |
frappe_setup_custom_fields.py | Creates 15 custom fields on 5 built-in DocTypes | 15 fields |
frappe_seed_rbac.py | Seeds RBAC configuration (Frappe native roles, CTMS roles, resources, permissions, nav) | 539 records |
frappe_seed_master_data.py | Seeds study-design lookups, medical departments, laboratory items, and drug catalogue | 88 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)
| # | DocType | Category | Naming |
|---|---|---|---|
| 1 | CTMS Role | RBAC | field: role_name |
| 2 | CTMS Resource | RBAC | field: resource_name |
| 3 | CTMS Action | RBAC | field: action_name |
| 4 | Study Status | Clinical | field: study_status |
| 5 | Study Type | Clinical | field: study_type_name |
| 6 | Study Phase | Clinical | field: study_phase |
| 7 | Event Type | Clinical | field: event_type_name |
| 8 | Site Status | Clinical | field: site_status_name |
| 9 | Intervention Model | Study Design | field: intervention_model_name |
| 10 | Masking | Study Design | field: masking_name |
| 11 | Control | Study Design | field: control_name |
| 12 | Allocation | Study Design | field: allocation_name |
| 13 | Classification | Study Design | field: classification_name |
| 14 | Purpose | Study Design | field: purpose_name |
| 15 | zynomi_drug_master | Drug Master | naming_series: DRG-.#### |
Phase 2 — Links to Phase 1 (5 DocTypes)
| # | DocType | Category |
|---|---|---|
| 16 | CTMS Permission | RBAC |
| 17 | CTMS Navigation | RBAC |
| 18 | Study Event | Clinical |
| 19 | Site Location | Clinical |
| 20 | Study | Clinical |
Phase 3 — Links to Phase 2 (5 DocTypes)
| # | DocType | Category |
|---|---|---|
| 21 | CTMS Nav Permission | RBAC |
| 22 | Site | Clinical |
| 23 | Subject | Clinical |
| 24 | Consent | Clinical |
| 25 | FamilyMedicalHistory | Clinical |
Phase 4 — Links to Phase 3 (9 DocTypes)
| # | DocType | Category |
|---|---|---|
| 26 | Study Sites | Clinical |
| 27 | study_personnel | Clinical |
| 28 | PhysicalExamination | CRF |
| 29 | AdverseEvents | CRF |
| 30 | CRF_FORM_DEMOGRAPHICS | CRF |
| 31 | CRF_FORM_VITAL_SIGNS | CRF |
| 32 | CRF_FORM_MEDICAL_HISTORY | CRF |
| 33 | Study CRF | CRF |
| 34 | CRF_FORM_MICRONUTRIENTS_ANTIOXIDANTS_HIV_INFECTION | CRF |
Custom Fields Inventory (15 fields on 5 DocTypes)
The custom fields script adds CTMS-specific fields to Frappe's built-in DocTypes:
| Built-in DocType | Fields | Description |
|---|---|---|
| Healthcare Practitioner | 4 | education, experience, biography, surgeries |
| Vital Signs | 2 | study_event (Link → Study Event), study (Link → Study) |
| Patient | 2 | height, weight |
| Drug Prescription | 4 | send_reminders, snooze_after, start_date, end_date |
| Nursing Task | 1 | clinical_trial (Select) |
Master Data Inventory (88 records)
Study Design Lookups (31 records)
| DocType | Records | Values |
|---|---|---|
| Intervention Model | 4 | Crossover, Factorial, Parallel, Single Group |
| Masking | 5 | Double Blind, Open Label, Quadruple Blind, Single Blind, Triple Blind |
| Control | 5 | Active, Dose Comparison, Historical, No Treatment, Placebo |
| Allocation | 3 | Non-Randomized, Not Applicable, Randomized |
| Classification | 7 | Bioavailability, Bioequivalence, Efficacy, Pharmacodynamics, Pharmacokinetics, Safety, Safety/Efficacy |
| Purpose | 7 | Basic 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)
| Record | Description |
|---|---|
| Clinical Trial | Department 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 Code | Item Name | Description |
|---|---|---|
| Laboratory | Laboratory | Laboratory |
| Imaging | Imaging | Imaging |
| USG Abdomen | Ultrasound Abdomen | Ultrasound Abdomen |
| X-Ray Chest | Chest 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:
| Dependency | Phase |
|---|---|
| No custom DocType links | Phase 1 |
| Links to Phase 1 DocTypes | Phase 2 |
| Links to Phase 2 DocTypes | Phase 3 |
| Links to Phase 3 DocTypes | Phase 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
| Trigger | Action |
|---|---|
| New DocType created in zynomi | Add to frappe_setup_custom_doctypes.py + schema JSON |
| New custom field on built-in DocType | Add to frappe_setup_custom_fields.py |
| New lookup values added | Add to frappe_seed_master_data.py |
| New drugs added to catalogue | Add to frappe_seed_master_data.py |
| New laboratory items needed | Add to frappe_seed_master_data.py (LABORATORY_ITEM_DATA) |
| New medical departments needed | Add to frappe_seed_master_data.py (MEDICAL_DEPARTMENT_DATA) |
| RBAC roles/permissions changed | Update frappe_seed_rbac.py |
| Field added/removed on DocType | Update 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.jsonif DocType fields changed - Update this documentation page
- Update the Initial Setup Guide
- Run
--dry-runagainst 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):
- Run the comparison script above to identify differences
- Fetch the correct schema from zynomi (source of truth)
- Update the provisioning script
- Re-run the script against the drifted instance
Missing Link Target
If you get a "LinkValidationError" when seeding data, ensure the linked DocTypes and records exist:
- Run Stage 1 first (DocTypes)
- Then Stage 2 (Custom Fields)
- Then Stage 3 (RBAC — including Frappe native roles)
- Then Stage 4 (Master data)
- 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
Roleentries (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