Repository navigation
Gamification service implementation for ai tutor #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
MP-DG
merged 24 commits into
main
from
gamification-service-implementation-for-ai-tutor
Dec 17, 2025
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
6afa760
Listen for UserHexadPlayerTypeSetEvent, persist the received player-t…
MP-DG 6f821b5
added skill level to prompt, stores skill level, no longer uses sql
MP-DG 58d5596
added proactive feedback. Currently accessable through query in the f…
MP-DG d572852
bugfix filtering for sources
MP-DG 6057ad7
tutor saves conversation context
MP-DG 2606977
saves students code recieved in student-code-submitted event
MP-DG b0d0cc5
implemented ai tutor can answer questions about submitted code
MP-DG 531b355
fixed proactive feedback for code assignment
MP-DG 3d9009b
updated meitrex-common to full release
MP-DG 8e57d1d
added tests for SubscriptionController
MP-DG 8b9eef1
added tests for ConversationHistoryService
MP-DG 42dd4d8
added tests for UserPlayerTypeService
MP-DG dc01749
added tests for UserSkillLevelService
MP-DG e27c568
added tests for UserPlyerTypeEntity
MP-DG 57b2744
added tests for persistance/mapper/*
MP-DG a2f1ff4
added more testcases for ProactiveFeedbackService
MP-DG 0e6e76d
updated tests for TutorService
MP-DG 9a01a6d
added tests for TutorController
MP-DG 8a981bb
updated test for UserSkillLevelMapper
MP-DG 77e1757
added tests for HintService
MP-DG 0ff0aa2
added missing @Transactional annotation
MP-DG 400abed
fixed sonar qube issue with unboxing
MP-DG b21cad6
fixed sonar qube issue
MP-DG 6a245ce
changed percentage.doubleValue to double (percentage)
MP-DG File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,4 +36,8 @@ out/ | |
| ### VS Code ### | ||
| .vscode/ | ||
|
|
||
| # Auto-generated API documentation | ||
| **/api.md | ||
| **/combined.graphql | ||
|
|
||
| node_modules/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
178 changes: 178 additions & 0 deletions
178
...in/java/de/unistuttgart/iste/meitrex/tutor_service/controller/SubscriptionController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| package de.unistuttgart.iste.meitrex.tutor_service.controller; | ||
|
|
||
| import de.unistuttgart.iste.meitrex.common.event.ContentProgressedEvent; | ||
| import de.unistuttgart.iste.meitrex.common.event.HexadPlayerType; | ||
| import de.unistuttgart.iste.meitrex.common.event.StudentCodeSubmittedEvent; | ||
| import de.unistuttgart.iste.meitrex.common.event.UserHexadPlayerTypeSetEvent; | ||
| import de.unistuttgart.iste.meitrex.common.event.skilllevels.UserSkillLevelChangedEvent; | ||
| import de.unistuttgart.iste.meitrex.tutor_service.service.ProactiveFeedbackService; | ||
| import de.unistuttgart.iste.meitrex.tutor_service.service.StudentCodeSubmissionService; | ||
| import de.unistuttgart.iste.meitrex.tutor_service.service.UserPlayerTypeService; | ||
| import de.unistuttgart.iste.meitrex.tutor_service.service.UserSkillLevelService; | ||
| import io.dapr.Topic; | ||
| import io.dapr.client.domain.CloudEvent; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestHeader; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import reactor.core.publisher.Mono; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Controller for subscribing to Dapr pub/sub events. | ||
| */ | ||
| @Slf4j | ||
| @RestController | ||
| @RequiredArgsConstructor | ||
| public class SubscriptionController { | ||
|
|
||
| private final UserPlayerTypeService userPlayerTypeService; | ||
| private final UserSkillLevelService userSkillLevelService; | ||
| private final ProactiveFeedbackService proactiveFeedbackService; | ||
| private final StudentCodeSubmissionService studentCodeSubmissionService; | ||
|
|
||
| /** | ||
| * Handles the user-hexad-player-type-set event. | ||
| * Saves the user's player type information when received. | ||
| * | ||
| * @param cloudEvent the cloud event containing the user hexad player type data | ||
| * @param headers request headers from Dapr | ||
| * @return Mono<Void> for reactive processing | ||
| */ | ||
| @Topic(name = "user-hexad-player-type-set", pubsubName = "meitrex") | ||
| @PostMapping(path = "/user-hexad-player-type-set-pubsub") | ||
| public Mono<Void> onUserHexadPlayerTypeSetEvent(@RequestBody CloudEvent<UserHexadPlayerTypeSetEvent> cloudEvent, | ||
| @RequestHeader Map<String, String> headers) { | ||
| return Mono.fromRunnable(() -> { | ||
| UserHexadPlayerTypeSetEvent event = cloudEvent.getData(); | ||
|
|
||
| log.info("Received UserHexadPlayerTypeSetEvent for user: {}, primaryType: {}", | ||
| event.getUserId(), | ||
| event.getPrimaryPlayerType()); | ||
|
|
||
| // Convert Float playerTypePercentages to Double | ||
| Map<HexadPlayerType, Double> playerTypePercentages = new HashMap<>(); | ||
| event.getPlayerTypePercentages().forEach((type, percentage) -> { | ||
| if (percentage != null) { | ||
| playerTypePercentages.put(type, (double) percentage); | ||
| } | ||
| }); | ||
|
|
||
| // Save the player type information | ||
| userPlayerTypeService.saveUserPlayerType( | ||
| event.getUserId(), | ||
| event.getPrimaryPlayerType(), | ||
| playerTypePercentages | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Handles the user-skill-level-changed event. | ||
| * Saves the user's skill level information when received. | ||
| * | ||
| * @param cloudEvent the cloud event containing the user skill level data | ||
| * @param headers request headers from Dapr | ||
| * @return Mono<Void> for reactive processing | ||
| */ | ||
| @Topic(name = "user-skill-level-changed", pubsubName = "meitrex") | ||
| @PostMapping(path = "/user-skill-level-changed-pubsub") | ||
| public Mono<Void> onUserSkillLevelChangedEvent(@RequestBody CloudEvent<UserSkillLevelChangedEvent> cloudEvent, | ||
| @RequestHeader Map<String, String> headers) { | ||
| return Mono.fromRunnable(() -> { | ||
| UserSkillLevelChangedEvent event = cloudEvent.getData(); | ||
|
|
||
| userSkillLevelService.saveUserSkillLevel( | ||
| event.getUserId(), | ||
| event.getSkillId(), | ||
| event.getNewValue() | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Handles the content-progressed event. | ||
| * Generates proactive tutor feedback when a user completes an assignment. | ||
| * Currently supports proactive feedback for assignments and quizzes. | ||
| * | ||
| * @param cloudEvent the cloud event containing the content progressed data | ||
| * @param headers request headers from Dapr | ||
| * @return Mono<Void> for reactive processing | ||
| */ | ||
| @Topic(name = "content-progressed", pubsubName = "meitrex") | ||
| @PostMapping(path = "/content-progressed-pubsub") | ||
| public Mono<Void> onContentProgressedEvent(@RequestBody CloudEvent<ContentProgressedEvent> cloudEvent, | ||
| @RequestHeader Map<String, String> headers) { | ||
| return Mono.fromRunnable(() -> { | ||
| ContentProgressedEvent event = cloudEvent.getData(); | ||
|
|
||
| if (event == null) { | ||
| log.warn("Received ContentProgressedEvent with null data"); | ||
| return; | ||
| } | ||
|
|
||
| log.info("Received ContentProgressedEvent for user: {}, content: {}, type: {}", | ||
| event.getUserId(), | ||
| event.getContentId(), | ||
| event.getContentType()); | ||
|
|
||
| if (event.getContentType() == ContentProgressedEvent.ContentType.ASSIGNMENT || | ||
| event.getContentType() == ContentProgressedEvent.ContentType.QUIZ) { | ||
| try { | ||
| proactiveFeedbackService.generateFeedback(event); | ||
| } catch (Exception e) { | ||
| log.error("Failed to generate feedback for user {} on content {}: {}", | ||
| event.getUserId(), event.getContentId(), e.getMessage(), e); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Handles the student-code-submitted event. | ||
| * Saves the student's code submission when received. | ||
| * Only keeps the latest submission per student per assignment. | ||
| * | ||
| * @param cloudEvent the cloud event containing the student code submission data | ||
| * @param headers request headers from Dapr | ||
| * @return Mono<Void> for reactive processing | ||
| */ | ||
| @Topic(name = "student-code-submitted", pubsubName = "meitrex") | ||
| @PostMapping(path = "/student-code-submitted-pubsub") | ||
| public Mono<Void> onStudentCodeSubmittedEvent(@RequestBody CloudEvent<StudentCodeSubmittedEvent> cloudEvent, | ||
| @RequestHeader Map<String, String> headers) { | ||
| return Mono.fromRunnable(() -> { | ||
| StudentCodeSubmittedEvent event = cloudEvent.getData(); | ||
|
|
||
| if (event == null) { | ||
| log.warn("Received StudentCodeSubmittedEvent with null data"); | ||
| return; | ||
| } | ||
|
|
||
| log.info("Received StudentCodeSubmittedEvent for student: {}, assignment: {}, commit: {}", | ||
| event.getStudentId(), | ||
| event.getAssignmentId(), | ||
| event.getCommitSha()); | ||
|
|
||
| try { | ||
| studentCodeSubmissionService.saveCodeSubmission( | ||
| event.getStudentId(), | ||
| event.getAssignmentId(), | ||
| event.getCourseId(), | ||
| event.getRepositoryUrl(), | ||
| event.getCommitSha(), | ||
| event.getCommitTimestamp(), | ||
| event.getFiles(), | ||
| event.getBranch() | ||
| ); | ||
| } catch (Exception e) { | ||
| log.error("Error processing StudentCodeSubmittedEvent for student {} on assignment {}: {}", | ||
| event.getStudentId(), event.getAssignmentId(), e.getMessage(), e); | ||
| } | ||
| }); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
...unistuttgart/iste/meitrex/tutor_service/persistence/entity/ConversationHistoryEntity.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package de.unistuttgart.iste.meitrex.tutor_service.persistence.entity; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.*; | ||
|
|
||
| import java.time.OffsetDateTime; | ||
| import java.util.UUID; | ||
|
|
||
| /** | ||
| * Entity representing a conversation exchange between user and tutor. | ||
| * Stores question-answer pairs for context in subsequent interactions. | ||
| */ | ||
| @Entity | ||
| @Table(name = "conversation_history", indexes = { | ||
| @Index(name = "idx_conversation_history_user_id", columnList = "user_id"), | ||
| @Index(name = "idx_conversation_history_course_id", columnList = "course_id"), | ||
| @Index(name = "idx_conversation_history_timestamp", columnList = "timestamp"), | ||
| @Index(name = "idx_conversation_history_user_course", columnList = "user_id,course_id") | ||
| }) | ||
| @Data | ||
| @Builder | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class ConversationHistoryEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.AUTO) | ||
| @Column(name = "id", nullable = false) | ||
| private UUID id; | ||
|
|
||
| @Column(name = "user_id", nullable = false) | ||
| private UUID userId; | ||
|
|
||
| @Column(name = "course_id", nullable = false) | ||
| private UUID courseId; | ||
|
|
||
| @Column(name = "user_message", nullable = false, columnDefinition = "TEXT") | ||
| private String userMessage; | ||
|
|
||
| @Column(name = "tutor_response", nullable = false, columnDefinition = "TEXT") | ||
| private String tutorResponse; | ||
|
|
||
| @Column(name = "timestamp", nullable = false) | ||
| private OffsetDateTime timestamp; | ||
| } |
46 changes: 46 additions & 0 deletions
46
...e/unistuttgart/iste/meitrex/tutor_service/persistence/entity/ProactiveFeedbackEntity.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package de.unistuttgart.iste.meitrex.tutor_service.persistence.entity; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.*; | ||
|
|
||
| import java.time.OffsetDateTime; | ||
| import java.util.UUID; | ||
|
|
||
| /** | ||
| * Entity representing feedback generated for a user after completing an assignment. | ||
| */ | ||
| @Entity | ||
| @Table(name = "proactive_feedback", indexes = { | ||
| @Index(name = "idx_proactive_feedback_user_id", columnList = "user_id"), | ||
| @Index(name = "idx_proactive_feedback_assessment_id", columnList = "assessment_id"), | ||
| @Index(name = "idx_proactive_feedback_user_assessment", columnList = "user_id,assessment_id") | ||
| }) | ||
| @Data | ||
| @Builder | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class ProactiveFeedbackEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.AUTO) | ||
| @Column(name = "id", nullable = false) | ||
| private UUID id; | ||
|
|
||
| @Column(name = "user_id", nullable = false) | ||
| private UUID userId; | ||
|
|
||
| @Column(name = "assessment_id", nullable = false) | ||
| private UUID assessmentId; | ||
|
|
||
| @Column(name = "feedback_text", nullable = false, columnDefinition = "TEXT") | ||
| private String feedbackText; | ||
|
|
||
| @Column(name = "correctness", nullable = false) | ||
| private Double correctness; | ||
|
|
||
| @Column(name = "success", nullable = false) | ||
| private Boolean success; | ||
|
|
||
| @Column(name = "created_at", nullable = false) | ||
| private OffsetDateTime createdAt; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Soll das auskommentiert sein?