package service import ( "bugulma/backend/internal/domain" "bugulma/backend/internal/localization" "context" "fmt" ) // StatsService provides translation statistics and reporting functionality type StatsService struct { locRepo domain.LocalizationRepository locService domain.LocalizationService entityLoader *EntityLoaderService } // NewStatsService creates a new stats service func NewStatsService( locRepo domain.LocalizationRepository, locService domain.LocalizationService, entityLoader *EntityLoaderService, ) *StatsService { return &StatsService{ locRepo: locRepo, locService: locService, entityLoader: entityLoader, } } // GetTranslationStats returns comprehensive translation statistics func (s *StatsService) GetTranslationStats(ctx context.Context, entityTypeFilter string) (*domain.TranslationStats, error) { entityTypes := localization.GetEntityTypesForFilter(entityTypeFilter) stats := &domain.TranslationStats{ EntityStats: make(map[string]*domain.EntityTranslationStats), } totalEntities := 0 totalFields := 0 totalTranslations := 0 for _, entityType := range entityTypes { entityStats, err := s.getEntityTranslationStats(ctx, entityType) if err != nil { continue } stats.EntityStats[entityType] = entityStats totalEntities += entityStats.TotalEntities totalFields += entityStats.TotalFields totalTranslations += entityStats.TotalTranslations } stats.TotalEntities = totalEntities stats.TotalFields = totalFields stats.TotalTranslations = totalTranslations return stats, nil } // GetUntranslatedFields returns fields that need translation func (s *StatsService) GetUntranslatedFields(ctx context.Context, entityTypeFilter, targetLocale string) ([]UntranslatedField, error) { entityTypes := localization.GetEntityTypesForFilter(entityTypeFilter) var results []UntranslatedField for _, entityType := range entityTypes { entities, err := s.entityLoader.LoadEntities(entityType, localization.LoadOptions{}) if err != nil { continue } fields := localization.GetFieldsForEntityType(entityType) for _, entity := range entities { entityID := s.entityLoader.GetEntityID(entity) for _, field := range fields { russianText := s.entityLoader.GetRussianContent(entity, field) if russianText == "" { continue } // Check if translation exists existing, err := s.locService.GetLocalizedValue(entityType, entityID, field, targetLocale) if err != nil || existing == "" { results = append(results, UntranslatedField{ EntityType: entityType, EntityID: entityID, Field: field, RussianText: russianText, Locale: targetLocale, }) } } } } return results, nil } // UntranslatedField represents a field that needs translation type UntranslatedField struct { EntityType string EntityID string Field string RussianText string Locale string } // GetCoverageReport returns a detailed coverage report func (s *StatsService) GetCoverageReport(ctx context.Context, entityTypeFilter string) (*CoverageReport, error) { stats, err := s.GetTranslationStats(ctx, entityTypeFilter) if err != nil { return nil, err } report := &CoverageReport{ TotalEntities: stats.TotalEntities, TotalFields: stats.TotalFields, TotalTranslations: stats.TotalTranslations, EntityReports: make(map[string]*EntityCoverageReport), } // Calculate overall coverage if stats.TotalFields > 0 { report.OverallCoverage = float64(stats.TotalTranslations) / float64(stats.TotalFields) * 100 } // Generate entity-specific reports for entityType, entityStats := range stats.EntityStats { entityReport := &EntityCoverageReport{ EntityType: entityType, TotalEntities: entityStats.TotalEntities, TotalFields: entityStats.TotalFields, RussianCount: entityStats.RussianCount, EnglishCount: entityStats.EnglishCount, TatarCount: entityStats.TatarCount, } if entityStats.TotalFields > 0 { entityReport.FieldCoverage = float64(entityStats.TotalTranslations) / float64(entityStats.TotalFields) * 100 } report.EntityReports[entityType] = entityReport } return report, nil } // CoverageReport represents a comprehensive coverage report type CoverageReport struct { TotalEntities int TotalFields int TotalTranslations int OverallCoverage float64 EntityReports map[string]*EntityCoverageReport } // EntityCoverageReport represents coverage for a specific entity type type EntityCoverageReport struct { EntityType string TotalEntities int TotalFields int RussianCount int EnglishCount int TatarCount int FieldCoverage float64 } // GetTranslationSummary returns a formatted summary string func (s *StatsService) GetTranslationSummary(ctx context.Context, entityTypeFilter string) (string, error) { stats, err := s.GetTranslationStats(ctx, entityTypeFilter) if err != nil { return "", err } summary := fmt.Sprintf("📊 Translation Statistics for: %s\n\n", entityTypeFilter) entityTypes := localization.GetEntityTypesForFilter(entityTypeFilter) for _, entityType := range entityTypes { if entityStats, exists := stats.EntityStats[entityType]; exists { summary += fmt.Sprintf("📋 %s Statistics:\n", entityType) summary += fmt.Sprintf(" 📊 Entities: %d\n", entityStats.TotalEntities) summary += fmt.Sprintf(" 📝 Fields per entity: %d\n", entityStats.TotalFields/entityStats.TotalEntities) summary += fmt.Sprintf(" 🌍 Complete Translations: %d/%d (%.1f%% coverage)\n", entityStats.TotalTranslations, entityStats.TotalFields, float64(entityStats.TotalTranslations)/float64(entityStats.TotalFields)*100) if entityStats.TotalTranslations > 0 { summary += fmt.Sprintf(" 📈 By locale: ru:%d en:%d tt:%d\n", entityStats.RussianCount, entityStats.EnglishCount, entityStats.TatarCount) } summary += "\n" } } if len(entityTypes) > 1 { summary += fmt.Sprintf("📈 Overall Statistics:\n") summary += fmt.Sprintf(" 📊 Total Entities: %d\n", stats.TotalEntities) summary += fmt.Sprintf(" 📝 Total Fields: %d\n", stats.TotalFields) summary += fmt.Sprintf(" 🌍 Complete Entity Translations: %d\n", stats.TotalTranslations) if stats.TotalFields > 0 { summary += fmt.Sprintf(" ✅ Overall Coverage: %.1f%%\n", float64(stats.TotalTranslations)/float64(stats.TotalFields)*100) } } return summary, nil } // getEntityTranslationStats gets stats for a specific entity type func (s *StatsService) getEntityTranslationStats(ctx context.Context, entityType string) (*domain.EntityTranslationStats, error) { entities, err := s.entityLoader.LoadEntities(entityType, localization.LoadOptions{}) if err != nil { return nil, err } fields := localization.GetFieldsForEntityType(entityType) totalFields := len(entities) * len(fields) // Count translations by locale ruCount := 0 enCount := 0 ttCount := 0 for _, entity := range entities { entityID := s.entityLoader.GetEntityID(entity) for _, field := range fields { if ruVal, _ := s.locService.GetLocalizedValue(entityType, entityID, field, "ru"); ruVal != "" { ruCount++ } if enVal, _ := s.locService.GetLocalizedValue(entityType, entityID, field, "en"); enVal != "" { enCount++ } if ttVal, _ := s.locService.GetLocalizedValue(entityType, entityID, field, "tt"); ttVal != "" { ttCount++ } } } return &domain.EntityTranslationStats{ EntityType: entityType, TotalEntities: len(entities), TotalFields: totalFields, RussianCount: ruCount, EnglishCount: enCount, TatarCount: ttCount, TotalTranslations: ruCount + enCount + ttCount, }, nil }