╔═════════════════════════════════════════════════════════════════════════════════════╗
║                   GOODGO ANALYTICS MODULE - ARCHITECTURE OVERVIEW                  ║
╚═════════════════════════════════════════════════════════════════════════════════════╝

┌─────────────────────────────────────────────────────────────────────────────────────┐
│ HTTP CLIENT                                                                         │
│ ├─ GET  /analytics/market-report?city=...                                          │
│ ├─ GET  /analytics/price-trend?district=...                                        │
│ ├─ POST /analytics/valuation (form body)                                           │
│ └─ GET  /avm/explain?valuationId=...                                               │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                         PRESENTATION LAYER                                         │
│  ┌──────────────────────────────────────────────────────────────────────────┐      │
│  │ AnalyticsController              AvmController                          │      │
│  │ ├─ GET /market-report            ├─ POST /batch                        │      │
│  │ ├─ GET /price-trend              ├─ GET /history/:id                   │      │
│  │ ├─ GET /heatmap                  ├─ GET /compare                       │      │
│  │ ├─ POST /valuation               ├─ GET /explain                       │      │
│  │ ├─ POST /valuation/batch         └─ POST /industrial                   │      │
│  │ └─ GET /neighborhoods/:d/score                                          │      │
│  └──────────────────────────────────────────────────────────────────────────┘      │
│                            (Request/Response DTOs)                                 │
│     GetMarketReportDto  PredictValuationDto  BatchValuationDto  etc.               │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                        │
                  ┌─────────────────────┼─────────────────────┐
                  │                     │                     │
                  ▼                     ▼                     ▼
         ┌───────────────────────────────────────────────────────────┐
         │ SHARED MIDDLEWARE & GUARDS (Global)                      │
         ├───────────────────────────────────────────────────────────┤
         │ • EndpointRateLimitGuard (Redis sliding-window)          │
         │   └─ Rate limit key: "rate:{strategy}:{id}:{path}"      │
         │ • JwtAuthGuard (verify JWT token)                        │
         │ • QuotaGuard (check subscription quota)                  │
         │ • CorrelationIdMiddleware (trace ID injection)           │
         │ • RequestLoggingMiddleware (audit logging)               │
         │ • SanitizeInputMiddleware (input validation)             │
         └───────────────────────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                    APPLICATION LAYER (CQRS Pattern)                                │
│ ┌──────────────────────────────────┐  ┌────────────────────────────────────────┐  │
│ │ QUERIES (Read Operations)        │  │ COMMANDS (Write Operations)            │  │
│ │                                  │  │                                        │  │
│ │ Query Classes & Handlers:        │  │ • GenerateReportHandler                │  │
│ │ ├─ GetPriceTrendQuery            │  │ • TrackEventHandler                    │  │
│ │ ├─ GetHeatmapQuery               │  │ • UpdateMarketIndexHandler             │  │
│ │ ├─ GetMarketReportQuery          │  │                                        │  │
│ │ ├─ GetDistrictStatsQuery         │  │ EVENT HANDLERS:                        │  │
│ │ ├─ GetValuationQuery             │  │ • ListingCreatedModerationHandler      │  │
│ │ ├─ PredictValuationQuery         │  │                                        │  │
│ │ ├─ BatchValuationQuery           │  │ QUERYBUS.EXECUTE()                    │  │
│ │ ├─ IndustrialValuationQuery      │  │ COMMANDBUS.EXECUTE()                  │  │
│ │ ├─ GetNeighborhoodScoreQuery     │  │ EVENTBUS.EMIT()                       │  │
│ │ ├─ GetNearbyPOIsQuery            │  │                                        │  │
│ │ ├─ GetListingAiAdviceQuery       │  │                                        │  │
│ │ ├─ GetProjectAiAdviceQuery       │  │                                        │  │
│ │ ├─ ValuationHistoryQuery         │  │                                        │  │
│ │ ├─ ValuationComparisonQuery      │  │                                        │  │
│ │ └─ ValuationExplanationQuery     │  │                                        │  │
│ └──────────────────────────────────┘  └────────────────────────────────────────┘  │
│                                                                                    │
│ All handlers follow this pattern:                                                 │
│  1. Build cache key with CacheService.buildKey(CachePrefix.*, ...params)          │
│  2. Call cache.getOrSet(cacheKey, loader, CacheTTL.*, 'metric_label')             │
│  3. Catch DomainException separately; wrap others                                 │
│  4. Return DTO from handler                                                       │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                        DOMAIN LAYER (DDD Pattern)                                  │
│ ┌────────────────────────────────────────────────────────────────────────────┐    │
│ │ REPOSITORY INTERFACES (Abstraction)                                        │    │
│ │ • IMarketIndexRepository                                                   │    │
│ │ • IValuationRepository                                                     │    │
│ │                                                                            │    │
│ │ DOMAIN SERVICES (Business Logic)                                          │    │
│ │ • IAVMService (interface)                                                │    │
│ │ • INeighborhoodScoreService (interface)                                  │    │
│ │                                                                            │    │
│ │ DOMAIN ENTITIES                                                           │    │
│ │ • MarketIndexEntity                                                       │    │
│ │ • ValuationEntity                                                         │    │
│ └────────────────────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────────────────────┘
         │                              │                              │
         ▼                              ▼                              ▼
