Skip to content

Commit 93f3753

Browse files
authored
truth-hitl: support enhanced review payload shapes for UI (#389)
1 parent 40c859e commit 93f3753

File tree

3 files changed

+110
-9
lines changed

3 files changed

+110
-9
lines changed

apps/truth-hitl/src/truth_hitl/review_manager.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
from pydantic import BaseModel
99

10+
ReasoningPayload = str | list[str] | dict[str, Any] | list[dict[str, Any]]
11+
SourceAssetPayload = str | dict[str, Any]
12+
1013

1114
class ReviewItem(BaseModel):
1215
"""A proposed attribute pending human review."""
@@ -27,8 +30,8 @@ class ReviewItem(BaseModel):
2730
reviewed_by: str | None = None
2831
original_data: dict[str, Any] | None = None
2932
enriched_data: dict[str, Any] | None = None
30-
reasoning: str | None = None
31-
source_assets: list[dict[str, Any]] | None = None
33+
reasoning: ReasoningPayload | None = None
34+
source_assets: list[SourceAssetPayload] | None = None
3235
source_type: str | None = None
3336

3437

@@ -55,8 +58,8 @@ class AuditEvent(BaseModel):
5558
timestamp: datetime
5659
original_data: dict[str, Any] | None = None
5760
enriched_data: dict[str, Any] | None = None
58-
reasoning: str | None = None
59-
source_assets: list[dict[str, Any]] | None = None
61+
reasoning: ReasoningPayload | None = None
62+
source_assets: list[SourceAssetPayload] | None = None
6063
source_type: str | None = None
6164

6265

apps/truth-hitl/tests/test_event_handlers.py

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import json
66

77
import pytest
8-
from truth_hitl.event_handlers import build_event_handlers
8+
import truth_hitl.event_handlers as event_handlers
9+
from truth_hitl.adapters import build_hitl_adapters
10+
from truth_hitl.review_manager import ReviewManager
911

1012

1113
class FakeEvent:
@@ -20,7 +22,7 @@ def body_as_str(self) -> str:
2022

2123
@pytest.mark.asyncio
2224
async def test_handle_hitl_job_enqueues_item():
23-
handlers = build_event_handlers()
25+
handlers = event_handlers.build_event_handlers()
2426
handler = handlers["hitl-jobs"]
2527

2628
payload = {
@@ -44,7 +46,7 @@ async def test_handle_hitl_job_enqueues_item():
4446

4547
@pytest.mark.asyncio
4648
async def test_handle_hitl_job_skips_missing_entity_id():
47-
handlers = build_event_handlers()
49+
handlers = event_handlers.build_event_handlers()
4850
handler = handlers["hitl-jobs"]
4951

5052
payload = {
@@ -57,7 +59,7 @@ async def test_handle_hitl_job_skips_missing_entity_id():
5759

5860
@pytest.mark.asyncio
5961
async def test_handle_hitl_job_skips_missing_attr_id():
60-
handlers = build_event_handlers()
62+
handlers = event_handlers.build_event_handlers()
6163
handler = handlers["hitl-jobs"]
6264

6365
payload = {
@@ -68,5 +70,62 @@ async def test_handle_hitl_job_skips_missing_attr_id():
6870

6971

7072
def test_build_event_handlers_includes_hitl_jobs():
71-
handlers = build_event_handlers()
73+
handlers = event_handlers.build_event_handlers()
7274
assert "hitl-jobs" in handlers
75+
76+
77+
@pytest.mark.asyncio
78+
async def test_handle_hitl_job_accepts_enhanced_ui_payload_shapes(monkeypatch):
79+
review_manager = ReviewManager()
80+
81+
def _stub_build_hitl_adapters():
82+
return build_hitl_adapters(review_manager=review_manager)
83+
84+
monkeypatch.setattr(event_handlers, "build_hitl_adapters", _stub_build_hitl_adapters)
85+
86+
handlers = event_handlers.build_event_handlers()
87+
handler = handlers["hitl-jobs"]
88+
89+
payload = {
90+
"event_type": "proposed_attribute",
91+
"data": {
92+
"entity_id": "prod-100",
93+
"attr_id": "attr-200",
94+
"field_name": "material",
95+
"proposed_value": "Organic Cotton",
96+
"confidence": 0.92,
97+
"source": "ai",
98+
"product_title": "Heritage Shirt",
99+
"category_label": "Apparel",
100+
"reasoning": [
101+
"Image texture suggests cotton",
102+
"Catalog title includes 'cotton'",
103+
],
104+
"source_assets": [
105+
"https://cdn.example.com/products/prod-100/front.jpg",
106+
{
107+
"url": "https://cdn.example.com/products/prod-100/zoom.jpg",
108+
"asset_id": "dam-200",
109+
"kind": "image",
110+
},
111+
],
112+
"source_type": "hybrid",
113+
},
114+
}
115+
116+
await handler(None, FakeEvent(payload))
117+
118+
queued = review_manager.get_by_entity("prod-100")
119+
assert len(queued) == 1
120+
assert queued[0].reasoning == [
121+
"Image texture suggests cotton",
122+
"Catalog title includes 'cotton'",
123+
]
124+
assert queued[0].source_assets == [
125+
"https://cdn.example.com/products/prod-100/front.jpg",
126+
{
127+
"url": "https://cdn.example.com/products/prod-100/zoom.jpg",
128+
"asset_id": "dam-200",
129+
"kind": "image",
130+
},
131+
]

docs/implementation/truth-layer-api.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,45 @@ When available, proposal payloads include enrichment context fields for reviewer
275275

276276
These fields are optional for backward compatibility with older proposals.
277277

278+
For enhanced review UI support, `reasoning` and `source_assets` accept richer shapes:
279+
280+
- `reasoning`: string, list of strings, or structured reasoning objects.
281+
- `source_assets`: list containing DAM image URLs and/or structured asset metadata objects.
282+
283+
### Example MCP response: `/review/get_proposal`
284+
285+
```json
286+
{
287+
"entity_id": "prod-100",
288+
"proposal": {
289+
"entity_id": "prod-100",
290+
"attr_id": "attr-200",
291+
"field_name": "material",
292+
"proposed_value": "Organic Cotton",
293+
"current_value": null,
294+
"original_data": {
295+
"material": null
296+
},
297+
"enriched_data": {
298+
"material": "Organic Cotton"
299+
},
300+
"reasoning": [
301+
"Image texture suggests cotton",
302+
"Catalog title includes 'cotton'"
303+
],
304+
"source_assets": [
305+
"https://cdn.example.com/products/prod-100/front.jpg",
306+
{
307+
"asset_id": "dam-200",
308+
"url": "https://cdn.example.com/products/prod-100/zoom.jpg",
309+
"kind": "image"
310+
}
311+
],
312+
"source_type": "hybrid"
313+
}
314+
}
315+
```
316+
278317
### HITL Decision Gate in the Notebook
279318

280319
- Queue observation occurs in `Stage 8 - HITL Queue Observation` using Truth HITL `stats` and `list` actions.

0 commit comments

Comments
 (0)