How Frappe is wired in rel-mis (Reliance / RF MIS) — REFERENCE for Swasti
Companion to
01-kickoff-brief.md. The decision in the kickoff was “Reliance route / SDK” for the mobile SDK and Frappe app structure. Treat this doc as reference, not template.Two important deltas vs. rel-mis (stated by Abhijit, post-kickoff):
- Geography model is different. Swasti does not need the
User Program Assignment+ multi-geography permission expansion that rel-mis built — Swasti’s surveyors are simpler. Read those sections (§4, §7) for context only; do not lift the model wholesale.- Bootstrap is via data import, not greenfield. Swasti has a live mForm V2 instance with form JSONs, geography masters, scheme master, member data, response history. The Frappe app is hydrated from there — see
01-kickoff-brief.md §10for the import pipeline plan. The “Replication checklist” at the end of this doc is a from-scratch-build path; the actual Swasti path is import-first.Use this doc to understand what good looks like in the rel-mis Frappe app, then design the Swasti app to match the patterns where they fit, ignore them where Swasti is simpler, and pull data from mForm V2 to populate it.
Sources mined
~/Dhwani/rel-mis/— Flutter mobile app + presentation/branding/E2E + skills + docs~/frappe-bench/apps/rf_mis/— Frappe server app (the actual backend) — NOT in~/Dhwani~/frappe-bench/apps/mobile_control/— companion Frappe app (mobile-side glue)~/Dhwani/rf-mis-v2/— exploratory v2 stack (FastAPI + Postgres + PowerSync) — not in production, not the route Swasti should take
Authoritative reading order before any work (per project rules):
~/Dhwani/rel-mis/CLAUDE.md+AGENTS.md~/Dhwani/rel-mis/docs/CODEBASE_GUIDE.md(mobile)~/frappe-bench/apps/rf_mis/docs/CODEBASE_GUIDE.md(server)~/Dhwani/rel-mis/docs/REGISTRY.md(doc index)~/Dhwani/rel-mis/skills/*.skill(project-specific skill prompts)
0. The shape, in one diagram
┌──────────────────────┐ HTTPS ┌───────────────────────────────┐
│ Flutter mobile │ ─────────────▶ │ Frappe site (rf-mis.local) │
│ ~/Dhwani/rel-mis/ │ POST │ │
│ mobile/ │ api/method/ │ apps: │
│ │ rf_mis.… │ ├ frappe │
│ RfFieldFactory │ │ ├ rf_mis (this project) │
│ extends SDK │ │ └ mobile_control │
│ FieldFactory │ │ │
└──────────────────────┘ └───────────────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌───────────────────────────────┐
│ frappe-mobile-sdk- │ │ MariaDB │
│ dhwani (private fork)│ │ (Frappe site DB) │
│ feat/rf-mis-field- │ └───────────────────────────────┘
│ extensions │
│ pub-cache only │
└──────────────────────┘
Key paths.
| Looking for | Path |
|---|---|
| Frappe site name (local) | rf-mis.local |
| Frappe staging | https://stg.rilmobileapi.mform.in |
| Frappe UAT | https://uat.rilmobileapi.mform.in |
rf_mis server app | ~/frappe-bench/apps/rf_mis/ |
mobile_control server app | ~/frappe-bench/apps/mobile_control/ |
| Flutter app | ~/Dhwani/rel-mis/mobile/ |
| Frappe SDK (pub cache) | ~/.pub-cache/git/frappe-mobile-sdk-dhwani-… |
| SDK repo | dhwani-ris/frappe-mobile-sdk-dhwani (private fork) |
| SDK feature branch | feat/rf-mis-field-extensions |
1. Frappe app structure
How rel-mis does it
~/frappe-bench/apps/rf_mis/rf_mis/ layout:
hooks.py # doc_events, scheduler, role hooks, fixtures
patches.txt # ordered migration list
rf_mis/
doctype/ # 21 RF/Swayamshree submission DocTypes + ~60 masters/options
disaster_management/doctype/ # 20 DM submission DocTypes
api/sync.py # ALL whitelisted mobile endpoints
utils/
geography.py # ancestor/descendant helpers, get_geography_link_fields()
geography_hooks.py # on_geography_update → recompute UPA allowed geo
workflow_utils.py # log_workflow_action() — the audit-log glue
permissions.py # has_permission() + get_permission_query_conditions()
patches/ # one .py per migration (idempotent backfills)
fixtures/ # role JSONs, master option seeds
page/approval_dashboard/ # custom Frappe Page (HTML/JS/CSS) — NOT Frappe Insights
21 RF/Swayamshree submission DocTypes (full list in hooks.py:39-61 _RFMIS_FORM_DOCTYPES):
Activity Form, Collective Services Register, Collective Trainings Register,
Collectives Profile Register, Community Cadre Leverage Register,
Community Cadre Partner Master, Community Cadre Profile Register,
Community Cadre Services Register, Community Cadre Trainings Register,
FPO FPC Master, Farm Mechanization to Farmer,
Member Access to Infrastructure Register, Member Capacity Building Register,
Member Profile Register, Member Technology Register,
Non-Agricultural Products Marketing and Sales, Output Marketing of Agri Products,
SHG Master Form, Sale of Agri Products, Sale of Processed Products, Scheme Master
Master / config DocTypes worth knowing (rf_mis/doctype/):
geography— single tree DocType; one row per state/district/block/GP/villageprogram— top-level program (e.g. Swayamshree, DM)program_form_registry— links a DocType to a program withis_publishedflagprogram_role— per-program role definition withcan_add / can_edit / can_approve / can_return / can_view_all_geographyprogram_workflow— workflow definition per programuser_program_assignment(UPA) — the central row: user × program × role × geography(ies). Multi-geo support via theupa_geographychild table.submission_approval_log— child Table DocType; embedded in every submission as fieldapproval_logsync_error_log— captures every server-side sync failure
What to copy for Swasti
- The same app skeleton is the right starting point. Create
swasti_mform(orswasti_v3) Frappe app, mirror layout:swasti_mform/{doctype, api, utils, permissions.py, patches, fixtures, page}. - Reuse
geography,program,program_form_registry,program_role,program_workflow,user_program_assignment,upa_geography,submission_approval_log,sync_error_logDocTypes verbatim — these are not RF-specific, they’re the Dhwani-mForm-on-Frappe platform layer. - Reuse
permissions.py,geography.py,geography_hooks.py,workflow_utils.pyverbatim. They drop in unchanged. - Reuse
mobile_controlcompanion app verbatim — it carries shared mobile-side glue (auth, fixtures).
What to change for Swasti
- New domain DocTypes for Swasti:
Member Profile Register(Swasti version — different fields from rel-mis),Scheme Application Register,Scheme Master,Document Application Register,Health Screening Register,Livelihood Register(the new 4th program). Plus child tables for follow-up rows (the audit log replaces “self-linking”). - The class-name rule (
DocType Name → DocTypeName) and thebench --site … import-docverification step are mandatory — see §13 (Hard-won lessons). - The 21-form list in
hooks.pyis hardcoded — Swasti will have its own list (~5 forms). Add yours, drop theirs.
2. Form configuration: Excel → DocType pipeline
How rel-mis does it
There is a skill prompt, not an automated pipeline. Two skills together cover the conversion:
| Skill file | Use when |
|---|---|
~/Dhwani/rel-mis/skills/mform2frappe.skill | Converting an mForm JSON file to a Frappe v16 DocType (one form at a time). Documents validation codes, restriction codes, input_type → fieldtype mapping, child-table extraction, multi-select option masters, getDynamicOption → fetch_from. |
~/Dhwani/rel-mis/skills/frappe-doctype-skill.skill | Designing/speccing DocTypes from scratch (fields, permissions, workflows, approval states, form layout). |
~/Dhwani/rel-mis/skills/frappe2mform.skill | Reverse mapping if needed for parity. |
The conversion is manual, one DocType at a time, AI-assisted. The skill enforces:
- Never change question titles — use
language[0].question[].titleexactly. - Normalise camelCase fieldnames (e.g.
gramPanchayat→grampanchayat, no underscore). - Build child DocTypes BEFORE parents.
- Multi-select pattern: Option Master (
Data, autoname=field:option_name) → Link Child DocType (Link → master) → parent usesTable MultiSelect. - Geography fields →
LinktoGeographyDocType. input_type 27(Dynamic Action) →Linkfield, NOT a Button.- After write:
bench --site rf-mis.local import-doc <path>BEFORE commit.
The pre-built form audit JSONs in ~/Dhwani/rel-mis/docs/form-audit/ (mform_…json, frappe_…json pairs) are the worked examples — copy this convention for Swasti.
What to copy for Swasti
- Use both skills as-is for any mForm V2 form being migrated. They are not RF-specific.
- Mirror the
docs/form-audit/directory: keep the original mForm JSON beside the converted Frappe DocType JSON for diff/audit. - Use the same
bench import-docdiscipline.
What to change for Swasti
- Module name in the skill prompt step 0 →
swasti_mform(the skill explicitly asks for module name). - Excel form-design template → still the canonical authoring format (per kickoff D1). The flow is Excel design → AI/manual DocType JSON via mform2frappe skill → bench import-doc → commit.
3. Mobile (Flutter) ↔ Frappe integration
How rel-mis does it
Auth. SDK-owned. lib/screens/rf_login_screen.dart calls authService.login() from the SDK. Token storage lives in the SDK; the app does not touch it. Anti-pattern: never pub upgrade, never edit SDK directly.
Sync API surface (whitelisted methods at ~/frappe-bench/apps/rf_mis/rf_mis/rf_mis/api/sync.py):
| Endpoint | Path | Purpose |
|---|---|---|
submit_form | rf_mis.rf_mis.api.sync.submit_form (sync.py:151) | Insert new doc; runs apply_workflow(doc, "Submit") to transition Draft → Submitted. Validates against Program Form Registry + is_published. |
update_form | …api.sync.update_form (sync.py:245) | Update an existing doc. |
get_form_definition | …api.sync.get_form_definition (sync.py:332) | Returns DocType + DocFields with depends_on, mandatory_depends_on, and our description-token decorations. |
get_user_forms | …api.sync.get_user_forms | Returns Program Form Registry rows where is_published=1 for the user’s program; mobile caches as cached_user_forms in SQLite sync_meta. |
Payload shape (from submit_form):
{
"doctype": "Member Profile Register",
"program": "Swayamshree",
"data": { "fieldname": "value", … },
"metadata": {
"device_id": "...", "app_version": "...",
"created_on_device": "...", "submitted_on_device": "...",
"latitude": 0.0, "longitude": 0.0,
"gps_accuracy_metres": 0.0, "location_captured": true
}
}
Returns: {name, workflow_state}. Errors raise frappe.ValidationError and a row is written to Sync Error Log DocType (_log_sync_error() — every exception logged since fix 9dfd8e1 2026-04-29).
Offline strategy. Mobile-side DeltaSyncService (mobile/lib/services/delta_sync_service.dart):
- Full sync on first login: downloads all DocType metadata + reference data into local SQLite (
rf_mis_sync.db). - Delta sync after that: only records changed since
last_sync_timestamp. - Submission queue:
mobile/lib/database/submission_queue_table.dart. Status enum:draft / pending / failed / syncing / synced. - Sync invalidation hook: every cache key MUST have an explicit invalidation in
DeltaSyncService.sync(). - Hardcoded sync targets at
delta_sync_service.dart:609(_syncLinkTargets) — list of masters to pull offline. Add new ones here.
File uploads. Standard Frappe Attach / Attach Image field uploads via SDK; max file size enforced server-side. Mobile sends Int/Float as strings — server controllers must cast (val = int(val) or float(val)).
SDK. frappe-mobile-sdk-dhwani is a private fork pinned to feat/rf-mis-field-extensions. The mobile app extends it via RfFieldFactory extends FieldFactory (mobile/lib/extensions/rf_field_factory.dart). All field-rendering / validation logic lives in:
- Description tokens — strings on the DocField
descriptionfield (the SDK reads them on mobile and Frappe Desk both reads them). Examples:int-range:1-10,date-max:today,photos-range:5-10,filter-sum:parent|link|opt=field,excel-allowed:true. - Generic SDK evaluators — e.g.
DependsOnEvaluator. Most behaviour is data-driven from DocField metadata. - Per-form Dart in
RfFieldFactory— last resort. Already 44 sites of_wrapWithSomeDependsOnIfNeededand counting.
What to copy for Swasti
- The same SDK on the same branch. Pin Swasti’s mobile app to
feat/rf-mis-field-extensions(or fork it if Swasti needs SDK divergence — recommend NOT diverging). - The 4 sync endpoints and the payload shape — they are platform contract, not RF-specific.
- The submission queue + delta sync model.
- Description-token vocabulary (no per-form Dart unless unavoidable).
- Sync Error Log discipline: every exception logged, no
ValidationErrorshort-circuits.
What to change for Swasti
- Replace
RfFieldFactorywithSwastiFieldFactory(or keep extending the same class — see decision below). - The hardcoded
_syncLinkTargetsmaster list indelta_sync_service.dart:609will be Swasti’s masters: Geography, Scheme Master, Member Profile Register, Document Master (probably), Health Screening masters. DmConfig.dmTreeandmodule_config.dart’sTrackConfigs._{member,cadre,collectives,dm}are hardcoded RF home structures that won’t work for Swasti. Replace wholesale with a Swasti home tree (4 programs: Member Profile master + Scheme + Document + Health + Livelihood). See §11 of mobile guide.- Decision needed: fork the mobile app vs. share. The cleanest answer is fork the app, share the SDK. The hardcoded program/track structures in mobile are the non-shareable part.
4. Geography model — the most directly reusable piece for Swasti
Swasti’s biggest pain point (kickoff §3 P1/P2). Rel-mis already solved this. Copy it.
How rel-mis does it
Geography DocType (rf_mis/doctype/geography/): single tree DocType. Each row is one node (state OR district OR block OR GP OR village). is_group=1 for non-leaf nodes; parent_geography links upward. Display field is geography_name — never the autoincrement code.
User Program Assignment (UPA) (rf_mis/doctype/user_program_assignment/): one row per (user, program). Fields: user, program, role_key, is_active, plus child table upa_geography (Table of Geography Links) — this is the multi-geography support. Each user can have N geographies under one UPA.
Allowed-geography pre-computation. UPA controller’s _recompute_allowed_geography() walks each assigned geography’s ancestors + descendants and stores the flat set on the doc. Reads in permissions.py:get_permission_query_conditions() use this set. Recompute is also triggered when a Geography node is edited via geography_hooks.py:on_geography_update().
Permission query conditions (permissions.py:get_permission_query_conditions()):
# Hot path, simplified
upa = _get_active_upa(user) # cached in Redis per request
program_cond = f"`tab{doctype}`.program = '{upa['program']}'"
if role.can_view_all_geography: # state-level approvers
return program_cond # no geo filter
# else: union over all of the user's allowed geographies, expanded ancestors + descendants
geo_clauses = " OR ".join(
f"`tab{doctype}`.{f} IN ({allowed_geos})"
for f in get_geography_link_fields(doctype) # any Link → Geography field on the doctype
)
return f"{program_cond} AND ({geo_clauses})"
has_permission() complements with per-document checks — rejects docs in other programs, allows write for approvers (even if can’t edit fields), denies delete except System Manager.
Geography prefill on mobile. Once a member is selected on a child form, the member’s geography flows through SDK fetch_from chains. If the mobile factory’s LinkDisplayConfig.filterByGrampanchayat is true for a master that doesn’t have a grampanchayat field, the picker silently shows zero rows — anti-pattern #24 (rel-mis/docs/CODEBASE_GUIDE.md mobile §1.24, diagnosed 2026-04-29).
Migration patch. Single-geo → multi-geo migration was a separate patch: rf_mis/patches/migrate_upa_to_multi_geo.py. Read it before designing Swasti’s UPA seed.
What to copy for Swasti — verbatim
GeographyDocType definition.User Program Assignment+UPA Geographychild table.permissions.py+geography.py+geography_hooks.py.- The
_get_active_upaRedis cache pattern — request-scoped, keyupa_<user>. - The
get_geography_link_fields()helper that scans DocType meta for fields linking toGeography.
What to change for Swasti
-
Per-program scope toggle (kickoff D2). Rel-mis hard-codes “always geography-scoped within program”. Swasti needs:
- Scheme & Document programs → user-scoped (surveyor only sees their own uploads).
- Health & Livelihood programs → geography-scoped (any surveyor in the geography sees it).
Cleanest place to add this: a flag on the
ProgramDocType (scoping_mode = "user" | "geography") consumed byget_permission_query_conditions(). Whenuser, appendtab{doctype}.owner = {user}. Whengeography, do today’s logic.This is the single biggest delta vs rel-mis. Build it from the start, not as a retrofit.
-
Hidden member-ID prefill (kickoff D2). Mobile-side: when a child form (Scheme/Health) is opened from a Member Profile, inject a hidden
memberLink field already populated. App-layer prefill, not data-layer. No change required server-side — the field is just a Link to Member Profile Register on every child form, defaulted from the navigation context. Pattern already exists in rel-mis for Activity Form ↔ Member.
5. Audit / history pattern — replaces mForm self-linking
How rel-mis does it
Two layers combine to give the audit trail Swasti’s kickoff D3 specifies:
-
Frappe native Workflow — defined per-program via
Program WorkflowDocType. Defines states (Draft, Submitted, District Approved, State Approved, SPM Signed Off, Returned) and transitions between them with role gating.apply_workflow(doc, action)performs transitions. -
approval_logTable field — every submission DocType has a child Table field namedapproval_loglinked to theSubmission Approval Logchild DocType. Thelog_workflow_action()hook (rf_mis/utils/workflow_utils.py:13) fires onon_updatefor every submission DocType (wired inhooks.py:88-101for all 21 + 20 form DocTypes via doc_events). Wheneverworkflow_statechanges, it appends a row:doc.append("approval_log", { "action": f"{before_state} → {after_state}", "from_state": …, "to_state": …, "actioned_by": frappe.session.user, "actioned_by_name": …, "actioned_at": now(), "remarks": "", }) doc.db_update() # avoids recursive on_update frappe.db.commit()
The hook has a guard (if not doc.meta.get_field("approval_log"): return) so masters can be wired generically without crashing.
Frappe also gives you version history for free — every field change on every DocType is in the Version table. Use this for non-workflow changes (free).
What to copy for Swasti — verbatim
Submission Approval Logchild DocType — drop in.workflow_utils.py:log_workflow_action()— drop in.- The
doc_eventswiring inhooks.py— adapt to Swasti’s form list. Program WorkflowDocType.
What to change for Swasti
- The kickoff-decided model is not exactly approval state transitions — it’s status updates on a single record. For follow-ups:
- Status field on the parent record (e.g.,
current_statuson Scheme Application) is the live state. - Each follow-up = a row in a child table (e.g.,
followup_logTable on Scheme Application) capturing the visit number, date, due date, status snapshot, remarks, surveyor. - Don’t reinvent: reuse the same child-table-as-audit-log pattern as
approval_log, but renamedfollowup_logand capturing followup-specific columns. The hook fires on field-set, not on workflow_state change.
- Status field on the parent record (e.g.,
- Frappe’s native
Versionlog already captures every field change — use it for “show me all status changes for this scheme” without writing a custom log. - The kickoff health screenshot (
screenshots/10-document-application-27min.jpg) shows the exact UI Swasti expects: a single member record with a list of timestamped follow-up visits. This child-table pattern delivers that view directly in Frappe Desk; the mobile renders the same table read-only.
6. Dashboard approach
How rel-mis does it
No Frappe Insights, no Superset. Custom Frappe Page at rf_mis/page/approval_dashboard/ — plain .html / .js / .css. JS makes whitelisted-method calls and renders charts client-side.
Design system: “Refined Utility” — codified in skill rf-mis-frappe-branding.skill and frappe-dashboard-design.skill. 8 binding rules (zero whitespace waste, names not IDs, Indian number formatting, India heatmap, Frappe sidebar untouched, mock data banner during dev, universal filter strip, light theme).
Approval portal at /app/rf-mis-portal (set as System Manager home page in hooks.py). Approvers see:
- Sidebar = Frappe Desk standard.
- Dashboard cards: Pending on Me / Approved This Month / Returned (computed from
approval_log+ workflow state). - Form list view with custom list_columns (configured via Program Form Registry).
- Form detail view = Frappe Desk form view with inline workflow buttons.
Pagination. Standard Frappe list-view pagination (page-based, configurable rows per page) — NOT load-more, NOT append. This matches kickoff D6.
What to copy for Swasti
- Same approach: custom Frappe Page for the Swasti dashboard. Don’t pitch Frappe Insights, don’t return to Superset.
- Same skill prompts (
frappe-dashboard-design,rf-mis-frappe-branding) — adapt the brand to Swasti’s logo/palette. - Same paginated list views.
- The approval portal pattern (
/app/swasti-portal) for any internal Swasti staff who need a web view.
What to change for Swasti
- Swasti dashboard cards differ from RF (no district/state approval gates). Likely Swasti cards: Pending Follow-ups Today / This Week / Overdue / Members Profiled / Schemes Applied / Documents Pending / Health Screenings.
- Brand: Swasti logo, Swasti palette. Re-skin via the branding skill.
- Build it scalable from the start (kickoff P3) — the rel-mis dashboard is per-form/program; Swasti’s must list members + cross-program follow-ups.
7. Permissions / roles
How rel-mis does it
Role model uses three layers:
- Frappe roles (DocType JSON
permissions[]). e.g.,RF MIS User,RF MIS Editor,System Manager. These gate the New button visibility and base-level access. - Program Role DocType — per-program role with capability flags:
can_add,can_edit,can_approve,can_return,can_view_all_geography. e.g.,FIA-Block,District Approver,State Approver,SPM-Swayamshree. - User Program Assignment (UPA) — pins a user to (program, role_key, geography(ies)).
Hot path (permissions.py:has_permission):
System Manageralways allowed.- Look up UPA for user. If none → deny.
- Doc must belong to user’s program → else deny.
- Capability flags on
Program Roledecide read/write/approve/return. can_view_all_geographyflag bypasses the geography filter (state-level approvers).
The has_permission returning True for write even when a user can’t edit fields is intentional — Frappe needs write for apply_workflow to fire. Form fields are made read-only via DocType Field Permissions or workflow-state-based readonly logic.
Test users: ~/Dhwani/rel-mis/docs/test-accounts.md lists per-role test accounts seeded by apps/rf_mis/scripts/seed_data.py. 7 records per form in the 7 different workflow states (draft / submitted / district_approved / state_approved / spm_signed_off / returned / re-submitted).
What to copy for Swasti
- The whole 3-layer role model. Verbatim.
permissions.py— drop in.- The seed script pattern (idempotent, re-runnable, generates per-role test users + sample data).
What to change for Swasti
- Swasti’s roles are different. Likely:
Surveyor(basic-tech field worker),Block Coordinator,Program Manager,Admin. Health program adds Anganwadi / ASHA / Nurse roles. - No multi-tier district→state approval chain (Swasti doesn’t have approvals on submissions per the kickoff).
- The
can_view_all_geographyflag is still useful (Program Manager / Admin sees everything).
8. Bulk upload
How rel-mis does it
Bulk upload exists. Done via Frappe Desk’s standard Data Import tool (per DocType). PM uploads a CSV/Excel; Frappe inserts rows. Each row goes through controller validate() and before_insert hooks. PM-uploaded rows are owned by the admin user, not by the field surveyor.
Known issue caught 2026-04-30 (commit 520ae8d): if a controller method references a dropped field, bulk import silently fails with AttributeError. Always grep controllers for dropped fieldnames in the same commit (anti-pattern #26 in server CODEBASE_GUIDE.md).
What to copy for Swasti
- Standard Frappe Data Import — don’t custom-build unless required.
- The same anti-pattern audit (grep for dropped fieldnames before commit).
What to change for Swasti
- Direct kickoff link: bulk-uploaded admin members were invisible to surveyors in rel-mis until permissions were broadened. With Swasti’s per-program scoping mode (§4 above), if Member Profile is geography-scoped (which is what the kickoff implies for Health), bulk-uploaded members will be visible to all surveyors in that geography — the issue resolves itself.
- For user-scoped programs (Scheme, Document), bulk upload still hits the rel-mis pain. Decide on a bulk-upload flow that assigns rows to surveyors at insert time (e.g., a
surveyorfield on Member Profile populated during import).
9. Multi-language
How rel-mis does it
Not implemented. rel-mis is English-only. There is no precedent for multi-language in this stack — Swasti is pioneering it.
What to change for Swasti
- This is net-new work (kickoff P7 + D8). Not in rel-mis to copy.
- Frappe’s native i18n: view labels translatable (Translation DocType) but data stays in English. Free-text fields are stored as user-typed.
- Plan: treat label translation as standard Frappe i18n; treat free-text data as a separate problem (downstream visualisation must handle mixed scripts).
- See kickoff brief §3 P7 and §4 D8 for the open questions.
10. QA / regression
How rel-mis does it
Frappe (web) E2E: Playwright at ~/Dhwani/rel-mis/tests/frappe-e2e/. Specs in specs/:
00-auth.spec.ts
cadre/
collectives/
masters/
member/
Has playwright.config.ts, globalSetup.ts, globalTeardown.ts, fixtures, helpers, scripts. Standard Playwright structure.
Mobile (Flutter): unit + widget tests at mobile/test/. No CI gate — tests run locally before commit. integration_test/ directory exists but is empty (dependency in pubspec.yaml for future use). Tech debt: wire flutter test into CI on PRs to develop.
PM feedback / regression cycle: skill rf-mis-pm-feedback-cycle.skill codifies the 6-phase loop (Evaluate → Plan → Fix → Verify → Release → Respond) with hard STOPs at each gate. Worth lifting wholesale.
What to copy for Swasti
- Whole Playwright setup (
tests/frappe-e2e/) — it’s project-agnostic enough; rename specs to Swasti forms. rf-mis-pm-feedback-cycle.skill→ adapt toswasti-pm-feedback-cycle.skill.- Mobile unit/widget test conventions.
What to change for Swasti
- Per kickoff D10: dedicated QA PC + automated regression on every APK is the Swasti requirement. rel-mis doesn’t have this yet. Build:
- A dedicated machine running an Android emulator + Frappe staging.
- AI/MCP runner that on every APK drop installs, runs the regression suite, reports.
- Frappe-side: Playwright suite running on every staging push (already exists — wire it to CI).
- Sachin (QA) owns first-run feature testing only; regression is automated.
11. Deployment / infra
How rel-mis does it
Local dev: bench standard. Site rf-mis.local. App install order in apps.txt:
frappe
rf_mis
mobile_control
Site setup:
bench new-site rf-mis.yourdomain.com --db-name rf_mis --admin-password ...
bench --site rf-mis.yourdomain.com install-app rf_mis
bench --site rf-mis.yourdomain.com install-app mobile_control
bench --site rf-mis.yourdomain.com migrate
bench build --app rf_mis
Post-install: Website Settings (app_name, logo); role home page already in hooks.py; login CSS via web_include_css; run apps/rf_mis/scripts/seed_data.py (idempotent — creates users, UPAs, program roles, sample records).
Branching / deploy:
frappe_rf_misrepo — feat/fix branches offstaging→ push tostaging→ auto-deploys. NO PRs.rel-misrepo (mobile) — feat branches offdevelop→ PR →develop.frappe_mobile_controlandfrappe-mobile-sdk— feature branches only, single PR at project end.- Commit deploy tags (parsed by CI):
[migrate](DocType JSON / patches),[restart](Python only),[build][restart](JS/CSS), no tag for docs-only.
APK builds:
- Firebase/staging:
flutter build apk --debug(PM testing). - Play Store / production:
flutter build apk --release. - Output:
mobile/build/app/outputs/flutter-apk/. - Release notes file per version:
docs/releases/vX.Y.Z.md.
rf-mis-v2/ is exploratory. Postgres + FastAPI + PowerSync. There’s a phase-2 plan at ~/Dhwani/rel-mis/docs/superpowers/plans/2026-04-02-v2-phase2-powersync.md with a 6-bucket sync rule design and Alembic migrations. Not in production. Do not adopt for Swasti — kickoff says replicate rel-mis.
What to copy for Swasti
- Same
benchsetup pattern. Same install order (frappe / swasti_mform / mobile_control). - Same seed-script discipline.
- Same commit deploy tags.
- Same APK build / release notes process.
What to change for Swasti
- New site name (
swasti.local/stg.swasti.…/uat.swasti.…). - New repo names:
swasti_mform(Frappe app),swasti-mobile(Flutter mobile). mobile_controlandfrappe-mobile-sdk-dhwaniare shared infrastructure. Add Swasti to their consumer list, but don’t fork them per project.
12. Quirky / hard-won lessons (read this before any code)
These are the anti-patterns rel-mis has already paid for. Each one will bite Swasti the same way.
| # | Lesson | Where it bit |
|---|---|---|
| 1 | DocType controller class name MUST equal doctype_name.replace(" ", "") — wrong name → silent DELETE on every migrate | server CODEBASE_GUIDE.md §1.13 |
| 2 | Table MultiSelect child DocType MUST have a Link field — never Data or Select | §1.14 |
| 3 | Display fields must be human-readable (geography_name, not codes) | §1.15 |
| 4 | mForm ID fields (drive_id, training_id, school_id, participant_unique_id) are obsolete — use Frappe autoname | §1.16 |
| 5 | mForm “No of X” count fields are obsolete — surface count in section heading | §1.17 |
| 6 | Geography Column Break anywhere between state→village fields kills GP auto-fill on mobile | §1.18 |
| 7 | Child DocType editable_grid = 0 if >3 fields OR has conditional fields; otherwise 1 | §1.19 |
| 8 | After writing any DocType JSON: bench --site rf-mis.local import-doc <path> BEFORE commit | §1.20 |
| 9 | reqd:1 + depends_on is an anti-pattern — use mandatory_depends_on for conditional required | §1.21 |
| 10 | Mobile sends Int/Float values as strings — server controllers must cast (val=int(val); see non_agricultural_products_marketing_and_sales.py) | §1.22 |
| 11 | frappe.db.db_name does not exist on MariaDB — use frappe.conf.db_name | §1.23 |
| 12 | Sync Error Log: never except frappe.ValidationError: raise short-circuit. Log every exception | §1.24 |
| 13 | Hardcoded mobile config shadows server unpublishes — when unpublishing in Program Form Registry, also remove from mobile/lib/extensions/dm_config.dart and module_config.dart | §1.25 + mobile §1.18 |
| 14 | Drop a field from DocType JSON → grep all controllers for self.<fieldname> and db.*(filters={'<fieldname>'…}) and helper methods. Bulk-import will fail silently otherwise | §1.26 |
| 15 | LinkDisplayConfig.filterByGrampanchayat defaults to true — set false for non-GP-level masters or picker shows zero rows silently | mobile CODEBASE_GUIDE.md §1.24 |
| 16 | Sync Now does NOT propagate server deletes / registry unpublishes — installed apps hold stale entries until reinstall. Mobile rebuild = release | mobile §1.22 |
| 17 | SDK DependsOnEvaluator doesn’t handle `(doc.field | |
| 18 | No pub upgrade ever. Always pub get. SDK pinned to feature branch | mobile §1.14 |
| 19 | Hot restart doesn’t pick up pub-cache SDK edits — flutter clean && flutter run | mobile §1.15 |
| 20 | FormBuilder nulls from unvisited tabs must not overwrite _draftData | mobile §1.16 |
The fact that all 20 of these are documented and have RCAs is the value. Re-read both CODEBASE_GUIDE.md files end-to-end before writing code.
13. Replication checklist for Swasti (ordered)
Concrete bootstrap sequence for a developer starting Swasti’s Frappe app from rel-mis:
- Repo skeletons.
- Create
swasti_mformFrappe app:bench new-app swasti_mform. Copyhooks.py,pyproject.toml,patches.txt, therf_mis/{api,utils,permissions.py,fixtures,patches,page}skeleton from~/frappe-bench/apps/rf_mis/. Trim DocType-specific code. - Create
swasti-mobileFlutter repo. Copy~/Dhwani/rel-mis/mobile/{lib,test,android,ios,pubspec.yaml,…}. Stripdm_config.dartand Track configs (will rebuild for Swasti’s 4 programs). - Add
swasti_mformto themobile_controlconsumer list (no fork). - Pin Flutter app to the same SDK branch (
feat/rf-mis-field-extensions).
- Create
- Platform DocTypes (drop in verbatim). From
rf_mis/doctype/, copy:geography,program,program_role,program_workflow,program_form_registry,user_program_assignment,upa_geography,submission_approval_log,sync_error_log. Runbench --site swasti.local import-docfor each. - Permissions / utils (drop in verbatim). Copy
permissions.py,utils/geography.py,utils/geography_hooks.py,utils/workflow_utils.py,utils/role_sync.py. Wiredoc_events,has_permission,permission_query_conditionsinhooks.py. - Domain DocTypes (build new — use mform2frappe skill). For each Swasti V2 mForm JSON:
- Use
mform2frappe.skillto generate the Frappe DocType JSON. - Mirror the
docs/form-audit/convention: keep mForm and Frappe JSONs side by side. - Wire each new DocType into
hooks.py_SWASTI_FORM_DOCTYPESlist. - For each: register in
Program Form Registry(program × form_doctype × is_published). - Verify with
bench import-docBEFORE commit.
- Use
- Per-program scoping mode (NEW for Swasti). Add
scoping_mode = "user" | "geography"field toProgramDocType. Branchpermissions.py:get_permission_query_conditions()on it:user→ also ANDowner = user;geography→ today’s logic. - Audit / follow-up child tables. For Scheme Application Register, Health Screening Register, etc., add a
followup_logTable field linked to a newFollowup Logchild DocType (copy shape ofSubmission Approval Log). Wire a hook to append on status field change (mirrorlog_workflow_action’s pattern but trigger on field-set instead of workflow-state). - Mobile factory. Rename
RfFieldFactory→SwastiFieldFactory(or keep extending — decide based on whether Swasti diverges). Re-authormodule_config.dartwith Swasti’s 4 programs (Member Profile master + Scheme + Document + Health + Livelihood). Re-authordm_config.dart(or delete if not needed). Update_syncLinkTargetswith Swasti masters. - Hidden member-ID prefill (mobile). When opening a child form from Member Profile context, inject a hidden
memberLink field defaulted to that member. App-layer only. - Custom dashboard. Create
swasti_mform/page/swasti_dashboard/(HTML/JS/CSS). Applyfrappe-dashboard-designskill rules. Cards = Pending Follow-ups Today / This Week / Overdue + per-program counts. - Branding. Apply
rf-mis-frappe-branding.skillwith Swasti’s logo + palette to the desk theme. - Multi-language. Net-new. Frappe Translation DocType for view labels. Free-text data stays as typed. Decide visualisation strategy for mixed-script free text.
- Seed script.
swasti_mform/scripts/seed_data.py— idempotent. Creates Programs, Roles, sample Geographies, test users, UPAs, sample members, sample schemes/health records. - E2E tests. Copy
~/Dhwani/rel-mis/tests/frappe-e2e/→swasti-mobile/tests/frappe-e2e/. Rename specs to Swasti’s forms. - PM feedback loop skill. Copy
rf-mis-pm-feedback-cycle.skill→swasti-pm-feedback-cycle.skill. Update credentials section. - Project docs. Mirror the rel-mis doc structure:
docs/CODEBASE_GUIDE.md(mobile + server),docs/REGISTRY.md,docs/test-accounts.md,docs/deployment.md,docs/pm-handover/{01-user-management.md, 02-form-registration.md, 03-portal-configuration.md, 04-troubleshooting.md}. These are one-time-write, then rotate. - Branching + deploy. Follow rel-mis branching matrix. Configure CI to parse
[migrate][restart][build]commit tags. - QA automation. Set up dedicated QA PC + emulator per kickoff D10. Wire Playwright + Flutter tests to CI on each push.
- Deploy.
benchsetup → installfrappe / swasti_mform / mobile_control→ migrate → seed → APK build (debug → staging, release → Play Store).
14. References (paths to file when starting work)
- Server entry points:
~/frappe-bench/apps/rf_mis/rf_mis/rf_mis/api/sync.py:151,245,332 - Workflow audit:
~/frappe-bench/apps/rf_mis/rf_mis/rf_mis/utils/workflow_utils.py:13 - Permissions:
~/frappe-bench/apps/rf_mis/rf_mis/rf_mis/permissions.py - Geography:
~/frappe-bench/apps/rf_mis/rf_mis/rf_mis/utils/geography.py,geography_hooks.py - Hardcoded form lists:
~/frappe-bench/apps/rf_mis/rf_mis/hooks.py:39-101 - Mobile factory:
~/Dhwani/rel-mis/mobile/lib/extensions/rf_field_factory.dart - Mobile sync:
~/Dhwani/rel-mis/mobile/lib/services/delta_sync_service.dart - Mobile config:
~/Dhwani/rel-mis/mobile/lib/extensions/dm_config.dart,mobile/lib/models/module_config.dart - Form audit pairs:
~/Dhwani/rel-mis/docs/form-audit/ - Skills:
~/Dhwani/rel-mis/skills/{mform2frappe,frappe2mform,frappe-doctype-skill,frappe-dashboard-design,rf-mis-frappe-branding,rf-mis-pm-feedback-cycle}.skill - Codebase guides (READ FIRST):
~/Dhwani/rel-mis/docs/CODEBASE_GUIDE.md(mobile) +~/frappe-bench/apps/rf_mis/docs/CODEBASE_GUIDE.md(server) - PM handover docs:
~/Dhwani/rel-mis/docs/pm-handover/ - E2E tests:
~/Dhwani/rel-mis/tests/frappe-e2e/ - Test accounts:
~/Dhwani/rel-mis/docs/test-accounts.md - Deployment:
~/Dhwani/rel-mis/docs/deployment.md