Swasti · mForm V2→V3

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):

  1. 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.
  2. 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 §10 for 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):

  1. ~/Dhwani/rel-mis/CLAUDE.md + AGENTS.md
  2. ~/Dhwani/rel-mis/docs/CODEBASE_GUIDE.md (mobile)
  3. ~/frappe-bench/apps/rf_mis/docs/CODEBASE_GUIDE.md (server)
  4. ~/Dhwani/rel-mis/docs/REGISTRY.md (doc index)
  5. ~/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 forPath
Frappe site name (local)rf-mis.local
Frappe staginghttps://stg.rilmobileapi.mform.in
Frappe UAThttps://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 repodhwani-ris/frappe-mobile-sdk-dhwani (private fork)
SDK feature branchfeat/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/village
  • program — top-level program (e.g. Swayamshree, DM)
  • program_form_registry — links a DocType to a program with is_published flag
  • program_role — per-program role definition with can_add / can_edit / can_approve / can_return / can_view_all_geography
  • program_workflow — workflow definition per program
  • user_program_assignment (UPA) — the central row: user × program × role × geography(ies). Multi-geo support via the upa_geography child table.
  • submission_approval_log — child Table DocType; embedded in every submission as field approval_log
  • sync_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 (or swasti_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_log DocTypes 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.py verbatim. They drop in unchanged.
  • Reuse mobile_control companion 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 the bench --site … import-doc verification step are mandatory — see §13 (Hard-won lessons).
  • The 21-form list in hooks.py is 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 fileUse when
~/Dhwani/rel-mis/skills/mform2frappe.skillConverting 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, getDynamicOptionfetch_from.
~/Dhwani/rel-mis/skills/frappe-doctype-skill.skillDesigning/speccing DocTypes from scratch (fields, permissions, workflows, approval states, form layout).
~/Dhwani/rel-mis/skills/frappe2mform.skillReverse 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[].title exactly.
  • Normalise camelCase fieldnames (e.g. gramPanchayatgrampanchayat, no underscore).
  • Build child DocTypes BEFORE parents.
  • Multi-select pattern: Option Master (Data, autoname=field:option_name) → Link Child DocType (Link → master) → parent uses Table MultiSelect.
  • Geography fields → Link to Geography DocType.
  • input_type 27 (Dynamic Action) → Link field, 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-doc discipline.

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):

