package graph import ( "context" "fmt" "github.com/neo4j/neo4j-go-driver/v5/neo4j" ) // NetworkAnalyzer handles network analysis operations type NetworkAnalyzer struct { sessionManager *SessionManager queryTemplates *QueryTemplates config *Config } // NewNetworkAnalyzer creates a new network analyzer instance func NewNetworkAnalyzer(sessionManager *SessionManager, queryTemplates *QueryTemplates, config *Config) *NetworkAnalyzer { return &NetworkAnalyzer{ sessionManager: sessionManager, queryTemplates: queryTemplates, config: config, } } // AnalyzeNetworkCentrality calculates centrality measures for organizations func (na *NetworkAnalyzer) AnalyzeNetworkCentrality(ctx context.Context) (map[string]*CentralityMetrics, error) { result, err := na.sessionManager.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) { cypher := na.queryTemplates.CalculateCentralityQuery() queryResult, err := tx.Run(ctx, cypher, nil) if err != nil { return nil, fmt.Errorf("failed to execute centrality query: %w", err) } centralityMap := make(map[string]*CentralityMetrics) totalOrgs := 0.0 maxDegree := 0.0 // First pass: collect degree centrality for queryResult.Next(ctx) { record := queryResult.Record() orgID, _ := record.Get("org_id") degree, _ := record.Get("degree_centrality") outFlows, _ := record.Get("out_flows") inFlows, _ := record.Get("in_flows") orgIDStr := getStringValue(orgID) degreeVal := getFloat64Value(degree) if degreeVal > maxDegree { maxDegree = degreeVal } centralityMap[orgIDStr] = &CentralityMetrics{ OrganizationID: orgIDStr, Degree: degreeVal, Betweenness: 0.0, // Would need more complex calculation Closeness: 0.0, // Would need path analysis OutFlows: getIntValue(outFlows), InFlows: getIntValue(inFlows), } totalOrgs++ } if err := queryResult.Err(); err != nil { return nil, fmt.Errorf("failed to iterate centrality results: %w", err) } // Normalize degree centrality (0-1 scale) if maxDegree > 0 { for _, metrics := range centralityMap { metrics.Degree = metrics.Degree / maxDegree } } return centralityMap, nil }) if err != nil { return nil, err } return result.(map[string]*CentralityMetrics), nil } // GetNetworkStatistics provides overall network statistics func (na *NetworkAnalyzer) GetNetworkStatistics(ctx context.Context) (*NetworkStatistics, error) { result, err := na.sessionManager.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) { cypher := na.queryTemplates.GetNetworkStatisticsQuery() queryResult, err := tx.Run(ctx, cypher, nil) if err != nil { return nil, fmt.Errorf("failed to execute network statistics query: %w", err) } if queryResult.Next(ctx) { record := queryResult.Record() stats := &NetworkStatistics{} if val, ok := record.Get("total_organizations"); ok { stats.TotalOrganizations = getIntValue(val) } if val, ok := record.Get("total_resource_flows"); ok { stats.TotalResourceFlows = getIntValue(val) } if val, ok := record.Get("total_matches"); ok { stats.TotalMatches = getIntValue(val) } if val, ok := record.Get("total_economic_value"); ok { stats.TotalEconomicValue = getFloat64Value(val) } if val, ok := record.Get("avg_distance"); ok { stats.AverageDistance = getFloat64Value(val) } if val, ok := record.Get("high_value_matches"); ok { stats.HighValueMatches = getIntValue(val) } if val, ok := record.Get("geographic_span_km"); ok { stats.GeographicSpanKm = getFloat64Value(val) } return stats, nil } // Return empty stats if no results return &NetworkStatistics{}, nil }) if err != nil { return nil, err } return result.(*NetworkStatistics), nil } // FindCircularEconomyCycles finds circular resource flow cycles func (na *NetworkAnalyzer) FindCircularEconomyCycles(ctx context.Context) ([]*CircularEconomyCycle, error) { result, err := na.sessionManager.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) { cypher := na.queryTemplates.FindCircularEconomyCyclesQuery() queryResult, err := tx.Run(ctx, cypher, map[string]interface{}{ "limit": na.config.DefaultResultLimit, }) if err != nil { return nil, fmt.Errorf("failed to execute circular economy query: %w", err) } var cycles []*CircularEconomyCycle for queryResult.Next(ctx) { record := queryResult.Record() cycleLength, _ := record.Get("cycle_length") flowIDs, _ := record.Get("flow_ids") organizations, _ := record.Get("organizations") cycleValue, _ := record.Get("cycle_value") orgCount, _ := record.Get("org_count") cycle := &CircularEconomyCycle{ CycleID: fmt.Sprintf("cycle_%d", len(cycles)+1), CycleLength: getIntValue(cycleLength), CycleValue: getFloat64Value(cycleValue), OrgCount: getIntValue(orgCount), FlowIDs: []string{}, Organizations: []string{}, } // Convert flow IDs if flowIDsSlice, ok := flowIDs.([]interface{}); ok { for _, flowID := range flowIDsSlice { if flowIDStr, ok := flowID.(string); ok { cycle.FlowIDs = append(cycle.FlowIDs, flowIDStr) } } } // Convert organizations if orgsSlice, ok := organizations.([]interface{}); ok { for _, org := range orgsSlice { if orgStr, ok := org.(string); ok { cycle.Organizations = append(cycle.Organizations, orgStr) } } } cycles = append(cycles, cycle) } if err := queryResult.Err(); err != nil { return nil, fmt.Errorf("failed to iterate cycle results: %w", err) } return cycles, nil }) if err != nil { return nil, err } return result.([]*CircularEconomyCycle), nil }