Read documents and import their data with explicit controls
Retain concrete evidence, review proposals and separate destination authority from assertions inside documents.

Found text is not an approved record
Reading documents automatically can reduce repetitive entry, but controlled import needs more than recognised text. It requires field meaning, traceable sources, business rules and a specifically authorised decision. An approval printed inside a document is not automatically permission in the destination application. A plausible candidate must also preserve missing or contradictory evidence instead of hiding it behind a complete-looking result.
Our original example is a fictional project brief naming a project identifier, document date, project name, release assertion, planned page scope and language. Three actual PDFs are created and read: a clear version, one with two release statements and an image-only version without text. The Python code runs against those files and additional field cases. No actual customer records are imported.
The scope is deliberately narrow. A deterministic parser handles one known small brief format, followed by a separate import-preparation gate. An arbitrary invoice, multi-column report or unfamiliar scanned form needs different handling. Stating that boundary makes the example useful: it shows where extraction ends and controlled business interpretation begins rather than claiming one script understands every PDF that might arrive.
Choose handling from the document’s actual structure
A digitally produced PDF can provide a text layer, while an image-only file may visibly contain the same information without offering extractable text. The actual image fixture produces empty extraction. The code invents no values and routes the case toward another recognition or review path. No OCR operation is performed in this example. That absence is treated as a known condition rather than concealed by guessed fields.
A text layer is not a complete semantic model either. Columns, tables, headers and ordering can make associations difficult. The pypdf documentation explains these extraction limits. Our brief intentionally uses a clear known structure. A real collection begins with inspection of representative variants before assigning an extraction strategy to a document class. Successful reading of one clean fixture is not evidence of universal layout understanding.
Keep the file as a specific version. A later submission must not silently replace the source behind a proposal that someone has already reviewed. The example uses a SHA-256 digest of source bytes to identify that version. The digest does not prove the content true, but it supports an explicit relationship between the file, its review and the prepared data package.
Define field meaning before extracting values
The example project identifier follows DEMO plus three digits. The document date is a specific ISO date. Page scope means planned project pages rather than the number of PDF pages. That distinction matters: the brief has one physical PDF page and proposes eight project pages. A perfectly recognised number is still wrong if assigned to the wrong destination field. Interpretation needs an agreement, not just optical confidence.
Release status is retained as a document assertion. The phrase “nicht freigegeben” maps explicitly to false, not true merely because it contains the positive word. An open status remains a separate unresolved value. This is an agreed enumeration exercised by real tests. Searching for a favourable substring would be an inappropriate method for determining the meaning of the statement.
For every field, specify type, allowed values and treatment of absence. Dates need calendar validity, planned pages need a bounded integer and language needs an agreed code. These are original example assumptions rather than universal requirements for project briefs. Another destination must establish its actual meanings before automatically filling fields. Similar labels can refer to different concepts in different workflows.
Retain evidence rather than unsupported values
Every extracted field keeps the actual page, text-line number and source wording. A reviewer can then inspect where it came from. The number eight alone is less useful than its associated page-scope line on page one. Evidence belongs with the proposal, not only in a later general log that the review interface cannot conveniently reach. Make the relationship practical for the decision being asked of the person.
The line number refers to extracted text, not a claimed exact image coordinate. This article additionally shows actual rendered PDF excerpts so the source text and visible document can be considered together. No pixel-accurate OCR highlighting is asserted. The provenance model is small and appropriate to what this parser actually produces, which is more useful than fabricated precision about a layout it has not semantically analysed.
Multiple occurrences remain available. Even identical repeated project identifiers block automatic treatment as one unique field in this conservative fixture. Another template may legitimately repeat them and would need its own explicit rule. The starting policy prevents the parser from arbitrarily choosing the first or last occurrence without business justification. Evidence preservation makes later clarification possible without rereading a vanished or overwritten source.
Treat contradiction as a workflow state
The second brief contains both a negative release assertion and an additional positive one. Both lines are actually found. The field receives a duplicate blocker and no selected authoritative value. The proposal remains reviewable rather than pretending that the source was unambiguous. Keeping that state is central to controlled import: uncertainty in evidence should remain visible in the result.
A blocker should give the reviewer a specific question. Here it asks which conflicting assertion needs clarification. A generic processing-failed message would be less useful. Provide both evidence locations and a meaningful next step. Whether the service needs a corrected document or a question to an owner depends on the real workflow rather than on a convenient default chosen by the parser.
An unresolved record does not become reliable because a language model summarises one version persuasively. Contradictory source evidence should remain contradictory in the proposal. A model may assist interpretation but cannot replace missing authority with confident wording. The actual decision stays governed outside extraction. This prevents an impressive summary from quietly becoming permission to make a downstream change.
Keep document instructions separate from operating rules
The contradictory fixture also asks the processor to skip checks and set approval to yes. That sentence is source material. It changes neither parser rules nor permissions. An executed case confirms that such a note does not turn the known negative field into a positive assertion. Fixed field mapping and a separate preparation decision provide the bounded functional relationship demonstrated here.
This boundary is especially important with AI-assisted processing. External documents can contain text phrased as instructions. OWASP describes indirect prompt-injection situations of this kind. Labelling text untrusted is not sufficient enforcement by itself: executing functions need limited authority and independent checks of their arguments, regardless of how convincing a model’s proposal appears. The workflow must maintain that separation at the point of action.
This deterministic parser is not a general detector of every malicious instruction. It illustrates one narrow boundary: only agreed field lines supply values, while permission to prepare an import comes from a separate trusted review process. Other text is not executed as rules. Preserve that distinction if a later model contributes additional candidates rather than assuming the model can decide its own authority from the document it reads.
OWASP: LLM Prompt Injection Prevention
Follow the executed Python helper
The code below was run with Python 3.12.14 and pypdf 6.10.0. It reads the known brief format, validates fields and creates a proposal containing provenance and blockers. The PDFs are original fictional fixtures. This is not a general parser for arbitrary layouts. Its accepted labels and values are intentionally small enough to inspect and test as a complete data contract.
File and page limits support this bounded exercise but do not provide full resource isolation for hostile PDFs. A production intake needs appropriate execution, time and memory controls and a maintained parser. The code performs no OCR and writes nothing into a production destination. After a matching simulated review decision, it only prepares a data package. That narrow output is important when interpreting the successful tests.
from pathlib import Path
from datetime import date
from hashlib import sha256
import json
import re
from pypdf import PdfReader
FIELDS={
'Projekt-ID':'project_id', 'Dokumentdatum':'document_date',
'Projektname':'project_name', 'Freigabestatus':'document_release_assertion',
'Seitenumfang':'planned_pages', 'Sprache':'language'
}
def values_hash(values):
raw=json.dumps(values,ensure_ascii=False,sort_keys=True,separators=(',',':'))
return sha256(raw.encode('utf-8')).hexdigest()
def propose(pages, source_hash):
found={key:[] for key in FIELDS.values()}
for page_number,text in enumerate(pages,1):
for line_number,line in enumerate(text.splitlines(),1):
raw=line.strip()
for label,key in FIELDS.items():
if raw.startswith(label+':'):
found[key].append({'page':page_number,'line':line_number,
'raw':raw,'value':raw[len(label)+1:].strip()})
values={}; blockers=[]
if not any(text.strip() for text in pages): blockers.append('no_text_layer')
for key,items in found.items():
if len(items)!=1:
blockers.append(('missing:' if not items else 'duplicate:')+key)
continue
value=items[0]['value']
try:
if key=='project_id':
if not re.fullmatch(r'DEMO-[0-9]{3}',value): raise ValueError()
elif key=='document_date':
if not re.fullmatch(r'[0-9]{4}-[0-9]{2}-[0-9]{2}',value): raise ValueError()
value=date.fromisoformat(value).isoformat()
elif key=='project_name':
if not 1<=len(value)<=120 or any(ord(c)<32 for c in value): raise ValueError()
elif key=='document_release_assertion':
allowed={'freigegeben':True,'nicht freigegeben':False,'offen':None}
if value not in allowed: raise ValueError()
value=allowed[value]
elif key=='planned_pages':
if not re.fullmatch(r'[0-9]{1,3}',value): raise ValueError()
value=int(value)
if not 1<=value<=500: raise ValueError()
elif key=='language':
if value not in {'de','en'}: raise ValueError()
values[key]=value
except ValueError:
blockers.append('invalid:'+key)
return {'source_sha256':source_hash,'values':values,'sources':found,
'blockers':sorted(blockers),'proposal_sha256':values_hash(values),
'review_required':True}
def extract_proposal(path):
path=Path(path)
if path.stat().st_size>1_000_000: raise ValueError('File limit exceeded')
source_hash=sha256(path.read_bytes()).hexdigest()
reader=PdfReader(path)
if reader.is_encrypted: raise ValueError('Encrypted document requires separate handling')
if len(reader.pages)>5: raise ValueError('Page limit exceeded')
pages=[page.extract_text() or '' for page in reader.pages]
return propose(pages,source_hash),pages
def prepare_import(candidate,current_source_hash,decision):
# decision comes from a trusted review workflow, never from document text.
if candidate['blockers']: raise ValueError('Blocked proposal')
if decision.get('approved') is not True: raise ValueError('Review required')
if decision.get('role')!='data_reviewer': raise ValueError('Review permission required')
reviewer=decision.get('reviewer_id')
if not isinstance(reviewer,str) or not re.fullmatch(r'reviewer-[a-z0-9-]+',reviewer):
raise ValueError('Reviewer reference required')
if not current_source_hash==candidate['source_sha256']==decision.get('source_sha256'):
raise ValueError('Source changed')
actual=values_hash(candidate['values'])
if not actual==candidate['proposal_sha256']==decision.get('proposal_sha256'):
raise ValueError('Proposal changed')
return {'data':dict(candidate['values']),'source_sha256':current_source_hash,
'proposal_sha256':actual,'reviewer_id':reviewer}
Check syntax separately from business meaning
A string matching four digits, hyphen, two digits, hyphen and two digits may still represent an impossible calendar date. The example requires that specific format and then validates the date. 29 February 2024 is accepted while the corresponding 2025 date is not. Both were exercised. A regular expression alone would not completely establish calendar validity or the intended meaning of a date field.
Planned pages use a bounded positive integer. Zero, negative numbers, fractions and values beyond the example limit are rejected. The limit needs a business reason in a real project. The value eight does not imply a general expectation for real relaunches; it makes the chosen type and counterexamples reproducible. Avoid treating a successful numeric parse as proof that a value belongs in any vaguely similar field.
Even a technically valid record can have the wrong business association. A project identifier must not be linked indiscriminately to a real customer with a similar name. The destination establishes resource identity, responsibility and permitted changes. This parser uses a fictional identifier and performs no real customer matching. Controlled association is another responsibility beyond correctly recognising the characters on a page.
Python: datetime · OWASP: Input Validation
Bind review to the particular source and proposal
A general document checkbox is insufficient for the example preparation gate. The decision identifies the source hash, a digest of ordered proposal values and the reviewing person’s reference. Changed source or edited values no longer match the old decision and are rejected. An earlier approval therefore cannot silently cover a later payload merely because it appears in the same interface or has a familiar document name.
The data_reviewer role comes from trusted review state, never from the PDF or an arbitrary browser field. The helper is not complete authentication. A real application establishes identity and permitted work before supplying that context. The isolated function then tests whether the specific decision contract matches. Keeping these responsibilities separate prevents a role-shaped string inside a document from becoming an access grant.
A successfully prepared package still contains data and provenance only. The negative document assertion remains false rather than turning into application authority. Actual destination storage needs its own controlled route, transaction behaviour and repetition policy. A file digest does not replace that work. Nor does a successful preparation case mean customer information has actually been written anywhere.
Add AI where it addresses a specific gap
A model can help suggest fields or a document class when descriptions vary substantially. The six explicit labels in this fixture do not require a model call. Avoiding an unnecessary dependency keeps the evidence simple. Choose the technique from the content: a fixed labelled line and a semantically unclear free-text passage are different extraction tasks, even when both ultimately produce a structured proposal.
An AI candidate needs the same evidence and permitted target values. A broad confidence score cannot substitute for a clear project identifier or resolved contradiction. Missing values should be allowed to remain visibly absent. An always-complete JSON object may otherwise look attractive while passing unsupported assertions to the next stage. Structural completeness is useful only when it corresponds to evidence.
Execution remains bounded outside the model. A proposal does not grant itself privileges or start arbitrary follow-up work. This example demonstrates a separate gate but runs no model request or general prompt-injection benchmark. A future AI extension receives representative cases of its own instead of inheriting unperformed assurances from a deterministic template exercise. The implementation’s actual capabilities and the product’s claims should stay aligned.
Interpret the 52 executed checks
All three actual PDFs are read and their results retained. The clear document supplies six validated values while still requiring review. The contradictory version keeps both release locations without silently choosing a winner. The image version has no text layer and produces no invented fields. Each PDF was also rendered and visually inspected, keeping extraction evidence separate from assumptions about appearance.
Additional cases cover calendar boundaries, identifiers, integers, language codes, absent and repeated fields and exact handling of negative, positive and open release wording. Evidence moved to page two retains that actual page number. These cases explain the rules more usefully than a general claim of high recognition accuracy. No accuracy percentage was measured for an uncontrolled document collection.
The gate rejects absent approval, wrong role, missing reviewer reference, changed source and modified proposal values. A matching simulated decision prepares a fictional package. There are 52 named cases, not an actual human approval or production import. Evidence concerns the displayed limited contract, allowing a real implementation to add its own document types, identity integration and destination writes without confusing those tasks with what was already tested.

