mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools
Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
* GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
* GET /api/v1/users/me/organizations - User organizations
* POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue
API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules
Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
207 lines
5.7 KiB
Go
207 lines
5.7 KiB
Go
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
|
|
}
|