#!/usr/bin/env python3 """Re-score existing generation predictions with normalized JSON metrics. This does NOT regenerate model outputs. It reads eval//predictions.json and writes: - eval//normalized_metrics.json - eval/all_normalized_metrics.json Normalization removes volatile/generated fields (ids, hrefs, descriptions, timestamps, schema links), sorts object lists deterministically, and computes normalized exact match + normalized field F1. Use this alongside raw metrics: raw metrics show deterministic reproduction; normalized metrics better estimate structural/semantic config agreement. """ import argparse import json import re from collections import defaultdict from pathlib import Path from typing import Any, Dict, List from tmf921_train.utils import aggregate_metrics, canonical_json, field_f1, parse_json, write_json VOLATILE_KEY_EXACT = { "id", "uuid", "href", "name", "description", "displayName", "label", "@schemaLocation", "schemaLocation", "version", "revision", "createdAt", "updatedAt", "modifiedAt", "lastModified", "timestamp", "creationDate", "lastUpdate", "requestedStartDate", "requestedCompletionDate", "startTime", "endTime", "validFrom", "validTo", "validFor", "correlationId", "requestId", "transactionId", "reservationId", } VOLATILE_KEY_FRAGMENTS = ["href", "schema", "timestamp", "uuid", "correlation", "transaction"] PROTECTED_KEYS = {"sst", "sd", "sliceType", "slice_type", "latency", "reliability", "dl", "ul", "maxUEs", "maxNumberOfUEs"} ID_LIKE_RE = re.compile(r"\b(?:intent|slice|policy|booking|cell|me|gnb|nsi|nssi|req|report|monitor|assurance)[-_][A-Za-z0-9._:-]+", re.IGNORECASE) HEX_RE = re.compile(r"\b[0-9a-f]{8,}\b", re.IGNORECASE) ISO_TIME_RE = re.compile(r"\b\d{4}-\d{2}-\d{2}[T ][0-9:.+-Z]*\b") def is_volatile_key(key: str) -> bool: if key in PROTECTED_KEYS: return False if key in VOLATILE_KEY_EXACT: return True lk = key.lower() if lk in {k.lower() for k in VOLATILE_KEY_EXACT}: return True return any(fragment in lk for fragment in VOLATILE_KEY_FRAGMENTS) def normalize_string(s: str) -> str: s = ISO_TIME_RE.sub("