turash/models/cli/commands.go
Damir Mukimov 4a2fda96cd
Initial commit: Repository setup with .gitignore, golangci-lint v2.6.0, and code quality checks
- Initialize git repository
- Add comprehensive .gitignore for Go projects
- Install golangci-lint v2.6.0 (latest v2) globally
- Configure .golangci.yml with appropriate linters and formatters
- Fix all formatting issues (gofmt)
- Fix all errcheck issues (unchecked errors)
- Adjust complexity threshold for validation functions
- All checks passing: build, test, vet, lint
2025-11-01 07:36:22 +01:00

414 lines
15 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(),
)
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(&paramsFile, "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(&paramsFile, "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(&paramsFile, "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(&paramsFile, "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
}