ERP institution standard fields: statutory identity at the org and institution levels¶
An institution record in Kwilo is, today, a contact card: name, code, email, phone, address, board. That is not enough to be an ERP record. Every Indian institution is asked — by a board inspector, a NAAC/NBA audit, a report-card letterhead, a government return — for a statutory identifier it has nowhere to store: a UDISE+ code for a school, an AISHE code for a college or university, a board/university affiliation number, an AICTE approval. And a customer that runs several institutions under one trust has no place for the group's legal name / PAN / GSTIN, nor a way to avoid retyping the same contact details for every campus. This doc covers a set of standard fields (modelled on OpenEduCat + Indian statutory reality) split across the two levels Kwilo already has — the Organization (the owning legal entity) and each OrgUnit / institution — with contact details inheriting org → institution so a single-school customer types them once and a multi-institution customer overrides per campus.
Status: in progress — Product Mindset Gate passed 2026-07-29. Phase 1 (data +
API + inheritance resolver) and Phase 2 (both intake forms) built; student/teacher
standardization is a separate, later track. Branch: to be split into its own
feat/erp-institution-standard-fields before the code PR; this doc lands with it.
Related: erp-higher-ed-scheduling.md and erp-branch-transfer.md (the higher-ed institution types whose statutory codes this stores), auth-and-onboarding.md (institution creation is part of org-admin onboarding).
Why this exists¶
- Who asked: Partly self-initiated — it surfaced from a standards audit of our institution model against OpenEduCat, the reference open-source education ERP. But it is grounded in live demand, not an internal hypothesis: we are running real higher-ed and K-12 tenants (Sapthagiri University, Sapthagiri School) and chasing deal-blocking ERP work where the institution record is expected to hold official identifiers.
- User pain: An org admin onboarding a real school today cannot record its UDISE+/AISHE code, its affiliation number, or even its website. When they later generate a report card, a board/audit export, or an official letterhead, the statutory identity that every Indian institution is asked for isn't in the system — so they keep it in a spreadsheet alongside Kwilo. A multi-campus trust hits a second wall: each campus has its own UDISE/AISHE code, and there is nowhere per-campus to put it, while the group's legal/tax identity has nowhere to live at all.
- Cost of not doing it: The institution record stays a contact card, not an
ERP record — and multi-institution groups can't be represented faithfully.
That is exactly the shape of the higher-ed and K-12 ERP deals we are pursuing,
so the gap lands during evaluation, not later. A
grepof the backend confirmed zero UDISE/AISHE references existed before this work. - Validated or guess: The fields are validated — statutory codes are legally required identifiers, not a hypothesis. The placement (org vs institution, with inheritance) was the reasoned design call and is the part this doc pins down (see Design decisions).
- How we will know it worked: An org admin onboards a second institution under one organization without duplicating anything they shouldn't, and a report card / export renders the correct UDISE/AISHE + affiliation without manual entry. Sean Ellis counterfactual: remove it a week after shipping and a real school's admin immediately asks "where do I put our AISHE code?" — a specific, loud complaint, because the field is legally mandatory.
Personas¶
Mr. Rao, org admin for the Sapthagiri Education Trust, which runs a school, a degree college, and an engineering college under one legal entity. He onboards each institution and expects the group's registered name, PAN, and GSTIN to live once at the org, and each campus's own UDISE/AISHE code to live on that campus.
Dr. Anita, principal of the engineering college. When she generates a letterhead or a NAAC submission, she expects the college's AISHE code, university affiliation number, and AICTE approval to already be in the system, not retyped.
A board inspector / auditor (indirect): the reason the codes must exist at all — the institution is asked for them on official returns.
ICP exclusions¶
- Corporate & training units (
workspace,training_center, …) have no statutory codes; the Statutory & affiliation section simply does not render for them. Their contact fields still inherit from the org. - Student & teacher standardization (middle name, government ID, blood group, DOB/gender on faculty) is explicitly a separate track — those are single-level records and the org↔institution inheritance model does not apply.
Outcomes (user outcomes, not deliverables)¶
- A single-school org fills contact details once (at the org) and the institution inherits them — no double entry.
- A multi-institution org gives each campus its own statutory codes and, where it differs, its own contact details, overriding the org default.
- Every institution type shows exactly the statutory fields that apply to it and none that don't.
Non-goals¶
- No statutory codes on the Organization. A group does not have one UDISE code; codes are per-campus and live only on the OrgUnit. Mirroring them upward would be meaningless and create a "which code is right?" sync trap.
- No format/validity enforcement of codes in this phase (UDISE is 11 digits,
PAN is
AAAAA9999A, etc.). Fields are stored as strings with length caps; the backend range-checks onlyestablishment_year. Format validation is a later nicety. - No student/teacher fields here (separate track).
- No new "manage organization" surface for org admins — Bucket A (legal/tax) is edited in the existing platform-admin Organization modal for now.
Risks & mitigations¶
- PII (PAN). Entity PAN is business data, not a person's, and is stored plain for now. Mitigation: a code comment flags that if personal PANs ever land in this column, it must be encrypted per the repo's PII rules. GSTIN/registration number are non-sensitive.
- Two parallel institution routers (
/org-unitsprimary,/schoolslegacy) both return the same schema. Risk: fields added to one but not the other → inconsistent responses. Mitigation: both routers (and the org-level/organizations/{id}/schoolslist) were updated together. - Inheritance ambiguity. Storing a resolved (already-inherited) value would
make "owned vs inherited" unrecoverable. Mitigation: raw per-level values stay
nullable in the DB; a read-time resolver returns an
effectiveblock for display, and edit forms round-trip the raw values.
Success signals¶
- Org admins stop keeping a side spreadsheet of statutory codes.
- Multi-institution tenants onboard a second campus without re-entering group contact/legal details.
- Report-card / export work (later) reads UDISE/AISHE + affiliation straight from the record with no manual step.
Mental model — three field buckets¶
The fields split by where they inherently belong, not "put everything both places":
- Org-only (Bucket A) — one legal entity owns many institutions:
legal_name,registration_number,pan,gstin, groupwebsite. - Institution-only (Bucket B) — inherently per-campus:
establishment_year, and the statutory codes:udise_code,board_affiliation_number(K-12);aishe_code,affiliation_number,naac_grade(higher ed);aicte_approval_number(engineering). - Both, institution inherits org when blank (Bucket C) — the contact block:
email,phone,website,address,city,state,pincode. Raw values stay per-level; a resolver (OrgUnit.resolve_effective_contact) fills an institution's blanks from its parent org and returns them as aneffectiveblock on the API response (for display / letterheads / reports). Owned-vs- inherited is never lost because the stored values are untouched.
User flows¶
- Single-school org: admin fills the org's contact + legal details once; the
institution's contact fields left blank resolve to the org's via
effective. - Multi-institution org: admin sets each campus's own statutory codes and, if a campus has its own phone/website, overrides there; blanks still inherit.
- Create/edit institution: the New/Edit Institution modal shows a
conditional "Statutory & affiliation" section keyed off
org_unit_type— UDISE+ & board affiliation no. for K-12; AISHE, NAAC, university affiliation no. for higher ed; AICTE approval for engineering. Fields hidden for the chosen type never reach the backend.
Design decisions¶
- Three-bucket + inheritance over "mirror every field at both levels." Mirroring would force single-school orgs to type everything twice and put meaningless statutory codes on the org. Matches OpenEduCat's multi-company model. (User-chosen.)
- Conditional-by-type statutory fields over one generic
registration_no/accreditationset. More UI logic, but the admin is guided to the right identifier (UDISE vs AISHE) instead of guessing. (User-chosen.) - PU colleges appear in both statutory buckets (they carry a board and an AISHE code) — an intentional overlap, not a bug.
- Additive, all-nullable migration (
00074_institution_standard_fields) — no backfill, safe to deploy ahead of UI.
Implementation map¶
- Backend:
models/organization.py(Bucket A),models/org_unit.py(Bucket B +resolve_effective_contact+INHERITED_CONTACT_FIELDS), migration00074_institution_standard_fields, schemas (InstitutionIdentityFieldsmixin +EffectiveContact), and the/org-units,/schools,/organizations/{id}/schoolsresponses (each eager-loads the parent org). - Frontend (
apps/web):SchoolModal(website + established-year row, conditional Statutory & affiliation section, inheritance caption) andOrganizationModal(Legal & tax section); service types inservices/usersandservices/organizations.
Rollout plan¶
- Ship Phase 1 (backend, additive migration) — inert until UI uses it.
- Ship Phase 2 (forms) — org admins can enter the fields.
- Follow-ups: real inherited-value placeholders in the modal (needs a "my organization" contact fetch); optional format validation; the separate student/teacher standardization track; consumption in report-card/export.
Changelog¶
- 2026-07-29 — Gate passed; three-bucket + inheritance and conditional-by-type chosen. Phases 1–2 built and browser-verified against a local tenant.