"""Vanta Crunch scoring function.

Scores model predictions (expected_return) against actual 5-minute forward
returns. The score is the product of predicted and actual returns:

    score = expected_return × actual_return

This rewards:
  - Correct direction (positive × positive = positive)
  - Calibrated magnitude (larger conviction on larger moves = higher score)
  - Penalizes wrong direction (positive × negative = negative)

The score maps directly to simulated PnL — if you treat expected_return as
a position signal, the score is your return for that period.
"""

from __future__ import annotations


def score_prediction(prediction: dict, ground_truth: dict) -> dict:
    """Score a single prediction against ground truth.

    Args:
        prediction: Model output, must contain "expected_return" (float).
        ground_truth: Resolved actuals with "return" (float) and "direction_up" (bool).

    Returns:
        Dict matching the ScoreResult contract.
    """
    # ── Extract prediction ────────────────────────────────────────────────
    try:
        expected_return = float(prediction.get("expected_return", 0.0))
    except (TypeError, ValueError):
        return {
            "value": 0.0,
            "expected_return": 0.0,
            "actual_return": 0.0,
            "gross_pnl": 0.0,
            "direction_correct": False,
            "success": False,
            "failed_reason": f"Invalid expected_return: {prediction.get('expected_return')!r}",
        }

    # ── Extract ground truth ──────────────────────────────────────────────
    actual_return = float(ground_truth.get("return", 0.0))
    direction_up = ground_truth.get("direction_up", actual_return > 0)

    # ── Score: prediction × actual return ─────────────────────────────────
    # Scale to basis points (1 bp = 0.01% = 0.0001) for readable display.
    # Both expected and actual are in decimal, so their product is in
    # decimal² — multiplying by 1e8 converts to bps².
    BPS_SCALE = 1e8
    gross_pnl = expected_return * actual_return * BPS_SCALE

    # Direction correctness
    if expected_return == 0.0:
        direction_correct = True  # No opinion is never "wrong"
    else:
        predicted_up = expected_return > 0
        direction_correct = predicted_up == direction_up

    return {
        "value": gross_pnl,
        "expected_return": expected_return,
        "actual_return": actual_return,
        "gross_pnl": gross_pnl,
        "direction_correct": direction_correct,
        "success": True,
        "failed_reason": None,
    }
