Skip to content

Use Cases Reference

The application layer exposes use cases as the primary entry points for interacting with the kernel. Each use case represents a single user action.

Overview

Use Case Purpose Input Output
ValidateQueryUseCase Validate a query request QueryRequest ValidationResultDTO
ExecuteQueryUseCase Execute a validated plan QueryPlan QueryResultDTO
CreateStudyUseCase Register a new study CreateStudyRequest StudyId
AcknowledgeIssuesUseCase Accept warnings AcknowledgmentRequest AcknowledgmentResultDTO

ValidateQueryUseCase

Validates a query request against catalog metadata and domain rules.

Location

invariant.application.use_cases.validate_query

Dependencies

Port Purpose
CatalogStore Load data products and indicator definitions
IdGenerator Generate query ID

Input: QueryRequest

from invariant.application.dto.query_request import (
    QueryRequest,
    DataProductSelectionRequest,
    MetricRequest,
    FilterRequest,
)

request = QueryRequest(
    intent="TABLE",  # or "NUMBER", "CHART", "MAP"
    selections=[
        DataProductSelectionRequest(
            data_product_id="aa0e8400-e29b-41d4-a716-446655440001",
            dimensions=["geography_code", "sex"],
            metrics=[
                MetricRequest(variable="population", aggregation="SUM"),
            ],
            filters=[
                FilterRequest(variable="sex", op="EQ", values=["Female"]),
            ],
        )
    ],
    combine=None,  # For multi-selection queries
    presentation=None,  # Optional presentation hints
)

Output: ValidationResultDTO

@dataclass(frozen=True)
class ValidationResultDTO:
    query_id: str              # Generated ID for this query
    status: SeverityLevel      # "ALLOW", "WARN", "REQUIRE_ACK", "BLOCK"
    issues: tuple[IssueDTO, ...]
    disclosures: tuple[DisclosureDTO, ...]
    can_execute: bool          # True if status is not BLOCK
    requires_acknowledgment: bool  # True if status is REQUIRE_ACK

Example

from invariant.application.use_cases.validate_query import ValidateQueryUseCase

use_case = ValidateQueryUseCase(
    catalog_store=catalog_store,
    id_generator=id_generator,
)

result = use_case.execute(request)

if result.can_execute:
    print(f"Query {result.query_id} is valid")
else:
    print("Validation failed:")
    for issue in result.issues:
        print(f"  [{issue.code}] {issue.message}")

Error Conditions

Exception Cause
DataProductNotFoundError Data product ID doesn't exist
VariableNotFoundError Variable name not in data product

ExecuteQueryUseCase

Executes a validated query plan and returns results.

Location

invariant.application.use_cases.execute_query

Dependencies

Port Purpose
CatalogStore Load suppression policies
QueryEngine Execute the plan
SuppressionEngine Apply suppression (optional)
AuditLog Record execution (optional)

Input: QueryPlan

The ExecuteQueryUseCase takes a QueryPlan rather than a QueryRequest. Use the QueryPlanBuilder to convert:

from invariant.application.services.query_plan_builder import build_query_plan

# After validation
snapshot = catalog_store.get_catalog_snapshot(dp_ids)
plan = build_query_plan(request, validation_result.query_id, snapshot)

Output: QueryResultDTO

@dataclass
class QueryResultDTO:
    query_id: str
    columns: list[str]
    rows: list[tuple[object, ...]]
    row_count: int
    execution_time_ms: int
    disclosures: list[DisclosureDTO]

Example

from invariant.application.use_cases.execute_query import ExecuteQueryUseCase

execute_use_case = ExecuteQueryUseCase(
    catalog_store=catalog_store,
    query_engine=query_engine,
    suppression_engine=suppression_engine,  # optional
    audit_log=audit_log,  # optional
)

result = execute_use_case.execute(plan)

for row in result.rows:
    print(row)

for disclosure in result.disclosures:
    print(f"[{disclosure.disclosure_type}] {disclosure.text}")

CreateStudyUseCase

Registers a new study in the catalog.

Location

invariant.application.use_cases.create_study

Dependencies

Port Purpose
CatalogStore Save the study
IdGenerator Generate study ID
Clock Timestamp creation

Input: CreateStudyRequest

from invariant.application.dto.catalog_write import CreateStudyRequest

request = CreateStudyRequest(
    name="South Africa Census 2021",
    publisher="Statistics South Africa",
    description="Decennial population and housing census",
)

Output: StudyId

Returns the generated StudyId for the new study.

Example

from invariant.application.use_cases.create_study import CreateStudyUseCase

use_case = CreateStudyUseCase(
    catalog_store=catalog_store,
    id_generator=id_generator,
    clock=clock,
)

study_id = use_case.execute(request)
print(f"Created study: {study_id}")

AcknowledgeIssuesUseCase

Records user acknowledgment of validation warnings, allowing query execution to proceed.

Location

invariant.application.use_cases.acknowledge_issues

Dependencies

Port Purpose
AuditLog Record acknowledgment

Input: AcknowledgmentRequest

from invariant.application.dto.validation_dto import AcknowledgmentRequest

request = AcknowledgmentRequest(
    query_id="q-abc123",
    acknowledged_issue_codes=["PARTIAL_COMPARABILITY", "CROSSWALK_APPLIED"],
    user_id="user@example.com",  # optional
)

Output: AcknowledgmentResultDTO

@dataclass(frozen=True)
class AcknowledgmentResultDTO:
    query_id: str
    acknowledged: bool
    can_execute: bool
    message: str | None

Example

from invariant.application.use_cases.acknowledge_issues import AcknowledgeIssuesUseCase

ack_use_case = AcknowledgeIssuesUseCase(audit_log=audit_log)

# After validation returns REQUIRE_ACK
if validation_result.requires_acknowledgment:
    ack_request = AcknowledgmentRequest(
        query_id=validation_result.query_id,
        acknowledged_issue_codes=[i.code for i in validation_result.issues],
    )
    ack_result = ack_use_case.execute(ack_request)

    if ack_result.can_execute:
        # Now execute the query
        execute_use_case.execute(plan)

Typical Flow

Here's how use cases are typically combined:

# 1. Validate
validation = validate_use_case.execute(request)

# 2. Check result
if validation.status == "BLOCK":
    # Show errors, cannot proceed
    return

if validation.status == "REQUIRE_ACK":
    # Show warnings, get user confirmation
    if not user_confirms():
        return
    # Record acknowledgment
    ack_use_case.execute(AcknowledgmentRequest(...))

# 3. Build plan
plan = build_query_plan(request, validation.query_id, snapshot)

# 4. Execute
result = execute_use_case.execute(plan)

# 5. Return results with disclosures
return result

DTOs Summary

Query DTOs

DTO Purpose
QueryRequest High-level query from UI/API
DataProductSelectionRequest Selection from one data product
MetricRequest Variable + aggregation
FilterRequest Filter condition
CombineRequest How to combine multiple selections
PresentationRequest Display hints

Validation DTOs

DTO Purpose
ValidationResultDTO Validation outcome
IssueDTO Single validation issue
DisclosureDTO Provenance message
RemediationDTO Suggested fix
AcknowledgmentRequest User acknowledgment input
AcknowledgmentResultDTO Acknowledgment outcome

Catalog DTOs

DTO Purpose
CreateStudyRequest New study input
CreateDatasetRequest New dataset input
CreateDataProductRequest New data product input
CreateVariableRequest New variable input
CreateIndicatorDefinitionRequest New indicator definition