package graph import ( "context" "fmt" "bugulma/backend/internal/domain" "github.com/neo4j/neo4j-go-driver/v5/neo4j" ) // Traversal handles graph traversal operations type Traversal struct { sessionManager *SessionManager queryTemplates *QueryTemplates nodeConverter *NodeConverter config *Config } // NewTraversal creates a new traversal instance func NewTraversal(sessionManager *SessionManager, queryTemplates *QueryTemplates, nodeConverter *NodeConverter, config *Config) *Traversal { return &Traversal{ sessionManager: sessionManager, queryTemplates: queryTemplates, nodeConverter: nodeConverter, config: config, } } // FindResourceChains finds chains of resource flows from waste to reuse func (t *Traversal) FindResourceChains( ctx context.Context, startResourceType domain.ResourceType, maxChainLength int, minValue float64, ) ([]*ResourceChain, error) { if maxChainLength > t.config.MaxChainLength { maxChainLength = t.config.MaxChainLength } if minValue < t.config.MinChainValue { minValue = t.config.MinChainValue } result, err := t.sessionManager.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) { cypher := t.queryTemplates.FindResourceChainsQuery(maxChainLength) queryResult, err := tx.Run(ctx, cypher, map[string]interface{}{ "resource_type": string(startResourceType), "min_value": minValue, "max_distance": t.config.MaxChainDistanceKm, "limit": t.config.DefaultResultLimit, }) if err != nil { return nil, fmt.Errorf("failed to execute resource chain query: %w", err) } var chains []*ResourceChain for queryResult.Next(ctx) { record := queryResult.Record() pathNodes, _ := record.Get("pathNodes") pathRels, _ := record.Get("pathRels") totalDistance, _ := record.Get("total_distance") totalValue, _ := record.Get("total_value") startOrgID, _ := record.Get("start_org_id") startOrgName, _ := record.Get("start_org_name") endOrgID, _ := record.Get("end_org_id") endOrgName, _ := record.Get("end_org_name") chain := &ResourceChain{ ChainID: fmt.Sprintf("chain_%s_%d", startResourceType, len(chains)+1), ResourceType: startResourceType, TotalDistanceKm: getFloat64Value(totalDistance), TotalCost: getFloat64Value(totalValue), EnvironmentalImpact: getFloat64Value(totalValue) * 0.3, Circular: false, Steps: []ResourceChainStep{}, } // Build chain steps from path nodes and relationships if nodesSlice, ok := pathNodes.([]interface{}); ok { if relsSlice, ok := pathRels.([]interface{}); ok { for i := 0; i < len(nodesSlice)-1 && i < len(relsSlice); i++ { sourceNode := nodesSlice[i].(neo4j.Node) targetNode := nodesSlice[i+1].(neo4j.Node) rel := relsSlice[i].(neo4j.Relationship) step := ResourceChainStep{ StepNumber: i + 1, SourceFlowID: getStringProp(sourceNode.Props, "id"), TargetFlowID: getStringProp(targetNode.Props, "id"), SourceOrgID: getStringValue(startOrgID), SourceOrgName: getStringValue(startOrgName), TargetOrgID: getStringValue(endOrgID), TargetOrgName: getStringValue(endOrgName), DistanceKm: getFloat64Prop(rel.Props, "distance_km"), TransportCost: getFloat64Prop(rel.Props, "transport_cost"), ProcessingCost: getFloat64Prop(rel.Props, "processing_cost"), ResourceQuantity: getFloat64Prop(sourceNode.Props, "quantity"), } chain.Steps = append(chain.Steps, step) } } } chains = append(chains, chain) } if err := queryResult.Err(); err != nil { return nil, fmt.Errorf("failed to iterate chain results: %w", err) } return chains, nil }) if err != nil { return nil, err } return result.([]*ResourceChain), nil } // FindSymbiosisNetworks identifies interconnected industrial symbiosis networks func (t *Traversal) FindSymbiosisNetworks( ctx context.Context, minOrganizations int, maxNetworkSize int, ) ([]*SymbiosisNetwork, error) { if minOrganizations < t.config.MinNetworkSize { minOrganizations = t.config.MinNetworkSize } if maxNetworkSize > t.config.MaxNetworkSize { maxNetworkSize = t.config.MaxNetworkSize } result, err := t.sessionManager.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) { cypher := t.queryTemplates.FindSymbiosisNetworksQuery() queryResult, err := tx.Run(ctx, cypher, map[string]interface{}{ "min_orgs": minOrganizations, "max_size": maxNetworkSize, "limit": t.config.DefaultResultLimit, }) if err != nil { return nil, fmt.Errorf("failed to execute symbiosis network query: %w", err) } var networks []*SymbiosisNetwork for queryResult.Next(ctx) { record := queryResult.Record() networkOrgs, _ := record.Get("network_orgs") connections, _ := record.Get("connections") totalValue, _ := record.Get("total_value") maxDistance, _ := record.Get("max_distance") network := &SymbiosisNetwork{ NetworkID: fmt.Sprintf("network_%d", len(networks)+1), TotalValue: getFloat64Value(totalValue), GeographicSpan: getFloat64Value(maxDistance), NetworkEfficiency: 0.85, EnvironmentalSavings: getFloat64Value(totalValue) * 0.2, Organizations: []NetworkOrganization{}, ResourceFlows: []NetworkResourceFlow{}, } // Process organizations if orgsSlice, ok := networkOrgs.([]interface{}); ok { for _, orgID := range orgsSlice { if orgIDStr, ok := orgID.(string); ok { orgNode, err := t.getOrganizationNode(ctx, tx, orgIDStr) if err == nil { props := orgNode.Props networkOrg := NetworkOrganization{ OrganizationID: orgIDStr, Name: getStringProp(props, "name"), Role: "participant", Latitude: getFloat64Prop(props, "latitude"), Longitude: getFloat64Prop(props, "longitude"), } network.Organizations = append(network.Organizations, networkOrg) } } } } // Process resource flows if connsSlice, ok := connections.([]interface{}); ok { for _, conn := range connsSlice { if connMap, ok := conn.(map[string]interface{}); ok { networkFlow := NetworkResourceFlow{ FlowID: getStringProp(connMap, "flow_id"), ResourceType: getStringProp(connMap, "resource_type"), Value: getFloat64Prop(connMap, "economic_value"), DistanceKm: getFloat64Prop(connMap, "distance"), } network.ResourceFlows = append(network.ResourceFlows, networkFlow) } } } networks = append(networks, network) } if err := queryResult.Err(); err != nil { return nil, fmt.Errorf("failed to iterate network results: %w", err) } return networks, nil }) if err != nil { return nil, err } return result.([]*SymbiosisNetwork), nil } // getOrganizationNode retrieves an organization node by ID func (t *Traversal) getOrganizationNode(ctx context.Context, tx neo4j.ManagedTransaction, orgID string) (neo4j.Node, error) { cypher := `MATCH (o:Organization {id: $org_id}) RETURN o LIMIT 1` result, err := tx.Run(ctx, cypher, map[string]interface{}{"org_id": orgID}) if err != nil { return neo4j.Node{}, err } if result.Next(ctx) { record := result.Record() if node, ok := record.Get("o"); ok { if neo4jNode, ok := node.(neo4j.Node); ok { return neo4jNode, nil } } } return neo4j.Node{}, fmt.Errorf("organization not found: %s", orgID) }