Skip to content

Development: Reuse compliance analysis across languages via snippet mapping - #2470

Open
ge94zec wants to merge 31 commits into
mainfrom
chore/2346-enhance-performance-for-compliance
Open

Development: Reuse compliance analysis across languages via snippet mapping #2470
ge94zec wants to merge 31 commits into
mainfrom
chore/2346-enhance-performance-for-compliance

Conversation

@ge94zec

@ge94zec ge94zec commented May 5, 2026

Copy link
Copy Markdown
Contributor

Checklist

General

Server

Client

  • Important: I implemented the changes with a very good performance, prevented too many (unnecessary) REST calls and made sure the UI is responsive, even with large data (e.g. using paging).
  • I strictly followed the principle of data economy for all client-server REST calls.
  • I strictly followed the client coding and design guidelines.

Motivation and Context

Closes: #2346

Description

This change improves the bilingual AI compliance workflow for job descriptions by replacing the second target-language compliance analysis with a snippet mapping. The editor now analyzes the source language once in analyzeJobDescription, streams and stores the translated text in translateTextStream, and then maps the detected compliance snippets onto the translated version automatically mapComplianceIssues. This reduces duplicate AI work, keeps translated highlights in sync with sourceIssues.

The duplicated compliance issue persistence logic in JobService was extracted into a shared helper. Since the Score is currently a combined value derived from both gender and legal analysis, this ensures the score remains consistent and is only updated when a full, valid recalculation is possible.

LLM compliance calls per autosave cycle is now reduced from 2 to 1. The translation with second analysis is reduced from 11s to approximately 2.30s.

Steps for Testing

Prerequisites:

  1. Log in to TUMApply as Prof
  2. Nav to Create position
  3. Verify that the complianceAnalysis of target stays within a 2-second threshold

Review Progress

Code Review

  • Code Review 1

Manual Tests

  • Test 1

Screenshots

Bildschirmfoto 2026-05-09 um 19 08 15

Test Coverage

Client

Class/File Line Coverage Lines Expects Ratio
job-creation-form.component.ts 72.64% 1284 96 7.5
editor.component.ts 50.45% 369 28 7.6

Server

Class/File Line Coverage Lines
MapComplianceIssuesRequestDTO.java 100.00% 14
AiService.java 5.80% 386
SnippetMatcher.java 100.00% 7
AiResource.java 48.15% 99
JobService.java 72.88% 385

Last updated: 2026-08-17 21:48:40 UTC

…rget text

- added endpoint POST that maps source issues to a target language
- added prompt that returns one mapped text snippet
  per line in source complianceIssues order, other fields carry over
- wired compliance mapping through client → server API
- separated compliance analysis from score calculation
- extracted save flow in JobService into Consumer
- improved performance by eliminating redundant analysis steps in job creation
@ge94zec ge94zec linked an issue May 5, 2026 that may be closed by this pull request
@github-actions github-actions Bot added server Pull requests that update Java code. (Added Automatically!) client Pull requests that update TypeScript code. (Added Automatically!) job Code changes in job module. labels May 5, 2026
@codacy-production

codacy-production Bot commented May 5, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 1 medium

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

Category Results
Complexity 1 medium

View in Codacy

🟢 Metrics 8 complexity

Metric Results
Complexity 8

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

📊 Server Test Coverage Too Low

🔍 View coverage locally:

./gradlew test jacocoTestReport
open build/reports/jacoco/test/html/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-server" artifact from this workflow run.

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

📊 Server Test Coverage Too Low

🔍 View coverage locally:

./gradlew test jacocoTestReport
open build/reports/jacoco/test/html/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-server" artifact from this workflow run.

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@ge94zec ge94zec changed the title Chore: Reuse compliance analysis across languages via snippet mapping Development: Reuse compliance analysis across languages via snippet mapping May 5, 2026
- updated openapi
- added comment to AiService
- simplified AiService mapping
- updated prompt
@github-actions github-actions Bot added the tests label May 5, 2026
@ge94zec ge94zec changed the title Development: Reuse compliance analysis across languages via snippet mapping Development: Reuse compliance analysis across languages via snippet mapping May 5, 2026
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

