Skip to main content

Document Intelligence Lab: From PDFs to Traceable AI Answers

An architecture deep dive into Java, Spring Boot, Next.js, local AI, Azure, hybrid RAG, and DuckDB/dbt.

Updated 30 min readMarkdown
Document Intelligence Lab: From PDFs to Traceable AI Answers

Turning a PDF into text is useful. Turning it into an answer that someone can inspect, challenge, and trace back to the original page is a much more interesting engineering problem.

That is why I built Document Intelligence Lab: an MIT-licensed research workspace for document extraction, hybrid retrieval, source-based AI answers, human review, and local analytical data pipelines.

The lab brings together Java 21, Spring Boot, LangChain4j, Next.js, Docling, Ollama, Qwen3, Azure AI, PostgreSQL, Qdrant, Keycloak, Redis, DuckDB, and dbt. Each component has a specific responsibility. This article explains those responsibilities, how the pieces communicate, and where the current implementation still has limits.

The central design principle: keep the original document, extracted observations, generated answers, and human approval as separate things. A fluent answer is not automatically an approved fact.

Source snapshot: this walkthrough describes the public repository at commit cf909b5. This article was reviewed and amended after a source-code audit on 23 October 2026. Snapshot-specific limitations and unreleased local changes are identified below. Features described as optional require their own setup and verification. The primary runtime is local Docker; the repository does not currently provide a public hosted application.

1. What does the project actually do?

The workspace studies three document domains: payroll and banking references, insurance documents, and general business documents such as invoices and contracts.

A typical session follows this sequence:

  1. Sign in and select an authorized dossier, the application's container for related documents and evidence.
  2. Upload a permitted PDF and explicitly start analysis.
  3. Follow the durable processing job through scanning and extraction.
  4. Open extracted fields and tables beside the original PDF.
  5. Search the dossier's evidence or ask Nova, the assistant, a source-based question.
  6. Inspect citations, report mistakes, and request human review.
  7. Optionally create reference labels, compare research methods, or build private analytical layers.

This is research software. It does not approve loans, settle insurance claims, transfer money, or automatically send the emails it drafts.

The interface organizes the work into seven destinations: Overview, Documents & review, Data Pipelines, AI Studio, Research lab, Review & trust, and Operations. English, German, and Vietnamese interfaces share the same source evidence.

The English Document Intelligence Lab overview with the configured Docling, MiniLM, Qdrant, and Ollama engines.

An existing local workspace capture from the public repository, dated 21 October 2026. The displayed identity is a development fixture. Configuration labels and saved counts are not a live model-health or accuracy test.

2. The architecture at a glance

Architecture of Document Intelligence Lab: authenticated browser, Java application boundary, extraction, retrieval, generation, persistence, analytical layers, and human review.

A conceptual map of responsibilities. It is not a literal network topology: application services mediate access, and local and Azure providers are explicitly selected alternatives.

The browser talks to an authenticated Next.js API proxy. The proxy forwards authorized requests to Spring Boot, where dossier access, document processing, retrieval, model calls, and review rules are enforced.

PostgreSQL keeps application state. Qdrant supports vector retrieval. Redis adds optional short-window limits. Keycloak supplies user identity. Document parsers, the local language model, and the analytical worker run in separate containers.

Process boundaries and job handoffs

The business backend is a modular Spring Boot application: case management, extraction coordination, retrieval, research, and review are modules within one Java service. Parsers, the local model runtime, and the analytical worker have separate process/container boundaries. The diagram groups responsibilities rather than presenting every box as an independently deployed business microservice.

Document jobs are backed by PostgreSQL records and leases. The ETL worker receives a private, version-bound file handoff and writes a result receipt that Java reconciles against the current source version. This path does not require a public worker REST endpoint or unrestricted access to the application database.

The application also includes deterministic intent routing through AgentSupervisor. It selects a bounded specialist and evidence/tool policy. Local Nova receives supplied context; the Azure path can use a bounded read-only tool loop. Specialist labels do not mean that multiple independent autonomous agents are collaborating on each answer.

