Files
goodgo-platform/docs/explorations/from-desktop/03_file_paths_reference.md
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

12 KiB

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<MarketSnapshotDto>
   └─ 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<T>(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

// 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