📊 Server Test Coverage Too Low

🔍 View coverage locally:

./gradlew test jacocoTestReport
open build/reports/jacoco/test/html/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-server" artifact from this workflow run.

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

📊 Client Test Coverage Too Low

🔍 View coverage locally:

npm run test:ci
open build/test-results/vitest/coverage/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-client" artifact from this workflow run.

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

📊 Server Test Coverage Too Low

🔍 View coverage locally:

./gradlew test jacocoTestReport
open build/reports/jacoco/test/html/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-server" artifact from this workflow run.

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

📊 Client Test Coverage Too Low

🔍 View coverage locally:

npm run test:ci
open build/test-results/vitest/coverage/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-client" artifact from this workflow run.

ge94zec added 2 commits May 9, 2026 02:39
- make job authorization check reusable in JobService
- check job access before mapping compliance issues
- Clear target-language issues in DB when source list is empty
- skip target mapping when source analysis was skipped/failed
…or-compliance' into chore/2346-enhance-performance-for-compliance
@github-actions

github-actions Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

📊 Server Test Coverage Too Low

🔍 View coverage locally:

./gradlew test jacocoTestReport
open build/reports/jacoco/test/html/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-server" artifact from this workflow run.

@github-actions

github-actions Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@codacy-production

codacy-production Bot commented May 9, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 3 medium

Alerts:
⚠ 3 issues (≤ 0 issues of at least minor severity)

Results:
3 new issues

Category Results
Complexity 3 medium

View in Codacy

🟢 Metrics 10 complexity

Metric Results
Complexity 10

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@github-actions

github-actions Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

📊 Server Test Coverage Too Low

🔍 View coverage locally:

./gradlew test jacocoTestReport
open build/reports/jacoco/test/html/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-server" artifact from this workflow run.

@github-actions

github-actions Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions github-actions Bot closed this Jul 25, 2026
# Conflicts:
#	src/main/java/de/tum/cit/aet/ai/service/AiService.java
#	src/main/webapp/app/job/job-creation-form/job-creation-form.component.ts
@ge94zec ge94zec reopened this Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📊 Server Test Coverage Too Low

🔍 View coverage locally:

./gradlew test jacocoTestReport
open build/reports/jacoco/test/html/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-server" artifact from this workflow run.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📊 Client Test Coverage Too Low

🔍 View coverage locally:

pnpm run test:ci
open build/test-results/vitest/coverage/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-client" artifact from this workflow run.

…nslated descriptions

- remove the second compliance analysis for the translated description
- reuse source-language compliance issues for snippet mapping
- update target-language highlights from mapped issues
@codacy-production

codacy-production Bot commented Aug 1, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 1 high · 1 medium

Alerts:
⚠ 2 issues (≤ 0 issues of at least minor severity)

Results:
2 new issues

Category Results
Security 1 high
Complexity 1 medium

View in Codacy

🟢 Metrics 18 complexity

Metric Results
Complexity 18

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📊 Server Test Coverage Too Low

🔍 View coverage locally:

./gradlew test jacocoTestReport
open build/reports/jacoco/test/html/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-server" artifact from this workflow run.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions github-actions Bot removed the stale label Aug 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

There hasn't been any activity on this pull request recently. Therefore, this pull request has been automatically marked as stale and will be closed if no further activity occurs within seven days. Thank you for your contributions.

@github-actions github-actions Bot added the stale label Aug 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@helios-aet
helios-aet Bot deployed to test-server August 17, 2026 10:03 Active
ge94zec and others added 2 commits August 17, 2026 13:29
- map source findings to exact translated snippets
- preserve issue metadata and persist mapped findings
- reject invalid mapping responses
- refine compliance and snippet-mapping prompts
- ignore empty mapped snippets during editor highlighting
- simplify focused mapping tests
@github-actions

Copy link
Copy Markdown
Contributor

🤖 OpenAPI spec and client code auto-updated and committed.

@github-actions

Copy link
Copy Markdown
Contributor

