from __future__ import annotations from pathlib import Path from govoplan_core.core.access import ( CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER, ) from govoplan_core.core.dataflows import CAPABILITY_DATAFLOW_DATASET_OUTPUT from govoplan_core.core.module_guards import ( drop_table_retirement_provider, persistent_table_uninstall_guard, ) from govoplan_core.core.modules import ( CapabilityDocumentation, DocumentationLink, DocumentationTopic, FrontendModule, FrontendRoute, MigrationSpec, ModuleContext, ModuleInterfaceProvider, ModuleInterfaceRequirement, ModuleManifest, NavItem, PermissionDefinition, RoleTemplate, ) from govoplan_core.core.provider_governance import ( ModuleArchitectureDeclaration, ModuleArchitectureDocumentation, ModuleMaturityEvidence, ) from govoplan_core.core.reporting import ( CAPABILITY_POLICY_REPORTING_GOVERNANCE, CAPABILITY_REPORTING_RETENTION, ) from govoplan_core.core.search import SearchSourceProviderRegistration from govoplan_core.core.views import ViewSurface from govoplan_core.db.base import Base from govoplan_reporting.backend.acl import ReportingScopeAclProvider from govoplan_reporting.backend.contracts import ( CAPABILITY_REPORTING_CHART_RENDERER, CAPABILITY_REPORTING_REGISTRY, CAPABILITY_REPORTING_RUNNER, CAPABILITY_REPORTING_SCHEDULER, ) from govoplan_reporting.backend.db import models as reporting_models from govoplan_reporting.backend.definitions import ( ADMIN_SCOPE, READ_SCOPE, WRITE_SCOPE, ) from govoplan_reporting.backend.execution import ( QUALITY_SCOPE, RUN_SCOPE, SqlReportingRunner, ) from govoplan_reporting.backend.operations import ( IMPORT_SCOPE, PUBLISH_SCOPE, SCHEDULE_SCOPE, SqlReportingScheduler, ) from govoplan_reporting.backend.query_engine import DefaultChartRenderer from govoplan_reporting.backend.registry import SqlReportingRegistry from govoplan_reporting.backend.search_source import create_reporting_search_source MODULE_ID = "reporting" MODULE_NAME = "Reporting" MODULE_VERSION = "0.1.14" def _permission(scope: str, label: str, description: str) -> PermissionDefinition: module_id, resource, action = scope.split(":", 2) return PermissionDefinition( scope=scope, label=label, description=description, category=MODULE_NAME, level="tenant", module_id=module_id, resource=resource, action=action, ) PERMISSIONS = ( _permission( READ_SCOPE, "View reporting definitions", "Read accessible datasets, semantic models, reports, and quality plans.", ), _permission( WRITE_SCOPE, "Manage reporting definitions", "Create immutable revisions of Reporting definitions.", ), _permission( ADMIN_SCOPE, "Administer reporting", "Manage restricted definitions and Reporting governance.", ), _permission( RUN_SCOPE, "Run reports", "Execute accessible report revisions and export their authorized result.", ), _permission( PUBLISH_SCOPE, "Publish reports", "Send successful report results to configured publication providers.", ), _permission( SCHEDULE_SCOPE, "Schedule reports", "Create schedules and dispatch due report runs.", ), _permission( QUALITY_SCOPE, "Run report quality plans", "Evaluate dataset quality plans and inspect evidence.", ), _permission( IMPORT_SCOPE, "Assess report imports", "Assess external BI metadata and accept bounded approximations.", ), ) ROLE_TEMPLATES = ( RoleTemplate( slug="reporting_analyst", name="Reporting analyst", description="Define semantic reports, run them, and save analytical views.", permissions=(READ_SCOPE, WRITE_SCOPE, RUN_SCOPE, QUALITY_SCOPE), ), RoleTemplate( slug="reporting_publisher", name="Reporting publisher", description="Run, schedule, export, and publish accessible reports.", permissions=(READ_SCOPE, RUN_SCOPE, PUBLISH_SCOPE, SCHEDULE_SCOPE), ), RoleTemplate( slug="reporting_administrator", name="Reporting administrator", description="Administer definitions, imports, quality, schedules, and publications.", permissions=tuple(item.scope for item in PERMISSIONS), ), ) def _router(context: ModuleContext): from govoplan_reporting.backend.router import create_router return create_router(context.registry) def _registry(context: ModuleContext) -> SqlReportingRegistry: del context return SqlReportingRegistry() def _runner(context: ModuleContext) -> SqlReportingRunner: return SqlReportingRunner(context.registry) def _scheduler(context: ModuleContext) -> SqlReportingScheduler: return SqlReportingScheduler(context.registry) def _chart_renderer(context: ModuleContext) -> DefaultChartRenderer: del context return DefaultChartRenderer() def _retention(context: ModuleContext): del context from govoplan_reporting.backend.retention import ReportingRetentionService return ReportingRetentionService() def _tenant_summary(session, tenant_id: str) -> dict[str, int]: definitions = ( session.query(reporting_models.ReportingDefinitionRevision) .filter( reporting_models.ReportingDefinitionRevision.tenant_id == tenant_id, reporting_models.ReportingDefinitionRevision.superseded_at.is_(None), ) .count() ) reports = ( session.query(reporting_models.ReportingDefinitionRevision) .filter( reporting_models.ReportingDefinitionRevision.tenant_id == tenant_id, reporting_models.ReportingDefinitionRevision.definition_kind == "report", reporting_models.ReportingDefinitionRevision.superseded_at.is_(None), ) .count() ) executions = ( session.query(reporting_models.ReportingExecution) .filter(reporting_models.ReportingExecution.tenant_id == tenant_id) .count() ) schedules = ( session.query(reporting_models.ReportingSchedule) .filter( reporting_models.ReportingSchedule.tenant_id == tenant_id, reporting_models.ReportingSchedule.enabled.is_(True), ) .count() ) return { "reporting_definitions": definitions, "reports": reports, "report_executions": executions, "active_report_schedules": schedules, } manifest = ModuleManifest( id=MODULE_ID, name=MODULE_NAME, version=MODULE_VERSION, dependencies=("access",), optional_dependencies=( "dataflow", "datasources", "connectors", "dashboard", "files", "mail", "templates", "workflow_engine", "policy", "search", "notifications", ), required_capabilities=( CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR, ), optional_capabilities=( CAPABILITY_DATAFLOW_DATASET_OUTPUT, CAPABILITY_POLICY_REPORTING_GOVERNANCE, ), permissions=PERMISSIONS, role_templates=ROLE_TEMPLATES, route_factory=_router, nav_items=( NavItem( path="/reports", label="Reporting", icon="clipboard-pen-line", required_any=(READ_SCOPE,), order=74, surface_id="reporting.navigation", ), ), frontend=FrontendModule( module_id=MODULE_ID, package_name="@govoplan/reporting-webui", routes=( FrontendRoute( path="/reports", component="ReportingPage", required_any=(READ_SCOPE,), order=74, surface_id="reporting.workspace", ), FrontendRoute( path="/reporting", component="ReportingPage", required_any=(READ_SCOPE,), order=175, surface_id="reporting.compatibility", ), ), nav_items=( NavItem( path="/reports", label="Reporting", icon="clipboard-pen-line", required_any=(READ_SCOPE,), order=74, surface_id="reporting.navigation", ), ), view_surfaces=( ViewSurface( id="reporting.parameters", module_id=MODULE_ID, kind="section", label="Report parameters and filters", parent_id="reporting.workspace", order=30, ), ViewSurface( id="reporting.results", module_id=MODULE_ID, kind="section", label="Authorized report results", parent_id="reporting.workspace", order=40, ), ), ), provides_interfaces=( ModuleInterfaceProvider(name="reporting.registry", version="0.1.0"), ModuleInterfaceProvider(name="reporting.runner", version="0.1.0"), ModuleInterfaceProvider(name="reporting.scheduler", version="0.1.0"), ModuleInterfaceProvider(name="reporting.chart_renderer", version="0.1.0"), ModuleInterfaceProvider(name=CAPABILITY_REPORTING_RETENTION, version="1.0.0"), ), requires_interfaces=( ModuleInterfaceRequirement( name="dataflow.dataset_output", version_min="0.1.0", version_max_exclusive="0.2.0", optional=True, ), ModuleInterfaceRequirement( name=CAPABILITY_POLICY_REPORTING_GOVERNANCE, version_min="1.0.0", version_max_exclusive="2.0.0", optional=True, ), ), capability_factories={ CAPABILITY_REPORTING_REGISTRY: _registry, CAPABILITY_REPORTING_RUNNER: _runner, CAPABILITY_REPORTING_SCHEDULER: _scheduler, CAPABILITY_REPORTING_CHART_RENDERER: _chart_renderer, CAPABILITY_REPORTING_RETENTION: _retention, }, capability_documentation={ CAPABILITY_REPORTING_REGISTRY: CapabilityDocumentation( label="Reporting definition registry", summary="Stores versioned datasets, semantic models, reports, and quality plans.", contract_version="0.1.0", ), CAPABILITY_REPORTING_RUNNER: CapabilityDocumentation( label="Governed report runner", summary="Executes a pinned report graph over an authorized provider-owned dataset.", contract_version="0.1.0", ), CAPABILITY_REPORTING_SCHEDULER: CapabilityDocumentation( label="Report schedule dispatcher", summary="Claims due report schedules and records run/publication evidence.", contract_version="0.1.0", ), CAPABILITY_REPORTING_CHART_RENDERER: CapabilityDocumentation( label="Report chart renderer", summary="Builds provider-neutral chart models with an accessible tabular fallback.", contract_version="0.1.0", ), CAPABILITY_REPORTING_RETENTION: CapabilityDocumentation( label="Reporting result retention", summary="Minimizes expired provider-report detail while retaining audit hashes and provenance.", contract_version="1.0", documentation_types=("admin",), audience=("privacy_officer", "operator", "system_admin"), ), }, search_sources=( SearchSourceProviderRegistration( id="reporting.reports", factory=create_reporting_search_source, ), ), migration_spec=MigrationSpec( module_id=MODULE_ID, metadata=Base.metadata, script_location=str(Path(__file__).with_name("migrations") / "versions"), retirement_supported=True, retirement_provider=drop_table_retirement_provider( reporting_models.ReportingImportAssessment, reporting_models.ReportingQualityResult, reporting_models.ReportingPublication, reporting_models.ReportingSchedule, reporting_models.ReportingSavedView, reporting_models.ReportingDefinitionGrant, reporting_models.ReportingExecution, reporting_models.ReportingProviderExport, reporting_models.ReportingProviderExecution, reporting_models.ReportingDefinitionRevision, reporting_models.ReportingDefinitionIdentity, label="Reporting", ), retirement_notes=( "Destructive retirement requires a database snapshot and removes " "Reporting definitions, results, quality evidence, schedules, and publications." ), ), uninstall_guard_providers=( persistent_table_uninstall_guard( reporting_models.ReportingDefinitionIdentity, reporting_models.ReportingDefinitionRevision, reporting_models.ReportingDefinitionGrant, reporting_models.ReportingExecution, reporting_models.ReportingProviderExecution, reporting_models.ReportingProviderExport, reporting_models.ReportingSavedView, reporting_models.ReportingSchedule, reporting_models.ReportingPublication, reporting_models.ReportingQualityResult, reporting_models.ReportingImportAssessment, label="Reporting", ), ), resource_acl_providers=( ReportingScopeAclProvider("analytical_dataset"), ReportingScopeAclProvider("semantic_model"), ReportingScopeAclProvider("report"), ReportingScopeAclProvider("report_execution"), ), tenant_summary_providers=(_tenant_summary,), documentation=( DocumentationTopic( id="reporting.governed-bi", title="Governed reporting and semantic BI", summary="Build reproducible reports over provider-owned datasets without bypassing module or row-level access.", body=( "Reporting pins dataset, semantic-model, and report revisions. Runs retain " "definition hashes, source fingerprints, policy provenance, quality evidence, " "authorized result rows, diagnostics, and output hashes. Safe dimensions, " "aggregations, typed expressions, filters, pivots, saved views, chart models, " "schedules, exports, and publication providers replace unchecked SQL in the " "presentation layer. Dataflow and module read models remain source owners." ), layer="available", documentation_types=("admin", "user"), audience=("user", "operator", "module_admin", "product_owner"), links=( DocumentationLink( label="Reporting module boundary", href="govoplan-reporting/docs/REPORTING_BOUNDARY.md", kind="repository", ), DocumentationLink( label="SuperX capability assessment", href="govoplan-reporting/docs/SUPERX_CAPABILITY_ASSESSMENT.md", kind="repository", ), DocumentationLink( label="Reporting user guide", href="govoplan-reporting/docs/USER_GUIDE.md", kind="repository", ), DocumentationLink( label="Reporting administration guide", href="govoplan-reporting/docs/ADMIN_GUIDE.md", kind="repository", ), ), ), ), architecture=ModuleArchitectureDeclaration( layer="data_reporting_integration", kind="domain", maturity="vertical_slice", evidence=( ModuleMaturityEvidence( kind="test", reference="tests/test_reporting_service.py", summary="Proves revision pinning, safe semantic execution, quality gates, access, replay, exports, and import blocking.", ), ModuleMaturityEvidence( kind="documentation", reference="docs/REPORTING_BOUNDARY.md", summary="Defines governed analytical source, semantic, execution, and publication ownership.", ), ), known_limits=( "Dataflow is the first live dataset adapter; additional module read models use the provider-neutral contract.", "Direct browser export supports CSV and JSON; XLSX, PDF, Files, Mail, and DMS delivery require an optional publication provider.", "Import assessment produces blocking diagnostics but does not execute source SQL or automatically activate generated definitions.", "The initial chart provider emits a renderer-neutral model and accessible table; richer visual renderers remain replaceable adapters.", ), owned_concepts=( "analytical dataset binding", "semantic dimension hierarchy and measure", "report definition and saved view", "report execution and publication evidence", "report quality plan and import assessment", ), non_owned_concepts=( "raw datasource ingestion", "data transformation pipeline", "source module authorization", "template document rendering", "file or DMS storage", ), documentation=ModuleArchitectureDocumentation( operations=("docs/OPERATIONS.md",), recovery=("docs/OPERATIONS.md",), security=("docs/OPERATIONS.md",), ), ), ) def get_manifest() -> ModuleManifest: return manifest