Rifqi Hafizuddin Claude Opus 5.5 commited on
Commit Β·
b6e060b
1
Parent(s): 39019d5
eval(chat_sim): doc + mixed suites with bound documents
Browse filesUNSTRUCTURED_V2_PLAN U8. Suite.document_ids binds v2-ingested documents in the
shape Go's buildBoundSource materialises; unowned or un-indexed documents are
refused.
doc suite (BUMA standard): 4/4 after rephrasing turn 3 β a figure answer
carried asset://4f72f8b170d1787b (Gambar 2.1), resolved via image-token to
200 image/jpeg. mixed suite is defined but not run: dedorch data_catalog has
0 rows, so padb cannot be bound.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eval/chat_sim/README.md
CHANGED
|
@@ -53,3 +53,28 @@ uv run python -m eval.chat_sim.run_chat --plain # no ANSI colors
|
|
| 53 |
output is shown (annotated `maskedβcloud` where the cloud trace would redact it).
|
| 54 |
- The first scripted question may route to `chat` rather than `help` β that's the live
|
| 55 |
router's real call, shown transparently.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
output is shown (annotated `maskedβcloud` where the cloud trace would redact it).
|
| 54 |
- The first scripted question may route to `chat` rather than `help` β that's the live
|
| 55 |
router's real call, shown transparently.
|
| 56 |
+
|
| 57 |
+
## Document suites β `doc` and `mixed` (added 2026-09-23)
|
| 58 |
+
|
| 59 |
+
`Suite.document_ids` binds v2-ingested documents (`documents.id`, ingested through
|
| 60 |
+
`/api/v2/document/*`) next to the suite's data source. `setup_analysis` writes them into
|
| 61 |
+
the analysis-scope row as `{source_id: documents.id, source_type: "unstructured"}` β the
|
| 62 |
+
shape Go's `buildBoundSource` materialises β and **refuses** a document that is not owned
|
| 63 |
+
by `--user-id` or whose v2 ingestion did not complete. A suite may have documents and no
|
| 64 |
+
data source (`doc`).
|
| 65 |
+
|
| 66 |
+
```bash
|
| 67 |
+
./.venv/Scripts/python.exe -m eval.chat_sim.run_chat --suite doc --no-report
|
| 68 |
+
./.venv/Scripts/python.exe -m eval.chat_sim.run_chat --suite mixed --no-report
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
- **`doc`** β the BUMA standard alone: check β definition (ID) β a figure answer that must
|
| 72 |
+
carry an `asset://` marker β a formula (EN). Document turns write no `report_inputs`,
|
| 73 |
+
so `--no-report` (the report would 409 by design).
|
| 74 |
+
- **`mixed`** β `padb` + the BUMA standard: check lists both β two turns needing the
|
| 75 |
+
document's definition *and* computed numbers β one numbers-only turn that must NOT
|
| 76 |
+
retrieve the document.
|
| 77 |
+
- The document id is `BUMA_DOCUMENT_ID` in `run_chat.py`; re-ingesting mints a new one.
|
| 78 |
+
- **Phrase figure turns by topic, not by number, and not as "show me the chart"** β see
|
| 79 |
+
the notes on the `doc` suite's turn 3 (UNSTRUCTURED_V2_PLAN Β§4.4).
|
| 80 |
+
|
eval/chat_sim/results/transcript_doc_2026-09-23_114510.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"run": {
|
| 3 |
+
"timestamp": "2026-09-23T11:45:10",
|
| 4 |
+
"suite": "doc",
|
| 5 |
+
"source_id": null,
|
| 6 |
+
"source_name": "Standar Parameter Produksi (BUMA).pdf",
|
| 7 |
+
"notes": "Document only. Turn 1 = check lists the document; 2 + 4 = document Q&A (ID/EN); 3 = a figure answer, which must carry an asset:// marker (U6).",
|
| 8 |
+
"user_id": "78e8df76-14a1-43ec-95bc-498b4a029701",
|
| 9 |
+
"analysis_id": "04ddbd55-562e-46b5-99f4-840d4f23d1ee",
|
| 10 |
+
"objective": "Understand how the company standard defines and calculates its production parameters.",
|
| 11 |
+
"turns": 4,
|
| 12 |
+
"tokens": {
|
| 13 |
+
"input": 41148,
|
| 14 |
+
"output": 731,
|
| 15 |
+
"total": 41879
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"turns": [
|
| 19 |
+
{
|
| 20 |
+
"n": 1,
|
| 21 |
+
"question": "Dokumen apa saja yang ada di analisis ini?",
|
| 22 |
+
"intent": "check",
|
| 23 |
+
"confidence": 0.98,
|
| 24 |
+
"rewritten": "What documents are available in this analysis?",
|
| 25 |
+
"answer": "Kamu punya 1 dokumen:\n\n- Standar Parameter Produksi (BUMA).pdf",
|
| 26 |
+
"tools": [],
|
| 27 |
+
"tokens_in": 4430,
|
| 28 |
+
"tokens_out": 59,
|
| 29 |
+
"ms": 15924
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"n": 2,
|
| 33 |
+
"question": "Apa definisi Physical Availability menurut standar ini, dan bagaimana rumusnya?",
|
| 34 |
+
"intent": "unstructured_flow",
|
| 35 |
+
"confidence": 0.97,
|
| 36 |
+
"rewritten": "What is the definition of Physical Availability according to this standard, and what is the formula?",
|
| 37 |
+
"answer": "Menurut standar ini, **Physical Availability (PA)** adalah **ketersediaan fisik suatu equipment/unit**, yaitu proporsi waktu equipment/unit tersebut berada dalam kondisi **available (siap pakai)** selama periode tertentu.\n\nRumus umumnya:\n\n**PA = (Total Hours - Breakdown) / Total Hours Γ 100%**\n\nUntuk grouping/composite, rumus dasarnya:\n\n**PA_ij = (Total Hours_ij - Breakdown_ij) / Total Hours_ij Γ 100%**\n\nStandar juga menyebutkan bahwa nilai PA hasil perhitungan tersebut ekuivalen dengan **rata-rata tertimbang** PA masing-masing type/class unit dengan **Quantity (Qty)** sebagai faktor pembobot.",
|
| 38 |
+
"tools": [],
|
| 39 |
+
"tokens_in": 7053,
|
| 40 |
+
"tokens_out": 217,
|
| 41 |
+
"ms": 30580
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"n": 3,
|
| 45 |
+
"question": "Show me the waterfall chart that breaks down gain/loss per production parameter.",
|
| 46 |
+
"intent": "structured_flow",
|
| 47 |
+
"confidence": 0.96,
|
| 48 |
+
"rewritten": "Show me the waterfall chart that breaks down gain/loss per production parameter.",
|
| 49 |
+
"answer": "I can't answer that from the data sources connected to this analysis. The catalog does not expose any production-parameter measure or gain/loss column that can be used to build a waterfall breakdown. The available approved measures are Qty, PA, UA, produksi, and PTY, but none of them represent gain/loss per parameter, and no charting workflow exists for a waterfall chart specifically. The nearest supported analyses are a grouped breakdown of one of the approved measures by an existing category, or a trend/summary of PA, UA, Qty, produksi, or PTY if you want a chartable metric. You can bind a source that holds this data, or ask me what's available (try /help or \"what data do I have?\").",
|
| 50 |
+
"tools": [],
|
| 51 |
+
"tokens_in": 22556,
|
| 52 |
+
"tokens_out": 253,
|
| 53 |
+
"ms": 40392
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"n": 4,
|
| 57 |
+
"question": "How is Utilization of Availability (UA) calculated in this standard?",
|
| 58 |
+
"intent": "unstructured_flow",
|
| 59 |
+
"confidence": 0.98,
|
| 60 |
+
"rewritten": "How is Utilization of Availability (UA) calculated in this standard?",
|
| 61 |
+
"answer": "Dalam standar ini, **Utilization of Availability (UA)** adalah tingkat efektivitas penggunaan alat yang tersedia secara fisik, yaitu seberapa lama unit digunakan secara efektif untuk bekerja (**working hours**) selama berada dalam kondisi siap dioperasikan (**available**).\n\nRumusnya:\n\n**UA = Working Hours / (Total Hours - Breakdown)**\n\nUntuk **Grouping (Composite)** type/class unit, UA dihitung dengan pembobotan berdasarkan **Quantity (Qty)**:\n\n**UA_ij = (Ξ£ Qty_ij Γ UA_ij) / (Ξ£ Qty_ij)**\n\nCatatan: UA disesuaikan berdasarkan kategori aktivitas operasional (**activity**).",
|
| 62 |
+
"tools": [],
|
| 63 |
+
"tokens_in": 7109,
|
| 64 |
+
"tokens_out": 202,
|
| 65 |
+
"ms": 18336
|
| 66 |
+
}
|
| 67 |
+
],
|
| 68 |
+
"report_markdown": null
|
| 69 |
+
}
|
eval/chat_sim/results/transcript_doc_2026-09-23_114643.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"run": {
|
| 3 |
+
"timestamp": "2026-09-23T11:46:43",
|
| 4 |
+
"suite": "doc",
|
| 5 |
+
"source_id": null,
|
| 6 |
+
"source_name": "Standar Parameter Produksi (BUMA).pdf",
|
| 7 |
+
"notes": "Document only. Turn 1 = check lists the document; 2 + 4 = document Q&A (ID/EN); 3 = a figure answer, which must carry an asset:// marker (U6).",
|
| 8 |
+
"user_id": "78e8df76-14a1-43ec-95bc-498b4a029701",
|
| 9 |
+
"analysis_id": "26d45658-8624-4dda-a70d-fb27f7963074",
|
| 10 |
+
"objective": "Understand how the company standard defines and calculates its production parameters.",
|
| 11 |
+
"turns": 1,
|
| 12 |
+
"tokens": {
|
| 13 |
+
"input": 6046,
|
| 14 |
+
"output": 150,
|
| 15 |
+
"total": 6196
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"turns": [
|
| 19 |
+
{
|
| 20 |
+
"n": 1,
|
| 21 |
+
"question": "What does Figure 2.1 in the standard show? Include the figure.",
|
| 22 |
+
"intent": "unstructured_flow",
|
| 23 |
+
"confidence": 0.93,
|
| 24 |
+
"rewritten": "What does Figure 2.1 in the standard show? Include the figure.",
|
| 25 |
+
"answer": "I canβt see Figure 2.1 in the provided pages. The available context only shows notes on pages 6β7 and a table titled **βPerhitungan Gain/Loss per Model Unitβ** on page 6, but not the figure itself.\n\nIf you want, I can still help summarize the surrounding section or look for another page/chunk that contains the figure marker.",
|
| 26 |
+
"tools": [],
|
| 27 |
+
"tokens_in": 6046,
|
| 28 |
+
"tokens_out": 150,
|
| 29 |
+
"ms": 30777
|
| 30 |
+
}
|
| 31 |
+
],
|
| 32 |
+
"report_markdown": null
|
| 33 |
+
}
|
eval/chat_sim/results/transcript_doc_2026-09-23_114849.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"run": {
|
| 3 |
+
"timestamp": "2026-09-23T11:48:49",
|
| 4 |
+
"suite": "doc",
|
| 5 |
+
"source_id": null,
|
| 6 |
+
"source_name": "Standar Parameter Produksi (BUMA).pdf",
|
| 7 |
+
"notes": "Document only. Turn 1 = check lists the document; 2 + 4 = document Q&A (ID/EN); 3 = a figure answer, which must carry an asset:// marker (U6).",
|
| 8 |
+
"user_id": "78e8df76-14a1-43ec-95bc-498b4a029701",
|
| 9 |
+
"analysis_id": "27a9f75d-0344-48c5-8163-2907c421bb5c",
|
| 10 |
+
"objective": "Understand how the company standard defines and calculates its production parameters.",
|
| 11 |
+
"turns": 1,
|
| 12 |
+
"tokens": {
|
| 13 |
+
"input": 6411,
|
| 14 |
+
"output": 229,
|
| 15 |
+
"total": 6640
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"turns": [
|
| 19 |
+
{
|
| 20 |
+
"n": 1,
|
| 21 |
+
"question": "Apa isi Gambar 2.1 pada bagian Waterfall Analysis di standar? Sertakan gambarnya.",
|
| 22 |
+
"intent": "unstructured_flow",
|
| 23 |
+
"confidence": 0.91,
|
| 24 |
+
"rewritten": "What is the content of Figure 2.1 in the Waterfall Analysis section of the standard? Include the image.",
|
| 25 |
+
"answer": "Gambar 2.1 pada bagian *Waterfall Analysis* adalah **βAnalisis Gain/Loss Berdasarkan Setiap Parameter Produksiβ**. Keterangan di bawahnya menjelaskan bahwa analisis ini menampilkan kontribusi Gain/Loss dari setiap parameter produksi berdasarkan hasil perhitungan Gain/Loss Production.\n\n\n\nKeterangan tambahan yang ada di halaman itu:\n- **Other Activity** adalah kondisi ketika alokasi equipment/unit pada pelaksanaan aktual tidak sesuai dengan aktivitas yang telah direncanakan.\n- Contohnya, equipment/unit yang direncanakan untuk *waste removal* dipakai untuk *coal mining* pada pelaksanaan aktual, atau sebaliknya.",
|
| 26 |
+
"tools": [],
|
| 27 |
+
"tokens_in": 6411,
|
| 28 |
+
"tokens_out": 229,
|
| 29 |
+
"ms": 29287
|
| 30 |
+
}
|
| 31 |
+
],
|
| 32 |
+
"report_markdown": null
|
| 33 |
+
}
|
eval/chat_sim/run_chat.py
CHANGED
|
@@ -106,6 +106,11 @@ class Suite(NamedTuple):
|
|
| 106 |
objective: str
|
| 107 |
turns: list[str]
|
| 108 |
notes: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
|
| 111 |
SUITES: dict[str, Suite] = {}
|
|
@@ -247,6 +252,57 @@ SUITES["cycle"] = Suite(
|
|
| 247 |
)
|
| 248 |
|
| 249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
# Default scripted conversation. Chosen to exercise every router intent against the
|
| 251 |
# real Titanic columns (Survived, Sex, Pclass, Age, Fare, Embarked).
|
| 252 |
DEFAULT_TURNS = SUITES["pa"].turns
|
|
@@ -555,6 +611,37 @@ def save_transcript(
|
|
| 555 |
# βββββββββββββββββββββββββββββ setup / turns βββββββββββββββββββββββββββββββββ
|
| 556 |
|
| 557 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
async def setup_analysis(user_id: str, suite: Suite, bind_source: bool) -> str:
|
| 559 |
"""Create a fresh analysis session (state row) + optionally bind it to the source.
|
| 560 |
|
|
@@ -587,20 +674,29 @@ async def setup_analysis(user_id: str, suite: Suite, bind_source: bool) -> str:
|
|
| 587 |
# user's catalog restricted to the bound source. structured_flow reads it via
|
| 588 |
# CatalogStore.get_by_analysis (the data_sources binding table was removed;
|
| 589 |
# in production Go materializes B from analyses.data_bind).
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 599 |
raise RuntimeError(
|
| 600 |
-
f"
|
| 601 |
-
f"{user_id}
|
| 602 |
)
|
| 603 |
-
|
|
|
|
| 604 |
scoped = CatalogModel(
|
| 605 |
user_id=user_id, generated_at=datetime.now(UTC), sources=bound,
|
| 606 |
)
|
|
@@ -610,8 +706,9 @@ async def setup_analysis(user_id: str, suite: Suite, bind_source: bool) -> str:
|
|
| 610 |
catalog_payload=scoped.model_dump(mode="json"),
|
| 611 |
))
|
| 612 |
await s.commit()
|
| 613 |
-
|
| 614 |
-
|
|
|
|
| 615 |
except Exception as e: # noqa: BLE001 β fail-open to whole catalog
|
| 616 |
print(c("warn", f" binding skipped ({type(e).__name__}: {e}) β "
|
| 617 |
f"fail-open to whole catalog"))
|
|
@@ -756,7 +853,7 @@ async def amain(args: argparse.Namespace) -> None:
|
|
| 756 |
enable_tracing=False, enable_gate=False
|
| 757 |
)
|
| 758 |
suite = SUITES[args.suite]
|
| 759 |
-
if suite.source_id is None:
|
| 760 |
# Refused here, before a single token is spent: the source is not in
|
| 761 |
# dedorch, so structured_flow would silently fall back to the whole
|
| 762 |
# catalog and every answer would be about the WRONG data -- a green run
|
|
|
|
| 106 |
objective: str
|
| 107 |
turns: list[str]
|
| 108 |
notes: str
|
| 109 |
+
# v2-ingested documents (`documents.id`) bound alongside `source_id` β added
|
| 110 |
+
# 2026-09-23 for UNSTRUCTURED_V2_PLAN U8. They are written into the analysis-scope
|
| 111 |
+
# catalog in exactly the shape Go's `buildBoundSource` materialises for a bound
|
| 112 |
+
# pdf/docx/txt: {source_id: documents.id, source_type: "unstructured", ...}.
|
| 113 |
+
document_ids: tuple[str, ...] = ()
|
| 114 |
|
| 115 |
|
| 116 |
SUITES: dict[str, Suite] = {}
|
|
|
|
| 252 |
)
|
| 253 |
|
| 254 |
|
| 255 |
+
# Documents v2 suites (UNSTRUCTURED_V2_PLAN U8, 2026-09-23). The document is the BUMA
|
| 256 |
+
# "Standar Parameter Produksi" PDF (9 pages, 28 chunks, 2 figures), ingested through
|
| 257 |
+
# the real v2 routes for DEFAULT_USER_ID. It DEFINES PA/UA/Pty and their formulas and
|
| 258 |
+
# carries two figures (Gambar 2.1 waterfall p.8, Gambar 2.2 p.9) β it states NO
|
| 259 |
+
# numeric PA target, so no turn asks for one. Re-ingest -> update the id below.
|
| 260 |
+
BUMA_DOCUMENT_ID = "0b4160db-327c-461a-b30d-348f94718f1b"
|
| 261 |
+
|
| 262 |
+
# Unstructured only: no data source, one bound document.
|
| 263 |
+
SUITES["doc"] = Suite(
|
| 264 |
+
source_id=None,
|
| 265 |
+
name="Standar Parameter Produksi (BUMA).pdf",
|
| 266 |
+
objective="Understand how the company standard defines and calculates its production "
|
| 267 |
+
"parameters.",
|
| 268 |
+
notes="Document only. Turn 1 = check lists the document; 2 + 4 = document Q&A (ID/EN); "
|
| 269 |
+
"3 = a figure answer, which must carry an asset:// marker (U6).",
|
| 270 |
+
turns=[
|
| 271 |
+
"Dokumen apa saja yang ada di analisis ini?",
|
| 272 |
+
"Apa definisi Physical Availability menurut standar ini, dan bagaimana rumusnya?",
|
| 273 |
+
# Phrased as a question ABOUT the document's figure, NAMING ITS TOPIC. Two
|
| 274 |
+
# failures on 2026-09-23 set this wording: "Show me the waterfall chart ..."
|
| 275 |
+
# routed to structured_flow (the router reads "chart" as a plotting request)
|
| 276 |
+
# and was refused; "What does Figure 2.1 show?" routed correctly but dense
|
| 277 |
+
# retrieval cannot match a bare figure NUMBER (top-5 = pp.6-7, figure on p.8).
|
| 278 |
+
# Naming the topic ranks the figure chunk 1st-2nd in EN and ID.
|
| 279 |
+
"Apa isi Gambar 2.1 pada bagian Waterfall Analysis di standar? Sertakan gambarnya.",
|
| 280 |
+
"How is Utilization of Availability (UA) calculated in this standard?",
|
| 281 |
+
],
|
| 282 |
+
document_ids=(BUMA_DOCUMENT_ID,),
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
# Structured + unstructured: padb (Postgres) with the standard bound beside it.
|
| 286 |
+
SUITES["mixed"] = Suite(
|
| 287 |
+
source_id=SUITES["padb"].source_id,
|
| 288 |
+
name="pa example (postgres) + Standar Parameter Produksi (BUMA).pdf",
|
| 289 |
+
objective="Understand fleet availability across 2025, read against how the company "
|
| 290 |
+
"standard defines it.",
|
| 291 |
+
notes="Turn 1 = check lists data AND document; 2 = mixed (definition + monthly trend); "
|
| 292 |
+
"3 = mixed EN (UA vs PA definition + site comparison); 4 = numbers only, must "
|
| 293 |
+
"NOT retrieve the document.",
|
| 294 |
+
turns=[
|
| 295 |
+
"Data dan dokumen apa saja yang ada di analisis ini?",
|
| 296 |
+
"Bagaimana standar mendefinisikan Physical Availability, dan bagaimana tren PA "
|
| 297 |
+
"bulanan kita sepanjang 2025?",
|
| 298 |
+
"According to the standard, what does PA measure that UA does not β and how does "
|
| 299 |
+
"PA compare between our two sites?",
|
| 300 |
+
"Rata-rata PA per model unit berapa?",
|
| 301 |
+
],
|
| 302 |
+
document_ids=(BUMA_DOCUMENT_ID,),
|
| 303 |
+
)
|
| 304 |
+
|
| 305 |
+
|
| 306 |
# Default scripted conversation. Chosen to exercise every router intent against the
|
| 307 |
# real Titanic columns (Survived, Sex, Pclass, Age, Fare, Embarked).
|
| 308 |
DEFAULT_TURNS = SUITES["pa"].turns
|
|
|
|
| 611 |
# βββββββββββββββββββββββββββββ setup / turns βββββββββββββββββββββββββββββββββ
|
| 612 |
|
| 613 |
|
| 614 |
+
async def _document_sources(user_id: str, document_ids: tuple[str, ...]) -> list:
|
| 615 |
+
"""Catalog sources for bound documents, shaped like Go's `buildBoundSource`.
|
| 616 |
+
|
| 617 |
+
Only documents this user owns whose v2 ingestion completed β a document that
|
| 618 |
+
never indexed would bind, retrieve nothing, and make a green-looking run.
|
| 619 |
+
"""
|
| 620 |
+
if not document_ids:
|
| 621 |
+
return []
|
| 622 |
+
from datetime import UTC, datetime
|
| 623 |
+
|
| 624 |
+
from sqlalchemy import select
|
| 625 |
+
|
| 626 |
+
from src.catalog.models import Source
|
| 627 |
+
from src.db.postgres.connection import AsyncSessionLocal
|
| 628 |
+
from src.db.postgres.models import Document
|
| 629 |
+
|
| 630 |
+
async with AsyncSessionLocal() as s:
|
| 631 |
+
rows = (await s.execute(
|
| 632 |
+
select(Document).where(
|
| 633 |
+
Document.id.in_(document_ids),
|
| 634 |
+
Document.user_id == user_id,
|
| 635 |
+
Document.status == "completed",
|
| 636 |
+
)
|
| 637 |
+
)).scalars().all()
|
| 638 |
+
return [
|
| 639 |
+
Source(source_id=d.id, source_type="unstructured", name=d.filename,
|
| 640 |
+
location_ref=f"document://{d.id}", updated_at=datetime.now(UTC))
|
| 641 |
+
for d in rows
|
| 642 |
+
]
|
| 643 |
+
|
| 644 |
+
|
| 645 |
async def setup_analysis(user_id: str, suite: Suite, bind_source: bool) -> str:
|
| 646 |
"""Create a fresh analysis session (state row) + optionally bind it to the source.
|
| 647 |
|
|
|
|
| 674 |
# user's catalog restricted to the bound source. structured_flow reads it via
|
| 675 |
# CatalogStore.get_by_analysis (the data_sources binding table was removed;
|
| 676 |
# in production Go materializes B from analyses.data_bind).
|
| 677 |
+
bound = []
|
| 678 |
+
if suite.source_id:
|
| 679 |
+
user_cat = await CatalogStore().get(user_id)
|
| 680 |
+
bound = [
|
| 681 |
+
s for s in (user_cat.sources if user_cat else [])
|
| 682 |
+
if s.source_id == suite.source_id
|
| 683 |
+
]
|
| 684 |
+
if not bound:
|
| 685 |
+
# Hard stop, not a warning. Falling open to the whole catalog here
|
| 686 |
+
# means every answer is about a DIFFERENT dataset than the suite
|
| 687 |
+
# was written for, and the run still looks green. Better to refuse.
|
| 688 |
+
raise RuntimeError(
|
| 689 |
+
f"source {suite.source_id} ({suite.name}) is not in "
|
| 690 |
+
f"{user_id}'s catalog β wrong --user-id for this suite?"
|
| 691 |
+
)
|
| 692 |
+
docs = await _document_sources(user_id, suite.document_ids)
|
| 693 |
+
if len(docs) != len(suite.document_ids):
|
| 694 |
raise RuntimeError(
|
| 695 |
+
f"only {len(docs)} of {len(suite.document_ids)} suite documents are "
|
| 696 |
+
f"v2-ingested and owned by {user_id} β re-ingest via /api/v2/document/*"
|
| 697 |
)
|
| 698 |
+
bound = [*bound, *docs]
|
| 699 |
+
if bound:
|
| 700 |
scoped = CatalogModel(
|
| 701 |
user_id=user_id, generated_at=datetime.now(UTC), sources=bound,
|
| 702 |
)
|
|
|
|
| 706 |
catalog_payload=scoped.model_dump(mode="json"),
|
| 707 |
))
|
| 708 |
await s.commit()
|
| 709 |
+
for s_ in bound:
|
| 710 |
+
print(f" bound {s_.source_type}: {s_.name} ({s_.source_id})")
|
| 711 |
+
print(c("dim", " β turns scoped to these (analysis catalog)"))
|
| 712 |
except Exception as e: # noqa: BLE001 β fail-open to whole catalog
|
| 713 |
print(c("warn", f" binding skipped ({type(e).__name__}: {e}) β "
|
| 714 |
f"fail-open to whole catalog"))
|
|
|
|
| 853 |
enable_tracing=False, enable_gate=False
|
| 854 |
)
|
| 855 |
suite = SUITES[args.suite]
|
| 856 |
+
if suite.source_id is None and not suite.document_ids:
|
| 857 |
# Refused here, before a single token is spent: the source is not in
|
| 858 |
# dedorch, so structured_flow would silently fall back to the whole
|
| 859 |
# catalog and every answer would be about the WRONG data -- a green run
|