Skip to content

[ADD] ai_document_extraction - #96

Open
volkantasci wants to merge 60 commits into
OCA:19.0from
volkantasci:19.0-ai-document-extraction
Open

[ADD] ai_document_extraction#96
volkantasci wants to merge 60 commits into
OCA:19.0from
volkantasci:19.0-ai-document-extraction

Conversation

@volkantasci

Copy link
Copy Markdown

Add the ai_document_extraction module: local OCR (PaddleOCR) + LLM (Ollama) invoice data extraction into draft account.move records.

Features

  • OpenCV image pre-processing (grayscale, CLAHE, denoise, Otsu, resize)
  • PaddleOCR with layout tags ([HEADER]/[BODY]/[FOOTER] from bounding boxes)
  • OpenAI-compatible LLM extraction (qwen3:4b via Ollama) with a strict JSON schema, logo/header guard and hallucination controls (no math, null for unknown)
  • Async processing through queue_job (non-blocking UI)
  • rapidfuzz fuzzy partner matching (threshold configurable)
  • Extraction wizard for the partner-fallback flow
  • Configurable via res.config.settings (API base URL, model, OCR language, match threshold)
  • Full i18n (POT) and OCA scaffolding (readme fragments, license, security)

Notes

  • External deps (paddleocr, rapidfuzz, etc.) are declared in the manifest / repo requirements.txt; cv2 is imported lazily and documented in readme/INSTALL.md since cv2 is not a valid PyPI distribution name for CI.
  • Tests mock the OCR/LLM at the service boundary, so CI needs no model downloads.
  • Depends on queue_job (OCA/queue, 19.0).

@OCA-git-bot OCA-git-bot added series:19.0 mod:ai_document_extraction Module ai_document_extraction labels Aug 6, 2026
@volkantasci

Copy link
Copy Markdown
Author

Updated the documentation (commit 1ee7c6e) to match the vision-LLM pipeline already implemented in the code: INSTALL.md now lists Pillow/pdf2image/requests instead of the removed PaddleOCR/OpenCV dependencies, and the readme no longer claims OCR is used. Describes running a vision model via Ollama (qwen3-vl) or a cloud provider (OpenRouter).

@volkantasci

Copy link
Copy Markdown
Author

Hi @etobella and @angelmoya 👋

Just a friendly ping — when you have a moment, could you take a look at this PR whenever convenient? All CI checks are green (pre-commit, Odoo/OCB tests, codecov, runboat).

Happy to adjust anything based on your feedback. Thanks so much for your time and all the maintenance work!

@etobella

Copy link
Copy Markdown
Member

Interesting, however, why don't you use https://github.com/OCA/ai/tree/18.0/ai_connection ? This way we can reuse code and so on.

…ted files, external deps)

- Drop cv2 from external_dependencies.python (not a valid PyPI name and not
  in manifestoo EXTERNAL_DEPENDENCIES_MAP; would break OCA CI pip install).
  Documented in readme/INSTALL.md instead.
- Replace per-module requirements.txt with readme/INSTALL.md (19.0 CI
  generates a repo-level requirements.txt from external_dependencies).
- Commit files generated by pre-commit: pyproject.toml (whool), README.rst,
  static/description/index.html, repo requirements.txt.
- Prettier reformat of empty view XMLs; add development_status Alpha.
- _parse_json_response uses raw_decode to find the first valid JSON object,
  ignoring trailing prose with extra braces and markdown code fences.
- Add tests for extract_invoice_data (mocked requests) covering the
  Authorization header behavior.
- _ai_prepare_image cleans up the temp file on render failure
- _ai_set_untaxed_line scopes account lookup to the move's company (company_ids)
- Add tests for action_extract_with_ai guards (non-draft, wrong move_type,
  missing attachment) and the enqueue path
… flow tests

- Add ir.model.access rules for extraction.wizard (Odoo 19 removed the
  transient-model bypass, so explicit ACLs are required).
- Pre-fill partner_id from the move in action_review_extraction and guard
  action_apply against clearing an existing partner with an empty selection.
