feat(template-rules): backend foundation for profile-based document recommendations
CI / detect-changes (push) Successful in 12s
CI / branch-name (push) Has been skipped
CI / test-python-backend (push) Successful in 33s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 19s
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 7s
CI / validate-canonical-controls (push) Successful in 16s
CI / loc-budget (push) Failing after 18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m27s
CI / test-go (push) Failing after 46s
CI / iace-gt-coverage (push) Successful in 25s
CI / detect-changes (push) Successful in 12s
CI / branch-name (push) Has been skipped
CI / test-python-backend (push) Successful in 33s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 19s
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 7s
CI / validate-canonical-controls (push) Successful in 16s
CI / loc-budget (push) Failing after 18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m27s
CI / test-go (push) Failing after 46s
CI / iace-gt-coverage (push) Successful in 25s
Introduces the sustainable backend replacement for the hardcoded inline rules in
admin-compliance/app/sdk/document-generator/templateRecommendations.ts.
What's in this commit (Phase 1.1 - 1.5 of the rustling-yawning-boot plan):
- Migration 147: 4 new tables
- compliance_template_rules (rule shell, document_type, current_version_id)
- compliance_template_rule_versions (lifecycle, JSONB conditions,
source_citation, change_summary, approval timestamps)
- compliance_template_rule_approvals (audit trail)
- compliance_tenant_rule_overrides (per-tenant classification overrides)
Plus partial unique index for "only one is_live=1 version per rule".
- SQLAlchemy models: TemplateRuleDB, TemplateRuleVersionDB,
TemplateRuleApprovalDB, TenantRuleOverrideDB (compliance/db/).
- Pydantic schemas (compliance/schemas/template_rule.py): full request/response
set including RecommendationRequest/Result with reasons and override tracking.
- TemplateRuleService (compliance/services/): CRUD + Lifecycle transitions
(submit_for_review/approve/publish/reject) following legal_document_service.py
pattern with _transition() helper and approval audit trail. Plus tenant
override upsert.
- RecommendationService: condition evaluator (eq, neq, in, not_in, gte/lte/gt/lt,
exists, truthy) over JSONB conditions, override application, reason generation
for human-readable explanations in workspace UI.
- 18 FastAPI routes in compliance/api/template_rule_routes.py covering rule CRUD,
version lifecycle, override management and POST /recommend evaluation endpoint.
- Seed data: 33 initial rules ported from templateRecommendations.ts in
compliance/data/template_rule_seed_data.py, written as published versions
on first seed run. Idempotent via rule_key.
Phase 1.6 (pytest suite) and Phase 2 (editorial UI in admin-compliance) follow
in separate commits.
[migration-approved]
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Seed-Runner: legt die in ``compliance.data.template_rule_seed_data.SEED_RULES``
|
||||
definierten Regeln als published Versionen in ``compliance_template_rules`` an.
|
||||
|
||||
Idempotent über ``rule_key`` — wiederholtes Ausführen erzeugt keine Duplikate.
|
||||
Quellen-Citation = ``rationale`` als Default (anwaltlich zu prüfen).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Pfad-Setup für Standalone-Ausführung
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from classroom_engine.database import SessionLocal # noqa: E402
|
||||
from compliance.data.template_rule_seed_data import SEED_RULES # noqa: E402
|
||||
from compliance.db.template_rule_models import ( # noqa: E402
|
||||
TemplateRuleDB,
|
||||
TemplateRuleVersionDB,
|
||||
TemplateRuleApprovalDB,
|
||||
)
|
||||
|
||||
|
||||
def seed() -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
created = 0
|
||||
skipped = 0
|
||||
for entry in SEED_RULES:
|
||||
existing = (
|
||||
db.query(TemplateRuleDB)
|
||||
.filter(TemplateRuleDB.rule_key == entry["rule_key"])
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
rule = TemplateRuleDB(
|
||||
rule_key=entry["rule_key"],
|
||||
document_type=entry["document_type"],
|
||||
title=entry["title"],
|
||||
)
|
||||
db.add(rule)
|
||||
db.flush()
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
version = TemplateRuleVersionDB(
|
||||
rule_id=rule.id,
|
||||
version_number=1,
|
||||
status="published",
|
||||
is_live=1,
|
||||
classification=entry["classification"],
|
||||
conditions=entry["conditions"],
|
||||
source_citation=entry.get("rationale", "TODO — anwaltlich zu pruefen"),
|
||||
rationale=entry.get("rationale"),
|
||||
change_summary="Initial-Seed der Inline-Regeln",
|
||||
created_by="seed",
|
||||
submitted_by="seed",
|
||||
submitted_at=now,
|
||||
approved_by="seed",
|
||||
approved_at=now,
|
||||
published_by="seed",
|
||||
published_at=now,
|
||||
)
|
||||
db.add(version)
|
||||
db.flush()
|
||||
|
||||
rule.current_version_id = version.id
|
||||
|
||||
for action in ("created", "submitted", "approved", "published"):
|
||||
db.add(TemplateRuleApprovalDB(
|
||||
version_id=version.id, action=action, approver="seed",
|
||||
))
|
||||
|
||||
created += 1
|
||||
|
||||
db.commit()
|
||||
print(f"OK created={created} skipped={skipped} (already present)")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
seed()
|
||||
Reference in New Issue
Block a user