feat(metrics): Implement scan metrics repository and PostgreSQL integration
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added IScanMetricsRepository interface for scan metrics persistence and retrieval. - Implemented PostgresScanMetricsRepository for PostgreSQL database interactions, including methods for saving and retrieving scan metrics and execution phases. - Introduced methods for obtaining TTE statistics and recent scans for tenants. - Implemented deletion of old metrics for retention purposes. test(tests): Add SCA Failure Catalogue tests for FC6-FC10 - Created ScaCatalogueDeterminismTests to validate determinism properties of SCA Failure Catalogue fixtures. - Developed ScaFailureCatalogueTests to ensure correct handling of specific failure modes in the scanner. - Included tests for manifest validation, file existence, and expected findings across multiple failure cases. feat(telemetry): Integrate scan completion metrics into the pipeline - Introduced IScanCompletionMetricsIntegration interface and ScanCompletionMetricsIntegration class to record metrics upon scan completion. - Implemented proof coverage and TTE metrics recording with logging for scan completion summaries.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// FreshnessAwareScoringService.cs
|
||||
// Sprint: SPRINT_3401_0001_0001_determinism_scoring_foundations
|
||||
// Task: DET-3401-003
|
||||
// Description: Integrates freshness multiplier into evidence scoring pipeline
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace StellaOps.Policy.Scoring;
|
||||
|
||||
/// <summary>
|
||||
/// Integrates evidence freshness into the scoring pipeline.
|
||||
/// </summary>
|
||||
public interface IFreshnessAwareScoringService
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies freshness adjustment to a finding's score.
|
||||
/// </summary>
|
||||
/// <param name="baseScore">Original score (0-100 or 0-10 depending on scale).</param>
|
||||
/// <param name="evidenceTimestamp">When the evidence was collected.</param>
|
||||
/// <param name="evaluationTime">Time of evaluation (for deterministic replay).</param>
|
||||
/// <returns>Adjusted score with explanation.</returns>
|
||||
FreshnessAdjustedScore AdjustForFreshness(
|
||||
int baseScore,
|
||||
DateTimeOffset evidenceTimestamp,
|
||||
DateTimeOffset evaluationTime);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the freshness bucket for a given evidence age.
|
||||
/// </summary>
|
||||
FreshnessBucketResult GetFreshnessBucket(
|
||||
DateTimeOffset evidenceTimestamp,
|
||||
DateTimeOffset evaluationTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of freshness adjustment with explanation data.
|
||||
/// </summary>
|
||||
/// <param name="OriginalScore">The score before freshness adjustment.</param>
|
||||
/// <param name="AdjustedScore">The score after freshness adjustment.</param>
|
||||
/// <param name="MultiplierBps">The multiplier applied (basis points).</param>
|
||||
/// <param name="EvidenceAgeDays">Age of the evidence in days.</param>
|
||||
/// <param name="BucketName">Name of the freshness bucket.</param>
|
||||
public sealed record FreshnessAdjustedScore(
|
||||
int OriginalScore,
|
||||
int AdjustedScore,
|
||||
int MultiplierBps,
|
||||
int EvidenceAgeDays,
|
||||
string BucketName);
|
||||
|
||||
/// <summary>
|
||||
/// Result of freshness bucket lookup.
|
||||
/// </summary>
|
||||
/// <param name="AgeDays">Age of evidence in days.</param>
|
||||
/// <param name="BucketName">Human-readable bucket name.</param>
|
||||
/// <param name="MultiplierBps">Multiplier in basis points.</param>
|
||||
/// <param name="MultiplierPercent">Multiplier as percentage.</param>
|
||||
public sealed record FreshnessBucketResult(
|
||||
int AgeDays,
|
||||
string BucketName,
|
||||
int MultiplierBps,
|
||||
decimal MultiplierPercent);
|
||||
|
||||
public sealed class FreshnessAwareScoringService : IFreshnessAwareScoringService
|
||||
{
|
||||
private readonly EvidenceFreshnessCalculator _calculator;
|
||||
private readonly ILogger<FreshnessAwareScoringService> _logger;
|
||||
|
||||
public FreshnessAwareScoringService(
|
||||
FreshnessMultiplierConfig? config = null,
|
||||
ILogger<FreshnessAwareScoringService>? logger = null)
|
||||
{
|
||||
_calculator = new EvidenceFreshnessCalculator(config);
|
||||
_logger = logger ?? Microsoft.Extensions.Logging.Abstractions.NullLogger<FreshnessAwareScoringService>.Instance;
|
||||
}
|
||||
|
||||
public FreshnessAdjustedScore AdjustForFreshness(
|
||||
int baseScore,
|
||||
DateTimeOffset evidenceTimestamp,
|
||||
DateTimeOffset evaluationTime)
|
||||
{
|
||||
var ageDays = (int)(evaluationTime - evidenceTimestamp).TotalDays;
|
||||
if (ageDays < 0) ageDays = 0;
|
||||
|
||||
var multiplierBps = _calculator.CalculateMultiplierBps(evidenceTimestamp, evaluationTime);
|
||||
var adjustedScore = _calculator.ApplyFreshness(baseScore, evidenceTimestamp, evaluationTime);
|
||||
var bucketName = GetBucketName(ageDays);
|
||||
|
||||
_logger.LogDebug(
|
||||
"Freshness adjustment: base={BaseScore}, adjusted={AdjustedScore}, age={AgeDays}d, bucket={Bucket}, multiplier={Mult}bps",
|
||||
baseScore, adjustedScore, ageDays, bucketName, multiplierBps);
|
||||
|
||||
return new FreshnessAdjustedScore(
|
||||
OriginalScore: baseScore,
|
||||
AdjustedScore: adjustedScore,
|
||||
MultiplierBps: multiplierBps,
|
||||
EvidenceAgeDays: ageDays,
|
||||
BucketName: bucketName);
|
||||
}
|
||||
|
||||
public FreshnessBucketResult GetFreshnessBucket(
|
||||
DateTimeOffset evidenceTimestamp,
|
||||
DateTimeOffset evaluationTime)
|
||||
{
|
||||
var ageDays = (int)(evaluationTime - evidenceTimestamp).TotalDays;
|
||||
if (ageDays < 0) ageDays = 0;
|
||||
|
||||
var multiplierBps = _calculator.CalculateMultiplierBps(evidenceTimestamp, evaluationTime);
|
||||
var bucketName = GetBucketName(ageDays);
|
||||
var multiplierPercent = multiplierBps / 100m;
|
||||
|
||||
return new FreshnessBucketResult(
|
||||
AgeDays: ageDays,
|
||||
BucketName: bucketName,
|
||||
MultiplierBps: multiplierBps,
|
||||
MultiplierPercent: multiplierPercent);
|
||||
}
|
||||
|
||||
private static string GetBucketName(int ageDays) => ageDays switch
|
||||
{
|
||||
<= 7 => "fresh_7d",
|
||||
<= 30 => "recent_30d",
|
||||
<= 90 => "moderate_90d",
|
||||
<= 180 => "aging_180d",
|
||||
<= 365 => "stale_365d",
|
||||
_ => "ancient"
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user