- Add tests for the empty-partner guard and the review wizard flow.
- Ignore expected AI-extraction error logs in checklog-odoo.cfg
- Robust amount coercion (string amounts from the LLM)
- Drop license header from readme INSTALL fragment
OCA CI images lack libGL, so importing cv2 (pulled transitively by
paddleocr) fails. Detect availability with find_spec and skip the
pre-processor test; ocr_engine imports cv2 only when image_height is
not provided.
The 'Extract with AI' button could not run on a freshly uploaded vendor
bill because the web client saves the form before executing the button
and invoice_date is required in the vendor bill form arch. Defaulting
invoice_date to today makes the form always saveable; the extracted date
still overrides it when the AI returns one.
- Fix AI Extraction section layout: state badge in the header, extracted
  amounts and raw JSON moved to a dedicated notebook page (the previous
  nested group rendered the fields stacked and misaligned).
- Add immediate feedback: chatter message when the extraction starts and a
  badge showing the processing state; hide the button while processing/done.
- Store the OCR-ready processed image as an attachment on the move so both
  the original upload and the processed PNG are kept.
- Allow customer invoices (out_invoice/out_receipt) in addition to vendor
  bills.
- Improve the LLM prompt: extract the real issuer name (legal-suffix company
  names in the header are the partner, standalone logos are ignored) and
  extract invoice line items.
- Apply extracted line items as invoice lines (falling back to a single
  untaxed line when no lines are visible).
- Feed the LLM the list of available taxes (id/name/rate) and active
  currency codes so it can pick the exact tax per invoice line and report
  the invoice currency instead of guessing.
- Extract a 'description' field and structured 'lines' (name, quantity,
  price_unit, tax_id); create real invoice lines with their tax applied.
  The generic 'AI extracted amount' line name is removed: when no lines are
  visible a single line is created using the extracted description.
- Apply the extracted currency to the move when it differs from the company
  currency; keep the company currency when the extraction has no currency.
- Add anti-hallucination validation: reject hash-like/URL invoice numbers,
  model names or bare logos as partner, invalid/out-of-range dates, unknown
  currencies and tax ids.
- Post a chatter warning when no invoice date could be extracted.
@volkantasci
volkantasci force-pushed the 19.0-ai-document-extraction branch from 3f64a21 to 0c759e6 Compare August 13, 2026 15:23
@volkantasci

Copy link
Copy Markdown
Author

Hi @etobella, thanks for the suggestion! Following up on your feedback:

We ported ai_connection (and its dependency ai_tool) to 19.0 in a separate PR — #100 — and refactored ai_document_extraction to use it:

  • Provider config (URL / model / API key) now lives on ai.connection records via a new openai_compatible kind, with an AiOpenAICompatibleClient (OpenAI-compatible /chat/completions, Ollama num_ctx/keep_alive auto-detected from the URL).
  • res.config.settings now links an AI Connection instead of storing API fields; extraction-only settings (fuzzy_match_threshold) remain.
  • The extraction job routes through connection._run(...); retry behavior on empty/unparseable responses is preserved.

All 85 tests pass across ai_tool, ai_connection, and ai_document_extraction; pre-commit and CI are green. Happy to adjust anything else.

@OCA-git-bot OCA-git-bot added mod:ai_tool Module ai_tool mod:ai_connection Module ai_connection labels Aug 13, 2026
@volkantasci

Copy link
Copy Markdown
Author

Quick update on the final UX since my previous comment (relevant for review):

  • The AI configuration no longer lives in General Settings: it is a small settings form under Settings > Technical > AI > AI Document Extraction, right next to AI Connection. It links the connection to use and the partner-match threshold, and keeps General Settings clean.
  • Fixed an Odoo 19 edge case: get_values() returning integer 0 for the unset ai_connection_id Many2one created a phantom record that crashed the settings onchange with 'int' object has no attribute 'origin'. It now returns False and is covered by a regression test.
  • The settings transient follows the OCA convention and lives in wizards/.

All green: 78 local tests, CI (pre-commit, Odoo/OCB, codecov, runboat).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mod:ai_connection Module ai_connection mod:ai_document_extraction Module ai_document_extraction mod:ai_tool Module ai_tool series:19.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants