Case study
A Conservation Leadership Training Platform
A multi-tenant capability platform for conservation organisations — protected-area authorities, ranger corps and training colleges that need to know what each role requires, where every person stands against it, and which training closes the distance.
- Role
- Contracted engineering. Architecture through handover.
- Duration
- Eight months, four delivery milestones.
- Stack
- Django, PostgreSQL, React, TypeScript, Docker.
- Status
- Built and tested; in final assurance.
The original draft of this case study was written by hand. It was then structured and expanded with AI assistance, and reviewed and approved by us before publication. The judgements, the decisions and the mistakes described are ours.
The problem
A ranger corps cannot tell you what it knows.
Protected-area organisations run distributed workforces under real constraint: remote sites, intermittent connectivity, thin administrative capacity, and turnover that takes institutional knowledge with it. They can tell you headcount and payroll. They cannot tell you, with evidence, what a section ranger is required to be able to do, how far any individual stands from that, or whether the training budget is buying the capability they actually lack.
The information exists. It sits in spreadsheets on individual laptops, in training certificates in filing cabinets, and in the judgement of long-serving managers. None of it survives a resignation. When a capable person leaves, the organisation loses both the capability and the only record that it existed.
The brief was to replace that arrangement with a system where role requirements are defined once, progress is recorded against them continuously, and the resulting picture is available to the people who have to act on it.
Architecture
One definition, four consumers.
The centre of the system is a competency framework two levels deep: six modules, each expressed at three levels of development, resolving into 117 discrete learning outcomes. Every other capability in the product is a view onto that one structure rather than a parallel implementation of it.
This is the decision the whole build turns on. Role requirements, individual progress, gap analysis, succession ranking and career roadmaps all resolve to the same outcome records — which is why the numbers on different screens agree with each other, and why adding a new consumer costs a query rather than a schema.
Scoring
Measuring a gap is a design decision, not a calculation.
Coverage for a person against a module is completed outcomes over assigned outcomes, banded at fixed thresholds: 80% and above is green, 40% and above is amber, below 40% is red. Work in progress counts zero. A half-finished module is not half a capability — either the person can do the thing or they cannot.
The consequential decision is what happens when nothing has been assigned at all. The obvious implementation returns 100%: no requirements, nothing missing, everything satisfied. That is wrong in a way that hides exactly the problem the system exists to expose — a role nobody has defined requirements for looks perfectly staffed. It returns zero instead, and the comment in the source says why.
0.0 when no targets assigned — not 100% (no requirements ≠ fully covered)
Readiness
Succession from evidence rather than recollection.
Asked who could step into a section ranger post, most organisations answer from memory. The platform answers by counting: for every employee, how many of that role's required outcomes they have actually completed. The ranking below is computed from live progress records, and the fraction beside each score is the working — nineteen of twenty-two requirements met.
Two scores, deliberately different
The system computes readiness two ways and it is worth being precise about why. The career and succession path is binary: an outcome is met or it is not, because promoting someone on half-finished evidence is how the old spreadsheet failed. The analytics engine is weighted, giving half credit for work in progress, because a capability trend that ignores everything currently underway shows a department as flat while it is visibly improving. Same records, two questions, two defensible answers — and they are never presented as one number.
Tenancy
Where a forged request actually dies.
Every organisation on the platform holds isolated data, and the tenant is named in a client-controlled request header. That header is trivially forgeable, so the interesting question is not that isolation exists but which layer refuses the request — and that answer had to be exact rather than reassuring.
Resolution and enforcement are separate. Middleware resolves the header to an organisation before authentication runs, which means it cannot check membership itself. Membership is therefore enforced inside the authentication class — the single point every authenticated request passes through, regardless of what permissions an individual view happens to declare. Views then scope their own queries to the resolved organisation, and soft-deletion is filtered by default at the manager layer.
Assurance
The tests were lying, and that was the finding.
Midway through the build, the coverage instrumentation was found to be measuring the wrong thing on both sides of the stack. The frontend number was the serious one: it had been reporting a comfortable figure while true coverage was a small fraction of it. A static analysis tool was also passing cleanly for the best possible reason — it was scanning no files at all.
All three were configuration faults rather than code faults, which is exactly why nothing had surfaced them. A green check that is not measuring anything is worse than no check, because it actively purchases confidence. Each was fixed, the true position was measured and stated plainly, and coverage was then raised against honest numbers rather than flattering ones.
The suite now stands at 861 backend tests across 41 files and 158 frontend tests across 21. A security hardening pass followed, and its findings were closed behind regression tests so the same defects cannot return quietly.
| Control | As configured |
|---|---|
| Access / refresh token lifetime | 60 min / 14 days, rotated with the old token blacklisted |
| Token transport | HttpOnly cookies, with double-submit CSRF on unsafe methods |
| Concurrent sessions | Capped at 5; exceeding it evicts the oldest rather than refusing the newest |
| Session window | 24 h rolling, reset on activity, expiring at browser close |
| Failed logins | Locked after 5 attempts for 15 min, tracked on address and identity together |
| Rate limits | Login 5/min, password reset 3/hour, registration 10/hour |
| Analytics cache | 1 h, partitioned by organisation in the key, invalidated on progress events |
| Retention | Soft-deleted records purged at 90 days; audit trail kept 2 years |
Scale
Approach
Structure before scale.
Map reality before proposing technology
The data model came out of how these organisations already work — roles, ranks, development levels, the real shape of a ranger career — not out of a framework's defaults. Where the existing process was sound the system encoded it. Where it was broken, that was named as a process problem rather than quietly automated.
Systems before features
Four capabilities that could each have been built as a separate product were built as views onto one competency framework. That is why the figures agree across screens, and why the fifth consumer will be cheap.
Build for the constrained case
The intended operators have limited technical capacity, distributed sites and real turnover. The system is containerised for predictable deployment, documented for people who did not build it, and structured so knowledge lives in the platform rather than in whoever currently holds it.
Say what is actually true
Coverage figures were corrected downward before they were improved. A gap of unknown size is reported as zero rather than complete. Two readiness scores are kept visibly distinct rather than averaged into one comfortable number. A system that flatters its operators is not a measurement system.
Outcome
A system that can be handed over.
The platform runs end to end: organisational structure, role requirements, the competency framework, progress tracking, gap analysis, career pathways, a shared training catalogue, a funding workflow, and the dashboards above them. Reporting, deployment and security hardening are complete; what remains is documentation and sign-off rather than development.
Scope grew materially over the engagement — a funding-application subsystem, a cross-organisational coordinating role, shared career ladders and a provider-facing dashboard all arrived after the original brief was met. The measure of the work is not that it launched. It is that it can now be operated, extended and understood by people who were not there when it was built.
Technology is never the objective. Better organisational capability is. Leadwood Systems