📊 Client Test Coverage Too Low

🔍 View coverage locally:

pnpm run test:ci
open build/test-results/vitest/coverage/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-client" artifact from this workflow run.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

…or-compliance' into chore/2346-enhance-performance-for-compliance
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@helios-aet
helios-aet Bot deployed to test-server August 17, 2026 12:12 Active
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@az108 az108 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed on top of Cathy's existing review — none of the points below overlap with hers.

The core idea is right: snippet mapping instead of a second full-text analysis is exactly the lever worth pulling here, and applyJobChangeForAnalysis quietly adds the first ownership check on this path — updateAiAnalysis had none on main. Raw ListList<ComplianceIssue> is a good catch too.

The prompt rewrite that ships with it is broken, though.

Blocking

1. {format} breaks every compliance analysis (AnalyzeComplianceText.st:59) — unresolved template variable, StTemplateRenderer defaults to ValidationMode.THROW. Reproduced against the real Spring AI 2.0.0-M4 jars; details inline.

2. Output contract no longer matches ComplianceIssue (AnalyzeComplianceText.st:52) — the prompt asks for text, category, suggestion, but there is no suggestion field anywhere in the project, and article / explanation / action are no longer requested at all. The compliance popover renders nothing but article and explanation.

3. {userLang} silently dropped (AnalyzeComplianceText.st:9) — the parameter is still passed from client through resource to service, and the JavaDoc still promises it controls the explanation language. It no longer does anything.

4. jobId has no Bean Validation (MapComplianceIssuesRequestDTO.java:13) — a request without it runs the LLM mapping, returns 200, and persists nothing without any error or log.

A general note on 1–3: the prompt rewrite looks unrelated to "reuse compliance analysis across languages via snippet mapping". Splitting it into its own PR would have surfaced these — the mapping logic itself is sound.

Test coverage

62be17d06 "removed tests" deletes AiServiceTest.java (418 lines) including five tests for mapComplianceIssues, with no resource-level replacement. That follows the no-*ServiceTest rule, but it leaves the empty-source-issues early return, the LLM catch branch, the size/null mismatch branch, and — most importantly — the silent dropping of non-verbatim snippets (continue after SnippetMatcher.isVerbatim) completely uncovered. SnippetMatcherTest only covers the trivial helper.

Things I checked and found fine

Listing these so they don't come up in a later round:

  • LLM call before the ownership check in mapComplianceIssues — consistent with analyzeJobDescription and extractPdfData, and this PR improves the situation rather than worsening it. isAdminOrMemberOf returns void and throws AccessDeniedException, so nothing is being silently ignored.
  • let finalContent: string | null = null — the type is forced by the pre-existing extractTranslatedTextFromStream(): string | null; no lint rule against null, and it appears in 77 of 267 client files.
  • The if (!jobId) return moved to the top of translateAndStoreOtherLanguage — both callers run after an awaited saveDraft with no await in between, so it is unreachable defence, not a behaviour change.
  • void-ed analysis promise in processDescriptionWithAianalyzeAndUpdateScore has nothing throwable outside its try, and main had the same shape with void Promise.all([...]).
  • Fully-qualified java.util.stream.IntStream — 19 comparable spots in src/main/java, no checkstyle or Spotless gate on imports.
  • postAndRead(..., Void.class, 400) without assertThatpostInvalid asserts the status itself; 81 test methods in the repo do it this way.
  • toHaveBeenCalledWith instead of toHaveBeenCalledOnce — the rule targets call-count assertions; 468 of 546 toHaveBeenCalledWith in the repo carry no count assertion.

- `suggestion`: exact fix in {descriptionLanguage}; REPLACE = safe alternative, ADD = sentence to append, REMOVE = "".
- If compliant, return exactly [].

{format}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{format} has no matching .param(...). AiService.analyzeJobDescription passes only descriptionLanguage, userLang, jobDescription and title, and this PR does not touch that call site.