The system separates two different AI jobs:

  • Document extraction: reading text, layout, fields, and tables from the original.
  • Language generation: explaining selected evidence or producing a draft answer.

Changing the language model does not automatically improve OCR. Improving OCR does not automatically make an answer correct.

The technology map

Technology Responsibility in this project
Java 21 and Spring Boot Application APIs, authorization, workers, domain rules, and provider integration
LangChain4j Java integration with embedding and language-model components
Next.js, React, and TypeScript The browser workspace and authenticated server-side proxy
Tailwind CSS and Lucide Interface styling and icons
next-intl Localized application routes and interface text
Zod and AI SDK Request/response contracts and the chat UI message transport
PDF.js Viewing protected originals inside the workspace
Docling, PDFBox, and Tesseract Configurable local PDF extraction and OCR
Azure Document Intelligence Explicit cloud layout and document extraction
MiniLM and ONNX Local semantic text embeddings
Qdrant and BM25-style scoring Semantic and keyword evidence retrieval
Ollama and Qwen3 Local Nova language generation
Azure OpenAI Separately configured cloud generation
Keycloak, OIDC, and PKCE User sign-in and identity tokens
PostgreSQL, JDBC, and Flyway Persistent records, database access, and schema migrations
Redis and ClamAV Shared usage limits and malware scanning
Python, DuckDB, dbt, and Parquet Local analytical transformations and portable data artifacts
Docker Compose Reproducible service composition, networks, and persistent volumes
Micrometer and OpenTelemetry Operational metrics and optional tracing
GitHub Actions, Vitest, and Java/Python tests Automated verification and release checks

The next sections explain the main paths in more detail.

3. Reading PDFs: Docling, PDFBox, Tesseract, and Azure

Docling: structure matters as much as text

A PDF may visually contain a clear table while storing its characters in an inconvenient order. Reading those characters alone can lose the relationship between a label, an amount, and its row or column.

The optional Docling CPU profile extracts text, page structure, and tables locally through Docling Serve. A Java adapter converts the provider response into the application's shared document representation.

The integration preserves page provenance and uses the existing queue, PDF viewer, and review process. The parser receives document bytes through its bounded interface; it does not need application credentials or arbitrary source URLs. The supplied local profile gives it no public host port or runtime network egress.

Unknown confidence remains unknown. A missing confidence value is not replaced with an impressive-looking score.

PDFBox and Tesseract: a local baseline

Apache PDFBox provides PDF handling and the baseline local extraction path. Tesseract supplies OCR for image-based content in that path.

These tools provide a useful baseline because digital text extraction and scanned-page recognition have different failure modes. Keeping the baseline helps make provider comparisons concrete instead of assuming that a more complex model must always be better.

Azure Document Intelligence: an explicitly selected cloud provider

The Azure path uses Azure Document Intelligence with prebuilt-layout. It requires the operator's own resource configuration and authorized Azure access.

The application maps the result into the same internal evidence model used by local extraction. Reviewers can therefore inspect comparable concepts—pages, fields, tables, and provenance—without treating provider-specific output as approval.

Cloud analysis can incur charges and sends the selected document to Azure. It is an explicit processing choice. A local provider failure does not silently switch the document to Azure.

The extraction coordinator also supports an explicitly configured azure-with-local-fallback mode: selected Azure provider failures can use the local PDFBox/Tesseract path, while invalid-document responses are propagated. The supplied Azure walkthrough selects plain azure, rather than this fallback mode. This optional extraction behavior is separate from Nova chat, whose selected model does not fall back to another provider.

From extracted text to business fields

OCR and layout models recover text and document structure. Java then maps recognized labels, page layouts, and table relationships into a bounded domain schema. DocumentExtractor, PayrollLayoutFields, and InvoiceTableFields implement explicit application rules. These rules are inspectable, but they are not a fine-tuned payroll, insurance, or invoice model.

