package graph import ( "context" "fmt" "bugulma/backend/internal/domain" "github.com/neo4j/neo4j-go-driver/v5/neo4j" ) // PathFinder handles path finding operations type PathFinder struct { sessionManager *SessionManager queryTemplates *QueryTemplates nodeConverter *NodeConverter config *Config } // NewPathFinder creates a new path finder instance func NewPathFinder(sessionManager *SessionManager, queryTemplates *QueryTemplates, nodeConverter *NodeConverter, config *Config) *PathFinder { return &PathFinder{ sessionManager: sessionManager, queryTemplates: queryTemplates, nodeConverter: nodeConverter, config: config, } } // FindOptimalResourcePaths finds the most cost-effective paths for resource flows func (pf *PathFinder) FindOptimalResourcePaths( ctx context.Context, sourceOrgID string, targetOrgID string, resourceType domain.ResourceType, maxHops int, ) ([]*ResourceChain, error) { if maxHops > pf.config.MaxPathHops { maxHops = pf.config.MaxPathHops } result, err := pf.sessionManager.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) { cypher := pf.queryTemplates.FindOptimalPathQuery(maxHops) queryResult, err := tx.Run(ctx, cypher, map[string]interface{}{ "source_org": sourceOrgID, "target_org": targetOrgID, "resource_type": string(resourceType), "max_distance": pf.config.MaxPathDistanceKm, "limit": pf.config.DefaultResultLimit, }) if err != nil { return nil, fmt.Errorf("failed to execute optimal path query: %w", err) } var chains []*ResourceChain for queryResult.Next(ctx) { record := queryResult.Record() pathNodes, _ := record.Get("pathNodes") pathRels, _ := record.Get("pathRels") totalCost, _ := record.Get("total_cost") totalDistance, _ := record.Get("total_distance") pathLength, _ := record.Get("path_length") chain := &ResourceChain{ ChainID: fmt.Sprintf("optimal_%s_%s", sourceOrgID, targetOrgID), ResourceType: resourceType, TotalDistanceKm: getFloat64Value(totalDistance), TotalCost: getFloat64Value(totalCost), EnvironmentalImpact: getFloat64Value(totalCost) * 0.2, Circular: false, Steps: []ResourceChainStep{}, } // Build steps from path if nodesSlice, ok := pathNodes.([]interface{}); ok { if relsSlice, ok := pathRels.([]interface{}); ok { stepNum := 1 for i := 0; i < len(nodesSlice)-1 && i < len(relsSlice); i++ { if node, ok := nodesSlice[i].(neo4j.Node); ok { if len(node.Labels) > 0 && node.Labels[0] == "ResourceFlow" { step := ResourceChainStep{ StepNumber: stepNum, SourceFlowID: getStringProp(node.Props, "id"), DistanceKm: getFloat64Value(totalDistance) / float64(getIntValue(pathLength)), } chain.Steps = append(chain.Steps, step) stepNum++ } } } } } chains = append(chains, chain) } if err := queryResult.Err(); err != nil { return nil, fmt.Errorf("failed to iterate path results: %w", err) } return chains, nil }) if err != nil { return nil, err } return result.([]*ResourceChain), nil }