AID-D-002.002: Labeled Performance Tracking with Ground Truth Feedback Loop

Version 1.20261015 · Actionable sub-technique

Track live model quality by joining predictions with delayed or human-provided ground truth labels and comparing the resulting metrics to validation baselines. This sub-technique applies only when you have a real correctness feedback loop, such as fraud confirmations, abuse-review outcomes, claim settlements, or human adjudication.

Pillars: model

Lifecycle: operation, validation

Scope boundary

Owns production quality tracking based on a real ground-truth feedback loop, including delayed label joins, metric comparison, label coverage, and feedback-population integrity.

AID-D-002.001: AID-D-002.002 measures correctness against trusted labels; AID-D-002.001 measures unlabeled input and output distribution shift. Ground-truth performance is a direct quality claim, while drift statistics are proxy signals that apply when labels are absent or delayed.

Implementation guidance

AID-D-002.002-G001

Monitor model performance metrics by comparing predictions to ground truth labels.
Reusable prediction-label join

Never inner-join predictions to available labels and report the surviving metric as fleet performance. Preserve unlabeled, late, orphaned, duplicate, abstained, and errored records; evaluate metrics only on predictions whose signed label-delay window has matured.

from __future__ import annotations

# File: detection/labeled_performance.py

from datetime import datetime, timedelta, timezone

import pandas as pd
from sklearn.metrics import accuracy_score, f1_score


def evaluate_labeled_performance(
    predictions: pd.DataFrame,
    labels: pd.DataFrame,
    policy: dict,
    *,
    as_of: datetime,
) -> dict:
    required = {
        "policy_version", "label_delay_seconds", "minimum_labeled_predictions",
        "maximum_unlabeled_rate", "minimum_accuracy", "minimum_f1",
        "positive_label",
    }
    if set(policy) != required:
        raise ValueError("performance policy schema mismatch")
    if predictions["prediction_id"].duplicated().any() or labels["prediction_id"].duplicated().any():
        raise ValueError("duplicate prediction or label identity")
    expected_prediction_columns = {
        "prediction_id", "predicted_label", "predicted_at", "prediction_status",
    }
    expected_label_columns = {"prediction_id", "label", "labeled_at"}
    if not expected_prediction_columns.issubset(predictions) or not expected_label_columns.issubset(labels):
        raise ValueError("prediction or label schema mismatch")
    current = as_of.astimezone(timezone.utc)
    cutoff = current - timedelta(seconds=int(policy["label_delay_seconds"]))
    pred = predictions.copy()
    pred["predicted_at"] = pd.to_datetime(pred["predicted_at"], utc=True, errors="raise")
    lab = labels.copy()
    lab["labeled_at"] = pd.to_datetime(lab["labeled_at"], utc=True, errors="raise")
    eligible = pred[pred["predicted_at"] <= cutoff].copy()
    joined = eligible.merge(lab, on="prediction_id", how="left", validate="one_to_one", indicator=True)
    orphan_labels = sorted(set(lab["prediction_id"]) - set(pred["prediction_id"]))
    scored = joined[
        (joined["_merge"] == "both")
        & (joined["prediction_status"] == "PREDICTED")
        & joined["predicted_label"].notna()
        & joined["label"].notna()
    ]
    eligible_count = len(joined)
    labeled_count = int((joined["_merge"] == "both").sum())
    unlabeled_count = eligible_count - labeled_count
    unlabeled_rate = unlabeled_count / max(1, eligible_count)
    if eligible_count == 0 or len(scored) < int(policy["minimum_labeled_predictions"]):
        return {
            "control_status": "INSUFFICIENT_DATA",
            "finding_status": "UNKNOWN",
            "eligible_predictions": eligible_count,
            "labeled_predictions": labeled_count,
            "scored_predictions": len(scored),
            "unlabeled_predictions": unlabeled_count,
            "orphan_label_ids": orphan_labels,
            "policy_version": policy["policy_version"],
        }
    accuracy = float(accuracy_score(scored["label"], scored["predicted_label"]))
    f1 = float(
        f1_score(
            scored["label"],
            scored["predicted_label"],
            pos_label=policy["positive_label"],
            zero_division=0,
        )
    )
    reasons = []
    if unlabeled_rate > float(policy["maximum_unlabeled_rate"]):
        reasons.append("label_coverage_degraded")
    if accuracy < float(policy["minimum_accuracy"]):
        reasons.append("accuracy_below_policy")
    if f1 < float(policy["minimum_f1"]):
        reasons.append("f1_below_policy")
    if orphan_labels:
        reasons.append("orphan_ground_truth_labels")
    return {
        "schema_version": "aidefend.labeled_performance.v1",
        "control_status": "PASS",
        "finding_status": "FINDING" if reasons else "NO_FINDING",
        "policy_version": policy["policy_version"],
        "as_of": current.isoformat(),
        "eligible_predictions": eligible_count,
        "labeled_predictions": labeled_count,
        "scored_predictions": len(scored),
        "unlabeled_predictions": unlabeled_count,
        "unlabeled_rate": round(unlabeled_rate, 8),
        "orphan_label_ids": orphan_labels,
        "accuracy": round(accuracy, 8),
        "f1": round(f1, 8),
        "reasons": reasons,
    }

Verification: Replay matched, unlabeled, late-label, orphan-label, abstention, prediction-error, duplicate-ID, empty, and degraded-performance fixtures. Rebuild both source populations independently and verify that missing labels never disappear from the denominator.

Action: Run the join only after the signed maturity delay, retain every unmatched state, and route coverage and performance findings without triggering retraining from this detector.

Open-source tools

Commercial / hosted tools

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-D-002.002: Labeled Performance Tracking with Ground Truth Feedback Loop | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version