For example, recognizing the text beside Netto-Verdienst and deciding which canonical field should contain it are separate steps. Better character recognition cannot by itself correct a wrong field mapping. Conflicting candidates require inspection rather than selecting the most convenient value.

Extraction is an observation stage. Even a well-structured result can attach the wrong amount to the right-looking field. The original remains the reference for review.

4. Finding the right evidence: MiniLM, ONNX, Qdrant, and hybrid retrieval

Retrieval-augmented generation, or RAG, supplies selected evidence to a language model before it answers. Its usefulness depends heavily on whether the selected evidence is relevant and properly scoped.

MiniLM embeddings explain semantic similarity

The local retrieval profile uses a quantized all-MiniLM-L6-v2 embedding model through ONNX, producing 384-dimensional vectors.

An embedding represents a piece of text numerically so that a question and a passage with related meaning can be compared. This is useful when the question's wording differs from the document's wording.

This model is English-focused. A multilingual interface does not automatically imply equally strong multilingual retrieval. The project includes a separate BGE-M3 adapter for experiments, discussed below.

Qdrant stores vectors with access scope

Qdrant stores vector representations with metadata that includes the tenant, dossier, and chunk identifier. Searches include tenant and dossier filters.

These filters complement server-side authorization. The application must first establish that the user can access the dossier; a vector database filter is not a replacement for that decision.

Live retrieval can synchronize the selected dossier's index on demand. OCR completion and retrieval indexing remain separate stages. If Qdrant is unavailable, the implementation can report a local-vector retrieval fallback; this is different from switching to a cloud language model.

Keyword retrieval still matters

Document questions often depend on exact labels, identifiers, or unusual terminology. The application combines semantic similarity with a BM25-style lexical score and query-term coverage.

At this source snapshot, RagEngine uses the following hybrid heuristic:

hybrid_score =
    0.48 × max(0, cosine_similarity)
  + 0.32 × (lexical_score / (lexical_score + 2))
  + 0.20 × query_term_coverage

The lexical implementation uses token-set membership and document-frequency/length normalization. It is a compact application implementation, not a claim to reproduce every detail of a full-text search engine's BM25.

The weights are engineering choices. They are not a learned ranker or calibrated probabilities. A score of 0.8 does not mean an answer has an 80% chance of being correct.

Chunking preserves the route back to the original

The repository compares four page-local chunking strategies:

Strategy What changes
Fixed Windows of 780 characters with 160-character overlap
Recursive / boundary-aware Groups text around paragraph and sentence boundaries, splitting oversized units
Semantic Uses embedding similarity to introduce additional boundaries
Hierarchical-child Creates child chunks with parent labels

These sizes are characters, not tokens. Chunking does not merge unrelated PDFs or page numbers. Hierarchical-child labels are implemented; parent-context expansion is not.

The application also represents extracted fields and tables as retrievable evidence. Reference experiments use an isolated corpus rather than overwriting the live dossier index.

Code to inspect: RagEngine.java and DocumentChunking.java.

5. Nova: LangChain4j, Ollama, Qwen3, and Azure OpenAI

Nova is the assistant inside the workspace. The interface exposes two modes:

  • App guidance explains the interface through local application logic without a language-model call.
  • Evidence analysis is the route for source-based questions to the configured model with authorized dossier context.

Routing limitation in cf909b5: the public frontend can still intercept some Evidence analysis questions through its guidance heuristic. For example, “What is the invoice number stated in this PDF?” matches a guidance expression and a PDF alias. A selected mode alone therefore does not prove that this snapshot invoked the model. The separate local working tree changes routing to respect the selected mode explicitly; that correction is not included in the public commit described here.

Each model request is a fresh question against the selected dossier. The visible chat transcript is not supplied as conversational model memory. Follow-up questions should restate their subject. Retaining answer history for review and sending prior turns back to a model are separate design choices.

LangChain4j is the Java integration layer

LangChain4j connects the Java application to model providers and embedding components. Authorization, evidence selection, quota checks, and review rules remain application responsibilities.

The local Nova path receives supplied authorized context and does not run autonomous tool loops. The separately configured Azure agent can use scoped, read-only tools. Those are different execution paths, not interchangeable capabilities.

