@@ -263,7 +263,14 @@ class BettingBrain:
|
|||||||
"OE": -12.0,
|
"OE": -12.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
def judge(self, package: Dict[str, Any]) -> Dict[str, Any]:
|
def judge(
|
||||||
|
self,
|
||||||
|
package: Dict[str, Any],
|
||||||
|
ms_real_odds: Optional[Dict[str, float]] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
# V35c: real bookmaker MS odds (from odds_data) for reference rows —
|
||||||
|
# the brain must never display synthetic 1/p "fair odds" as offered.
|
||||||
|
self._ms_real_odds = ms_real_odds if isinstance(ms_real_odds, dict) else {}
|
||||||
v27_engine = package.get("v27_engine")
|
v27_engine = package.get("v27_engine")
|
||||||
if not isinstance(v27_engine, dict):
|
if not isinstance(v27_engine, dict):
|
||||||
return package
|
return package
|
||||||
@@ -916,9 +923,13 @@ class BettingBrain:
|
|||||||
prob = self._safe_float(probs.get(pick), 0.0)
|
prob = self._safe_float(probs.get(pick), 0.0)
|
||||||
if prob is None or prob <= 0.0:
|
if prob is None or prob <= 0.0:
|
||||||
continue
|
continue
|
||||||
implied_odd = round(1.0 / prob, 2) if prob > 0.01 else 0.0
|
# V35c: only REAL bookmaker odds may be displayed. The old fallback
|
||||||
ref_odd = existing_odds_by_pick.get(pick) or implied_odd
|
# showed synthetic fair-odds (1/prob) as "Oran" — users could read
|
||||||
rows[key] = {
|
# it as an offered price (e.g. X shown at 4.53 while the bulletin
|
||||||
|
# offered ~3.58). No real price → odds 0.0 and the FE renders "-".
|
||||||
|
real = self._safe_float(getattr(self, "_ms_real_odds", {}).get(pick), 0.0) or 0.0
|
||||||
|
ref_odd = existing_odds_by_pick.get(pick) or (real if real > 1.01 else 0.0)
|
||||||
|
row = {
|
||||||
"market": "MS",
|
"market": "MS",
|
||||||
"pick": pick,
|
"pick": pick,
|
||||||
"probability": round(prob, 4),
|
"probability": round(prob, 4),
|
||||||
@@ -932,6 +943,12 @@ class BettingBrain:
|
|||||||
"bet_grade": "PASS",
|
"bet_grade": "PASS",
|
||||||
"decision_reasons": ["underdog_reference_for_completeness"],
|
"decision_reasons": ["underdog_reference_for_completeness"],
|
||||||
}
|
}
|
||||||
|
if ref_odd > 1.01:
|
||||||
|
# honest economics vs the real price (vig shows as it truly is)
|
||||||
|
row["implied_prob"] = round(1.0 / ref_odd, 4)
|
||||||
|
row["ev_edge"] = round(prob * ref_odd - 1.0, 4)
|
||||||
|
row["edge"] = row["ev_edge"]
|
||||||
|
rows[key] = row
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _merge_row(existing: Optional[Dict[str, Any]], incoming: Dict[str, Any]) -> Dict[str, Any]:
|
def _merge_row(existing: Optional[Dict[str, Any]], incoming: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
|||||||
@@ -60,8 +60,23 @@ from models.calibration import get_calibrator
|
|||||||
|
|
||||||
|
|
||||||
class UpperBrainMixin:
|
class UpperBrainMixin:
|
||||||
def _apply_upper_brain_guards(self, package: Dict[str, Any]) -> Dict[str, Any]:
|
def _apply_upper_brain_guards(
|
||||||
return BettingBrain().judge(package)
|
self, package: Dict[str, Any], data: Any = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
# V35c: hand the brain the REAL bookmaker MS odds so its reference rows
|
||||||
|
# can never display synthetic 1/p prices as if they were offered.
|
||||||
|
ms_real_odds = None
|
||||||
|
if data is not None:
|
||||||
|
try:
|
||||||
|
odds = getattr(data, "odds_data", None) or {}
|
||||||
|
ms_real_odds = {
|
||||||
|
"1": self._real_market_odds(odds, "ms_h"),
|
||||||
|
"X": self._real_market_odds(odds, "ms_d"),
|
||||||
|
"2": self._real_market_odds(odds, "ms_a"),
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
ms_real_odds = None
|
||||||
|
return BettingBrain().judge(package, ms_real_odds=ms_real_odds)
|
||||||
|
|
||||||
v27_engine = package.get("v27_engine")
|
v27_engine = package.get("v27_engine")
|
||||||
if not isinstance(v27_engine, dict) or not v27_engine.get("triple_value"):
|
if not isinstance(v27_engine, dict) or not v27_engine.get("triple_value"):
|
||||||
|
|||||||
@@ -668,7 +668,28 @@ class SingleMatchOrchestrator(
|
|||||||
base_package.setdefault("analysis_details", {})
|
base_package.setdefault("analysis_details", {})
|
||||||
base_package["analysis_details"]["v27_loaded"] = False
|
base_package["analysis_details"]["v27_loaded"] = False
|
||||||
|
|
||||||
base_package = self._apply_upper_brain_guards(base_package)
|
base_package = self._apply_upper_brain_guards(base_package, data)
|
||||||
|
|
||||||
|
# V35c: the brain rebuilt main/value/supporting/bet_summary AFTER the
|
||||||
|
# market anchor ran inside _build_prediction_package — re-stamp the
|
||||||
|
# calibrated display fields (Güven/CI/Model%/edge) so they stay
|
||||||
|
# consistent, BEFORE the commentary reads the package.
|
||||||
|
self._apply_anchor_to_picks(
|
||||||
|
base_package.get("market_board") or {},
|
||||||
|
base_package.get("main_pick"),
|
||||||
|
base_package.get("value_pick"),
|
||||||
|
base_package.get("aggressive_pick"),
|
||||||
|
base_package.get("supporting_picks"),
|
||||||
|
base_package.get("bet_summary"),
|
||||||
|
)
|
||||||
|
_mp = base_package.get("main_pick")
|
||||||
|
_advice = base_package.get("bet_advice")
|
||||||
|
if isinstance(_mp, dict) and isinstance(_advice, dict) and _mp.get("confidence_band"):
|
||||||
|
_advice["confidence_band"] = _mp["confidence_band"]
|
||||||
|
# no fabricated value bets: a value pick must carry measured positive edge
|
||||||
|
_vp = base_package.get("value_pick")
|
||||||
|
if isinstance(_vp, dict) and float(_vp.get("ev_edge", 0.0) or 0.0) <= 0.0:
|
||||||
|
base_package["value_pick"] = None
|
||||||
|
|
||||||
# ── Match Commentary: human-readable summary ──────────────
|
# ── Match Commentary: human-readable summary ──────────────
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user