- 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>
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
-
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
-
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
-
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)
EndpointRateLimitGuard→ Redis rate limitJwtAuthGuard→ JWT verificationQuotaGuard→ 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:
-
Define Cache Strategy
- Choose TTL from
CacheTTL.*or create new one - Use appropriate
CachePrefix.*
- Choose TTL from
-
Create Query & Handler
- Query class: simple data holder
- Handler: cache-aside + repository call
-
Define DTO
- Request DTO for controller parameters
- Response DTO as handler return type
-
Add Controller Endpoint
- Use guard stack:
EndpointRateLimitGuard→JwtAuthGuard→QuotaGuard - Call
queryBus.execute()
- Use guard stack:
-
Register in Module
- Add handler to
QueryHandlersarray - Add to module
providers
- Add handler to
-
Error Handling
- Catch and rethrow
DomainException - Log unexpected errors with context
- Return user-friendly message
- Catch and rethrow
📊 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 referenceANALYTICS_QUICK_REFERENCE.md- Developer quick guideANALYTICS_ARCHITECTURE_DIAGRAM.txt- Visual overviewEXPLORATION_SUMMARY.md- This file