Ollama serves the local language model

The supplied local walkthrough uses Ollama with the explicit model identifier:

qwen3:4b-instruct-2507-q4_K_M

This is a Qwen3 4B Instruct checkpoint with quantized weights. Quantization reduces weight precision to make local execution more practical, with quality and resource trade-offs that still need measurement. The generic qwen3:4b alias is not treated as an equivalent replacement in the project.

The CPU runtime image and the model weights are separate artifacts. The model is prepared explicitly and retained in a private Docker volume. Normal startup does not quietly download a missing model, and a local inference failure does not trigger Azure generation.

Local CPU inference can take minutes. The published walkthrough records two fresh request timeouts during capture and identifies the saved historical answer shown instead. An edited walkthrough is not a latency benchmark.

Azure OpenAI is a separate configured route

The Azure route connects the Java application to the operator's own Azure OpenAI deployment. It requires explicit Azure configuration and can incur inference charges.

The lab uses Microsoft Entra authorization for Azure access. The local Azure CLI token bridge supplies short-lived developer credentials; it is not Managed Identity. A hosted deployment would need an independently designed and scoped identity configuration.

There is a provenance-label limitation in this snapshot: choosing the default credential configuration records the mode as azure-managed-identity, although DefaultAzureCredential is a credential chain and does not establish which identity actually succeeded. Treat that value as a configuration-derived label, not verified proof of Managed Identity.

Exactly one Nova chat provider is selected by configuration. Historical answers retain their original provider provenance instead of being relabeled after a configuration change.

Structured output and citations constrain the answer

Selected local review tasks use explicit JSON Schema, strict Java decoding, and checks against allowed source IDs and original quotations. Chronology, clause review, and sensitive-data categories are bounded tasks with specific evidence limits.

An invalid or incomplete draft can be rejected. The application should not fabricate a successful replacement to make the interface appear healthy.

However, three statements remain different:

  1. The JSON has the expected shape.
  2. The cited source exists.
  3. The claim is factually supported by that source.

Passing the first two does not establish the third. Numeric checks can flag absent currency/amount pairs, but they do not fully establish the correct person, period, or financial meaning.

The stored source preview is also shorter than some model contexts: AnswerEvidence retains up to 1,000 characters per excerpt, and the downstream money check uses that excerpt. A supported amount later in a longer authorized context can therefore receive a conservative warning. Such a warning can reflect excerpt truncation rather than a fabricated amount; the original and the inspected scope still need review.

6. The web workspace: Next.js, React, TypeScript, and PDF.js

The frontend uses Next.js 16 and React 19, with TypeScript for application contracts. The server-side API layer forwards authenticated requests to Java, applies body bounds and origin checks, and translates backend failures into controlled UI responses.

Zod validates structures such as conversations and trust metadata. TypeScript helps during development; runtime validation is still needed for data arriving across a network boundary.

Tailwind CSS provides the styling system, while Lucide supplies icons. next-intl supports localized routes and interface text. Source quotations, labels, currencies, and amounts are preserved rather than silently rewritten to match the UI language.

PDF.js renders private original documents. Viewer assets are served locally, and document requests use authenticated endpoints. A source link can open the correct page without pretending that an exact text region was found when it was not.

The chat UI uses AI SDK message-stream primitives to deliver answer text, source references, and trust metadata. In this implementation, the proxy first waits for the Java response and then emits UI message events. It should not be described as token-by-token model streaming.

The frontend dependency list also includes Sharp for image processing, SWC helpers for the JavaScript toolchain, and PostCSS for CSS processing. These support the web application; they are not additional document AI engines.

7. Identity and isolation: Keycloak, OIDC, PKCE, and Spring Security

Keycloak handles application sign-in using OpenID Connect, built on OAuth 2.0, with an authorization-code flow and PKCE. PKCE binds code redemption to the initiating client flow.

The Java API uses Spring Security as an OAuth2 resource server. It validates JWT issuer and API audience, then applies roles and tenant/dossier authorization.

