import { forwardRef, Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; import { makeCounterProvider, makeHistogramProvider } from '@willsoto/nestjs-prometheus'; import { ListingsModule } from '@modules/listings'; import { ProjectsModule } from '@modules/projects'; import { GenerateReportHandler } from './application/commands/generate-report/generate-report.handler'; import { TrackEventHandler } from './application/commands/track-event/track-event.handler'; import { UpdateMarketIndexHandler } from './application/commands/update-market-index/update-market-index.handler'; import { ListingCreatedModerationHandler } from './application/event-handlers/listing-created-moderation.handler'; import { BatchValuationHandler } from './application/queries/batch-valuation/batch-valuation.handler'; import { IndustrialValuationHandler } from './application/queries/industrial-valuation/industrial-valuation.handler'; import { GetDistrictStatsHandler } from './application/queries/get-district-stats/get-district-stats.handler'; import { GetHeatmapHandler } from './application/queries/get-heatmap/get-heatmap.handler'; import { GetListingAiAdviceHandler } from './application/queries/get-listing-ai-advice/get-listing-ai-advice.handler'; import { GetListingVolumeWardHandler } from './application/queries/get-listing-volume-ward/get-listing-volume-ward.handler'; import { GetMarketReportHandler } from './application/queries/get-market-report/get-market-report.handler'; import { GetMarketHistoryHandler } from './application/queries/get-market-history/get-market-history.handler'; import { GetMarketSnapshotHandler } from './application/queries/get-market-snapshot/get-market-snapshot.handler'; import { GetPriceMoversHandler } from './application/queries/get-price-movers/get-price-movers.handler'; import { GetTrendingAreasHandler } from './application/queries/get-trending-areas/get-trending-areas.handler'; import { GetProjectAiAdviceHandler } from './application/queries/get-project-ai-advice/get-project-ai-advice.handler'; import { GetNearbyPOIsHandler } from './application/queries/get-nearby-pois/get-nearby-pois.handler'; import { GetNeighborhoodScoreHandler } from './application/queries/get-neighborhood-score/get-neighborhood-score.handler'; import { GetPriceTrendHandler } from './application/queries/get-price-trend/get-price-trend.handler'; import { GetValuationHandler } from './application/queries/get-valuation/get-valuation.handler'; import { PredictValuationHandler } from './application/queries/predict-valuation/predict-valuation.handler'; import { ValuationComparisonHandler } from './application/queries/valuation-comparison/valuation-comparison.handler'; import { ValuationExplanationHandler } from './application/queries/valuation-explanation/valuation-explanation.handler'; import { ValuationHistoryHandler } from './application/queries/valuation-history/valuation-history.handler'; import { MARKET_INDEX_REPOSITORY } from './domain/repositories/market-index.repository'; import { VALUATION_REPOSITORY } from './domain/repositories/valuation.repository'; import { AVM_SERVICE } from './domain/services/avm-service'; import { NEIGHBORHOOD_SCORE_SERVICE } from './domain/services/neighborhood-score.service'; import { PrismaMarketIndexRepository } from './infrastructure/repositories/prisma-market-index.repository'; import { PrismaValuationRepository } from './infrastructure/repositories/prisma-valuation.repository'; import { AI_SERVICE_CLIENT, AiServiceClient } from './infrastructure/services/ai-service.client'; import { HttpAVMService } from './infrastructure/services/http-avm.service'; import { MarketIndexCronService } from './infrastructure/services/market-index-cron.service'; import { RefreshMaterializedViewCronService, MATVIEW_REFRESH_TOTAL, MATVIEW_REFRESH_DURATION, MATVIEW_REFRESH_ERRORS, } from './infrastructure/services/refresh-materialized-view-cron.service'; import { HttpNeighborhoodScoreService, PrismaNeighborhoodScoreService, } from './infrastructure/services/neighborhood-score.service'; import { PrismaAVMService } from './infrastructure/services/prisma-avm.service'; import { AnalyticsController } from './presentation/controllers/analytics.controller'; import { AvmController } from './presentation/controllers/avm.controller'; const CommandHandlers = [ TrackEventHandler, GenerateReportHandler, UpdateMarketIndexHandler, ]; const QueryHandlers = [ GetMarketReportHandler, GetMarketHistoryHandler, GetHeatmapHandler, GetListingVolumeWardHandler, GetPriceTrendHandler, GetDistrictStatsHandler, GetValuationHandler, PredictValuationHandler, BatchValuationHandler, ValuationHistoryHandler, ValuationComparisonHandler, ValuationExplanationHandler, GetNeighborhoodScoreHandler, GetNearbyPOIsHandler, IndustrialValuationHandler, GetListingAiAdviceHandler, GetProjectAiAdviceHandler, GetMarketSnapshotHandler, GetPriceMoversHandler, GetTrendingAreasHandler, ]; const EventHandlers = [ ListingCreatedModerationHandler, ]; @Module({ imports: [CqrsModule, forwardRef(() => ListingsModule), ProjectsModule], controllers: [AnalyticsController, AvmController], providers: [ // AI service client { provide: AI_SERVICE_CLIENT, useClass: AiServiceClient }, // Repositories { provide: MARKET_INDEX_REPOSITORY, useClass: PrismaMarketIndexRepository }, { provide: VALUATION_REPOSITORY, useClass: PrismaValuationRepository }, // AVM: HttpAVMService calls Python AI first, falls back to PrismaAVMService PrismaAVMService, { provide: AVM_SERVICE, useClass: HttpAVMService }, // Neighborhood scoring: HTTP proxy → Python AI service, falls back to Prisma scoring PrismaNeighborhoodScoreService, { provide: NEIGHBORHOOD_SCORE_SERVICE, useClass: HttpNeighborhoodScoreService }, // Cron MarketIndexCronService, RefreshMaterializedViewCronService, // Materialized-view refresh metrics makeCounterProvider({ name: MATVIEW_REFRESH_TOTAL, help: 'Total materialized-view refresh attempts', labelNames: ['view', 'status'], }), makeHistogramProvider({ name: MATVIEW_REFRESH_DURATION, help: 'Duration of materialized-view refresh in seconds', labelNames: ['view'], buckets: [1, 5, 15, 30, 60, 120, 300], }), makeCounterProvider({ name: MATVIEW_REFRESH_ERRORS, help: 'Total materialized-view refresh errors', labelNames: ['view', 'reason'], }), // CQRS ...CommandHandlers, ...QueryHandlers, ...EventHandlers, ], exports: [MARKET_INDEX_REPOSITORY, VALUATION_REPOSITORY, AVM_SERVICE, AI_SERVICE_CLIENT], }) export class AnalyticsModule {}