turash/bugulma/backend/internal/service/economic_service.go
Damir Mukimov 000eab4740
Major repository reorganization and missing backend endpoints implementation
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools

Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
  * GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
  * GET /api/v1/users/me/organizations - User organizations
  * POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue

API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules

Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
2025-11-25 06:01:16 +01:00

193 lines
6.3 KiB
Go

package service
import (
"context"
"encoding/json"
"fmt"
"bugulma/backend/internal/domain"
"bugulma/backend/internal/financial"
)
// EconomicService provides high-level economic analysis services
type EconomicService struct {
calculator financial.Calculator
}
// NewEconomicService creates a new economic service
func NewEconomicService(calculator financial.Calculator) *EconomicService {
return &EconomicService{
calculator: calculator,
}
}
// AnalyzeMatch performs comprehensive economic analysis for a resource flow match
func (es *EconomicService) AnalyzeMatch(
ctx context.Context,
source, target *domain.ResourceFlow,
distanceKm float64,
) (*financial.EconomicAnalysis, error) {
// Extract resource flow data
data, err := es.extractResourceFlowData(source, target, distanceKm)
if err != nil {
return nil, fmt.Errorf("failed to extract resource flow data: %w", err)
}
// Get default assumptions (could be made configurable per organization/client)
assumptions := es.getDefaultAssumptions()
// Perform basic economic analysis first
analysis, err := es.calculator.Calculate(financial.AnalysisTypeBasic, data, assumptions)
if err != nil {
return nil, fmt.Errorf("failed to perform economic analysis: %w", err)
}
// Type assert to get the EconomicAnalysis
basicAnalysis, ok := analysis.(*financial.EconomicAnalysis)
if !ok {
return nil, fmt.Errorf("unexpected analysis type returned for basic analysis: %T", analysis)
}
return basicAnalysis, nil
}
// AnalyzeAdvancedMatch performs comprehensive advanced economic analysis for a resource flow match
func (es *EconomicService) AnalyzeAdvancedMatch(
ctx context.Context,
source, target *domain.ResourceFlow,
distanceKm float64,
) (*financial.AdvancedEconomicAnalysis, error) {
// Extract resource flow data
data, err := es.extractResourceFlowData(source, target, distanceKm)
if err != nil {
return nil, fmt.Errorf("failed to extract resource flow data: %w", err)
}
// Get default assumptions (could be made configurable per organization/client)
assumptions := es.getDefaultAssumptions()
// Perform advanced economic analysis
analysis, err := es.calculator.Calculate(financial.AnalysisTypeAdvanced, data, assumptions)
if err != nil {
return nil, fmt.Errorf("failed to perform advanced economic analysis: %w", err)
}
// Type assert to get the AdvancedEconomicAnalysis
advancedAnalysis, ok := analysis.(*financial.AdvancedEconomicAnalysis)
if !ok {
return nil, fmt.Errorf("unexpected analysis type returned for advanced analysis")
}
return advancedAnalysis, nil
}
// AnalyzeMatchWithSensitivity performs economic analysis with sensitivity analysis
func (es *EconomicService) AnalyzeMatchWithSensitivity(
ctx context.Context,
source, target *domain.ResourceFlow,
distanceKm float64,
) (*financial.EconomicAnalysis, error) {
data, err := es.extractResourceFlowData(source, target, distanceKm)
if err != nil {
return nil, fmt.Errorf("failed to extract resource flow data: %w", err)
}
assumptions := es.getDefaultAssumptions()
analysis, err := es.calculator.Calculate(financial.AnalysisTypeSensitivity, data, assumptions)
if err != nil {
return nil, fmt.Errorf("failed to perform sensitivity analysis: %w", err)
}
// Type assert to get the EconomicAnalysis
sensitivityAnalysis, ok := analysis.(*financial.EconomicAnalysis)
if !ok {
return nil, fmt.Errorf("unexpected analysis type returned for sensitivity analysis: %T", analysis)
}
return sensitivityAnalysis, nil
}
// extractResourceFlowData extracts financial data from resource flow entities
func (es *EconomicService) extractResourceFlowData(
source, target *domain.ResourceFlow,
distanceKm float64,
) (*financial.ResourceFlowData, error) {
var sourceEcon, targetEcon domain.EconomicData
if err := json.Unmarshal(source.EconomicData, &sourceEcon); err != nil {
return nil, fmt.Errorf("failed to unmarshal source economic data: %w", err)
}
if err := json.Unmarshal(target.EconomicData, &targetEcon); err != nil {
return nil, fmt.Errorf("failed to unmarshal target economic data: %w", err)
}
var sourceQty domain.Quantity
if err := json.Unmarshal(source.Quantity, &sourceQty); err != nil {
return nil, fmt.Errorf("failed to unmarshal source quantity: %w", err)
}
// Convert to annual volume
annualVolume := es.calculateAnnualVolume(sourceQty.Amount, sourceQty.TemporalUnit)
return &financial.ResourceFlowData{
ResourceType: string(source.Type),
DistanceKm: distanceKm,
AnnualVolume: annualVolume,
CostIn: targetEcon.CostIn, // Buyer's cost
CostOut: sourceEcon.CostOut, // Seller's cost
TemporalUnit: string(sourceQty.TemporalUnit),
}, nil
}
// calculateAnnualVolume converts temporal unit to annual volume
func (es *EconomicService) calculateAnnualVolume(amount float64, unit domain.TemporalUnit) float64 {
multipliers := map[domain.TemporalUnit]float64{
domain.UnitPerSecond: 31536000, // 365 * 24 * 3600
domain.UnitPerMinute: 525600, // 365 * 24 * 60
domain.UnitPerHour: 8760, // 365 * 24
domain.UnitPerDay: 365,
domain.UnitPerWeek: 52,
domain.UnitPerMonth: 12,
domain.UnitPerYear: 1,
domain.UnitContinuous: 1, // Already annual
}
multiplier, exists := multipliers[unit]
if !exists {
multiplier = 1 // Default to annual
}
return amount * multiplier
}
// getDefaultAssumptions returns default economic assumptions
func (es *EconomicService) getDefaultAssumptions() *financial.EconomicAssumptions {
return &financial.EconomicAssumptions{
DiscountRate: 0.08, // 8%
ProjectLifeYears: 10,
OperatingHoursYear: 8760, // 24/7 operation
MaintenanceCostFactor: 0.02, // 2% of CAPEX
EnergyCostInflation: 0.03, // 3% inflation
InsuranceCostPct: 0.005, // 0.5% of CAPEX
RegulatoryCostBuffer: 0.1, // 10% buffer
ContingencyPct: 0.15, // 15% contingency
}
}
// GetViabilitySummary provides a high-level viability assessment
func (es *EconomicService) GetViabilitySummary(analysis *financial.EconomicAnalysis) string {
if analysis.IsViable && analysis.ConfidenceScore > 0.8 {
return "Highly viable - strong economic case with low risk"
} else if analysis.IsViable && analysis.ConfidenceScore > 0.6 {
return "Viable - positive economics with moderate confidence"
} else if analysis.IsViable {
return "Marginally viable - meets basic criteria but high uncertainty"
} else {
return "Not viable - negative economic indicators"
}
}