Skip to content

Commit 811f5d0

Browse files
committed
fix: address review feedback for gender bias analysis
- run gender bias analysis independently of AI consent and system availability - return NEUTRAL for empty bias results while reserving undefined for pending analysis - restore the previous legal scoring rules for CRITICAL_AGG and TRANSPARENCY - revert unrelated IntelliJ run configuration and persistent MySQL volume changes - add NON_EMPTY serialization to biased and compliance issue DTOs - validate the analysis request jobId with @NotNull - add guarded Liquibase preconditions for the biased-issue unique constraint - rename the gender_bias_score database column to ai_score - document the new JobRepository queries and explain their separate loading strategy - replace ineffective HashSet usage with ArrayList for compliance issues - remove unused gender analysis service and editor code - move calculateCombinedAiScore and its test to ComplianceScoreCalculator - add JavaDoc for the combined AI score calculation - cover empty gender analysis results in status and button tests - rename the stale codingDisplay test description - remove the ineffective language-change test - refactor editor tests to use component inputs and template events instead of private access
1 parent a25e72d commit 811f5d0

20 files changed

Lines changed: 129 additions & 139 deletions

File tree

.run/DocApplyApp.run.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
<configuration default="false" name="DocApplyApp" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
33
<option name="ACTIVE_PROFILES" value="dev" />
44
<option name="FRAME_DEACTIVATION_UPDATE_POLICY" value="UpdateClassesAndResources" />
5-
<module name="doc-apply.main" />
5+
<module name="de.tum.cit.aet.doc-apply.main" />
66
<option name="SPRING_BOOT_MAIN_CLASS" value="de.tum.cit.aet.DocApplyApp" />
77
<method v="2">
88
<option name="Make" enabled="true" />
99
</method>
1010
</configuration>
11-
</component>
11+
</component>

docker/local-setup/mysql.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ services:
55
image: mysql:9.3.0
66
volumes:
77
- ./config/mysql:/etc/mysql/conf.d
8-
- docapply-mysql-data:/var/lib/mysql
98
environment:
109
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
1110
- MYSQL_DATABASE=docapply
@@ -19,7 +18,3 @@ services:
1918
interval: 5s
2019
timeout: 10s
2120
retries: 10
22-
23-
volumes:
24-
docapply-mysql-data:
25-
name: docapply-mysql-data

docker/local-setup/services.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,3 @@ services:
99
extends:
1010
file: keycloak.yml
1111
service: keycloak
12-
13-
volumes:
14-
docapply-mysql-data:
15-
name: docapply-mysql-data
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.tum.cit.aet.ai.dto;
22

3+
import jakarta.validation.constraints.NotNull;
34
import java.util.UUID;
45

5-
public record AnalyzeJobDescriptionRequestDTO(UUID jobId, String title, String jobDescriptionEN, String jobDescriptionDE) {}
6+
public record AnalyzeJobDescriptionRequestDTO(@NotNull UUID jobId, String title, String jobDescriptionEN, String jobDescriptionDE) {}

src/main/java/de/tum/cit/aet/ai/dto/BiasedIssueDTO.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package de.tum.cit.aet.ai.dto;
22

3+
import com.fasterxml.jackson.annotation.JsonInclude;
34
import de.tum.cit.aet.ai.domain.BiasedIssue;
45
import de.tum.cit.aet.core.constants.GenderCategory;
56

