Skip to content
bitzorcas
中EN

Concept

Reporting Data Marts

A source-aligned guide to Reporting QUERY endpoints, asynchronous export, permission and DataScope gates, ticket projection, owner-local Marts, and the remaining production-data and recovery gaps.

Last updated

Reporting is a read-side projection module, not a general-purpose BI engine. It owns two denormalized Mart tables: Rpt_TicketSummary stores the current ticket snapshot, while Rpt_AuditActivityDaily stores daily counts by tenant, date, user, and action. Its public business surface consists of two RFC 10008 QUERY endpoints and one asynchronous ticket-summary export submission endpoint.

1. Implemented scope

CapabilityCurrent implementation
Ticket summary queryQUERY /api/reports/tickets/summary; POST fallback at /api/reports/tickets/summary/_query
Audit activity queryQUERY /api/reports/activities/daily; POST fallback at /api/reports/activities/daily/_query
Ticket summary exportPOST /api/reports/tickets/summary/export; csv and excel only; always asynchronous
Permissions.read for queries and .export for export submission
FeatureThe runtime maps resource module reporting to platform.reporting
Data scopeEach handler evaluates authorization again and accepts only DataScope.Tenant
Ticket projectionSubscribes to opened, assigned, started, resolved, closed, and reopened
StorageTwo owner-local Mart rows accessed through IEntitySet<T> and Query Shape
Host without persistenceAn unavailable IReportingMartStore fails closed instead of returning fabricated empty data

The module does not contain a report designer, chart/dashboard engine, cross-tenant analytics, an audit-daily aggregation job, projection checkpoint, inbox, versioned replay, reconciliation, shadow rebuild, freshness API, or Mart lifecycle cleanup.

2. Runtime flow

missing

Ticket aggregate
six IIntegrationEvent types

Generated IntegrationTopic registry
topic + business fields

Transactional CAP Outbox
eventId + eventType + business fields

TicketReportingEventConsumer

Rpt_TicketSummary

Audit daily aggregation writer
not implemented

Rpt_AuditActivityDaily

QUERY /api/reports/...

Asynchronous Export Builder

platform.reporting + exact permission
+ DataScope.Tenant

Business handlers do not manually publish Ticket*IntegrationEvent DTOs. Ticket domain events also implement IIntegrationEvent and carry [IntegrationTopic]. The source generator builds the topic and scalar-payload mapping, and the command transaction writes that message to the CAP Outbox before commit. Reporting’s Ticket*IntegrationEvent records are consumer-binding views of that wire shape.

3. Query protocol

List and paginated reads do not have GET aliases. Clients should send a JSON-body QUERY request. When a proxy or client tool cannot send QUERY, use the POST fallback with the same body.

ReportRequest fieldsResponse
Ticket summarystatus?, from?, to?, pageIndex=1, pageSize=20PagedResult<TicketSummaryRow>
Audit dailyrequired from and to, plus pageIndex=1, pageSize=20PagedResult<AuditActivityDailyRow>
Query Closed ticket summaries
QUERY /api/reports/tickets/summary HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9
Content-Type: application/json
Accept: application/json
{
"status": "Closed",
"from": "2026-07-01T00:00:00Z",
"to": "2026-07-31T23:59:59Z",
"pageIndex": 1,
"pageSize": 50
}

Only method and path change for the compatibility request:

Use the POST fallback when QUERY is unavailable
POST /api/reports/tickets/summary/_query HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9
Content-Type: application/json
{"status":"Closed","from":"2026-07-01T00:00:00Z","to":"2026-07-31T23:59:59Z","pageIndex":1,"pageSize":50}

Request rules are explicit:

  • pageIndex must be at least 1; pageSize must be between 1 and 1000;
  • status is trimmed, may contain at most 64 characters, and may not contain control characters; it is not currently resolved against TicketStatus;
  • from must not be later than to, and the span may not exceed 366 days;
  • both boundaries are inclusive; ticket queries filter OpenedAt, while audit queries filter ActivityDate;
  • ticket ranges use DateTimeOffset but are not forced to UTC; audit ranges use DateTime without a stricter Kind contract.

4. Permission, Feature, and data scope

Entry pointResource and actionDerived permission
Ticket summary queryreporting/ticket-summary + Readreporting.ticket-summary.read
Audit daily queryreporting/activity + Readreporting.activity.read
Ticket summary exportreporting/ticket-summary + Exportreporting.ticket-summary.export

The shared Feature evaluator maps reporting to the central platform.reporting entitlement and returns Deny when it is disabled. Reporting also contributes an owner catalog definition named reporting.mart, but the query authorization chain does not evaluate that code. Deployment and plan configuration must therefore treat platform.reporting as the current runtime fact. The duplicate feature concepts remain a source-level consistency issue.

Query and export handlers require an authenticated user or delegated user, a positive stable UserId, and a valid TenantId. They then call IAuthorizationDecisionService; execution continues only when IsAllowed is true and DataScope == Tenant. Own, Department, Office, and other narrower scopes fail closed instead of becoming row predicates.

