linvest21/shft-artifacts / code /self_healing_finetuning /eval /paired_eval_defect_ranker.py
linvest21's picture
download
raw
14.1 kB
from __future__ import annotations
import json
import re
from collections import Counter, defaultdict
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from n21.config import write_json
from n21.settings import SHFT_WORKSPACE_ROOT
ASSETS = ("equity", "fixed_income", "multi_asset")
ROLES = (
"chief_investment_officer",
"client_portfolio_manager",
"performance_manager",
"portfolio_manager",
"researcher",
"risk_manager",
)
DEFECT_TYPES = (
"numeric_reasoning",
"fact_inference_separation",
"role_discipline",
"risk_tradeoff_framing",
"hallucination_unsupported_claim",
"weak_source_grounding",
"overfit_memorized_answer_style",
)
def utc_now() -> str:
return datetime.now(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z")
def read_json(path: Path) -> dict[str, Any] | None:
if not path.exists():
return None
return json.loads(path.read_text(encoding="utf-8-sig"))
def read_jsonl(path: Path) -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
if not path.exists():
return rows
for line_no, line in enumerate(path.read_text(encoding="utf-8-sig").splitlines(), start=1):
if not line.strip():
continue
item = json.loads(line)
if not isinstance(item, dict):
raise ValueError(f"{path}:{line_no} must contain a JSON object")
rows.append(item)
return rows
def latest_submitted_run(release_id: str) -> Path | None:
runs_root = SHFT_WORKSPACE_ROOT / "runs"
dirs = [
item
for item in runs_root.glob(f"run_{release_id}*")
if item.is_dir() and not item.name.endswith("_paired_eval_code")
]
dirs = sorted(dirs, key=lambda item: item.stat().st_mtime, reverse=True)
for run_path in dirs:
if (run_path / "trainer_state" / "train_handle.json").exists():
return run_path
return dirs[0] if dirs else None
def bool_check(score: dict[str, Any], name: str) -> bool | None:
checks = score.get("checks")
if not isinstance(checks, dict):
return None
value = checks.get(name)
return value if isinstance(value, bool) else None
def answer_text(prediction: dict[str, Any]) -> str:
return str(prediction.get("candidate_answer") or "")
def detect_role_discipline(prediction: dict[str, Any]) -> list[str]:
text = answer_text(prediction).lower()
reasons: list[str] = []
if bool_check(prediction.get("candidate_score", {}), "no_investment_advice") is False:
reasons.append("candidate gave buy/sell/hold/short-style investment advice")
if re.search(r"\b(as an ai|i cannot|i do not have access|not financial advice)\b", text):
reasons.append("candidate broke analyst-role voice")
if len(text.split()) < 18:
reasons.append("candidate answer too thin for role-grade analysis")
return reasons
def detect_weak_source_grounding(prediction: dict[str, Any]) -> list[str]:
text = answer_text(prediction).lower()
prompt = str(prediction.get("prompt") or "").lower()
reasons: list[str] = []
grounding_terms = ("reported", "filing", "disclosed", "source", "data", "management", "statement", "fact")
if not any(term in text for term in grounding_terms):
reasons.append("candidate did not anchor answer to reported/source facts")
prompt_numbers = set(re.findall(r"\$?\d+(?:\.\d+)?%?", prompt))
if prompt_numbers:
answer_numbers = set(re.findall(r"\$?\d+(?:\.\d+)?%?", text))
if not prompt_numbers.intersection(answer_numbers):
reasons.append("candidate omitted numeric evidence present in prompt")
return reasons
def detect_overfit_style(prediction: dict[str, Any]) -> list[str]:
text = answer_text(prediction)
lower = text.lower()
reasons: list[str] = []
repeated_phrases = [
"numeric anchor:",
"reported facts:",
"risk/tradeoff:",
"required next evidence:",
]
phrase_hits = sum(1 for phrase in repeated_phrases if phrase in lower)
if phrase_hits >= 3:
reasons.append("candidate uses repair-template wording heavily")
sentences = [item.strip() for item in re.split(r"[.!?]\s+", lower) if item.strip()]
if len(sentences) >= 4 and len(set(sentences)) <= max(2, len(sentences) // 2):
reasons.append("candidate repeats sentence structure/content")
return reasons
def classify_prediction(prediction: dict[str, Any]) -> dict[str, list[str]]:
score = prediction.get("candidate_score", {})
checks = score.get("checks") if isinstance(score.get("checks"), dict) else {}
defects: dict[str, list[str]] = {name: [] for name in DEFECT_TYPES}
if checks.get("numeric_reasoning_present") is False:
defects["numeric_reasoning"].append("numeric_reasoning_present=false")
if checks.get("fact_inference_separation") is False:
defects["fact_inference_separation"].append("fact_inference_separation=false")
if checks.get("risk_or_tradeoff_identified") is False or checks.get("neutral_language") is False:
defects["risk_tradeoff_framing"].append("risk/tradeoff or neutral-language check failed")
if checks.get("no_unsupported_certainty") is False:
defects["hallucination_unsupported_claim"].append("no_unsupported_certainty=false")
defects["role_discipline"].extend(detect_role_discipline(prediction))
defects["weak_source_grounding"].extend(detect_weak_source_grounding(prediction))
defects["overfit_memorized_answer_style"].extend(detect_overfit_style(prediction))
return {key: value for key, value in defects.items() if value}
@dataclass
class RoleDefectResult:
asset_class: str
role: str
release_id: str
run_id: str | None
status: str
prediction_count: int
failed_prediction_count: int
defect_counts: dict[str, int]
top_defects: list[dict[str, Any]]
paired_predictions: str | None
paired_eval_report: str | None
def rank_run_defects(*, run_path: Path, asset_class: str, role: str, release_id: str) -> RoleDefectResult:
predictions_path = run_path / "eval" / "paired_predictions.jsonl"
report_path = run_path / "eval" / "paired_eval_report.json"
if not predictions_path.exists():
return RoleDefectResult(
asset_class=asset_class,
role=role,
release_id=release_id,
run_id=run_path.name,
status="proof_missing",
prediction_count=0,
failed_prediction_count=0,
defect_counts={name: 0 for name in DEFECT_TYPES},
top_defects=[],
paired_predictions=str(predictions_path),
paired_eval_report=str(report_path),
)
rows = read_jsonl(predictions_path)
defect_counts: Counter[str] = Counter()
examples: dict[str, list[dict[str, Any]]] = defaultdict(list)
failed_count = 0
for row in rows:
defects = classify_prediction(row)
if defects or row.get("candidate_score", {}).get("critical_pass") is False:
failed_count += 1
for defect, reasons in defects.items():
defect_counts[defect] += 1
if len(examples[defect]) < 3:
examples[defect].append(
{
"id": row.get("id"),
"task": row.get("task"),
"delta": row.get("delta"),
"reasons": reasons,
"prompt_preview": str(row.get("prompt") or "")[:240],
}
)
counts = {name: int(defect_counts.get(name, 0)) for name in DEFECT_TYPES}
top = [
{"defect_type": name, "count": count, "examples": examples.get(name, [])}
for name, count in sorted(counts.items(), key=lambda item: (-item[1], item[0]))
if count > 0
]
return RoleDefectResult(
asset_class=asset_class,
role=role,
release_id=release_id,
run_id=run_path.name,
status="ranked" if rows else "no_predictions",
prediction_count=len(rows),
failed_prediction_count=failed_count,
defect_counts=counts,
top_defects=top,
paired_predictions=str(predictions_path),
paired_eval_report=str(report_path),
)
def result_to_dict(result: RoleDefectResult) -> dict[str, Any]:
return {
"asset_class": result.asset_class,
"role": result.role,
"release_id": result.release_id,
"run_id": result.run_id,
"status": result.status,
"prediction_count": result.prediction_count,
"failed_prediction_count": result.failed_prediction_count,
"defect_counts": result.defect_counts,
"top_defects": result.top_defects,
"paired_predictions": result.paired_predictions,
"paired_eval_report": result.paired_eval_report,
}
def rank_all_role_defects(*, output_path: Path | None = None) -> dict[str, Any]:
role_results: list[RoleDefectResult] = []
aggregate_counts: Counter[str] = Counter()
for asset_class in ASSETS:
for role in ROLES:
release_id = f"linvest21_fingpt_{asset_class}_{role}_v1_001"
run_path = latest_submitted_run(release_id)
if run_path is None:
result = RoleDefectResult(
asset_class=asset_class,
role=role,
release_id=release_id,
run_id=None,
status="run_missing",
prediction_count=0,
failed_prediction_count=0,
defect_counts={name: 0 for name in DEFECT_TYPES},
top_defects=[],
paired_predictions=None,
paired_eval_report=None,
)
else:
result = rank_run_defects(run_path=run_path, asset_class=asset_class, role=role, release_id=release_id)
role_results.append(result)
aggregate_counts.update(result.defect_counts)
payload = {
"schema_version": "paired_eval_defect_ranker_v1",
"created_at": utc_now(),
"defect_types": list(DEFECT_TYPES),
"summary": {
"role_count": len(role_results),
"ranked_role_count": sum(1 for item in role_results if item.status == "ranked"),
"proof_missing_role_count": sum(1 for item in role_results if item.status == "proof_missing"),
"run_missing_role_count": sum(1 for item in role_results if item.status == "run_missing"),
"prediction_count": sum(item.prediction_count for item in role_results),
"failed_prediction_count": sum(item.failed_prediction_count for item in role_results),
"defect_counts": {name: int(aggregate_counts.get(name, 0)) for name in DEFECT_TYPES},
},
"roles": [result_to_dict(item) for item in role_results],
}
output = output_path or SHFT_WORKSPACE_ROOT / "verification" / "paired_eval_defect_ranking_latest.json"
write_json(output, payload)
markdown_output = output.with_suffix(".md")
markdown_output.write_text(markdown_report(payload), encoding="utf-8")
payload["output_path"] = str(output)
payload["markdown_output_path"] = str(markdown_output)
return payload
def markdown_report(payload: dict[str, Any]) -> str:
summary = payload["summary"]
widths = [12, 24, 14, 10, 10, 10, 10, 10, 10, 10]
headers = [
"Asset",
"Role",
"Status",
"Numeric",
"Fact/Inf",
"Role",
"Risk",
"Unsup",
"Ground",
"Overfit",
]
def sep(left: str, middle: str, right: str) -> str:
return left + middle.join("─" * (width + 2) for width in widths) + right
def line(values: list[Any]) -> str:
return "│ " + " │ ".join(str(value).ljust(width) for value, width in zip(values, widths, strict=True)) + " │"
lines = [
"# Paired Eval Defect Ranking",
"",
f"Generated: {payload['created_at']}",
"",
"Defect taxonomy:",
"",
]
for defect_type in payload["defect_types"]:
lines.append(f"- {defect_type}")
lines.extend(
[
"",
"Summary:",
"",
f"- role_count: {summary['role_count']}",
f"- ranked_role_count: {summary['ranked_role_count']}",
f"- proof_missing_role_count: {summary['proof_missing_role_count']}",
f"- run_missing_role_count: {summary['run_missing_role_count']}",
f"- prediction_count: {summary['prediction_count']}",
f"- failed_prediction_count: {summary['failed_prediction_count']}",
"",
"Status By Role",
"",
sep("┌", "┬", "┐"),
line(headers),
sep("├", "┼", "┤"),
]
)
for row in payload["roles"]:
counts = row["defect_counts"]
lines.append(
line(
[
row["asset_class"],
row["role"],
row["status"],
counts["numeric_reasoning"],
counts["fact_inference_separation"],
counts["role_discipline"],
counts["risk_tradeoff_framing"],
counts["hallucination_unsupported_claim"],
counts["weak_source_grounding"],
counts["overfit_memorized_answer_style"],
]
)
)
lines.append(sep("└", "┴", "┘"))
return "\n".join(lines) + "\n"

Xet Storage Details

Size:
14.1 kB
·
Xet hash:
cd0b7236d05717a04fa71a8cf83a96b3d6eef8bca3b1e42d3522f051e7c55cb4

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.