package cli import ( "fmt" "github.com/damirmukimov/city_resource_graph/models/match" "github.com/damirmukimov/city_resource_graph/models/scenarios" "github.com/damirmukimov/city_resource_graph/models/transport" "github.com/spf13/cobra" ) // NewRootCmd creates the root command func NewRootCmd() *cobra.Command { rootCmd := &cobra.Command{ Use: "models", Short: "Turash Mathematical Model CLI", Long: `A production-ready Go implementation of the Turash mathematical model for consistent financial and environmental impact calculations. This tool provides command-line access to run calculations, validate parameters, and generate reports for the Turash project.`, } // Add subcommands rootCmd.AddCommand( NewCalculateCmd(), NewValidateCmd(), NewSummaryCmd(), NewScenariosCmd(), NewExchangeCmd(), NewMatchCmd(), NewBudgetCmd(), ) return rootCmd } // NewCalculateCmd creates the calculate command func NewCalculateCmd() *cobra.Command { var ( paramsFile string format string ) cmd := &cobra.Command{ Use: "calculate [params.yaml]", Short: "Run the complete mathematical model", Long: `Calculate all metrics across all specified years using the provided parameters. Outputs results in JSON format by default, or as a formatted summary.`, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 0 { paramsFile = args[0] } calculator := NewCalculator(format) return calculator.CalculateAndFormat(paramsFile) }, } cmd.Flags().StringVarP(¶msFile, "file", "f", "params.yaml", "Parameters file path") cmd.Flags().StringVarP(&format, "format", "o", "json", "Output format (json, summary)") return cmd } // NewValidateCmd creates the validate command func NewValidateCmd() *cobra.Command { var paramsFile string cmd := &cobra.Command{ Use: "validate [params.yaml]", Short: "Validate parameter file", Long: `Validate the parameter file without running calculations. Checks for missing values, invalid ranges, and logical inconsistencies.`, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 0 { paramsFile = args[0] } calculator := NewCalculator("summary") return calculator.ValidateOnly(paramsFile) }, } cmd.Flags().StringVarP(¶msFile, "file", "f", "params.yaml", "Parameters file path") return cmd } // NewSummaryCmd creates the summary command func NewSummaryCmd() *cobra.Command { var paramsFile string cmd := &cobra.Command{ Use: "summary [params.yaml]", Short: "Print summary statistics", Long: `Calculate the model and display a human-readable summary of key metrics including revenue, costs, profitability, and environmental impact.`, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 0 { paramsFile = args[0] } calculator := NewCalculator("summary") return calculator.SummaryOnly(paramsFile) }, } cmd.Flags().StringVarP(¶msFile, "file", "f", "params.yaml", "Parameters file path") return cmd } // NewScenariosCmd creates the scenarios command func NewScenariosCmd() *cobra.Command { var paramsFile string cmd := &cobra.Command{ Use: "scenarios [params.yaml]", Short: "Run sensitivity analysis with multiple scenarios", Long: `Run sensitivity analysis by varying key parameters (adoption rates, pricing, costs) to show best-case, worst-case, and base-case scenarios for funding applications.`, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 0 { paramsFile = args[0] } calculator := NewCalculator("summary") return runScenarios(calculator, paramsFile) }, } cmd.Flags().StringVarP(¶msFile, "file", "f", "params.yaml", "Parameters file path") return cmd } // NewExchangeCmd creates the exchange command for calculating symbiosis exchange costs func NewExchangeCmd() *cobra.Command { var ( symbiosisType string value float64 volume float64 distance float64 complexity string riskLevel string ) cmd := &cobra.Command{ Use: "exchange", Short: "Calculate costs for industrial symbiosis exchanges", Long: `Calculate the total costs for enabling different types of industrial symbiosis exchanges, including physical transport, regulatory compliance, risk mitigation, and platform fees.`, RunE: func(cmd *cobra.Command, args []string) error { return runExchangeCalculation(symbiosisType, value, volume, distance, complexity, riskLevel) }, } cmd.Flags().StringVar(&symbiosisType, "type", "waste_to_resource", "Type of symbiosis exchange") cmd.Flags().Float64Var(&value, "value", 100000, "Annual exchange value (€)") cmd.Flags().Float64Var(&volume, "volume", 1000, "Exchange volume (units)") cmd.Flags().Float64Var(&distance, "distance", 5.0, "Distance between parties (km)") cmd.Flags().StringVar(&complexity, "complexity", "medium", "Implementation complexity (low/medium/high)") cmd.Flags().StringVar(&riskLevel, "risk", "medium", "Risk level (low/medium/high)") return cmd } // NewMatchCmd creates the match command for calculating individual match economics func NewMatchCmd() *cobra.Command { var ( sourceID string sourceType string sourceDirection string sourceQuantity float64 sourceUnit string sourceCostPerUnit float64 targetID string targetType string targetDirection string targetQuantity float64 targetUnit string targetCostPerUnit float64 distance float64 initialInvestment float64 symbiosisType string complexity string riskLevel string annualQuantity float64 unitValue float64 co2ReductionFactor float64 ) cmd := &cobra.Command{ Use: "match", Short: "Calculate economics for individual business-to-business matches", Long: `Calculate NPV, IRR, payback period, and other economic metrics for individual resource flow matches between businesses, including transportation costs and CO2 reductions.`, RunE: func(cmd *cobra.Command, args []string) error { return runMatchEconomics(sourceID, sourceType, sourceDirection, sourceQuantity, sourceUnit, sourceCostPerUnit, targetID, targetType, targetDirection, targetQuantity, targetUnit, targetCostPerUnit, distance, initialInvestment, symbiosisType, complexity, riskLevel, annualQuantity, unitValue, co2ReductionFactor) }, } cmd.Flags().StringVar(&sourceID, "source-id", "", "Source resource flow ID") cmd.Flags().StringVar(&sourceType, "source-type", "", "Source resource type") cmd.Flags().StringVar(&sourceDirection, "source-dir", "output", "Source direction (input/output)") cmd.Flags().Float64Var(&sourceQuantity, "source-qty", 0, "Source quantity") cmd.Flags().StringVar(&sourceUnit, "source-unit", "", "Source unit of measurement") cmd.Flags().Float64Var(&sourceCostPerUnit, "source-cost", 0, "Source cost per unit (€)") cmd.Flags().StringVar(&targetID, "target-id", "", "Target resource flow ID") cmd.Flags().StringVar(&targetType, "target-type", "", "Target resource type") cmd.Flags().StringVar(&targetDirection, "target-dir", "input", "Target direction (input/output)") cmd.Flags().Float64Var(&targetQuantity, "target-qty", 0, "Target quantity") cmd.Flags().StringVar(&targetUnit, "target-unit", "", "Target unit of measurement") cmd.Flags().Float64Var(&targetCostPerUnit, "target-cost", 0, "Target cost per unit (€)") cmd.Flags().Float64Var(&distance, "distance", 0, "Distance between businesses (km)") cmd.Flags().Float64Var(&initialInvestment, "investment", 0, "Initial investment cost (€)") cmd.Flags().StringVar(&symbiosisType, "type", "waste_to_resource", "Symbiosis type") cmd.Flags().StringVar(&complexity, "complexity", "medium", "Implementation complexity") cmd.Flags().StringVar(&riskLevel, "risk", "medium", "Risk level") cmd.Flags().Float64Var(&annualQuantity, "annual-qty", 0, "Annual exchange quantity") cmd.Flags().Float64Var(&unitValue, "unit-value", 0, "Exchange value per unit (€)") cmd.Flags().Float64Var(&co2ReductionFactor, "co2-factor", 0, "CO2 reduction tonnes per unit") if err := cmd.MarkFlagRequired("source-id"); err != nil { panic(fmt.Sprintf("failed to mark source-id as required: %v", err)) } if err := cmd.MarkFlagRequired("target-id"); err != nil { panic(fmt.Sprintf("failed to mark target-id as required: %v", err)) } if err := cmd.MarkFlagRequired("annual-qty"); err != nil { panic(fmt.Sprintf("failed to mark annual-qty as required: %v", err)) } if err := cmd.MarkFlagRequired("unit-value"); err != nil { panic(fmt.Sprintf("failed to mark unit-value as required: %v", err)) } return cmd } // runExchangeCalculation executes exchange cost calculation func runExchangeCalculation(symbiosisType string, value, volume, distance float64, complexity, riskLevel string) error { params := transport.ExchangeParams{ SymbiosisType: transport.SymbiosisType(symbiosisType), Value: value, Volume: volume, DistanceKm: distance, Complexity: complexity, RiskLevel: riskLevel, } cost, err := transport.CalculateExchangeCost(params) if err != nil { return fmt.Errorf("failed to calculate exchange cost: %w", err) } // Display results fmt.Println("Industrial Symbiosis Exchange Cost Analysis") fmt.Println("==========================================") fmt.Printf("Exchange Type: %s\n", symbiosisType) fmt.Printf("Annual Value: €%.0f\n", value) fmt.Printf("Volume: %.0f units\n", volume) fmt.Printf("Distance: %.1f km\n", distance) fmt.Printf("Complexity: %s\n", complexity) fmt.Printf("Risk Level: %s\n", riskLevel) fmt.Println() fmt.Println("Cost Breakdown:") fmt.Printf(" Capital Cost: €%.0f\n", cost.CapitalCost) fmt.Printf(" Annual Operating Cost: €%.0f\n", cost.AnnualOpexCost) fmt.Printf(" Platform Fee: €%.0f\n", cost.PlatformFee) fmt.Printf(" Regulatory Cost: €%.0f\n", cost.RegulatoryCost) fmt.Printf(" Risk Mitigation Cost: €%.0f\n", cost.RiskMitigationCost) fmt.Println() fmt.Printf("Total Annual Cost: €%.0f\n", cost.TotalAnnualCost) fmt.Printf("Cost Per Unit: €%.2f\n", cost.CostPerUnit) fmt.Printf("Feasibility: %s\n", cost.Feasibility) if cost.ROIYears > 0 && cost.ROIYears < 999 { fmt.Printf("Payback Period: %.1f years\n", cost.ROIYears) } else { fmt.Println("Payback Period: Never (costs exceed benefits)") } return nil } // runMatchEconomics executes individual match economic calculations func runMatchEconomics(sourceID, sourceType, sourceDirection string, sourceQuantity float64, sourceUnit string, sourceCostPerUnit float64, targetID, targetType, targetDirection string, targetQuantity float64, targetUnit string, targetCostPerUnit, distance, initialInvestment float64, symbiosisType, complexity, riskLevel string, annualQuantity, unitValue, co2ReductionFactor float64) error { params := match.MatchEconomicsParams{ SourceResource: match.ResourceFlowSummary{ ID: sourceID, Type: sourceType, Direction: sourceDirection, Quantity: sourceQuantity, Unit: sourceUnit, CostPerUnit: sourceCostPerUnit, }, TargetResource: match.ResourceFlowSummary{ ID: targetID, Type: targetType, Direction: targetDirection, Quantity: targetQuantity, Unit: targetUnit, CostPerUnit: targetCostPerUnit, }, DistanceKm: distance, InitialInvestment: initialInvestment, SymbiosisType: transport.SymbiosisType(symbiosisType), Complexity: complexity, RiskLevel: riskLevel, AnnualQuantity: annualQuantity, UnitValue: unitValue, CO2ReductionFactor: co2ReductionFactor, } assumptions := match.DefaultCalculationAssumptions() result, err := match.CalculateMatchEconomics(params, assumptions) if err != nil { return fmt.Errorf("failed to calculate match economics: %w", err) } // Display results fmt.Println("Individual Match Economic Analysis") fmt.Println("==================================") fmt.Printf("Match ID: %s\n", result.MatchID) fmt.Printf("Source: %s (%s %s)\n", result.SourceResource.ID, result.SourceResource.Type, result.SourceResource.Direction) fmt.Printf("Target: %s (%s %s)\n", result.TargetResource.ID, result.TargetResource.Type, result.TargetResource.Direction) fmt.Printf("Distance: %.1f km\n", distance) fmt.Printf("Annual Exchange: %.0f units @ €%.2f/unit\n", annualQuantity, unitValue) fmt.Printf("Initial Investment: €%.0f\n", initialInvestment) fmt.Printf("Symbiosis Type: %s\n", symbiosisType) fmt.Println() fmt.Println("Economic Results:") fmt.Printf(" Annual Savings: €%.0f\n", result.Calculations.AnnualSavings) fmt.Printf(" Payback Period: %.1f years\n", result.Calculations.PaybackPeriodYears) fmt.Printf(" NPV (10 years): €%.0f\n", result.Calculations.NPV10Years) fmt.Printf(" IRR: %.1f%%\n", result.Calculations.IRRPercent) fmt.Println() fmt.Println("Transportation & Impact:") fmt.Printf(" Annual Transport Cost: €%.0f\n", result.Calculations.TransportationCosts.AnnualCost) fmt.Printf(" Transport Method: %s\n", result.Calculations.TransportationCosts.Method) fmt.Printf(" Feasibility Score: %.1f\n", result.Calculations.TransportationCosts.Feasibility) fmt.Printf(" CO2 Reduction: %.1f tonnes/year\n", result.Calculations.CO2ReductionTonnes) fmt.Println() fmt.Println("Requirements & Complexity:") fmt.Printf(" Implementation Complexity: %s\n", result.Calculations.ImplementationComplexity) fmt.Printf(" Regulatory Requirements: %v\n", result.Calculations.RegulatoryRequirements) if result.Calculations.PaybackPeriodYears > 10 { fmt.Println("\n⚠️ WARNING: Match does not pay back within 10 years") } else { fmt.Println("\n✅ Match appears economically viable") } return nil } // runScenarios executes scenario analysis func runScenarios(calc *Calculator, paramsFile string) error { // Load and validate parameters p, err := calc.LoadAndValidateParams(paramsFile) if err != nil { return err } // Run sensitivity analysis results := scenarios.RunSensitivityAnalysis(p) // Display results fmt.Println("=== Scenario Analysis ===") fmt.Println("Sensitivity analysis across 5 scenarios varying adoption, pricing, and costs") fmt.Println() fmt.Printf("%-15s %-10s %-12s %-12s %-10s %-8s\n", "Scenario", "Revenue", "Costs", "Profit", "NPV", "IRR") fmt.Println("-----------------------------------------------------------------------") for _, result := range results { fmt.Printf("%-15s €%-9.0f €%-11.0f €%-11.0f €%-9.0f %.1f%%\n", result.Name, result.KeyMetrics.TotalRevenue/1000, // Show in thousands result.KeyMetrics.TotalCosts/1000, result.KeyMetrics.TotalProfit/1000, result.KeyMetrics.NPV/1000, result.KeyMetrics.IRR) } fmt.Println() fmt.Println("Detailed scenario descriptions:") for _, result := range results { fmt.Printf("\n%s: %s\n", result.Name, result.Description) fmt.Printf(" Year 3: %d customers, €%.0f revenue\n", result.KeyMetrics.Year3Customers, result.KeyMetrics.Year3Revenue) } return nil }