Fifteen rows across five tasks said the mechanism was there and the test was
not. Each of these is a case where being wrong would be silent.
- the price rows must add up to the final amount. The chain test checks every
number individually, which stays green if a new row is added and left out of
the total; this checks the relationship itself.
- a fixed deposit beats a percentage one, and neither can exceed the final
amount — charging a deposit larger than the bill puts the patient in debt
before the visit.
- an appointment booked without a service still gets an invoice. Slot mode has
no service, and without this the financial report is short a row with nothing
to say which.
- the four accuracy thresholds, each tested on its own boundary. One step off
and either everything is red (so nobody looks) or nothing is (so the report
is pointless). Includes a short-running service, since the deviation is
measured on its absolute value.
- all six policy templates build a policy that survives the normal validation,
simulation and activation path. A template is a shortcut, not a second road:
if one of them produced something the validator rejects, a user could create
a rule in one click that never works.
- simulation leaves nothing pending for a later flush in the same request. That
is what the finally-rollback-clear is for, and the failure would surface in
the next operation rather than in the sandbox.
The course controller was reading $this->credits without it being injected —
phpstan caught it; the package-shortfall path had no test yet and would have
500'd on the first course that had a package.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 09 shipped a powerful API that a non-technical clinic owner could not
safely use. This closes that gap: activation now requires having seen what the
rule actually does.
- PolicySimulator runs a policy against real past appointments and writes
nothing: evaluation works on facts (never entities), the whole run sits in a
transaction rolled back and cleared in `finally`, and a test counts rows in
five sensitive tables before and after
- activate() now demands a simulation of the *same version* — a report for
version 1 does not unlock version 2
- PolicyTemplateRegistry: six ready-made rules, so the common case never
touches a raw condition
- Severity from the affected ratio; 0% is a warning too, since a rule that
changes nothing usually has a condition that never matches
- An empty clinic still succeeds with a warning, otherwise a new clinic could
never activate anything
Admin: PoliciesPage, PolicyFormPage, PolicySimulationPage, and a
PolicyConditionBuilder built entirely from GET /policy-schema — a test proves a
field that exists only in the schema shows up with no frontend change, and that
operators are filtered per field type.
The schema response now carries per-field metadata (label, type, meaningful
operators) so the form has one source of truth instead of two.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>