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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"""

import json
from datetime import date, datetime, time
from pathlib import Path
from typing import Never, cast

Expand Down Expand Up @@ -93,6 +94,25 @@ def _load_prompt_and_rules(self) -> str:

return prompt_template.replace("{{RULES_DSL}}", rules_text)

def _json_default(self, value: object) -> str:
"""Convert non-JSON-native values from processed output into strings."""
if isinstance(value, (datetime, date, time)):
return value.isoformat()
raise TypeError(f"Object of type {type(value).__name__} is not JSON serializable")

def _serialize_processed_output(self, processed_output: dict) -> str:
"""Serialize processed output for prompt injection.

Content-processing results can contain Python datetime objects when they
are materialized from storage, so serialize those explicitly instead of
letting ``json.dumps`` fail mid-workflow.
"""
return json.dumps(
processed_output,
ensure_ascii=False,
default=self._json_default,
)

@handler
async def handle_execute(
self,
Expand Down Expand Up @@ -157,7 +177,7 @@ async def handle_execute(
extracted_file = ExtractedFile(
file_name=document["file_name"],
mime_type=document["mime_type"],
extracted_content=json.dumps(processed_output),
extracted_content=self._serialize_processed_output(processed_output),
)
processed_files.append(extracted_file)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@

from __future__ import annotations

from unittest.mock import patch
import json
import sys
from datetime import datetime
from unittest.mock import MagicMock, patch

import pytest

from steps.gap_analysis.executor.gap_executor import GapExecutor
with patch.dict(
sys.modules,
{
"repositories.claim_processes": MagicMock(Claim_Processes=object),
"services.content_process_service": MagicMock(ContentProcessService=object),
},
):
with patch("agent_framework.handler", lambda fn: fn):
from steps.gap_analysis.executor.gap_executor import GapExecutor


class TestReadTextFile:
Expand Down Expand Up @@ -69,3 +80,27 @@ def fake_read(path):

with pytest.raises(RuntimeError, match="Invalid YAML"):
exe._load_prompt_and_rules()


class TestSerializeProcessedOutput:
def _make_executor(self):
with patch.object(GapExecutor, "__init__", lambda self, *a, **kw: None):
exe = GapExecutor.__new__(GapExecutor)
exe._PROMPT_FILE_NAME = "gap_executor_prompt.txt"
exe._RULES_FILE_NAME = "fnol_gap_rules.dsl.yaml"
return exe

def test_serializes_datetime_values(self):
exe = self._make_executor()

serialized = exe._serialize_processed_output(
{
"created_at": datetime(2026, 3, 27, 12, 56, 20),
"nested": {"updated_at": datetime(2026, 3, 27, 13, 1, 2)},
}
)

assert json.loads(serialized) == {
"created_at": "2026-03-27T12:56:20",
"nested": {"updated_at": "2026-03-27T13:01:02"},
}
Loading