< Summary

Information
Class: Intentum.Core.Behavior.BehaviorSpace
Assembly: Intentum.Core
File(s): /home/runner/work/Intentum/Intentum/src/Intentum.Core/Behavior/BehaviorSpace.cs
Line coverage
96%
Covered lines: 92
Uncovered lines: 3
Coverable lines: 95
Total lines: 158
Line coverage: 96.8%
Branch coverage
81%
Covered branches: 39
Total branches: 48
Branch coverage: 81.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%11100%
Observe(...)100%11100%
SetMetadata(...)100%11100%
GetMetadata(...)100%44100%
GetEventsInWindow(...)100%22100%
GetEventsInWindow(...)100%11100%
GetTimeSpan()100%22100%
ToVector()100%11100%
ToVector(...)100%44100%
ToVector(...)100%11100%
ToVector(...)100%22100%
GetCacheKey(...)100%22100%
UseCache(...)83.33%66100%
ApplyOptions(...)56.25%161684.61%
ApplyCap(...)100%44100%
ApplySoftCap(...)100%22100%
ApplyL1(...)75%4487.5%

File(s)

/home/runner/work/Intentum/Intentum/src/Intentum.Core/Behavior/BehaviorSpace.cs

#LineLine coverage
 1namespace Intentum.Core.Behavior;
 2
 3/// <summary>
 4/// Observed behavior space; replaces scenario-based tests.
 5/// </summary>
 6public sealed class BehaviorSpace
 7{
 3508    private readonly List<BehaviorEvent> _events = [];
 3509    private readonly Dictionary<string, object> _metadata = new();
 35010    private readonly Dictionary<string, BehaviorVector> _cachedVectors = new();
 11
 12    /// <summary>All observed behavior events in order.</summary>
 36313    public IReadOnlyCollection<BehaviorEvent> Events => _events;
 14
 15    /// <summary>Metadata associated with this behavior space (e.g., sector, domain, session ID).</summary>
 4716    public IReadOnlyDictionary<string, object> Metadata => _metadata;
 17
 18    /// <summary>Records a single behavior event (actor and action). Invalidates all cached vectors.</summary>
 19    public void Observe(BehaviorEvent behaviorEvent)
 63820    {
 63821        _cachedVectors.Clear();
 63822        _events.Add(behaviorEvent);
 63823    }
 24
 25    /// <summary>Sets metadata for this behavior space.</summary>
 26    public void SetMetadata(string key, object value)
 2927        => _metadata[key] = value;
 28
 29    /// <summary>Gets metadata value by key.</summary>
 30    public T? GetMetadata<T>(string key)
 5231        => _metadata.TryGetValue(key, out var value) && value is T typed ? typed : default;
 32
 33    /// <summary>Gets events within a time window.</summary>
 34    public IReadOnlyCollection<BehaviorEvent> GetEventsInWindow(DateTimeOffset start, DateTimeOffset end)
 935    {
 936        return _events
 2137            .Where(e => e.OccurredAt >= start && e.OccurredAt <= end)
 938            .ToList();
 939    }
 40
 41    /// <summary>Gets events within a time window relative to now.</summary>
 42    public IReadOnlyCollection<BehaviorEvent> GetEventsInWindow(TimeSpan window)
 143    {
 144        var end = DateTimeOffset.UtcNow;
 145        var start = end - window;
 146        return GetEventsInWindow(start, end);
 147    }
 48
 49    /// <summary>Gets the time span of all events.</summary>
 50    public TimeSpan? GetTimeSpan()
 251    {
 252        if (_events.Count == 0)
 153            return null;
 54
 355        var earliest = _events.Min(e => e.OccurredAt);
 356        var latest = _events.Max(e => e.OccurredAt);
 157        return latest - earliest;
 258    }
 59
 60    /// <summary>Builds a behavior vector from observed events (actor:action keys with counts). Result is cached until O
 61    public BehaviorVector ToVector()
 1562        => ToVector(null);
 63
 64    /// <summary>Builds a behavior vector with optional normalization/cap. Result is cached based on options hash.</summ
 65    public BehaviorVector ToVector(ToVectorOptions? options)
 12866    {
 12867        var cacheKey = GetCacheKey(options);
 68
 12869        if (_cachedVectors.TryGetValue(cacheKey, out var cached))
 1170            return cached;
 71
 11772        var dimensions = new Dictionary<string, double>();
 73
 94574        foreach (var evt in _events)
 29775        {
 29776            var key = $"{evt.Actor}:{evt.Action}";
 29777            dimensions[key] = dimensions.GetValueOrDefault(key) + 1;
 29778        }
 79
 11780        dimensions = ApplyOptions(dimensions, options);
 11781        var result = new BehaviorVector(dimensions);
 11782        _cachedVectors[cacheKey] = result;
 11783        return result;
 12884    }
 85
 86    /// <summary>Builds a behavior vector from events within a time window.</summary>
 87    public BehaviorVector ToVector(DateTimeOffset start, DateTimeOffset end)
 188        => ToVector(start, end, null);
 89
 90    /// <summary>Builds a behavior vector from events within a time window with optional normalization.</summary>
 91    public BehaviorVector ToVector(DateTimeOffset start, DateTimeOffset end, ToVectorOptions? options)
 292    {
 293        var dimensions = new Dictionary<string, double>();
 294        var eventsInWindow = GetEventsInWindow(start, end);
 95
 1496        foreach (var evt in eventsInWindow)
 497        {
 498            var key = $"{evt.Actor}:{evt.Action}";
 499            dimensions[key] = dimensions.GetValueOrDefault(key) + 1;
 4100        }
 101
 2102        dimensions = ApplyOptions(dimensions, options);
 2103        return new BehaviorVector(dimensions);
 2104    }
 105
 106    private static string GetCacheKey(ToVectorOptions? options)
 128107    {
 249108        if (options == null) return "default";
 7109        return $"{options.Normalization}_{options.CapPerDimension}";
 128110    }
 111
 112    private static bool UseCache(ToVectorOptions? options)
 119113        => options == null || options is { Normalization: VectorNormalization.None, CapPerDimension: <= 0 };
 114
 115    private static Dictionary<string, double> ApplyOptions(Dictionary<string, double> dimensions, ToVectorOptions? optio
 119116    {
 119117        if (UseCache(options))
 112118            return dimensions;
 119
 7120        var cap = options!.CapPerDimension > 0 ? options.CapPerDimension : (double?)null;
 121
 7122        return options.Normalization switch
 7123        {
 4124            VectorNormalization.Cap when cap.HasValue => ApplyCap(dimensions, cap.Value),
 2125            VectorNormalization.SoftCap when cap.HasValue => ApplySoftCap(dimensions, cap.Value),
 4126            VectorNormalization.L1 => ApplyL1(dimensions),
 0127            _ when cap.HasValue && options.Normalization == VectorNormalization.None => ApplyCap(dimensions, cap.Value),
 0128            _ => dimensions
 7129        };
 119130    }
 131
 132    private static Dictionary<string, double> ApplyCap(Dictionary<string, double> dimensions, double cap)
 2133    {
 12134        foreach (var k in dimensions.Keys.ToList())
 3135        {
 3136            if (dimensions[k] > cap)
 1137                dimensions[k] = cap;
 3138        }
 2139        return dimensions;
 2140    }
 141
 142    private static Dictionary<string, double> ApplySoftCap(Dictionary<string, double> dimensions, double cap)
 1143    {
 7144        foreach (var k in dimensions.Keys.ToList())
 2145            dimensions[k] = Math.Min(1.0, dimensions[k] / cap);
 1146        return dimensions;
 1147    }
 148
 149    private static Dictionary<string, double> ApplyL1(Dictionary<string, double> dimensions)
 4150    {
 4151        var sum = dimensions.Values.Sum();
 4152        if (sum <= 0)
 0153            return dimensions;
 24154        foreach (var k in dimensions.Keys.ToList())
 6155            dimensions[k] /= sum;
 4156        return dimensions;
 4157    }
 158}