mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
- 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
414 lines
15 KiB
Go
414 lines
15 KiB
Go
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(¶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
|
||
}
|