Skip to content
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
7c1b236
added prompt
ge94zec Mar 17, 2026
bf426b2
prettier
ge94zec Mar 17, 2026
d25fb1e
Merge branch 'main' into feat/2063-add-compliance-server-side
ge94zec Mar 18, 2026
91c583e
chore: update OpenAPI spec and generated client
github-actions[bot] Mar 18, 2026
5abed90
updated java doc
ge94zec Mar 18, 2026
4a034ec
Merge remote-tracking branch 'origin/feat/2063-add-compliance-server-…
ge94zec Mar 18, 2026
ae98a60
fix: change requests
ge94zec Mar 21, 2026
6570dda
Merge branch 'main' into feat/2063-add-compliance-server-side
ge94zec Mar 21, 2026
33f4691
chore: update OpenAPI spec and generated client
github-actions[bot] Mar 21, 2026
2502905
Merge branch 'main' into feat/2063-add-compliance-server-side
ge94zec Mar 23, 2026
00fd920
Merge remote-tracking branch 'origin/main' into feat/2063-add-complia…
ge94zec Apr 7, 2026
e392f27
chore: update OpenAPI spec and generated client
github-actions[bot] Apr 12, 2026
b33244c
feat(ai-score): implement server-side inclusivity score calculation w…
ge94zec Apr 12, 2026
b66e7ca
Merge remote-tracking branch 'origin/feat/2063-add-compliance-server-…
ge94zec Apr 12, 2026
74b6b72
prettier
ge94zec Apr 12, 2026
7258506
Merge branch 'main' into feat/2063-add-compliance-server-side
ge94zec Apr 12, 2026
de857d5
chore: update OpenAPI spec and generated client
github-actions[bot] Apr 12, 2026
a641e92
fix client tests
ge94zec Apr 12, 2026
7ec341d
fix client tests
ge94zec Apr 13, 2026
17acc03
Merge branch 'main' into feat/2063-add-compliance-server-side
ge94zec Apr 13, 2026
b8f8944
fix change requests
ge94zec Apr 13, 2026
ce179aa
Merge remote-tracking branch 'origin/feat/2063-add-compliance-server-…
ge94zec Apr 13, 2026
292f6de
Merge branch 'main' into feat/2063-add-compliance-server-side
ge94zec Apr 13, 2026
f20fc15
changed ComplianceIssueDTO to class
ge94zec Apr 14, 2026
a9f0fe5
Merge remote-tracking branch 'origin/feat/2063-add-compliance-server-…
ge94zec Apr 14, 2026
140fd6a
chore: update OpenAPI spec and generated client
github-actions[bot] Apr 14, 2026
4ccd2b6
eslint
ge94zec Apr 14, 2026
db09e52
Merge remote-tracking branch 'origin/feat/2063-add-compliance-server-…
ge94zec Apr 14, 2026
3ca255c
-refactored comments
ge94zec Apr 14, 2026
97e4fd4
client and server tests fix
ge94zec Apr 14, 2026
7a9dc3d
prettier
ge94zec Apr 14, 2026
c0c73fc
-server tests fix
ge94zec Apr 14, 2026
e6367d4
Merge branch 'feat/2063-add-compliance-server-side' into feat/2064-ad…
ge94zec Apr 14, 2026
1dd4d7c
feat: add text highlighting
ge94zec Apr 15, 2026
b257d23
Merge remote-tracking branch 'origin/main' into feat/2064-add-complia…
ge94zec Apr 17, 2026
eef156c
Merge remote-tracking branch 'origin/main' into feat/2064-add-complia…
ge94zec Apr 17, 2026
6cf7747
Fix highlighting while translating
ge94zec Apr 17, 2026
f781993
Fix AI score ring undefined -> 0 animation
ge94zec Apr 17, 2026
5ffe4ad
Fix client/serevr tests
ge94zec Apr 17, 2026
5a29c93
Merged liquibase changelog to one
ge94zec Apr 17, 2026
3e1e8c8
feat: add Ai compliance popover and sidebar UI
ge94zec Apr 18, 2026
0ceeed6
fix bug
ge94zec Apr 19, 2026
4f736a9
server test fix
ge94zec Apr 19, 2026
8f4f5f6
Merge remote-tracking branch 'origin/main' into feat/2062-feature-add…
az108 Apr 19, 2026
541340c
fix merge conflict issues
az108 Apr 19, 2026
21582d3
create custom component for compliance checker category pills
az108 Apr 19, 2026
e540a58
Merge remote-tracking branch 'origin/main' into feat/2062-feature-add…
az108 Apr 19, 2026
3fc6df4
create custom component for compliance checker category pills
az108 Apr 19, 2026
b91a41a
chore: update OpenAPI spec and generated client
github-actions[bot] Apr 19, 2026
ef1da78
prettier/lint
ge94zec Apr 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ paths:
in: query
required: true
schema: {type: string}
- name: userLanguage
in: query
required: false
schema: {type: string, default: en}
requestBody:
content:
application/json:
Expand Down Expand Up @@ -2806,6 +2810,7 @@ components:
enum: [CRITICAL_AGG, TRANSPARENCY, GENDER_INCLUSIVE, GENDER_EXCLUSIVE]
explanation: {type: string}
id: {type: string}
language: {type: string}
text: {type: string}
ConflictDataDTO:
type: object
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/de/tum/cit/aet/ai/dto/ComplianceIssue.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ public class ComplianceIssue {

@Enumerated(EnumType.STRING)
private ComplianceAction action;

private String language;
}
14 changes: 8 additions & 6 deletions src/main/java/de/tum/cit/aet/ai/service/AiService.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import javax.imageio.ImageIO;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.Loader;
Expand Down Expand Up @@ -222,13 +219,14 @@ public ExtractedApplicationDataDTO extractAndPersistPdfData(String applicationId
*
* @param jobFormDTO The data transfer object containing the current state of the job posting.
* @param lang The language identifier (de/en) currently active in the editor.
* @param userLang controls the language of explanation texts in the returned issues.
* @return A list of compliance issues containing the combined legal and linguistic findings.
*/
public List<ComplianceIssue> analyzeCurrentJobDescription(JobFormDTO jobFormDTO, String lang) {
public List<ComplianceIssue> analyzeCurrentJobDescription(JobFormDTO jobFormDTO, String lang, String userLang) {
String raw = "de".equals(lang) ? jobFormDTO.jobDescriptionDE() : jobFormDTO.jobDescriptionEN();
String input = raw != null ? Jsoup.parse(raw).text() : "";
GenderBiasAnalysisResponse genderAnalysis = genderBiasAnalysisService.analyzeText(input, lang);
return analyzeJobDescription(jobFormDTO.title(), jobFormDTO.jobId(), input, lang, genderAnalysis, null);
return analyzeJobDescription(jobFormDTO.title(), jobFormDTO.jobId(), input, lang, userLang, genderAnalysis, null);
}

/**
Expand All @@ -245,6 +243,7 @@ public List<ComplianceIssue> analyzeCurrentJobDescription(JobFormDTO jobFormDTO,
* @param jobId Unique identifier for the job.
* @param text Extracted raw text of the job description.
* @param lang the analysis language, expected to be `de` or `en`
* @param userLang controls the language of explanation texts in the returned issues.
* @param analysis Result of the primary linguistic gender analysis.
* @param translatedAnalysis Second analysis of the translated counterpart.
* @return A list containing all identified compliance issues.
Expand All @@ -255,6 +254,7 @@ public List<ComplianceIssue> analyzeJobDescription(
UUID jobId,
String text,
String lang,
String userLang,
GenderBiasAnalysisResponse analysis,
GenderBiasAnalysisResponse translatedAnalysis
) {
Expand All @@ -266,11 +266,13 @@ public List<ComplianceIssue> analyzeJobDescription(
u
.text(complianceResource)
.param("descriptionLanguage", lang)
.param("userLang", userLang)
.param("jobDescription", text)
.param("title", title != null ? title : "")
)
.call()
.entity(new ParameterizedTypeReference<>() {});
complianceIssues.forEach(issue -> issue.setLanguage(lang));
} catch (Exception e) {
throw new InternalServerException("Compliance analysis parsing failed", e);
}
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/de/tum/cit/aet/ai/web/AiResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,18 @@ public ResponseEntity<ExtractedApplicationDataDTO> extractPdfData(
*
* @param jobForm the job form data used as the basis for the analysis
* @param descriptionLanguage the language of the job description, `de` or `en`
* @param userLanguage the language in which issue explanations should be returned
* @return a ResponseEntity containing detected compliance findings
*/

@ProfessorOrEmployeeOrAdmin
@PostMapping(value = "analyze-job-description", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<ComplianceIssue>> analyzeJobDescriptionForCompliance(
@RequestBody JobFormDTO jobForm,
@RequestParam("lang") String descriptionLanguage
@RequestParam("lang") String descriptionLanguage,
@RequestParam(defaultValue = "en") String userLanguage
) {
log.info("POST /api/ai/analyzeJobDescription - Request received (toLang={})", descriptionLanguage);
return ResponseEntity.ok(aiService.analyzeCurrentJobDescription(jobForm, descriptionLanguage));
return ResponseEntity.ok(aiService.analyzeCurrentJobDescription(jobForm, descriptionLanguage, userLanguage));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<column name="article" type="varchar(255)"/>
<column name="explanation" type="text"/>
<column name="action" type="varchar(50)"/>
<column name="language" type="varchar(5)"/>
</createTable>
</changeSet>

Expand Down
20 changes: 9 additions & 11 deletions src/main/resources/prompts/AnalyzeComplianceText.st
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,33 @@ You are the TUMApply ComplianceReader, an AI legal and compliance assistant for
TASK:
Analyze the provided job posting text for legal risks and missing information based on German and EU law.
The input description language is: {descriptionLanguage}.
The explanation must be written in: {userLang}.
You must analyze meaning, not just keywords.

RULES:
1) CATEGORY CRITICAL_AGG:
1.
- Detect discrimination by protected characteristics under section 1 AGG.
- Detect exclusion based on disability, age, origin, or gender.
- Detect requests for unnecessary personal data or sharing with third parties without consent under Art. 5 DSGVO.
- JOB TITLE CHECK: If title "{title}" is not gender-neutral with "(m/f/d)" or "(m/w/d)", flag it.
2.
- Scan for violations of Data Minimization (Art. 5 DSGVO).
- This includes requests for unnecessary personal data (e.g., photos, ID copies, criminal records, marital status, religion) or explicit sharing of applicant data with third parties without consent.
- Action: Instruct to completely REMOVE the sentence.
- JOB TITLE CHECK: Evaluate the "Job Title" provided in the input. If "{title}" is not gender-neutral including "(m/f/d)" or "(m/w/d)", flag it.

2) CATEGORY TRANSPARENCY:
- Detect missing transparency when applicant data is processed or shared by external parties.
- This includes mentions of external consultants, partners, systems, or entities involved in recruitment, such as headhunters, assessment centers, ATS providers, or cooperative arrangements with universities or consortia.
- NOT triggered by: what data is requested from the applicant.
- Action: Suggest a message that clarifies whether applicant data is shared with external recipients (Art. 13 DSGVO)

INPUT TEXT:
{title}
{jobDescription}

OUTPUT:
- Return ONLY a valid JSON array.
- No markdown.
- No prose.
- One object per issue.
- Return ONLY a valid JSON array. No markdown. No prose.
- One object per issue. NEVER merge issues! Each violation gets its own object
- Object fields must be exactly:
id, text, category, article, explanation, action
- category must be exactly CRITICAL_AGG or TRANSPARENCY.
- action must be exactly REPLACE or ADD or REMOVE.
- text must be an exact snippet from input text (plain text, no HTML tags).
- text: MAXIMUM 2-4 WORDS. Extract exact snippet from input text that uniquely identifies the violation (plain text, no HTML tags).
- explanation must be a single SHORT sentence stating the legal violation directly. Sound like a formal legal notice.
- If no issues exist, return exactly [].
2 changes: 1 addition & 1 deletion src/main/resources/prompts/TranslateText.st
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ INPUT TEXT (HTML):
OUTPUT CONSTRAINTS - CRITICAL:
1. Fidelity: You must preserve the exact formatting of the original text, including line breaks, bullet points, and paragraphs.
2. Style: Provide a natural, grammatically correct translation that mirrors the professional tone of the source. Prioritize functional equivalence over literal word-for-word translation.
3. Integrity: Use gender-inclusive language appropriate for Higher Education (e.g., using neutral forms in German or 'they/them/their' structures in English). Avoid words with forbidden stems: [{nonInclusiveWords}]. Rather use alternatives from: [{inclusiveWords}].
3. Integrity: Use language appropriate for Higher Education (e.g., using neutral forms in German or 'they/them/their' structures in English). Translate ALL content literally and completely. Your task is translation only.
4. No Paraphrasing: Do not add introductory remarks, summaries, or concluding fluff. Return only the translated text.
5. Terminology: Ensure legal requirements (e.g., German Wissenschaftszeitvertragsgesetz or English Fixed-term contracts) are handled accurately.
6. Technical Terms: Keep all industry-standard terminology (e.g., "Java", "Scrum", "Stakeholder", "Python", "fMRI", "TV-L E13", "PyTorch", etc.) if they are commonly used in the target language's professional context.
Expand Down
6 changes: 5 additions & 1 deletion src/main/webapp/app/generated/api/ai-resource-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ export class AiResourceApi {
*
* @param lang
* @param jobFormDTO
* @param userLanguage
*/
analyzeJobDescriptionForCompliance(lang: string, jobFormDTO: JobFormDTO): Observable<Array<ComplianceIssue>> {
analyzeJobDescriptionForCompliance(lang: string, jobFormDTO: JobFormDTO, userLanguage?: string): Observable<Array<ComplianceIssue>> {
const queryParams = new URLSearchParams();
if (lang !== undefined && lang !== null) {
queryParams.set('lang', String(lang));
}
if (userLanguage !== undefined && userLanguage !== null) {
queryParams.set('userLanguage', String(userLanguage));
}
const queryString = queryParams.toString();
const url = `${this.basePath}/api/ai/analyze-job-description${queryString ? `?${queryString}` : ''}`;
return this.http.post<Array<ComplianceIssue>>(url, jobFormDTO);
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/app/generated/model/compliance-issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface ComplianceIssue {
readonly category?: ComplianceIssueCategoryEnum;
readonly explanation?: string;
readonly id?: string;
readonly language?: string;
readonly text?: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ <h1 jhiTranslate="{{ pageTitle() }}"></h1>
[required]="true"
[model]="basicInfoForm.get('title')?.value"
[control]="basicInfoForm.get('title') ?? undefined"
[complianceError]="titleComplianceError() ?? undefined"
/>
</div>
</div>
Expand Down Expand Up @@ -179,7 +180,9 @@ <h1 jhiTranslate="{{ pageTitle() }}"></h1>
[shouldTranslate]="true"
[showGenderDecoderButton]="true"
height="20rem"
(highlightHovered)="onHighlightHovered($event)"
/>
<jhi-compliance-popover [issue]="activePopoverIssue()" [x]="popoverX()" [y]="popoverY()" />
</div>
</div>
</div>
Expand All @@ -204,7 +207,10 @@ <h1 jhiTranslate="{{ pageTitle() }}"></h1>
[isGenerating]="isGeneratingDraft()"
[isAnalyzing]="isScoreProcessing()"
[isRewriteMode]="rewriteButtonSignal()"
[currentLang]="currentDescriptionLanguage()"
[complianceIssues]="complianceIssues()"
(generate)="generateJobApplicationDraft()"
(filterComplianceCat)="onComplianceFilterChange($event)"
/>
</aside>
}
Expand Down
Loading
Loading