Files
orion/app/modules/contracts/metrics.py
Samir Boulahtit f20266167d
Some checks failed
CI / ruff (push) Failing after 7s
CI / pytest (push) Failing after 1s
CI / architecture (push) Failing after 9s
CI / dependency-scanning (push) Successful in 27s
CI / audit (push) Successful in 8s
CI / docs (push) Has been skipped
fix(lint): auto-fix ruff violations and tune lint rules
- Auto-fixed 4,496 lint issues (import sorting, modern syntax, etc.)
- Added ignore rules for patterns intentional in this codebase:
  E402 (late imports), E712 (SQLAlchemy filters), B904 (raise from),
  SIM108/SIM105/SIM117 (readability preferences)
- Added per-file ignores for tests and scripts
- Excluded broken scripts/rename_terminology.py (has curly quotes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:10:42 +01:00

216 lines
6.7 KiB
Python

# app/modules/contracts/metrics.py
"""
Metrics provider protocol for cross-module statistics aggregation.
This module defines the protocol that modules implement to expose their own metrics.
The core module's StatsAggregator discovers and aggregates all providers.
Benefits:
- Each module owns its metrics (no cross-module coupling)
- Dashboards always work (aggregator is in core)
- Optional modules are truly optional (can be removed without breaking app)
- Easy to add new metrics (just implement protocol in your module)
Usage:
# 1. Implement the protocol in your module
class OrderMetricsProvider:
@property
def metrics_category(self) -> str:
return "orders"
def get_store_metrics(self, db, store_id, **kwargs) -> list[MetricValue]:
return [
MetricValue(key="orders.total", value=42, label="Total Orders", category="orders")
]
# 2. Register in module definition
def _get_metrics_provider():
from app.modules.orders.services.order_metrics import order_metrics_provider
return order_metrics_provider
orders_module = ModuleDefinition(
code="orders",
metrics_provider=_get_metrics_provider,
# ...
)
# 3. Metrics appear automatically in dashboards when module is enabled
"""
from dataclasses import dataclass
from datetime import datetime
from typing import TYPE_CHECKING, Protocol, runtime_checkable
if TYPE_CHECKING:
from sqlalchemy.orm import Session
@dataclass
class MetricValue:
"""
Standard metric value with metadata.
This is the unit of data returned by metrics providers.
It contains both the value and metadata for display.
Attributes:
key: Unique identifier for this metric (e.g., "orders.total_count")
Format: "{category}.{metric_name}" for consistency
value: The actual metric value (int, float, or str)
label: Human-readable label for display (e.g., "Total Orders")
category: Grouping category (should match metrics_category of provider)
icon: Optional Lucide icon name for UI display (e.g., "shopping-cart")
description: Optional longer description of what this metric represents
unit: Optional unit suffix (e.g., "EUR", "%", "items")
trend: Optional trend indicator ("up", "down", "stable")
trend_value: Optional numeric trend value (e.g., percentage change)
Example:
MetricValue(
key="orders.total_count",
value=1234,
label="Total Orders",
category="orders",
icon="shopping-cart",
unit="orders",
)
"""
key: str
value: int | float | str
label: str
category: str
icon: str | None = None
description: str | None = None
unit: str | None = None
trend: str | None = None # "up", "down", "stable"
trend_value: float | None = None
@dataclass
class MetricsContext:
"""
Context for metrics collection.
Provides filtering and scoping options for metrics providers.
Attributes:
date_from: Start of date range filter
date_to: End of date range filter
include_trends: Whether to calculate trends (may be expensive)
period: Time period for analytics (e.g., "7d", "30d", "90d", "1y")
"""
date_from: datetime | None = None
date_to: datetime | None = None
include_trends: bool = False
period: str = "30d"
@runtime_checkable
class MetricsProviderProtocol(Protocol):
"""
Protocol for modules that provide metrics/statistics.
Each module implements this to expose its own metrics.
The core module's StatsAggregator discovers and aggregates all providers.
Implementation Notes:
- Providers should be stateless (all data via db session)
- Return empty list if no metrics available (don't raise)
- Use consistent key format: "{category}.{metric_name}"
- Include icon hints for UI rendering
- Be mindful of query performance (use efficient aggregations)
Example Implementation:
class OrderMetricsProvider:
@property
def metrics_category(self) -> str:
return "orders"
def get_store_metrics(
self, db: Session, store_id: int, context: MetricsContext | None = None
) -> list[MetricValue]:
from app.modules.orders.models import Order
total = db.query(Order).filter(Order.store_id == store_id).count()
return [
MetricValue(
key="orders.total",
value=total,
label="Total Orders",
category="orders",
icon="shopping-cart"
)
]
def get_platform_metrics(
self, db: Session, platform_id: int, context: MetricsContext | None = None
) -> list[MetricValue]:
# Aggregate across all stores in platform
...
"""
@property
def metrics_category(self) -> str:
"""
Category name for this provider's metrics.
Should be a short, lowercase identifier matching the module's domain.
Examples: "orders", "inventory", "customers", "billing"
Returns:
Category string used for grouping metrics
"""
...
def get_store_metrics(
self,
db: "Session",
store_id: int,
context: MetricsContext | None = None,
) -> list[MetricValue]:
"""
Get metrics for a specific store.
Called by the store dashboard to display store-scoped statistics.
Should only include data belonging to the specified store.
Args:
db: Database session for queries
store_id: ID of the store to get metrics for
context: Optional filtering/scoping context
Returns:
List of MetricValue objects for this store
"""
...
def get_platform_metrics(
self,
db: "Session",
platform_id: int,
context: MetricsContext | None = None,
) -> list[MetricValue]:
"""
Get metrics aggregated for a platform.
Called by the admin dashboard to display platform-wide statistics.
Should aggregate data across all stores in the platform.
Args:
db: Database session for queries
platform_id: ID of the platform to get metrics for
context: Optional filtering/scoping context
Returns:
List of MetricValue objects aggregated for the platform
"""
...
__all__ = [
"MetricValue",
"MetricsContext",
"MetricsProviderProtocol",
]