turash/bugulma/backend/internal/graph/path_finder.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

113 lines
3.2 KiB
Go

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
}