The roles have different responsibilities. Advisors can work with dossiers in their authorized tenant; reviewers and administrators control designated review transitions. Access combines verified tenant membership, application roles, and tenant-bound dossier lookups. The lab does not implement a separate owner/member sharing list for each dossier. Guessing an ID does not grant cross-tenant access.

There are two separate identity systems:

Identity boundary What it authorizes
Keycloak A user's access to the lab and its dossier-scoped operations
Microsoft Entra The Java application's authorized calls to selected Azure resources

Confusing these boundaries can produce both access failures and excessive permissions. An app role is not an Azure resource role.

In the supplied platform profile, ClamAV scans uploaded files before extraction and Redis adds short-window limits. An enabled Redis limiter fails closed if the service becomes unavailable. Bare-service defaults are different: without a configured ClamAV host the scanner reports structural-only checks, and without enabled Redis the application uses its database quotas. Configuration must be inspected rather than assuming every launch has the same controls.

PostgreSQL atomically reserves account and tenant budgets, including per-minute and daily buckets. The reservation size counts Unicode characters from the question and selected context plus an allowance; it is not a provider-token meter or a monetary spending cap. Redis supplements these reservations rather than owning every budget. Request/page/file bounds, source checks, and suspicious-passage handling add further controls.

These controls address specific risks. Their presence is not a claim of complete protection against prompt injection or a production security certification.

8. Durable processing: PostgreSQL, JDBC, Flyway, and workers

A document-processing request should not depend on a browser tab staying open. The lab persists processing state and lets the UI follow the job.

PostgreSQL stores dossiers, jobs, evidence, references, and review history. A separate PostgreSQL database holds Keycloak identity state. JDBC provides Java database access, and Flyway applies versioned schema migrations.

Workers use durable records and recovery behavior, with attempts and failure history available for inspection. Document stage/state events are recorded transactionally with the relevant job update. Saved extraction checkpoints can avoid unnecessary repeat OCR after a later-stage failure.

This is not an exactly-once billing guarantee: if a provider completes work but its response is lost before checkpointing, a subsequent attempt can still repeat inference.

Automatic retry is bounded to selected transient failures. Manual retry is deliberately restricted to approved, byte-hashed DATEV/Kaggle reference inputs and preserves the failed job. Arbitrary uploads use the normal consent and submission flow again.

The UI distinguishes processed, indexed, answered, and human-confirmed. A job marked ready for review is not equivalent to an approved document.

Retention also has multiple scopes. Expiring an original PDF does not automatically erase chat history, reference labels, analytical exports, or browser downloads. Each copy has to be understood as a separate data lifecycle.

9. Local analytics: Python, DuckDB, dbt, Parquet, and SCD Type 2

Document extraction becomes more useful when its observations can be studied as data. The lab includes an offline Python worker with DuckDB, dbt Core, and dbt-duckdb.

Bronze, Silver, and Gold layers with source-version receipts, preserved raw values, human review, and SCD Type 2 history.

Why DuckDB and dbt?

DuckDB is an embedded analytical database: it can execute analytical SQL locally without adding another continuously exposed database service. dbt organizes SQL transformations and data tests, while the dbt-duckdb adapter executes them on DuckDB. No dbt Cloud account is required for this path.

The worker has no public port, network access, database password, or Azure credentials in the supplied profile. It processes an explicitly authorized, version-bound private handoff rather than receiving unrestricted application access.

Bronze, Silver, and Gold have different promises

Layer Responsibility
Bronze Retain immutable extraction snapshots, provenance, and available original hashes; references do not copy PDF bytes
Silver Preserve raw values, normalize fields, and record findings such as ambiguous dates or conflicting candidates
Gold Sum eligible reviewed extracted observations, separately by field and currency; no transaction-level deduplication guarantee

For example, 03/07/2023 is ambiguous without adequate locale/context. Preserving the literal and flagging ambiguity is preferable to silently choosing a date. Likewise, an amount in INR must not be merged into an EUR aggregate merely because both values are numeric.

