Architecture¶
The Results SDK is a layered system with clear separation of concerns. This page explains each layer, the design patterns in use, and how a typical request flows from user code to the backend and back.
Package Structure¶
owi.metadatabase.results
├── io.py # ResultsAPI — HTTP client layer
├── models.py # Pydantic data models
├── endpoints.py # Route definitions
├── protocols.py # Runtime-checkable protocol contracts
├── registry.py # Analysis registration
├── serializers.py # Django ↔ SDK translation
├── utils.py # Helpers (token loading, logging)
├── analyses/ # Concrete analysis implementations
│ ├── base.py # BaseAnalysis mixin (template method)
│ ├── lifetime_design_frequencies.py
│ ├── lifetime_design_verification.py
│ ├── wind_speed_histogram.py
│ └── ceit.py # CEIT sensor data handling
├── plotting/ # Visualization layer
│ ├── strategies.py # Generic single-analysis plot strategies
│ ├── definitions.py # Cross-analysis plot definition types
│ ├── registry.py # Registered cross-analysis plot definitions
│ ├── theme.py # Chart styling
│ ├── frequency.py # Frequency-specific plotters
│ ├── verification.py # Verification time-series and water-depth trend plotters
│ ├── frequency_verification.py # Cross-analysis fleetwide plot
│ ├── ceit.py # CEIT-specific plotters
│ └── response.py # Response builders (notebook, HTML, JSON)
└── services/ # High-level service facade
├── core.py # ResultsService + ApiResultsRepository
└── ceit.py # CeitResultsService
Design Patterns¶
Registry Pattern¶
Concrete analyses register themselves with @register_analysis. The
AnalysisRegistry holds a name → class mapping used by ResultsService
for dispatch:
from owi.metadatabase.results.registry import register_analysis
@register_analysis
class LifetimeDesignFrequencies(BaseAnalysis):
analysis_name = "LifetimeDesignFrequencies"
...
Template Method¶
BaseAnalysis defines a skeleton workflow with default implementations:
validate_inputs() → compute() → to_results() → from_results() →
plot(). Concrete analyses override specific steps.
Strategy Pattern¶
Generic single-analysis rendering is delegated to
PlotStrategyProtocol implementations (e.g. HistogramPlotStrategy,
TimeSeriesPlotStrategy). The analysis chooses a strategy by name, and
get_plot_strategy() resolves it. Analyses with plot-specific behavior
can still bypass the generic strategies and call dedicated plotting
modules directly.
Registered Plot Definitions¶
Cross-analysis plots are registered separately from analyses through the
plotting registry. A PlotDefinition declares the supported analysis
names, the named source queries required by the plot, and the renderer
used after those sources are fetched and normalized. ResultsService
resolves these definitions by plot_type before it falls back to the
ordinary single-analysis plotting path.
For example, the lifetime design frequency/verification workflow
registers fleetwide, asset-level, and delta histogram plot definitions
that all draw from the named frequency and verification sources.
Adapter Pattern¶
ApiResultsRepository wraps ResultsAPI to satisfy
ResultsRepositoryProtocol, decoupling the service layer from HTTP
transport.
Facade Pattern¶
ResultsService provides a single entry point combining the repository,
registry, and serialization logic. Users call get_results() or
plot_results() without managing the underlying components.
Protocol-driven Contracts¶
The SDK defines runtime-checkable protocols (ResultProtocol,
PlotStrategyProtocol, AnalysisProtocol, PlotDefinitionProtocol,
ResultsRepositoryProtocol, QueryServiceProtocol) to enforce
structural typing without requiring inheritance.
Request Lifecycle¶
sequenceDiagram
participant User
participant Service as ResultsService
participant Repo as ApiResultsRepository
participant API as ResultsAPI
participant Backend as REST API
User->>Service: get_results("LifetimeDesignFrequencies", filters)
Service->>Repo: list_results(query)
Repo->>API: list_results(**filters)
API->>Backend: GET /api/v1/results/routes/result/?...
Backend-->>API: JSON response
API-->>Repo: DataFrame
Repo-->>Service: DataFrame
Service->>Service: deserialize_result_series()
Service->>Service: analysis.from_results()
Service-->>User: normalized DataFrame
Data Flow Summary¶
- User calls
ResultsService.get_results()with an analysis name and filters. - Service looks up the analysis class in the registry.
- Repository translates the query into HTTP parameters.
- API sends an authenticated GET request and converts the JSON response to a DataFrame.
- Service deserializes the raw rows into typed
ResultSeriesobjects. - Analysis reconstructs the normalized computation frame from the result series.
- User receives a clean pandas DataFrame.
For cross-analysis plots, the service follows the same repository and deserialization flow, but fans out across multiple named sources before passing the normalized source frames to the registered plot definition.
Return Value Conventions¶
All API methods return a dict with consistent keys:
| Key | Type | Description |
|---|---|---|
data |
DataFrame |
Tabular result data. |
exists |
bool |
True when at least one row was returned. |
id |
int (optional) |
The primary key of a created or fetched row. |