Files
goodgo-platform/docs/explorations/ANALYTICS_ARCHITECTURE_DIAGRAM.txt
Ho Ngoc Hai 08b96f9c2d docs: consolidate exploration & audit reports under docs/ (TEC-3094)
- Move 8 stray .md (+5 .txt) from ~/Desktop into docs/explorations/from-desktop/
- Reorganize 27 .md/.txt at workspace root:
  - audit reports -> docs/audits/
  - exploration reports -> docs/explorations/
  - design system -> docs/design-system/
- Keep only README/CHANGELOG/CONTRIBUTING/CLAUDE at repo root
- Refresh docs/README.md as canonical index with links to all groups
- Note: pre-existing docs/audits/AUDIT_INDEX.md and AUDIT_SUMMARY.md were
  overwritten by the newer root-level versions during the move

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-21 16:29:24 +07:00

218 lines
19 KiB
Plaintext

╔═════════════════════════════════════════════════════════════════════════════════════╗
║ 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