< Summary

Information
Class: Intentum.AI.Similarity.TimeDecaySimilarityEngine
Assembly: Intentum.AI
File(s): /home/runner/work/Intentum/Intentum/src/Intentum.AI/Similarity/TimeDecaySimilarityEngine.cs
Line coverage
95%
Covered lines: 41
Uncovered lines: 2
Coverable lines: 43
Total lines: 91
Line coverage: 95.3%
Branch coverage
83%
Covered branches: 20
Total branches: 24
Branch coverage: 83.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%44100%
CalculateIntentScoreWithTimeDecay(...)81.25%161696%
CalculateIntentScore(...)50%2280%
CalculateDecayFactor(...)100%22100%

File(s)

/home/runner/work/Intentum/Intentum/src/Intentum.AI/Similarity/TimeDecaySimilarityEngine.cs

#LineLine coverage
 1using Intentum.AI.Embeddings;
 2using Intentum.Core.Behavior;
 3
 4namespace Intentum.AI.Similarity;
 5
 6/// <summary>
 7/// Similarity engine that applies time-based decay to embeddings.
 8/// More recent events have higher influence on intent inference.
 9/// When used with LlmIntentModel, time decay is applied automatically (ITimeAwareSimilarityEngine).
 10/// </summary>
 11public sealed class TimeDecaySimilarityEngine : ITimeAwareSimilarityEngine
 12{
 13    private readonly TimeSpan _halfLife;
 14    private readonly DateTimeOffset _referenceTime;
 15
 16    /// <summary>
 17    /// Creates a time decay similarity engine.
 18    /// </summary>
 19    /// <param name="halfLife">Time span after which an event's weight is halved. Defaults to 1 hour.</param>
 20    /// <param name="referenceTime">Reference time for decay calculation. Defaults to UtcNow.</param>
 421    public TimeDecaySimilarityEngine(
 422        TimeSpan? halfLife = null,
 423        DateTimeOffset? referenceTime = null)
 424    {
 425        _halfLife = halfLife ?? TimeSpan.FromHours(1);
 426        _referenceTime = referenceTime ?? DateTimeOffset.UtcNow;
 427    }
 28
 29    /// <summary>
 30    /// Calculates intent score with time decay applied.
 31    /// Note: This overload requires BehaviorSpace to access timestamps.
 32    /// </summary>
 33    public double CalculateIntentScoreWithTimeDecay(
 34        BehaviorSpace behaviorSpace,
 35        IReadOnlyCollection<IntentEmbedding> embeddings)
 336    {
 337        if (embeddings.Count == 0 || behaviorSpace.Events.Count == 0)
 138            return 0;
 39
 40        // Create a mapping from source (actor:action) to the most recent timestamp
 241        var sourceToTimestamp = new Dictionary<string, DateTimeOffset>();
 1242        foreach (var evt in behaviorSpace.Events)
 343        {
 344            var source = $"{evt.Actor}:{evt.Action}";
 345            if (!sourceToTimestamp.TryGetValue(source, out var existing) || evt.OccurredAt > existing)
 346            {
 347                sourceToTimestamp[source] = evt.OccurredAt;
 348            }
 349        }
 50
 251        var totalWeightedScore = 0.0;
 252        var totalWeight = 0.0;
 53
 1254        foreach (var embedding in embeddings)
 355        {
 356            if (!sourceToTimestamp.TryGetValue(embedding.Source, out var timestamp))
 057                continue;
 58
 359            var age = _referenceTime - timestamp;
 360            var decayFactor = CalculateDecayFactor(age);
 61
 362            totalWeightedScore += embedding.Score * decayFactor;
 363            totalWeight += decayFactor;
 364        }
 65
 266        return totalWeight > 0 ? totalWeightedScore / totalWeight : 0;
 367    }
 68
 69    /// <summary>
 70    /// Standard interface implementation - uses simple average (no time decay).
 71    /// Use CalculateIntentScoreWithTimeDecay for time-based decay.
 72    /// </summary>
 73    public double CalculateIntentScore(IReadOnlyCollection<IntentEmbedding> embeddings)
 174    {
 75        // Fallback to simple average when timestamps are not available
 176        if (embeddings.Count == 0)
 077            return 0;
 78
 379        return embeddings.Average(e => e.Score);
 180    }
 81
 82    private double CalculateDecayFactor(TimeSpan age)
 383    {
 384        if (age <= TimeSpan.Zero)
 185            return 1.0;
 86
 87        // Exponential decay: weight = 2^(-age/halfLife)
 288        var halfLifeCount = age.TotalMilliseconds / _halfLife.TotalMilliseconds;
 289        return Math.Pow(2, -halfLifeCount);
 390    }
 91}