Normalization is deliberately narrow: the worker handles five monetary field keys and recognizes EUR, INR, USD, and GBP. In public cf909b5, 2.967,11 EUR is accepted but the otherwise ordinary ungrouped form 2967,11 EUR is rejected as ambiguous. The local v2 correction is not part of this source snapshot.

Gold counts observations, not unique invoices

The duplicate check is scoped to (page, field label) within each document. If a document repeats the same Invoice total: 100 EUR on two different pages, both reviewed observations can pass that check and contribute 200 EUR to Gold. The current aggregate is not a reconciled invoice ledger. Reliable invoice-level totals need an explicit document/business grain and a policy for repeated or conflicting candidates; silently dropping equal amounts would also discard some legitimate observations.

The three current dbt tests check that selected Gold columns are non-null. They do not establish unique business events, correct sums, valid SCD2 intervals, or semantic source accuracy. Empty Gold can pass these structural checks and is expected when there are no eligible reviewed amounts.

Parquet provides column-oriented analytical artifacts that can be inspected with other data tools. Downloads remain private and subject to authentication and explicit consent.

Slowly Changing Dimension Type 2, or SCD Type 2, retains observed snapshot history when a document's extracted state changes. The observation timestamp is not automatically the document's business-effective date.

Source-version receipts let unchanged completed work be reused and stale results be rejected. In the documented synthetic-invoice walkthrough, the saved run contained one Bronze snapshot, two Silver fields, and zero Gold entries because the source was not reviewer-confirmed. Zero Gold was the intended boundary, not a failed transformation.

10. Evaluation: measure one claim at a time

The research workspace supports independently checked reference versions. A person inspects the original and records the expected field or target document/page before running a comparison.

For retrieval, two useful metrics are:

  • Hit rate@5: the fraction of labelled questions whose target document/page appears in the first five results.
  • MRR@5: the average reciprocal rank of the first matching target in that range; a target at rank 1 contributes 1, rank 2 contributes 0.5, and a miss contributes 0.

This setup labels one target page per question. It should not be presented as a complete multi-relevant-document Recall@5 benchmark.

For extraction, saved predictions are compared with labelled fields. Parsing, schema validity, quotation support, and reference match are separate measurements. The zero-shot/few-shot experiment makes real local calls while keeping expected reference values out of the model prompts.

The published example is deliberately informative: zero-shot returned a supported invoice field, while few-shot abstained. Valid JSON did not turn the abstention into a correct field prediction. The failed sample remains visible.

The repository records scoped acceptance checks, but this article does not report a fresh benchmark or claim accuracy across arbitrary PDFs. The documented local timeouts are also part of the evidence.

11. What are the 16 AI-assisted workflows?

The capability directory lists workflows, not independently deployed models. Multiple tasks share the same extraction or chat provider.

Workflow Practical purpose
Text, layout, and OCR Read the source document
Table extraction Inspect row and cell structure
Hybrid evidence search Find relevant passages with lexical and semantic signals
Semantic embeddings Compare meaning-based retrieval with other methods
Nova document Q&A Answer a question with source references
Selected-field explanation Explain a field in its original context
Evidence summary Draft a source-based overview
Cross-document comparison Compare separate documents without merging identities or currencies
Discrepancy explanation Surface differences for inspection
Document-request draft Prepare an editable request; no email is sent
Review checklist draft Suggest checks for a human reviewer
German terminology explanation Explain labels while preserving the source
German–Vietnamese summary Draft bilingual explanations without claiming certified translation
Document chronology Identify supported dated events within the inspected evidence
Clause and obligation review Draft observations about selected clauses; no coverage or legal decision
Sensitive-data review draft Identify categories for manual inspection; no automatic PDF redaction

The additional rule-based, navigation, research, and review controls are not extra AI models. An exercise being available in the interface does not mean that every task has passed a broad accuracy benchmark.

12. Optional integrations: what is available and what remains experimental?

