turash/bugulma/backend/internal/service/stats_service.go

253 lines
7.6 KiB
Go

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
}