Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions src/ui/main_ui_elite_recommendation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Any

from src.ui.full_pipeline_elite_status import (
DEFAULT_ELITE_EVIDENCE_ROOT,
UIElitePipelineStatus,
full_pipeline_elite_policy_for_ui,
read_full_pipeline_elite_status,
)
from src.ui.move_policy import (
BOT_MOVE_POLICY_STATISTICAL,
bot_move_policy_label,
normalize_bot_move_policy,
)


@dataclass(frozen=True)
class MainUIEliteRecommendation:
username: str
time_class: str
status: str
decision: str
reason: str
recommended_policy: str
move_policy: str
move_policy_label: str
evidence_path: Path
ready: bool
technical_error: bool
should_select_policy: bool


def _title_for_decision(decision: str) -> str:
if decision == "elite_guarded_ready":
return "Elite Guarded ready"
if decision == "elite_guarded_strict_ready":
return "Elite Guarded Strict ready"
if decision == "needs_more_evidence":
return "Elite evidence needs more data"
if decision == "elite_guarded_blocked":
return "Elite guarded runtime blocked"
return "Elite evidence unavailable"


def build_main_ui_elite_recommendation(
*,
username: object,
time_class: object,
evidence_root: Path | str = DEFAULT_ELITE_EVIDENCE_ROOT,
evidence_path: Path | None = None,
) -> MainUIEliteRecommendation:
status = read_full_pipeline_elite_status(
username=username,
time_class=time_class,
evidence_root=evidence_root,
evidence_path=evidence_path,
)
policy = full_pipeline_elite_policy_for_ui(status)
normalized_policy = normalize_bot_move_policy(policy)

return MainUIEliteRecommendation(
username=status.username,
time_class=status.time_class,
status=status.status,
decision=status.decision,
reason=status.reason,
recommended_policy=status.recommended_policy,
move_policy=normalized_policy,
move_policy_label=bot_move_policy_label(normalized_policy),
evidence_path=status.evidence_path,
ready=status.ready and not status.technical_error,
technical_error=status.technical_error,
should_select_policy=(
status.ready
and not status.technical_error
and normalized_policy != BOT_MOVE_POLICY_STATISTICAL
),
)


def recommendation_from_elite_status(
status: UIElitePipelineStatus,
) -> MainUIEliteRecommendation:
policy = full_pipeline_elite_policy_for_ui(status)
normalized_policy = normalize_bot_move_policy(policy)

return MainUIEliteRecommendation(
username=status.username,
time_class=status.time_class,
status=status.status,
decision=status.decision,
reason=status.reason,
recommended_policy=status.recommended_policy,
move_policy=normalized_policy,
move_policy_label=bot_move_policy_label(normalized_policy),
evidence_path=status.evidence_path,
ready=status.ready and not status.technical_error,
technical_error=status.technical_error,
should_select_policy=(
status.ready
and not status.technical_error
and normalized_policy != BOT_MOVE_POLICY_STATISTICAL
),
)


def render_main_ui_elite_recommendation(
recommendation: MainUIEliteRecommendation,
) -> str:
lines = [
f"Full Pipeline Elite Status: {_title_for_decision(recommendation.decision)}",
f"Recommended UI policy: {recommendation.move_policy_label}",
f"Move policy key: {recommendation.move_policy}",
f"Reason: {recommendation.reason}",
f"Evidence report: {recommendation.evidence_path}",
]

if recommendation.should_select_policy:
lines.append("UI recommendation: ready; this policy can be selected.")
else:
lines.append("UI recommendation: not ready; keep current/statistical policy.")

return "\n".join(lines)


def main_ui_elite_recommendation_lines(
recommendation: MainUIEliteRecommendation | None,
) -> list[str]:
if recommendation is None:
return []

lines = [
f"Full Pipeline Elite Status: {_title_for_decision(recommendation.decision)}",
f"Recommended UI policy: {recommendation.move_policy_label}",
f"Move policy key: {recommendation.move_policy}",
]

