Refine final report consolidation and text cleanup
Browse files
backend/services/orchestrator_service.py
CHANGED
|
@@ -69,6 +69,34 @@ def _format_output_for_report(output_data) -> str:
|
|
| 69 |
return clean_report_text(dedupe_lines("\n".join(_format_value_for_report(primary))))
|
| 70 |
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
def _has_usable_output(output_data) -> bool:
|
| 73 |
if not output_data:
|
| 74 |
return False
|
|
@@ -505,11 +533,19 @@ class OrchestratorService:
|
|
| 505 |
lines.extend(["## Approved Work Summary", ""])
|
| 506 |
|
| 507 |
report_exclusions: list[str] = []
|
| 508 |
-
|
|
|
|
| 509 |
curated_text, excluded_lines = self._curate_task_output(task.get("output_data"))
|
| 510 |
report_exclusions.extend(excluded_lines)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
lines.extend([
|
| 512 |
-
f"### {
|
| 513 |
task.get("description") or "No task description provided.",
|
| 514 |
"",
|
| 515 |
curated_text,
|
|
@@ -551,7 +587,7 @@ class OrchestratorService:
|
|
| 551 |
if isinstance(data, str):
|
| 552 |
conclusion = data
|
| 553 |
elif isinstance(data, dict):
|
| 554 |
-
conclusion =
|
| 555 |
except Exception as exc:
|
| 556 |
logger.warning(f"Failed to generate dynamic conclusion: {exc}")
|
| 557 |
|
|
@@ -560,7 +596,7 @@ class OrchestratorService:
|
|
| 560 |
conclusion,
|
| 561 |
"",
|
| 562 |
"## Completion Status",
|
| 563 |
-
f"{len(tasks)} tasks reached done status. {
|
| 564 |
])
|
| 565 |
|
| 566 |
supabase.table("projects").update({"status": "completed"}).eq("id", project_id).execute()
|
|
@@ -577,7 +613,7 @@ class OrchestratorService:
|
|
| 577 |
return {
|
| 578 |
"project_id": project_id,
|
| 579 |
"project_name": project["name"],
|
| 580 |
-
"task_count":
|
| 581 |
"variant": variant,
|
| 582 |
"report": clean_report_text(dedupe_lines(report)),
|
| 583 |
"charts": _build_report_charts(curated_tasks)
|
|
|
|
| 69 |
return clean_report_text(dedupe_lines("\n".join(_format_value_for_report(primary))))
|
| 70 |
|
| 71 |
|
| 72 |
+
def _is_empty_curated_text(text: str) -> bool:
|
| 73 |
+
normalized = (text or "").strip().lower()
|
| 74 |
+
return normalized in {
|
| 75 |
+
"",
|
| 76 |
+
"no approved output was saved for this task.",
|
| 77 |
+
"{}",
|
| 78 |
+
"[]",
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _format_conclusion_payload(data: dict) -> str:
|
| 83 |
+
conclusion = data.get("strategicConclusion") or data.get("conclusion") or data.get("content") or ""
|
| 84 |
+
next_steps = data.get("nextSteps") or data.get("next_steps") or []
|
| 85 |
+
|
| 86 |
+
lines: list[str] = []
|
| 87 |
+
if isinstance(conclusion, str) and conclusion.strip():
|
| 88 |
+
lines.append(conclusion.strip())
|
| 89 |
+
|
| 90 |
+
if isinstance(next_steps, list) and next_steps:
|
| 91 |
+
lines.append("")
|
| 92 |
+
lines.append("Next steps:")
|
| 93 |
+
for step in next_steps[:5]:
|
| 94 |
+
if isinstance(step, str) and step.strip():
|
| 95 |
+
lines.append(f"- {step.strip()}")
|
| 96 |
+
|
| 97 |
+
return "\n".join(lines).strip() or "\n".join(_format_value_for_report(data))
|
| 98 |
+
|
| 99 |
+
|
| 100 |
def _has_usable_output(output_data) -> bool:
|
| 101 |
if not output_data:
|
| 102 |
return False
|
|
|
|
| 533 |
lines.extend(["## Approved Work Summary", ""])
|
| 534 |
|
| 535 |
report_exclusions: list[str] = []
|
| 536 |
+
kept_task_count = 0
|
| 537 |
+
for task in curated_tasks:
|
| 538 |
curated_text, excluded_lines = self._curate_task_output(task.get("output_data"))
|
| 539 |
report_exclusions.extend(excluded_lines)
|
| 540 |
+
if _is_empty_curated_text(curated_text):
|
| 541 |
+
excluded_tasks.append({
|
| 542 |
+
"title": task.get("title", "Untitled task"),
|
| 543 |
+
"reasons": ["Task output became empty after quality filtering."]
|
| 544 |
+
})
|
| 545 |
+
continue
|
| 546 |
+
kept_task_count += 1
|
| 547 |
lines.extend([
|
| 548 |
+
f"### {kept_task_count}. {task['title']}",
|
| 549 |
task.get("description") or "No task description provided.",
|
| 550 |
"",
|
| 551 |
curated_text,
|
|
|
|
| 587 |
if isinstance(data, str):
|
| 588 |
conclusion = data
|
| 589 |
elif isinstance(data, dict):
|
| 590 |
+
conclusion = _format_conclusion_payload(data)
|
| 591 |
except Exception as exc:
|
| 592 |
logger.warning(f"Failed to generate dynamic conclusion: {exc}")
|
| 593 |
|
|
|
|
| 596 |
conclusion,
|
| 597 |
"",
|
| 598 |
"## Completion Status",
|
| 599 |
+
f"{len(tasks)} tasks reached done status. {kept_task_count} task outputs were included in the final report. {len(excluded_tasks)} task outputs were excluded from the final report."
|
| 600 |
])
|
| 601 |
|
| 602 |
supabase.table("projects").update({"status": "completed"}).eq("id", project_id).execute()
|
|
|
|
| 613 |
return {
|
| 614 |
"project_id": project_id,
|
| 615 |
"project_name": project["name"],
|
| 616 |
+
"task_count": kept_task_count,
|
| 617 |
"variant": variant,
|
| 618 |
"report": clean_report_text(dedupe_lines(report)),
|
| 619 |
"charts": _build_report_charts(curated_tasks)
|
backend/services/output_quality.py
CHANGED
|
@@ -271,7 +271,7 @@ def report_text_from_output(output_data: Any) -> str:
|
|
| 271 |
|
| 272 |
|
| 273 |
def clean_report_text(text: str) -> str:
|
| 274 |
-
cleaned = text.replace("■", "-")
|
| 275 |
cleaned = re.sub(r"[ \t]+", " ", cleaned)
|
| 276 |
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
|
| 277 |
return cleaned.strip()
|
|
@@ -307,3 +307,5 @@ def filter_report_sections(text: str) -> tuple[str, list[str]]:
|
|
| 307 |
continue
|
| 308 |
kept_lines.append(line)
|
| 309 |
return "\n".join(kept_lines).strip(), excluded
|
|
|
|
|
|
|
|
|
| 271 |
|
| 272 |
|
| 273 |
def clean_report_text(text: str) -> str:
|
| 274 |
+
cleaned = text.replace("■", "-").replace("\u25A0", "-")
|
| 275 |
cleaned = re.sub(r"[ \t]+", " ", cleaned)
|
| 276 |
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
|
| 277 |
return cleaned.strip()
|
|
|
|
| 307 |
continue
|
| 308 |
kept_lines.append(line)
|
| 309 |
return "\n".join(kept_lines).strip(), excluded
|
| 310 |
+
|
| 311 |
+
|