Design the review view around the decision
A useful view places source and proposal together, with evidence easy to reach and contradictions clearly named. The illustration also shows why one physical PDF page must not be confused with eight planned project pages. Small semantic distinctions like this can matter more than an elaborate import animation. The reviewer needs enough context to judge meaning rather than merely a visually complete set of fields.
Allow correction or deferral. A correction creates a new proposal version for a matching review, while retaining the original evidence. An interface offering only accept-everything and reject-everything may hide necessary distinctions. Exact control scope follows the person’s responsibility and real document collection. It should not be dictated solely by whichever two actions happened to be easiest to implement.
After the decision, status must remain truthful. Prepared package differs from stored in the destination. A technical failure in the next step must not be concealed by the same generic success message. Completion, retries and errors need separate checks for that route. Parsing is one part of the procedure and should not claim outcomes that only another stage can establish.
Accept with representative documents and counterexamples
Begin with representative document classes in an appropriately protected test environment. Specify expected values and evidence for each. Add missing pages, changed order, duplicate labels and image-only variants. A parser familiar only with its own clean specimen has no evidence for a diverse real intake. Expand coverage from actual patterns and decisions rather than assuming one template generalises automatically.
The original briefs are precisely that bounded starting point. Another format requires new examples and an explicit rule choice. An unfamiliar field should not simply be pushed into the closest-sounding target. An unmapped state is often more honest and easier to correct than a plausible wrong association. Treat uncertainty as useful information that the next responsible step can resolve.
Document who maintains the rules and which changes trigger review. A new template, parser release or additional destination field may invalidate earlier assumptions. Source digests and named cases help isolate that change but do not replace responsibility for meaning. A well-maintained process keeps both technical reproducibility and business ownership visible instead of turning extraction into an unexplained automated side effect.
Build a controlled end-to-end route
The route contains intake, recognition, candidate fields, review and bounded import. Each stage has a defined claim and output. Document content stays source, the parser supplies candidates, an authorised review addresses a specific package and the destination controls its actual changes. This division makes failures and ownership understandable. It also allows a component to improve without silently expanding its authority.
Automation is useful when recurring correct associations save work and uncertain cases reach focused clarification. The aim is not the greatest possible number of unchecked populated fields. The actual PDF example shows why stopping at negative or contradictory evidence can be valuable. An open case with evidence is easier to handle than a wrong entry that passed quietly because the interface looked confident.
These 52 executed cases supply a reproducible small core. A real service extends it with its own document classes, permissions and destination operations. Text extraction and AI suggestions can then become useful tools within a controlled workflow, while actual changes remain at the point where the application can check, perform and record them. That is the practical relationship the example is designed to demonstrate.
Example field decisions
| Source | Target meaning | Check |
|---|---|---|
| Project ID DEMO-017 | Fictional project reference | Exact example format |
| Document date 2026-03-01 | Document calendar date | Format and calendar validity |
| Negative release wording | Document assertion false | Exact permitted values |
| Page scope 8 | Planned project pages | Bounded positive integer |
| Two status lines | No unique assertion | Block and retain evidence |
| Image-only PDF | No extracted text fields | Require another recognition route |
Controlled import checklist
- Identify document class, exact version and allowed fields.
- Distinguish text layers, images and layout limits.
- Define meaning, type and missing-value policy.
- Retain source page and actual evidence text.
- Handle duplicates and contradictions without silently choosing.
- Separate document instructions from operating rules and authority.
- Bind review to source and proposal versions.
- Verify destination action, repetition and actual completion separately.
Sources and verification scope
Primary sources reviewed on 9 November 2026. Three original fictional PDFs were extracted using pypdf 6.10.0 on Python 3.12.14 and visually reviewed. Fifty-two extraction, rule and gate cases passed. No OCR run, actual reviewer decision or production customer import occurred.








