Add dark theme, richer Markdown and readable plugin results. Improve Android input evidence, response integrity and diagnostics. Unify lifecycle, guidance receipts and evidence sources across clients. Keep direct action dispatch without an added AI quota approval layer. Align version metadata at 2.2.0 and bundle Helper 1.2.2. Validation: 3063 backend tests before the version bump; 44 after. Frontend: 317 tests and production build passed.
139 lines
5.7 KiB
Python
139 lines
5.7 KiB
Python
"""Read-only lifecycle projection; elapsed wall time is never model speed."""
|
|
|
|
import math
|
|
import time
|
|
|
|
|
|
TERMINAL = {"completed", "success", "failed", "cancelled", "skipped", "interrupted"}
|
|
|
|
|
|
def number(value) -> float | None:
|
|
if isinstance(value, (float, int)) and not isinstance(value, bool) and math.isfinite(value):
|
|
return float(value)
|
|
return None
|
|
|
|
|
|
def review_seconds(reviews: list[dict], *, until: float, finished: bool) -> tuple[float, bool]:
|
|
intervals = []
|
|
complete = True
|
|
for review in reviews:
|
|
start = number(review.get("started_at"))
|
|
end = number(review.get("ended_at"))
|
|
if start is None:
|
|
complete = False
|
|
continue
|
|
if end is None:
|
|
if not finished and review.get("status") == "running":
|
|
end = until
|
|
else:
|
|
end = number(review.get("updated_at")) or start
|
|
complete = False
|
|
intervals.append((min(start, until), min(max(start, end), until)))
|
|
total = 0.0
|
|
last = 0.0
|
|
for start, end in sorted(intervals):
|
|
total += max(0.0, end - max(start, last))
|
|
last = max(last, end)
|
|
return round(total, 3), complete
|
|
|
|
|
|
def progress_snapshot(*, job=None, session=None, control=None, reviews=None, now=None) -> dict:
|
|
job, session, control, reviews = job or {}, session or {}, control or {}, reviews or []
|
|
now = time.time() if now is None else now
|
|
status = job.get("status") or session.get("status") or "unknown"
|
|
finished = status in TERMINAL or status == "finalizing"
|
|
accepted = number(job.get("created_at"))
|
|
started = number(job.get("worker_started_at"))
|
|
if started is None and session.get("start_time") and status != "queued":
|
|
started = number(job.get("started_at"))
|
|
ended = (number(job.get("ended_at")) or number(session.get("end_time"))) if finished else None
|
|
finalized = number(job.get("finalized_at"))
|
|
until = ended if ended is not None else now
|
|
observed_start = started or number(session.get("start_time"))
|
|
timing = control.get("timing")
|
|
timing = timing if isinstance(timing, dict) else {}
|
|
since = number(timing.get("state_started_at"))
|
|
seconds = timing.get("seconds")
|
|
recorded = (
|
|
isinstance(seconds, dict)
|
|
and since is not None
|
|
and since <= until
|
|
and (not finished or ended is not None)
|
|
)
|
|
durations = dict.fromkeys(
|
|
("total", "queued", "resource_wait", "paused", "final_review", "execution", "report_export")
|
|
)
|
|
if accepted is not None:
|
|
total_end = (
|
|
finalized or (now if status == "finalizing" else ended) or (None if finished else now)
|
|
)
|
|
durations["total"] = (
|
|
round(max(0, total_end - accepted), 3) if total_end is not None else None
|
|
)
|
|
if started is not None or status in {"pending", "queued"}:
|
|
durations["queued"] = round(max(0, (started or until) - accepted), 3)
|
|
review_elapsed, review_complete = review_seconds(reviews, until=until, finished=finished)
|
|
if reviews or recorded:
|
|
durations["final_review"] = review_elapsed
|
|
if recorded:
|
|
seconds = {key: max(0.0, number(value) or 0.0) for key, value in seconds.items()}
|
|
phase = control.get("status")
|
|
seconds[phase] = seconds.get(phase, 0.0) + max(0, until - since)
|
|
durations["resource_wait"] = round(seconds.get("queued", 0) + seconds.get("resuming", 0), 3)
|
|
durations["paused"] = round(
|
|
sum(seconds.get(key, 0) for key in ("task_paused", "demonstrating", "analyzing_demo")),
|
|
3,
|
|
)
|
|
if started is not None and review_complete:
|
|
durations["execution"] = round(
|
|
max(
|
|
0,
|
|
until
|
|
- started
|
|
- durations["resource_wait"]
|
|
- durations["paused"]
|
|
- review_elapsed,
|
|
),
|
|
3,
|
|
)
|
|
if ended is not None and (finalized is not None or status == "finalizing"):
|
|
durations["report_export"] = round(max(0, (finalized or now) - ended), 3)
|
|
phase = "running"
|
|
if status in TERMINAL:
|
|
phase = "finished"
|
|
elif status == "finalizing":
|
|
phase = "report_export"
|
|
elif status in {"paused", "task_paused"}:
|
|
phase = "paused"
|
|
elif control.get("status") in {"task_paused", "demonstrating", "analyzing_demo", "resuming"}:
|
|
phase = {"task_paused": "paused", "resuming": "waiting_resources"}.get(
|
|
control["status"], control["status"]
|
|
)
|
|
elif control.get("status") == "queued" and started is not None:
|
|
phase = "waiting_resources"
|
|
elif status in {"queued", "pending", "reserved", "draft"}:
|
|
phase = "queued"
|
|
elif any(
|
|
review.get("status") == "running" and not review.get("ended_at") for review in reviews
|
|
):
|
|
phase = "final_review"
|
|
elif status == "unknown":
|
|
phase = "unknown"
|
|
return {
|
|
"schema_version": 1,
|
|
"status": status,
|
|
"phase": phase,
|
|
"observed_at": (finalized or ended) if status in TERMINAL else now,
|
|
"accepted_at": accepted,
|
|
"worker_started_at": started,
|
|
"worker_ended_at": ended,
|
|
"finalized_at": finalized,
|
|
"durations": durations,
|
|
"observed_seconds": round(max(0, until - observed_start), 3)
|
|
if observed_start and (not finished or ended is not None)
|
|
else None,
|
|
"control_timing_recorded": recorded,
|
|
"review_timing_complete": review_complete,
|
|
"note": "分项使用实际记录;缺失为未知。执行含初始化与报告整理,不是模型推理耗时。暂停包含人工示范及分析。执行完成不等于验证通过。",
|
|
}
|