Spring AI's default renderer is StTemplateRenderer (DefaultChatClient.java:88DEFAULT_TEMPLATE_RENDERER = StTemplateRenderer.builder().build()) with DEFAULT_VALIDATION_MODE = ValidationMode.THROW (StTemplateRenderer.java:67), and SpringAIConfiguration.java:44 is a plain ChatClient.builder(chatModel).build() — no custom renderer, no relaxed validation. .entity() does not create a format template variable: doSingleWithBeanOutputConverter only puts the format into the request context under ChatClientAttributes.OUTPUT_FORMAT, and the user text was already rendered back at .call(). format is not an ST built-in either, so it is not skipped.

Reproduced against the real 2.0.0-M4 jars with both prompt versions and the four actual params:

PR   -> IllegalStateException: Not all variables were replaced in the template.
        Missing variable names are: [format].
MAIN -> RENDER OK, length=2895

The exception is caught by analyzeJobDescription, calls aiFeatureToggleService.recordFailure() (which also trips the AI circuit breaker) and surfaces as InternalServerException("Compliance analysis parsing failed"). It is not data-dependent — every invocation fails while the AI toggle is on. CI does not catch it: AiResourceTest mocks AiService, and nothing in the suite renders a real prompt.

Fix: drop this line. Spring AI appends the format instruction on its own, which is why no other prompt in the repo has {format}.

{jobDescription}

OUTPUT
Return only a minified one-line JSON array; no markdown or prose. Use exactly these fields:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output contract no longer matches the type this is deserialized into. analyzeJobDescription still parses List<ComplianceIssue>, and ComplianceIssue has id, category, text, article, explanation, action, language — there is no suggestion field anywhere in src/main/java or the generated client. So suggestion is silently discarded by the converter (FAIL_ON_UNKNOWN_PROPERTIES is off), while article, explanation and action are no longer requested at all. main asked for them explicitly: "Object fields must be exactly: id, text, category, article, explanation, action".

This is not cosmetic. ai-compliance-popover.component.html:7-8 renders nothing but {{ issue()?.article }} and {{ issue()?.explanation }}, and job-creation-form.component.ts:338 uses issue.explanation for titleComplianceError. Both end up empty or filled with whatever the model happens to invent from the JSON schema.

Either keep the previous field list, or add suggestion to ComplianceIssue and migrate the UI in the same PR.

- Match meaning case-insensitively; examples are non-exhaustive.
- Check every occurrence against all four categories and return every match. Do not narrate the analysis.

LANGUAGES

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{userLang} is gone from the template — main had The explanation must be written in: {userLang}. here — but the parameter is still threaded through the entire chain: the client sends it (job-creation-form.component.ts:1874), AiResource.analyzeJobDescription takes @RequestParam(defaultValue = "en") String userLanguage, and AiService still calls .param("userLang", userLang). The JavaDoc still states it "controls the language of explanation texts in the returned issues".

StTemplateRenderer.validate only reports missing variables, never surplus ones, so this fails silently — the user-language feature is simply gone while the API signature and docs still promise it. Either wire the variable back into the prompt or remove the parameter from client, resource, service and JavaDoc.

@JsonInclude
public record MapComplianceIssuesRequestDTO(
String toLang,
UUID jobId,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jobId is the only field here without validation, while translatedText and complianceIssues carry @NotBlank / @NotNull. applyJobChangeForAnalysis opens with if (jobId == null) { return; } — no log, no exception. A request without it runs the expensive LLM mapping, returns 200 with the mapped issues, and persists nothing. The empty-source-issues early return is affected too. The generated OpenAPI model has readonly jobId?: string, so a spec-compliant caller is allowed to omit it.

@NotNull UUID jobId is what the server guidelines document — the input-DTO example in server-development.mdx (Request Validation) is literally public record CreateApplicationDTO(@NotBlank String motivation, @NotNull UUID jobId) {} — and what AssignSlotRequestDTO, BookSlotRequestDTO and UpdateApplicationDTO already do.

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

Labels

client Pull requests that update TypeScript code. (Added Automatically!) job Code changes in job module. server Pull requests that update Java code. (Added Automatically!) stale tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhance performance for compliance

3 participants