  Analytics Dependency Status: ❌ NOT FIXED                                                                                                                                                                                                                                                                                        ─

  The MetricsProvider pattern exists in contracts/metrics.py, but admin_stores.py still has hard imports:

  File: app/modules/tenancy/routes/api/admin_stores.py
  ┌──────┬────────────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────┐
  │ Line │                                 Import                                 │                   Used In                   │
  ├──────┼────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────┤
  │ 20   │ from app.modules.analytics.services.stats_service import stats_service │ get_store_statistics_endpoint() (line 110) │
  ├──────┼────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────┤
  │ 23   │ from app.modules.analytics.schemas import StoreStatsResponse          │ Return type (line 104)                      │
  └──────┴────────────────────────────────────────────────────────────────────────┴─────────────────────────────────────────────┘
  ---
  All Core → Optional Dependencies

  1. tenancy → marketplace

  File: app/modules/tenancy/models/__init__.py:22
  from app.modules.marketplace.models.marketplace_import_job import MarketplaceImportJob
  - Purpose: SQLAlchemy needs this to resolve User.marketplace_import_jobs and Store.marketplace_import_jobs relationships

  File: app/modules/tenancy/services/store_service.py
  ┌──────┬─────────────────────────────────────┬───────────────────────────────────────────────────────────────────────┐
  │ Line │               Import                │                          Functions Using It                           │
  ├──────┼─────────────────────────────────────┼───────────────────────────────────────────────────────────────────────┤
  │ 19   │ MarketplaceProductNotFoundException │ add_product_to_catalog() :463, _get_product_by_id_or_raise() :571     │
  ├──────┼─────────────────────────────────────┼───────────────────────────────────────────────────────────────────────┤
  │ 26   │ MarketplaceProduct model            │ _get_product_by_id_or_raise() :565-566, add_product_to_catalog() :466 │
  └──────┴─────────────────────────────────────┴───────────────────────────────────────────────────────────────────────┘
  ---
  2. tenancy → catalog

  File: app/modules/tenancy/services/store_service.py
  ┌──────┬───────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
  │ Line │            Import             │                               Functions Using It                               │
  ├──────┼───────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
  │ 18   │ ProductAlreadyExistsException │ add_product_to_catalog() :472                                                  │
  ├──────┼───────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
  │ 27   │ Product model                 │ add_product_to_catalog() :477, get_products() :533, _product_in_catalog() :579 │
  ├──────┼───────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
  │ 30   │ ProductCreate schema          │ add_product_to_catalog() parameter type :447                                   │
  └──────┴───────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
  ---
  3. billing → catalog

  File: app/modules/billing/services/subscription_service.py:48
  ┌───────────────────────┬─────────┬─────────────────────────────────────────────────────────────────────────┐
  │       Function        │  Lines  │                              What It Does                               │
  ├───────────────────────┼─────────┼─────────────────────────────────────────────────────────────────────────┤
  │ get_usage()           │ 371-376 │ db.query(func.count(Product.id)).filter(Product.store_id == store_id) │
  ├───────────────────────┼─────────┼─────────────────────────────────────────────────────────────────────────┤
  │ get_usage_summary()   │ 420-425 │ Same product count query                                                │
  ├───────────────────────┼─────────┼─────────────────────────────────────────────────────────────────────────┤
  │ can_add_product()     │ 514-519 │ Same product count query                                                │
  ├───────────────────────┼─────────┼─────────────────────────────────────────────────────────────────────────┤
  │ check_product_limit() │ 532-537 │ Same product count query                                                │
  └───────────────────────┴─────────┴─────────────────────────────────────────────────────────────────────────┘
  File: app/modules/billing/services/admin_subscription_service.py:30
  ┌───────────────────────────┬─────────┬─────────────────────────────────────────────────────────────────────────┐
  │         Function          │  Lines  │                              What It Does                               │
  ├───────────────────────────┼─────────┼─────────────────────────────────────────────────────────────────────────┤
  │ get_store_usage_counts() │ 212-217 │ db.query(func.count(Product.id)).filter(Product.store_id == store_id) │
  └───────────────────────────┴─────────┴─────────────────────────────────────────────────────────────────────────┘
  File: app/modules/billing/services/capacity_forecast_service.py:19
  ┌──────────────────────────┬──────┬───────────────────────────────────────────┐
  │         Function         │ Line │               What It Does                │
  ├──────────────────────────┼──────┼───────────────────────────────────────────┤
  │ capture_daily_snapshot() │ 91   │ db.query(func.count(Product.id)).scalar() │
  └──────────────────────────┴──────┴───────────────────────────────────────────┘
  ---
  Summary: What to Move Where?
  ┌──────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────┐
  │                Dependency                │                                          Recommendation                                           │
  ├──────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ admin_stores.py → analytics             │ Use the existing MetricsProvider pattern via stats_aggregator                                     │
  ├──────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ store_service.py → marketplace/catalog  │ Move add_product_to_catalog() and get_products() to catalog module - these are product operations │
  ├──────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ tenancy/models/__init__.py → marketplace │ Remove MarketplaceImportJob import, move relationships to marketplace module                      │
  ├──────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ billing/* → catalog (Product count)      │ Create ProductCountProvider protocol in contracts                                                 │
  └──────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────┘
  Would you like me to start working on any of these?


--------------------
Modular Architecture Analysis

 Executive Summary

 This analysis answers two questions:
 1. Are all modules self-contained and respecting architecture rules?
 2. Would the app still run if non-core modules are removed?

 Key Findings:
 - ✅ Route registration is fully auto-discovered (no hardcoded module routes)
 - ✅ Middleware has no module-specific dependencies
 - ✅ API routers use dynamic discovery pattern
 - ❌ CRITICAL: Core modules have hard dependencies on optional modules - the app would CRASH if optional modules are removed

 ---
 Part 1: Architecture Rule Compliance

 What's Working Well
 ┌─────────────────────────┬────────┬────────────────────────────────────────────────┐
 │         Aspect          │ Status │                    Details                     │
 ├─────────────────────────┼────────┼────────────────────────────────────────────────┤
 │ Module auto-discovery   │ ✅     │ All modules discovered via definition.py       │
 ├─────────────────────────┼────────┼────────────────────────────────────────────────┤
 │ Route auto-registration │ ✅     │ Admin, store, storefront routes fully dynamic │
 ├─────────────────────────┼────────┼────────────────────────────────────────────────┤
 │ Middleware stack        │ ✅     │ No hardcoded module dependencies               │
 ├─────────────────────────┼────────┼────────────────────────────────────────────────┤
 │ Static file mounting    │ ✅     │ Dynamic scanning of module directories         │
 ├─────────────────────────┼────────┼────────────────────────────────────────────────┤
 │ Module enablement       │ ✅     │ Controlled via PlatformModule.is_enabled       │
 └─────────────────────────┴────────┴────────────────────────────────────────────────┘
 Previous Issues Fixed (This Session)
 ┌───────────────────────────────────┬──────────┬─────────────────────────────────────────────────────────────┐
 │               Issue               │  Status  │                         Fix Applied                         │
 ├───────────────────────────────────┼──────────┼─────────────────────────────────────────────────────────────┤
 │ Module definition variable names  │ ✅ Fixed │ catalog, checkout, cart renamed to *_module                 │
 ├───────────────────────────────────┼──────────┼─────────────────────────────────────────────────────────────┤
 │ Router attachment functions       │ ✅ Fixed │ Added get_*_module_with_routers() functions                 │
 ├───────────────────────────────────┼──────────┼─────────────────────────────────────────────────────────────┤
 │ Billing exceptions location       │ ✅ Fixed │ Moved to exceptions.py with backwards-compat aliases        │
 ├───────────────────────────────────┼──────────┼─────────────────────────────────────────────────────────────┤
 │ StoreEmailSettingsService DI     │ ✅ Fixed │ Changed to db-as-parameter pattern                          │
 ├───────────────────────────────────┼──────────┼─────────────────────────────────────────────────────────────┤
 │ StoreDomainService exception bug │ ✅ Fixed │ Added proper re-raise for DomainVerificationFailedException │
 ├───────────────────────────────────┼──────────┼─────────────────────────────────────────────────────────────┤
 │ StoreTeamService exception bug   │ ✅ Fixed │ Fixed CannotRemoveOwnerException arguments                  │
 └───────────────────────────────────┴──────────┴─────────────────────────────────────────────────────────────┘
 ---
 Part 2: Critical Architecture Violations

 ❌ Core → Optional Module Dependencies

 The app would NOT run if optional modules are removed. Core modules have hard imports from optional modules:

 tenancy (core) → optional modules

 tenancy/models/__init__.py:22
   → from app.modules.marketplace.models import MarketplaceImportJob

 tenancy/services/admin_service.py:36,40
   → from app.modules.marketplace.models import MarketplaceImportJob
   → from app.modules.marketplace.schemas import MarketplaceImportJobResponse

 tenancy/services/store_service.py:18,19,26,27,30
   → from app.modules.catalog.exceptions import ProductAlreadyExistsException
   → from app.modules.marketplace.exceptions import MarketplaceProductNotFoundException
   → from app.modules.marketplace.models import MarketplaceProduct
   → from app.modules.catalog.models import Product
   → from app.modules.catalog.schemas import ProductCreate

 tenancy/services/store_team_service.py:34
   → from app.modules.billing.exceptions import TierLimitExceededException

 tenancy/routes/api/admin_stores.py:20,23,348,399
   → from app.modules.analytics.services.stats_service import stats_service
   → from app.modules.analytics.schemas import StoreStatsResponse
   → from app.modules.marketplace.services import letzshop_export_service

 tenancy/routes/api/admin_platform_users.py:18
   → from app.modules.analytics.services.stats_service import stats_service

 core (core) → analytics (optional)

 core/routes/api/admin_dashboard.py:14,16
   → from app.modules.analytics.services.stats_service import stats_service
   → from app.modules.analytics.schemas import StoreStatsResponse, ...

 core/routes/api/store_dashboard.py:17,20
   → from app.modules.analytics.services.stats_service import stats_service
   → from app.modules.analytics.schemas import StoreStatsResponse, ...

 cms (core) → optional modules

 cms/models/__init__.py:14, cms/models/media.py:9, cms/services/media_service.py:31
   → from app.modules.catalog.models import ProductMedia

 cms/routes/pages/platform.py:17
   → from app.modules.billing.models import TIER_LIMITS, TierCode

 cms/services/store_email_settings_service.py:33
   → from app.modules.billing.models import StoreSubscription, TierCode

 customers (core) → orders (optional)

 customers/services/customer_service.py:332,368
   → from app.modules.orders.models import Order  # (lazy import but still hard dep)

 ---
 Part 3: Module Categorization (UPDATED 2026-02-03)

 Core Modules (8) - Always enabled
 ┌───────────┬───────────────────────────────────────────────────┬──────────────────────────────┐
 │  Module   │                      Purpose                      │     Depends on Optional?     │
 ├───────────┼───────────────────────────────────────────────────┼──────────────────────────────┤
 │ contracts │ Cross-module Protocol interfaces                  │ No                           │
 ├───────────┼───────────────────────────────────────────────────┼──────────────────────────────┤
 │ core      │ Dashboard, settings, profile                      │ ❌ YES (analytics)           │
 ├───────────┼───────────────────────────────────────────────────┼──────────────────────────────┤
 │ cms       │ Content pages, media, themes                      │ ❌ YES (catalog)             │
 ├───────────┼───────────────────────────────────────────────────┼──────────────────────────────┤
 │ customers │ Customer database, profiles                       │ ❌ YES (orders)              │
 ├───────────┼───────────────────────────────────────────────────┼──────────────────────────────┤
 │ tenancy   │ Platforms, merchants, stores, users              │ ❌ YES (marketplace,catalog, │
 │           │                                                   │         analytics)           │
 ├───────────┼───────────────────────────────────────────────────┼──────────────────────────────┤
 │ billing   │ Subscriptions, tier limits, invoices              │ No (depends on payments,     │
 │           │                                                   │     which is also core)      │
 ├───────────┼───────────────────────────────────────────────────┼──────────────────────────────┤
 │ payments  │ Payment gateways (Stripe, PayPal)                 │ No                           │
 ├───────────┼───────────────────────────────────────────────────┼──────────────────────────────┤
 │ messaging │ Email, notifications, templates                   │ No                           │
 └───────────┴───────────────────────────────────────────────────┴──────────────────────────────┘

 Why billing, payments, messaging are core (decided 2026-02-03):
 - billing: Tier limits affect team size, product limits, email providers - pervasive
 - payments: Required by billing for subscription payment processing
 - messaging: Email required for registration, password reset, team invitations

 Optional Modules (8) - Can be disabled per platform
 ┌─────────────┬────────────────────────────┬───────────────────────────────────┐
 │   Module    │          Purpose           │           Dependencies            │
 ├─────────────┼────────────────────────────┼───────────────────────────────────┤
 │ analytics   │ Reports, dashboards        │ (standalone)                      │
 ├─────────────┼────────────────────────────┼───────────────────────────────────┤
 │ cart        │ Shopping cart              │ inventory                         │
 ├─────────────┼────────────────────────────┼───────────────────────────────────┤
 │ catalog     │ Product catalog            │ inventory                         │
 ├─────────────┼────────────────────────────┼───────────────────────────────────┤
 │ checkout    │ Order placement            │ cart, orders, customers           │
 ├─────────────┼────────────────────────────┼───────────────────────────────────┤
 │ inventory   │ Stock management           │ (standalone)                      │
 ├─────────────┼────────────────────────────┼───────────────────────────────────┤
 │ loyalty     │ Loyalty programs           │ customers                         │
 ├─────────────┼────────────────────────────┼───────────────────────────────────┤
 │ marketplace │ Letzshop integration       │ inventory                         │
 ├─────────────┼────────────────────────────┼───────────────────────────────────┤
 │ orders      │ Order management           │ (standalone)                      │
 └─────────────┴────────────────────────────┴───────────────────────────────────┘

 Internal Modules (2) - Admin-only
 ┌────────────┬─────────────────────────────────┐
 │   Module   │             Purpose             │
 ├────────────┼─────────────────────────────────┤
 │ dev-tools  │ Component library, icon browser │
 ├────────────┼─────────────────────────────────┤
 │ monitoring │ Logs, tasks, health checks      │
 └────────────┴─────────────────────────────────┘
 ---
 Part 4: Answer to User Questions

 Q1: Are all modules self-contained and respecting architecture rules?

 NO. While the route registration and middleware are properly decoupled, the core modules have hard import dependencies on optional modules. This violates the documented rule that "core
 modules cannot depend on optional modules."

 Specific violations:
 - tenancy depends on: marketplace, billing, catalog, analytics
 - core depends on: analytics
 - cms depends on: catalog, billing
 - customers depends on: orders

 Q2: Would the app still run if non-core modules are removed?

 NO. The app would crash immediately on import with ImportError or ModuleNotFoundError because:

 1. tenancy/models/__init__.py imports MarketplaceImportJob at module load time
 2. core/routes/api/admin_dashboard.py imports stats_service at module load time
 3. cms/models/__init__.py imports ProductMedia at module load time

 Even if using lazy imports (like in customers/services/customer_service.py), the code paths that use these imports would still fail at runtime.

 ---
 Part 5: Contracts Pattern Deep Dive (Option A)

 How the Existing Contracts Module Works

 The app/modules/contracts/ module already implements Protocol-based interfaces:

 app/modules/contracts/
 ├── __init__.py          # Exports all protocols
 ├── base.py              # ServiceProtocol, CRUDServiceProtocol
 ├── cms.py               # ContentServiceProtocol, MediaServiceProtocol
 └── definition.py        # Module definition (is_core=True)

 Key Pattern:
 # 1. Protocol definition in contracts (no implementation)
 @runtime_checkable
 class ContentServiceProtocol(Protocol):
     def get_page_for_store(self, db: Session, ...) -> object | None: ...

 # 2. Implementation in the module itself
 class ContentPageService:  # Implements the protocol implicitly (duck typing)
     def get_page_for_store(self, db: Session, ...) -> ContentPage | None:
         # actual implementation

 # 3. Usage in other modules (depends on protocol, not implementation)
 class OrderService:
     def __init__(self, content: ContentServiceProtocol | None = None):
         self._content = content

     @property
     def content(self) -> ContentServiceProtocol:
         if self._content is None:
             from app.modules.cms.services import content_page_service  # Lazy load
             self._content = content_page_service
         return self._content

 ---
 Part 6: Metrics Provider Pattern (Proposed)

 Design Goal

 Each module provides its own metrics/stats. The analytics module aggregates them but doesn't implement them.

 Step 1: Define MetricsProvider Protocol

 File: app/modules/contracts/metrics.py
 from typing import Protocol, runtime_checkable, TYPE_CHECKING
 from dataclasses import dataclass

 if TYPE_CHECKING:
     from sqlalchemy.orm import Session

 @dataclass
 class MetricValue:
     """Standard metric value with metadata."""
     key: str                    # e.g., "orders.total_count"
     value: int | float | str
     label: str                  # Human-readable label
     category: str              # Grouping category
     icon: str | None = None    # Optional UI icon

 @runtime_checkable
 class MetricsProviderProtocol(Protocol):
     """
     Protocol for modules that provide metrics/statistics.

     Each module implements this to expose its own metrics.
     The analytics module discovers and aggregates all providers.
     """

     @property
     def metrics_category(self) -> str:
         """Category name for this provider's metrics (e.g., 'orders', 'inventory')."""
         ...

     def get_store_metrics(
         self,
         db: "Session",
         store_id: int,
         date_from: "datetime | None" = None,
         date_to: "datetime | None" = None,
     ) -> list[MetricValue]:
         """Get metrics for a specific store."""
         ...

     def get_platform_metrics(
         self,
         db: "Session",
         platform_id: int,
         date_from: "datetime | None" = None,
         date_to: "datetime | None" = None,
     ) -> list[MetricValue]:
         """Get metrics aggregated for a platform."""
         ...

 Step 2: Each Module Implements the Protocol

 File: app/modules/orders/services/order_metrics.py
 from app.modules.contracts.metrics import MetricsProviderProtocol, MetricValue
 from sqlalchemy.orm import Session

 class OrderMetricsProvider:
     """Metrics provider for orders module."""

     @property
     def metrics_category(self) -> str:
         return "orders"

     def get_store_metrics(
         self, db: Session, store_id: int, date_from=None, date_to=None
     ) -> list[MetricValue]:
         from app.modules.orders.models import Order

         query = db.query(Order).filter(Order.store_id == store_id)
         if date_from:
             query = query.filter(Order.created_at >= date_from)
         if date_to:
             query = query.filter(Order.created_at <= date_to)

         total_orders = query.count()
         total_revenue = query.with_entities(func.sum(Order.total)).scalar() or 0

         return [
             MetricValue(
                 key="orders.total_count",
                 value=total_orders,
                 label="Total Orders",
                 category="orders",
                 icon="shopping-cart"
             ),
             MetricValue(
                 key="orders.total_revenue",
                 value=float(total_revenue),
                 label="Total Revenue",
                 category="orders",
                 icon="currency-euro"
             ),
         ]

     def get_platform_metrics(self, db: Session, platform_id: int, **kwargs):
         # Aggregate across all stores in platform
         ...

 # Singleton instance
 order_metrics_provider = OrderMetricsProvider()

 Step 3: Register Provider in Module Definition

 File: app/modules/orders/definition.py
 from app.modules.base import ModuleDefinition

 def _get_metrics_provider():
     """Lazy load metrics provider to avoid circular imports."""
     from app.modules.orders.services.order_metrics import order_metrics_provider
     return order_metrics_provider

 orders_module = ModuleDefinition(
     code="orders",
     name="Order Management",
     # ... existing config ...
     metrics_provider=_get_metrics_provider,  # NEW: Optional callable
 )

 Step 4: Analytics Module Discovers All Providers

 File: app/modules/analytics/services/stats_aggregator.py
 from app.modules.contracts.metrics import MetricsProviderProtocol, MetricValue
 from app.modules.registry import MODULES
 from sqlalchemy.orm import Session

 class StatsAggregatorService:
     """Aggregates metrics from all module providers."""

     def _get_enabled_providers(self, db: Session, platform_id: int) -> list[MetricsProviderProtocol]:
         """Get metrics providers from enabled modules."""
         from app.modules.service import module_service

         providers = []
         for module in MODULES.values():
             # Check if module is enabled for this platform
             if not module_service.is_module_enabled(db, platform_id, module.code):
                 continue

             # Check if module has a metrics provider
             if hasattr(module, 'metrics_provider') and module.metrics_provider:
                 provider = module.metrics_provider()  # Call the lazy getter
                 if provider:
                     providers.append(provider)

         return providers

     def get_store_stats(
         self, db: Session, store_id: int, platform_id: int, **kwargs
     ) -> dict[str, list[MetricValue]]:
         """Get all metrics for a store, grouped by category."""
         providers = self._get_enabled_providers(db, platform_id)

         result = {}
         for provider in providers:
             metrics = provider.get_store_metrics(db, store_id, **kwargs)
             result[provider.metrics_category] = metrics

         return result

 stats_aggregator = StatsAggregatorService()

 Step 5: Dashboard Uses Protocol, Not Implementation

 File: app/modules/core/routes/api/store_dashboard.py
 from fastapi import APIRouter, Depends
 from sqlalchemy.orm import Session

 from app.core.database import get_db
 from app.api.deps import get_current_store_api
 from models.schema.auth import UserContext

 router = APIRouter(prefix="/dashboard")

 @router.get("/stats")
 def get_dashboard_stats(
     current_user: UserContext = Depends(get_current_store_api),
     db: Session = Depends(get_db),
 ):
     """Get aggregated stats for store dashboard."""
     # Lazy import - only fails if analytics module removed AND this endpoint called
     from app.modules.analytics.services.stats_aggregator import stats_aggregator

     return stats_aggregator.get_store_stats(
         db=db,
         store_id=current_user.token_store_id,
         platform_id=current_user.platform_id,
     )

 ---
 Part 7: Creating a New Module with Metrics

 Checklist for New Module with Metrics Support

 1. Create module structure:
 app/modules/my_module/
 ├── __init__.py
 ├── definition.py         # Include metrics_provider
 ├── models/
 ├── services/
 │   ├── __init__.py
 │   └── my_metrics.py     # Implement MetricsProviderProtocol
 ├── routes/
 └── schemas/
 2. Implement metrics provider:
 # app/modules/my_module/services/my_metrics.py
 from app.modules.contracts.metrics import MetricsProviderProtocol, MetricValue

 class MyModuleMetricsProvider:
     @property
     def metrics_category(self) -> str:
         return "my_module"

     def get_store_metrics(self, db, store_id, **kwargs):
         return [
             MetricValue(key="my_module.count", value=42, label="My Count", category="my_module")
         ]

     def get_platform_metrics(self, db, platform_id, **kwargs):
         return []

 my_module_metrics = MyModuleMetricsProvider()
 3. Register in definition:
 # app/modules/my_module/definition.py
 def _get_metrics_provider():
     from app.modules.my_module.services.my_metrics import my_module_metrics
     return my_module_metrics

 my_module = ModuleDefinition(
     code="my_module",
     metrics_provider=_get_metrics_provider,
     # ...
 )
 4. Metrics appear automatically in dashboards when module is enabled

 ---
 Part 8: Benefits of This Architecture
 ┌───────────────────────────┬───────────────────────┬──────────────────────────────────┐
 │          Aspect           │        Before         │              After               │
 ├───────────────────────────┼───────────────────────┼──────────────────────────────────┤
 │ Core depends on analytics │ ❌ Hard import        │ ✅ Optional lazy import          │
 ├───────────────────────────┼───────────────────────┼──────────────────────────────────┤
 │ Adding new metrics        │ Edit analytics module │ Just add provider to your module │
 ├───────────────────────────┼───────────────────────┼──────────────────────────────────┤
 │ Module isolation          │ ❌ Coupled            │ ✅ Truly independent             │
 ├───────────────────────────┼───────────────────────┼──────────────────────────────────┤
 │ Testing                   │ Hard (need analytics) │ Easy (mock protocol)             │
 ├───────────────────────────┼───────────────────────┼──────────────────────────────────┤
 │ Disable analytics module  │ ❌ App crashes        │ ✅ Dashboard shows "no stats"    │
 └───────────────────────────┴───────────────────────┴──────────────────────────────────┘
 ---
 Part 9: Core Module Metrics & Dashboard Architecture

 Key Questions Answered

 Q1: How do core modules implement MetricsProviderProtocol?

 Core modules implement MetricsProviderProtocol the same way as optional modules. The difference is they're always enabled.

 tenancy module metrics:
 # app/modules/tenancy/services/tenancy_metrics.py
 class TenancyMetricsProvider:
     @property
     def metrics_category(self) -> str:
         return "tenancy"

     def get_store_metrics(self, db, store_id, **kwargs):
         # Store-specific: team members count, domains count
         from app.modules.tenancy.models import StoreUser, StoreDomain

         team_count = db.query(StoreUser).filter(
             StoreUser.store_id == store_id, StoreUser.is_active == True
         ).count()

         domains_count = db.query(StoreDomain).filter(
             StoreDomain.store_id == store_id
         ).count()

         return [
             MetricValue(key="tenancy.team_members", value=team_count,
                        label="Team Members", category="tenancy", icon="users"),
             MetricValue(key="tenancy.domains", value=domains_count,
                        label="Custom Domains", category="tenancy", icon="globe"),
         ]

     def get_platform_metrics(self, db, platform_id, **kwargs):
         # Platform-wide: total stores, total users, active stores
         from app.modules.tenancy.models import Store, User

         total_stores = db.query(Store).filter(
             Store.platform_id == platform_id
         ).count()

         active_stores = db.query(Store).filter(
             Store.platform_id == platform_id, Store.is_active == True
         ).count()

         return [
             MetricValue(key="tenancy.total_stores", value=total_stores,
                        label="Total Stores", category="tenancy", icon="store"),
             MetricValue(key="tenancy.active_stores", value=active_stores,
                        label="Active Stores", category="tenancy", icon="check-circle"),
         ]

 customers module metrics:
 # app/modules/customers/services/customer_metrics.py
 class CustomerMetricsProvider:
     @property
     def metrics_category(self) -> str:
         return "customers"

     def get_store_metrics(self, db, store_id, date_from=None, date_to=None):
         from app.modules.customers.models import Customer

         query = db.query(Customer).filter(Customer.store_id == store_id)
         total = query.count()

         # New customers in period
         if date_from:
             new_query = query.filter(Customer.created_at >= date_from)
             if date_to:
                 new_query = new_query.filter(Customer.created_at <= date_to)
             new_count = new_query.count()
         else:
             new_count = 0

         return [
             MetricValue(key="customers.total", value=total,
                        label="Total Customers", category="customers", icon="users"),
             MetricValue(key="customers.new", value=new_count,
                        label="New Customers", category="customers", icon="user-plus"),
         ]

     def get_platform_metrics(self, db, platform_id, **kwargs):
         # Platform admin sees aggregated customer stats
         ...

 Q2: Should analytics be part of core?

 Recommendation: Split analytics into two parts:
 ┌────────────────────────┬─────────────────────────────┬─────────────────────────────────────────────────────┐
 │       Component        │          Location           │                       Purpose                       │
 ├────────────────────────┼─────────────────────────────┼─────────────────────────────────────────────────────┤
 │ StatsAggregatorService │ core module                 │ Basic metric discovery & aggregation for dashboards │
 ├────────────────────────┼─────────────────────────────┼─────────────────────────────────────────────────────┤
 │ Advanced Analytics     │ analytics module (optional) │ Reports, charts, trends, exports, historical data   │
 └────────────────────────┴─────────────────────────────┴─────────────────────────────────────────────────────┘
 Why this split works:
 - Dashboards always work (aggregator in core)
 - Advanced analytics features can be disabled per platform
 - No circular dependencies
 - Clear separation of concerns

 Implementation:
 app/modules/core/services/
 ├── stats_aggregator.py    # Discovers & aggregates MetricsProviders (CORE)
 └── ...

 app/modules/analytics/services/
 ├── reports_service.py     # Advanced reports (OPTIONAL)
 ├── trends_service.py      # Historical trends (OPTIONAL)
 └── export_service.py      # Data exports (OPTIONAL)

 StatsAggregatorService in core:
 # app/modules/core/services/stats_aggregator.py
 from app.modules.contracts.metrics import MetricsProviderProtocol, MetricValue
 from app.modules.registry import MODULES

 class StatsAggregatorService:
     """
     Core service that discovers and aggregates metrics from all modules.
     Lives in core module so dashboards always work.
     """

     def _get_providers(self, db, platform_id) -> list[MetricsProviderProtocol]:
         """Get all metrics providers from enabled modules."""
         from app.modules.service import module_service

         providers = []
         for module in MODULES.values():
             # Always include core modules, check others
             if not module.is_core:
                 if not module_service.is_module_enabled(db, platform_id, module.code):
                     continue

             if hasattr(module, 'metrics_provider') and module.metrics_provider:
                 provider = module.metrics_provider()
                 if provider:
                     providers.append(provider)

         return providers

     def get_store_dashboard_stats(self, db, store_id, platform_id, **kwargs):
         """For store dashboard - single store metrics."""
         providers = self._get_providers(db, platform_id)
         return {p.metrics_category: p.get_store_metrics(db, store_id, **kwargs)
                 for p in providers}

     def get_admin_dashboard_stats(self, db, platform_id, **kwargs):
         """For admin dashboard - platform-wide metrics."""
         providers = self._get_providers(db, platform_id)
         return {p.metrics_category: p.get_platform_metrics(db, platform_id, **kwargs)
                 for p in providers}

 stats_aggregator = StatsAggregatorService()

 Q3: Should this be used by both admin and store dashboards?

 YES. The protocol has two methods for this exact purpose:
 ┌───────────────────────────────────┬──────────────────┬───────────────────────────────┐
 │              Method               │     Used By      │          Data Scope           │
 ├───────────────────────────────────┼──────────────────┼───────────────────────────────┤
 │ get_store_metrics(store_id)     │ Store Dashboard │ Single store's data          │
 ├───────────────────────────────────┼──────────────────┼───────────────────────────────┤
 │ get_platform_metrics(platform_id) │ Admin Dashboard  │ Aggregated across all stores │
 └───────────────────────────────────┴──────────────────┴───────────────────────────────┘
 Store Dashboard:
 # app/modules/core/routes/api/store_dashboard.py
 @router.get("/stats")
 def get_store_stats(
     current_user: UserContext = Depends(get_current_store_api),
     db: Session = Depends(get_db),
 ):
     from app.modules.core.services.stats_aggregator import stats_aggregator

     return stats_aggregator.get_store_dashboard_stats(
         db=db,
         store_id=current_user.token_store_id,
         platform_id=current_user.platform_id,
     )

 Admin Dashboard:
 # app/modules/core/routes/api/admin_dashboard.py
 @router.get("/stats")
 def get_platform_stats(
     current_user: UserContext = Depends(get_current_platform_admin_api),
     db: Session = Depends(get_db),
 ):
     from app.modules.core.services.stats_aggregator import stats_aggregator

     return stats_aggregator.get_admin_dashboard_stats(
         db=db,
         platform_id=current_user.platform_id,
     )

 Module Categorization Update (UPDATED 2026-02-03)

 With this architecture, module categorization becomes:
 ┌───────────┬──────────┬─────────────────┬───────────────────────────────────────┐
 │  Module   │   Type   │  Has Metrics?   │           Metrics Examples            │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ core      │ Core     │ ✅ + Aggregator │ System stats, aggregator service      │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ tenancy   │ Core     │ ✅              │ Stores, users, team members, domains │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ customers │ Core     │ ✅              │ Customer counts, new customers        │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ cms       │ Core     │ ✅              │ Pages, media items                    │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ contracts │ Core     │ ❌              │ Just protocols, no data               │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ billing   │ Core     │ ✅              │ Subscription stats, tier usage        │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ payments  │ Core     │ ✅              │ Transaction stats                     │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ messaging │ Core     │ ✅              │ Email stats, notification counts      │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ orders    │ Optional │ ✅              │ Order counts, revenue                 │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ inventory │ Optional │ ✅              │ Stock levels, low stock               │
 ├───────────┼──────────┼─────────────────┼───────────────────────────────────────┤
 │ analytics │ Optional │ ❌              │ Advanced reports (uses aggregator)    │
 └───────────┴──────────┴─────────────────┴───────────────────────────────────────┘
 ---
 Part 10: Implementation Steps

 Phase 1: Create Metrics Contract (contracts module)

 1. Create app/modules/contracts/metrics.py with:
   - MetricValue dataclass
   - MetricsProviderProtocol
 2. Add metrics_provider: Callable | None field to ModuleDefinition base class
 3. Export from contracts/__init__.py

 Phase 2: Create Stats Aggregator (core module)

 1. Create app/modules/core/services/stats_aggregator.py
   - StatsAggregatorService that discovers all metrics providers
   - get_store_dashboard_stats() method
   - get_admin_dashboard_stats() method
 2. Register in core module exports

 Phase 3: Add Metrics Providers to Core Modules

 1. tenancy → tenancy_metrics.py
   - Store count, user count, team members, domains
 2. customers → customer_metrics.py
   - Customer count, new customers, active customers
 3. cms → cms_metrics.py
   - Page count, media count

 Phase 4: Add Metrics Providers to Optional Modules

 Priority order:
 1. orders → order_metrics.py - order counts, revenue
 2. inventory → inventory_metrics.py - stock levels, low stock alerts
 3. billing → billing_metrics.py - subscription stats
 4. marketplace → marketplace_metrics.py - import stats

 Phase 5: Update Dashboard Routes

 1. Update core/routes/api/store_dashboard.py to use aggregator
 2. Update core/routes/api/admin_dashboard.py to use aggregator
 3. Remove direct imports from analytics module
 4. Handle graceful degradation when no metrics available

 Phase 6: Refactor Analytics Module (Optional Features)

 1. Keep advanced features in analytics module:
   - reports_service.py - detailed reports
   - trends_service.py - historical trends
   - export_service.py - data exports
 2. Analytics module can use stats_aggregator for base data
 3. Remove duplicated metric calculations from analytics

 Phase 7: Update Tests

 1. Add unit tests for StatsAggregatorService
 2. Add unit tests for each metrics provider
 3. Update dashboard API tests
 4. Test with analytics module disabled

 ---
 Verification Commands

 # Check module discovery
 python -c "from app.modules.registry import MODULES; print(list(MODULES.keys()))"

 # Test metrics provider discovery
 python -c "
 from app.modules.registry import MODULES
 for m in MODULES.values():
     if hasattr(m, 'metrics_provider') and m.metrics_provider:
         print(f'{m.code}: has metrics provider')
 "

 # Test stats aggregator (after implementation)
 python -c "
 from app.modules.core.services.stats_aggregator import stats_aggregator
 from app.core.database import SessionLocal
 db = SessionLocal()
 try:
     # Test with platform_id=1
     stats = stats_aggregator.get_admin_dashboard_stats(db, platform_id=1)
     print('Categories:', list(stats.keys()))
 finally:
     db.close()
 "

 # Run metrics provider tests
 pytest tests/unit/services/test_*_metrics.py -v

 # Verify no core→optional imports remain
 grep -r "from app.modules.analytics" app/modules/core/ app/modules/tenancy/ || echo "Clean!"

 ---
 Summary: What Changes

 Before (Current State)

 core/routes/admin_dashboard.py
   └── imports stats_service from analytics module ❌
       └── analytics calculates ALL metrics internally

 After (New Architecture)

 core/routes/admin_dashboard.py
   └── imports stats_aggregator from core module ✅
       └── discovers MetricsProviders from ALL modules
           ├── tenancy_metrics (core)
           ├── customer_metrics (core)
           ├── order_metrics (optional, if enabled)
           ├── inventory_metrics (optional, if enabled)
           └── ...

 analytics module (optional)
   └── Advanced features: reports, trends, exports
       └── can use stats_aggregator for base data

 Key Benefits

 1. Dashboards always work - aggregator is in core
 2. Each module owns its metrics - no cross-module coupling
 3. Optional modules truly optional - can be removed without breaking app
 4. Easy to add new metrics - just implement protocol in your module
 5. Both dashboards supported - store (per-store) and admin (platform-wide)
