generated from john/python-template
V4.1 revisions in preparation for v4.2. AI data capture now better defined.
This commit is contained in:
@@ -0,0 +1,176 @@
|
|||||||
|
# Raw API Response Q&A
|
||||||
|
I initiated this exchange from within VSC using my Copilot Pro subscription. I believe the model was GPT-5.3-Codex, but I am not certain.
|
||||||
|
|
||||||
|
## My prompt
|
||||||
|
```I can now see the ai_metadata and raw_api_response in the database. Help me understand what the two fields represent```
|
||||||
|
|
||||||
|
## AI Response
|
||||||
|
Yes — in this project they are two different kinds of JSON evidence attached to a transcription attempt.
|
||||||
|
|
||||||
|
- `ai_metadata` is a compact, normalized summary of the AI run.
|
||||||
|
- It is meant for operational/useful details such as:
|
||||||
|
- `finish_reason`
|
||||||
|
- token usage, such as `input_tokens`, `output_tokens`, and `total_tokens`
|
||||||
|
- It is not the full provider response; it is a curated summary for debugging and tracking.
|
||||||
|
|
||||||
|
- `raw_api_response` is the full upstream provider response, stored as JSON-compatible data.
|
||||||
|
- This preserves whatever the AI provider returned so you can inspect it later.
|
||||||
|
- It can include things like model info, choices, message content, usage, and other provider-specific fields.
|
||||||
|
|
||||||
|
A simple way to think about it:
|
||||||
|
|
||||||
|
- `raw_transcription` = the text the system extracted from the AI response
|
||||||
|
- `ai_metadata` = a summarized record of how the AI call ended and what it used
|
||||||
|
- `raw_api_response` = the full original response payload from the provider
|
||||||
|
|
||||||
|
You can see this in models.py, where both fields are stored on the job-source record, and in openrouter.py, where the provider builds `ai_metadata` from completion/usage details and stores the full response in `raw_api_response`.
|
||||||
|
|
||||||
|
Example shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
ai_metadata = {
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"usage": {
|
||||||
|
"input_tokens": 123,
|
||||||
|
"output_tokens": 45,
|
||||||
|
"total_tokens": 168
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
raw_api_response = {
|
||||||
|
"id": "...",
|
||||||
|
"model": "google/gemini-2.5-flash",
|
||||||
|
"choices": [...],
|
||||||
|
"usage": {...}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want, I can also help you interpret a specific row’s values from your database.
|
||||||
|
|
||||||
|
## My Prompt
|
||||||
|
```
|
||||||
|
I was under the impression that there may be other information in the AI response, like:
|
||||||
|
* AI's confidence level in the accuracy of the transcription--especially when inserting guesses (in square brackets)
|
||||||
|
* The coordinates of the text box where the text was found on the screen
|
||||||
|
|
||||||
|
Is this JSONB data returned by all AIs? Or some, but not all? Or none?
|
||||||
|
```
|
||||||
|
|
||||||
|
## AI Response
|
||||||
|
I’m checking the provider implementation and docs to answer this based on what this system actually captures rather than assuming a universal AI API format.
|
||||||
|
|
||||||
|
Searched for text `confidence|bounding|bbox|coordinates|box|square bracket|guess` (`**/src/**`), 9 results
|
||||||
|
|
||||||
|
Short answer: it depends on the provider and model, and in this codebase the app does not assume those fields exist.
|
||||||
|
|
||||||
|
- The JSONB columns are generic containers, so they can hold extra provider-specific data if the API returns it. You can see that in base.py and models.py.
|
||||||
|
- For the current OpenRouter integration, the app only extracts a small subset of information:
|
||||||
|
- `finish_reason`
|
||||||
|
- token usage
|
||||||
|
- the full provider response as raw JSON
|
||||||
|
That logic is in openrouter.py.
|
||||||
|
|
||||||
|
So for your examples:
|
||||||
|
|
||||||
|
- Confidence in transcription accuracy:
|
||||||
|
- Some providers/models may expose something like confidence, probability, or logits.
|
||||||
|
- Many do not.
|
||||||
|
- This app does not currently parse or surface that as a dedicated field.
|
||||||
|
|
||||||
|
- Coordinates of detected text boxes:
|
||||||
|
- Some OCR/vision APIs return bounding boxes.
|
||||||
|
- Many chat-style LLM APIs do not.
|
||||||
|
- This app does not currently extract or normalize that data.
|
||||||
|
|
||||||
|
In other words:
|
||||||
|
- “Some, but not all” is the right answer.
|
||||||
|
- “None” is not quite right for the raw JSON field, because if a provider returns those values, they would be stored there.
|
||||||
|
- But “not guaranteed by this system” is the most accurate statement, since the current implementation does not request or interpret them as first-class fields.
|
||||||
|
|
||||||
|
If you want, I can help sketch what would be needed to start capturing confidence or bounding-box data explicitly.
|
||||||
|
|
||||||
|
## My Prompt
|
||||||
|
```
|
||||||
|
So if I understand you correctly, the current contents of "raw_api_response" is not actually the full contents of the AI response? If true, why is it being abbreviated?
|
||||||
|
```
|
||||||
|
|
||||||
|
## AI Response
|
||||||
|
Yes — that is the right interpretation.
|
||||||
|
|
||||||
|
What is being stored in the current implementation is not a byte-for-byte copy of the provider’s original response. It is a best-effort, JSON-compatible representation of the provider response object after the code has tried to serialize it for the database. You can see that in openrouter.py, where the code runs through a serializer and then stores the result.
|
||||||
|
|
||||||
|
Why it is abbreviated:
|
||||||
|
|
||||||
|
- The provider SDK returns objects, not plain JSON.
|
||||||
|
- The app needs something that can be stored in a JSONB column, so it converts nested objects into JSON-friendly data.
|
||||||
|
- Some values are not serializable by default, so they are reduced to strings or simplified structures.
|
||||||
|
- The code is not capturing the full transport-level HTTP payload, headers, status codes, or raw wire bytes.
|
||||||
|
- The field is intended mainly for inspection and debugging, not as a perfect archival copy.
|
||||||
|
|
||||||
|
So the more precise wording is:
|
||||||
|
|
||||||
|
- `raw_api_response` = a normalized, JSON-friendly snapshot of the provider response
|
||||||
|
- not = the full original upstream response verbatim
|
||||||
|
|
||||||
|
If you want the truly full response, the system would need to capture the actual HTTP response body and metadata from the provider client rather than the SDK object after conversion.
|
||||||
|
|
||||||
|
## My Prompt
|
||||||
|
```
|
||||||
|
What are the pros/cons of capturing the actual HTTP response? Is there a long term benefit?
|
||||||
|
```
|
||||||
|
|
||||||
|
## AI Response
|
||||||
|
Yes — there can be a strong long-term benefit, but it comes with tradeoffs.
|
||||||
|
|
||||||
|
Pros
|
||||||
|
|
||||||
|
- Better debugging:
|
||||||
|
- You can inspect the exact provider payload when a transcription fails or behaves unexpectedly.
|
||||||
|
- This is especially helpful when a provider changes schema or returns unexpected fields.
|
||||||
|
|
||||||
|
- Better reproducibility:
|
||||||
|
- You preserve the input/output pair for a given request, which can help compare model behavior over time.
|
||||||
|
- Useful for auditing, regression analysis, and support cases.
|
||||||
|
|
||||||
|
- More complete evidence:
|
||||||
|
- You can capture status codes, headers, response bodies, and provider-specific metadata that the current normalized JSON may lose.
|
||||||
|
|
||||||
|
- Future-proofing:
|
||||||
|
- If you later want to extract new fields such as confidence, bounding boxes, or token details, you may already have the raw payload available.
|
||||||
|
|
||||||
|
Cons
|
||||||
|
|
||||||
|
- Storage cost:
|
||||||
|
- Raw HTTP responses can be large, especially if you store full bodies, headers, and repeated requests.
|
||||||
|
- Over time this can grow database size and backup/restore cost.
|
||||||
|
|
||||||
|
- Privacy and compliance:
|
||||||
|
- Some provider responses may include sensitive or regulated content.
|
||||||
|
- You need to think about retention policy and access controls.
|
||||||
|
|
||||||
|
- Complexity:
|
||||||
|
- You need a clear schema or storage strategy for raw payloads.
|
||||||
|
- You may end up storing a mix of JSON, compressed blobs, or external objects.
|
||||||
|
|
||||||
|
- Maintenance burden:
|
||||||
|
- Raw payloads can become noisy and hard to query.
|
||||||
|
- You may need separate indexes, retention rules, or archival storage.
|
||||||
|
|
||||||
|
Long-term benefit
|
||||||
|
|
||||||
|
- If your goal is traceability and model observability, yes, it is often worth it.
|
||||||
|
- In a transcription system, especially one that depends on third-party AI providers, raw response capture can become valuable for:
|
||||||
|
- troubleshooting provider changes,
|
||||||
|
- investigating hallucinations or formatting regressions,
|
||||||
|
- building confidence/quality analytics later.
|
||||||
|
|
||||||
|
A practical compromise is often:
|
||||||
|
|
||||||
|
- store a compact normalized summary in `ai_metadata`,
|
||||||
|
- store the full raw response only for selected requests or for a limited retention window,
|
||||||
|
- and keep the raw payload in an external blob/object store rather than in the main relational table if volume becomes significant.
|
||||||
|
|
||||||
|
So the short version is: capturing the actual HTTP response is usually a good investment if you care about long-term observability and auditability, but it should be designed carefully to avoid storage and privacy problems.
|
||||||
|
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
# Raw API Response Q&A - part 2
|
||||||
|
In this exchange I sought a second opinion about what raw API response data I should be collecting in my transcription app. I uploaded part 1 to Gemini 3.6 Flash, then posted my first prompt.
|
||||||
|
|
||||||
|
## My prompt
|
||||||
|
```I'm trying to decide what data I should capture from the AI doing transcriptions for my transcription app. I will have 2000-3000 documents at most (not millions!) These are family heirlooms I am transcribing for posterity, and I want to use the archival methods/practices that I can. Review the attached document and tell me what you think.```
|
||||||
|
|
||||||
|
## AI Response (Gemini 3.6 Flash)
|
||||||
|
The assistant's breakdown of `ai_metadata` vs. `raw_api_response` is technically spot-on, but **Response #4 gives advice meant for massive, enterprise-scale software, not a focused archival project.**
|
||||||
|
|
||||||
|
When building an application for 2,000–3,000 family heirloom documents, the trade-offs change completely:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. The Scale Reality: Ignore the Storage Warnings
|
||||||
|
|
||||||
|
In Response #4, the assistant cautions about storage costs, database bloat, and needing external cloud blob stores.
|
||||||
|
|
||||||
|
* **The Math:** A rich, full JSON response from an LLM API averages **5 KB to 15 KB**.
|
||||||
|
* **Your Total Volume:** $3\text{,}000 \text{ documents} \times 15\text{ KB} = \mathbf{\sim 45\text{ MB}}$ total.
|
||||||
|
|
||||||
|
45 Megabytes is negligible. It can easily sit inside PostgreSQL JSONB columns forever without causing performance or cost issues. You do not need complex retention policies, blob storage buckets, or truncated responses.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Archival Science Perspective: Provenance Matters
|
||||||
|
|
||||||
|
From a formal archival and documentary editing perspective, AI-assisted transcription is a form of document processing that requires **provenance** (origin and chain of custody).
|
||||||
|
|
||||||
|
In 20 years, when AI models have shifted dramatically, having the record of *how* a draft was generated is invaluable. Storing complete AI responses ensures:
|
||||||
|
|
||||||
|
* **Auditability:** You can trace why a specific transcription error, hallucination, or bracketed guess `[inaudible]` occurred.
|
||||||
|
* **Reproducibility:** You capture the exact model string, parameters, and model outputs used at that point in history.
|
||||||
|
* **Future Extraction:** If OpenRouter or model providers start embedding new provider-level metadata (such as reasoning traces or extended token details), you won’t regret having thrown it away.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. What You Should Actually Capture
|
||||||
|
|
||||||
|
To balance clean querying with archival completeness, stick with the two-field approach, but don't hold back on what you put in them.
|
||||||
|
|
||||||
|
#### A. Operational Summary (`ai_metadata`)
|
||||||
|
|
||||||
|
Use this for fast filtering, UI display, and analytics in your app.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"provider": "openrouter",
|
||||||
|
"model_requested": "google/gemini-2.5-flash",
|
||||||
|
"model_used": "google/gemini-2.5-flash",
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"tokens": {
|
||||||
|
"prompt_tokens": 1250,
|
||||||
|
"completion_tokens": 340,
|
||||||
|
"total_tokens": 1590
|
||||||
|
},
|
||||||
|
"latency_ms": 1420,
|
||||||
|
"cost_usd": 0.00018
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. Complete Payload (`raw_api_response`)
|
||||||
|
|
||||||
|
Store the **complete, unfiltered SDK response dictionary** returned by the API client. If the OpenRouter Python client hands back a response object, serialize its entire `.model_dump()` or `.dict()` straight into your `raw_api_response` JSONB column.
|
||||||
|
|
||||||
|
#### C. Input Parameters (Don't Forget the Prompt!)
|
||||||
|
|
||||||
|
To have a true archival record, you also need to store what you *sent* the AI, not just what it sent back. Either on the attempt record or in the metadata, record:
|
||||||
|
|
||||||
|
* The exact system prompt version used.
|
||||||
|
* The temperature / top_p settings.
|
||||||
|
* The image filename / hash sent to the vision model.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. The Reality on Bounding Boxes & Confidence Scores
|
||||||
|
|
||||||
|
Regarding your second prompt about bounding boxes and confidence scores:
|
||||||
|
|
||||||
|
1. **Bounding Boxes (Coordinates):** Standard Vision LLMs (like Gemini Flash, Claude 3.5 Sonnet, or GPT-4o) do **not** automatically return bounding boxes in API response metadata. OCR-specific tools (like Amazon Textract or Google Cloud Vision) return explicit spatial coordinates, but chat/vision LLMs only do so if you specifically instruct them in your prompt to format their output as JSON containing coordinates.
|
||||||
|
2. **Confidence Scores:** Standard completion APIs do not return a clean "95% confidence" score. The closest equivalent is token `logprobs` (log probabilities), which can be requested via API parameters (`logprobs: true`), but token probability measures statistical model likelihood during token generation, not necessarily historical accuracy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Summary Recommendation
|
||||||
|
|
||||||
|
Save the full, unfiltered API response object directly into `raw_api_response` without stripping anything out, alongside a clean `ai_metadata` summary. At 3,000 items, keeping complete raw evidence is effortless, aligns perfectly with documentary preservation standards, and protects your project's historical audit trail.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## My Prompt
|
||||||
|
```
|
||||||
|
1. You mentioned the raw api response could "easily sit inside PostgreSQL JSONB columns". I'm currently running on SQLite during development and testing, but I had already anticipated moving to PostgresSQL before I went into production. Should I go ahead an move to PostgresSQL before implementing the capture of the raw api responses?
|
||||||
|
|
||||||
|
2. Can you elaborate on "C. Input Parameters (Don't Forget the Prompt!)".
|
||||||
|
- I'm currently capturing the *name* of the prompt, but not the full text.
|
||||||
|
- what are the temperature / top_p settings?
|
||||||
|
- I'm already capturing the image_filename (It's being stored outside of the database in a folder in the python project. Can/should these images be stored in Postgres too?)
|
||||||
|
```
|
||||||
|
|
||||||
|
## AI Response (Gemini 3.6 Flash)
|
||||||
|
### 1. Should you move to PostgreSQL now or stay on SQLite?
|
||||||
|
|
||||||
|
**Recommendation: Stay on SQLite for now. Do not delay your development.**
|
||||||
|
|
||||||
|
Modern SQLite handles JSON natively via its standard JSON extension and functions. You do not need PostgreSQL’s dedicated `JSONB` binary data type just to capture and store complete API responses during development.
|
||||||
|
|
||||||
|
#### Why Staying on SQLite First Makes Sense:
|
||||||
|
|
||||||
|
* **Feature Parity for Raw Payloads:** Storing a raw API payload as a JSON-formatted string in a standard SQLite column behaves conceptually the same as putting it into PostgreSQL's `JSONB` column.
|
||||||
|
* **No Database Drift:** If you use an ORM (like SQLAlchemy or Tortoise ORM), defining a generic `JSON` column type translates smoothly between both engines.
|
||||||
|
* **Seamless Migration Later:** Since your total collection will max out at 2,000–3,000 documents, your entire dataset will easily fit in a small SQLite file. Migrating that entire dataset to PostgreSQL before going live will take minutes, not days.
|
||||||
|
|
||||||
|
Keep building features and capturing response data on SQLite. Move to PostgreSQL only when you are ready to deploy your production environment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Elaborating on Input Parameters
|
||||||
|
|
||||||
|
To satisfy archival provenance standards, capturing *how* you prompted the AI is just as crucial as capturing *what* the AI returned.
|
||||||
|
|
||||||
|
#### A. Full Text of the Prompt (Beyond the Name)
|
||||||
|
|
||||||
|
Storing only the prompt's reference name (e.g., `v1_transcription_prompt`) leaves a critical gap: if you tweak that prompt later, you lose the exact instructions used for past runs.
|
||||||
|
|
||||||
|
**What to capture:**
|
||||||
|
|
||||||
|
* **System Prompt:** The exact system instructions provided to the model.
|
||||||
|
* **User Prompt:** The exact dynamic prompt template merged with document-specific context.
|
||||||
|
* **Prompt Version/Hash:** An explicit version identifier or SHA-256 hash of the full prompt string for quick grouping.
|
||||||
|
|
||||||
|
#### B. Temperature and Top_P Settings
|
||||||
|
|
||||||
|
These are the foundational hyper-parameters controlling the randomness and creativity of the AI model's output:
|
||||||
|
|
||||||
|
| Parameter | What It Controls | Typical Value for OCR/Transcription | Why Record It? |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| **`temperature`** | Randomness of token selection. `0.0` is deterministic; higher values (e.g., `0.7`) introduce variability. | `0.0` to `0.2` | Confirms whether the run was deterministic (strict extraction) or creative (guessing missing words). |
|
||||||
|
| **`top_p`** (Nucleus Sampling) | Cuts off low-probability tokens. `0.1` means only top 10% likely words are evaluated. | `1.0` or model default | Documents constraints on the vocabulary pool available to the model during execution. |
|
||||||
|
|
||||||
|
Storing these alongside the prompt ensures you can accurately evaluate why two different runs on the same document yielded different results.
|
||||||
|
|
||||||
|
#### C. Where Should the Input Images Be Stored?
|
||||||
|
|
||||||
|
Keep storing the actual image files **in the filesystem**, not in PostgreSQL or SQLite binary `BLOB` columns.
|
||||||
|
|
||||||
|
```
|
||||||
|
/project_root
|
||||||
|
├── /storage
|
||||||
|
│ └── /images
|
||||||
|
│ └── document_00101_raw.jpg
|
||||||
|
└── /database
|
||||||
|
└── (stores path: "storage/images/document_00101_raw.jpg")
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Why File-System Storage is Preferred:
|
||||||
|
|
||||||
|
1. **Database Simplicity:** Databases perform best when handling structured metadata, indexes, and JSON queries. Storing heavy binary images in database tables inflates backup sizes and slows down general queries.
|
||||||
|
2. **Web Server & UI Efficiency:** Serving image files directly from the disk path (or an internal file server) allows web clients to leverage standard HTTP byte-range requests, browser caching, and instant thumbnail generation without overloading database connections.
|
||||||
|
3. **Archival Safety:** Image files stored cleanly in standard folder hierarchies (e.g., JPEG, TIFF, PNG) are portable, easier to back up incrementally, and accessible independently of any database engine.
|
||||||
|
|
||||||
|
**Database Rule of Thumb:** Store the **file path**, **filename**, **file size**, and **cryptographic hash** (like SHA-256) inside PostgreSQL/SQLite. Store the **binary image bytes** on disk.
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
# Digital Evidence and AI Processing Provenance (Invariant)
|
||||||
|
|
||||||
|
## 1. Purpose
|
||||||
|
|
||||||
|
This document defines non-negotiable evidence and provenance rules for the transcription application.
|
||||||
|
|
||||||
|
The application exists to preserve historical source material and produce useful transcriptions without losing the ability to inspect, reinterpret, or reprocess the evidence later. Provider integrations, model names, schemas, and user interfaces may change; the principles below must remain true.
|
||||||
|
|
||||||
|
## 2. Evidence Model
|
||||||
|
|
||||||
|
The application distinguishes five kinds of information:
|
||||||
|
|
||||||
|
1. **Source evidence**: the original uploaded media and the facts needed to identify and verify it.
|
||||||
|
2. **Execution specification**: the frozen instructions, parameters, source identity, and software context for one processing attempt.
|
||||||
|
3. **Transport evidence**: the response received at the application/provider boundary, including safe protocol metadata.
|
||||||
|
4. **Normalized data**: selected fields extracted for search, display, accounting, and workflow behavior.
|
||||||
|
5. **Derived artifacts**: outputs produced from source evidence, such as transcription text, OCR geometry, confidence data, layout analysis, or entity extraction.
|
||||||
|
|
||||||
|
Normalized data and derived artifacts never replace source or transport evidence.
|
||||||
|
|
||||||
|
## 3. Core Invariants
|
||||||
|
|
||||||
|
### 3.1 Original Source Preservation
|
||||||
|
|
||||||
|
1. The original uploaded bytes are the primary evidence and must be preserved without transformation.
|
||||||
|
2. Each source must have a cryptographic content digest, byte size, and stable identity.
|
||||||
|
3. Processing may use transformed derivatives, but those derivatives must not overwrite the original.
|
||||||
|
4. A derivative used for processing must record its relationship to the original, its transformation, and its own digest.
|
||||||
|
5. Moving or renaming a stored file must not change its evidence identity.
|
||||||
|
|
||||||
|
### 3.2 Append-Only Processing History
|
||||||
|
|
||||||
|
1. Every processing attempt must have a distinct execution record, whether it succeeds, partially succeeds, times out, or fails.
|
||||||
|
2. A later attempt must not overwrite the evidence from an earlier attempt.
|
||||||
|
3. A convenient “latest transcription” value may be maintained as a cache or projection, but it is not the authoritative execution history.
|
||||||
|
4. Human revisions must remain distinguishable from all machine-generated outputs.
|
||||||
|
5. Reprocessing a source must create new evidence rather than rewriting historical evidence.
|
||||||
|
|
||||||
|
### 3.3 Frozen Execution Specification
|
||||||
|
|
||||||
|
Each execution must preserve enough information to understand what the application asked the processor to do:
|
||||||
|
|
||||||
|
1. Requested provider, model, and provider-routing constraints.
|
||||||
|
2. Full effective system and user instructions.
|
||||||
|
3. Prompt asset name and content digest when a prompt asset is used.
|
||||||
|
4. Every explicitly supplied generation or processing parameter.
|
||||||
|
5. Whether an optional parameter was explicitly set or omitted.
|
||||||
|
6. Source and derivative digests, media type, dimensions or page geometry when known, and page identity.
|
||||||
|
7. A secret-safe representation of the request structure.
|
||||||
|
8. Application, provider-adapter, and client-library versions sufficient to interpret the execution.
|
||||||
|
|
||||||
|
The execution specification must not contain credentials, authorization headers, secret query values, or unnecessary duplicate source binaries.
|
||||||
|
|
||||||
|
### 3.4 Evidence-Layer Terminology
|
||||||
|
|
||||||
|
The following terms are not interchangeable:
|
||||||
|
|
||||||
|
- **Transport response**: the status, safe headers, and exact response body received by the application at its HTTP boundary.
|
||||||
|
- **Router-normalized response**: a response transformed by an intermediary into its common schema.
|
||||||
|
- **SDK-parsed response**: an object created when a client library validates or filters a response.
|
||||||
|
- **Normalized metadata**: application-selected fields derived from a response.
|
||||||
|
- **Native provider response**: the upstream provider's own response before any intermediary transformation.
|
||||||
|
|
||||||
|
The application and its documentation must identify which layer is stored. A response must not be described as “raw,” “complete,” or “native” without naming the boundary at which that claim is true.
|
||||||
|
|
||||||
|
### 3.5 Transport Evidence
|
||||||
|
|
||||||
|
1. Preserve the exact successful response body received at the application's transport boundary before SDK model parsing can discard unknown fields.
|
||||||
|
2. Preserve the response status and an allowlisted set of non-secret headers needed for correlation, content interpretation, rate-limit diagnosis, or audit.
|
||||||
|
3. Preserve provider/router request and generation identifiers when available.
|
||||||
|
4. Preserve safe response evidence for unsuccessful calls when a response was received.
|
||||||
|
5. Record explicitly when no response was received, such as a local timeout or connection failure.
|
||||||
|
6. Retain parsed and normalized forms only as additional representations of the preserved response.
|
||||||
|
|
||||||
|
Wire-level packet capture, TLS session data, credentials, and unrestricted headers are neither required nor permitted.
|
||||||
|
|
||||||
|
These requirements apply to executions performed after transport capture is implemented. For earlier executions, the absence of transport evidence must be represented explicitly. An SDK snapshot or normalized record must never be relabeled or backfilled as transport evidence.
|
||||||
|
|
||||||
|
### 3.6 Derived Artifact Provenance
|
||||||
|
|
||||||
|
1. Every derived artifact must identify its source evidence and producing execution.
|
||||||
|
2. Each artifact must declare its semantic type, media/serialization format, schema name and version, producer, producer version, and creation time.
|
||||||
|
3. Artifact content must be stored directly or referenced by a stable path or object identifier and protected by a cryptographic digest.
|
||||||
|
4. Coordinates must declare their coordinate system, units, origin, page/image dimensions, and transformation history.
|
||||||
|
5. Confidence values must identify the producer and scope to which they apply; values from different producers must not be treated as directly comparable without validation.
|
||||||
|
6. Provider-specific payloads may be retained, but durable application behavior must not depend on undocumented provider fields.
|
||||||
|
|
||||||
|
This model must accommodate future OCR text, word or line polygons, layout regions, confidence data, alternate transcriptions, and structured extraction without adding a dedicated column for every possible feature.
|
||||||
|
|
||||||
|
### 3.7 Integrity and Auditability
|
||||||
|
|
||||||
|
1. Stored evidence must be exportable with enough identifiers and metadata to verify relationships and digests outside the application.
|
||||||
|
2. Evidence mutation, deletion, and retention behavior must be explicit and testable.
|
||||||
|
3. Schema upgrades must preserve existing evidence and its original meaning.
|
||||||
|
4. Backfills must be identified as backfills; they must not imply that previously uncaptured evidence existed.
|
||||||
|
5. Integrity verification must distinguish a missing file, digest mismatch, unavailable external artifact, and malformed metadata.
|
||||||
|
|
||||||
|
### 3.8 Security and Privacy
|
||||||
|
|
||||||
|
1. API keys, authorization headers, cookies, and credentials must never be persisted as provenance.
|
||||||
|
2. Persist only headers and metadata fields that appear on an explicit allowlist of known-safe fields. Discard all other fields before storage; never persist an unrestricted capture and attempt to redact it afterward.
|
||||||
|
3. Request manifests should reference source content by identity instead of duplicating base64 source data.
|
||||||
|
4. Diagnostic displays and exports must avoid exposing secrets or machine-local details that are not necessary for evidence interpretation.
|
||||||
|
|
||||||
|
## 4. Reproducibility Limits
|
||||||
|
|
||||||
|
Provenance supports explanation, comparison, and best-effort reproduction; it does not guarantee identical output.
|
||||||
|
|
||||||
|
Identical requests may produce different results because of model updates, provider routing, nondeterministic computation, undocumented defaults, safety systems, or retired endpoints. The application must preserve whether a parameter was omitted rather than pretending to know the provider default used at that time.
|
||||||
|
|
||||||
|
Likewise, preserving a general vision-model response does not create OCR coordinates that were never returned. Future coordinate extraction remains possible because the original source evidence is preserved and can be processed again by a suitable system.
|
||||||
|
|
||||||
|
## 5. Model Evaluation Policy
|
||||||
|
|
||||||
|
Model selection must be based on a representative sample of the actual archive rather than vendor claims alone.
|
||||||
|
|
||||||
|
Evaluation should:
|
||||||
|
|
||||||
|
1. Use manually reviewed reference transcriptions following the project's [Transcription Methodology](transcription_methodology.md).
|
||||||
|
2. Represent printed, typed, handwritten, degraded, tabular, multilingual, and spatially complex material present in the archive.
|
||||||
|
3. Measure character and word error rates where appropriate.
|
||||||
|
4. Separately record silent corrections, invented text, omitted text, uncertainty handling, layout fidelity, cost, and latency.
|
||||||
|
5. Preserve the exact model, endpoint or route, parameters, prompt, source digest, and scoring method for every comparison.
|
||||||
|
6. Treat model rankings as corpus- and version-specific, not permanent declarations of a universal “best” model.
|
||||||
|
|
||||||
|
Benchmark material containing family records remains private application data unless explicitly approved for publication.
|
||||||
|
|
||||||
|
## 6. Ownership and Change Policy
|
||||||
|
|
||||||
|
1. Versioned architecture, schema, scope, and implementation documents define how a release satisfies this invariant.
|
||||||
|
2. Provider adapters own the capture of provider-boundary evidence.
|
||||||
|
3. Services own validation, persistence, retention, and export behavior.
|
||||||
|
4. UI pages may inspect evidence through service contracts but do not define evidence semantics.
|
||||||
|
5. If implementation conflicts with this invariant, either correct the implementation or explicitly revise this document before accepting the behavior.
|
||||||
|
6. Revisions to this document require deliberate review because they change the long-term preservation contract.
|
||||||
|
|
||||||
|
## 7. Related Invariants
|
||||||
|
|
||||||
|
- [Historical Document Transcription Design Intent](intent.md)
|
||||||
|
- [Transcription Methodology & Style Guide](transcription_methodology.md)
|
||||||
|
- [UI Style Guide](ui_style_guide.md)
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
# Implementation Plan (Version 4.3)
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Make processing evidence precise, append-only, secret-safe, and exportable while preserving every existing record and creating a provider-neutral home for future OCR/layout artifacts.
|
||||||
|
|
||||||
|
## Implementation Principles
|
||||||
|
|
||||||
|
- Implement the [Digital Evidence and AI Processing Provenance invariant](../invariant/ai_evidence_and_provenance.md), not a provider-specific approximation of it.
|
||||||
|
- Capture transport evidence before SDK parsing.
|
||||||
|
- Keep exact evidence separate from parsed and normalized representations.
|
||||||
|
- Prefer additive schema evolution and explicit compatibility behavior.
|
||||||
|
- Reference source content by digest rather than duplicating it in request JSON.
|
||||||
|
- Use allowlists for safe metadata capture.
|
||||||
|
- Keep persistence and evidence semantics behind service boundaries.
|
||||||
|
- Do not change the default model until a representative benchmark supports that decision.
|
||||||
|
|
||||||
|
## Current-State Gaps
|
||||||
|
|
||||||
|
| Current behavior | Gap to close |
|
||||||
|
| --- | --- |
|
||||||
|
| `Source` stores original file path, digest, and size. | Media type and image/page geometry used for an execution are not frozen with that execution. |
|
||||||
|
| `Job` stores prompt text/hash, requested model after resolution, temperature, and `top_p`. | The complete effective request structure, omitted-versus-explicit parameter state, routing constraints, and software versions are not frozen. |
|
||||||
|
| `JobSource.raw_api_response` stores `model_dump()` output from the OpenRouter SDK. | The exact HTTP body can be normalized by OpenRouter and filtered again by the SDK before persistence. |
|
||||||
|
| `JobSource.ai_metadata` stores finish reason and basic token counts. | Detailed accounting remains only in the SDK snapshot and is not a substitute for exact evidence. |
|
||||||
|
| Provider exceptions become application errors. | Safe HTTP error bodies, statuses, headers, and no-response distinctions are not persisted. |
|
||||||
|
| Worker logs elapsed time. | Execution duration is not stored on `JobSource`. |
|
||||||
|
| Source Detail displays AI metadata and the SDK snapshot. | The UI does not identify evidence layers or expose request/transport/software provenance. |
|
||||||
|
| No generic processing-artifact model exists. | Future OCR geometry would require ad hoc provider fields or an unrelated schema. |
|
||||||
|
|
||||||
|
## Expected Project Impact
|
||||||
|
|
||||||
|
| Area | Expected impact |
|
||||||
|
| --- | --- |
|
||||||
|
| Database models and upgrades | Add execution-specification, transport-evidence, timing, software-context, and generic artifact storage without removing existing columns. |
|
||||||
|
| OpenRouter adapter | Introduce a transport boundary that can capture exact body/status/safe headers before typed SDK parsing, or use supported SDK hooks that expose the unparsed response reliably. |
|
||||||
|
| Provider contract | Return structured evidence for success and failure without leaking provider-specific transport concerns into workflow orchestration. |
|
||||||
|
| Source and workflow services | Persist one append-only execution outcome and its artifacts transactionally; retain compatibility projections. |
|
||||||
|
| UI | Label and inspect evidence layers; export safe evidence packages through service operations. |
|
||||||
|
| Benchmarking | Add a private manifest and repeatable evaluator using the literal-transcription methodology. |
|
||||||
|
| Tests and documentation | Add compatibility, capture, security, integrity, export, and benchmark-scoring coverage; correct overstated V4 evidence language. |
|
||||||
|
|
||||||
|
## Proposed Data Design
|
||||||
|
|
||||||
|
Exact names should be confirmed against existing conventions before migration code is written. The design should provide the following logical records.
|
||||||
|
|
||||||
|
### 1. Execution Evidence
|
||||||
|
|
||||||
|
Extend `JobSource` or associate it one-to-one with a new execution-evidence record containing:
|
||||||
|
|
||||||
|
- Request manifest JSON and manifest schema version.
|
||||||
|
- Transport status, body bytes or exact decoded body plus encoding/content type, and safe headers.
|
||||||
|
- Parsed SDK snapshot retained separately from transport content.
|
||||||
|
- Application, adapter, SDK, and runtime version metadata.
|
||||||
|
- Start, finish, and duration values.
|
||||||
|
- Router/provider request and generation identifiers when available.
|
||||||
|
- Failure phase and whether an HTTP response was received.
|
||||||
|
|
||||||
|
The implementation should evaluate a companion table rather than continuing to widen `JobSource`. A companion record better isolates large/optional evidence and permits clear one-to-one compatibility semantics.
|
||||||
|
|
||||||
|
### 2. Generic Processing Artifact
|
||||||
|
|
||||||
|
Add a one-to-many artifact model associated with a source and, when applicable, a producing execution:
|
||||||
|
|
||||||
|
- Stable artifact UUID.
|
||||||
|
- `source_id` and optional execution/`job_source_id`.
|
||||||
|
- Semantic artifact type.
|
||||||
|
- Media/serialization format.
|
||||||
|
- Schema name and version.
|
||||||
|
- Producer and producer version.
|
||||||
|
- Inline JSON payload or external location.
|
||||||
|
- Payload digest and byte size.
|
||||||
|
- Coordinate-system metadata when relevant.
|
||||||
|
- Creation timestamp.
|
||||||
|
|
||||||
|
Enforce exactly one content location: inline payload or external reference. An external artifact must be written durably and hashed before its database record commits.
|
||||||
|
|
||||||
|
### 3. Compatibility Projections
|
||||||
|
|
||||||
|
- Keep `JobSource.raw_api_response` unchanged for existing and new compatibility reads until a later deprecation decision.
|
||||||
|
- Keep `JobSource.ai_metadata` for indexed/display-ready normalized values.
|
||||||
|
- Keep `Source.raw_transcription` as the latest successful machine-output projection while treating per-execution `JobSource.raw_transcription` as history.
|
||||||
|
- Document that older rows have an SDK snapshot but no exact transport capture.
|
||||||
|
|
||||||
|
## Implementation Phases
|
||||||
|
|
||||||
|
### 1. Correct Terminology and Define Typed Contracts
|
||||||
|
|
||||||
|
- Add typed domain models for request manifests, software context, transport metadata, failure phase, and artifact descriptors.
|
||||||
|
- Version every persisted JSON contract from its first release.
|
||||||
|
- Define the safe response-header allowlist. Begin with correlation, content type/encoding, date, retry/rate-limit, and router-specific generation identifiers only when documented and non-secret.
|
||||||
|
- Define size limits and external-storage thresholds for exact bodies and artifacts.
|
||||||
|
- Correct `docs/ver4/schema_v4.md` under “Page-Level Execution and AI Outputs” so the existing column is described as an SDK-serialized OpenRouter response snapshot, not a complete provider envelope, exact HTTP body, or native upstream-provider response. Apply the same terminology to architecture and UI schema references.
|
||||||
|
- Add serialization and secret-rejection unit tests before provider changes.
|
||||||
|
|
||||||
|
### 2. Add Additive Persistence and Upgrade Behavior
|
||||||
|
|
||||||
|
- Add the selected execution-evidence and artifact models.
|
||||||
|
- Add foreign keys, uniqueness constraints, and indexes for source/execution lookup.
|
||||||
|
- Implement idempotent upgrades following the repository's existing schema-upgrade policy.
|
||||||
|
- Do not populate exact response fields for historical rows.
|
||||||
|
- Do not write a capture-time classification onto historical rows during migration. Compatibility reads may describe a populated legacy `raw_api_response` as an SDK snapshot, but exports must identify that description as a later compatibility interpretation rather than execution-time metadata.
|
||||||
|
- Verify JSON portability and large-payload behavior for SQLite and PostgreSQL.
|
||||||
|
- Add upgrade tests starting from a representative pre-V4.3 schema.
|
||||||
|
|
||||||
|
### 3. Build Secret-Safe Request Manifests
|
||||||
|
|
||||||
|
- Build the manifest from the concrete outgoing request body immediately before transport, not from a narrower typed projection that may discard unrecognized request fields.
|
||||||
|
- Replace each image payload in that concrete representation with a source reference containing source UUID, digest, byte size, media type, dimensions, and transformation identity.
|
||||||
|
- Store exact prompt content and preserve omitted-versus-explicit parameter state.
|
||||||
|
- Include requested model, routing preferences, response-format requirements, and timeout/retry policy.
|
||||||
|
- Record application version/commit when available, adapter contract version, SDK package/version, and request-manifest schema version.
|
||||||
|
- Hash the canonical manifest representation for integrity checks.
|
||||||
|
- Test that credentials and embedded image data cannot enter the persisted manifest.
|
||||||
|
- Test that every field actually sent to the provider, including routing and future provider options, is represented or explicitly excluded by the manifest transform.
|
||||||
|
|
||||||
|
### 4. Capture OpenRouter Transport Evidence
|
||||||
|
|
||||||
|
- Evaluate the installed OpenRouter SDK hooks/client injection first.
|
||||||
|
- If hooks cannot expose an exact stable response before typed parsing, implement the non-streaming OpenRouter call through the existing async HTTP client boundary while retaining typed validation in the adapter.
|
||||||
|
- Read the response body once, preserve it exactly, then parse and normalize it.
|
||||||
|
- Store status, content type/encoding, allowlisted headers, request/generation ID, and timing.
|
||||||
|
- Maintain current authentication, referer/title headers, timeout behavior, and error classification.
|
||||||
|
- Explicitly document that the captured body is the OpenRouter-normalized transport response, not Gemini/Anthropic/OpenAI native upstream JSON.
|
||||||
|
- Add fixture-based tests proving unknown response fields survive transport capture even if a typed parser ignores them.
|
||||||
|
|
||||||
|
### 5. Preserve Failure Evidence
|
||||||
|
|
||||||
|
- Return or raise a typed provider failure that carries safe evidence separately from its user-facing error.
|
||||||
|
- Persist non-success status/body/allowlisted headers before marking an execution failed.
|
||||||
|
- Represent DNS/connect/TLS/local timeout failures as no-response outcomes with a failure phase and safe diagnostic category.
|
||||||
|
- Preserve response-validation failures with both the exact body and validation details.
|
||||||
|
- Keep transcription-quality rejection distinct from provider failure because a valid provider response was received.
|
||||||
|
- Ensure error strings and logs do not contain authorization data or embedded image payloads.
|
||||||
|
- Add tests for 4xx, 5xx, malformed JSON, schema mismatch, timeout, connection failure, and quality rejection.
|
||||||
|
|
||||||
|
### 6. Make Execution History Reliably Append-Only
|
||||||
|
|
||||||
|
- Confirm retry behavior creates a distinct execution attempt rather than reusing and overwriting a completed evidence record.
|
||||||
|
- Separate queue linkage from execution-attempt identity; the current update-in-place behavior cannot serve as append-only execution history.
|
||||||
|
- Assign each attempt a deterministic, monotonically increasing attempt number scoped to its Job and Source, enforced by a database uniqueness constraint.
|
||||||
|
- Update the latest-transcription projection only after a successful attempt.
|
||||||
|
- Never update prior response bodies, manifests, timings, or artifacts during a retry.
|
||||||
|
- Select the latest attempt and latest successful attempt by the persisted attempt number with a stable identifier as a defensive secondary key, never by timestamp alone.
|
||||||
|
- Add service/workflow tests covering retries, partial success, interrupted jobs, and historical projection behavior.
|
||||||
|
|
||||||
|
### 7. Add Generic Artifact Persistence
|
||||||
|
|
||||||
|
- Implement service operations to create, read, list, verify, export, and, only under explicit retention policy, delete artifacts.
|
||||||
|
- Validate semantic type, schema/version, digest, media type, and coordinate metadata.
|
||||||
|
- Support JSON artifacts inline initially when within the agreed size threshold.
|
||||||
|
- Support external artifacts through a constrained application-data root with atomic write, digest verification, and explicit missing-file errors.
|
||||||
|
- Add a provider-neutral example fixture representing OCR words/lines with polygons and confidence values.
|
||||||
|
- Do not integrate a live OCR vendor in this phase.
|
||||||
|
|
||||||
|
### 8. Add Evidence Inspection and Export
|
||||||
|
|
||||||
|
- Rename the current Source Detail label to identify historical values as an OpenRouter SDK Response Snapshot.
|
||||||
|
- Add separate sections for Request Manifest, Transport Response, Normalized Metadata, Software Context, and Derived Artifacts.
|
||||||
|
- Show an explicit “not captured for this historical execution” state instead of an empty object.
|
||||||
|
- Keep large bodies collapsed by default and avoid rendering embedded source data.
|
||||||
|
- Add a service-owned export that packages a versioned manifest, evidence JSON/body files, artifact content or references, and digest inventory.
|
||||||
|
- Exclude secrets and machine-local paths that are not required to interpret the evidence.
|
||||||
|
- Add UI and export tests for new, historical, failed, and large-evidence records.
|
||||||
|
|
||||||
|
### 9. Establish the Private Benchmark
|
||||||
|
|
||||||
|
- Select a small initial corpus, then expand only when it exposes meaningful differences.
|
||||||
|
- Stratify examples by printed/typed text, handwriting style, degradation, layout complexity, language, and editorial anomaly.
|
||||||
|
- Reference existing Source UUIDs and digests in a private manifest; do not copy family documents into public test fixtures.
|
||||||
|
- Create manually reviewed reference transcriptions following the invariant methodology.
|
||||||
|
- Implement or adopt existing project-compatible CER/WER calculations without changing dependencies unless justified.
|
||||||
|
- Score omissions, inventions, silent modernization, uncertainty markup, and layout fidelity separately from CER/WER.
|
||||||
|
- Record cost and latency from preserved execution evidence.
|
||||||
|
- Run the current `google/gemini-2.5-flash` configuration as the baseline before testing alternatives.
|
||||||
|
- Treat results as model-version/route/corpus specific and preserve each comparison run.
|
||||||
|
|
||||||
|
### 10. Verify, Migrate, and Align Documentation
|
||||||
|
|
||||||
|
- Run the smallest focused model, provider, service, workflow, UI, upgrade, and export test groups first.
|
||||||
|
- Run broader regression tests only after focused validation passes.
|
||||||
|
- Execute all destructive tests through `tools/run_destructive_tests.py`.
|
||||||
|
- Verify backup creation and required restoration behavior before any test touching real application data.
|
||||||
|
- Confirm existing Source Detail records remain readable after upgrade.
|
||||||
|
- Update V4 architecture, schema, requirements, and UI schema mappings to point to V4.3 semantics.
|
||||||
|
- Record any deliberate deviation from this plan in the V4.3 scope before release.
|
||||||
|
|
||||||
|
## Recommended Delivery Order
|
||||||
|
|
||||||
|
1. Typed/versioned evidence contracts and terminology.
|
||||||
|
2. Additive execution-evidence persistence.
|
||||||
|
3. Secret-safe request manifests.
|
||||||
|
4. Exact OpenRouter transport capture.
|
||||||
|
5. Failure evidence and append-only retry semantics.
|
||||||
|
6. Generic artifact persistence.
|
||||||
|
7. Inspection and export.
|
||||||
|
8. Private benchmark tooling and baseline run.
|
||||||
|
9. Migration, regression verification, and documentation alignment.
|
||||||
|
|
||||||
|
## Key Implementation Decisions to Resolve
|
||||||
|
|
||||||
|
1. Whether execution evidence is a one-to-one companion to `JobSource` or part of a new execution-attempt model required for append-only retries.
|
||||||
|
2. Whether exact response bodies remain database values at expected sizes or move to hashed external files above a threshold.
|
||||||
|
3. The canonical JSON algorithm used to hash request manifests.
|
||||||
|
4. The safe-header allowlist supported by OpenRouter and future adapters.
|
||||||
|
5. The application version identity available in local, packaged, and uncommitted development builds.
|
||||||
|
6. The initial inline/external artifact size threshold and application-data root.
|
||||||
|
7. Whether evidence exports include original source binaries by default, optionally, or only by reference.
|
||||||
|
8. The minimum private benchmark corpus size and review process before model comparisons influence defaults.
|
||||||
|
|
||||||
|
These decisions must be settled before their corresponding implementation phase; they do not weaken the invariant or expand V4.3 into live OCR integration.
|
||||||
|
|
||||||
|
## Done When
|
||||||
|
|
||||||
|
- Every V4.3 acceptance criterion is satisfied by focused tests or an explicit demonstration.
|
||||||
|
- Existing SDK snapshots retain their content and are labeled accurately.
|
||||||
|
- New successful and failed calls preserve secret-safe provider-boundary evidence.
|
||||||
|
- Unknown transport fields survive even when the typed SDK/parser does not recognize them.
|
||||||
|
- Retries cannot overwrite prior execution evidence.
|
||||||
|
- A generic versioned artifact can represent OCR geometry and pass integrity verification.
|
||||||
|
- Evidence can be safely inspected and exported with schema identities and digests.
|
||||||
|
- The current model has a reproducible private benchmark baseline.
|
||||||
|
- No credential or embedded source payload appears in persisted manifests, safe headers, logs, or exports.
|
||||||
|
- Existing V4.1 and V4.2 behavior remains compatible.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Live OCR/document-AI provider integration.
|
||||||
|
- Automatic model switching.
|
||||||
|
- Archive-wide reprocessing.
|
||||||
|
- Native upstream-provider response capture through OpenRouter when OpenRouter does not expose it.
|
||||||
|
- Guarantees of deterministic hosted-model output.
|
||||||
|
|
||||||
|
## Related Local References
|
||||||
|
|
||||||
|
- [V4.3 Scope Boundary](scope_boundary_v4_3.md)
|
||||||
|
- [Digital Evidence and AI Processing Provenance](../invariant/ai_evidence_and_provenance.md)
|
||||||
|
- [V4 Architecture](../ver4/architecture_v4.md)
|
||||||
|
- [V4 Schema](../ver4/schema_v4.md)
|
||||||
|
- [V4 Requirements](../ver4/requirements_v4.md)
|
||||||
|
- [V4.2 Implementation Plan](../ver4.2/implementation_plan_v4_2.md)
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
# V4.3 Scope Boundary
|
||||||
|
|
||||||
|
This document defines the proposed boundary for the digital-evidence and AI-provenance revision that follows V4.2. V4 remains the architecture baseline; V4.3 makes the existing evidence claims precise and adds a provider-neutral foundation for future processing artifacts.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
- Align the application with the [Digital Evidence and AI Processing Provenance invariant](../invariant/ai_evidence_and_provenance.md).
|
||||||
|
- Preserve provider-boundary evidence before SDK parsing can remove unknown fields.
|
||||||
|
- Make successful and failed processing attempts inspectable without storing secrets.
|
||||||
|
- Support future OCR and layout outputs without coupling the database to one vendor.
|
||||||
|
- Establish a repeatable method for comparing transcription models against this archive.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
### 1. Evidence Terminology and Existing-Data Compatibility
|
||||||
|
|
||||||
|
- Define transport response, router-normalized response, SDK response, normalized metadata, and derived artifact consistently in code, schema documentation, and UI labels.
|
||||||
|
- Treat existing `JobSource.raw_api_response` values as historical SDK response snapshots.
|
||||||
|
- Preserve every existing `Job`, `Source`, and `JobSource` row.
|
||||||
|
- Use additive migrations and compatibility reads; do not reinterpret previously stored values as exact transport captures.
|
||||||
|
- Correct the “Page-Level Execution and AI Outputs” rule in `docs/ver4/schema_v4.md` that currently describes `JOB_SOURCE` as storing a complete provider response envelope. The corrected rule must identify `raw_api_response` as an SDK-serialized OpenRouter response snapshot and state that it is neither the exact HTTP body nor the native upstream-provider response.
|
||||||
|
|
||||||
|
### 2. Secret-Safe Request Manifests
|
||||||
|
|
||||||
|
- Persist the effective request specification for each page execution without storing credentials or duplicate base64 media.
|
||||||
|
- Include requested provider/model, routing constraints, prompt content and hash, explicitly supplied parameters, source digest, media type, dimensions when known, and page identity.
|
||||||
|
- Distinguish an omitted optional parameter from an explicitly supplied null or value.
|
||||||
|
- Record application, provider-adapter, Python client, and relevant schema versions.
|
||||||
|
- Use source or derivative references in place of embedded media bytes.
|
||||||
|
|
||||||
|
### 3. Provider-Boundary Response Capture
|
||||||
|
|
||||||
|
- Capture the exact HTTP response body before OpenRouter SDK parsing for non-streaming transcription calls.
|
||||||
|
- Store HTTP status and an explicit allowlist of safe response headers.
|
||||||
|
- Store router request/generation identifiers and resolved model/provider-routing metadata when exposed.
|
||||||
|
- Preserve the current parsed SDK snapshot and normalized metadata where useful.
|
||||||
|
- Keep exact body, parsed representation, and normalized fields distinguishable.
|
||||||
|
|
||||||
|
### 4. Failure Evidence and Timing
|
||||||
|
|
||||||
|
- Create or update a page execution record for every attempted provider call.
|
||||||
|
- Persist safe response evidence for non-success HTTP responses.
|
||||||
|
- Distinguish HTTP response failures, connection failures, local timeouts, response-validation failures, and transcription-quality failures.
|
||||||
|
- Store execution start/end times or duration using a clearly defined clock policy.
|
||||||
|
- Do not collapse a provider error body into only a generic user-facing message.
|
||||||
|
|
||||||
|
### 5. Generic Processing Artifacts
|
||||||
|
|
||||||
|
- Add a provider-neutral representation for versioned derived artifacts.
|
||||||
|
- Support inline JSON and externally stored payloads with a digest and stable reference.
|
||||||
|
- Record artifact type, format, schema/version, producer/version, source, producing execution, and creation time.
|
||||||
|
- Define coordinate-system metadata sufficient for word, line, block, or page geometry.
|
||||||
|
- Permit future OCR/layout/confidence results without implementing a vendor-specific table for each provider.
|
||||||
|
|
||||||
|
### 6. Evidence Inspection and Export
|
||||||
|
|
||||||
|
- Expand Source Detail and/or Job Detail to identify the evidence layer being displayed.
|
||||||
|
- Provide readable JSON inspection for request manifests, transport metadata, parsed responses, normalized metadata, and derived artifacts.
|
||||||
|
- Provide a safe export containing evidence content or references, relationships, schema versions, and digests.
|
||||||
|
- Clearly label evidence that was not captured for historical records.
|
||||||
|
- Do not display or export credentials, unrestricted headers, or embedded base64 source media.
|
||||||
|
|
||||||
|
### 7. Representative-Corpus Benchmark Protocol
|
||||||
|
|
||||||
|
- Define a private benchmark manifest referencing source digests rather than duplicating archival media.
|
||||||
|
- Include representative printed, typed, handwritten, degraded, tabular, and spatially complex pages.
|
||||||
|
- Pair each benchmark item with a manually reviewed literal transcription.
|
||||||
|
- Score character error rate, word error rate, omissions, inventions, silent normalization, uncertainty handling, layout fidelity, cost, and latency.
|
||||||
|
- Preserve the complete execution provenance for every benchmark run.
|
||||||
|
- Keep the current model as a baseline; do not change the application default solely from vendor benchmarks.
|
||||||
|
|
||||||
|
### 8. Migration, Integrity, and Verification
|
||||||
|
|
||||||
|
- Provide non-destructive upgrade behavior for supported SQLite and PostgreSQL deployments.
|
||||||
|
- Backfill only facts that can be derived reliably from existing records.
|
||||||
|
- Mark unavailable historical evidence as unavailable rather than fabricating it.
|
||||||
|
- Add digest, serialization, header-allowlist, failure-path, compatibility, artifact, export, and UI inspection tests.
|
||||||
|
- Run destructive tests only through the repository's required backup-and-restore wrapper.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Selecting or declaring a permanent best transcription model.
|
||||||
|
- Changing the default transcription model without benchmark evidence and a separate decision.
|
||||||
|
- Integrating Azure Document Intelligence, Google Document AI, Transkribus, Mistral OCR, or another OCR provider in V4.3.
|
||||||
|
- Generating bounding boxes retroactively for existing transcriptions.
|
||||||
|
- Bulk reprocessing the archive.
|
||||||
|
- Packet capture, TLS evidence, full unrestricted request/response headers, or credential retention.
|
||||||
|
- Storing duplicate base64 source images in request manifests.
|
||||||
|
- Guaranteeing byte-identical reproduction from nondeterministic or updated hosted models.
|
||||||
|
- Automatic entity extraction, biography generation, or genealogical inference.
|
||||||
|
- Replacing the relational database with an event store or content-addressed object store.
|
||||||
|
- Destructive renaming or removal of `raw_api_response`.
|
||||||
|
|
||||||
|
## Locked Design Decisions
|
||||||
|
|
||||||
|
### A. The Original Source Is Primary Evidence
|
||||||
|
|
||||||
|
- Original uploaded bytes and their digest remain authoritative.
|
||||||
|
- Processing derivatives and outputs are independently identified derived evidence.
|
||||||
|
- Future OCR/layout work reuses the original or a documented derivative.
|
||||||
|
|
||||||
|
### B. Evidence Is Layered
|
||||||
|
|
||||||
|
- Exact transport evidence, SDK-parsed objects, normalized metadata, and transcription text serve different purposes.
|
||||||
|
- One representation must not silently stand in for another.
|
||||||
|
- UI and export labels name the stored evidence layer.
|
||||||
|
|
||||||
|
### C. History Is Append-Only
|
||||||
|
|
||||||
|
- A retry or reprocessing attempt creates new execution evidence.
|
||||||
|
- Convenience caches may change, but historical execution output does not.
|
||||||
|
- Human revisions remain separate from machine output.
|
||||||
|
|
||||||
|
### D. Capture Is Secret-Safe by Construction
|
||||||
|
|
||||||
|
- Safe headers are allowlisted.
|
||||||
|
- Authorization, cookies, API keys, and unrestricted headers are never persisted.
|
||||||
|
- Request manifests reference source digests instead of embedding source bytes.
|
||||||
|
|
||||||
|
### E. Derived Artifacts Are Generic and Versioned
|
||||||
|
|
||||||
|
- Artifact storage is not limited to bounding boxes.
|
||||||
|
- Coordinate metadata declares units, origin, dimensions, and transformations.
|
||||||
|
- Provider-specific payloads may be retained without making provider-specific fields the durable application contract.
|
||||||
|
|
||||||
|
### F. Existing Evidence Keeps Its Original Meaning
|
||||||
|
|
||||||
|
- Existing `raw_api_response` data remains an SDK response snapshot.
|
||||||
|
- A migration may label or classify it but may not claim that missing transport data was captured.
|
||||||
|
- Historical nulls and absent fields remain distinguishable from new explicitly captured values.
|
||||||
|
|
||||||
|
## Data and Compatibility Policy
|
||||||
|
|
||||||
|
- All schema changes are additive in V4.3.
|
||||||
|
- Existing source files, hashes, transcriptions, revisions, prompts, jobs, and relationships remain valid.
|
||||||
|
- Compatibility reads continue to display historical SDK snapshots.
|
||||||
|
- Large derived artifacts may be stored outside the database when the database retains a stable reference, digest, media type, and schema identity.
|
||||||
|
- JSON evidence must remain portable across SQLite and PostgreSQL.
|
||||||
|
- Exports use explicit schema versions so later releases can interpret older packages.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. A new execution can be traced from its source digest through its frozen request manifest, transport response, parsed/normalized data, and derived outputs.
|
||||||
|
2. Exact response content is captured before SDK parsing and is clearly distinguished from the existing SDK snapshot.
|
||||||
|
3. Failed HTTP calls retain safe provider evidence; calls with no response record that fact explicitly.
|
||||||
|
4. Omitted parameters remain distinguishable from explicit values.
|
||||||
|
5. No persisted request, header set, UI display, log, or export contains API credentials.
|
||||||
|
6. Retrying or reprocessing does not overwrite prior execution evidence.
|
||||||
|
7. Historical records remain readable and are not mislabeled as exact transport captures.
|
||||||
|
8. A versioned generic artifact can represent OCR/layout JSON and its coordinate system without a provider-specific schema change.
|
||||||
|
9. Evidence exports include relationships, schema identities, and digests sufficient for independent integrity checks.
|
||||||
|
10. The benchmark protocol can compare the current baseline with another model on the same private corpus and scoring rules.
|
||||||
|
11. Additive migrations and focused tests work across the supported persistence model.
|
||||||
|
12. All destructive-test runs comply with the backup-and-restore protocol.
|
||||||
|
|
||||||
|
## Related Local References
|
||||||
|
|
||||||
|
- [V4.3 Implementation Plan](implementation_plan_v4_3.md)
|
||||||
|
- [Digital Evidence and AI Processing Provenance](../invariant/ai_evidence_and_provenance.md)
|
||||||
|
- [V4 Architecture](../ver4/architecture_v4.md)
|
||||||
|
- [V4 Schema](../ver4/schema_v4.md)
|
||||||
|
- [V4 Requirements](../ver4/requirements_v4.md)
|
||||||
|
- [Transcription Methodology](../invariant/transcription_methodology.md)
|
||||||
@@ -56,6 +56,7 @@ def render_documents_table(rows: Sequence[DocumentTableRow]) -> None:
|
|||||||
"field": "name",
|
"field": "name",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "font-serif font-semibold text-left ui-table-cell-wrap",
|
"classes": "font-serif font-semibold text-left ui-table-cell-wrap",
|
||||||
|
"align": "left",
|
||||||
"style": "width: 30%;",
|
"style": "width: 30%;",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -63,7 +64,8 @@ def render_documents_table(rows: Sequence[DocumentTableRow]) -> None:
|
|||||||
"label": "Type",
|
"label": "Type",
|
||||||
"field": "document_type",
|
"field": "document_type",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "text-left ui-table-cell-wrap",
|
"classes": "ui-table-cell-wrap",
|
||||||
|
"align": "center",
|
||||||
"style": "width: 14%;",
|
"style": "width: 14%;",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -71,7 +73,8 @@ def render_documents_table(rows: Sequence[DocumentTableRow]) -> None:
|
|||||||
"label": "Author",
|
"label": "Author",
|
||||||
"field": "authors",
|
"field": "authors",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "text-left ui-table-cell-wrap",
|
"classes": "ui-table-cell-wrap",
|
||||||
|
"align": "center",
|
||||||
"style": "width: 22%;",
|
"style": "width: 22%;",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -80,6 +83,7 @@ def render_documents_table(rows: Sequence[DocumentTableRow]) -> None:
|
|||||||
"field": "document_date",
|
"field": "document_date",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "font-mono text-xs",
|
"classes": "font-mono text-xs",
|
||||||
|
"align": "center",
|
||||||
"style": "width: 14%;",
|
"style": "width: 14%;",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -88,6 +92,7 @@ def render_documents_table(rows: Sequence[DocumentTableRow]) -> None:
|
|||||||
"field": "archive_identifier",
|
"field": "archive_identifier",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "font-mono text-xs",
|
"classes": "font-mono text-xs",
|
||||||
|
"align": "center",
|
||||||
"style": "width: 20%;",
|
"style": "width: 20%;",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -62,14 +62,16 @@ def render_people_table(rows: Sequence[PersonTableRow]) -> None:
|
|||||||
"label": "Display Name",
|
"label": "Display Name",
|
||||||
"field": "display_name",
|
"field": "display_name",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "text-left ui-table-cell-wrap",
|
"classes": "ui-table-cell-wrap",
|
||||||
|
"align": "center",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "maiden_name",
|
"name": "maiden_name",
|
||||||
"label": "Maiden Name",
|
"label": "Maiden Name",
|
||||||
"field": "maiden_name",
|
"field": "maiden_name",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "text-left ui-table-cell-wrap",
|
"classes": "ui-table-cell-wrap",
|
||||||
|
"align": "center",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "birth_date",
|
"name": "birth_date",
|
||||||
@@ -77,6 +79,7 @@ def render_people_table(rows: Sequence[PersonTableRow]) -> None:
|
|||||||
"field": "birth_date",
|
"field": "birth_date",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "font-mono text-xs",
|
"classes": "font-mono text-xs",
|
||||||
|
"align": "center",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "death_date",
|
"name": "death_date",
|
||||||
@@ -84,6 +87,7 @@ def render_people_table(rows: Sequence[PersonTableRow]) -> None:
|
|||||||
"field": "death_date",
|
"field": "death_date",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "font-mono text-xs",
|
"classes": "font-mono text-xs",
|
||||||
|
"align": "center",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
default_sort_by="full_name",
|
default_sort_by="full_name",
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ def render_sources_table(rows: Sequence[SourceTableRow]) -> None:
|
|||||||
"field": "document_name",
|
"field": "document_name",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "font-serif text-left ui-table-cell-wrap",
|
"classes": "font-serif text-left ui-table-cell-wrap",
|
||||||
|
"align": "left",
|
||||||
"style": "width: 27%;",
|
"style": "width: 27%;",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -73,6 +74,7 @@ def render_sources_table(rows: Sequence[SourceTableRow]) -> None:
|
|||||||
"field": "upload_name",
|
"field": "upload_name",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "font-serif text-left ui-table-cell-wrap",
|
"classes": "font-serif text-left ui-table-cell-wrap",
|
||||||
|
"align": "left",
|
||||||
"style": "width: 25%;",
|
"style": "width: 25%;",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -81,6 +83,7 @@ def render_sources_table(rows: Sequence[SourceTableRow]) -> None:
|
|||||||
"field": "job_source_status",
|
"field": "job_source_status",
|
||||||
"sortable": True,
|
"sortable": True,
|
||||||
"classes": "font-mono",
|
"classes": "font-mono",
|
||||||
|
"align": "center",
|
||||||
"style": "width: 14%;",
|
"style": "width: 14%;",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -89,6 +92,7 @@ def render_sources_table(rows: Sequence[SourceTableRow]) -> None:
|
|||||||
"field": "job_source_error_detail",
|
"field": "job_source_error_detail",
|
||||||
"sortable": False,
|
"sortable": False,
|
||||||
"classes": "font-mono text-xs text-left ui-text-muted ui-table-cell-wrap",
|
"classes": "font-mono text-xs text-left ui-text-muted ui-table-cell-wrap",
|
||||||
|
"align": "left",
|
||||||
"style": "width: 24%;",
|
"style": "width: 24%;",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -597,8 +597,14 @@ def _render_related_people_card(document: Document) -> None:
|
|||||||
with ui.column().classes("w-full ui-row-surface p-2 gap-1"):
|
with ui.column().classes("w-full ui-row-surface p-2 gap-1"):
|
||||||
archival_badge(role_code)
|
archival_badge(role_code)
|
||||||
for person in grouped[role_code]:
|
for person in grouped[role_code]:
|
||||||
ui.link(person.full_name, f"/ui/people/{person.id}").classes(
|
ui.button(
|
||||||
"text-xs font-semibold ui-link-primary"
|
person.full_name,
|
||||||
|
on_click=lambda _=None, person_id=person.id: ui.navigate.to(
|
||||||
|
f"/people/{person_id}"
|
||||||
|
),
|
||||||
|
icon="person",
|
||||||
|
).props("flat dense no-caps").classes(
|
||||||
|
"self-start text-xs font-semibold ui-link-primary"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ def _render_person_form_fields(
|
|||||||
with ui.row().classes("w-full gap-3 grid grid-cols-1 md:grid-cols-3"):
|
with ui.row().classes("w-full gap-3 grid grid-cols-1 md:grid-cols-3"):
|
||||||
birth_date_input = (
|
birth_date_input = (
|
||||||
ui.input(
|
ui.input(
|
||||||
label="Birth date (YYYY-MM-DD)",
|
label="Birth date",
|
||||||
value=person.birth_date.isoformat() if person and person.birth_date else "",
|
value=person.birth_date.isoformat() if person and person.birth_date else "",
|
||||||
)
|
)
|
||||||
.props('outlined type="date"')
|
.props('outlined type="date"')
|
||||||
@@ -363,7 +363,7 @@ def _render_person_form_fields(
|
|||||||
with ui.row().classes("w-full gap-3 grid grid-cols-1 md:grid-cols-3"):
|
with ui.row().classes("w-full gap-3 grid grid-cols-1 md:grid-cols-3"):
|
||||||
death_date_input = (
|
death_date_input = (
|
||||||
ui.input(
|
ui.input(
|
||||||
label="Death date (YYYY-MM-DD)",
|
label="Death date",
|
||||||
value=person.death_date.isoformat() if person and person.death_date else "",
|
value=person.death_date.isoformat() if person and person.death_date else "",
|
||||||
)
|
)
|
||||||
.props('outlined type="date"')
|
.props('outlined type="date"')
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@@ -327,6 +328,24 @@ def _render_source_job_metadata_zone(latest_job_source: JobSource | None) -> Non
|
|||||||
ui.label("Failure Detail:").classes("ui-text-muted text-xs mb-1")
|
ui.label("Failure Detail:").classes("ui-text-muted text-xs mb-1")
|
||||||
ui.label(latest_job_source.error_detail).classes("p-2 ui-note-box text-xs")
|
ui.label(latest_job_source.error_detail).classes("p-2 ui-note-box text-xs")
|
||||||
|
|
||||||
|
_render_provider_evidence(latest_job_source)
|
||||||
|
|
||||||
|
|
||||||
|
def _render_provider_evidence(job_source: JobSource) -> None:
|
||||||
|
ui.label("Provider Evidence").classes("text-xs font-semibold ui-text-primary mt-3")
|
||||||
|
_render_json_evidence("AI Metadata", job_source.ai_metadata)
|
||||||
|
_render_json_evidence("Raw API Response", job_source.raw_api_response)
|
||||||
|
|
||||||
|
|
||||||
|
def _render_json_evidence(label: str, value: object | None) -> None:
|
||||||
|
with ui.expansion(label, icon="data_object").classes("w-full ui-row-surface"):
|
||||||
|
if value is None:
|
||||||
|
render_empty_state(f"No {label.lower()} stored for this execution.", italic=True)
|
||||||
|
return
|
||||||
|
ui.code(json.dumps(value, indent=2, sort_keys=True, ensure_ascii=False), language="json").classes(
|
||||||
|
"w-full text-xs"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _render_source_revision_logistics_zone(source: Source) -> None:
|
def _render_source_revision_logistics_zone(source: Source) -> None:
|
||||||
with archival_card(title="Revision Logistics"):
|
with archival_card(title="Revision Logistics"):
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ async def seed_job(app_client: tuple[FastAPI, TestClient]) -> Callable[..., Awai
|
|||||||
error_detail: str | None = None,
|
error_detail: str | None = None,
|
||||||
revision_text: str | None = None,
|
revision_text: str | None = None,
|
||||||
source_file: Path | None = None,
|
source_file: Path | None = None,
|
||||||
|
ai_metadata: dict | None = None,
|
||||||
|
raw_api_response: dict | None = None,
|
||||||
) -> UUID:
|
) -> UUID:
|
||||||
async with session_scope() as session:
|
async with session_scope() as session:
|
||||||
stored_path = app.state.settings.upload_dir / filename
|
stored_path = app.state.settings.upload_dir / filename
|
||||||
@@ -130,6 +132,8 @@ async def seed_job(app_client: tuple[FastAPI, TestClient]) -> Callable[..., Awai
|
|||||||
),
|
),
|
||||||
raw_transcription=transcription_text,
|
raw_transcription=transcription_text,
|
||||||
error_detail=error_detail,
|
error_detail=error_detail,
|
||||||
|
ai_metadata=ai_metadata,
|
||||||
|
raw_api_response=raw_api_response,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ class TestDocumentsPageRendering:
|
|||||||
assert "Letter from Hig" in response.text
|
assert "Letter from Hig" in response.text
|
||||||
assert "ZC-1924-001" in response.text
|
assert "ZC-1924-001" in response.text
|
||||||
assert "Zenna Cochran" in response.text
|
assert "Zenna Cochran" in response.text
|
||||||
assert f"/ui/people/{seed_person_and_document[1]}" in response.text
|
|
||||||
assert "PIPELINE JOBS" in response.text.upper()
|
assert "PIPELINE JOBS" in response.text.upper()
|
||||||
assert "Edit Document" in response.text
|
assert "Edit Document" in response.text
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,10 @@ class TestPeoplePageRendering:
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert "Create Person Record" in response.text
|
assert "Create Person Record" in response.text
|
||||||
assert "Full name is required." in response.text
|
assert "Full name is required." in response.text
|
||||||
assert "Birth date (YYYY-MM-DD)" in response.text
|
assert "Birth date" in response.text
|
||||||
assert "Death date (YYYY-MM-DD)" in response.text
|
assert "Death date" in response.text
|
||||||
|
assert "Birth date (YYYY-MM-DD)" not in response.text
|
||||||
|
assert "Death date (YYYY-MM-DD)" not in response.text
|
||||||
assert "FamilySearch ID" in response.text
|
assert "FamilySearch ID" in response.text
|
||||||
assert "Biography" in response.text
|
assert "Biography" in response.text
|
||||||
assert "Save person" in response.text
|
assert "Save person" in response.text
|
||||||
|
|||||||
@@ -192,6 +192,8 @@ class TestSourcesPageRendering:
|
|||||||
transcription_text="original transcription text",
|
transcription_text="original transcription text",
|
||||||
revision_text="human revision text",
|
revision_text="human revision text",
|
||||||
source_file=fixture_path,
|
source_file=fixture_path,
|
||||||
|
ai_metadata={"finish_reason": "stop", "confidence": 0.98},
|
||||||
|
raw_api_response={"id": "response-123", "model": "test-model"},
|
||||||
)
|
)
|
||||||
|
|
||||||
async with session_scope() as session:
|
async with session_scope() as session:
|
||||||
@@ -216,6 +218,10 @@ class TestSourcesPageRendering:
|
|||||||
assert "Save revision" in response.text
|
assert "Save revision" in response.text
|
||||||
assert "Previous Page" in response.text
|
assert "Previous Page" in response.text
|
||||||
assert "Next Page" in response.text
|
assert "Next Page" in response.text
|
||||||
|
assert "AI Metadata" in response.text
|
||||||
|
assert "Raw API Response" in response.text
|
||||||
|
assert "finish_reason" in response.text
|
||||||
|
assert "response-123" in response.text
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_source_delete_page_blocks_when_source_is_job_linked(
|
async def test_source_delete_page_blocks_when_source_is_job_linked(
|
||||||
|
|||||||
Reference in New Issue
Block a user