From 51e499102633d9428f67d09c5ed5f264684a6d86 Mon Sep 17 00:00:00 2001 From: Lisa Julia Nebel Date: Mon, 6 Jul 2026 13:39:19 +0200 Subject: [PATCH 1/6] Update README files --- README.md | 54 ++++++++++++++++++++++++--- cds-feature-ai-core/README.md | 2 +- cds-feature-recommendations/README.md | 7 +++- cds-feature-sap-document-ai/README.md | 13 ++++++- cds-starter-ai/README.md | 49 ++++++++++++++++++++++++ integration-tests/README.md | 10 ++--- 6 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 cds-starter-ai/README.md diff --git a/README.md b/README.md index b2ec6ec..816bbbb 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## About this project -This repository contains a collection of AI plugins for [CAP Java](https://cap.cloud.sap/docs/java/) applications, leveraging [SAP AI Core](https://help.sap.com/docs/sap-ai-core) and the SAP-RPT-1 foundation model. +This repository contains a collection of AI plugins for [CAP Java](https://cap.cloud.sap/docs/java/) applications, leveraging [SAP AI Core](https://help.sap.com/docs/sap-ai-core), the SAP-RPT-1 foundation model, and SAP Document AI. ### Plugins @@ -16,7 +16,7 @@ This repository contains a collection of AI plugins for [CAP Java](https://cap.c ### Starter -For the simplest setup, add the **`cds-starter-ai`** dependency which bundles both plugins: +For the simplest setup to get recommendations, add **`cds-starter-ai`** which currently bundles cds-feature-ai-core and cds-feature-recommendations: ```xml @@ -39,9 +39,10 @@ For the simplest setup, add the **`cds-starter-ai`** dependency which bundles bo - Java 17+ - CAP Java 4.9+ - Node.js 20+ with `@sap/cds-dk` 9+ (for CDS build tooling) -- An [SAP AI Core](https://help.sap.com/docs/sap-ai-core) service binding (for production use) +- An [SAP AI Core](https://help.sap.com/docs/sap-ai-core) service binding (for `cds-feature-ai-core` and `cds-feature-recommendations`) +- A [SAP Document Information Extraction](https://help.sap.com/docs/document-ai) service binding (for `cds-feature-sap-document-ai`) -Without an AI Core binding the plugins fall back to mock implementations for local development. +Without the respective service binding, each plugin falls back to a mock or degraded mode for local development. ## Samples @@ -54,7 +55,50 @@ mvn clean install # build all modules mvn test # run unit tests ``` -For per-plugin details (configuration, programmatic API, multi-tenancy behaviour) see the individual module READMEs. For integration tests against a real AI Core instance see [`integration-tests/spring`](integration-tests/spring). +For per-plugin details (configuration, programmatic API, multi-tenancy behaviour) see the individual module READMEs. For integration tests against a real AI Core instance see [`integration-tests/`](integration-tests/README.md). + +## GitHub Actions + +### CI Checks + +Every pull request to `main` and every push to `main` runs the following checks (see [`.github/workflows/pipeline.yml`](.github/workflows/pipeline.yml)): + +| Job | What it enforces | +|---|---| +| `tests` | Unit tests (`mvn test -P '!with-integration-tests'`) on Java 17 and 21 | +| `integration-tests` | Full integration tests on Java 17 and 21 | +| `local-mtx-tests` | MTX lifecycle tests using a local sidecar (`mvn verify -pl integration-tests/mtx-local/srv`) on Java 17 and 21 | +| `sonarqube-scan` | SonarQube static analysis | +| `codeql` | CodeQL security scanning (`security-extended` queries) on Java/Kotlin and GitHub Actions | +| `blackduck` (main only) | BlackDuck full open-source compliance scan | + +Static analysis runs at **compile time on every build** via Maven plugins configured in the root `pom.xml`: + +- **Spotless** — Google Java Format + SAP license headers (enforced at `process-sources`) +- **SpotBugs** — `effort=Max` (at `process-test-classes`) +- **PMD + CPD** — SAP Cloud SDK ruleset (at `process-test-classes`) +- **Maven Enforcer** — no duplicate dependency versions, requires Maven 3.6.3+ and Java 17+ + +### Integration Tests + +All integration tests live in [`integration-tests/`](integration-tests/README.md) — see that README for test modules, run commands, and profiles. + +### JaCoCo Aggregation + +Coverage is aggregated in the `coverage-report/` module using JaCoCo's `report-aggregate` goal. Each feature module runs `prepare-agent` to collect `.exec` data; the `coverage-report` module merges all `.exec` files and generates a single HTML report fed into SonarQube. + +Coverage thresholds are enforced by SonarQube in the pipeline, not by JaCoCo check goals. + +To generate the report locally: + +```bash +mvn clean verify +# Report: coverage-report/target/site/jacoco-aggregate/index.html +``` + +### Release + +Releases are triggered by publishing a GitHub Release with a matching tag. The [`release.yml`](.github/workflows/release.yml) workflow gates on two protected environments (`release-approval` then `release`), verifies the POM `revision` matches the tag, runs BlackDuck, builds without integration tests, and deploys to Maven Central with GPG signing. ## Support, Feedback, Contributing diff --git a/cds-feature-ai-core/README.md b/cds-feature-ai-core/README.md index 886aa22..1899195 100644 --- a/cds-feature-ai-core/README.md +++ b/cds-feature-ai-core/README.md @@ -110,4 +110,4 @@ clients (for example `RptInferenceClient` from `cds-feature-recommendations`). ## Sample -A runnable CAP Java sample demonstrating the AI plugins (which depend on this module) lives at [`samples/bookshop`](../samples/bookshop). +A runnable CAP Java sample demonstrating both `cds-feature-ai-core` and `cds-feature-recommendations` lives at [`samples/bookshop`](../samples/bookshop). The `AdminService.Books` entity is draft-enabled with value-list fields (genre, author) — when editing a book draft, the plugin suggests values based on the existing catalog. diff --git a/cds-feature-recommendations/README.md b/cds-feature-recommendations/README.md index 03c0a6f..cb74aa5 100644 --- a/cds-feature-recommendations/README.md +++ b/cds-feature-recommendations/README.md @@ -2,7 +2,7 @@ AI-powered field recommendations for SAP Fiori UIs in CAP Java applications, leveraging SAP AI Core and the SAP-RPT-1 foundation model. -## How It Works +## Features The plugin generically hooks into any draft-enabled entity that has properties with a value help. When a user edits a draft record, the plugin: @@ -98,6 +98,11 @@ annotate Books with { }] } } + +// Opt out a specific field +annotate Books with { + genre @UI.RecommendationState: 0; +} ``` #### Adding the SAP_Recommendations navigation property manually diff --git a/cds-feature-sap-document-ai/README.md b/cds-feature-sap-document-ai/README.md index e7977ba..b20ee2b 100644 --- a/cds-feature-sap-document-ai/README.md +++ b/cds-feature-sap-document-ai/README.md @@ -9,6 +9,7 @@ A CAP Java plugin that integrates [SAP Document AI](https://help.sap.com/docs/do - [Integration Guide](#integration-guide) - [Usage](#usage) - [CDS Model](#cds-model) +- [Multi-Tenancy](#multi-tenancy) - [Bookshop Sample](#bookshop-sample) - [Running without a Document AI service binding](#running-without-a-die-service-binding) - [Running with a Document AI service binding (hybrid mode)](#running-with-a-die-service-binding-hybrid-mode) @@ -190,7 +191,9 @@ Submit a document via your application. The plugin logs progress at `INFO` level > **Note:** In the current version, document extraction can only be triggered programmatically via event emission, as shown in the [Integration Guide](#integration-guide). Annotation-based triggering (e.g. declaratively marking an entity field or action to trigger extraction) is not yet supported and is planned for a future release. -> **Note:** Multitenancy is not implemented in the current version and is planned for a future release. Until then, the plugin should only be used in single-tenant deployments. +## Multi-Tenancy + +Multi-tenancy is not implemented in the current version and is planned for a future release. The `tenantId` field is stored on the `ExtractionJob` entity as groundwork. ### CDS Model @@ -382,6 +385,14 @@ The plugin communicates with the SAP Document Information Extraction service via --- +## Known Limitations + +- **Multi-tenancy** — not implemented; all jobs run in a single-tenant context. Planned for a future release. +- **Annotation-based triggering** — document extraction can only be initiated programmatically via event emission; declarative triggering is not yet supported. +- **OData API** — the plugin uses the DIE REST API only; OData API support is planned for a future release. + +--- + ## Monitoring and Logging All plugin log statements are prefixed with `[sap-document-ai]` to facilitate log filtering. The plugin uses SLF4J and is configured through the standard logging framework of the host application. diff --git a/cds-starter-ai/README.md b/cds-starter-ai/README.md new file mode 100644 index 0000000..a98c44a --- /dev/null +++ b/cds-starter-ai/README.md @@ -0,0 +1,49 @@ +# cds-starter-ai + +A zero-source convenience module that bundles all SAP AI plugins for CAP Java into a single dependency. + +## What it includes + +| Module | Scope | Description | +|---|---|---| +| [`cds-feature-ai-core`](../cds-feature-ai-core/README.md) | `compile` | Bridges CAP Java to SAP AI Core — resource groups, deployments, configurations, inference client | +| [`cds-feature-recommendations`](../cds-feature-recommendations/README.md) | `runtime` | AI-powered field recommendations for Fiori Elements draft UIs | + +`cds-feature-sap-document-ai` is currently **not** included as the plugin is not yet fully multi-tenant-ready. It will be added to the starter once MT support is complete. Add it as an explicit dependency if you need it today. + +## Setup + +```xml + + com.sap.cds + cds-starter-ai + ${cds-ai.version} + +``` + +Both plugins auto-register via Java's `ServiceLoader` — no further code changes are required. + +For the Node.js side of `cds-feature-recommendations`, also add the `@cap-js/ai` CDS plugin to your `package.json`: + +```json +{ + "dependencies": { + "@cap-js/ai": "^1" + } +} +``` + +## Prerequisites + +- Java 17+ +- CAP Java 4.9+ +- An [SAP AI Core](https://help.sap.com/docs/sap-ai-core) service binding (for production use) +- A [SAP Document Information Extraction](https://help.sap.com/docs/document-ai) service binding (once `cds-feature-sap-document-ai` is included) + +Without the respective bindings the plugins fall back to mock implementations — useful for local development. + +## Related + +- [SAP AI Core Documentation](https://help.sap.com/docs/sap-ai-core) +- [SAP AI SDK for Java](https://github.com/SAP/ai-sdk-java) +- [`cds-feature-sap-document-ai`](../cds-feature-sap-document-ai/README.md) — standalone document extraction plugin diff --git a/integration-tests/README.md b/integration-tests/README.md index 42a660e..4a7fb0e 100644 --- a/integration-tests/README.md +++ b/integration-tests/README.md @@ -45,7 +45,7 @@ Aggregated code coverage is produced by the `coverage-report/` module at the pro 1. Each module that runs tests has the JaCoCo agent attached (`prepare-agent`), which writes a `target/jacoco.exec` file during test execution. 2. The `coverage-report` module (built last in the reactor) merges all `.exec` files into a single `target/jacoco-merged.exec`. -3. It then generates an aggregated HTML/XML report via `jacoco:report-aggregate` and runs `jacoco:check` against configurable thresholds. +3. It then generates an aggregated HTML/XML report via `jacoco:report-aggregate`. Coverage thresholds are enforced by SonarQube in the pipeline, not by JaCoCo check goals. ### Generating the report @@ -61,12 +61,7 @@ coverage-report/target/site/jacoco-aggregate/index.html ### Thresholds -Per-module thresholds are defined in `coverage-report/pom.xml`: - -| Module | Instruction | Branch | Complexity | -|--------|-------------|--------|------------| -| `cds-feature-ai-core` | 0% | 0% | 0% | -| `cds-feature-recommendations` | 80% | 80% | 80% | +Coverage thresholds are enforced by SonarQube in the CI pipeline. The JaCoCo `check` goal is not used — the aggregated report feeds SonarQube, which is the authoritative gate. ### Coverage data sources @@ -74,5 +69,6 @@ The merged report combines execution data from: - `cds-feature-ai-core/target/jacoco.exec` (unit tests) - `cds-feature-recommendations/target/jacoco.exec` (unit tests) +- `cds-feature-sap-document-ai/target/jacoco.exec` (unit tests) - `integration-tests/spring/target/jacoco.exec` (integration tests) - `integration-tests/mtx-local/srv/target/jacoco.exec` (MTX integration tests, only when profile is active) From 1ac91523bfad47ec802b10865adf739ee113a83f Mon Sep 17 00:00:00 2001 From: Lisa Julia Nebel Date: Mon, 6 Jul 2026 15:04:58 +0200 Subject: [PATCH 2/6] Adapt to Marvins comments --- CONTRIBUTING.md | 39 +++++++++++++++++ README.md | 61 +-------------------------- cds-feature-ai-core/README.md | 2 +- cds-feature-sap-document-ai/README.md | 2 +- integration-tests/README.md | 2 +- 5 files changed, 43 insertions(+), 63 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8278ed7..1c7ba33 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,45 @@ The following rule governs code contributions: - Contributions must be licensed under the [Apache 2.0 License](./LICENSE) - Due to legal reasons, contributors will be asked to accept a Developer Certificate of Origin (DCO) when they create the first pull request to this project. This happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/). +## CI and Release + +### CI Checks + +Every pull request to `main` and every push to `main` runs the following checks (see [`.github/workflows/pipeline.yml`](.github/workflows/pipeline.yml)): + +| Job | What it enforces | +|---|---| +| `tests` | Unit tests (`mvn test -P '!with-integration-tests'`) on Java 17 and 21 | +| `integration-tests` | Full integration tests on Java 17 and 21 | +| `local-mtx-tests` | MTX lifecycle tests using a local sidecar (`mvn verify -pl integration-tests/mtx-local/srv`) on Java 17 and 21 | +| `sonarqube-scan` | SonarQube static analysis | +| `codeql` | CodeQL security scanning (`security-extended` queries) on Java/Kotlin and GitHub Actions | +| `blackduck` (main only) | BlackDuck full open-source compliance scan | + +Static analysis runs at **compile time on every build** via Maven plugins configured in the root `pom.xml`: + +- **Spotless** — Google Java Format + SAP license headers (enforced at `process-sources`) +- **SpotBugs** — `effort=Max` (at `process-test-classes`) +- **PMD + CPD** — SAP Cloud SDK ruleset (at `process-test-classes`) +- **Maven Enforcer** — no duplicate dependency versions, requires Maven 3.6.3+ and Java 17+ + +Integration tests live in [`integration-tests/`](integration-tests/README.md) — see that README for test modules, run commands, and profiles. + +### JaCoCo Coverage + +Coverage is aggregated in the `coverage-report/` module using JaCoCo's `report-aggregate` goal. Each feature module runs `prepare-agent` to collect `.exec` data; the `coverage-report` module merges all `.exec` files and generates a single HTML report fed into SonarQube. Coverage thresholds (80% on new code) are enforced by SonarQube in the pipeline. + +To generate the report locally: + +```bash +mvn clean verify +# Report: coverage-report/target/site/jacoco-aggregate/index.html +``` + +### Release + +Releases are triggered by publishing a GitHub Release with a matching tag. The [`release.yml`](.github/workflows/release.yml) workflow gates on two protected environments (`release-approval` then `release`), verifies the POM `revision` matches the tag, runs BlackDuck, builds without integration tests, and deploys to Maven Central with GPG signing. + ## Issues and Planning - We use GitHub issues to track bugs and enhancement requests. diff --git a/README.md b/README.md index 816bbbb..933180c 100644 --- a/README.md +++ b/README.md @@ -16,23 +16,7 @@ This repository contains a collection of AI plugins for [CAP Java](https://cap.c ### Starter -For the simplest setup to get recommendations, add **`cds-starter-ai`** which currently bundles cds-feature-ai-core and cds-feature-recommendations: - -```xml - - com.sap.cds - cds-starter-ai - ${cds-ai.version} - -``` - -```json -"dependencies": { - "@cap-js/ai": "^1" -} -``` - -> Note: [`cds-feature-sap-document-ai`](cds-feature-sap-document-ai/README.md) is not part of `cds-starter-ai` yet - it will be added once the plugin supports multi-tenancy. Add it as an explicit dependency in your application if you need it today. +See [`cds-starter-ai`](cds-starter-ai/README.md) for the quickest setup. ## Prerequisites @@ -57,49 +41,6 @@ mvn test # run unit tests For per-plugin details (configuration, programmatic API, multi-tenancy behaviour) see the individual module READMEs. For integration tests against a real AI Core instance see [`integration-tests/`](integration-tests/README.md). -## GitHub Actions - -### CI Checks - -Every pull request to `main` and every push to `main` runs the following checks (see [`.github/workflows/pipeline.yml`](.github/workflows/pipeline.yml)): - -| Job | What it enforces | -|---|---| -| `tests` | Unit tests (`mvn test -P '!with-integration-tests'`) on Java 17 and 21 | -| `integration-tests` | Full integration tests on Java 17 and 21 | -| `local-mtx-tests` | MTX lifecycle tests using a local sidecar (`mvn verify -pl integration-tests/mtx-local/srv`) on Java 17 and 21 | -| `sonarqube-scan` | SonarQube static analysis | -| `codeql` | CodeQL security scanning (`security-extended` queries) on Java/Kotlin and GitHub Actions | -| `blackduck` (main only) | BlackDuck full open-source compliance scan | - -Static analysis runs at **compile time on every build** via Maven plugins configured in the root `pom.xml`: - -- **Spotless** — Google Java Format + SAP license headers (enforced at `process-sources`) -- **SpotBugs** — `effort=Max` (at `process-test-classes`) -- **PMD + CPD** — SAP Cloud SDK ruleset (at `process-test-classes`) -- **Maven Enforcer** — no duplicate dependency versions, requires Maven 3.6.3+ and Java 17+ - -### Integration Tests - -All integration tests live in [`integration-tests/`](integration-tests/README.md) — see that README for test modules, run commands, and profiles. - -### JaCoCo Aggregation - -Coverage is aggregated in the `coverage-report/` module using JaCoCo's `report-aggregate` goal. Each feature module runs `prepare-agent` to collect `.exec` data; the `coverage-report` module merges all `.exec` files and generates a single HTML report fed into SonarQube. - -Coverage thresholds are enforced by SonarQube in the pipeline, not by JaCoCo check goals. - -To generate the report locally: - -```bash -mvn clean verify -# Report: coverage-report/target/site/jacoco-aggregate/index.html -``` - -### Release - -Releases are triggered by publishing a GitHub Release with a matching tag. The [`release.yml`](.github/workflows/release.yml) workflow gates on two protected environments (`release-approval` then `release`), verifies the POM `revision` matches the tag, runs BlackDuck, builds without integration tests, and deploys to Maven Central with GPG signing. - ## Support, Feedback, Contributing This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/cap-java/cds-ai/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md). diff --git a/cds-feature-ai-core/README.md b/cds-feature-ai-core/README.md index 1899195..886aa22 100644 --- a/cds-feature-ai-core/README.md +++ b/cds-feature-ai-core/README.md @@ -110,4 +110,4 @@ clients (for example `RptInferenceClient` from `cds-feature-recommendations`). ## Sample -A runnable CAP Java sample demonstrating both `cds-feature-ai-core` and `cds-feature-recommendations` lives at [`samples/bookshop`](../samples/bookshop). The `AdminService.Books` entity is draft-enabled with value-list fields (genre, author) — when editing a book draft, the plugin suggests values based on the existing catalog. +A runnable CAP Java sample demonstrating the AI plugins (which depend on this module) lives at [`samples/bookshop`](../samples/bookshop). diff --git a/cds-feature-sap-document-ai/README.md b/cds-feature-sap-document-ai/README.md index b20ee2b..aa532ad 100644 --- a/cds-feature-sap-document-ai/README.md +++ b/cds-feature-sap-document-ai/README.md @@ -389,7 +389,7 @@ The plugin communicates with the SAP Document Information Extraction service via - **Multi-tenancy** — not implemented; all jobs run in a single-tenant context. Planned for a future release. - **Annotation-based triggering** — document extraction can only be initiated programmatically via event emission; declarative triggering is not yet supported. -- **OData API** — the plugin uses the DIE REST API only; OData API support is planned for a future release. +- **OData API** — the plugin uses the Document AI Service REST API only; OData API support is planned for a future release. --- diff --git a/integration-tests/README.md b/integration-tests/README.md index 4a7fb0e..38e4622 100644 --- a/integration-tests/README.md +++ b/integration-tests/README.md @@ -61,7 +61,7 @@ coverage-report/target/site/jacoco-aggregate/index.html ### Thresholds -Coverage thresholds are enforced by SonarQube in the CI pipeline. The JaCoCo `check` goal is not used — the aggregated report feeds SonarQube, which is the authoritative gate. +Coverage thresholds are enforced by SonarQube in the CI pipeline (80% on new code). ### Coverage data sources From 32a102f3095a2d292127ede2bba0b38774d8fbda Mon Sep 17 00:00:00 2001 From: Lisa Julia Nebel Date: Mon, 6 Jul 2026 16:46:07 +0200 Subject: [PATCH 3/6] Remove missing odata api from limitations --- cds-feature-sap-document-ai/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/cds-feature-sap-document-ai/README.md b/cds-feature-sap-document-ai/README.md index aa532ad..226cb36 100644 --- a/cds-feature-sap-document-ai/README.md +++ b/cds-feature-sap-document-ai/README.md @@ -389,7 +389,6 @@ The plugin communicates with the SAP Document Information Extraction service via - **Multi-tenancy** — not implemented; all jobs run in a single-tenant context. Planned for a future release. - **Annotation-based triggering** — document extraction can only be initiated programmatically via event emission; declarative triggering is not yet supported. -- **OData API** — the plugin uses the Document AI Service REST API only; OData API support is planned for a future release. --- From 0a6862c457ed38686467c50032b3339f757afa70 Mon Sep 17 00:00:00 2001 From: Lisa Julia Nebel Date: Tue, 7 Jul 2026 14:09:06 +0200 Subject: [PATCH 4/6] Update cds-starter-ai/README.md Co-authored-by: Marvin L Signed-off-by: Lisa Julia Nebel --- cds-starter-ai/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cds-starter-ai/README.md b/cds-starter-ai/README.md index a98c44a..2483ebc 100644 --- a/cds-starter-ai/README.md +++ b/cds-starter-ai/README.md @@ -35,7 +35,7 @@ For the Node.js side of `cds-feature-recommendations`, also add the `@cap-js/ai` ## Prerequisites -- Java 17+ +- Java 21+ - CAP Java 4.9+ - An [SAP AI Core](https://help.sap.com/docs/sap-ai-core) service binding (for production use) - A [SAP Document Information Extraction](https://help.sap.com/docs/document-ai) service binding (once `cds-feature-sap-document-ai` is included) From e7bdbff017b91977ba0fcd371b6471cfd48f7e8c Mon Sep 17 00:00:00 2001 From: Samyuktha Prabhu <33195453+samyuktaprabhu@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:10:59 +0200 Subject: [PATCH 5/6] chore: add sample invoice document & link GH issues (#114) * chore: review fixes * chore: link GH issues * chore: add sample invoice document * chore: review fixes * chore: move file --- cds-feature-sap-document-ai/README.md | 2 + cds-feature-sap-document-ai/docs/handover.md | 38 ++++++++++++++++--- samples/bookshop/dummy invoice.pdf | Bin 0 -> 48016 bytes 3 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 samples/bookshop/dummy invoice.pdf diff --git a/cds-feature-sap-document-ai/README.md b/cds-feature-sap-document-ai/README.md index 226cb36..8f91c7a 100644 --- a/cds-feature-sap-document-ai/README.md +++ b/cds-feature-sap-document-ai/README.md @@ -276,6 +276,8 @@ The plugin will resolve the Document AI service binding at startup, construct an The `AdminService` exposes a `Books` entity with a bound action `extractDocumentData()` illustrating how to trigger extraction from a CAP action. The `Attachments` composition on `Books` provides a Fiori UI for file upload and is used here purely as a convenient way to supply documents in the sample. The CAP Attachments plugin is not a dependency of this plugin - document storage and retrieval are outside the scope of `cds-feature-sap-document-ai`, which is concerned solely with submitting documents to SAP Document AI and delivering the extracted results. +A sample PDF invoice (`dummy invoice.pdf`) is included in the `samples/bookshop/` directory. You can upload it via the Fiori UI and trigger `extractDocumentData()` to see the full extraction flow end-to-end without needing your own test document. + --- ## How It Works diff --git a/cds-feature-sap-document-ai/docs/handover.md b/cds-feature-sap-document-ai/docs/handover.md index f814cf6..91d377b 100644 --- a/cds-feature-sap-document-ai/docs/handover.md +++ b/cds-feature-sap-document-ai/docs/handover.md @@ -55,7 +55,6 @@ This is an alpha release. The core extraction pipeline works end-to-end, and the | S9 | **CLI scaffolding** | Setup is manual and documented in the README. A `cds add document-ai` command is not yet implemented. | | S10 | **OData API** | REST API works across all plans. OData support for higher-tier plans is a future enhancement. | | S11 | **Job cleanup** | Job records are kept for observability. A configurable retention policy is not yet implemented. | -| S12 | **Multiple bindings** | One Document AI binding is resolved at startup. Routing documents to different instances by type, region, or business unit is not yet supported. | --- @@ -79,26 +78,36 @@ If a binding is added after the app starts, it won't be picked up until a restar These are ideas and suggestions and not a fixed plan. The ordering reflects what felt most important at the time of writing, but the incoming team should feel free to reprioritise based on their own context and stakeholder needs. -### 1. Multitenancy _(S1)_ +### 1. Multitenancy _(S1)_ - Priority A natural first area to tackle would be making each tenant use its own Document AI credentials with isolated jobs. The polling logic and HTTP client would need to become tenant-aware - the `tenantId` field is already on `ExtractionJob`, so no schema migration is needed. -### 2. Annotation-Based Triggering _(S2)_ +**Tracking issue:** [#98](https://github.com/cap-java/cds-ai/issues/98) + +### 2. Annotation-Based Triggering _(S2)_ - Priority One possible enhancement is to allow developers to annotate a CDS entity field with `@DocumentAI` to automatically trigger extraction - removing the need for boilerplate event emission. This could cover both the backend (plugin reacts to annotated field writes) and the Fiori Elements UI (an "Upload & Extract" button injected automatically on the Object Page). +**Tracking issue:** [#97](https://github.com/cap-java/cds-ai/issues/97) + ### 3. Job Recovery on Startup _(S6)_ A useful addition could be a startup check for any jobs left in `PENDING`, `SUBMITTED`, or `RUNNING` status from before a restart, resuming polling for them automatically rather than waiting for a new submission to arrive. +**Tracking issue:** [#100](https://github.com/cap-java/cds-ai/issues/100) + ### 4. Extraction Progress Indicator The backend already tracks `SUBMITTED` and `RUNNING` states - it could be worth surfacing that status in the Fiori Elements Object Page as a visible progress indicator or status strip so users have feedback while extraction is running. +**Tracking issue:** [#108](https://github.com/cap-java/cds-ai/issues/108) + ### 5. Automatic Field Mapping _(S5)_ One idea is to have the plugin match extracted fields to CDS entity properties by name convention and pre-fill the Fiori form automatically. Fields below a configurable confidence threshold could be visually flagged (e.g. amber highlight) so users know what to double-check before saving. +**Tracking issue:** [#101](https://github.com/cap-java/cds-ai/issues/101) + ### 6. Document Viewer with Extraction Highlights and Human-in-the-Loop Verification A more exploratory idea is to visualise extracted fields on the document itself - bounding boxes colour-coded by confidence level, with click-to-focus between the document viewer and the form. Bounding box coordinates come back from Document AI and would need to be stored alongside the extraction result and exposed to a UI viewer component. @@ -107,45 +116,62 @@ It could also be worth visually marking AI-extracted fields in the Fiori form (e A further possibility is a human-in-the-loop confirmation step: the user reviews the extracted fields, corrects any errors, and explicitly confirms the result. This confirmed payload could be submitted back to Document AI as ground-truth feedback to activate the [instant learning](https://help.sap.com/docs/document-ai/sap-document-ai/instant-learning?locale=en-US) feature, improving model accuracy for that schema over time. +**Tracking issue:** [#109](https://github.com/cap-java/cds-ai/issues/109) + ### 7. Document AI Outbound Channels - Push-Based Result Delivery Document AI supports outbound channels at the schema level: notification channels (status pushes) and extension channels (callbacks triggered after prediction). One option worth exploring is registering the plugin as a target so Document AI pushes results to it directly, eliminating the need to poll. The `DocumentAiClient` interface and `ExtractionService.updateExtractionResult()` are already the right place to plug this in. +**Tracking issue:** [#106](https://github.com/cap-java/cds-ai/issues/106) + ### 8. Custom Schema Synchronisation _(S4)_ One possible enhancement is to let developers define custom document type extraction schemas in the CDS model via annotations, with the plugin syncing these to Document AI automatically at deploy time or startup - removing the need for manual configuration in the Document AI workspace. +**Tracking issue:** [#107](https://github.com/cap-java/cds-ai/issues/107) + ### 9. Customisable Extraction Templates Right now, every submission requires constructing the Document AI `options` JSON by hand. A template mechanism could let developers define named configurations - document type, schema ID, field selection, confidence thresholds - declaratively in the CDS model or `application.yaml`, and just reference the template name at submission time. +**Tracking issue:** [#116](https://github.com/cap-java/cds-ai/issues/116) + ### 10. Local Mock Mode _(S7)_ A mock mode returning configurable static extraction results without a real Document AI binding would make local development more convenient. -### 11. Application-Level Outbound Channels +**Tracking issue:** [#102](https://github.com/cap-java/cds-ai/issues/102) + +### 11. Configurable result delivery channels The plugin currently delivers results only via the `DocumentExtractionResult` CDS event. It could be worth exploring additional delivery channels so consuming applications can receive results through whatever channel fits their architecture. +**Tracking issue:** [#115](https://github.com/cap-java/cds-ai/issues/115) + ### 12. `cds add document-ai` Scaffold Command _(S9)_ A `cds add document-ai` CLI command could set up the Document AI service binding in `mta.yaml`, enable the persistent outbox in `application.yaml`, and generate boilerplate handler stubs - lowering the barrier significantly for new adopters. +**Tracking issue:** [#105](https://github.com/cap-java/cds-ai/issues/105) + ### 13. OData API Support _(S10)_ For applications on higher-tier plans, it could be worth exploring the Document AI OData API as an alternative transport, enabling richer querying and result navigation. +**Tracking issue:** [#99](https://github.com/cap-java/cds-ai/issues/99) + ### 14. Terminal Job Cleanup _(S11)_ A configurable retention policy that deletes or archives `ExtractionJob` rows after they've been in `DONE` or `FAILED` status for a set period would prevent unbounded table growth on high-volume deployments. +**Tracking issue:** [#103](https://github.com/cap-java/cds-ai/issues/103) + ### 15. Malware Scanning _(S8)_ It may be worth assessing whether documents should be scanned via SAP Malware Scanning Service before being forwarded to Document AI - particularly for multitenant deployments where uploaded content is less trusted. -### 16. Multiple Service Binding Support _(S12)_ +**Tracking issue:** [#104](https://github.com/cap-java/cds-ai/issues/104) -Supporting multiple Document AI bindings could allow applications to route documents to different instances based on context - document type, region, business unit. This would need a binding selection strategy, either convention-based or configurable via annotations or `application.yaml`. --- diff --git a/samples/bookshop/dummy invoice.pdf b/samples/bookshop/dummy invoice.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2309f5c58cc99a06895d1cbcacd96479fdd3280d GIT binary patch literal 48016 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 Date: Wed, 8 Jul 2026 10:59:48 +0200 Subject: [PATCH 6/6] Update CONTRIBUTING.md Co-authored-by: Marvin L Signed-off-by: Lisa Julia Nebel --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c7ba33..f7baf9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,7 @@ Static analysis runs at **compile time on every build** via Maven plugins config - **Spotless** — Google Java Format + SAP license headers (enforced at `process-sources`) - **SpotBugs** — `effort=Max` (at `process-test-classes`) - **PMD + CPD** — SAP Cloud SDK ruleset (at `process-test-classes`) -- **Maven Enforcer** — no duplicate dependency versions, requires Maven 3.6.3+ and Java 17+ +- **Maven Enforcer** — no duplicate dependency versions, requires Maven 3.6.3+ and Java 21+ Integration tests live in [`integration-tests/`](integration-tests/README.md) — see that README for test modules, run commands, and profiles.