┌────────────────────┐    ┌────────────────────────┐    ┌─────────────────────────┐
│ INFRASTRUCTURE     │    │ REDIS CACHE SERVICE    │    │ PRISMA REPOSITORIES    │
├────────────────────┤    ├────────────────────────┤    ├─────────────────────────┤
│ • HttpAVMService   │    │ cache.getOrSet()       │    │ PrismaMarketIndexRepo  │
│   (→ Python AI)    │    │                        │    │ PrismaValuationRepo    │
│                    │    │ Metrics:               │    │                        │
│ • PrismaAVMService │    │ • cache_hit_total      │    │ Converts:              │
│   (fallback)       │    │ • cache_miss_total     │    │ Prisma → Domain Entity │
│                    │    │ • cache_degradation    │    │                        │
│ • HttpNbScore      │    │                        │    │ Query patterns:        │
│   Service          │    │ Cache Prefixes:        │    │ • findById             │
│   (→ Python)       │    │ • cache:market:trend   │    │ • findMany             │
│                    │    │ • cache:market:report  │    │ • getMarketReport      │
│ • PrismaNeighbor   │    │ • cache:market:heatmap │    │ • getHeatmap           │
│   Score Service    │    │ • cache:valuation      │    │ • getPriceTrend        │
│   (fallback)       │    │                        │    │ • getDistrictStats     │
│                    │    │ TTLs:                  │    │                        │
│ • AiServiceClient  │    │ • MARKET_DATA: 1800s   │    │ All use PrismaService  │
│   (Claude API)     │    │ • MARKET_REPORT: 900s  │    │ for database access    │
│                    │    │ • HEATMAP: 300s        │    │                        │
│ • MarketIndex      │    │ • DISTRICT_STATS: 300s │    │                        │
│   CronService      │    │ • REFERENCE_DATA: 86400s │  │                        │
└────────────────────┘    └────────────────────────┘    └─────────────────────────┘
         │                              │                              │
         │                              │                              │
         │                 ┌────────────┴───────────┐                  │
         │                 ▼                        ▼                  │
         │         ┌──────────────────┐     ┌───────────────────┐    │
         │         │  REDIS           │     │  POSTGRESQL 16    │    │
         │         ├──────────────────┤     ├───────────────────┤    │
         │         │ Sliding Window   │     │ Tables:           │    │
         │         │ Rate Limiter     │     │ • Property        │    │
         │         │                  │     │ • Listing         │    │
         │         │ Cache Storage    │     │ • PriceHistory    │    │
         │         │ (cache-aside)    │     │ • MarketIndex     │    │
         │         │                  │     │ • Valuation       │    │
         │         │ Metrics:         │     │ • User            │    │
         │         │ rate:*:*:*       │     │ • Subscription    │    │
         │         └──────────────────┘     │ + PostGIS         │    │
         │                                   │ (geometry)        │    │
         │                                   └───────────────────┘    │
         │                                                            │
         └────────────────────────────────────────────────────────────┘
                              │
                              ▼
           ┌──────────────────────────────────────┐
           │  EXTERNAL SERVICES                   │
           ├──────────────────────────────────────┤
           │ • Python AI Service                  │
           │   (AVM, Neighborhood Score)          │
           │ • Anthropic Claude API               │
           │   (Listing/Project AI Advice)        │
           │ • Google Maps/OSM API                │
           │   (Nearby POIs)                      │
           └──────────────────────────────────────┘


╔═════════════════════════════════════════════════════════════════════════════════════╗
║                            DATA FLOW EXAMPLE                                        ║
║                   GET /analytics/price-trend?district=...                          ║
╚═════════════════════════════════════════════════════════════════════════════════════╝

  1. Controller receives query DTO
     └─ @Query() dto: GetPriceTrendDto

  2. Controller validates & creates Query object
     └─ new GetPriceTrendQuery(dto.district, dto.city, dto.propertyType, dto.periods)

  3. Controller sends to QueryBus
     └─ queryBus.execute(query)

  4. QueryBus routes to GetPriceTrendHandler

  5. Handler builds cache key
     └─ CacheService.buildKey(CachePrefix.MARKET_TREND, district, city, propertyType, periods)
     └─ Result: "cache:market:trend:Quận 1:Hồ Chí Minh:APARTMENT:2024-Q1,2024-Q2"

  6. Handler calls cache.getOrSet()
     a) Redis lookup → FOUND? Return cached data
     b) MISS? Call loader function:
        • Call marketIndexRepo.getPriceTrend()
        • PrismaMarketIndexRepository queries PostgreSQL
        • Fetch: SELECT * FROM "MarketIndex" WHERE district=? AND city=? AND ...
        • Convert Prisma model → DomainEntity
        • Return trend data
     c) Store in Redis with TTL (1800s = 30 min)
     d) Return data to caller

  7. Handler returns PriceTrendDto to controller

  8. Controller returns JSON to client

  9. Response includes:
     • Cached status (if applicable)
     • Rate-limit headers (X-RateLimit-*)
     • CorrelationId (for tracing)
     • Standard error format (if error)


╔═════════════════════════════════════════════════════════════════════════════════════╗
║                         SHARED UTILITIES & EXPORTS                                 ║
╚═════════════════════════════════════════════════════════════════════════════════════╝

From @modules/shared:
  • CacheService
  • CachePrefix (enum)
  • CacheTTL (const)
  • RedisService
  • LoggerService
  • PrismaService
  • DomainException & subclasses
  • EndpointRateLimit (decorator)
  • EndpointRateLimitGuard
  • ErrorResponseBody interface
  • JwtAuthGuard
  • QuotaGuard
  • @RequireQuota decorator

Exports from analytics.module.ts:
  • MARKET_INDEX_REPOSITORY
  • VALUATION_REPOSITORY
  • AVM_SERVICE
  • AI_SERVICE_CLIENT