EndpointPathPurpose
submit_formrf_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_formsReturns 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:

  1. Description tokens — strings on the DocField description field (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.
  2. Generic SDK evaluators — e.g. DependsOnEvaluator. Most behaviour is data-driven from DocField metadata.
  3. Per-form Dart in RfFieldFactory — last resort. Already 44 sites of _wrapWithSomeDependsOnIfNeeded and 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 ValidationError short-circuits.

What to change for Swasti

  • Replace RfFieldFactory with SwastiFieldFactory (or keep extending the same class — see decision below).
  • The hardcoded _syncLinkTargets master list in delta_sync_service.dart:609 will be Swasti’s masters: Geography, Scheme Master, Member Profile Register, Document Master (probably), Health Screening masters.
  • DmConfig.dmTree and module_config.dart’s TrackConfigs._{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

  • Geography DocType definition.
  • User Program Assignment + UPA Geography child table.
  • permissions.py + geography.py + geography_hooks.py.
  • The _get_active_upa Redis cache pattern — request-scoped, key upa_<user>.
  • The get_geography_link_fields() helper that scans DocType meta for fields linking to Geography.

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 Program DocType (scoping_mode = "user" | "geography") consumed by get_permission_query_conditions(). When user, append tab{doctype}.owner = {user}. When geography, 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 member Link 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:

  1. Frappe native Workflow — defined per-program via Program Workflow DocType. 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.

  2. approval_log Table field — every submission DocType has a child Table field named approval_log linked to the Submission Approval Log child DocType. The log_workflow_action() hook (rf_mis/utils/workflow_utils.py:13) fires on on_update for every submission DocType (wired in hooks.py:88-101 for all 21 + 20 form DocTypes via doc_events). Whenever workflow_state changes, 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 Log child DocType — drop in.
  • workflow_utils.py:log_workflow_action() — drop in.
  • The doc_events wiring in hooks.py — adapt to Swasti’s form list.
  • Program Workflow DocType.

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_status on Scheme Application) is the live state.
    • Each follow-up = a row in a child table (e.g., followup_log Table 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 renamed followup_log and capturing followup-specific columns. The hook fires on field-set, not on workflow_state change.
  • Frappe’s native Version log 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:

  1. 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.
  2. 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.
  3. User Program Assignment (UPA) — pins a user to (program, role_key, geography(ies)).

Hot path (permissions.py:has_permission):

  • System Manager always allowed.
  • Look up UPA for user. If none → deny.
  • Doc must belong to user’s program → else deny.
  • Capability flags on Program Role decide read/write/approve/return.
  • can_view_all_geography flag 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_geography flag 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 surveyor field 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 to swasti-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:
    1. A dedicated machine running an Android emulator + Frappe staging.
    2. AI/MCP runner that on every APK drop installs, runs the regression suite, reports.
    3. 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_mis repo — feat/fix branches off staging → push to stagingauto-deploys. NO PRs.
  • rel-mis repo (mobile) — feat branches off develop → PR → develop.
  • frappe_mobile_control and frappe-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 bench setup 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_control and frappe-mobile-sdk-dhwani are 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.

#LessonWhere it bit
1DocType controller class name MUST equal doctype_name.replace(" ", "") — wrong name → silent DELETE on every migrateserver CODEBASE_GUIDE.md §1.13
2Table MultiSelect child DocType MUST have a Link field — never Data or Select§1.14
3Display fields must be human-readable (geography_name, not codes)§1.15
4mForm ID fields (drive_id, training_id, school_id, participant_unique_id) are obsolete — use Frappe autoname§1.16
5mForm “No of X” count fields are obsolete — surface count in section heading§1.17
6Geography Column Break anywhere between state→village fields kills GP auto-fill on mobile§1.18
7Child DocType editable_grid = 0 if >3 fields OR has conditional fields; otherwise 1§1.19
8After writing any DocType JSON: bench --site rf-mis.local import-doc <path> BEFORE commit§1.20
9reqd:1 + depends_on is an anti-pattern — use mandatory_depends_on for conditional required§1.21
10Mobile sends Int/Float values as strings — server controllers must cast (val=int(val); see non_agricultural_products_marketing_and_sales.py)§1.22
11frappe.db.db_name does not exist on MariaDB — use frappe.conf.db_name§1.23
12Sync Error Log: never except frappe.ValidationError: raise short-circuit. Log every exception§1.24
13Hardcoded 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
14Drop 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
15LinkDisplayConfig.filterByGrampanchayat defaults to true — set false for non-GP-level masters or picker shows zero rows silentlymobile CODEBASE_GUIDE.md §1.24
16Sync Now does NOT propagate server deletes / registry unpublishes — installed apps hold stale entries until reinstall. Mobile rebuild = releasemobile §1.22
17SDK DependsOnEvaluator doesn’t handle `(doc.field
18No pub upgrade ever. Always pub get. SDK pinned to feature branchmobile §1.14
19Hot restart doesn’t pick up pub-cache SDK edits — flutter clean && flutter runmobile §1.15
20FormBuilder nulls from unvisited tabs must not overwrite _draftDatamobile §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:

  1. Repo skeletons.
    • Create swasti_mform Frappe app: bench new-app swasti_mform. Copy hooks.py, pyproject.toml, patches.txt, the rf_mis/{api,utils,permissions.py,fixtures,patches,page} skeleton from ~/frappe-bench/apps/rf_mis/. Trim DocType-specific code.
    • Create swasti-mobile Flutter repo. Copy ~/Dhwani/rel-mis/mobile/{lib,test,android,ios,pubspec.yaml,…}. Strip dm_config.dart and Track configs (will rebuild for Swasti’s 4 programs).
    • Add swasti_mform to the mobile_control consumer list (no fork).
    • Pin Flutter app to the same SDK branch (feat/rf-mis-field-extensions).
  2. 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. Run bench --site swasti.local import-doc for each.
  3. Permissions / utils (drop in verbatim). Copy permissions.py, utils/geography.py, utils/geography_hooks.py, utils/workflow_utils.py, utils/role_sync.py. Wire doc_events, has_permission, permission_query_conditions in hooks.py.
  4. Domain DocTypes (build new — use mform2frappe skill). For each Swasti V2 mForm JSON:
    • Use mform2frappe.skill to 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_DOCTYPES list.
    • For each: register in Program Form Registry (program × form_doctype × is_published).
    • Verify with bench import-doc BEFORE commit.
  5. Per-program scoping mode (NEW for Swasti). Add scoping_mode = "user" | "geography" field to Program DocType. Branch permissions.py:get_permission_query_conditions() on it: user → also AND owner = user; geography → today’s logic.
  6. Audit / follow-up child tables. For Scheme Application Register, Health Screening Register, etc., add a followup_log Table field linked to a new Followup Log child DocType (copy shape of Submission Approval Log). Wire a hook to append on status field change (mirror log_workflow_action’s pattern but trigger on field-set instead of workflow-state).
  7. Mobile factory. Rename RfFieldFactorySwastiFieldFactory (or keep extending — decide based on whether Swasti diverges). Re-author module_config.dart with Swasti’s 4 programs (Member Profile master + Scheme + Document + Health + Livelihood). Re-author dm_config.dart (or delete if not needed). Update _syncLinkTargets with Swasti masters.
  8. Hidden member-ID prefill (mobile). When opening a child form from Member Profile context, inject a hidden member Link field defaulted to that member. App-layer only.
  9. Custom dashboard. Create swasti_mform/page/swasti_dashboard/ (HTML/JS/CSS). Apply frappe-dashboard-design skill rules. Cards = Pending Follow-ups Today / This Week / Overdue + per-program counts.
  10. Branding. Apply rf-mis-frappe-branding.skill with Swasti’s logo + palette to the desk theme.
  11. Multi-language. Net-new. Frappe Translation DocType for view labels. Free-text data stays as typed. Decide visualisation strategy for mixed-script free text.
  12. Seed script. swasti_mform/scripts/seed_data.py — idempotent. Creates Programs, Roles, sample Geographies, test users, UPAs, sample members, sample schemes/health records.
  13. E2E tests. Copy ~/Dhwani/rel-mis/tests/frappe-e2e/swasti-mobile/tests/frappe-e2e/. Rename specs to Swasti’s forms.
  14. PM feedback loop skill. Copy rf-mis-pm-feedback-cycle.skillswasti-pm-feedback-cycle.skill. Update credentials section.
  15. 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.
  16. Branching + deploy. Follow rel-mis branching matrix. Configure CI to parse [migrate][restart][build] commit tags.
  17. QA automation. Set up dedicated QA PC + emulator per kickoff D10. Wire Playwright + Flutter tests to CI on each push.
  18. Deploy. bench setup → install frappe / 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

Last updated 2026-05-04