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
23 changes: 23 additions & 0 deletions src/ui/main_window_parts/persona.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,13 +486,36 @@ def _on_persona_creation_completed(
or ""
)

friendly_title = str(
getattr(
result,
"friendly_title",
"",
)
or ""
)

friendly_message = str(
getattr(
result,
"friendly_message",
"",
)
or ""
)

if self.persona_dialog is not None:
if readiness_ready:
final_message = (
f"Full pipeline selesai: {username} — "
f"{', '.join(completed_modes)}. "
f"Runtime READY: {recommended_policy}"
)
elif friendly_message:
final_message = (
f"{friendly_title}\n"
f"{friendly_message}"
)
else:
final_message = (
f"Persona {username} selesai: "
Expand Down
33 changes: 19 additions & 14 deletions src/ui/persona_creation_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
build_unified_full_pipeline_command,
complete_persona_exists,
disk_space_status_for_pipeline,
friendly_pipeline_status,
normalize_pipeline_time_class,
normalize_pipeline_username,
progress_percent_for_pipeline_line,
Expand Down Expand Up @@ -116,7 +117,7 @@ def _run_subprocess(
raise PersonaBuildCancelled()

self._recent_lines.append(line)
self._recent_lines = self._recent_lines[-40:]
self._recent_lines = self._recent_lines[-80:]

self._report_progress(
progress_percent_for_pipeline_line(line),
Expand Down Expand Up @@ -149,12 +150,17 @@ def _build_readiness_result(
line,
)

warning = ""
if pipeline_returncode != 0:
warning = (
"Full pipeline ended with a non-zero exit code, "
"but complete persona output exists; UI will load it "
"and keep runtime policy safe according to readiness."
friendly = friendly_pipeline_status(
pipeline_returncode=pipeline_returncode,
readiness_ready=bool(report.ready),
readiness_report_text=rendered,
recent_output_lines=tuple(self._recent_lines),
)

if pipeline_returncode != 0 or not report.ready:
self._report_progress(
99,
friendly.message,
)

return UnifiedPersonaPipelineResult(
Expand All @@ -166,7 +172,12 @@ def _build_readiness_result(
recommended_policy=report.recommendation.move_policy_label,
move_policy=report.recommendation.move_policy,
readiness_report_text=rendered,
warning=warning,
warning="",
friendly_status=friendly.status,
friendly_title=friendly.title,
friendly_message=friendly.message,
safe_to_play=friendly.safe_to_play,
advanced_ml_ready=friendly.advanced_ml_ready,
)

def _disk_full_failure_message(self) -> str:
Expand Down Expand Up @@ -230,12 +241,6 @@ def run(self) -> None:

result = self._build_readiness_result(return_code)

if result.warning:
self._report_progress(
99,
result.warning,
)

except PersonaBuildCancelled:
self.cancelled.emit()
return
Expand Down
94 changes: 94 additions & 0 deletions src/ui/unified_persona_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ class UnifiedPersonaPipelineConfig:
adaptive_direct_games: bool = True


@dataclass(frozen=True)
class PipelineFriendlyStatus:
status: str
title: str
message: str
safe_to_play: bool
advanced_ml_ready: bool


@dataclass(frozen=True)
class UnifiedPersonaPipelineResult:
username: str
Expand All @@ -40,6 +49,11 @@ class UnifiedPersonaPipelineResult:
move_policy: str
readiness_report_text: str
warning: str = ""
friendly_status: str = ""
friendly_title: str = ""
friendly_message: str = ""
safe_to_play: bool = True
advanced_ml_ready: bool = False


@dataclass(frozen=True)
Expand Down Expand Up @@ -251,3 +265,83 @@ def complete_persona_exists(
username,
time_class,
).exists()


def friendly_pipeline_status(
*,
pipeline_returncode: int,
readiness_ready: bool,
readiness_report_text: str = "",
recent_output_lines: Sequence[str] = (),
) -> PipelineFriendlyStatus:
combined = "\n".join(
(
str(readiness_report_text or ""),
*(str(line) for line in recent_output_lines),
)
).lower()

if readiness_ready:
return PipelineFriendlyStatus(
status="runtime_ready",
title="Persona ready — advanced runtime ready",
message=(
"Persona ready. Advanced ML runtime is ready and the UI "
"can use the recommended policy."
),
safe_to_play=True,
advanced_ml_ready=True,
)

if "needs_more_evidence" in combined:
return PipelineFriendlyStatus(
status="advanced_ml_needs_more_evidence",
title="Persona ready — advanced ML needs more evidence",
message=(
"Persona ready. Advanced ML was trained, but the release "
"gate needs more evidence before it can be enabled. "
"The bot will use Statistical selector safely."
),
safe_to_play=True,
advanced_ml_ready=False,
)

if (
"elite_evidence_missing" in combined
or "statistical_only" in combined
or "recommended ui policy: statistical selector" in combined
):
return PipelineFriendlyStatus(
status="persona_ready_statistical",
title="Persona ready — using safe fallback",
message=(
"Persona ready. Advanced runtime is not unlocked yet, "
"so the bot will use Statistical selector safely."
),
safe_to_play=True,
advanced_ml_ready=False,
)

if pipeline_returncode != 0:
return PipelineFriendlyStatus(
status="persona_ready_with_pipeline_warning",
title="Persona ready — pipeline finished with warning",
message=(
"Persona ready, but one advanced pipeline step did not "
"finish cleanly. The bot will use the safe runtime policy "
"selected by readiness checks."
),
safe_to_play=True,
advanced_ml_ready=False,
)

return PipelineFriendlyStatus(
status="persona_ready_statistical",
title="Persona ready — using safe fallback",
message=(
"Persona ready. Runtime readiness did not unlock advanced ML, "
"so the bot will use Statistical selector safely."
),
safe_to_play=True,
advanced_ml_ready=False,
)
30 changes: 30 additions & 0 deletions tests/test_ui_unified_persona_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
complete_persona_exists,
disk_space_status_for_pipeline,
expected_complete_persona_path,
friendly_pipeline_status,
normalize_pipeline_time_class,
progress_percent_for_pipeline_line,
resolve_pipeline_counts,
Expand Down Expand Up @@ -87,3 +88,32 @@ def test_disk_space_status_for_pipeline_reports_requirement(
assert status.required_gb >= 10.0
assert status.free_gb >= 0.0
assert isinstance(status.ok, bool)


def test_friendly_pipeline_status_for_needs_more_evidence() -> None:
status = friendly_pipeline_status(
pipeline_returncode=1,
readiness_ready=False,
readiness_report_text="Runtime readiness: NOT READY",
recent_output_lines=(
"Decision : needs_more_evidence",
"Failed checks : temporal.coverage.train_games",
),
)

assert status.status == "advanced_ml_needs_more_evidence"
assert status.safe_to_play is True
assert status.advanced_ml_ready is False
assert "Statistical selector" in status.message


def test_friendly_pipeline_status_for_runtime_ready() -> None:
status = friendly_pipeline_status(
pipeline_returncode=0,
readiness_ready=True,
readiness_report_text="Runtime readiness: READY",
)

assert status.status == "runtime_ready"
assert status.safe_to_play is True
assert status.advanced_ml_ready is True