7+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
68
public record BiasedIssueDTO(String language, String word, GenderCategory type) {
79
public static BiasedIssueDTO from(BiasedIssue issue) {
810
return new BiasedIssueDTO(issue.getLanguage(), issue.getWord(), issue.getType());

src/main/java/de/tum/cit/aet/ai/dto/ComplianceIssueDTO.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package de.tum.cit.aet.ai.dto;
22

3+
import com.fasterxml.jackson.annotation.JsonInclude;
34
import de.tum.cit.aet.ai.constants.ComplianceAction;
45
import de.tum.cit.aet.ai.constants.ComplianceCategory;
56
import de.tum.cit.aet.ai.domain.ComplianceIssue;
67

8+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
79
public record ComplianceIssueDTO(
810
String id,
911
ComplianceCategory category,

src/main/java/de/tum/cit/aet/ai/service/GenderBiasAnalysisService.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
import de.tum.cit.aet.core.constants.GenderCategory;
55
import de.tum.cit.aet.core.service.GenderBiasAnalyzer;
66
import java.util.ArrayList;
7-
import java.util.HashSet;
87
import java.util.List;
9-
import java.util.Set;
108
import lombok.RequiredArgsConstructor;
119
import org.springframework.stereotype.Service;
1210

@@ -19,17 +17,6 @@ public class GenderBiasAnalysisService {
1917

2018
private final GenderBiasAnalyzer analyzer;
2119

22-
/**
23-
* Analyze the given text for gender bias.
24-
*
25-
* @param text the text to analyze
26-
* @param language the language code (e.g., "en" or "de")
27-
* @return a response containing the analysis result and identified biased words
28-
*/
29-
public Set<BiasedIssue> analyzeText(String text, String language) {
30-
return new HashSet<>(analyzeOccurrences(text, language));
31-
}
32-
3320
/**
3421
* Analyze the given text while retaining repeated occurrences for score calculation.
3522
*

src/main/java/de/tum/cit/aet/ai/util/ComplianceScoreCalculator.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package de.tum.cit.aet.ai.util;
22

33
import de.tum.cit.aet.ai.constants.ComplianceCategory;
4+
import de.tum.cit.aet.ai.domain.ComplianceIssue;
45
import de.tum.cit.aet.core.constants.GenderCategory;
6+
import java.util.HashSet;
57
import java.util.List;
68
import java.util.Map;
9+
import java.util.Set;
710
import java.util.function.Function;
811
import java.util.stream.Collectors;
912

@@ -38,21 +41,36 @@ public static int calculateLegalScore(List<ComplianceCategory> categories) {
3841
.stream()
3942
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
4043

41-
if (
42-
counts.getOrDefault(ComplianceCategory.CRITICAL_AGG, 0L) > 0 ||
43-
counts.getOrDefault(ComplianceCategory.DSGVO_MINIMIZATION, 0L) > 0
44-
) {
44+
if (counts.getOrDefault(ComplianceCategory.CRITICAL_AGG, 0L) > 0) {
4545
return 0;
4646
}
4747

48-
double totalCount =
49-
(double) counts.getOrDefault(ComplianceCategory.TRANSPARENCY, 0L) +
50-
(double) counts.getOrDefault(ComplianceCategory.PUBLIC_SECTOR, 0L);
48+
double totalCount = (double) counts.getOrDefault(ComplianceCategory.TRANSPARENCY, 0L);
5149

5250
double score = 100.0 * Math.pow(PENALTY_FACTOR, totalCount);
5351
return (int) Math.max(0, Math.round(score));
5452
}
5553

54+
/**
55+
* Combines the gender inclusivity score with the legal compliance score using
56+
* their geometric mean. Compliance issues that represent the same finding in
57+
* multiple languages are counted only once based on their non-empty identifier.
58+
*
59+
* @param genderScore the gender inclusivity score from 0 to 100
60+
* @param complianceIssues the detected compliance issues across all languages
61+
* @return the combined AI score from 0 to 100
62+
*/
63+
public static int calculateCombinedAiScore(int genderScore, List<ComplianceIssue> complianceIssues) {
64+
Set<String> issueIds = new HashSet<>();
65+
int legalScore = calculateLegalScore(
66+
complianceIssues.stream()
67+
.filter(issue -> issue.getId() == null || issue.getId().isBlank() || issueIds.add(issue.getId()))
68+
.map(ComplianceIssue::getCategory)
69+
.toList()
70+
);
71+
return (int) Math.round(Math.sqrt((double) genderScore * legalScore));
72+
}
73+
5674
/**
5775
* Calculates the combined gender bias score across two languages for consistency.
5876
*

src/main/java/de/tum/cit/aet/job/domain/Job.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public class Job extends AbstractAuditingEntity {
112112
private Set<Application> applications;
113113

114114
// Compliance fields for score calculation
115-
@Column(name = "gender_bias_score")
115+
@Column(name = "ai_score")
116116
private Integer aiScore;
117117

118118
@ElementCollection

src/main/java/de/tum/cit/aet/job/repository/JobRepository.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package de.tum.cit.aet.job.repository;
22

3+
import de.tum.cit.aet.ai.domain.BiasedIssue;
4+
import de.tum.cit.aet.ai.domain.ComplianceIssue;
35
import de.tum.cit.aet.core.repository.DocApplyJpaRepository;
46
import de.tum.cit.aet.job.constants.Campus;
57
import de.tum.cit.aet.job.constants.JobState;
@@ -358,16 +360,47 @@ ORDER BY CONCAT(p.firstName, ' ', p.lastName) ASC
358360
@Query("SELECT DISTINCT j.image.imageId FROM Job j WHERE j.image.imageId IN :imageIds")
359361
Set<UUID> findInUseImageIds(@Param("imageIds") List<UUID> imageIds);
360362

363+
/**
364+
* Loads a job with its supervising professor, research group and image.
365+
* The issue collections are intentionally not part of the entity graph and
366+
* are fetched by their own queries instead,
367+
* since joining both would produce a Cartesian product.
368+
*
369+
* @param jobId the job identifier
370+
* @return the job, if it exists
371+
*/
361372
@EntityGraph(attributePaths = { "supervisingProfessor", "researchGroup", "image" })
362373
@Query("SELECT j FROM Job j WHERE j.jobId = :jobId")
363374
Optional<Job> findByIdWithDetails(@Param("jobId") UUID jobId);
364375

376+
/**
377+
* Loads the compliance issues of a job in a dedicated query. Fetching them together
378+
* with the biased issues would produce a Cartesian product and duplicate list entries.
379+
*
380+
* @param jobId the job identifier
381+
* @return the persisted compliance issues
382+
*/
365383
@Query("SELECT issue FROM Job j JOIN j.complianceIssues issue WHERE j.jobId = :jobId")
366-
List<de.tum.cit.aet.ai.domain.ComplianceIssue> findComplianceIssuesByJobId(@Param("jobId") UUID jobId);
384+
List<ComplianceIssue> findComplianceIssuesByJobId(@Param("jobId") UUID jobId);
367385

386+
/**
387+
* Loads biased issues separately from compliance issues to avoid a Cartesian
388+
* product and retain the set semantics of the persisted collection.
389+
*
390+
* @param jobId the job identifier
391+
* @return the persisted biased issues
392+
*/
368393
@Query("SELECT issue FROM Job j JOIN j.biasedIssues issue WHERE j.jobId = :jobId")
369-
Set<de.tum.cit.aet.ai.domain.BiasedIssue> findBiasedIssuesByJobId(@Param("jobId") UUID jobId);
394+
Set<BiasedIssue> findBiasedIssuesByJobId(@Param("jobId") UUID jobId);
370395

396+
/**
397+
* Loads the job used for an analysis update deliberately without an entity graph:
398+
* the update only touches the score and the issue collections, so eagerly loading
399+
* the professor, research group and image would be wasted work.
400+
*
401+
* @param jobId the job identifier
402+
* @return the job to update, if it exists
403+
*/
371404
@Query("SELECT j FROM Job j WHERE j.jobId = :jobId")
372405
Optional<Job> findByIdForAiUpdate(@Param("jobId") UUID jobId);
373406
}

0 commit comments

Comments
 (0)