The repository contains explicit boundaries for further experiments. These should be understood as adapters and operator workflows, not a list of services already running successfully.

Integration Why it exists Current boundary
PaddleOCR / PP-StructureV3 Alternative local OCR and document structure experiments Requires its CPU runtime, reviewed cached models, and live extraction checks
BGE-M3 Multilingual semantic retrieval experiments Separate 1,024-dimensional collection; weights and benchmarking required
BGE reranker Reorder a bounded hybrid candidate set with a learned model Separate weights; compare relevance gains against latency and memory
Local vision Inspect one selected original page with a vision-capable model Separately prepared model and explicit consent; output remains an observation
Azure Blob Storage Controlled intake from an existing private container Authorized prefix, explicit pull/consent, ETag-conditioned download, then the scanned queue
Azure Data Factory Submit a scoped request to an existing pipeline Operator execution and remote connectivity required; submission is not downstream completion
Azure AI Evaluation SDK / Foundry-related evaluation Evaluate exported saved answers and excerpts with an authorized judge Explicit cloud execution, independent references, and compatibility checks required
Application Insights Optional Azure trace delivery Separate configuration and actual privacy/delivery verification required

BGE-M3's different dimensionality is why it uses a different vector collection. Reusing an incompatible collection would corrupt the retrieval experiment rather than upgrade it.

The Data Factory helper does not make a developer's localhost reachable from Azure. The evaluation bridge does not automatically create a Foundry project. Optional page vision does not replace verified OCR. These distinctions keep the architecture understandable as it grows.

13. Operations and delivery: Docker, telemetry, and CI

Docker Compose makes the research environment reproducible

Docker Compose assembles the web app, Java API, identity provider, databases, scanner, retrieval services, and selected AI profiles. Named volumes retain databases, private documents, vectors, analytical artifacts, and separately prepared model weights.

Development ports bind to 127.0.0.1. The supplied parsers and local model workers have constrained internal boundaries. Adding a public cloud deployment is a separate design and verification task.

Where data lives and what a backup covers

Store Contents and recovery boundary
Application PostgreSQL Dossiers, jobs, answers, audit/review/research records, and original source bytes attached to dossiers in credit_files
Identity PostgreSQL Keycloak identity state, separate from the application database
Private document store/volume Intake and processing source bytes; after extraction Java also attaches source bytes to the application database
Qdrant volume Vectors and retrieval metadata; preserve or rebuild from authorized retained evidence
Private ETL volume DuckDB history, handoffs, receipts, and Parquet, with a separate retention lifecycle
Ollama model volume Explicitly prepared weights, separate from application state and documents

Backing up PostgreSQL includes its stored source copies, but does not preserve every queued object, vector, analytical artifact, or model volume. Recovery planning needs all relevant stores, not only a successful database dump.

Stopping containers is different from removing persistent data. Do not use down -v or volume pruning as a routine fix for a lab whose records you want to retain.

The repository also contains optional infrastructure source using Bicep and Terraform, plus historical deployment support such as Caddy and a small Go identity-proxy component. Infrastructure source is not evidence of an active deployment. The current README identifies local Docker as the primary runtime and says the previous Azure VM backend and Vercel project were removed.

Micrometer and OpenTelemetry observe behavior

Spring Boot Actuator exposes operational health information. Micrometer supports metrics, including an administrator-protected Prometheus endpoint. OpenTelemetry provides tracing integration and an optional collector.

The optional Application Insights configuration is traces-only and strips or limits attributes. Actual delivery still needs verification. Prompts, tokens, PDF text, and private identifiers do not belong in operational telemetry.

Health and latency are useful engineering signals. They do not measure hallucination rate, document accuracy, customer savings, or billed model costs.

GitHub Actions checks several layers

The repository's workflows include:

  • ESLint, TypeScript checks, Vitest, and a Next.js build.
  • Java verification through Maven and Spring's testing stack.
  • Python tests, including the document ETL path.
  • Docker/OAuth integration checks with real local services.
  • Gitleaks for secret scanning and Trivy for dependency/image checks.
  • CycloneDX SBOM generation to inventory Java dependencies.
  • A manually triggered image-publishing workflow with Cosign signing and GitHub OIDC.

