turash/bugulma/backend/internal/handler/graph_traversal_handler.go
Damir Mukimov 000eab4740
Major repository reorganization and missing backend endpoints implementation
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)
2025-11-25 06:01:16 +01:00

270 lines
8.6 KiB
Go

package handler
import (
"bugulma/backend/internal/domain"
"bugulma/backend/internal/service"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
// GraphTraversalHandler handles advanced graph traversal operations
type GraphTraversalHandler struct {
traversalService *service.GraphTraversalService
}
// NewGraphTraversalHandler creates a new graph traversal handler
func NewGraphTraversalHandler(traversalService *service.GraphTraversalService) *GraphTraversalHandler {
return &GraphTraversalHandler{
traversalService: traversalService,
}
}
// FindResourceChains finds chains of resource flows from waste to reuse
// @Summary Find resource chains
// @Tags graph-traversal
// @Produce json
// @Param resourceType query string true "Resource type (e.g., biowaste, heat, water)"
// @Param maxLength query int false "Maximum chain length" default(5)
// @Param minValue query number false "Minimum economic value" default(1000)
// @Success 200 {object} map[string]interface{}
// @Router /api/graph/resource-chains [get]
func (h *GraphTraversalHandler) FindResourceChains(c *gin.Context) {
resourceTypeStr := c.Query("resourceType")
if resourceTypeStr == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "resourceType parameter is required"})
return
}
resourceType := domain.ResourceType(resourceTypeStr)
maxLengthStr := c.DefaultQuery("maxLength", "5")
minValueStr := c.DefaultQuery("minValue", "1000")
maxLength, err := strconv.Atoi(maxLengthStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid maxLength parameter"})
return
}
minValue, err := strconv.ParseFloat(minValueStr, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid minValue parameter"})
return
}
chains, err := h.traversalService.FindResourceChains(c.Request.Context(), resourceType, maxLength, minValue)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"resource_chains": chains,
"total_chains": len(chains),
"resource_type": resourceType,
"criteria": gin.H{
"max_length": maxLength,
"min_value": minValue,
},
})
}
// FindSymbiosisNetworks identifies interconnected industrial symbiosis networks
// @Summary Find symbiosis networks
// @Tags graph-traversal
// @Produce json
// @Param minOrganizations query int false "Minimum organizations per network" default(3)
// @Param maxNetworkSize query int false "Maximum network size" default(20)
// @Success 200 {object} map[string]interface{}
// @Router /api/graph/symbiosis-networks [get]
func (h *GraphTraversalHandler) FindSymbiosisNetworks(c *gin.Context) {
minOrgsStr := c.DefaultQuery("minOrganizations", "3")
maxSizeStr := c.DefaultQuery("maxNetworkSize", "20")
minOrgs, err := strconv.Atoi(minOrgsStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid minOrganizations parameter"})
return
}
maxSize, err := strconv.Atoi(maxSizeStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid maxNetworkSize parameter"})
return
}
networks, err := h.traversalService.FindSymbiosisNetworks(c.Request.Context(), minOrgs, maxSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"symbiosis_networks": networks,
"total_networks": len(networks),
"criteria": gin.H{
"min_organizations": minOrgs,
"max_network_size": maxSize,
},
})
}
// FindOptimalResourcePaths finds the most cost-effective paths for resource flows
// @Summary Find optimal resource paths
// @Tags graph-traversal
// @Produce json
// @Param sourceOrgId query string true "Source organization ID"
// @Param targetOrgId query string true "Target organization ID"
// @Param resourceType query string true "Resource type"
// @Param maxHops query int false "Maximum hops in path" default(4)
// @Success 200 {object} map[string]interface{}
// @Router /api/graph/optimal-paths [get]
func (h *GraphTraversalHandler) FindOptimalResourcePaths(c *gin.Context) {
sourceOrgID := c.Query("sourceOrgId")
targetOrgID := c.Query("targetOrgId")
resourceTypeStr := c.Query("resourceType")
maxHopsStr := c.DefaultQuery("maxHops", "4")
if sourceOrgID == "" || targetOrgID == "" || resourceTypeStr == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "sourceOrgId, targetOrgId, and resourceType parameters are required"})
return
}
resourceType := domain.ResourceType(resourceTypeStr)
maxHops, err := strconv.Atoi(maxHopsStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid maxHops parameter"})
return
}
paths, err := h.traversalService.FindOptimalResourcePaths(c.Request.Context(), sourceOrgID, targetOrgID, resourceType, maxHops)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"optimal_paths": paths,
"total_paths": len(paths),
"route": gin.H{
"source_org": sourceOrgID,
"target_org": targetOrgID,
"resource_type": resourceType,
"max_hops": maxHops,
},
})
}
// AnalyzeNetworkCentrality calculates centrality measures for organizations
// @Summary Analyze network centrality
// @Tags graph-traversal
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Router /api/graph/centrality [get]
func (h *GraphTraversalHandler) AnalyzeNetworkCentrality(c *gin.Context) {
centrality, err := h.traversalService.AnalyzeNetworkCentrality(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Convert to response format
var sortedOrgs []map[string]interface{}
for orgID, metrics := range centrality {
sortedOrgs = append(sortedOrgs, map[string]interface{}{
"org_id": orgID,
"degree": metrics.Degree,
"betweenness": metrics.Betweenness,
"closeness": metrics.Closeness,
"out_flows": metrics.OutFlows,
"in_flows": metrics.InFlows,
})
}
// Sort by degree centrality (descending)
for i := 0; i < len(sortedOrgs)-1; i++ {
for j := i + 1; j < len(sortedOrgs); j++ {
if sortedOrgs[i]["degree"].(float64) < sortedOrgs[j]["degree"].(float64) {
sortedOrgs[i], sortedOrgs[j] = sortedOrgs[j], sortedOrgs[i]
}
}
}
c.JSON(http.StatusOK, gin.H{
"centrality_analysis": gin.H{
"organizations": sortedOrgs,
"total_analyzed": len(sortedOrgs),
"metrics": []string{"degree", "betweenness", "closeness"},
},
"interpretation": gin.H{
"degree_centrality": "Number of direct connections (local importance)",
"betweenness_centrality": "Control over resource flows (network influence)",
"closeness_centrality": "Average distance to other organizations (accessibility)",
},
})
}
// GetNetworkStatistics provides overall network statistics
// @Summary Get network statistics
// @Tags graph-traversal
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Router /api/graph/statistics [get]
func (h *GraphTraversalHandler) GetNetworkStatistics(c *gin.Context) {
stats, err := h.traversalService.GetNetworkStatistics(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"network_statistics": gin.H{
"total_organizations": stats.TotalOrganizations,
"total_resource_flows": stats.TotalResourceFlows,
"total_matches": stats.TotalMatches,
"total_economic_value": stats.TotalEconomicValue,
"avg_distance_km": stats.AverageDistance,
"high_value_matches": stats.HighValueMatches,
"geographic_span_km": stats.GeographicSpanKm,
},
"generated_at": time.Now().Format(time.RFC3339),
})
}
// GetCircularEconomyAnalysis analyzes circular economy potential
// @Summary Analyze circular economy potential
// @Tags graph-traversal
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Router /api/graph/circular-economy [get]
func (h *GraphTraversalHandler) GetCircularEconomyAnalysis(c *gin.Context) {
cycles, err := h.traversalService.FindCircularEconomyCycles(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
totalCircularValue := 0.0
for _, cycle := range cycles {
totalCircularValue += cycle.CycleValue
}
avgCycleValue := 0.0
if len(cycles) > 0 {
avgCycleValue = totalCircularValue / float64(len(cycles))
}
c.JSON(http.StatusOK, gin.H{
"circular_economy_analysis": gin.H{
"circular_cycles": cycles,
"total_cycles": len(cycles),
"total_circular_value": totalCircularValue,
"avg_cycle_value": avgCycleValue,
"circular_efficiency": totalCircularValue / 100000.0, // Simplified metric
},
"analysis_timestamp": time.Now().Format(time.RFC3339),
})
}