if not recommendation.should_select_policy:
lines.append("Elite policy not auto-selected until evidence is ready.")

return lines


def _set_combo_policy(combo: Any, move_policy: str) -> bool:
if combo is None:
return False

try:
index = combo.findData(move_policy)
except Exception:
index = -1

if index is None or int(index) < 0:
return False

try:
combo.blockSignals(True)
except Exception:
pass

try:
combo.setCurrentIndex(int(index))
finally:
try:
combo.blockSignals(False)
except Exception:
pass

return True


def apply_main_ui_elite_recommendation_to_window(
window: Any,
*,
username: object | None = None,
time_class: object | None = None,
evidence_root: Path | str = DEFAULT_ELITE_EVIDENCE_ROOT,
evidence_path: Path | None = None,
auto_select_ready: bool = True,
) -> MainUIEliteRecommendation:
recommendation = build_main_ui_elite_recommendation(
username=username if username is not None else getattr(window, "username", ""),
time_class=time_class if time_class is not None else getattr(window, "time_class", ""),
evidence_root=evidence_root,
evidence_path=evidence_path,
)

setattr(window, "main_ui_elite_recommendation", recommendation)
setattr(window, "full_pipeline_elite_recommendation", recommendation)

if auto_select_ready and recommendation.should_select_policy:
setattr(window, "move_policy", recommendation.move_policy)
_set_combo_policy(
getattr(window, "move_policy_combo", None),
recommendation.move_policy,
)

sync = getattr(window, "_sync_beta_dashboard_policy", None)
if callable(sync):
sync()

return recommendation
17 changes: 16 additions & 1 deletion src/ui/main_window_parts/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
render_beta_dashboard,
set_beta_dashboard_policy,
)
from src.ui.main_ui_elite_recommendation import (
main_ui_elite_recommendation_lines,
)


class MainWindowGameMixin:
Expand Down Expand Up @@ -1030,6 +1033,17 @@ def _show_persona_summary(
"",
]

elite_lines = main_ui_elite_recommendation_lines(
getattr(
self,
"main_ui_elite_recommendation",
None,
)
)
if elite_lines:
lines.extend(elite_lines)
lines.append("")

for explanation in explanations:
lines.append(
f"• {explanation}"
Expand Down Expand Up @@ -1091,7 +1105,8 @@ def _show_bot_decision(
)
else:
lines.append(
"Move policy: Statistical selector"
"Move policy: "
+ bot_move_policy_label(policy)
)

if isinstance(guarded_hybrid_result, dict):
Expand Down
28 changes: 28 additions & 0 deletions src/ui/main_window_parts/persona.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
from src.persona.persona_manager import PersonaManagerError
from src.ui.persona_creation_worker import PersonaCreationThread
from src.ui.persona_progress_dialog import PersonaProgressDialog
from src.ui.main_ui_elite_recommendation import (
apply_main_ui_elite_recommendation_to_window,
)


class MainWindowPersonaMixin:
Expand Down Expand Up @@ -260,6 +263,12 @@ def _load_persona(
False
)

apply_main_ui_elite_recommendation_to_window(
self,
username=normalized_username,
time_class=selected_mode,
)

self._refresh_saved_personas(
select_username=normalized_username
)
Expand All @@ -276,13 +285,32 @@ def _load_persona(
if self.direct_ranker_shadow.enabled
else "direct ranker shadow off"
)
elite_recommendation = getattr(
self,
"main_ui_elite_recommendation",
None,
)
elite_status = ""
if (
elite_recommendation is not None
and getattr(
elite_recommendation,
"should_select_policy",
False,
)
):
elite_status = (
" — recommended: "
+ str(elite_recommendation.move_policy_label)
)

self.status_label.setText(
f"Persona loaded: "
f"{normalized_username} — "
f"{selected_mode.upper()} — "
f"{shadow_status} — "
f"{direct_status}"
f"{elite_status}"
)

if start_new_game:
Expand Down
Loading