Reporting Component Design
Table of Contents
Overview
Introduce ores.reporting, a new domain component responsible for defining and
executing reports within ORE Studio. Reports are structured, recurring analytical
outputs driven by a schedule and parameterised by domain data such as portfolios
and books.
The component models two distinct concepts: a report definition, which is the
persistent template describing what a report does and when it runs, and a report
instance, which is a single execution of that definition. Both have their own
state machines, and the lifecycle of an instance is driven by events fired from
ores.scheduler.
Goals
- Define a domain model for report definitions and report instances.
- Support multiple report types, starting with risk reports (backed by ORE).
- Use cron expressions (via
ores.scheduler) to drive recurring execution. - Track report instance lifecycle with a dedicated state machine.
- Lay the foundation for future grid-based execution via a yet-to-be-defined
ores.gridcomponent.
Non-Goals
- Implementing
ores.grid— that component does not yet exist and is out of scope for this design. - Rendering or presenting report output in the UI — covered by later stories.
- Persisting report output artefacts (files, PDFs) — deferred to a later phase.
- Real-time streaming of report progress — deferred to a later phase.
Architecture
ores.reporting sits in the Domain Layer, alongside ores.ore and
ores.refdata. It depends on:
ores.scheduler— for cron-based scheduling and trigger events.ores.ore— for access to ORE engine capabilities needed by risk reports.ores.eventing— to publish and consume lifecycle events.ores.database— for persistence of definitions and instances.
In the future it will also depend on:
ores.grid— for distributed/parallel execution of report computations.
[ ores.scheduler ] ──trigger──▶ [ ores.reporting ] ──runs──▶ [ ores.ore ]
▲ │ │
│ register/deregister │ publishes events │
└──────────────────────────────────┘ [ ores.grid ] (future)
Domain Model
report_type
An enumeration of the supported report categories:
enum class report_type { risk, ///< ORE-backed risk computation (VaR, PFE, etc.) grid ///< Distributed computation on ores.grid (future) };
report_definition_status
The status values that drive the report_definition state machine. Stored as a
reference data coding scheme in the database (not a check constraint) so that
the state machine can evolve over time without schema migrations.
| Code | Description |
|---|---|
draft |
Created but not yet scheduled. Editable in full. |
active |
Registered with the scheduler. Executes on its cron schedule. |
suspended |
Temporarily removed from the scheduler. No new instances. |
archived |
Terminal. Retained for history; no further execution. |
report_definition_status State Machine
┌─────────┐
create │ │
─────────▶│ draft │
│ │
└────┬────┘
│ activate
▼
┌─────────┐ suspend ┌───────────┐
│ │──────────────────────────▶│ │
│ active │ │ suspended │
│ │◀──────────────────────────│ │
└────┬────┘ reactivate └───────────┘
│ archive
▼
┌──────────┐
│ archived │ (terminal)
└──────────┘
Transitions:
- draft → active: User activates the definition. The cron job is registered
with
ores.scheduler. Thecron_job_idis stored on the definition. - active → suspended: User suspends the definition. The cron job is removed
from
ores.scheduler.cron_job_idis cleared. - suspended → active: User reactivates the definition. A new cron entry is
registered and
cron_job_idis updated. - active → archived / suspended → archived / draft → archived: User archives the definition. Terminal; no further execution.
report_definition
The persistent template for a report. Parameterised by report_type to carry
type-specific configuration.
struct report_definition final { boost::uuids::uuid id; utility::uuid::tenant_id tenant_id; boost::uuids::uuid party_id; std::string name; std::string description; report_type type; report_definition_status status; /// Validated cron expression that drives recurrence. scheduler::domain::cron_expression schedule_expression; /// pg_cron job ID. Present only when status == active. std::optional<std::int64_t> cron_job_id; /// Type-specific configuration (see below). std::variant<risk_report_config /*, grid_report_config (future) */> config; int version = 0; std::string modified_by; std::string performed_by; std::string change_reason_code; std::string change_commentary; std::chrono::system_clock::time_point recorded_at; };
risk_report_config
Configuration specific to risk reports. Declares the inputs passed to ORE.
struct risk_report_config final { /// Portfolios to include (by UUID). Empty means all portfolios. std::vector<boost::uuids::uuid> portfolio_ids; /// Books to include (by UUID). Empty means all books in selected portfolios. std::vector<boost::uuids::uuid> book_ids; /// ORE computation type (e.g. "NPV", "PFE", "VaR"). Maps to an ORE /// analytics configuration block. std::string analytics_type; /// Market data as-of convention: "live" | "eod" | ISO-8601 date string. std::string market_data_convention; };
report_instance_status
The status values for the report_instance state machine:
| Code | Description |
|---|---|
pending |
Created in response to a scheduler trigger; not yet running. |
running |
Execution is in progress. |
completed |
Execution finished successfully. (terminal) |
failed |
Execution finished with an error. (terminal) |
cancelled |
Cancelled by a user or by archiving the parent definition. (terminal) |
report_instance_status State Machine
scheduler trigger
│
▼
┌─────────┐ start execution ┌─────────┐
│ pending │────────────────────▶│ running │
└────┬────┘ └────┬────┘
│ │
│ cancel ┌───────┼───────┐
▼ │ │ │
┌──────────┐ success │ failure │ cancel
│cancelled │◀──────┐ ▼ ▼ ▼
└──────────┘ │ ┌──────────┐ ┌───────┐ ┌──────────┐
│ │completed │ │failed │ │cancelled │
│ └──────────┘ └───────┘ └──────────┘
│ (all terminal)
└─── (from pending, before start)
report_instance
A single execution of a report_definition. Created automatically when the
scheduler fires the trigger for an active definition.
struct report_instance final { boost::uuids::uuid id; utility::uuid::tenant_id tenant_id; boost::uuids::uuid party_id; /// The definition that produced this instance. boost::uuids::uuid definition_id; report_instance_status status; /// Scheduler trigger that caused this instance to be created. std::int64_t trigger_run_id; /// Execution log / error message. std::string output_message; std::chrono::system_clock::time_point created_at; std::chrono::system_clock::time_point started_at; std::optional<std::chrono::system_clock::time_point> completed_at; [[nodiscard]] std::optional<std::chrono::seconds> duration() const noexcept { if (!completed_at) return std::nullopt; return std::chrono::duration_cast<std::chrono::seconds>(*completed_at - started_at); } };
Execution Flow
Report Definition Activation
- User sets definition status to
active. - Reporting service calls
ores.schedulerto register a cron job whose payload identifies the report definition by UUID. - Scheduler returns a
cron_job_id; this is stored on the definition. - Definition is persisted with
status = active.
Scheduled Execution
ores.schedulerfires a trigger at the scheduled time (a pg_cron event reaches the event bus viaores.eventing).- The reporting service listener receives the event, identifies the
report_definitionfrom the payload, and creates areport_instancewithstatus = pending. - The instance transitions to
runningand execution begins:- Risk reports: The service constructs ORE input from
risk_report_configand delegates computation toores.ore. - Grid reports (future): The service submits work to
ores.grid.
- Risk reports: The service constructs ORE input from
- On completion the instance transitions to
completedorfailed. Output metadata and any error messages are stored on the instance.
Open Questions
- Report output storage: Where do the artefacts (NPV tables, PFE curves,
etc.) produced by risk reports live? A dedicated
report_outputtable? Object storage? This is deferred to a later design iteration. - Execution environment for risk reports: Does ORE run in-process, as a
subprocess, or via a future
ores.gridnode? In-process is simplest for the first iteration; subprocess isolation may be required later. - Concurrency: Should multiple instances of the same definition be allowed
to run simultaneously, or should a new trigger be skipped if an instance is
already
running? - ores.grid contract: The interface between
ores.reportingand the futureores.gridcomponent needs to be defined before grid-type reports can be designed in detail. - Multi-party visibility: Should report definitions be scoped to a party, or can they span multiple parties within a tenant?
- Cancellation: What is the mechanism for cancelling a running risk report (i.e., interrupting an in-progress ORE computation)?
Stories
Stories will be added here once the design is reviewed and approved. Expected phases:
- Foundation: SQL schema, domain types, JSON I/O, repository, basic CRUD via CLI and HTTP.
- Scheduler integration: Activate/suspend transitions wiring to
ores.scheduler. - Risk report execution: Integration with
ores.oreto run computations and populatereport_instancerecords. - UI: Qt and/or Wt surfaces for managing definitions and viewing instances.
- Grid execution (future): Integrate with
ores.gridonce that component exists.