feat: implement sanctions screening vertical

This commit is contained in:
2026-07-29 18:46:53 +02:00
parent 05c3a2257a
commit 11404ac42f
25 changed files with 6531 additions and 38 deletions
+290 -24
View File
@@ -1,14 +1,56 @@
from __future__ import annotations
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
from govoplan_core.core.modules import DocumentationLink, DocumentationTopic, ModuleManifest, PermissionDefinition, RoleTemplate
from pathlib import Path
from govoplan_core.core.access import (
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
)
from govoplan_core.core.module_guards import (
drop_table_retirement_provider,
persistent_table_uninstall_guard,
)
from govoplan_core.core.modules import (
DocumentationLink,
DocumentationTopic,
FrontendModule,
FrontendRoute,
MigrationSpec,
ModuleInterfaceRequirement,
ModuleManifest,
NavItem,
PermissionDefinition,
RoleTemplate,
)
from govoplan_core.core.views import ViewSurface
from govoplan_core.db.base import Base
from govoplan_risk_compliance.backend.db.models import (
RiskSanctionsAddress,
RiskSanctionsAlias,
RiskSanctionsDate,
RiskSanctionsEntry,
RiskSanctionsIdentifier,
RiskSanctionsListSnapshot,
RiskScreeningCandidate,
RiskScreeningDisposition,
RiskScreeningException,
RiskScreeningRun,
RiskScreeningSubjectSnapshot,
)
from govoplan_risk_compliance.backend.permissions import (
ADMIN_SCOPE,
READ_SCOPE,
SANCTIONS_ADMIN_SCOPE,
SANCTIONS_READ_SCOPE,
SANCTIONS_REVIEW_SCOPE,
SANCTIONS_SCREEN_SCOPE,
WRITE_SCOPE,
)
MODULE_ID = "risk_compliance"
MODULE_NAME = "Risk Compliance"
MODULE_VERSION = "0.1.8"
READ_SCOPE = "risk_compliance:workspace:read"
WRITE_SCOPE = "risk_compliance:workspace:write"
ADMIN_SCOPE = "risk_compliance:workspace:admin"
OPTIONAL_DEPENDENCIES = (
"audit",
"policy",
@@ -17,10 +59,28 @@ OPTIONAL_DEPENDENCIES = (
"files",
"tasks",
"notifications",
"connectors",
)
_PERSISTENT_MODELS = (
RiskScreeningException,
RiskScreeningDisposition,
RiskScreeningCandidate,
RiskScreeningRun,
RiskScreeningSubjectSnapshot,
RiskSanctionsAddress,
RiskSanctionsDate,
RiskSanctionsIdentifier,
RiskSanctionsAlias,
RiskSanctionsEntry,
RiskSanctionsListSnapshot,
)
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
def _permission(
scope: str,
label: str,
description: str,
) -> PermissionDefinition:
module_id, resource, action = scope.split(":", 2)
return PermissionDefinition(
scope=scope,
@@ -35,52 +95,159 @@ def _permission(scope: str, label: str, description: str) -> PermissionDefinitio
PERMISSIONS = (
_permission(READ_SCOPE, "View risk compliance workspace", "Read risk compliance records, configuration, and workflow context."),
_permission(WRITE_SCOPE, "Manage risk compliance workspace", "Create and update risk compliance records and workflow state."),
_permission(ADMIN_SCOPE, "Administer risk compliance workspace", "Configure risk compliance policies, templates, and tenant-level administration."),
_permission(
READ_SCOPE,
"View risk compliance workspace",
"Read risk compliance records, configuration, and workflow context.",
),
_permission(
WRITE_SCOPE,
"Manage risk compliance workspace",
"Create and update risk compliance records and workflow state.",
),
_permission(
ADMIN_SCOPE,
"Administer risk compliance workspace",
"Configure risk compliance policies, templates, and tenant administration.",
),
_permission(
SANCTIONS_READ_SCOPE,
"View sanctions screening evidence",
"Read list snapshots, screening runs, and candidate evidence.",
),
_permission(
SANCTIONS_SCREEN_SCOPE,
"Run sanctions screening",
"Submit subjects for deterministic screening against an immutable list.",
),
_permission(
SANCTIONS_REVIEW_SCOPE,
"Review sanctions candidates",
"Record evidence-backed candidate dispositions.",
),
_permission(
SANCTIONS_ADMIN_SCOPE,
"Administer sanctions screening",
"Import source snapshots, configure policy, and authorize review overrides.",
),
)
ROLE_TEMPLATES = (
RoleTemplate(
slug="risk_compliance_manager",
name="Risk Compliance manager",
description="Manage risk compliance records and workflow state.",
permissions=(READ_SCOPE, WRITE_SCOPE),
description=(
"Manage compliance workflows and administer sanctions screening."
),
permissions=(
READ_SCOPE,
WRITE_SCOPE,
SANCTIONS_READ_SCOPE,
SANCTIONS_SCREEN_SCOPE,
SANCTIONS_REVIEW_SCOPE,
SANCTIONS_ADMIN_SCOPE,
),
),
RoleTemplate(
slug="risk_compliance_reviewer",
name="Risk Compliance reviewer",
description=(
"Run screenings and independently review potential matches."
),
permissions=(
READ_SCOPE,
SANCTIONS_READ_SCOPE,
SANCTIONS_SCREEN_SCOPE,
SANCTIONS_REVIEW_SCOPE,
),
),
RoleTemplate(
slug="risk_compliance_viewer",
name="Risk Compliance viewer",
description="Read risk compliance records and workflow context.",
permissions=(READ_SCOPE,),
description="Read risk compliance records and screening evidence.",
permissions=(READ_SCOPE, SANCTIONS_READ_SCOPE),
),
)
def _route_factory(_context):
from govoplan_risk_compliance.backend.router import router
return router
def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
return {
"risk_sanctions_list_snapshots": (
session.query(RiskSanctionsListSnapshot)
.filter(
RiskSanctionsListSnapshot.tenant_id == tenant_id
)
.count()
),
"risk_screening_runs": (
session.query(RiskScreeningRun)
.filter(RiskScreeningRun.tenant_id == tenant_id)
.count()
),
"risk_pending_screening_candidates": (
session.query(RiskScreeningCandidate)
.filter(
RiskScreeningCandidate.tenant_id == tenant_id,
RiskScreeningCandidate.review_status.in_(
("pending", "exception_review")
),
)
.count()
),
}
DOCUMENTATION = (
DocumentationTopic(
id=f"{MODULE_ID}.module-boundary",
title=f"{MODULE_NAME} module boundary",
summary="Risk and compliance workflows for data protection incidents, DPIAs, compliance controls, audit measures, risk registers, and internal-control evidence.",
summary=(
"Risk and compliance workflows own legal evaluation, immutable "
"screening evidence, review, and dispositions."
),
body=(
"This repository is currently a platform module seed. It registers the domain boundary, "
"permission surface, role templates, and documentation metadata before runtime APIs, "
"database models, migrations, and WebUI routes are introduced."
"Connectors may acquire source evidence, but Risk Compliance "
"owns immutable normalized sanctions lists, version-pinned "
"screening, candidate review, and legal dispositions. Fuzzy "
"matching only creates candidates and never confirms a match."
),
layer="available",
documentation_types=("admin",),
audience=("operator", "module_admin", "product_owner"),
documentation_types=("admin", "user"),
audience=(
"operator",
"module_admin",
"compliance_reviewer",
),
order=100,
related_modules=OPTIONAL_DEPENDENCIES,
links=(
DocumentationLink(
label="Repository domain boundary",
href="govoplan-risk-compliance/docs/RISK_COMPLIANCE_DOMAIN_BOUNDARY.md",
href=(
"govoplan-risk-compliance/"
"docs/RISK_COMPLIANCE_DOMAIN_BOUNDARY.md"
),
kind="repository",
),
),
metadata={
"seed": True,
"domain_objects": ['risk registers', 'compliance controls', 'DPIA records', 'data protection incident records', 'audit measures', 'internal-control evidence'],
"first_slice": "Define risk register, control, evidence, DPIA, incident, measure, and review-cycle concepts.",
"domain_objects": [
"sanctions list snapshots",
"screening runs",
"candidate evidence",
"review dispositions",
"time-bounded exceptions",
],
"privacy": (
"Queue and audit summaries contain stable references and "
"minimal subject data."
),
},
),
)
@@ -91,12 +258,111 @@ manifest = ModuleManifest(
version=MODULE_VERSION,
dependencies=("access",),
optional_dependencies=OPTIONAL_DEPENDENCIES,
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
required_capabilities=(
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
),
requires_interfaces=(
ModuleInterfaceRequirement(
name="connectors.sanctions_snapshots",
version_min="1.0.0",
version_max_exclusive="2.0.0",
optional=True,
),
),
permissions=PERMISSIONS,
role_templates=ROLE_TEMPLATES,
route_factory=_route_factory,
frontend=FrontendModule(
module_id=MODULE_ID,
package_name="@govoplan/risk-compliance-webui",
routes=(
FrontendRoute(
path="/risk-compliance",
component="RiskCompliancePage",
required_any=(SANCTIONS_READ_SCOPE,),
order=115,
surface_id="risk_compliance.workspace",
),
),
nav_items=(
NavItem(
path="/risk-compliance",
label="Risk Compliance",
icon="shield-check",
required_any=(SANCTIONS_READ_SCOPE,),
order=115,
surface_id="risk_compliance.navigation",
),
),
view_surfaces=(
ViewSurface(
id="risk_compliance.sanctions.sources",
module_id=MODULE_ID,
kind="section",
label="Sanctions source snapshots",
order=20,
),
ViewSurface(
id="risk_compliance.sanctions.screening",
module_id=MODULE_ID,
kind="section",
label="Sanctions screening",
order=30,
),
ViewSurface(
id="risk_compliance.sanctions.review",
module_id=MODULE_ID,
kind="section",
label="Sanctions review queue",
order=40,
),
),
),
tenant_summary_providers=(_tenant_summary,),
migration_spec=MigrationSpec(
module_id=MODULE_ID,
metadata=Base.metadata,
script_location=str(
Path(__file__).with_name("migrations") / "versions"
),
migration_after=("connectors",),
retirement_supported=True,
retirement_provider=drop_table_retirement_provider(
*_PERSISTENT_MODELS,
label="Risk Compliance",
),
retirement_notes=(
"Destructive retirement removes immutable sanctions list, "
"screening, and review evidence after a database snapshot."
),
),
uninstall_guard_providers=(
persistent_table_uninstall_guard(
*_PERSISTENT_MODELS,
label="Risk Compliance",
),
),
documentation=DOCUMENTATION,
)
def get_manifest() -> ModuleManifest:
return manifest
__all__ = [
"ADMIN_SCOPE",
"MODULE_ID",
"MODULE_VERSION",
"PERMISSIONS",
"READ_SCOPE",
"ROLE_TEMPLATES",
"SANCTIONS_ADMIN_SCOPE",
"SANCTIONS_READ_SCOPE",
"SANCTIONS_REVIEW_SCOPE",
"SANCTIONS_SCREEN_SCOPE",
"WRITE_SCOPE",
"get_manifest",
"manifest",
]