package market import ( "fmt" "github.com/damirmukimov/city_resource_graph/models/params" ) // MarketSize represents market size metrics. // These are contextual constants for documentation, not drivers of calculations. type MarketSize struct { TAM float64 `json:"tam"` // Total Addressable Market (EUR) SAM float64 `json:"sam"` // Serviceable Addressable Market (EUR) SOM float64 `json:"som"` // Serviceable Obtainable Market (EUR) } // Constants represents market size constants for EU funding context. // These are not computed - they represent the problem space. type Constants struct { TAM float64 `json:"tam"` // €500B - EU industrial resource flows AddressableDigital float64 `json:"addressable_digital"` // €2-5B - Small/medium cities PilotCityEconomicBenefit float64 `json:"pilot_city_economic_benefit"` // €3-5M/year per city ScalabilityPotential float64 `json:"scalability_potential"` // €300-500M/year if 100 cities EUIndustrialFacilities int `json:"eu_industrial_facilities"` // 2.1M facilities EnergyWastePotential float64 `json:"energy_waste_potential"` // 45% recoverable ResourceCostReduction float64 `json:"resource_cost_reduction"` // 20-30% via symbiosis } // ErrMissingMarketData indicates required market data is missing from params. type ErrMissingMarketData struct { Field string } func (e ErrMissingMarketData) Error() string { return fmt.Sprintf("missing market data: %s", e.Field) } // GetMarketSize returns market size constants from parameters. // Returns error if required fields are missing. func GetMarketSize(p *params.Params) (Constants, error) { mp := p.Market if mp.TAM == 0 { return Constants{}, ErrMissingMarketData{Field: "market.tam"} } return Constants{ TAM: mp.TAM, AddressableDigital: mp.AddressableDigital, PilotCityEconomicBenefit: mp.PilotCityEconomicBenefit, ScalabilityPotential: mp.ScalabilityPotential, EUIndustrialFacilities: mp.EUIndustrialFacilities, EnergyWastePotential: mp.EnergyWastePotential, ResourceCostReduction: mp.ResourceCostReduction, }, nil } // CalculateSAM computes the Serviceable Addressable Market. // Formula: SAM = TAM × Viable Exchange Rate × Platform Capture Rate × 2 // (×2 accounts for additional procurement optimization) // Returns error if required fields are missing. func CalculateSAM(p *params.Params) (float64, error) { mp := p.Market if mp.TAM == 0 { return 0, ErrMissingMarketData{Field: "market.tam"} } if mp.ViableExchangeRate == 0 { return 0, ErrMissingMarketData{Field: "market.viable_exchange_rate"} } if mp.PlatformCaptureRate == 0 { return 0, ErrMissingMarketData{Field: "market.platform_capture_rate"} } return mp.TAM * mp.ViableExchangeRate * mp.PlatformCaptureRate * 2.0, nil } // GetSOM returns the Serviceable Obtainable Market for a given year. // This represents post-grant commercial scaling potential. // Returns error if SOM data for the year is missing. func GetSOM(year int, p *params.Params) (float64, error) { som := p.Market.SOM.GetYear(year) if som == 0 { return 0, ErrMissingMarketData{Field: fmt.Sprintf("market.som.%d", year)} } return som, nil }