A software bill of materials is an inventory, not a security audit. A signing workflow's presence does not prove that a particular image has been published or signed. Tests provide evidence about the cases they exercise, not universal certification.

14. Run the local research path

The documented starting point requires Git, Node.js 22+, Docker with Compose v2, and enough available CPU, RAM, and disk. The guide suggests four CPUs and 8 GB of Docker RAM; local inference and initial model preparation can still be resource-intensive.

git clone https://github.com/quocvietphung/document-intelligence-lab.git
cd document-intelligence-lab

# Reproduce the public source snapshot described in this article.
git checkout cf909b5f235589bd9e94317fb4a15e2238549ea2
mkdir -p .local/reference-pdfs

# Explicit first-time model preparation and startup.
node scripts/docker-local.mjs --prepare-model

The chosen local model download is approximately 2.5 GB, separate from images and build caches. A fresh clone does not contain private PDFs, model weights, or saved research results.

Subsequent starts reuse the prepared model and persistent data:

node scripts/docker-local.mjs

Input and inspected-evidence limits

The current lab bounds document uploads to 3 MB and local PDFs to four pages; the supplied Azure profile sets a two-page PDF limit. Provider-specific validation and page-text bounds apply as well.

The bounded local Ollama chronology, clause, and privacy tasks select the first three eligible original-page excerpts across the dossier, each limited to 2,000 characters, and return up to three findings. A statement that no clause was found applies to those inspected excerpts rather than clearing the whole document or dossier. Other retrieval and provider paths have their own context bounds.

Open http://localhost:3000/en and follow the README's local Keycloak sign-in instructions. Use a fictional or explicitly permitted document, wait for its extraction result, and ask a question such as:

What invoice number, currency, and gross total are stated? Cite the source page. If the evidence is insufficient, say so.

Inspect the actual provider identity, open each source, and compare the original. Do not treat a running container, a greeting, or a model badge as a successful evidence-answer test.

For source verification and contribution checks, the repository documents:

npm run verify
mvn -f services/credit-api/pom.xml test

# Requires the local Docker environment.
node scripts/test-oauth.mjs

Azure and optional-model experiments have separate instructions. They should not be enabled simply to complete the basic local walkthrough.

What has not yet been released in this snapshot?

As of this article's source audit, a separate local working tree contains explicit Nova mode routing, a one-inference-at-a-time Java wrapper, distinct busy/timeout responses, and ETL v2 amount parsing with method-aware receipt reuse. These changes are not included in public commit cf909b5 and need their own release and verification. They should not be inferred from the setup instructions above, and historical failures should retain their original version and date.

15. What I want readers to take away

The interesting part of document AI is how the system handles a difficult question: what evidence would let another person check this answer?

In this lab, that question shapes the architecture. Page provenance influences chunking. Authorization scopes retrieval. Provider identities remain visible. Unsupported drafts can fail. Human review controls which values enter analytical Gold. Source versions determine whether results can be reused.

There is still work to do: broader evaluation datasets, multilingual retrieval comparisons, local inference reliability, measured optional-model trade-offs, and independently reviewed deployment and recovery procedures. The current project is a place to study these problems openly, with both successful and failed observations visible.

If you work on OCR, Java AI systems, RAG evaluation, or document data engineering, I would welcome a reproducible issue, a carefully scoped improvement, or feedback on an experiment.

Explore the source: quocvietphung/document-intelligence-lab. If it is useful to your work, a GitHub star helps other developers discover it.

Source guide and further reading

Audit scope: the amendments above are based on source comparison and small synthetic checks of routing, amount normalization, and aggregation SQL. They do not report a new end-to-end, live-model, or performance benchmark.

The MIT license covers the project's own code. Dependencies, container images, model weights, and publisher documents retain their own terms. The diagrams in this article describe the architecture; they are not performance or accuracy results.

No comments yet. Be the first to comment.