A stack list is a commodity. The reasoning is not.
Every software company publishes the same page: a row of logos for the languages and databases it uses. It proves nothing, because the list is almost identical everywhere and none of it explains a single decision.
So this page states the reason instead, and names what was rejected to get there. Several of these choices cost us something — a harder operational burden, a slower answer, a queue somebody has to work. Those are the ones worth reading.
Everything below is either a decision already recorded in our technical requirements or a fact we verified independently. Where a number is a budget the build enforces rather than something measured on a running service, it says so. No system described here is serving a customer today.
A real constraint solver, and an answer when there is no answer
Building a school’s weekly routine is genuinely NP-hard: two thousand lessons each needing a slot, a competent teacher and a suitable room, with nobody in two places at once.
The decision
OR-Tools CP-SAT, in its own service
- Because
- It is an anytime solver, which is what surfacing a first feasible answer within sixty seconds actually requires. The service never touches the database — it receives a self-contained model and returns a solution — which keeps the isolation boundary out of it entirely.
- Rejected
- Bespoke heuristics, which give neither an optimality signal nor explainable infeasibility.
The obvious way to encode this problem produces 691.2 million boolean variables and does not solve. Modelling it as one integer variable per lesson for slot, teacher and room — with channelling variables where the constraints need them — brings it to roughly 108,000 booleans. That is a 6,400-fold reduction, and it is the entire reason a ten-minute budget is plausible rather than fantasy.
The grid is sized at six working days by eight periods, not five by eight, because the default weekend here is Friday and Saturday and an institution that teaches on Saturday sets it differently. Sizing on the five-day case and discovering the six-day case in production is a specific failure this decision exists to prevent.
When a routine is impossible, the solver says which constraints make it impossible. Every relaxable constraint sits behind an assumption literal, so an infeasible model yields a minimal core that can be translated into a sentence: this class needs five periods a week, the only competent teacher is unavailable on two days, and here are the three things that would change it. A solver that says no without saying why is worse than the spreadsheet it replaced.
After a single change — one teacher’s availability, one room withdrawn — at least 90% of unchanged lessons must keep their slot, teacher and room. That is a hard requirement, checked in the build. A re-solve that moves four hundred lessons and reprints seventy teachers’ cards to accommodate one absence is technically correct and commercially fatal.
The same input with the same seed produces the same routine, every time. Parallel search is only deterministic when the worker count is fixed and interleaved search is on, so both are pinned — and so is the solver version, because upgrading it rewrites routines that nobody asked to change.
Seventy-two hours with no network, and nothing lost
A teacher marks attendance in a classroom with no Wi-Fi. A field officer records mortality inside a poultry shed with no signal at all, one-handed, in about forty seconds.
No screen in a capture flow makes a network call — including validation. Every piece of reference data the flow needs, from cause codes to medication catalogues to species standards, lives on the device.
There are two mechanisms, deliberately, because there are two jobs. The attendance app carries a hand-rolled append-only queue over browser storage with exactly one write shape, because it must not drag a sync SDK into a 250 KB budget. A unit test asserts that the queue’s type union has exactly one member, so adding a second write shape is a reviewed decision rather than a drift.
The field app carries a full sync engine with nine write shapes, a local database and deterministic conflict rules, because it holds a full route, batch state and historic visits and has to survive three days.
Sync conflicts resolve by rule, not by last-writer-wins. Last-writer-wins silently discards a record somebody drove to a farm to collect.
Money that reconciles, in a country with its own rails
A guardian pays through a mobile wallet, or at a bank counter, or in cash at the school office. Three days later a settlement file arrives and somebody has to make all of it agree.
Reconciliation is three-way: the invoice, the gateway’s record of the attempt, and the settlement line the bank actually paid. Anything that does not match lands in an exceptions queue under one of nine codes, in one of four work queues, with an age on it.
Matching is exact, never probabilistic. A fuzzy match that silently attaches a payment to the wrong student is worse than a queue somebody has to work, because the first one is discovered by a parent and the second by a clerk.
Payment gateways sit behind an adapter with at least two implementations plus a manual method, because the commercial terms of any one gateway are not a thing to build an architecture around.
The institution is merchant of record and Nimikh is never in the flow of funds. That is not a design preference — aggregating payments in Bangladesh is a licensed activity with a capital requirement and personal liability attached.
Messaging with a consent ledger and a hard cap
A Bangla SMS is two or more parts at seventy Unicode characters each, and the parts are billed. Fee deadlines and result publication land in the same week.
Every send is priced before it is sent. A fee-due message is three parts in Bangla and two in English — the composer states that difference rather than quietly choosing the cheaper language on the reader’s behalf.
Messaging is prepaid into a per-institution wallet with a hard cap. Without the cap, one result day can consume an institution’s entire margin, and the institution would find out afterwards.
Consent is a ledger, not a checkbox. Who agreed to what, on which channel, when, and what they were told at the time.
Isolation enforced by the database, proved by a test
One deployment holds many institutions’ student records, staff records and money. A bug that crosses that boundary is not a bug, it is an incident.
The decision
PostgreSQL, with row-level security as the isolation mechanism
- Because
- The isolation boundary and the money need to be the same transactional store, and row-level security makes the boundary a database object rather than a code convention.
- Rejected
- A schema per institution, which multiplies migration risk by the number of customers.
Every tenant-scoped table carries a tenant id with a row-level security policy. Application-layer checks are a convenience; the database is the control.
The whole API test suite runs a second time as a foreign tenant, and must return zero rows and zero successful responses on every single resource. A failure there blocks the release, unconditionally.
Roles are assigned per user per scope, not globally. A coordinator for the morning shift cannot dispatch for the day shift.
Support access into a customer’s data requires an open ticket, a consent flag, a reason code, a time box — and the customer is notified before the access happens, not after.
Data that stays in the country, and leaves when you ask
Bangladesh’s Personal Data Protection Act has been in force since 15 April 2026 and imposes class-based localisation. There is no AWS, Azure or GCP region in the country.
In-country hosting here means colocation and a self-managed database, not a managed-service purchase. That is a real cost, and it is a cost a foreign vendor cannot avoid either.
Every record you gave us and every record we created for you, in open formats, self-serve, within fifteen minutes. Not a support ticket, not a data-liberation fee, not a quarterly export window.
One exception, stated here rather than in a footnote: biometric templates are not exported as templates, because they are non-portable by design and export-restricted. The export carries a signed inventory of what is held, and a purge certificate on request.
Every state change is attributed, timestamped and hash-chained. Nothing is silently changed, and the account owner can see all of it.
Video that never leaves the site
Cloud video analytics is priced at USD 20–50 per camera per month. The thing it replaces costs BDT 10,000–15,000 a month and is a person.
Detection runs on a box at the site. Only events, metadata and short clips leave. The whole ingest and restream layer is permissively licensed, and so is the detector — embedding a model on a customer’s hardware is distribution, and a copyleft licence there would oblige publishing the product.
A detection is validated over time and filtered by class before anyone is told about it. An operator interrupted for nothing stops responding to the alerts that matter, which turns a detection system into an expensive way of doing nothing.
No accuracy figure, no false-alarm-reduction percentage and no camera-per-box count is published before it is measured on the customer’s own cameras. There is no audited figure for this class of system, and a number that comes from somebody else’s site is a number about somebody else’s site.
Budgets set by the cheapest phone in the room
Around 30% of devices in Bangladesh run Android 12 or older (StatCounter, 31 Aug 2026), and the median connection is disputed enough that the budget is set at the low end of the range rather than the middle.
Key screens are interactive within three seconds on a four-gigabyte Android over five megabits with 200 ms of latency, with the JavaScript on teacher and guardian paths capped at 250 KB compressed. Exceeding it fails the build.
Blur filters, backdrop filters and multi-layer shadows are banned outright. They force a composite layer and measure roughly four times slower on the GPUs that dominate the low-cost phone segment — so the visual language is built from type, rule and space instead.
Only transform and opacity ever animate, at most two elements at once, and lists over twenty items do not animate at all. Motion explains a spatial relationship or points at a change the reader did not cause. It never signals success, quality or brand.
Availability is committed at 99.5% in the first year — which is the honest number for a single in-country primary — but the promise that matters is different: no change is deployed inside a peak window, and the daily dispatch window between 07:00 and 08:15 is named in the contract alongside fee deadlines and result days.
The stack, with the reason attached
The middle column is the part every company publishes. The right column is the part that can be argued with, and it is the only one that tells you anything.
| Layer | Choice | Because |
|---|---|---|
| Language and runtime | TypeScript on Node, strict | One language across the web tier and the API, for a team small enough that a third runtime is a tax. |
| Web | Next.js and React | Guardian-facing surfaces are server-rendered and cacheable, because result day is a twenty-thousand-view burst against one endpoint and a client bundle cannot absorb it. |
| Database | PostgreSQL | Row-level security is the isolation mechanism, and money has to be atomic with the records it belongs to. |
| Solver | OR-Tools CP-SAT, isolated in its own service | Anytime search, an optimality signal, and an infeasibility core that can be explained to a coordinator. |
| Field application | React Native with a local database and a sync engine | Background sync, camera and GPS, and three days of usable offline life on an Android handset. |
| Object storage | Self-hosted, in-country, with an S3-compatible API | The API keeps the application portable; the location satisfies the localisation requirement. |
| Edge video | Permissively licensed ingest and detector on a commodity accelerator | Putting a model on a customer’s hardware is distribution. A copyleft detector would oblige publishing the product. |
| Error tracking | Self-hosted, inside Bangladesh | An exception payload carries local variables and request bodies. That is confidential-class data, and a hosted tracker would carry it out of the country. |
Every number here is a commitment
Almost every figure in these specifications carries a threshold and a consequence attached to it — a build that fails, a release that is blocked, a drill that is timed. This is all of them in one place, for software that is still being built. Not one of them is a result measured on a service running for a customer, because there is no such service yet.
| The number | What it commits to |
|---|---|
| 300 ms and 800 ms | Reads and writes respectively, at the ninety-fifth percentile, measured at the application edge. Held under load profiles run at twice the modelled peak, and at two and a half times before a known peak window. |
| 3.0 seconds | Key screens interactive on a four-gigabyte Android over five megabits with 200 ms of latency. Checked against that device profile in the build, not against the laptop the code was written on. |
| 250 KB gzipped | All JavaScript on teacher and guardian paths. Exceeding it fails the build, which is why the attendance app carries a hand-rolled queue rather than a sync library. |
| 125 KB, four files | The entire font budget, both scripts included. Bangla faces are large, so they are subsetted to the glyph ranges actually used rather than shipped whole. |
| 3 seconds | From an absence being logged to substitute suggestions being on the screen, at the ninety-ninth percentile — inside the window where a coordinator is still holding the problem in their head. |
| 60 seconds, then 10 minutes | A first feasible routine, then the full solve, for 2,000 lessons across 120 teachers and 60 rooms. The first number is the one that matters in practice: improving answers stream to the screen rather than a progress bar spinning against a silent process. |
| 90% of unchanged lessons | After a single change — one teacher’s availability, one room withdrawn — at least this share keep their slot, their teacher and their room. The test is blocking, and the diff is shown on screen before a routine is published. |
| 2,500 invoices in 60 seconds | Issued asynchronously with progress shown, rather than a screen that appears to have frozen while a term’s billing generates. |
| 72 hours | Capture with no network at all, syncing back with no loss, no duplicate and no silent overwrite, and every conflict visible in the queue rather than resolved out of sight. |
| 15 minutes | A complete self-serve export for a 2,500-student institution — every record you gave us and every record we created for you, in open formats. Not a support ticket, not a fee, not a quarterly window. |
| 15 minutes and 4 hours | Recovery point and recovery time, with a timed restore drill every month. A recovery objective nobody has rehearsed is a number rather than a plan. |
| 35 days | The minimum period a backup stays immutable. Encrypted, and held in a second facility inside Bangladesh, so a bad actor with production access still cannot erase the history. |
| 20,000 views in 10 minutes | Guardian page views against a single endpoint on result day, tested cold-cache as well as warm. A burst that only passes warm is not a tested burst. |
| 15 minutes | The time within which a recording failure or a device health failure is detected. A camera that quietly stopped recording last Tuesday is worse than no camera, because somebody believed it was working. |
| 99.5% monthly | Availability in the first year. That is the honest figure for a single in-country primary with a manual-promotion standby, and 99.9% was rejected with the arithmetic rather than adopted as a slogan. |
| 07:00 to 08:15 | The daily dispatch window, named in the contract alongside fee deadlines and result days. No change is deployed inside it, and none inside the others. |
| WCAG 2.2 AA | Automated checks on every end-to-end page plus a manual keyboard pass on every screen rather than a sample, blocking the release on a violation. A functional constraint, not a badge — there is no certificate, and accessibility certificates do not exist. |
| 44 × 44 px | Every touch target, asserted in component tests, so a control that shrinks below it fails before anyone tries to tap it one-handed on a bus. |
| 360 px | The narrowest supported width, treated as the design floor rather than a fallback. A visual check catches Bangla overflow there, because Bangla sets 20 to 30 per cent longer than the English a layout was drawn against. |
Commitments, not results. Several of these still depend on an experiment we have not run. The ten-minute solve envelope at full school size is the clearest case: it rests on the arithmetic of the encoding, not on a stopwatch. Where that is true, the number is something we have written down and expect to be measured against — never something we are reporting.
The same discipline governs what is missing. There is no uptime history, no certification, no penetration-test result and no accuracy figure anywhere on this page, because none of those has happened. Where a figure would have to come from work we have not yet done, it is absent rather than estimated.
The target we refused
A blanket ninety-fifth percentile under 200 ms was proposed for every endpoint, and rejected. A reconciliation matching endpoint and a batch of two and a half thousand invoices cannot meet it, and a target a team cannot meet is a target a team learns to measure around — by excluding the slow endpoints, by sampling the quiet hour, by redefining what counts as a request. Three hundred milliseconds for reads and eight hundred for writes hold across every endpoint, including the awkward ones. Which target somebody refused tells you more than which one they accepted.
The promise we cannot make
Recovery time is committed at four hours. The dispatch window is forty-five minutes long. Those two numbers do not reconcile, and we are not going to pretend they do: if the service is down at 07:10, we will not be back before the bell. So the commitment is a different one — dispatch degrades rather than stops. The last published routine stays readable offline, on the printed cards and on the front-desk board. Absences come in by SMS. And when the service returns, every substitution is recorded with the time it actually happened rather than the time somebody got round to typing it.
Argue with any of it
If one of these decisions is wrong for your situation, the reason is written down and can be tested against your constraints rather than ours. Tell us what the system has to survive and we will tell you which of the above changes.