diff --git a/backend/app/main.py b/backend/app/main.py index 2dbcfa1..4f0e642 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -280,7 +280,7 @@ def runs(): improvement for response in responses for improvement in response.get("improvements", []) - if isinstance(improvement, dict) + if isinstance(improvement, dict) and not str(improvement.get("known_issue_id") or "").strip() ] issues = [ issue diff --git a/backend/app/store.py b/backend/app/store.py index c74e7dc..a344171 100644 --- a/backend/app/store.py +++ b/backend/app/store.py @@ -2429,7 +2429,7 @@ def _known_unresolved_issues(self, run: Run, *, limit: int = 12) -> list[dict[st candidates.append(run) values: list[dict[str, str]] = [] for source_run in candidates: - for result in source_run.supervisor_results: + for record_index, result in enumerate(source_run.supervisor_results): if ( not isinstance(result, dict) or result.get("stage", "issue_assessment") != "issue_assessment" @@ -2446,9 +2446,11 @@ def _known_unresolved_issues(self, run: Run, *, limit: int = 12) -> list[dict[st for index, proposal in enumerate(proposals): if not isinstance(proposal, dict): continue - proposal_id = f"{source_run.id}:{result.get('iteration', 0)}:{index}" + proposal_id = f"{source_run.id}:{result.get('iteration', 0)}:{record_index}:{index}" if proposals is reported: - proposal_id = f"{source_run.id}:{result.get('iteration', 0)}:issue:{index}" + proposal_id = ( + f"{source_run.id}:{result.get('iteration', 0)}:{record_index}:issue:{index}" + ) management = records.get(proposal_id, {}) if management.get("status") in {"resolved", "deleted"}: continue @@ -3395,6 +3397,11 @@ def parse_timestamp(value: object) -> datetime | None: improvements = ( response.get("improvements", []) if isinstance(response.get("improvements"), list) else [] ) + improvements = [ + item + for item in improvements + if isinstance(item, dict) and not str(item.get("known_issue_id") or "").strip() + ] issues = ( response.get("reported_issues", []) if isinstance(response.get("reported_issues"), list) @@ -3575,7 +3582,7 @@ def active_evaluations(self, runs: list[Run] | None = None) -> list[dict[str, An improvement for response in responses for improvement in response.get("improvements", []) - if isinstance(improvement, dict) + if isinstance(improvement, dict) and not str(improvement.get("known_issue_id") or "").strip() ] issues = [ issue @@ -4608,7 +4615,14 @@ def supervisor_result(result: object) -> object: reported_at = now().isoformat() for improvement in result["improvements"]: improvement.setdefault("reported_at", reported_at) - improvement.setdefault("effect_score", (result.get("evaluation") or {}).get("score")) + item_evaluation = improvement.get("evaluation") + item_score = item_evaluation.get("score") if isinstance(item_evaluation, dict) else None + if improvement.get("effect_score") is None: + improvement["effect_score"] = ( + item_score + if item_score is not None + else (result.get("evaluation") or {}).get("score") + ) improvement.setdefault( "attempted", improvement.get("status") in {"adopted", "accepted", "rejected"} ) diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 3055c00..5580605 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -1025,7 +1025,13 @@ def test_active_evaluations_count_feedback_across_all_iterations(monkeypatch): { "iteration": 1, "response": { - "improvements": [{"title": "Add refund intake", "status": "accepted"}], + "improvements": [ + {"title": "Add refund intake", "status": "accepted"}, + { + "known_issue_id": "feedback-history-run:0:0:0", + "evidence": "The previously reported issue remains visible.", + }, + ], "reported_issues": [{"title": "Missing refund details"}], }, }, @@ -1738,7 +1744,7 @@ def complete(self, _settings, prompt): { "improvements": [ { - "known_issue_id": "known-issue-run:1:0", + "known_issue_id": "known-issue-run:1:0:0", "evidence": "The basis is still absent in this iteration.", "evaluation": {"score": 7, "approval": "pending", "summary": "Known issue."}, } @@ -1769,10 +1775,12 @@ def complete(self, _settings, prompt): store._complete_supervision(run.id) assert "# Known unresolved issues" in captured_prompts[0] - assert "known-issue-run:1:0" in captured_prompts[0] + assert "known-issue-run:1:0:0" in captured_prompts[0] assert [item["title"] for item in store.proposal_lifecycles()] == [ "Portfolio calculation basis is unclear" ] + retained_evidence = store._load(run.id).supervisor_results[-1]["response"]["improvements"][0] + assert retained_evidence["effect_score"] == 7 def test_runner_context_uses_the_supplied_model_profile_without_exposing_its_secret(tmp_path, monkeypatch): @@ -2668,6 +2676,12 @@ def test_issue_management_separates_multiple_supervisor_records_in_one_iteration by_title = {item["title"]: item for item in store.issue_management_items()} assert by_title["First issue"]["comments"][0]["body"] == "Only the first issue" assert by_title["Second issue"]["comments"] == [] + assert {issue["id"] for issue in store._known_unresolved_issues(store.runs()[0])} == { + "run-duplicate-assessments:1:0:0", + "run-duplicate-assessments:1:1:0", + "run-duplicate-assessments:2:2:issue:0", + "run-duplicate-assessments:2:3:issue:0", + } def test_hello_accepts_unsaved_profile_settings(): diff --git a/frontend/src/features/evaluations/page.tsx b/frontend/src/features/evaluations/page.tsx index d525b12..1a48b58 100644 --- a/frontend/src/features/evaluations/page.tsx +++ b/frontend/src/features/evaluations/page.tsx @@ -658,6 +658,7 @@ export function EvaluationsPage({ issueStatuses = new Set(); for (const record of selected?.supervisor_results ?? []) { for (const improvement of record.response?.improvements ?? []) { + if (String(improvement.known_issue_id ?? "").trim()) continue; if (improvement.status) improvementStatuses.add(String(improvement.status)); } for (const issue of record.response?.reported_issues ?? []) { @@ -681,7 +682,9 @@ export function EvaluationsPage({ recordEvaluation = response?.evaluation, decision = recordEvaluation?.approval ?? "no_response", score = recordEvaluation?.score, - improvements = response?.improvements ?? [], + improvements = (response?.improvements ?? []).filter( + (improvement) => !String(improvement.known_issue_id ?? "").trim(), + ), issues = response?.reported_issues ?? []; const inRange = (!resultIterationFrom || record.iteration >= Number(resultIterationFrom)) && @@ -760,7 +763,9 @@ export function EvaluationsPage({ resultTranslationIds.every((templateId) => Boolean(resultTranslations.content(templateId))); const resultTranslationsLoading = resultTranslations.isLoading(resultTranslationIds); const resultImprovements = resultRecords.flatMap((record) => - (translateResultResponse(record)?.improvements ?? []).map((item) => ({ + (translateResultResponse(record)?.improvements ?? []) + .filter((item) => !String(item.known_issue_id ?? "").trim()) + .map((item) => ({ ...item, __iteration: record.iteration, })), diff --git a/frontend/src/features/evaluations/run-detail-result-panel.tsx b/frontend/src/features/evaluations/run-detail-result-panel.tsx index 0cb99d5..029f604 100644 --- a/frontend/src/features/evaluations/run-detail-result-panel.tsx +++ b/frontend/src/features/evaluations/run-detail-result-panel.tsx @@ -28,8 +28,11 @@ function browserJourney(result?: Record) { function ResultList({ items, kind, locale, empty, l }: { items: RecordItem[]; kind: "improvement" | "issue"; locale: Locale; empty: string; l: Messages }) { return
{items.length ? items.map((item, index) => { - const status = String(item.status ?? "—"), severity = String(item.severity ?? "—"), reportedAt = typeof item.reported_at === "string" ? item.reported_at : undefined; - return
{String(item.title ?? "—")}

{String(kind === "improvement" ? (item.rationale ?? "—") : (item.evidence ?? "—"))}

{l.resultIteration}#{String(item.__iteration ?? "—")}{kind === "improvement" ? <>{l.resultStatus}{status}{l.resultScore}{String(item.effect_score ?? item.score ?? "—")}{l.resultAttempted}{item.attempted === true || ["adopted", "accepted"].includes(status) ? l.resultYes : l.resultNo} : <>{l.resultSeverity}{severity}{l.resultStatus}{status}}
; + const status = String(item.status ?? "—"), severity = String(item.severity ?? "—"), reportedAt = typeof item.reported_at === "string" ? item.reported_at : undefined, + evaluation = item.evaluation, + evaluationScore = evaluation && typeof evaluation === "object" ? (evaluation as RecordItem).score : undefined, + score = item.effect_score ?? item.score ?? evaluationScore ?? "—"; + return
{String(item.title ?? "—")}

{String(kind === "improvement" ? (item.rationale ?? "—") : (item.evidence ?? "—"))}

{l.resultIteration}#{String(item.__iteration ?? "—")}{kind === "improvement" ? <>{l.resultStatus}{status}{l.resultScore}{String(score)}{l.resultAttempted}{item.attempted === true || ["adopted", "accepted"].includes(status) ? l.resultYes : l.resultNo} : <>{l.resultSeverity}{severity}{l.resultStatus}{status}}
; }) :

{empty}

}
; }