Fix temporal persistence wiring and restore slurp_full suite
This commit is contained in:
@@ -3,8 +3,8 @@ package temporal
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -14,58 +14,58 @@ import (
|
||||
// querySystemImpl implements decision-hop based query operations
|
||||
type querySystemImpl struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
|
||||
// Reference to the temporal graph
|
||||
graph *temporalGraphImpl
|
||||
graph *temporalGraphImpl
|
||||
navigator DecisionNavigator
|
||||
analyzer InfluenceAnalyzer
|
||||
detector StalenessDetector
|
||||
|
||||
analyzer InfluenceAnalyzer
|
||||
detector StalenessDetector
|
||||
|
||||
// Query optimization
|
||||
queryCache map[string]interface{}
|
||||
cacheTimeout time.Duration
|
||||
queryCache map[string]interface{}
|
||||
cacheTimeout time.Duration
|
||||
lastCacheClean time.Time
|
||||
|
||||
|
||||
// Query statistics
|
||||
queryStats map[string]*QueryStatistics
|
||||
}
|
||||
|
||||
// QueryStatistics represents statistics for different query types
|
||||
type QueryStatistics struct {
|
||||
QueryType string `json:"query_type"`
|
||||
TotalQueries int64 `json:"total_queries"`
|
||||
AverageTime time.Duration `json:"average_time"`
|
||||
CacheHits int64 `json:"cache_hits"`
|
||||
CacheMisses int64 `json:"cache_misses"`
|
||||
LastQuery time.Time `json:"last_query"`
|
||||
QueryType string `json:"query_type"`
|
||||
TotalQueries int64 `json:"total_queries"`
|
||||
AverageTime time.Duration `json:"average_time"`
|
||||
CacheHits int64 `json:"cache_hits"`
|
||||
CacheMisses int64 `json:"cache_misses"`
|
||||
LastQuery time.Time `json:"last_query"`
|
||||
}
|
||||
|
||||
// HopQuery represents a decision-hop based query
|
||||
type HopQuery struct {
|
||||
StartAddress ucxl.Address `json:"start_address"` // Starting point
|
||||
MaxHops int `json:"max_hops"` // Maximum hops to traverse
|
||||
Direction string `json:"direction"` // "forward", "backward", "both"
|
||||
FilterCriteria *HopFilter `json:"filter_criteria"` // Filtering options
|
||||
SortCriteria *HopSort `json:"sort_criteria"` // Sorting options
|
||||
Limit int `json:"limit"` // Maximum results
|
||||
IncludeMetadata bool `json:"include_metadata"` // Include detailed metadata
|
||||
StartAddress ucxl.Address `json:"start_address"` // Starting point
|
||||
MaxHops int `json:"max_hops"` // Maximum hops to traverse
|
||||
Direction string `json:"direction"` // "forward", "backward", "both"
|
||||
FilterCriteria *HopFilter `json:"filter_criteria"` // Filtering options
|
||||
SortCriteria *HopSort `json:"sort_criteria"` // Sorting options
|
||||
Limit int `json:"limit"` // Maximum results
|
||||
IncludeMetadata bool `json:"include_metadata"` // Include detailed metadata
|
||||
}
|
||||
|
||||
// HopFilter represents filtering criteria for hop queries
|
||||
type HopFilter struct {
|
||||
ChangeReasons []ChangeReason `json:"change_reasons"` // Filter by change reasons
|
||||
ImpactScopes []ImpactScope `json:"impact_scopes"` // Filter by impact scopes
|
||||
MinConfidence float64 `json:"min_confidence"` // Minimum confidence threshold
|
||||
MaxAge time.Duration `json:"max_age"` // Maximum age of decisions
|
||||
DecisionMakers []string `json:"decision_makers"` // Filter by decision makers
|
||||
Tags []string `json:"tags"` // Filter by context tags
|
||||
Technologies []string `json:"technologies"` // Filter by technologies
|
||||
MinInfluenceCount int `json:"min_influence_count"` // Minimum number of influences
|
||||
ExcludeStale bool `json:"exclude_stale"` // Exclude stale contexts
|
||||
OnlyMajorDecisions bool `json:"only_major_decisions"` // Only major decisions
|
||||
ChangeReasons []ChangeReason `json:"change_reasons"` // Filter by change reasons
|
||||
ImpactScopes []ImpactScope `json:"impact_scopes"` // Filter by impact scopes
|
||||
MinConfidence float64 `json:"min_confidence"` // Minimum confidence threshold
|
||||
MaxAge time.Duration `json:"max_age"` // Maximum age of decisions
|
||||
DecisionMakers []string `json:"decision_makers"` // Filter by decision makers
|
||||
Tags []string `json:"tags"` // Filter by context tags
|
||||
Technologies []string `json:"technologies"` // Filter by technologies
|
||||
MinInfluenceCount int `json:"min_influence_count"` // Minimum number of influences
|
||||
ExcludeStale bool `json:"exclude_stale"` // Exclude stale contexts
|
||||
OnlyMajorDecisions bool `json:"only_major_decisions"` // Only major decisions
|
||||
}
|
||||
|
||||
// HopSort represents sorting criteria for hop queries
|
||||
// HopSort represents sorting criteria for hop queries
|
||||
type HopSort struct {
|
||||
SortBy string `json:"sort_by"` // "hops", "time", "confidence", "influence"
|
||||
SortDirection string `json:"sort_direction"` // "asc", "desc"
|
||||
@@ -74,52 +74,52 @@ type HopSort struct {
|
||||
|
||||
// HopQueryResult represents the result of a hop-based query
|
||||
type HopQueryResult struct {
|
||||
Query *HopQuery `json:"query"` // Original query
|
||||
Results []*HopResult `json:"results"` // Query results
|
||||
TotalFound int `json:"total_found"` // Total results found
|
||||
ExecutionTime time.Duration `json:"execution_time"` // Query execution time
|
||||
FromCache bool `json:"from_cache"` // Whether result came from cache
|
||||
QueryPath []*QueryPathStep `json:"query_path"` // Path of query execution
|
||||
Statistics *QueryExecution `json:"statistics"` // Execution statistics
|
||||
Query *HopQuery `json:"query"` // Original query
|
||||
Results []*HopResult `json:"results"` // Query results
|
||||
TotalFound int `json:"total_found"` // Total results found
|
||||
ExecutionTime time.Duration `json:"execution_time"` // Query execution time
|
||||
FromCache bool `json:"from_cache"` // Whether result came from cache
|
||||
QueryPath []*QueryPathStep `json:"query_path"` // Path of query execution
|
||||
Statistics *QueryExecution `json:"statistics"` // Execution statistics
|
||||
}
|
||||
|
||||
// HopResult represents a single result from a hop query
|
||||
type HopResult struct {
|
||||
Address ucxl.Address `json:"address"` // Context address
|
||||
HopDistance int `json:"hop_distance"` // Decision hops from start
|
||||
TemporalNode *TemporalNode `json:"temporal_node"` // Temporal node data
|
||||
Path []*DecisionStep `json:"path"` // Path from start to this result
|
||||
Relationship string `json:"relationship"` // Relationship type
|
||||
RelevanceScore float64 `json:"relevance_score"` // Relevance to query
|
||||
MatchReasons []string `json:"match_reasons"` // Why this matched
|
||||
Metadata map[string]interface{} `json:"metadata"` // Additional metadata
|
||||
Address ucxl.Address `json:"address"` // Context address
|
||||
HopDistance int `json:"hop_distance"` // Decision hops from start
|
||||
TemporalNode *TemporalNode `json:"temporal_node"` // Temporal node data
|
||||
Path []*DecisionStep `json:"path"` // Path from start to this result
|
||||
Relationship string `json:"relationship"` // Relationship type
|
||||
RelevanceScore float64 `json:"relevance_score"` // Relevance to query
|
||||
MatchReasons []string `json:"match_reasons"` // Why this matched
|
||||
Metadata map[string]interface{} `json:"metadata"` // Additional metadata
|
||||
}
|
||||
|
||||
// QueryPathStep represents a step in query execution path
|
||||
type QueryPathStep struct {
|
||||
Step int `json:"step"` // Step number
|
||||
Operation string `json:"operation"` // Operation performed
|
||||
NodesExamined int `json:"nodes_examined"` // Nodes examined in this step
|
||||
NodesFiltered int `json:"nodes_filtered"` // Nodes filtered out
|
||||
Duration time.Duration `json:"duration"` // Step duration
|
||||
Description string `json:"description"` // Step description
|
||||
Step int `json:"step"` // Step number
|
||||
Operation string `json:"operation"` // Operation performed
|
||||
NodesExamined int `json:"nodes_examined"` // Nodes examined in this step
|
||||
NodesFiltered int `json:"nodes_filtered"` // Nodes filtered out
|
||||
Duration time.Duration `json:"duration"` // Step duration
|
||||
Description string `json:"description"` // Step description
|
||||
}
|
||||
|
||||
// QueryExecution represents query execution statistics
|
||||
type QueryExecution struct {
|
||||
StartTime time.Time `json:"start_time"` // Query start time
|
||||
EndTime time.Time `json:"end_time"` // Query end time
|
||||
Duration time.Duration `json:"duration"` // Total duration
|
||||
NodesVisited int `json:"nodes_visited"` // Total nodes visited
|
||||
EdgesTraversed int `json:"edges_traversed"` // Total edges traversed
|
||||
CacheAccesses int `json:"cache_accesses"` // Cache access count
|
||||
FilterSteps int `json:"filter_steps"` // Number of filter steps
|
||||
SortOperations int `json:"sort_operations"` // Number of sort operations
|
||||
MemoryUsed int64 `json:"memory_used"` // Estimated memory used
|
||||
StartTime time.Time `json:"start_time"` // Query start time
|
||||
EndTime time.Time `json:"end_time"` // Query end time
|
||||
Duration time.Duration `json:"duration"` // Total duration
|
||||
NodesVisited int `json:"nodes_visited"` // Total nodes visited
|
||||
EdgesTraversed int `json:"edges_traversed"` // Total edges traversed
|
||||
CacheAccesses int `json:"cache_accesses"` // Cache access count
|
||||
FilterSteps int `json:"filter_steps"` // Number of filter steps
|
||||
SortOperations int `json:"sort_operations"` // Number of sort operations
|
||||
MemoryUsed int64 `json:"memory_used"` // Estimated memory used
|
||||
}
|
||||
|
||||
// NewQuerySystem creates a new decision-hop query system
|
||||
func NewQuerySystem(graph *temporalGraphImpl, navigator DecisionNavigator,
|
||||
func NewQuerySystem(graph *temporalGraphImpl, navigator DecisionNavigator,
|
||||
analyzer InfluenceAnalyzer, detector StalenessDetector) *querySystemImpl {
|
||||
return &querySystemImpl{
|
||||
graph: graph,
|
||||
@@ -136,12 +136,12 @@ func NewQuerySystem(graph *temporalGraphImpl, navigator DecisionNavigator,
|
||||
// ExecuteHopQuery executes a decision-hop based query
|
||||
func (qs *querySystemImpl) ExecuteHopQuery(ctx context.Context, query *HopQuery) (*HopQueryResult, error) {
|
||||
startTime := time.Now()
|
||||
|
||||
|
||||
// Validate query
|
||||
if err := qs.validateQuery(query); err != nil {
|
||||
return nil, fmt.Errorf("invalid query: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Check cache
|
||||
cacheKey := qs.generateCacheKey(query)
|
||||
if cached, found := qs.getFromCache(cacheKey); found {
|
||||
@@ -151,26 +151,26 @@ func (qs *querySystemImpl) ExecuteHopQuery(ctx context.Context, query *HopQuery)
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Execute query
|
||||
result, err := qs.executeHopQueryInternal(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
// Set execution time and cache result
|
||||
result.ExecutionTime = time.Since(startTime)
|
||||
result.FromCache = false
|
||||
qs.setCache(cacheKey, result)
|
||||
qs.updateQueryStats("hop_query", result.ExecutionTime, false)
|
||||
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindDecisionsWithinHops finds all decisions within N hops of a given address
|
||||
func (qs *querySystemImpl) FindDecisionsWithinHops(ctx context.Context, address ucxl.Address,
|
||||
func (qs *querySystemImpl) FindDecisionsWithinHops(ctx context.Context, address ucxl.Address,
|
||||
maxHops int, filter *HopFilter) ([]*HopResult, error) {
|
||||
|
||||
|
||||
query := &HopQuery{
|
||||
StartAddress: address,
|
||||
MaxHops: maxHops,
|
||||
@@ -179,12 +179,12 @@ func (qs *querySystemImpl) FindDecisionsWithinHops(ctx context.Context, address
|
||||
SortCriteria: &HopSort{SortBy: "hops", SortDirection: "asc"},
|
||||
IncludeMetadata: false,
|
||||
}
|
||||
|
||||
|
||||
result, err := qs.ExecuteHopQuery(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
return result.Results, nil
|
||||
}
|
||||
|
||||
@@ -198,31 +198,31 @@ func (qs *querySystemImpl) FindInfluenceChain(ctx context.Context, from, to ucxl
|
||||
func (qs *querySystemImpl) AnalyzeDecisionGenealogy(ctx context.Context, address ucxl.Address) (*DecisionGenealogy, error) {
|
||||
qs.mu.RLock()
|
||||
defer qs.mu.RUnlock()
|
||||
|
||||
|
||||
// Get evolution history
|
||||
history, err := qs.graph.GetEvolutionHistory(ctx, address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get evolution history: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Get decision timeline
|
||||
timeline, err := qs.navigator.GetDecisionTimeline(ctx, address, true, 10)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get decision timeline: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Analyze ancestry
|
||||
ancestry := qs.analyzeAncestry(history)
|
||||
|
||||
// Analyze descendants
|
||||
|
||||
// Analyze descendants
|
||||
descendants := qs.analyzeDescendants(address, 5)
|
||||
|
||||
|
||||
// Find influential ancestors
|
||||
influentialAncestors := qs.findInfluentialAncestors(history)
|
||||
|
||||
|
||||
// Calculate genealogy metrics
|
||||
metrics := qs.calculateGenealogyMetrics(history, descendants)
|
||||
|
||||
|
||||
genealogy := &DecisionGenealogy{
|
||||
Address: address,
|
||||
DirectAncestors: ancestry.DirectAncestors,
|
||||
@@ -233,58 +233,58 @@ func (qs *querySystemImpl) AnalyzeDecisionGenealogy(ctx context.Context, address
|
||||
GenealogyDepth: ancestry.MaxDepth,
|
||||
BranchingFactor: descendants.BranchingFactor,
|
||||
DecisionTimeline: timeline,
|
||||
Metrics: metrics,
|
||||
AnalyzedAt: time.Now(),
|
||||
Metrics: metrics,
|
||||
AnalyzedAt: time.Now(),
|
||||
}
|
||||
|
||||
|
||||
return genealogy, nil
|
||||
}
|
||||
|
||||
// FindSimilarDecisionPatterns finds decisions with similar patterns
|
||||
func (qs *querySystemImpl) FindSimilarDecisionPatterns(ctx context.Context, referenceAddress ucxl.Address,
|
||||
func (qs *querySystemImpl) FindSimilarDecisionPatterns(ctx context.Context, referenceAddress ucxl.Address,
|
||||
maxResults int) ([]*SimilarDecisionMatch, error) {
|
||||
|
||||
|
||||
qs.mu.RLock()
|
||||
defer qs.mu.RUnlock()
|
||||
|
||||
|
||||
// Get reference node
|
||||
refNode, err := qs.graph.getLatestNodeUnsafe(referenceAddress)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reference node not found: %w", err)
|
||||
}
|
||||
|
||||
|
||||
matches := make([]*SimilarDecisionMatch, 0)
|
||||
|
||||
|
||||
// Compare with all other nodes
|
||||
for _, node := range qs.graph.nodes {
|
||||
if node.UCXLAddress.String() == referenceAddress.String() {
|
||||
continue // Skip self
|
||||
}
|
||||
|
||||
|
||||
similarity := qs.calculateDecisionSimilarity(refNode, node)
|
||||
if similarity > 0.3 { // Threshold for meaningful similarity
|
||||
match := &SimilarDecisionMatch{
|
||||
Address: node.UCXLAddress,
|
||||
TemporalNode: node,
|
||||
SimilarityScore: similarity,
|
||||
Address: node.UCXLAddress,
|
||||
TemporalNode: node,
|
||||
SimilarityScore: similarity,
|
||||
SimilarityReasons: qs.getSimilarityReasons(refNode, node),
|
||||
PatternType: qs.identifyPatternType(refNode, node),
|
||||
Confidence: similarity * 0.9, // Slightly lower confidence
|
||||
PatternType: qs.identifyPatternType(refNode, node),
|
||||
Confidence: similarity * 0.9, // Slightly lower confidence
|
||||
}
|
||||
matches = append(matches, match)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sort by similarity score
|
||||
sort.Slice(matches, func(i, j int) bool {
|
||||
return matches[i].SimilarityScore > matches[j].SimilarityScore
|
||||
})
|
||||
|
||||
|
||||
// Limit results
|
||||
if maxResults > 0 && len(matches) > maxResults {
|
||||
matches = matches[:maxResults]
|
||||
}
|
||||
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
@@ -292,13 +292,13 @@ func (qs *querySystemImpl) FindSimilarDecisionPatterns(ctx context.Context, refe
|
||||
func (qs *querySystemImpl) DiscoverDecisionClusters(ctx context.Context, minClusterSize int) ([]*DecisionCluster, error) {
|
||||
qs.mu.RLock()
|
||||
defer qs.mu.RUnlock()
|
||||
|
||||
|
||||
// Use influence analyzer to get clusters
|
||||
analysis, err := qs.analyzer.AnalyzeInfluenceNetwork(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to analyze influence network: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Filter clusters by minimum size
|
||||
clusters := make([]*DecisionCluster, 0)
|
||||
for _, community := range analysis.Communities {
|
||||
@@ -307,7 +307,7 @@ func (qs *querySystemImpl) DiscoverDecisionClusters(ctx context.Context, minClus
|
||||
clusters = append(clusters, cluster)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
@@ -317,16 +317,16 @@ func (qs *querySystemImpl) executeHopQueryInternal(ctx context.Context, query *H
|
||||
execution := &QueryExecution{
|
||||
StartTime: time.Now(),
|
||||
}
|
||||
|
||||
|
||||
queryPath := make([]*QueryPathStep, 0)
|
||||
|
||||
|
||||
// Step 1: Get starting node
|
||||
step1Start := time.Now()
|
||||
startNode, err := qs.graph.getLatestNodeUnsafe(query.StartAddress)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("start node not found: %w", err)
|
||||
}
|
||||
|
||||
|
||||
queryPath = append(queryPath, &QueryPathStep{
|
||||
Step: 1,
|
||||
Operation: "get_start_node",
|
||||
@@ -335,12 +335,12 @@ func (qs *querySystemImpl) executeHopQueryInternal(ctx context.Context, query *H
|
||||
Duration: time.Since(step1Start),
|
||||
Description: "Retrieved starting node",
|
||||
})
|
||||
|
||||
|
||||
// Step 2: Traverse decision graph
|
||||
step2Start := time.Now()
|
||||
candidates := qs.traverseDecisionGraph(startNode, query.MaxHops, query.Direction)
|
||||
execution.NodesVisited = len(candidates)
|
||||
|
||||
|
||||
queryPath = append(queryPath, &QueryPathStep{
|
||||
Step: 2,
|
||||
Operation: "traverse_graph",
|
||||
@@ -349,12 +349,12 @@ func (qs *querySystemImpl) executeHopQueryInternal(ctx context.Context, query *H
|
||||
Duration: time.Since(step2Start),
|
||||
Description: fmt.Sprintf("Traversed decision graph up to %d hops", query.MaxHops),
|
||||
})
|
||||
|
||||
|
||||
// Step 3: Apply filters
|
||||
step3Start := time.Now()
|
||||
filtered := qs.applyFilters(candidates, query.FilterCriteria)
|
||||
execution.FilterSteps = 1
|
||||
|
||||
|
||||
queryPath = append(queryPath, &QueryPathStep{
|
||||
Step: 3,
|
||||
Operation: "apply_filters",
|
||||
@@ -363,11 +363,11 @@ func (qs *querySystemImpl) executeHopQueryInternal(ctx context.Context, query *H
|
||||
Duration: time.Since(step3Start),
|
||||
Description: fmt.Sprintf("Applied filters, removed %d candidates", len(candidates)-len(filtered)),
|
||||
})
|
||||
|
||||
|
||||
// Step 4: Calculate relevance scores
|
||||
step4Start := time.Now()
|
||||
results := qs.calculateRelevanceScores(filtered, startNode, query)
|
||||
|
||||
|
||||
queryPath = append(queryPath, &QueryPathStep{
|
||||
Step: 4,
|
||||
Operation: "calculate_relevance",
|
||||
@@ -376,14 +376,14 @@ func (qs *querySystemImpl) executeHopQueryInternal(ctx context.Context, query *H
|
||||
Duration: time.Since(step4Start),
|
||||
Description: "Calculated relevance scores",
|
||||
})
|
||||
|
||||
|
||||
// Step 5: Sort results
|
||||
step5Start := time.Time{}
|
||||
if query.SortCriteria != nil {
|
||||
step5Start = time.Now()
|
||||
qs.sortResults(results, query.SortCriteria)
|
||||
execution.SortOperations = 1
|
||||
|
||||
|
||||
queryPath = append(queryPath, &QueryPathStep{
|
||||
Step: 5,
|
||||
Operation: "sort_results",
|
||||
@@ -393,17 +393,17 @@ func (qs *querySystemImpl) executeHopQueryInternal(ctx context.Context, query *H
|
||||
Description: fmt.Sprintf("Sorted by %s %s", query.SortCriteria.SortBy, query.SortCriteria.SortDirection),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Step 6: Apply limit
|
||||
totalFound := len(results)
|
||||
if query.Limit > 0 && len(results) > query.Limit {
|
||||
results = results[:query.Limit]
|
||||
}
|
||||
|
||||
|
||||
// Complete execution statistics
|
||||
execution.EndTime = time.Now()
|
||||
execution.Duration = execution.EndTime.Sub(execution.StartTime)
|
||||
|
||||
|
||||
result := &HopQueryResult{
|
||||
Query: query,
|
||||
Results: results,
|
||||
@@ -413,46 +413,46 @@ func (qs *querySystemImpl) executeHopQueryInternal(ctx context.Context, query *H
|
||||
QueryPath: queryPath,
|
||||
Statistics: execution,
|
||||
}
|
||||
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (qs *querySystemImpl) traverseDecisionGraph(startNode *TemporalNode, maxHops int, direction string) []*hopCandidate {
|
||||
candidates := make([]*hopCandidate, 0)
|
||||
visited := make(map[string]bool)
|
||||
|
||||
|
||||
// BFS traversal
|
||||
queue := []*hopCandidate{{
|
||||
node: startNode,
|
||||
distance: 0,
|
||||
path: []*DecisionStep{},
|
||||
}}
|
||||
|
||||
|
||||
for len(queue) > 0 {
|
||||
current := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
|
||||
nodeID := current.node.ID
|
||||
if visited[nodeID] || current.distance > maxHops {
|
||||
continue
|
||||
}
|
||||
visited[nodeID] = true
|
||||
|
||||
|
||||
// Add to candidates (except start node)
|
||||
if current.distance > 0 {
|
||||
candidates = append(candidates, current)
|
||||
}
|
||||
|
||||
|
||||
// Add neighbors based on direction
|
||||
if direction == "forward" || direction == "both" {
|
||||
qs.addForwardNeighbors(current, &queue, maxHops)
|
||||
}
|
||||
|
||||
|
||||
if direction == "backward" || direction == "both" {
|
||||
qs.addBackwardNeighbors(current, &queue, maxHops)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return candidates
|
||||
}
|
||||
|
||||
@@ -460,21 +460,21 @@ func (qs *querySystemImpl) applyFilters(candidates []*hopCandidate, filter *HopF
|
||||
if filter == nil {
|
||||
return candidates
|
||||
}
|
||||
|
||||
|
||||
filtered := make([]*hopCandidate, 0)
|
||||
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if qs.passesFilter(candidate, filter) {
|
||||
filtered = append(filtered, candidate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (qs *querySystemImpl) passesFilter(candidate *hopCandidate, filter *HopFilter) bool {
|
||||
node := candidate.node
|
||||
|
||||
|
||||
// Change reason filter
|
||||
if len(filter.ChangeReasons) > 0 {
|
||||
found := false
|
||||
@@ -488,7 +488,7 @@ func (qs *querySystemImpl) passesFilter(candidate *hopCandidate, filter *HopFilt
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Impact scope filter
|
||||
if len(filter.ImpactScopes) > 0 {
|
||||
found := false
|
||||
@@ -502,17 +502,17 @@ func (qs *querySystemImpl) passesFilter(candidate *hopCandidate, filter *HopFilt
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Confidence filter
|
||||
if filter.MinConfidence > 0 && node.Confidence < filter.MinConfidence {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Age filter
|
||||
if filter.MaxAge > 0 && time.Since(node.Timestamp) > filter.MaxAge {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Decision maker filter
|
||||
if len(filter.DecisionMakers) > 0 {
|
||||
if decision, exists := qs.graph.decisions[node.DecisionID]; exists {
|
||||
@@ -530,7 +530,7 @@ func (qs *querySystemImpl) passesFilter(candidate *hopCandidate, filter *HopFilt
|
||||
return false // No decision metadata
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Technology filter
|
||||
if len(filter.Technologies) > 0 && node.Context != nil {
|
||||
found := false
|
||||
@@ -549,7 +549,7 @@ func (qs *querySystemImpl) passesFilter(candidate *hopCandidate, filter *HopFilt
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Tag filter
|
||||
if len(filter.Tags) > 0 && node.Context != nil {
|
||||
found := false
|
||||
@@ -568,32 +568,32 @@ func (qs *querySystemImpl) passesFilter(candidate *hopCandidate, filter *HopFilt
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Influence count filter
|
||||
if filter.MinInfluenceCount > 0 && len(node.Influences) < filter.MinInfluenceCount {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Staleness filter
|
||||
if filter.ExcludeStale && node.Staleness > 0.6 {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Major decisions filter
|
||||
if filter.OnlyMajorDecisions && !qs.isMajorDecision(node) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (qs *querySystemImpl) calculateRelevanceScores(candidates []*hopCandidate, startNode *TemporalNode, query *HopQuery) []*HopResult {
|
||||
results := make([]*HopResult, len(candidates))
|
||||
|
||||
|
||||
for i, candidate := range candidates {
|
||||
relevanceScore := qs.calculateRelevance(candidate, startNode, query)
|
||||
matchReasons := qs.getMatchReasons(candidate, query.FilterCriteria)
|
||||
|
||||
|
||||
results[i] = &HopResult{
|
||||
Address: candidate.node.UCXLAddress,
|
||||
HopDistance: candidate.distance,
|
||||
@@ -605,26 +605,26 @@ func (qs *querySystemImpl) calculateRelevanceScores(candidates []*hopCandidate,
|
||||
Metadata: qs.buildMetadata(candidate, query.IncludeMetadata),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (qs *querySystemImpl) calculateRelevance(candidate *hopCandidate, startNode *TemporalNode, query *HopQuery) float64 {
|
||||
score := 1.0
|
||||
|
||||
|
||||
// Distance-based relevance (closer = more relevant)
|
||||
distanceScore := 1.0 - (float64(candidate.distance-1) / float64(query.MaxHops))
|
||||
score *= distanceScore
|
||||
|
||||
|
||||
// Confidence-based relevance
|
||||
confidenceScore := candidate.node.Confidence
|
||||
score *= confidenceScore
|
||||
|
||||
|
||||
// Recency-based relevance
|
||||
age := time.Since(candidate.node.Timestamp)
|
||||
recencyScore := math.Max(0.1, 1.0-age.Hours()/(30*24)) // Decay over 30 days
|
||||
score *= recencyScore
|
||||
|
||||
|
||||
// Impact-based relevance
|
||||
var impactScore float64
|
||||
switch candidate.node.ImpactScope {
|
||||
@@ -638,14 +638,14 @@ func (qs *querySystemImpl) calculateRelevance(candidate *hopCandidate, startNode
|
||||
impactScore = 0.4
|
||||
}
|
||||
score *= impactScore
|
||||
|
||||
|
||||
return math.Min(1.0, score)
|
||||
}
|
||||
|
||||
func (qs *querySystemImpl) sortResults(results []*HopResult, sortCriteria *HopSort) {
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
var aVal, bVal float64
|
||||
|
||||
|
||||
switch sortCriteria.SortBy {
|
||||
case "hops":
|
||||
aVal, bVal = float64(results[i].HopDistance), float64(results[j].HopDistance)
|
||||
@@ -660,7 +660,7 @@ func (qs *querySystemImpl) sortResults(results []*HopResult, sortCriteria *HopSo
|
||||
default:
|
||||
aVal, bVal = results[i].RelevanceScore, results[j].RelevanceScore
|
||||
}
|
||||
|
||||
|
||||
if sortCriteria.SortDirection == "desc" {
|
||||
return aVal > bVal
|
||||
}
|
||||
@@ -680,7 +680,7 @@ func (qs *querySystemImpl) addForwardNeighbors(current *hopCandidate, queue *[]*
|
||||
if current.distance >= maxHops {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
nodeID := current.node.ID
|
||||
if influences, exists := qs.graph.influences[nodeID]; exists {
|
||||
for _, influencedID := range influences {
|
||||
@@ -692,7 +692,7 @@ func (qs *querySystemImpl) addForwardNeighbors(current *hopCandidate, queue *[]*
|
||||
Relationship: "influences",
|
||||
}
|
||||
newPath := append(current.path, step)
|
||||
|
||||
|
||||
*queue = append(*queue, &hopCandidate{
|
||||
node: influencedNode,
|
||||
distance: current.distance + 1,
|
||||
@@ -707,7 +707,7 @@ func (qs *querySystemImpl) addBackwardNeighbors(current *hopCandidate, queue *[]
|
||||
if current.distance >= maxHops {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
nodeID := current.node.ID
|
||||
if influencedBy, exists := qs.graph.influencedBy[nodeID]; exists {
|
||||
for _, influencerID := range influencedBy {
|
||||
@@ -719,7 +719,7 @@ func (qs *querySystemImpl) addBackwardNeighbors(current *hopCandidate, queue *[]
|
||||
Relationship: "influenced_by",
|
||||
}
|
||||
newPath := append(current.path, step)
|
||||
|
||||
|
||||
*queue = append(*queue, &hopCandidate{
|
||||
node: influencerNode,
|
||||
distance: current.distance + 1,
|
||||
@@ -732,22 +732,22 @@ func (qs *querySystemImpl) addBackwardNeighbors(current *hopCandidate, queue *[]
|
||||
|
||||
func (qs *querySystemImpl) isMajorDecision(node *TemporalNode) bool {
|
||||
return node.ChangeReason == ReasonArchitectureChange ||
|
||||
node.ChangeReason == ReasonDesignDecision ||
|
||||
node.ChangeReason == ReasonRequirementsChange ||
|
||||
node.ImpactScope == ImpactSystem ||
|
||||
node.ImpactScope == ImpactProject
|
||||
node.ChangeReason == ReasonDesignDecision ||
|
||||
node.ChangeReason == ReasonRequirementsChange ||
|
||||
node.ImpactScope == ImpactSystem ||
|
||||
node.ImpactScope == ImpactProject
|
||||
}
|
||||
|
||||
func (qs *querySystemImpl) getMatchReasons(candidate *hopCandidate, filter *HopFilter) []string {
|
||||
reasons := make([]string, 0)
|
||||
|
||||
|
||||
if filter == nil {
|
||||
reasons = append(reasons, "no_filters_applied")
|
||||
return reasons
|
||||
}
|
||||
|
||||
|
||||
node := candidate.node
|
||||
|
||||
|
||||
if len(filter.ChangeReasons) > 0 {
|
||||
for _, reason := range filter.ChangeReasons {
|
||||
if node.ChangeReason == reason {
|
||||
@@ -755,7 +755,7 @@ func (qs *querySystemImpl) getMatchReasons(candidate *hopCandidate, filter *HopF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if len(filter.ImpactScopes) > 0 {
|
||||
for _, scope := range filter.ImpactScopes {
|
||||
if node.ImpactScope == scope {
|
||||
@@ -763,15 +763,15 @@ func (qs *querySystemImpl) getMatchReasons(candidate *hopCandidate, filter *HopF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if filter.MinConfidence > 0 && node.Confidence >= filter.MinConfidence {
|
||||
reasons = append(reasons, fmt.Sprintf("confidence: %.2f >= %.2f", node.Confidence, filter.MinConfidence))
|
||||
}
|
||||
|
||||
|
||||
if filter.MinInfluenceCount > 0 && len(node.Influences) >= filter.MinInfluenceCount {
|
||||
reasons = append(reasons, fmt.Sprintf("influence_count: %d >= %d", len(node.Influences), filter.MinInfluenceCount))
|
||||
}
|
||||
|
||||
|
||||
return reasons
|
||||
}
|
||||
|
||||
@@ -779,7 +779,7 @@ func (qs *querySystemImpl) determineRelationship(candidate *hopCandidate, startN
|
||||
if len(candidate.path) == 0 {
|
||||
return "self"
|
||||
}
|
||||
|
||||
|
||||
// Look at the last step in the path
|
||||
lastStep := candidate.path[len(candidate.path)-1]
|
||||
return lastStep.Relationship
|
||||
@@ -787,12 +787,12 @@ func (qs *querySystemImpl) determineRelationship(candidate *hopCandidate, startN
|
||||
|
||||
func (qs *querySystemImpl) buildMetadata(candidate *hopCandidate, includeDetailed bool) map[string]interface{} {
|
||||
metadata := make(map[string]interface{})
|
||||
|
||||
|
||||
metadata["hop_distance"] = candidate.distance
|
||||
metadata["path_length"] = len(candidate.path)
|
||||
metadata["node_id"] = candidate.node.ID
|
||||
metadata["decision_id"] = candidate.node.DecisionID
|
||||
|
||||
|
||||
if includeDetailed {
|
||||
metadata["timestamp"] = candidate.node.Timestamp
|
||||
metadata["change_reason"] = candidate.node.ChangeReason
|
||||
@@ -801,19 +801,19 @@ func (qs *querySystemImpl) buildMetadata(candidate *hopCandidate, includeDetaile
|
||||
metadata["staleness"] = candidate.node.Staleness
|
||||
metadata["influence_count"] = len(candidate.node.Influences)
|
||||
metadata["influenced_by_count"] = len(candidate.node.InfluencedBy)
|
||||
|
||||
|
||||
if candidate.node.Context != nil {
|
||||
metadata["context_summary"] = candidate.node.Context.Summary
|
||||
metadata["technologies"] = candidate.node.Context.Technologies
|
||||
metadata["tags"] = candidate.node.Context.Tags
|
||||
}
|
||||
|
||||
|
||||
if decision, exists := qs.graph.decisions[candidate.node.DecisionID]; exists {
|
||||
metadata["decision_maker"] = decision.Maker
|
||||
metadata["decision_rationale"] = decision.Rationale
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
@@ -823,26 +823,26 @@ func (qs *querySystemImpl) validateQuery(query *HopQuery) error {
|
||||
if err := query.StartAddress.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid start address: %w", err)
|
||||
}
|
||||
|
||||
|
||||
if query.MaxHops < 1 || query.MaxHops > 20 {
|
||||
return fmt.Errorf("max hops must be between 1 and 20")
|
||||
}
|
||||
|
||||
|
||||
if query.Direction != "" && query.Direction != "forward" && query.Direction != "backward" && query.Direction != "both" {
|
||||
return fmt.Errorf("direction must be 'forward', 'backward', or 'both'")
|
||||
}
|
||||
|
||||
|
||||
if query.Limit < 0 {
|
||||
return fmt.Errorf("limit cannot be negative")
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qs *querySystemImpl) generateCacheKey(query *HopQuery) string {
|
||||
return fmt.Sprintf("hop_query_%s_%d_%s_%v",
|
||||
query.StartAddress.String(),
|
||||
query.MaxHops,
|
||||
return fmt.Sprintf("hop_query_%s_%d_%s_%v",
|
||||
query.StartAddress.String(),
|
||||
query.MaxHops,
|
||||
query.Direction,
|
||||
query.FilterCriteria != nil)
|
||||
}
|
||||
@@ -850,7 +850,7 @@ func (qs *querySystemImpl) generateCacheKey(query *HopQuery) string {
|
||||
func (qs *querySystemImpl) getFromCache(key string) (interface{}, bool) {
|
||||
qs.mu.RLock()
|
||||
defer qs.mu.RUnlock()
|
||||
|
||||
|
||||
value, exists := qs.queryCache[key]
|
||||
return value, exists
|
||||
}
|
||||
@@ -858,36 +858,36 @@ func (qs *querySystemImpl) getFromCache(key string) (interface{}, bool) {
|
||||
func (qs *querySystemImpl) setCache(key string, value interface{}) {
|
||||
qs.mu.Lock()
|
||||
defer qs.mu.Unlock()
|
||||
|
||||
|
||||
// Clean cache if needed
|
||||
if time.Since(qs.lastCacheClean) > qs.cacheTimeout {
|
||||
qs.queryCache = make(map[string]interface{})
|
||||
qs.lastCacheClean = time.Now()
|
||||
}
|
||||
|
||||
|
||||
qs.queryCache[key] = value
|
||||
}
|
||||
|
||||
func (qs *querySystemImpl) updateQueryStats(queryType string, duration time.Duration, cacheHit bool) {
|
||||
qs.mu.Lock()
|
||||
defer qs.mu.Unlock()
|
||||
|
||||
|
||||
stats, exists := qs.queryStats[queryType]
|
||||
if !exists {
|
||||
stats = &QueryStatistics{QueryType: queryType}
|
||||
qs.queryStats[queryType] = stats
|
||||
}
|
||||
|
||||
|
||||
stats.TotalQueries++
|
||||
stats.LastQuery = time.Now()
|
||||
|
||||
|
||||
// Update average time
|
||||
if stats.AverageTime == 0 {
|
||||
stats.AverageTime = duration
|
||||
} else {
|
||||
stats.AverageTime = (stats.AverageTime + duration) / 2
|
||||
}
|
||||
|
||||
|
||||
if cacheHit {
|
||||
stats.CacheHits++
|
||||
} else {
|
||||
@@ -901,42 +901,42 @@ func (qs *querySystemImpl) updateQueryStats(queryType string, duration time.Dura
|
||||
|
||||
// DecisionGenealogy represents the genealogy of decisions for a context
|
||||
type DecisionGenealogy struct {
|
||||
Address ucxl.Address `json:"address"`
|
||||
DirectAncestors []ucxl.Address `json:"direct_ancestors"`
|
||||
AllAncestors []ucxl.Address `json:"all_ancestors"`
|
||||
DirectDescendants []ucxl.Address `json:"direct_descendants"`
|
||||
AllDescendants []ucxl.Address `json:"all_descendants"`
|
||||
InfluentialAncestors []*InfluentialAncestor `json:"influential_ancestors"`
|
||||
GenealogyDepth int `json:"genealogy_depth"`
|
||||
BranchingFactor float64 `json:"branching_factor"`
|
||||
DecisionTimeline *DecisionTimeline `json:"decision_timeline"`
|
||||
Metrics *GenealogyMetrics `json:"metrics"`
|
||||
AnalyzedAt time.Time `json:"analyzed_at"`
|
||||
Address ucxl.Address `json:"address"`
|
||||
DirectAncestors []ucxl.Address `json:"direct_ancestors"`
|
||||
AllAncestors []ucxl.Address `json:"all_ancestors"`
|
||||
DirectDescendants []ucxl.Address `json:"direct_descendants"`
|
||||
AllDescendants []ucxl.Address `json:"all_descendants"`
|
||||
InfluentialAncestors []*InfluentialAncestor `json:"influential_ancestors"`
|
||||
GenealogyDepth int `json:"genealogy_depth"`
|
||||
BranchingFactor float64 `json:"branching_factor"`
|
||||
DecisionTimeline *DecisionTimeline `json:"decision_timeline"`
|
||||
Metrics *GenealogyMetrics `json:"metrics"`
|
||||
AnalyzedAt time.Time `json:"analyzed_at"`
|
||||
}
|
||||
|
||||
// Additional supporting types for genealogy and similarity analysis...
|
||||
type InfluentialAncestor struct {
|
||||
Address ucxl.Address `json:"address"`
|
||||
InfluenceScore float64 `json:"influence_score"`
|
||||
GenerationsBack int `json:"generations_back"`
|
||||
InfluenceType string `json:"influence_type"`
|
||||
Address ucxl.Address `json:"address"`
|
||||
InfluenceScore float64 `json:"influence_score"`
|
||||
GenerationsBack int `json:"generations_back"`
|
||||
InfluenceType string `json:"influence_type"`
|
||||
}
|
||||
|
||||
type GenealogyMetrics struct {
|
||||
TotalAncestors int `json:"total_ancestors"`
|
||||
TotalDescendants int `json:"total_descendants"`
|
||||
MaxDepth int `json:"max_depth"`
|
||||
AverageBranching float64 `json:"average_branching"`
|
||||
InfluenceSpread float64 `json:"influence_spread"`
|
||||
TotalAncestors int `json:"total_ancestors"`
|
||||
TotalDescendants int `json:"total_descendants"`
|
||||
MaxDepth int `json:"max_depth"`
|
||||
AverageBranching float64 `json:"average_branching"`
|
||||
InfluenceSpread float64 `json:"influence_spread"`
|
||||
}
|
||||
|
||||
type SimilarDecisionMatch struct {
|
||||
Address ucxl.Address `json:"address"`
|
||||
TemporalNode *TemporalNode `json:"temporal_node"`
|
||||
SimilarityScore float64 `json:"similarity_score"`
|
||||
SimilarityReasons []string `json:"similarity_reasons"`
|
||||
PatternType string `json:"pattern_type"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
Address ucxl.Address `json:"address"`
|
||||
TemporalNode *TemporalNode `json:"temporal_node"`
|
||||
SimilarityScore float64 `json:"similarity_score"`
|
||||
SimilarityReasons []string `json:"similarity_reasons"`
|
||||
PatternType string `json:"pattern_type"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
}
|
||||
|
||||
// Placeholder implementations for the analysis methods
|
||||
@@ -978,10 +978,10 @@ func (qs *querySystemImpl) identifyPatternType(node1, node2 *TemporalNode) strin
|
||||
func (qs *querySystemImpl) convertCommunityToCluster(community Community) *DecisionCluster {
|
||||
// Implementation would convert community to decision cluster
|
||||
return &DecisionCluster{
|
||||
ID: community.ID,
|
||||
Decisions: community.Nodes,
|
||||
ID: community.ID,
|
||||
Decisions: community.Nodes,
|
||||
ClusterSize: len(community.Nodes),
|
||||
Cohesion: community.Modularity,
|
||||
Cohesion: community.Modularity,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -996,4 +996,4 @@ type descendantAnalysis struct {
|
||||
DirectDescendants []ucxl.Address
|
||||
AllDescendants []ucxl.Address
|
||||
BranchingFactor float64
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user