Happy House - Ecommerce Docs

EMI Calculator Backend Documentation

Backend architecture, data model, exact arithmetic, services, caching, and operational behavior for the EMI Calculator module.

EMI Calculator - Backend Documentation

1. Documentation Evidence

AreaFiles InspectedVerified Details
Module wiringapps/api/src/modules/emi/*.module.tsAggregate + customer + admin aggregate + 6 admin leaves + shared
Controllerscustomer/emi-customer.controller.ts, admin/*/*.controller.ts25 routes, permissions, rate limits
Servicescustomer/emi-customer.service.ts, shared/*.service.ts, admin/*/*.service.tsEstimator, offerability, eligibility, config
DTOscustomer/dto/*.ts, admin/*/dto/*.tsValidation, examples
Schemapackages/db/src/schema/emi/*.ts6 tables, 2 enums, constraints
Mathapps/api/src/utils/emi/emi-math.util.tsThe exact-rational arithmetic
Jobsn/aNo queue exists
Cacheemi.constants.ts, cache-invalidation.tags.tsemi:v1: keys, emi domain

2. Backend Scope and Boundaries

Owns

  • The exact-rational EMI arithmetic (computeEmi).
  • The customer estimator (2 public GET routes).
  • Bank offerability (active + rate in force + tenure present).
  • Configuration: the store-wide switch, price source, eligibility mode (cached 30s).
  • The tenure vocabulary.
  • Bank CRUD, append-only rate history, bank tenure replacement.
  • Eligibility selection tables and the category-descendant expansion.
  • The emi cache domain.

Does Not Own

  • Product pricing — reads product.selling_price/mrp, writes nothing.
  • Category hierarchy — reads it for descendant expansion, owns nothing.
  • Any loan, payment, reservation or price mutation. The platform is not a lender.
  • A queue — nothing is async.

Source of Truth

ConcernSource of TruthNotes
Configurationemi_setting row (singleton, CHECK (id = 1))Cached 30s; missing row = defaults
Ratesemi_bank_rate append-only historyCurrent = newest effective_from <= now()
Offered periodsemi_bank_tenure link rowsMust be in the vocabulary
Eligibilitysettings mode + selection tablesCategory set cached; product set is a PK probe
QuotescomputeEmiExact rational, no float

3. Module Composition

ModuleTypePathControllersProvidersExportsResponsibility
EmiModuleAggregateemi.module.tsNoneAdmin + CustomerComposes
EmiCustomerModuleLeafcustomer/EmiCustomerControllerEmiCustomerService2 public routes
EmiAdminAggregateModuleAggregateadmin/emi-admin-aggregate.module.tsNone6 leavesComposes
EmiSettingsAdminModuleLeafadmin/settings/controllerserviceGlobal switch, price source, mode
EmiTenureAdminModuleLeafadmin/tenure/controllerserviceVocabulary
EmiEligibilityAdminModuleLeafadmin/eligibility/controllerserviceSelections + effective list
EmiBankAdminModuleLeafadmin/bank/controllerserviceBanks CRUD + reorder
EmiBankRateAdminModuleLeafadmin/bank-rate/controllerserviceRate history
EmiBankTenureAdminModuleLeafadmin/bank-tenure/controllerservicePer-bank periods
EmiSharedModuleSharedshared/None4 services4 servicesConfig, catalog, expansion, constants

4. File and Directory Map

apps/api/src/modules/emi/
  emi.module.ts
  customer/
    emi-customer.controller.ts
    emi-customer.module.ts
    emi-customer.service.ts
    dto/emi-customer.dto.ts
  admin/
    emi-admin-aggregate.module.ts
    settings/  (controller, module, service, dto/)
    tenure/    (controller, module, service, dto/)
    eligibility/ (controller, module, service, dto/)
    bank/      (controller, module, service, dto/)
    bank-rate/ (controller, module, service, dto/)
    bank-tenure/ (controller, module, service, dto/)
  shared/
    emi.constants.ts
    emi-config.service.ts
    emi-bank-catalog.service.ts
    emi-category-expansion.service.ts
    emi-shared.module.ts
apps/api/src/utils/emi/
  emi-math.util.ts
FilePurposeKey ExportsNotes
utils/emi/emi-math.util.tsExact EMI arithmeticcomputeEmi, isCalculableEmi, EMI_MAX_TENURE_MONTHS, EMI_MAX_ANNUAL_RATE_BPS, EMI_MAX_PRINCIPALPure; no NestJS import
shared/emi.constants.tsAll policy + PG error helperscache keys, live statuses, admin sorts, isReferencedRowError, isUniqueViolation, isCheckViolation, normalizeBankName23001 AND 23503 both mapped
shared/emi-config.service.tsCached config for readsgetSettings, getEligibleCategoryIdscachedOrDirect degrades to DB on Redis failure
shared/emi-bank-catalog.service.tsOfferable bank listgetOfferableBanksTTL capped at next rate boundary, clamped ≥1
shared/emi-category-expansion.service.tsDescendant category idsexpandEligibleCategoryIdsCached as an array, returned as a Set
customer/emi-customer.service.tsEstimatorgetOptions, calculateReads only; 200-with-false vs coded errors
admin/settings/emi-settings-admin.service.tsSettings upsertget, updateSingleton upsert + emi invalidation
admin/tenure/emi-tenure-admin.service.tsVocabulary replacelist, replaceDeactivate vs delete; EMI_TENURE_IN_USE
admin/eligibility/emi-eligibility-admin.service.tsSelection tableslist/add/remove categories + products, listEffectiveProductsExpansion + live filters
admin/bank/emi-bank-admin.service.tsBank CRUDlist, findOne, create, update, remove, restore, reorderName NFC-normalised; loan-range checks
admin/bank-rate/emi-bank-rate-admin.service.tsRate historylist, create, removeAppend-only; future-only delete
admin/bank-tenure/emi-bank-tenure-admin.service.tsPer-bank periodslist, replaceVocabulary-validated; FOR UPDATE serialisation

5. Data Model

5.1 Schema Source

packages/db/src/schema/emi/
  enums.ts
  emi-setting.ts
  emi-tenure.ts
  emi-eligibility.ts
  emi-bank.ts
  emi-bank-rate.ts
  emi-bank-tenure.ts

5.2 Tables and Collections

emi_setting

ColumnTypeDefaultConstraintNotes
idserial1chk_emi_setting_singleton (id = 1)A CEILING not a floor — zero rows is legal, repairable state
is_enabledbooleanfalseThe store-wide switch
price_sourceenumselling_priceselling_price / mrp
eligibility_modeenumselectedentire_store / selected
updated_byuuidFK SET NULLAdmin who last changed it
created_at / updated_attimestampnow

emi_tenure

The vocabulary. months smallint PK (1…120, chk_emi_tenure_months_range), display_order, is_active. Seeded: 3, 6, 9, 12, 18, 24, 36, 48, 60. Deactivation, never deletion while referenced (ON DELETE RESTRICT from emi_bank_tenure).

emi_eligibility

Two link tables: emi_eligible_categories (category_id, FK RESTRICT) and emi_eligible_products (product_id, FK RESTRICT). Rows removed explicitly on deselect. The category table is expanded to descendants at read time (with the products schema's own cycle-prevention checks).

emi_bank

ColumnTypeNotes
public_iduuid v7unique
namevarcharuq_emi_bank_name_live on lower(btrim(name)); NFC-normalised on write
logo_key / logo_alttextEMI_BANK_LOGO_ALT_REQUIRES_KEY pairs them
min_loan_amount / max_loan_amountbigintnullable; chk_emi_bank_loan_range_ordered (min ≤ max; min = max permitted)
display_orderintchk_emi_bank_display_order_non_negative
is_activebooleanofferability condition
deleted_at / deleted_bysoft delete

emi_bank_rate

ColumnTypeNotes
bank_idintFK RESTRICT
annual_rate_bpsint0…10000 (chk_emi_bank_rate_bps_range)
effective_fromtimestamptz< 2100 (chk_emi_bank_rate_effective_from_bounded); full ISO instant
created_by / deleted_byuuidFK SET NULL
deleted_attimestampsoft delete

uq_emi_bank_rate_effective is partial on deleted_at IS NULL — cancelling a scheduled rate then re-entering the same date must not collide with the soft-deleted row. The index also serves the current-rate read (btree backwards scan).

emi_bank_tenure

Link table (bank_id, months) with PK on both. FK to bank RESTRICT, FK to emi_tenure RESTRICT. Replaced wholesale in a transaction under FOR UPDATE on the bank row (two concurrent replaces would otherwise produce the union of their sets).

5.3 Relationship Diagram

6. Services and Responsibilities

6.1 EmiMathUtil (pure functions)

FunctionReadsWritesNotes
computeEmi(principal, bps, months)Exact rational BigInt; one rounding at the end; validates its own inputs
isCalculableEmi(...)For deciding whether to OFFER, never to trust

The derivation: r = bps/120000 exactly, 1 + r = (120000 + bps)/120000, so

        P · bps · (120000 + bps)^n
EMI = ───────────────────────────────
      120000 · ((120000 + bps)^n − 120000^n)

Numerator and denominator are exact integers; there is exactly ONE rounding. Rounding is max(roundHalfUp(exact), ceil(P/n)) — the floor makes totalInterest ≥ 0 a theorem. 0% is a named branch returning zero interest by definition.

6.2 EmiConfigService

MethodReadsWritesSide EffectsErrors
getSettingssettings (cached)none — cache-down falls through
getEligibleCategoryIdsexpansion (cached)same

cachedOrDirect wraps getOrSet because that method rethrows on its lock path regardless of fallbackOnError — Redis down would otherwise 500 every EMI request.

6.3 EmiBankCatalogService

getOfferableBanks — active, not deleted, a rate in force, at least one tenure (the last two are cross-table minimums no constraint can enforce; filtering here is the enforcement). resolveTtlSeconds = max(1, min(VOLATILE, secondsUntilNextRateBoundary)) — a rate becoming effective is a clock event, so without the cap the cache would advertise a superseded rate for a full TTL.

6.4 EmiCustomerService

getOptions — 200 with available: false for every ineligibility (feature off, product not selected, zero price, no banks); only a missing product 404s. Quotes computed server-side per bank per tenure. calculate — raises specific codes (403/404/409/400/422) because a client reaching it has already been told what is on offer; a failure means something changed underneath or the request was hand-made.

resolvePricingBasis(product, variantPublicId?) decides what price this request quotes against, and both public routes call it before anything else. (apps/api/src/modules/emi/customer/emi-customer.service.ts:136-184)

  • No variantId — returns { variantId: null, variantName: null, mrp: product.mrp, sellingPrice: product.sellingPrice }, the product ROLLUP. Deliberate: a product page renders the EMI affordance before a configuration is chosen, and "from Rs X" is the same honest figure the storefront card already shows.
  • variantId supplied — one query, scoped to productVariants.publicId = variantId AND productVariants.productId = product.id AND isActive AND deletedAt IS NULL. The productId scoping is what makes this an IDENTITY check, not merely a lookup: it is what refuses a variant that belongs to a different product rather than silently pricing one product off another's configuration. No match — soft-deleted, deactivated, or another product's — is 404 EMI_VARIANT_NOT_FOUND, never a fallback to the rollup. A fallback would answer a question the customer did not ask with a number they cannot be charged, which is the exact defect this method exists to remove.

Why this matters for the money, not only the 404: product.selling_price and product.mrp are rollups — the MINIMUM across live variants — and financing a chosen configuration off the rollup would have quoted the Rs 90,000 phone an installment plan computed from the Rs 50,000 sibling's price, with nothing in the response revealing which configuration the figure belonged to. The special-deal campaign is resolved against the variant's own base price but the product's identity (a deal targets a product/brand/series, never a variant) — the same call shape the product page, the cart and the checkout freeze all use, which is what keeps the four in agreement. (apps/api/src/modules/emi/customer/emi-customer.service.ts:85-124)

6.5 Admin services

  • Settings: singleton upsert ON CONFLICT (id); invalidates emi domain after commit.
  • Tenures: replace deactivates rather than deletes referenced periods (EMI_TENURE_IN_USE); vocabulary validation names the unusable periods.
  • Eligibility: add/remove with ALREADY_ELIGIBLE codes; listEffectiveProducts mirrors the customer gate (sellingPrice > 0, live statuses).
  • Banks: create/update validate loan range and logo pairing; delete/restore soft; reorder rewrites displayOrder transactionally, 404 on missing ids.
  • Rates: append-only — no PATCH; delete only while effective_from > now(); duplicate effective dates on live rows raise EMI_RATE_EFFECTIVE_DATE_ALREADY_EXISTS.
  • Bank tenures: replace validates against the vocabulary (only is_active periods assignable — deactivation blocks NEW assignments only), serialises with FOR UPDATE.

7. Runtime Flows

7.1 The estimator

The sequence in feature.mdx §5.1. Every read goes through the 30s cache; a cache failure degrades to a direct DB read with a warn log, never a 500.

7.2 The rate boundary

8. Caching

Cache Key PatternBuilderValueTTLInvalidationCaller
emi:v1:settingsEMI_SETTINGS_CACHE_KEYsettings snapshotVOLATILE (30s)emi domain on settings writeEmiConfigService
emi:v1:eligible-categoriesEMI_ELIGIBLE_CATEGORIES_CACHE_KEYid array30semi domain on eligibility writeEmiConfigService
emi:v1:banksEMI_BANKS_CACHE_KEYofferable banksmin(30s, next rate boundary)emi domain on bank/rate/tenure writeEmiBankCatalogService
  • v1 versions the SERIALISED SHAPE, not the data — bump on a response-shape change; stays inside the emi:* invalidation glob.
  • Nothing price-derived is ever cached.
  • Every admin mutation invalidates emi after commit, fire-and-forget.
  • Redis down → warn + uncached read, never a 500.

9. BullMQ, Schedulers, and Async Work

None. No queue, no cron, no scheduler. The rate boundary needs no job — the TTL cap expires on its own, which is the design: a job that must run for an advertised interest rate to be correct fails silently when it does not run; a TTL that expires on its own has no such failure mode.

10. Realtime and Events

None.

11. Security, Auth, and Abuse Controls

  • Customer routes: @Public(); PUBLIC_READ 60/min. No guest identity, no persistence.
  • Admin routes: JwtAuthGuard + RoleGuard; Emi_* or EmiBanks_* per route; ADMIN_READ 30/min reads, ADMIN_WRITE 10/min mutations.
  • Tenure DoS control: tenureMonths is an exponent; DTO bounds at 1…120 (and the math util validates its own inputs before any exponentiation).
  • Loan-range boundaries: inclusive at both ends (min = max permitted and reachable).
  • Injection surface: admin bank sort keys are a closed set (EMI_BANK_ADMIN_SORTS).
  • Referenced-row deletes: RESTRICT refusals mapped on BOTH 23001 and 23503 — a DrizzleQueryError carries the driver code on .cause, not the outer object; reading only the outer code makes every translation dead code.
  • Name uniqueness: NFC normalisation on write because an index cannot normalise Unicode composition.
  • Audit: created_by/deleted_by/updated_by columns, SET NULL on admin removal; rate history is the record of what customers were quoted.

13. Error Handling

Error CodeHTTP StatusThrown ByConditionClient Action
EMI_DISABLED403customer servicefeature off store-wideHide the affordance
EMI_PRODUCT_NOT_ELIGIBLE403customer serviceproduct not selectedHide the affordance
EMI_PRICE_NOT_AVAILABLE409customer serviceprice resolves to zeroHide the affordance
EMI_NO_BANK_AVAILABLE409customer serviceno offerable bankHide the affordance
EMI_BANK_NOT_FOUND404customer + admin servicesunknown/no-longer-offered bankRe-fetch /options
EMI_TENURE_NOT_SUPPORTED400customer servicebank does not offer the periodRe-fetch /options
EMI_AMOUNT_BELOW_BANK_MINIMUM409customer serviceprice under floorRe-fetch; the flag is there
EMI_AMOUNT_ABOVE_BANK_MAXIMUM409customer serviceprice over ceilingsame
EMI_CALCULATION_OUT_OF_RANGE422customer servicearithmetic guard has a holeReport it
EMI_PRODUCT_NOT_FOUND404customer + eligibilityno such live product404 the page
EMI_BANK_NAME_ALREADY_EXISTS409bank serviceduplicate live nameUse another name
EMI_BANK_LOAN_RANGE_INVALID400bank servicemin > maxFix the pair
EMI_BANK_LOGO_ALT_REQUIRES_KEY400bank servicealt without keyFix the pair
EMI_BANK_INACTIVE409bank serviceacting on a deactivated bankRe-activate first
EMI_BANK_HAS_NO_TENURES409bank serviceoffered periods absentAssign some
EMI_BANK_RATE_NOT_CONFIGURED409rate serviceno rate row at allCreate one
EMI_RATE_IN_FORCE_NOT_DELETABLE409rate servicedeleting an effective rateSupersede instead
EMI_RATE_EFFECTIVE_DATE_ALREADY_EXISTS409rate serviceduplicate live effective datePick another
EMI_RATE_EFFECTIVE_DATE_OUT_OF_RANGE400rate servicedate beyond 2100Fix the date
EMI_TENURE_IN_USE409tenure servicedeleting an assigned periodDeactivate instead
EMI_TENURE_NOT_IN_VOCABULARY400tenure/bank-tenure servicesperiod outside the vocabulary (or deactivated)Pick a listed period
EMI_CATEGORY_NOT_FOUND404eligibilityno such categoryRe-check
EMI_CATEGORY_ALREADY_ELIGIBLE409eligibilityduplicate selection
EMI_PRODUCT_ALREADY_ELIGIBLE409eligibilityduplicate selection

24 codes, all in use (the EMI_* block of error-codes.ts).

14. Observability

SignalLocationPurpose
LogLogger in config/catalog (cache-down warns), admin servicesCache degradation and admin operations
Cache stateRedis keys emi:v1:*Shape versioning, TTL caps
Audit columnscreated_by/deleted_by/updated_byWho changed what
Rate historyemi_bank_rateThe record of what was advertised

No activity/audit-log events — that was designed and withdrawn (master plan §6.2).

15. Testing and Validation

Test TypeFilesCoverage
Unitemi-math.util.spec.ts31 tests; 3 mutations each caught by the right test
Integrationemi-bank-tenure.int.spec.ts, emi-schema-contract.int.spec.tsTenure replace serialisation; products price constraint asserted
Probe.omc/plans/emicalc/probe-constraints.mjs8,640-combination arithmetic sweep; 38 schema assertions accept+reject
QA ledger.omc/plans/emicalc/qa-gate-ledger.mdFull gate history, all PASS

Validation: tsc -b --force (0 new errors), pnpm rules (390 codes), drizzle-kit check + drift (clean), unit + integration suites.

16. Mandatory Backend Deep-Dive Pack

16.1 Submodule Coverage Matrix

UnitTypeOwnsDepends OnCalled ByCallsState TouchedFailure Modes
computeEmipure fnexact arithmeticmoney util typescustomer service, catalognonenoneEmiRangeError
EmiConfigServiceservicecached configDB, cache, expansioncustomer, eligibility3settings, category cachecache-down → DB
EmiBankCatalogServiceserviceofferable banksDB, cachecustomer3banks cachecache-down → DB
EmiCategoryExpansionServiceservicedescendant idsDBconfig, eligibility1cycles prevented upstream
EmiCustomerServiceserviceestimatorDB, config, catalog, math, storagecustomer controller6nonecoded errors
EmiSettingsAdminServiceservicesettings writeDB, cache invalidationsettings controller2settings + cache
EmiTenureAdminServiceservicevocabularyDBtenure controller2tenures + cacheIN_USE
EmiEligibilityAdminServiceserviceselectionsDB, expansioneligibility controller4selection tables + cacheduplicate codes
EmiBankAdminServiceservicebanksDB, cachebank controller3banks + cacheloan-range, logo pairs
EmiBankRateAdminServiceservicerate historyDB, cacherate controller3rates + cachein-force delete refused
EmiBankTenureAdminServiceserviceper-bank periodsDBbank-tenure controller3link rows + cachevocabulary errors
Controllers (7)controllersroutingservicesHTTPservicespipes

16.2 UML and Architecture Diagram Pack

Component diagram: §3. Sequence diagrams: feature §5.1 (estimator), §7.2 (rate boundary). ER diagram: §5.3. State diagram: feature §7 (rate lifecycle).

16.3 Code Flow Narrative

The estimator is fully narrated in feature §5.1. The rate boundary (§7.2) is the module's second critical path. Every admin mutation follows one shape: validate → write (in a transaction where concurrency matters) → invalidate emi domain after commit (fire-and-forget, never inside the transaction).

16.4 Data Layer Deep Dive

Field-level detail in §5.2. Index/constraint rationale:

Index/ConstraintColumnsTypeQuery/Invariant SupportedTradeoff
uq_emi_bank_name_livelower(btrim(name))uniqueOne live nameNFC must be normalised on write
chk_emi_bank_loan_range_orderedmin, maxcheckmin ≤ max, min = max allowedboundary reachable
uq_emi_bank_rate_effectivebank, effective_frompartial unique (live)One live rate per date; serves the current-rate readtwo soft-deleted rows with one date possible
chk_emi_bank_rate_bps_rangebpscheck0…10000mirrored in math util
chk_emi_setting_singletonidcheckOne settings rowzero rows legal (defaults)
FK RESTRICT everywhererefsFKNo silent orphaningrefusals must be coded

16.5 Business Logic and Invariant Catalog

InvariantEnforced ByWhy It ExistsFailure ErrorTests
EMI computed exactly, one roundingcomputeEmiFloat form is wrong by a paisaEmiRangeError / 422unit + 8,640 probe
totalInterest >= 0ceil(P/n) floorNo negative reported interestunit
0% reports zeronamed branchUniform formula liesunit
Tenure boundedutil + DTODoS (exponent)400unit
Rate in force not deletablerate serviceRecord of what was quotedEMI_RATE_IN_FORCE_NOT_DELETABLEint
Offerable = 4 conditionscatalog filterTwo are unconstrainableint
Tenures ∈ vocabularyFK + service checkComparison table lines upEMI_TENURE_NOT_IN_VOCABULARYint
Price sources = 2enum + products CHECK4 names for 2 behavioursschema-contract spec
Nothing price-derived cachedno price keysStale price = wrong installment
Redis down ≠ 500cachedOrDirectFeature survives Redis loss

16.6 Tradeoffs, Alternatives, and ADR Notes

DecisionContextChosen OptionAlternativesWhy ChosenTradeoffsRevisit Trigger
BigInt exact rationalFloat EMI is wrongcomputeEmiMath.powOne paisa mattersBigInt cost (0.004ms)
Round-half-up + floorCeiling overchargedmax(round, ceil(P/n))ceilingprobe-disproven
2 price sourcesConstraint collapses 4→2enum + contract spec4 nameshonest admin choicebreaks silently if products relaxesproducts constraint change
Tenure vocabularyComparison integrityemi_tenure domainfree-range 1–120no 13-vs-12~9-row table
Append-only ratesAdvertised-rate recordhistory tableeditable columnauditabilityfuture-only delete
TTL-capped bank cacheClock events invalidate nothingmin(30s, boundary)scheduled jobno silent job failureboundary query per fill
30s config TTLBounded stalenessVOLATILE5 minwithdrawn-category speed30s wait
Redis-down degradationgetOrSet rethrowscachedOrDirectfix redis servicelocal, 30+ callers safeper-module wrapredis contract change
Separate permsDifferent peopleEmi / EmiBanksone moduleleast privilegeEmi_UPDATE is wider — documented

16.7 Operational Runbook

OperationHow to InspectHealthy StateFailure SignalRecovery
CacheRedis emi:v1:*30s TTLs; bank key capped at boundarywarn logs on cache-downautomatic DB fallback
Ratesemi_bank_rate historyone in-force per bankbank missing from /optionsadd rate or tenure
Feature stateGET /settingsisEnabled reflects intent30s lag after changewait or re-check
Tenure vocabularyGET /tenures9 seeded periodsbank offers deactivated periodservice blocks new assignment
RESTRICT refusalsAPI errorscoded errorsraw 23001/23503 in logsthat is a bug — sqlStateOf should catch

16.8 Backend Risk Register

RiskAreaImpactCurrent MitigationRemaining Gap
Float EMI driftarithmeticwrong paisaBigInt rational
DoS via tenurearithmeticevent-loop block120 cap + self-validation
Cache advertises superseded ratecatalogwrong advertised rateTTL cap at boundaryboundary query itself cached? no — per fill
Products constraint relaxesprice source2-source enum quietly wrongschema-contract specspec fails loudly, then fix
Redis outagereadsfeature downcachedOrDirectlock-path rethrow covered
Concurrent tenure replacebank tenuresunion of setsFOR UPDATE

17. Zero-Omission Backend Checklist

  • Every file in the module directory is represented (§4, §16.1).
  • Every controller, service, provider, DTO, enum, and schema is documented.
  • Every method with business behavior has a code-flow narrative (§6, §7, §16.3).
  • Every table has field-level detail (§5.2).
  • Every index, constraint, relation, and delete behavior has rationale (§5.2, §16.4).
  • Every lifecycle has a state diagram and transition table (feature §7).
  • Every read/write flow has sequence and activity diagrams (§7, feature §5.1).
  • Every business invariant is cataloged (§16.5).
  • Cache keys, invalidation paths, and queue jobs are documented (§8, §9 — no queue exists by design).
  • Every architectural tradeoff is documented with alternatives and revisit triggers (§16.6).
  • Every operational failure mode has a runbook entry (§16.7).

18. Backend Completion Checklist

  • Module boundaries are documented (§2).
  • Every controller, service, DTO, schema file, cache key, and event is covered.
  • Every table has a field table and relationship diagram (§5).
  • Every runtime flow has a diagram and branch notes (§7).
  • API and features/flows docs are linked (See Also).
  • No claim is made without a source file reference.

See Also

  • API doc: /docs/developer/emi/api
  • Features and flows doc: /docs/developer/emi/feature
  • TDD: not yet published