@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
import django
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||
django.setup()
|
||||
|
||||
from apps.profiles.models import SearchProfile
|
||||
|
||||
|
||||
def _load_profile(profile_id: int | None, username: str | None) -> SearchProfile:
|
||||
if profile_id:
|
||||
return SearchProfile.objects.get(pk=profile_id)
|
||||
if username:
|
||||
profile = SearchProfile.objects.filter(user__username=username, is_active=True).first()
|
||||
if profile is None:
|
||||
raise ValueError(f"Geen actief profiel gevonden voor gebruiker {username}")
|
||||
return profile
|
||||
raise ValueError("Kies --profile-id of --user.")
|
||||
|
||||
|
||||
def _collect_feedback_churn(profile: SearchProfile) -> dict:
|
||||
from apps.jobs.models import Feedback
|
||||
|
||||
all_feedback = Feedback.objects.filter(profile=profile).order_by("created_at")
|
||||
status = Counter()
|
||||
features = Counter()
|
||||
sample_count = 0
|
||||
false_negatives = []
|
||||
|
||||
for feedback in all_feedback:
|
||||
learning = feedback.metadata.get("learning") if isinstance(feedback.metadata, dict) else None
|
||||
if not isinstance(learning, dict):
|
||||
continue
|
||||
reason_code = learning.get("reason_code", "")
|
||||
status[learning.get("status", "")] += 1
|
||||
if feature := learning.get("feature"):
|
||||
features[feature] += 1
|
||||
sample_count += 1
|
||||
if learning.get("status") == "queued" and reason_code.startswith("non_learning_"):
|
||||
false_negatives.append(
|
||||
{
|
||||
"job_id": str(feedback.job_id),
|
||||
"reason_code": reason_code,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"samples": dict(status),
|
||||
"features": dict(features),
|
||||
"total_feedback": all_feedback.count(),
|
||||
"sample_count": sample_count,
|
||||
"queued_non_learning_signals": false_negatives[:25],
|
||||
}
|
||||
|
||||
|
||||
def _collect_weight_churn(profile: SearchProfile) -> list[dict[str, float]]:
|
||||
revisions = list(profile.revisions.order_by("version"))
|
||||
if len(revisions) < 2:
|
||||
return []
|
||||
|
||||
base = revisions[0].snapshot
|
||||
latest = revisions[-1].snapshot
|
||||
base_weights = base.get("weights", {})
|
||||
latest_weights = latest.get("weights", {})
|
||||
churn = []
|
||||
for feature in sorted(set(base_weights) | set(latest_weights)):
|
||||
before = float(base_weights.get(feature, 0.0))
|
||||
after = float(latest_weights.get(feature, 0.0))
|
||||
if before == after:
|
||||
continue
|
||||
churn.append({"feature": feature, "delta": round(after - before, 3)})
|
||||
return sorted(churn, key=lambda item: abs(item["delta"]), reverse=True)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--profile-id", type=int, default=None)
|
||||
parser.add_argument("--user", default=None)
|
||||
parser.add_argument("--top", type=int, default=5)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if not args.profile_id and not args.user:
|
||||
raise SystemExit("Gebruik --profile-id of --user.")
|
||||
|
||||
profile = _load_profile(args.profile_id, args.user)
|
||||
feedback_report = _collect_feedback_churn(profile)
|
||||
churn = _collect_weight_churn(profile)
|
||||
report = {
|
||||
"profile_id": profile.pk,
|
||||
"profile_name": profile.name,
|
||||
"learning_enabled": profile.learning_enabled,
|
||||
"feedback": feedback_report,
|
||||
"weight_churn": churn[: args.top],
|
||||
}
|
||||
output = Path("feedback_learning_report.txt")
|
||||
output.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
f"Feedbacklearningrapport voor {profile.name} (id={profile.pk})",
|
||||
f"Learning actief: {'ja' if profile.learning_enabled else 'nee'}",
|
||||
f"Feedbacktotalen: {feedback_report['total_feedback']}",
|
||||
f"Statuscounts: {feedback_report['samples']}",
|
||||
f"Featurecounts: {feedback_report['features']}",
|
||||
"Top-churn:",
|
||||
]
|
||||
+ [f" - {item['feature']}: {item['delta']:+.3f}" for item in report["weight_churn"]]
|
||||
+ [f"\nMogelijke non-learn false negatives (max {args.top}):"]
|
||||
+ [f" - {item['job_id']} ({item['reason_code']})" for item in feedback_report["queued_non_learning_signals"][: args.top]]
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(output.read_text(encoding="utf-8"))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user