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)
261 lines
8.7 KiB
Go
261 lines
8.7 KiB
Go
package graph
|
|
|
|
import "fmt"
|
|
|
|
// QueryTemplates contains reusable Cypher query templates
|
|
type QueryTemplates struct{}
|
|
|
|
// NewQueryTemplates creates a new query templates instance
|
|
func NewQueryTemplates() *QueryTemplates {
|
|
return &QueryTemplates{}
|
|
}
|
|
|
|
// FindResourceChainsQuery returns the Cypher query for finding resource chains
|
|
func (qt *QueryTemplates) FindResourceChainsQuery(maxLength int) string {
|
|
return fmt.Sprintf(`
|
|
MATCH (start:ResourceFlow {type: $resource_type, direction: "output"})
|
|
MATCH path = (start)-[:MATCHES*1..%d]-(connected:ResourceFlow)
|
|
WHERE connected.direction = "input"
|
|
AND connected.type = $resource_type
|
|
AND start <> connected
|
|
|
|
WITH path, nodes(path) AS pathNodes, relationships(path) AS pathRels
|
|
WHERE size(pathNodes) >= 2 AND size(pathNodes) <= %d
|
|
|
|
// Get sites for distance calculation
|
|
MATCH (startSite:Site)-[:HOSTS]->(start)
|
|
MATCH (endSite:Site)-[:HOSTS]->(connected)
|
|
WHERE startSite.latitude IS NOT NULL
|
|
AND startSite.longitude IS NOT NULL
|
|
AND endSite.latitude IS NOT NULL
|
|
AND endSite.longitude IS NOT NULL
|
|
|
|
// Calculate total distance along the path
|
|
WITH path, pathNodes, pathRels, startSite, endSite,
|
|
point.distance(
|
|
point({latitude: startSite.latitude, longitude: startSite.longitude}),
|
|
point({latitude: endSite.latitude, longitude: endSite.longitude})
|
|
) / 1000.0 AS total_distance
|
|
|
|
WHERE total_distance <= $max_distance
|
|
|
|
// Get organization information
|
|
OPTIONAL MATCH (startOrg:Organization)-[:OPERATES_AT]->(startSite)
|
|
OPTIONAL MATCH (endOrg:Organization)-[:OPERATES_AT]->(endSite)
|
|
|
|
// Calculate total value from relationships
|
|
WITH path, pathNodes, pathRels, total_distance, startOrg, endOrg,
|
|
reduce(totalValue = 0.0, rel IN pathRels |
|
|
totalValue + COALESCE(rel.economic_value, 0.0)
|
|
) AS total_value
|
|
|
|
WHERE total_value >= $min_value
|
|
|
|
RETURN
|
|
pathNodes,
|
|
pathRels,
|
|
total_distance,
|
|
total_value,
|
|
startOrg.id AS start_org_id,
|
|
startOrg.name AS start_org_name,
|
|
endOrg.id AS end_org_id,
|
|
endOrg.name AS end_org_name
|
|
ORDER BY total_value DESC, total_distance ASC
|
|
LIMIT $limit
|
|
`, maxLength, maxLength)
|
|
}
|
|
|
|
// FindSymbiosisNetworksQuery returns the Cypher query for finding symbiosis networks
|
|
func (qt *QueryTemplates) FindSymbiosisNetworksQuery() string {
|
|
return `
|
|
MATCH (o1:Organization)-[:PRODUCES|CONSUMES]->(rf1:ResourceFlow)
|
|
MATCH (rf1)-[m:MATCHES]-(rf2:ResourceFlow)
|
|
MATCH (rf2)<-[:PRODUCES|CONSUMES]-(o2:Organization)
|
|
WHERE o1 <> o2
|
|
|
|
WITH o1, o2, m, rf1, rf2
|
|
// Get site information for distance calculation
|
|
MATCH (s1:Site)-[:HOSTS]->(rf1)
|
|
MATCH (s2:Site)-[:HOSTS]->(rf2)
|
|
WHERE s1.latitude IS NOT NULL AND s2.latitude IS NOT NULL
|
|
|
|
WITH o1, o2, m, rf1, rf2, s1, s2,
|
|
point.distance(
|
|
point({latitude: s1.latitude, longitude: s1.longitude}),
|
|
point({latitude: s2.latitude, longitude: s2.longitude})
|
|
) / 1000.0 AS distance_km
|
|
|
|
// Group by connected organizations
|
|
WITH o1.id AS org1_id, o1.name AS org1_name, o1.latitude AS org1_lat, o1.longitude AS org1_lng,
|
|
o2.id AS org2_id, o2.name AS org2_name, o2.latitude AS org2_lat, o2.longitude AS org2_lng,
|
|
collect(DISTINCT {
|
|
flow_id: rf1.id,
|
|
resource_type: rf1.type,
|
|
match_id: m.match_id,
|
|
economic_value: m.economic_value,
|
|
distance: distance_km
|
|
}) AS connections
|
|
|
|
WHERE size(connections) >= 2
|
|
|
|
// Aggregate network metrics
|
|
WITH collect(DISTINCT org1_id) + collect(DISTINCT org2_id) AS network_orgs,
|
|
connections,
|
|
reduce(totalValue = 0.0, conn IN connections |
|
|
totalValue + COALESCE(conn.economic_value, 0.0)
|
|
) AS total_value,
|
|
reduce(maxDist = 0.0, conn IN connections |
|
|
CASE WHEN conn.distance > maxDist THEN conn.distance ELSE maxDist END
|
|
) AS max_distance
|
|
|
|
WHERE size(network_orgs) >= $min_orgs AND size(network_orgs) <= $max_size
|
|
|
|
RETURN
|
|
network_orgs,
|
|
connections,
|
|
total_value,
|
|
max_distance
|
|
ORDER BY total_value DESC
|
|
LIMIT $limit
|
|
`
|
|
}
|
|
|
|
// FindOptimalPathQuery returns the Cypher query for finding optimal paths
|
|
func (qt *QueryTemplates) FindOptimalPathQuery(maxHops int) string {
|
|
return fmt.Sprintf(`
|
|
MATCH (source:Organization {id: $source_org})
|
|
MATCH (target:Organization {id: $target_org})
|
|
|
|
// Find paths through resource flows
|
|
MATCH path = shortestPath(
|
|
(source)-[:PRODUCES|CONSUMES|OPERATES_AT*1..%d]-(target)
|
|
)
|
|
|
|
WITH path, nodes(path) AS pathNodes, relationships(path) AS pathRels
|
|
WHERE size(pathNodes) >= 2
|
|
|
|
// Extract resource flows from path
|
|
WITH path, pathNodes, pathRels,
|
|
[node IN pathNodes WHERE node:ResourceFlow] AS flowNodes,
|
|
[rel IN pathRels WHERE type(rel) = 'MATCHES'] AS matchRels
|
|
|
|
WHERE size(flowNodes) >= 1 AND size(matchRels) >= 1
|
|
|
|
// Calculate path cost
|
|
WITH path, pathNodes, pathRels, flowNodes, matchRels,
|
|
reduce(totalCost = 0.0, rel IN matchRels |
|
|
totalCost + COALESCE(rel.transport_cost, 0.0) + COALESCE(rel.processing_cost, 0.0)
|
|
) AS total_cost,
|
|
reduce(totalDist = 0.0, rel IN matchRels |
|
|
totalDist + COALESCE(rel.distance_km, 0.0)
|
|
) AS total_distance
|
|
|
|
WHERE total_distance <= $max_distance
|
|
|
|
RETURN pathNodes, pathRels, total_cost, total_distance, length(path) AS path_length
|
|
ORDER BY total_cost ASC, total_distance ASC
|
|
LIMIT $limit
|
|
`, maxHops*2)
|
|
}
|
|
|
|
// CalculateCentralityQuery returns the Cypher query for calculating centrality
|
|
func (qt *QueryTemplates) CalculateCentralityQuery() string {
|
|
return `
|
|
MATCH (o:Organization)
|
|
OPTIONAL MATCH (o)-[:PRODUCES|CONSUMES]->(rf1:ResourceFlow)-[:MATCHES]-(rf2:ResourceFlow)<-[:PRODUCES|CONSUMES]-(other:Organization)
|
|
WHERE o <> other
|
|
|
|
WITH o.id AS org_id,
|
|
count(DISTINCT other) AS degree_centrality,
|
|
count(DISTINCT rf1) AS out_flows,
|
|
count(DISTINCT rf2) AS in_flows
|
|
|
|
WHERE degree_centrality > 0
|
|
|
|
RETURN org_id, degree_centrality, out_flows, in_flows
|
|
ORDER BY degree_centrality DESC
|
|
`
|
|
}
|
|
|
|
// GetNetworkStatisticsQuery returns the Cypher query for network statistics
|
|
func (qt *QueryTemplates) GetNetworkStatisticsQuery() string {
|
|
return `
|
|
MATCH (o:Organization)
|
|
OPTIONAL MATCH (o)-[:PRODUCES|CONSUMES]->(rf:ResourceFlow)
|
|
OPTIONAL MATCH (rf)-[m:MATCHES]-(rf2:ResourceFlow)
|
|
OPTIONAL MATCH (rf2)<-[:PRODUCES|CONSUMES]-(o2:Organization)
|
|
WHERE o <> o2
|
|
|
|
WITH
|
|
count(DISTINCT o) AS total_organizations,
|
|
count(DISTINCT rf) AS total_resource_flows,
|
|
count(DISTINCT m) AS total_matches,
|
|
sum(DISTINCT m.economic_value) AS total_economic_value,
|
|
avg(m.distance_km) AS avg_distance,
|
|
count(DISTINCT CASE WHEN m.economic_value > 50000 THEN m END) AS high_value_matches
|
|
|
|
MATCH (o:Organization)-[:OPERATES_AT]->(s:Site)
|
|
WITH total_organizations, total_resource_flows, total_matches, total_economic_value, avg_distance, high_value_matches,
|
|
collect(DISTINCT {lat: s.latitude, lng: s.longitude}) AS locations
|
|
|
|
// Calculate geographic span
|
|
WITH total_organizations, total_resource_flows, total_matches, total_economic_value, avg_distance, high_value_matches, locations,
|
|
reduce(maxLat = -90, loc IN locations | CASE WHEN loc.lat > maxLat THEN loc.lat ELSE maxLat END) AS max_lat,
|
|
reduce(minLat = 90, loc IN locations | CASE WHEN loc.lat < minLat THEN loc.lat ELSE minLat END) AS min_lat,
|
|
reduce(maxLng = -180, loc IN locations | CASE WHEN loc.lng > maxLng THEN loc.lng ELSE maxLng END) AS max_lng,
|
|
reduce(minLng = 180, loc IN locations | CASE WHEN loc.lng < minLng THEN loc.lng ELSE minLng END) AS min_lng
|
|
|
|
RETURN
|
|
total_organizations,
|
|
total_resource_flows,
|
|
total_matches,
|
|
total_economic_value,
|
|
avg_distance,
|
|
high_value_matches,
|
|
point.distance(
|
|
point({latitude: min_lat, longitude: min_lng}),
|
|
point({latitude: max_lat, longitude: max_lng})
|
|
) / 1000.0 AS geographic_span_km
|
|
`
|
|
}
|
|
|
|
// FindCircularEconomyCyclesQuery returns the Cypher query for finding circular economy cycles
|
|
func (qt *QueryTemplates) FindCircularEconomyCyclesQuery() string {
|
|
return `
|
|
// Find circular resource flows (waste -> recycling -> reuse -> waste)
|
|
MATCH cycle = (start:ResourceFlow)-[r:MATCHES*3..6]-(start)
|
|
WHERE start.direction = "output"
|
|
AND ALL(i IN range(0, length(r)-2) WHERE
|
|
nodes(cycle)[i].direction <> nodes(cycle)[i+1].direction)
|
|
|
|
WITH cycle,
|
|
length(r) AS cycle_length,
|
|
[node IN nodes(cycle) | node.type] AS resource_types,
|
|
[node IN nodes(cycle) | node.id] AS flow_ids,
|
|
reduce(totalValue = 0.0, rel IN r | totalValue + rel.economic_value) AS cycle_value
|
|
|
|
WHERE cycle_value > 0
|
|
|
|
// Get organization information for each cycle
|
|
MATCH (org:Organization)-[:PRODUCES|CONSUMES]->(flow:ResourceFlow)
|
|
WHERE flow IN nodes(cycle)
|
|
|
|
WITH cycle, cycle_length, resource_types, flow_ids, cycle_value,
|
|
collect(DISTINCT org.name) AS organizations,
|
|
count(DISTINCT org) AS org_count
|
|
|
|
WHERE org_count >= 3
|
|
|
|
RETURN
|
|
cycle,
|
|
cycle_length,
|
|
resource_types,
|
|
flow_ids,
|
|
cycle_value,
|
|
organizations,
|
|
org_count
|
|
ORDER BY cycle_value DESC
|
|
LIMIT $limit
|
|
`
|
|
}
|