Files
govoplan-reporting/src/govoplan_reporting/backend/manifest.py
T

508 lines
18 KiB
Python

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