Engineering case study
A working production system arrives as a folder. Its authors are gone, its specification never existed. This is an account of coming to understand it, rebuilding it, and handing it back in a form the receiving party can stand up and verify alone.
An insurer's product-management and policy-sales system — an AngularJS front end on a PHP back end, embedded into a public web page — was handed over for modernisation. Two developers, AI-assisted, over 234 days of which 96 carry commits. The work produced 125,769 lines of source and 991,137 lines of documentation.
Read those last two numbers again. Roughly eight lines of documentation for every line of source. On a project that builds something new, a ratio like that would be absurd. Here it is the shape of the work.
What the client was buying was not code. They already had code — working, in production, for years. What they lacked, and what had stopped them changing anything, was understanding. And understanding has no other form: it exists as documentation or it does not exist at all. In a takeover the documentation is the deliverable, and the ratio is simply what that costs.
Before any of that there was an act nobody plans for. The inherited system was not containerised, and no deployment documentation existed in any form — so the first work was not understanding it but making it reproducible: standing it up from scratch, by experiment, until it could be observed twice and give the same answer.
Then three acts, and most legacy projects fail by skipping one of them. Come to know the system and prove that you know it. Rebuild it so the rebuild is verifiable against the original rather than merely nicer. Then hand it back so the receiving party can run and check it without you — which is a distinct engineering job, not the moment after the last commit.
Nothing from the client's system appears on this page: no screen, no schema object, no business rule, no commercial figure. The captures taken while documenting the original are counted here — 539 of them — and shown nowhere. The client is not named, and neither is their market segment.
The starting position was a folder. In it: the legacy database with its structure and its data, the original application source for both halves — an AngularJS single-page front end and a PHP back end, served inside a WordPress shell page — the initialisation and seed scripts, and the pricing tables in two formats.
What was not in it matters more. The system was not containerised in any form, and there was no deployment documentation whatsoever — nothing describing how to stand it up, what it depended on, or how its environment was configured. A running production system existed somewhere; a way to reproduce it did not.
Before a single behaviour could be documented, the inherited system had to be made reproducible. That meant containerising it from scratch and working out its environment, its character encoding and its initialisation by experiment — roughly 400 files of environment work that produced no documentation and no product.
It is the easiest thing in a takeover to leave out of a plan, and everything else is impossible without it. You cannot document behaviour you cannot observe twice.
Even with all of that, it was still not a specification. A running system plus its source tells you what happens; it does not tell you what is supposed to happen, and it certainly does not tell you which of its behaviours are decisions and which are accidents that nobody has dared touch.
The data model underneath was layered: reference data at the base, the product structure above it, then the product-dependent configuration where pricing and surcharge rules live, and the business modules on top. That layering is the reason a system like this becomes untouchable. A change to a rule near the bottom reaches the surface through paths nobody has mapped, so eventually nobody changes anything, and the system quietly sets the pace of the business rather than the other way round.
That is one cost of a system nobody can explain. Not the maintenance bill — the decisions that stop being taken.
Discomfort does not release a budget. What does is the moment the stack itself becomes disqualifying: when the business is held to a requirement the system cannot meet at all, rather than one it meets awkwardly.
| What the old stack could not satisfy |
|---|
| Runtime and framework versions past their supported life |
| Security and compliance obligations the stack cannot meet |
| Deployability — nothing containerised, nothing reproducible |
| Integration with systems that expect current interfaces |
| A shrinking pool of people willing to maintain it |
These are not preferences. An unsupported runtime is not "old"; it is a component that will never receive another security fix, on a system holding other people's data. That is the argument that gets signed off, and it is worth stating plainly, because a modernisation proposal built on developer comfort deserves to lose to one built on this.
"Can AI write this faster" is the wrong question, and it is the one most accounts answer. Speed at writing code is not the constraint in a takeover. On day one there is nothing safe to write, because nothing is understood well enough for a change to be provably harmless.
The real question has two halves, and the second is the hard one:
How do you come to know a system nobody can explain any more — and how do you prove that you know it, to yourself and to the person paying?
Proof matters because understanding is invisible. A developer who has read the code feels confident, and that feeling is indistinguishable from the same feeling held by a developer who has misread it. The only difference that survives contact with reality is whether the understanding was written down in a form somebody else can check.
Which is why act one produced documents rather than code, and why the documents were audited rather than merely written.
Reverse engineering was run as a product, not as preparation. Each area had a definite scope and was tracked to completion, and several carried an average audit score rather than a tick.
| Area | Documented |
|---|---|
| Database table groups | 32 of 32 |
| Separate database audits | 33 |
| Screen documents | 142 of 142 |
| Function documents | 25 of 25 |
| Front-end documents | 25 of 25 |
| Back-end documents | 14 of 14 |
Behaviour was captured as well as described. Where a written description of a screen could be read two ways, a capture from the running original settles it. Those captures are evidence for the person doing the rebuild, not illustrations for a reader — which is why this page counts them and shows none.
The ratio is the honest measure of a takeover. A project that reports a thin documentation trail after inheriting an undocumented system has not done act one; it has skipped to act two and is about to reimplement behaviours it never understood, including the accidental ones.
Rebuilding began with the pricing engine, described in the project's own plan as the heart of the system. Building the core first, standalone, means the part everything else depends on is finished and testable before anything is built on top of it. It also concentrates the hardest domain logic into a component small enough to be reasoned about.
Then the two halves — and here is the mechanism that makes the whole rebuild verifiable, and the most transferable thing in this account.
The test suite was written first, against the still-running original. It is the invariant. Each half was then replaced on its own, and every resulting configuration was measured against the same unchanged suite.
The original ran AngularJS on PHP, inside a WordPress shell page. The target runs React on Node, inside the same shell. Both intermediate configurations were built and tested too — AngularJS on Node, and React on PHP — which is what turns a leap into two separate, verifiable steps. The rule is simply: never swap both halves at once. When a configuration goes red, there is one candidate cause, not two.
Neither intermediate is scaffolding. Each runs, passes the suite, and could have been shipped as it stood — which is what makes it safe to stop part-way, and on a client project that is not a theoretical property. Budgets and priorities move.
Swapping one variable at a time only means something if everything else is genuinely constant. Three things were held fixed for the whole route: the database, which was never migrated; a payment-provider emulator standing in for the real payment service; and a mail catcher instead of a live SMTP server.
The last two are not conveniences. A test that reaches a real payment provider is not repeatable and should not be run at all; a test that sends real e-mail is worse. Replacing every external dependency with something deterministic is the second half of act zero, and it is the reason the suite could run unattended, in any environment, as often as needed.
A rewrite normally has no oracle. Every difference between old and new is either an improvement or a regression, and there is no way to tell which — the knowledge that would settle it is exactly what you did not have when you started.
Authoring the suite against the original creates that oracle while the original is still there to be asked. It is a wasting asset: the day the original is switched off, the answer to "what was it supposed to do" becomes unavailable for ever. Write the questions down while something can still answer them.
The rebuild was planned before it was written, in numbered phases with an audit score each: 17 phases for the first engine generation, 13 for the second, and 15 fix plans between them. Separately, a full system plan for the eventual complete rewrite was produced and audited — 16 documents, 13 phases, 5 milestones, with a target stack of a modern server framework, a React-based front end and PostgreSQL.
Naming a target stack identifies nobody and is the part a reader can use. The plan's commercial figures belong to the client and are not reproduced here.
This is the act most projects treat as an afternoon of copying files, and the one that decides whether any of the rest was worth doing.
Handover is not publishing. It is the receiving party being able to stand the system up, in their own environment, and verify it themselves — without the person who built it in the room.
That has a shape, and the shape costs real work.
| Artefact | Count |
|---|---|
| Independently deliverable packages | 8 |
| Stack configurations built and tested | 4 |
| Acceptance rounds, each a full pass | 5 |
| Handover issues | 139 |
| Handover implementation plans | 92 |
| Environment configuration files | 44 |
| Handover test scripts | 52 |
| Continuous-integration pipeline files | 23 |
| Handover concepts and reports | 54 |
And the handover closed where it should: merged into the client's own version control and tagged there. Not a zip file, not a shared drive. The work landed in the system the receiving party already uses, in a state they can inspect, revert and continue.
Two streams of findings ran, and they ended in different places. That difference is worth being precise about, because the honest version is more useful than a clean one.
Findings reported from outside — by the people testing what was delivered — were all fixed, and all reported back. That loop is closed. A defect that is fixed but never confirmed to the person who raised it is not closed; it is merely quiet.
Findings from the project's own internal testing are not all fixed. Some remain open at the point this account was written. Saying so costs nothing and buys the only thing that matters in a report like this, which is that the other statements can be believed.
The audit rounds show the shape. On the rebuilt back end, an initial round examined 50 candidate findings and confirmed and fixed 17 — meaning two thirds of what was raised turned out not to be defects. A later revalidation round re-examined those 17 and closed them with 0 still open. The front end went through four such rounds.
Separately, one coverage campaign ran the end-to-end suite 604 times across 12 environment configurations and surfaced 12 issues. A suite that only ever runs in one environment cannot find that class of defect at all.
The work ran on two machines, with a deliberate line between them.
Inside a session, work was decomposed to purpose-briefed subagents, one per task. What did not exist was a set of standing role sessions coordinating with one another, each holding a fixed responsibility over time.
That was not a preference. At the time this project ran, agents could not coordinate with each other without a human standing in the middle as the message bus — and a human message bus does not scale past about three of them. Until that changed, the achievable unit was machine-level separation plus subagents inside sessions, which is exactly what was built.
The ambition was never the limiting factor. The tooling was.
This account is therefore a dated snapshot, not a claim about what is possible in general. The project began in November 2025, and what an arrangement like this can reach has moved since. Check the measurement date before drawing any conclusion from it.
The practical lesson generalises past this particular toolchain: when a method seems stuck, the binding constraint is often not the discipline but what the tools let you coordinate. It is worth knowing which one you are up against, because only one of them improves by trying harder.
First commit 2025-11-20, last 2026-07-12: 234 elapsed days, of which 96 carry commits, across 482 commits and 28 tagged releases.
The two dormant months matter for a reader trying to plan. Elapsed time in client work is not effort; it is effort plus the client's availability, and the second is usually the larger term. Quoting 234 days as if it were 234 days of work would be a lie by arithmetic.
Kept short: this is evidence for the account, not the account.
| Measure | Files | Lines |
|---|---|---|
| Documentation | 2,490 | 991,137 |
| Source | 644 | 125,769 |
| Tests | 223 | 50,262 |
| Database scripts | 43 | 12,152 |
| Shell automation | 154 | — |
| Behaviour captures (never shown) | 539 | — |
| All tracked files | 4,597 | — |
The documentation splits roughly into the rebuild specifications, the reverse-engineering record of the original system, and the handover material — with the handover alone accounting for several hundred documents.
| Suite | Result |
|---|---|
| Rewritten front end, unit | 96 / 96 |
| Ported back end, unit | 67 / 67 |
| Policy wizard, end to end | 8 / 8 |
| Validation suite | 20 / 20 |
| Authentication flow, end to end | 47 / 56 |
These suites are not incidental. The end-to-end tests are the same ones authored against the original AngularJS-and-PHP system, unchanged — which is what makes a number like 8 of 8 mean something. It is not "the new code passes its own tests"; it is "the new code answers the questions the old system answered, the same way".
The last row is the interesting one. It is reported here because it was reported there — in the project's own status line, at the top of its own README, where anyone opening the workspace would see it first. A team that puts its worst number where it cannot be missed is a team whose good numbers are worth something.
Correctness rested on three legs rather than one: unit tests around the rebuilt components, end-to-end runs through the real flows, and the audit rounds that examined the result independently of whoever produced it — including the two-thirds of raised findings that turned out not to be defects at all.
The inherited system arrived with no automated tests at all. It now has 50,262 lines of them across 223 files.
Nobody bought that. It arrived as a by-product of needing an oracle — and because the suite was authored against the original, it describes the behaviour the business actually depends on rather than the behaviour of the rewrite. It outlives every implementation it was used to verify, including the ones still to come.
The project stopped at a phase boundary, and the record says so plainly.
Understanding, the minimum viable core, the rebuild of the front end and the port of the back end were completed, as was the full system plan for the eventual complete rewrite. The implementation of that plan and the data migration behind it are recorded as planned, not as done. They were never started.
On defects, the position is asymmetric and worth stating exactly:
Reported from outside: all fixed, all reported back. That stream is closed.
Found by internal testing: not all fixed. Some remain open.
The asymmetry is a choice, not an oversight. A finding raised by the people relying on the delivery blocks their work and closes the loop only when they are told it is done. A finding the team raises against itself competes for the same hours as everything else, and some of them lose. Reporting the two as one number would hide exactly the distinction a reader needs.
None of this is a disclaimer. A project that can say precisely which acts it completed, which it planned, and which findings it is still carrying is demonstrating the same discipline as one that can prove what it shipped.
Little of this is specific to insurance, or to this stack. Four things transfer.
Understand, rebuild, hand over — in that order. Skip act one and you will faithfully reimplement bugs you never recognised as bugs, because from the inside an accident and a decision look identical. Skip act three and the client owns code they cannot run, which from their side is indistinguishable from owning nothing.
Expect documentation on the order of the source, and in a takeover expect it to exceed the source substantially — here by roughly eight to one. If your plan has a line for "documentation" at five per cent of effort, your plan is for a greenfield project wearing a takeover's name.
Can the receiving party stand it up in their own environment, from what you gave them, with you unavailable? Does each package carry its own configuration template and its own tests? Have you proven it in more than one combination? Does the work land in their version control, tagged? If any answer is no, the handover is a hope, not an engineering artefact.
While the original still runs, you have something no greenfield project ever has: a reference implementation that answers every question about intended behaviour. It is a wasting asset.
So bank it. Write the test suite against the original before you change anything, then replace one half of the system at a time and measure every intermediate state against that same suite. Two independent swaps instead of one simultaneous leap; a single candidate cause whenever something breaks; and a contract that survives every implementation you throw away.
If you take one thing from this account, take that. It costs a fortnight at the start and removes the question "is this different or is this broken" from the whole rest of the project.
Roughly eight lines of documentation per line of source is not a rounding error in a budget. It is most of the budget. Anyone adopting this approach should price act one honestly and expect to defend it, because it is the act that produces no running software and is therefore the easiest to cut — and cutting it is what turns a modernisation into a very expensive rewrite of misunderstandings.
Whether AI-assisted work is effective on a system does not depend on the model. It depends on three properties of the system itself:
| Condition | Inherited | Rebuilt |
|---|---|---|
| A reproducible environment — it can be stood up on demand and behaves the same way | no | yes |
| Written understanding — what it does exists as documents, not only in a head | no | yes |
| Behaviour locked in tests — a change can be shown safe, not argued safe | no | yes |
The inherited system had none of the three. The rebuilt one has all three — not as a goal anybody set, but as a consequence of doing the three acts properly.
Which answers the question the ratio raises. The documentation is not a one-off cost recovered by the delivery. It is the substrate that makes every later change cheap — whoever, or whatever, makes it.
That is the part worth understanding before deciding whether act one is affordable. A system that arrives undocumented, untested and unreproducible is expensive to change for ever, and every year of deferral compounds it. The same system with those three properties can be maintained and extended the same way it was rebuilt, at the same speed, by whoever comes next. The modernisation is the visible deliverable. The change in what the system costs to change is the durable one.
The method establishes what a system does and rebuilds it verifiably. It does not decide whether the business still wants that behaviour. Every faithfully reproduced rule is also a faithfully reproduced decision from years ago, and some of those deserve to be questioned rather than ported. That conversation is the client's, and it belongs after act two, when there is finally something measurable to change.
Nor does it remove judgement. Two thirds of the findings raised in the first audit round were not defects. Someone had to work out which third was real.
Most companies do not have a greenfield project. They have a system that works, that nobody fully understands, and that quietly decides what the business can and cannot do next. If that is familiar — or if you have taken one of these on and would tell it differently — I am interested in the comparison. Ferenc Erb on LinkedIn
Two developers: Ferenc Erb and László Villányi.