Skip to content

Commit 4c18d07

Browse files
Merge branch 'ftr/sidebar-improvements' into development
2 parents 6abbec8 + 6953270 commit 4c18d07

File tree

19 files changed

+823
-82
lines changed

19 files changed

+823
-82
lines changed

backend/app/agents/domain_agent_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ async def run(
219219
input_data: dict[str, object] = {
220220
"file_ids": file_ids,
221221
"file_count": len(files),
222+
"file_names": [f.original_filename for f in files],
222223
}
223224
if stage_suffix:
224225
input_data["stage_suffix"] = stage_suffix

backend/app/api/agents.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from app.schemas.agent import (
2121
AnalysisMode,
2222
AnalysisStartRequest,
23+
ExecutionDetailResponse,
2324
OrchestratorOutput,
2425
TriageOutput,
2526
)
@@ -412,3 +413,48 @@ async def get_analysis_status(
412413
error=error_msg,
413414
domain_results_summary=domain_summary,
414415
)
416+
417+
418+
# ---------------------------------------------------------------------------
419+
# Execution Detail Endpoint
420+
# ---------------------------------------------------------------------------
421+
422+
423+
@router.get(
424+
"/api/agents/executions/{execution_id}",
425+
response_model=ExecutionDetailResponse,
426+
summary="Get detailed agent execution data",
427+
responses={
428+
401: {"model": ErrorResponse, "description": "Unauthorized"},
429+
404: {"model": ErrorResponse, "description": "Execution not found"},
430+
},
431+
)
432+
async def get_execution_detail(
433+
execution_id: UUID,
434+
current_user: CurrentUser,
435+
db: Annotated[AsyncSession, Depends(get_db)],
436+
) -> ExecutionDetailResponse:
437+
"""Fetch full execution data (output_data, input_data, thinking_traces).
438+
439+
Verifies case ownership via the execution's case_id.
440+
"""
441+
result = await db.execute(
442+
select(AgentExecution).where(AgentExecution.id == execution_id)
443+
)
444+
execution = result.scalar_one_or_none()
445+
446+
if not execution:
447+
raise HTTPException(
448+
status_code=status.HTTP_404_NOT_FOUND,
449+
detail="Execution not found",
450+
)
451+
452+
# Verify ownership via case
453+
case = await _get_user_case(db, execution.case_id, current_user.id)
454+
if not case:
455+
raise HTTPException(
456+
status_code=status.HTTP_404_NOT_FOUND,
457+
detail="Execution not found",
458+
)
459+
460+
return ExecutionDetailResponse.model_validate(execution)

backend/app/schemas/agent.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,3 +684,22 @@ class AgentExecutionResponse(BaseModel):
684684
updated_at: datetime = Field(..., description="Record last update time")
685685

686686
model_config = ConfigDict(from_attributes=True)
687+
688+
689+
class ExecutionDetailResponse(BaseModel):
690+
"""Detailed execution data for a single agent run."""
691+
692+
id: UUID = Field(..., description="Execution record ID")
693+
agent_name: str = Field(..., description="Logical agent name")
694+
model_name: str = Field(..., description="Gemini model ID")
695+
input_data: dict[str, object] | None = Field(
696+
default=None, description="Agent input context"
697+
)
698+
output_data: dict[str, object] | None = Field(
699+
default=None, description="Structured agent output"
700+
)
701+
thinking_traces: list[dict[str, object]] | None = Field(
702+
default=None, description="Thinking traces"
703+
)
704+
705+
model_config = ConfigDict(from_attributes=True)

backend/app/services/agent_events.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ def build_execution_metadata(
535535
duration_ms = int(delta.total_seconds() * 1000)
536536

537537
return {
538+
"executionId": str(execution.id),
538539
"inputTokens": execution.input_tokens or 0,
539540
"outputTokens": execution.output_tokens or 0,
540541
"durationMs": duration_ms or 0,
@@ -622,6 +623,7 @@ def build_agent_result(
622623
routing_decisions_camel.append(
623624
{
624625
"fileId": rd.get("file_id", ""),
626+
"fileName": rd.get("file_name", ""),
625627
"targetAgent": agent,
626628
"reason": rd.get("reasoning", ""),
627629
"domainScore": score,
@@ -644,16 +646,21 @@ def build_agent_result(
644646
# Domain agents (financial, legal, evidence)
645647
findings = output.get("findings", [])
646648
entities = output.get("entities", [])
647-
# Extract group label from input_data stage_suffix
649+
# Extract group label and file names from input_data
648650
group_label = "default"
651+
file_names: list[str] = []
649652
if execution.input_data and isinstance(execution.input_data, dict):
650653
raw_suffix = execution.input_data.get("stage_suffix", "")
651654
group_label = (
652655
raw_suffix.lstrip("_") if isinstance(raw_suffix, str) else "default"
653656
) or "default"
657+
raw_names = execution.input_data.get("file_names", [])
658+
if isinstance(raw_names, list):
659+
file_names = [str(n) for n in raw_names]
654660

655661
result["baseAgentType"] = agent_name
656662
result["groupLabel"] = group_label
663+
result["fileNames"] = file_names
657664
result["outputs"] = [
658665
{
659666
"type": f"{agent_name}-findings",

backend/app/services/pipeline.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ async def run_analysis_workflow(
366366
"routingDecisions": [
367367
{
368368
"fileId": rd.file_id,
369+
"fileName": rd.file_name or "",
369370
"targetAgent": agent,
370371
"reason": rd.reasoning,
371372
"domainScore": getattr(rd.domain_scores, agent, 0),
@@ -530,6 +531,12 @@ async def run_analysis_workflow(
530531
]
531532
expected_tasks = compute_agent_tasks(orchestrator_output, files)
532533

534+
# Build lookup: compound_id -> list of original filenames
535+
domain_file_names: dict[str, list[str]] = {}
536+
for task in expected_tasks:
537+
cid = f"{task.agent_type}_{task.group_label}"
538+
domain_file_names[cid] = [f.original_filename for f in task.files]
539+
533540
# Emit agent-started for each expected (agent_type, group_label) pair
534541
# Use compound identifier: "{agent_type}_{group_label}"
535542
domain_task_ids: dict[str, str] = {} # compound_id -> task_id
@@ -607,6 +614,7 @@ async def run_analysis_workflow(
607614
"agentType": compound_id,
608615
"baseAgentType": domain_agent,
609616
"groupLabel": grp_label,
617+
"fileNames": domain_file_names.get(compound_id, []),
610618
"outputs": [
611619
{
612620
"type": f"{domain_agent}-findings",

backend/redact_pdf.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ def main():
7979
sys.exit(1)
8080

8181
if not input_path.suffix.lower() == ".pdf":
82-
print(f"Error: Input must be a PDF file: {args.pdf_file}", file=sys.stderr)
82+
print(
83+
f"Error: Input must be a PDF file: {args.pdf_file}",
84+
file=sys.stderr,
85+
)
8386
sys.exit(1)
8487

8588
try:
@@ -98,7 +101,7 @@ def main():
98101
permanent=args.permanent,
99102
)
100103

101-
print(f"✓ Redaction complete!")
104+
print("✓ Redaction complete!")
102105
print(f" Output: {output_file}")
103106
print(f" Redacted items: {info['redaction_count']}")
104107

backend/test_redaction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def test_redaction():
2424
try:
2525
output_file, response = agent.redact_pdf(pdf_path, redaction_prompt)
2626

27-
print(f"✓ Success!")
27+
print("✓ Success!")
2828
print(f" Output: {output_file}")
2929
print(f" Redacted: {len(response.targets)} items")
3030
print(f" Reasoning: {response.reasoning}\n")

backend/test_video_redaction.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def test_video_redaction():
6767
}
6868

6969
print("\nSending request to API...")
70-
print(f"⏳ Processing... (this may take 2-10 minutes)")
70+
print("⏳ Processing... (this may take 2-10 minutes)")
7171
print(f"Started at: {datetime.now().strftime('%H:%M:%S')}")
7272
print()
7373

@@ -122,7 +122,7 @@ def test_video_redaction():
122122
if result.get("logs"):
123123
log_path = f"{stem}_logs_{timestamp}.txt"
124124
with open(log_path, "w") as f:
125-
f.write(f"Video Censorship Pipeline Logs\n")
125+
f.write("Video Censorship Pipeline Logs\n")
126126
f.write(f"Generated: {datetime.now().isoformat()}\n")
127127
f.write(f"Video: {video_path}\n")
128128
f.write(f"Prompt: {prompt}\n")
@@ -133,7 +133,7 @@ def test_video_redaction():
133133
print(f"📝 Logs saved: {log_path}")
134134

135135
# Print last 20 logs
136-
print(f"\n📋 Pipeline Logs (last 20 entries):")
136+
print("\n📋 Pipeline Logs (last 20 entries):")
137137
for log in result["logs"][-20:]:
138138
if "[ERROR]" in log:
139139
print(f" ❌ {log}")
@@ -143,7 +143,7 @@ def test_video_redaction():
143143
print(f" ℹ️ {log}")
144144

145145
except requests.Timeout:
146-
print(f"❌ Request timed out after 15 minutes")
146+
print("❌ Request timed out after 15 minutes")
147147
print(" Video may be too long or complex")
148148
except requests.RequestException as e:
149149
print(f"❌ Request failed: {e}")

0 commit comments

Comments
 (0)