Files
goodgo-platform/docs/explorations/EXPLORATION_SUMMARY.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 Exploration - Summary Report

Exploration Complete

I've thoroughly explored the GoodGo Platform analytics module and created comprehensive documentation. Here's what was analyzed:


📄 Documentation Created

  1. ANALYTICS_ARCHITECTURE.md (Comprehensive, 10 sections)

    • Full module structure with DDD/CQRS layers
    • Controller & endpoint mapping
    • Query/handler CQRS pattern deep-dive
    • Redis caching patterns with Lua scripts
    • Prisma schema for Property, Listing, MarketIndex, Valuation models
    • Shared guards, decorators, and exception patterns
    • DTO patterns and module dependency injection
    • Key patterns and quick reference paths
  2. ANALYTICS_QUICK_REFERENCE.md (Developer-friendly reference)

    • Quick architecture overview
    • File paths and module organization
    • Guard & decorator stack with usage examples
    • Cache patterns (cache-aside, TTLs, prefixes)
    • Prisma models summary
    • CQRS handler pattern code example
    • Error handling pattern
    • Common endpoints list
    • Dependency injection patterns
    • Key conventions table
  3. ANALYTICS_ARCHITECTURE_DIAGRAM.txt (Visual reference)

    • Complete system architecture diagram
    • Data flow walkthrough for example request
    • All layers from HTTP → Database
    • External service integrations
    • Shared utilities & exports

🎯 Key Findings

1. Architecture Pattern: DDD + CQRS

  • Presentation: Controllers (analytics, avm) + DTOs
  • Application: Query/Command handlers with Prometheus metrics
  • Domain: Repository interfaces, service abstractions, entities
  • Infrastructure: Prisma repositories, external service clients

