feat(policy): rule builder and mandatory dry-run sandbox
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>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260731065427 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE policy_simulation_runs (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, policy_version SMALLINT NOT NULL, sample_size SMALLINT NOT NULL, affected_count SMALLINT NOT NULL, severity VARCHAR(10) NOT NULL, report JSON NOT NULL, created_at INT NOT NULL, entity_type VARCHAR(10) NOT NULL, entity_id INT NOT NULL, policy_id INT NOT NULL, run_by INT DEFAULT NULL, UNIQUE INDEX UNIQ_F993F7C8D17F50A6 (uuid), INDEX IDX_F993F7C82D29E3C6 (policy_id), INDEX IDX_F993F7C84114BD6 (run_by), INDEX idx_psr_policy (policy_id, policy_version, created_at), INDEX idx_psr_tenant (entity_type, entity_id, created_at), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
|
||||
$this->addSql('ALTER TABLE policy_simulation_runs ADD CONSTRAINT FK_F993F7C82D29E3C6 FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE policy_simulation_runs ADD CONSTRAINT FK_F993F7C84114BD6 FOREIGN KEY (run_by) REFERENCES users (id) ON DELETE SET NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE policy_simulation_runs DROP FOREIGN KEY FK_F993F7C82D29E3C6');
|
||||
$this->addSql('ALTER TABLE policy_simulation_runs DROP FOREIGN KEY FK_F993F7C84114BD6');
|
||||
$this->addSql('DROP TABLE policy_simulation_runs');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user