This avoids loading a tenant-wide data set and filtering it in memory, but it also means only tenant-scope users can use these reports. Public rows still expose Subject, RequesterId, AssigneeId, or per-user activity counts. No field masker is wired today.

5. Asynchronous ticket-summary export

Submit a ticket-summary export
POST /api/reports/tickets/summary/export HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9
Content-Type: application/json
{
"status": "Closed",
"from": "2026-07-01T00:00:00Z",
"to": "2026-07-31T23:59:59Z",
"format": "csv",
"idempotencyKey": "reporting-202607-closed-v1"
}

Success returns { "jobId": "..." }. The implementation fixes the following boundaries:

  • the trimmed idempotency key must contain 8–128 non-control characters; filters and format are part of the request fingerprint;
  • the Builder Key is fixed as reporting.ticket-summary, and the server owns the columns;
  • only ExportScope.All is accepted; client-selected CheckedIds are rejected;
  • ForceAsync=true; raw Mart reads default to 5000 rows and cap at 100000, with a 1000-row authorization scan batch;
  • the builder calls the Tickets owner’s ITicketResourceAuthorizationReader for every Ticket; a revoked row is omitted from later batches;
  • retry re-evaluates .export and tenant DataScope;
  • task display fields expose only normalized status/from/to, not the original parameter JSON.

History, status, download, cancellation, and retry are provided by the platform Export Center under /api/export/..., not by Reporting-specific routes.

6. Ticket projection

TopicProjection change
ticket.openedCreate New with requester, subject, priority, and OpenedAt
ticket.assignedSet AssigneeId and Assigned
ticket.startedSet InProgress
ticket.resolvedSet Resolved
ticket.closedSet Closed and ClosedAt
ticket.reopenedSet Reopened and clear ClosedAt

The consumer skips an immediate duplicate of the last EventId and skips non-opened events whose OccurredAt is earlier than LastUpdatedAt. A missing opened predecessor, Mart read/write failure, or other processing exception is rethrown so CAP can retry. An integration test directly constructs all six consumer DTOs and verifies the lifecycle, direct duplicate, and stale-close cases.

Three important boundaries remain:

  1. Ticket.Open calls Raise(new TicketOpened(ticket.Id, ...)) while Id is still "0"; the repository assigns the final ID later. Opened cannot join subsequent events by TicketId.
  2. The Mart stores only the most recent EventId. It handles A,A but cannot recognize A,B,A, and events carry no aggregate version or sequence.
  3. Upsert is read-then-write without compare-and-set; equal timestamps and concurrent delivery can still be resolved by commit order.

See Ticket projection and event ordering.

7. Audit daily status

Rpt_AuditActivityDaily and UpsertAuditActivityDailyAsync exist, but production source has no consumer, JobHost executor, SQL aggregation service, checkpoint, or backfill command. The handler test returns a manually configured substitute row and proves mapping only.

ActivityDate is a full DateTime, and the store does not normalize it to UTC midnight. Upsert replaces the complete ActivityCount; it is not an atomic increment. Different times or DateTime kinds can therefore split one business day into multiple unique rows. See Audit activity daily aggregation.

8. Mart query and consistency semantics

Both standard lists use Query Shape and add a trusted TenantId predicate:

  • tickets sort by (OpenedAt desc, TicketId desc);
  • audit daily sorts by (ActivityDate desc, UserId desc, ActionType desc);
  • From and To are inclusive;
  • public DTOs omit Mart LastEventId and LastUpdatedAt, so clients cannot determine a watermark;
  • both Upserts use check-then-insert/update with no atomic database Upsert or version condition.

See Mart models, paging, and data scope.

9. Test evidence and production gaps

Existing evidence covers query-handler rules, Read actions, tenant DataScope, Feature mapping, export submission and row-level owner rechecks, the six-event consumer sequence, ORM-neutral Infrastructure, Mart metadata, and the API Shell unavailable adapter.

Still missing are a real Ticket-create-to-Outbox-to-CAP binding test, a final opened-ID guard, a dual-ORM ReportingMartStore behavior suite, version/inbox/gap semantics, concurrent Upsert tests, the audit-daily producer, real HTTP authorization coverage, projection watermarks, reconciliation, rebuild drills, and production-scale query plans.

See Testing, observability, rebuild, and GA for the acceptance checklist.

10. Source map

TopicSource entry
Queriessrc/Platform/Reporting/...Application/Queries
Export submission and builder...Application/Commands/ExportTicketSummary, TicketSummaryExportBuilder.cs
Request and DataScope rules...Application/ReportingApplicationRules.cs
Permissions and owner FeatureReportingConstants.cs, ReportingFeatures.cs
Runtime Feature mappingsrc/Framework/BitzOrcas.Application/Authorization/Feature/FeaturePolicyEvaluator.cs
Ticket consumer...Reporting.Infrastructure/TicketReportingEventConsumer.cs
Mart Store and Query Shape...Reporting.Infrastructure/ReportingMartStore.cs
Ticket publisher eventssrc/Platform/Tickets/...Contracts/Tickets/Events/Ticket*.cs
Audit daily row...Reporting.Infrastructure/Persistence/RptAuditActivityDailyRecord.cs

Back to platform modules · Tickets · Auditing · Authorization

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%