2. Controllers (2 total)

  • AnalyticsController/analytics/* (13+ endpoints)
  • AvmController/avm/* (5 endpoints)

3. Query Handlers (14+)

All follow cache-aside pattern:

  • Price trends, heatmaps, market reports, district stats
  • Valuations (single, batch, history, comparison, explanation)
  • Industrial valuation
  • Neighborhood scores, nearby POIs
  • AI advice (Claude integration for listings/projects)

4. Redis Caching Strategy

Pattern: Cache-aside with graceful degradation

cache.getOrSet(key, loader, TTL, metricLabel)

TTLs for analytics:

  • MARKET_DATA: 1800s (30 min) - price trends
  • MARKET_REPORT: 900s (15 min) - summaries
  • HEATMAP: 300s (5 min) - tiles
  • DISTRICT_STATS: 300s (5 min) - statistics

Cache Prefixes:

cache:market:report
cache:market:trend
cache:market:heatmap
cache:market:district
cache:valuation

5. Rate Limiting (Redis Sliding-Window)

Guard: EndpointRateLimitGuard Decorator: @EndpointRateLimit({ limit, windowSeconds, keyStrategy }) Implementation: Lua script with sorted set (ZSET) in Redis Strategy: 'user' (by user ID) or 'ip' (by client IP)

Example:

@EndpointRateLimit({ limit: 10, windowSeconds: 60, keyStrategy: 'user' })
@UseGuards(EndpointRateLimitGuard, JwtAuthGuard, QuotaGuard)

6. Guards Stack (Order Matters)

  1. EndpointRateLimitGuard → Redis rate limit
  2. JwtAuthGuard → JWT verification
  3. QuotaGuard → Subscription quota check

7. Prisma Schema - Key Models

Property:

  • propertyType (APARTMENT, HOUSE, LAND, COMMERCIAL)
  • status (ACTIVE, SOLD, RENTED, REMOVED)
  • district, city, location (PostGIS geometry)
  • areaM2, bedrooms, bathrooms, yearBuilt, etc.

Listing (analytics-aware):

  • priceVND (BigInt, checked > 0)
  • pricePerM2 (float, derived for analytics)
  • aiPriceEstimate, aiConfidence (AVM fields)
  • viewCount, saveCount, inquiryCount (tracking)
  • publishedAt, createdAt, status

MarketIndex (pre-calculated):

  • medianPrice, avgPriceM2, totalListings
  • daysOnMarket, inventoryLevel, absorptionRate
  • yoyChange (year-over-year)
  • Unique index: (district, city, propertyType, period)

Valuation (AVM storage):

  • estimatedPrice, confidence
  • drivers (JSON), comparables (JSON)
  • explanation, model version

8. Repository Pattern

Interface (domain):

export const MARKET_INDEX_REPOSITORY = Symbol('MARKET_INDEX_REPOSITORY');
export interface IMarketIndexRepository {
  findById(id: string): Promise<MarketIndexEntity | null>;
  getMarketReport(...): Promise<MarketReportResult[]>;
  getPriceTrend(...): Promise<PriceTrendPoint[]>;
  getHeatmap(...): Promise<HeatmapDataPoint[]>;
  getDistrictStats(...): Promise<DistrictStatsResult[]>;
}

Implementation (infrastructure):

@Injectable()
export class PrismaMarketIndexRepository implements IMarketIndexRepository {
  constructor(private readonly prisma: PrismaService) {}
  // Converts Prisma → Domain entities
}

Injection (handler):

constructor(
  @Inject(MARKET_INDEX_REPOSITORY)
  private readonly repo: IMarketIndexRepository
) {}

9. Error Handling Pattern

try {
  return this.cache.getOrSet(...);
} catch (error) {
  if (error instanceof DomainException) throw error;  // Re-throw
  this.logger.error(...);  // Log with context
  throw new InternalServerErrorException('...');  // Wrap & return user message
}

10. Shared Module Exports

From @modules/shared:

  • CacheService with getOrSet() method
  • RedisService connection pool
  • LoggerService structured logging
  • PrismaService database access
  • DomainException & subclasses
  • EndpointRateLimit decorator
  • EndpointRateLimitGuard guard
  • JwtAuthGuard, QuotaGuard
  • Error response standardization via GlobalExceptionFilter

11. DTO Conventions

  • Request DTOs: Query parameters, body validation
  • Response DTOs: Defined as handler return type interfaces
  • BigInt handling: Always stringified for JSON safety (.toString())

🔗 File Path Quick Map

ANALYTICS ROOT
└── apps/api/src/modules/analytics/

CONTROLLERS
├── presentation/controllers/analytics.controller.ts       (13+ endpoints)
└── presentation/controllers/avm.controller.ts             (5 endpoints)

QUERY HANDLERS (14+ total)
├── application/queries/get-price-trend/                   ← Cache-aside pattern
├── application/queries/get-heatmap/
├── application/queries/get-market-report/
├── application/queries/get-valuation/
├── application/queries/predict-valuation/
├── application/queries/batch-valuation/
├── application/queries/valuation-history/
├── application/queries/valuation-comparison/
├── application/queries/valuation-explanation/
├── application/queries/get-neighborhood-score/
├── application/queries/get-nearby-pois/
├── application/queries/get-listing-ai-advice/            (Claude)
└── application/queries/get-project-ai-advice/            (Claude)

COMMAND HANDLERS (3)
├── application/commands/generate-report/
├── application/commands/track-event/
└── application/commands/update-market-index/

EVENT HANDLERS (1)
└── application/event-handlers/listing-created-moderation.handler.ts

REPOSITORIES
├── domain/repositories/market-index.repository.ts        (interface)
├── domain/repositories/valuation.repository.ts           (interface)
├── infrastructure/repositories/prisma-market-index.repository.ts
└── infrastructure/repositories/prisma-valuation.repository.ts

SERVICES
├── infrastructure/services/http-avm.service.ts           (→ Python AI)
├── infrastructure/services/prisma-avm.service.ts         (fallback)
├── infrastructure/services/http-neighborhood-score.service.ts
├── infrastructure/services/prisma-neighborhood-score.service.ts
├── infrastructure/services/ai-service.client.ts          (Claude)
└── infrastructure/services/market-index-cron.service.ts

SHARED MODULE (Global)
└── apps/api/src/modules/shared/
    ├── infrastructure/
    │   ├── cache.service.ts                              ← Cache-aside
    │   ├── redis.service.ts                              ← Connection pool
    │   ├── logger.service.ts                             ← Structured logging
    │   ├── prisma.service.ts                             ← Database
    │   ├── guards/endpoint-rate-limit.guard.ts           ← Lua sliding-window
    │   ├── decorators/endpoint-rate-limit.decorator.ts
    │   ├── middleware/correlation-id.middleware.ts       ← Trace ID
    │   └── filters/global-exception.filter.ts            ← Error standardization
    └── domain/
        ├── domain-exception.ts                           ← Exception base
        └── error-codes.ts

💡 Implementation Patterns to Follow

1. New Query Handler Template

@QueryHandler(YourQuery)
export class YourQueryHandler implements IQueryHandler<YourQuery> {
  constructor(
    @Inject(YOUR_REPOSITORY)
    private readonly repo: IYourRepository,
    private readonly cache: CacheService,
    private readonly logger: LoggerService,
  ) {}

  async execute(query: YourQuery): Promise<YourDto> {
    try {
      const cacheKey = CacheService.buildKey(
        CachePrefix.YOUR_PREFIX,
        query.param1,
        query.param2,
      );

      return this.cache.getOrSet(
        cacheKey,
        async () => {
          const data = await this.repo.yourMethod(...);
          return { ...data };
        },
        CacheTTL.YOUR_TTL,
        'your_metric_label',
      );
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(`Failed to ...`, error?.stack, this.constructor.name);
      throw new InternalServerErrorException('User-friendly message');
    }
  }
}

2. New Controller Endpoint Template

@ApiBearerAuth('JWT')
@EndpointRateLimit({ limit: 10, windowSeconds: 60, keyStrategy: 'user' })
@UseGuards(EndpointRateLimitGuard, JwtAuthGuard, QuotaGuard)
@RequireQuota('analytics_queries')
@Get('your-endpoint')
@ApiOperation({ summary: 'Description' })
async yourMethod(@Query() dto: YourDto): Promise<YourResultDto> {
  return this.queryBus.execute(
    new YourQuery(dto.param1, dto.param2, ...)
  );
}

3. Register Handler in Module

const QueryHandlers = [
  // existing handlers...
  YourQueryHandler,
];

@Module({
  providers: [
    ...QueryHandlers,
  ],
})

🚀 Next Steps for Implementation

When building new analytics features:

  1. Define Cache Strategy

    • Choose TTL from CacheTTL.* or create new one
    • Use appropriate CachePrefix.*
  2. Create Query & Handler

    • Query class: simple data holder
    • Handler: cache-aside + repository call
  3. Define DTO

    • Request DTO for controller parameters
    • Response DTO as handler return type
  4. Add Controller Endpoint

    • Use guard stack: EndpointRateLimitGuardJwtAuthGuardQuotaGuard
    • Call queryBus.execute()
  5. Register in Module

    • Add handler to QueryHandlers array
    • Add to module providers
  6. Error Handling

    • Catch and rethrow DomainException
    • Log unexpected errors with context
    • Return user-friendly message

📊 Statistics

  • Controllers: 2
  • Query Handlers: 14+
  • Command Handlers: 3
  • Event Handlers: 1
  • DTOs: 15+
  • Repositories: 2 interfaces + 2 implementations
  • Cache Prefixes: 12
  • TTLs Configured: 10+
  • Endpoints: 18+
  • Rate Limit Configurations: Multiple per endpoint

🎓 Architecture Highlights

DDD Layers: Clear separation of concerns CQRS Pattern: Query/Command handlers with event sourcing capability Cache-Aside Pattern: Redis caching with graceful degradation Sliding-Window Rate Limiting: Accurate per-endpoint limiting with Redis Dependency Injection: Repository pattern with interface abstraction Error Standardization: Global exception filter with error codes Prometheus Metrics: Cache hit/miss/degradation tracking Middleware Stack: Correlation ID, audit logging, CSRF, input sanitization External Service Fallback: HTTP → Python AI with Prisma fallback Quota Management: Subscription-based quota enforcement per resource


All documentation files saved to project root:

  • ANALYTICS_ARCHITECTURE.md - Comprehensive reference
  • ANALYTICS_QUICK_REFERENCE.md - Developer quick guide
  • ANALYTICS_ARCHITECTURE_DIAGRAM.txt - Visual overview
  • EXPLORATION_SUMMARY.md - This file