# Analytics Module — File Paths & Quick Reference ## 🔗 Core Module Files ``` /apps/api/src/modules/analytics/ │ ├── analytics.module.ts │ └─ Registers all handlers, repositories, services │ └─ Module metadata: imports, controllers, providers, exports │ └─ KEY: CommandHandlers, QueryHandlers, EventHandlers arrays │ ├── index.ts │ └─ Public exports of analytics module │ └── README.md └─ Module documentation ``` ## 🎯 Controllers (Entry Points) ``` /apps/api/src/modules/analytics/presentation/controllers/ analytics.controller.ts (19 endpoints) ├─ GET /analytics/market-report ├─ GET /analytics/market-snapshot ├─ GET /analytics/price-trend ├─ GET /analytics/heatmap ├─ GET /analytics/district-stats ├─ GET /analytics/valuation (query param) ├─ POST /analytics/valuation (body) ├─ POST /analytics/valuation/batch ├─ GET /analytics/valuation/history/:propertyId ├─ POST /analytics/valuation/compare ├─ GET /analytics/neighborhoods/:district/score ├─ GET /analytics/pois/nearby ├─ POST /analytics/listings/:id/ai-advice └─ POST /analytics/projects/:id/ai-advice avm.controller.ts (5 endpoints) ├─ POST /avm/batch ├─ GET /avm/history/:propertyId ├─ GET /avm/compare?ids=... ├─ GET /avm/explain?valuationId=... └─ POST /avm/industrial ``` ## 📋 DTOs (Requests & Responses) ``` /apps/api/src/modules/analytics/presentation/dto/ REQUEST DTOs (from @Query/@Body): ├─ get-district-stats.dto.ts (city, period) ├─ get-heatmap.dto.ts (city, period) ├─ get-market-report.dto.ts (city, period, propertyType) ├─ get-market-snapshot.dto.ts (city, propertyType) ├─ get-price-trend.dto.ts (district, city, propertyType, periods) ├─ get-valuation.dto.ts (propertyId | lat/lng/areaM2) ├─ get-nearby-pois.dto.ts (lat, lng, radius, limit) ├─ predict-valuation.dto.ts (20+ fields, v1 & v2) ├─ batch-valuation.dto.ts (propertyIds: string[]) ├─ valuation-history.dto.ts (limit) ├─ valuation-comparison.dto.ts (propertyIds) ├─ avm-compare-query.dto.ts (ids) ├─ avm-explain-query.dto.ts (valuationId) ├─ industrial-valuation.dto.ts (30+ industrial fields) └─ get-trending-areas.dto.ts (city, propertyType, limit, period) RESPONSE DTOs (exported from handlers): (See handler files below) ``` ## 🔄 Queries (CQRS Pattern) ``` /apps/api/src/modules/analytics/application/queries/ STRUCTURE OF EACH QUERY TYPE: get-market-snapshot/ ├─ get-market-snapshot.query.ts │ └─ export class GetMarketSnapshotQuery { ... } │ └─ get-market-snapshot.handler.ts ├─ @QueryHandler(GetMarketSnapshotQuery) ├─ execute(query): Promise └─ export interface MarketSnapshotDto { ... } ALL QUERY TYPES (15+): ├─ get-market-snapshot/ ..................... Dashboard overview ├─ get-district-stats/ ..................... Stats aggregation ├─ get-price-trend/ ........................ Time-series data ├─ get-heatmap/ ........................... Geographic visualization ├─ get-valuation/ ......................... Single valuation ├─ predict-valuation/ ..................... AI prediction ├─ batch-valuation/ ....................... Multiple valuations ├─ valuation-history/ ..................... Time-series valuations ├─ valuation-comparison/ .................. Side-by-side comparison ├─ valuation-explanation/ ................. Model drivers ├─ get-neighborhood-score/ ................ Neighborhood quality ├─ get-nearby-pois/ ....................... Point of interests ├─ get-listing-ai-advice/ ................. Claude analysis ├─ get-project-ai-advice/ ................. Project analysis ├─ industrial-valuation/ .................. Industrial rent ├─ get-market-report/ ..................... Detailed report └─ get-trending-areas/ .................... Trending districts ``` ## 🏛️ Domain Layer ``` /apps/api/src/modules/analytics/domain/ repositories/ (Interfaces only — NO IMPLEMENTATION) ├─ market-index.repository.ts │ ├─ export const MARKET_INDEX_REPOSITORY = Symbol(...) │ ├─ export interface IMarketIndexRepository { │ │ findById(id) │ │ findByKey(district, city, propertyType, period) │ │ save(entity) │ │ update(entity) │ │ getMarketReport(city, period, propertyType?) │ │ getHeatmap(city, period) │ │ getPriceTrend(district, city, propertyType, periods) │ │ getDistrictStats(city, period) │ │ } │ └─ Result interfaces: MarketReportResult, HeatmapDataPoint, etc. │ └─ valuation.repository.ts ├─ export const VALUATION_REPOSITORY = Symbol(...) └─ export interface IValuationRepository { ... } entities/ ├─ market-index.entity.ts │ └─ Domain logic for market data aggregation │ └─ valuation.entity.ts └─ Domain logic for property valuation services/ ├─ avm-service.ts │ └─ export const AVM_SERVICE = Symbol(...) │ └─ IAVMService interface: predict(), getComparables(), etc. │ └─ neighborhood-score.service.ts └─ INeighborhoodScoreService interface events/ └─ market-index-updated.event.ts └─ Domain event when market data updates ``` ## 🔧 Infrastructure Layer ``` /apps/api/src/modules/analytics/infrastructure/ repositories/ (IMPLEMENTATIONS) ├─ prisma-market-index.repository.ts │ ├─ @Injectable() │ └─ class PrismaMarketIndexRepository implements IMarketIndexRepository { │ └─ Uses PrismaService for data access │ └─ prisma-valuation.repository.ts └─ class PrismaValuationRepository implements IValuationRepository services/ (IMPLEMENTATIONS) ├─ http-avm.service.ts │ ├─ @Injectable() │ ├─ Calls Python AI service (HTTP client) │ └─ Falls back to PrismaAVMService if Python is down │ ├─ prisma-avm.service.ts │ ├─ @Injectable() │ └─ Fallback ML model using Prisma data │ ├─ http-neighborhood-score.service.ts │ ├─ HTTP proxy to external scoring service │ └─ Falls back to PrismaNeighborhoodScoreService │ ├─ prisma-neighborhood-score.service.ts │ └─ In-DB scoring logic │ ├─ ai-service.client.ts │ ├─ Wrapper around Anthropic SDK │ └─ Calls Claude API for AI analysis │ └─ market-index-cron.service.ts └─ Scheduled job to update MarketIndex table ``` ## 🎨 Interceptors ``` /apps/api/src/modules/analytics/presentation/interceptors/ cache-meta.interceptor.ts ├─ @Injectable() CacheMetaInterceptor ├─ Wraps response: T => { data: T; cacheMeta: {...} } ├─ cacheMeta includes: cachedAt, nextRefreshAt, source └─ Applied via @UseInterceptors(CacheMetaInterceptor) ``` ## 📦 Shared Module (Reusable Utilities) ``` /apps/api/src/modules/shared/ infrastructure/ cache.service.ts ├─ @Injectable() CacheService ├─ async getOrSet(key, loader, ttl, resource) │ └─ Cache-aside pattern │ └─ Metrics: cache_hit_total, cache_miss_total, cache_degradation_total ├─ async invalidate(key) ├─ async invalidateByPrefix(prefix) ├─ static buildKey(prefix, ...parts) └─ Graceful degradation when Redis is down decorators/ ├─ cacheable.decorator.ts │ ├─ @Cacheable(options) method decorator │ └─ Declarative caching for query handlers │ └─ other decorators... cache-meta.store.ts ├─ export const cacheMetaStorage = new AsyncLocalStorage() └─ Per-request storage of cache metadata logger.service.ts ├─ @Injectable() LoggerService ├─ log(), warn(), error() with context └─ Winston integration prisma.service.ts ├─ @Injectable() PrismaService ├─ Wrapper around Prisma Client └─ Handles connection lifecycle redis.service.ts ├─ @Injectable() RedisService ├─ get(), set(), del(), scan() └─ Health check & graceful degradation guards/ ├─ endpoint-rate-limit.guard.ts ├─ ... other guards └─ auth module exports JwtAuthGuard shared.module.ts └─ Registers all shared services ``` ## 📊 Database Schema ``` prisma/schema.prisma Models relevant to analytics: ├─ Property │ ├─ id, propertyType, address, district, city │ ├─ location (PostGIS Point) │ ├─ areaM2, bedrooms, bathrooms, floors │ └─ Indexes: [propertyType], [district, city], [location] │ ├─ Listing │ ├─ id, propertyId (FK), sellerId (FK) │ ├─ status (ACTIVE, SOLD, EXPIRED, ...) │ ├─ priceVND (BigInt), pricePerM2, publishedAt │ ├─ aiPriceEstimate, aiConfidence (for AVM) │ └─ Indexes: [status], [sellerId, status], [publishedAt] │ ├─ MarketIndex │ ├─ district, city, propertyType, period │ ├─ medianPrice (BigInt), avgPriceM2 │ ├─ totalListings, daysOnMarket, inventoryLevel │ └─ Unique: [district, city, propertyType, period] │ ├─ Valuation │ ├─ id, propertyId (FK) │ ├─ estimatedPrice (BigInt), confidence │ ├─ method (AVM_v1, AVM_v2, MANUAL) │ ├─ features (Json), comparables (Json), explainers (Json) │ └─ Index: [propertyId, valuationDate DESC] │ └─ ProjectDevelopment ├─ id, slug, developer ├─ location (PostGIS Point) ├─ minPrice, maxPrice, pricePerM2Range └─ Index: [district, city], [location] ``` ## 🌳 Directory Tree Summary ``` goodgo-platform-ai/ └─ apps/api/src/modules/ ├─ analytics/ (this module) .................. ~2000 LOC │ ├─ presentation/ │ │ ├─ controllers/ │ │ │ ├─ analytics.controller.ts ......... 331 lines │ │ │ └─ avm.controller.ts .............. 171 lines │ │ ├─ dto/ (15+ files) │ │ └─ interceptors/ │ │ └─ cache-meta.interceptor.ts ....... 61 lines │ ├─ application/ │ │ ├─ queries/ (15+ handlers) │ │ ├─ commands/ (3 handlers) │ │ └─ event-handlers/ │ ├─ domain/ │ │ ├─ repositories/ (2 interfaces) │ │ ├─ entities/ (2 entities) │ │ ├─ services/ (2 interfaces) │ │ └─ events/ │ ├─ infrastructure/ │ │ ├─ repositories/ (2 implementations) │ │ └─ services/ (6 implementations) │ └─ analytics.module.ts │ ├─ shared/ ............................. Reusable utilities │ ├─ infrastructure/ │ │ ├─ cache.service.ts ............... 191 lines (core pattern) │ │ ├─ decorators/ │ │ │ └─ cacheable.decorator.ts ....... 57 lines │ │ ├─ cache-meta.store.ts │ │ ├─ logger.service.ts │ │ ├─ prisma.service.ts │ │ ├─ redis.service.ts │ │ └─ guards/ (rate limit, auth, etc) │ └─ shared.module.ts │ ├─ auth/ ............................. Authentication ├─ subscriptions/ ..................... Quota & billing ├─ listings/ ......................... Listing management └─ [other modules] ``` ## 🔑 Key Imports Pattern ```ts // In analytics.controller.ts import { QueryBus } from '@nestjs/cqrs'; import { JwtAuthGuard } from '@modules/auth'; import { EndpointRateLimit, EndpointRateLimitGuard } from '@modules/shared'; import { RequireQuota, QuotaGuard } from '@modules/subscriptions'; // In query handlers import { DomainException, CacheService, CachePrefix, CacheTTL, Cacheable, LoggerService, PrismaService, } from '@modules/shared'; import { MARKET_INDEX_REPOSITORY, type IMarketIndexRepository } from '../../domain/...'; // In infrastructure repositories import { Injectable } from '@nestjs/common'; import { PrismaService } from '@modules/shared'; import { type IMarketIndexRepository } from '../../domain/...'; ``` ## 🚀 Key Numbers | Metric | Value | |--------|-------| | Controllers | 2 | | Endpoints | 24 | | Query Handlers | 15+ | | DTOs | 15+ | | Repository Interfaces | 2 | | Repository Implementations | 2 | | Services (interfaces) | 2 | | Services (implementations) | 6+ | | Cache Prefixes | 10+ | | Cache TTLs | 20+ | | Total LOC (analytics module) | ~2000 | | Total LOC (shared/cache) | ~250 |