Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ede6582
Add alternate state logic for LLM question parts
sjd210 Oct 16, 2025
1bfd46e
Add function to extract attempts by question ID
sjd210 Oct 21, 2025
2a4b1bf
Read marksAwarded value for LLMFreeText responses
sjd210 Oct 21, 2025
983de47
Change question correctness threshold to maxMarks
sjd210 Oct 21, 2025
501149d
Clean up experimental code
sjd210 Oct 21, 2025
c76f5f0
Adapt retrieval function response type as argument
sjd210 Oct 21, 2025
02d68a4
Merge branch 'main' into improvement/count-llm-marks-seperately
sjd210 Oct 22, 2025
7a6e2e3
Migrate database to add mark to question_attempts
sjd210 Oct 22, 2025
92203a6
Convert database migration query to split by dates
sjd210 Oct 22, 2025
9230a55
Update tests to new LLMFreeText correct definition
sjd210 Oct 22, 2025
a02f051
Update postgres functions to process marks field
sjd210 Oct 22, 2025
f2c4fd4
Add marks to test question_attempts table
sjd210 Oct 22, 2025
2700c48
Add marks to lightweight response
sjd210 Oct 22, 2025
0b5809d
Add marks to lightweight responses in tests
sjd210 Oct 22, 2025
248a000
Allow QuestionValidationResponse to derive marks
sjd210 Oct 22, 2025
696ecdf
Also add marks field to quiz_question_attempts
sjd210 Oct 23, 2025
c982579
Remove question ID extraction code
sjd210 Oct 23, 2025
d4e0cd9
Augment game items with new marks field
sjd210 Oct 23, 2025
36a60e7
Update migration script dates split ranges
sjd210 Oct 23, 2025
13a1077
Restructure update_marks function as a scalar
sjd210 Oct 23, 2025
a62e73f
Move game item augmentation to another branch
sjd210 Oct 23, 2025
1874716
Add marks to QuestionValidationResponseDTO
sjd210 Oct 23, 2025
733d777
Correct syntax error
sjd210 Oct 23, 2025
4cd2ea1
Simplify conditional
sjd210 Oct 23, 2025
351d514
Replace marksAwarded with marks in LLM response
sjd210 Oct 23, 2025
b750792
Allow quiz question attempt to register with marks
sjd210 Oct 23, 2025
05b8e3c
Cleanup unnecessary changes
sjd210 Oct 23, 2025
ecd6cf4
Replace placeholder database access code
sjd210 Oct 27, 2025
00db082
Remove unused imports
sjd210 Oct 27, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public GameManager(final GitContentManager contentManager,
* list of question categories (i.e. problem_solving, book) to include in filtered results
* @param boardOwner
* The user that should be marked as the creator of the gameBoard.
* @return a gameboard if possible that satisifies the conditions provided by the parameters. Will return null if no
* @return a gameboard if possible that satisfies the conditions provided by the parameters. Will return null if no
* questions can be provided.
* @throws SegueDatabaseException
* - if there is an error contacting the database.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ public PgQuizQuestionAttemptPersistenceManager(final PostgresSqlDb database, fin
@Override
public void registerQuestionAttempt(Long quizAttemptId, QuestionValidationResponse questionResponse) throws SegueDatabaseException {

String query = "INSERT INTO quiz_question_attempts(quiz_attempt_id, question_id, question_attempt, correct, \"timestamp\")" +
" VALUES (?, ?, ?::text::jsonb, ?, ?);";
String query = "INSERT INTO quiz_question_attempts(quiz_attempt_id, question_id, question_attempt, correct, \"timestamp\", marks)" +
" VALUES (?, ?, ?::text::jsonb, ?, ?, ?);";
try (Connection conn = database.getDatabaseConnection();
PreparedStatement pst = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
) {
Expand All @@ -75,6 +75,12 @@ public void registerQuestionAttempt(Long quizAttemptId, QuestionValidationRespon
}
pst.setTimestamp(5, new java.sql.Timestamp(questionResponse.getDateAttempted().getTime()));

if (questionResponse.getMarks() != null) {
pst.setInt(6, questionResponse.getMarks());
} else {
pst.setInt(6, java.sql.Types.NULL);
}

if (pst.executeUpdate() == 0) {
throw new SegueDatabaseException("Unable to save quiz question attempt.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

@DTOMapping(LLMFreeTextQuestionValidationResponseDTO.class)
public class LLMFreeTextQuestionValidationResponse extends QuestionValidationResponse {
private Integer marksAwarded;
private List<LLMFreeTextMarkSchemeEntry> markBreakdown;

public LLMFreeTextQuestionValidationResponse() {
Expand All @@ -24,10 +23,10 @@ public LLMFreeTextQuestionValidationResponse(final String questionId, final Choi
}

public Integer getMarksAwarded() {
return marksAwarded;
return super.getMarks();
}
public void setMarksAwarded(Integer marksAwarded) {
this.marksAwarded = marksAwarded;
super.setMarks(marksAwarded);
}

public List<LLMFreeTextMarkSchemeEntry> getMarkBreakdown() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class LightweightQuestionValidationResponse {
private String questionId;
private Boolean correct;
private Date dateAttempted;
private Integer marks;

/**
* Default Constructor for mappers.
Expand All @@ -27,12 +28,15 @@ public LightweightQuestionValidationResponse() {
* -
* @param dateAttempted
* -
* @param marks
* -
*/
public LightweightQuestionValidationResponse(final String questionId, final Boolean correct,
final Date dateAttempted) {
final Date dateAttempted, final Integer marks) {
this.questionId = questionId;
this.correct = correct;
this.dateAttempted = dateAttempted;
this.marks = marks;
}

/**
Expand Down Expand Up @@ -92,9 +96,28 @@ public void setDateAttempted(final Date dateAttempted) {
this.dateAttempted = dateAttempted;
}

/**
* Gets the marks.
*
* @return the marks
*/
public Integer getMarks() {
return marks;
}

/**
* Sets the marks.
*
* @param marks
* the marks to set
*/
public void setMarks(final Integer marks) {
this.marks = marks;
}

@Override
public String toString() {
return "QuestionValidationResponse [questionId=" + questionId + ", correct=" + correct +
", dateAttempted=" + dateAttempted + "]";
", dateAttempted=" + dateAttempted + ", marks=" + marks + "]";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,33 @@ public QuestionValidationResponse() {
* -
* @param dateAttempted
* -
* @param marks
* -
*/
public QuestionValidationResponse(final String questionId, final Choice answer, final Boolean correct,
final Content explanation, final Date dateAttempted, final Integer marks) {
super(questionId, correct, dateAttempted, marks);
this.answer = answer;
this.explanation = explanation;
}

/**
* Constructor without specifying marks (instead derived from 'correct')
*
* @param questionId
* -
* @param answer
* -
* @param correct
* -
* @param explanation
* -
* @param dateAttempted
* -
*/
public QuestionValidationResponse(final String questionId, final Choice answer, final Boolean correct,
final Content explanation, final Date dateAttempted) {
super(questionId, correct, dateAttempted);
super(questionId, correct, dateAttempted, (correct != null && correct) ? 1 : 0);
this.answer = answer;
this.explanation = explanation;
}
Expand Down Expand Up @@ -101,7 +124,7 @@ public final void setExplanation(final Content explanation) {
public String toString() {
return "QuestionValidationResponse [questionId=" + super.getQuestionId() + ", answer=" + answer +
", correct=" + super.isCorrect() + ", explanation=" + explanation +
", dateAttempted=" + super.getDateAttempted() + "]";
", dateAttempted=" + super.getDateAttempted() + ", marks=" + super.getMarks() + "]";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ public FormulaValidationResponseDTO() {
* -
* @param dateAttempted
* -
* @param marks
* -
*/
public FormulaValidationResponseDTO(final String questionId, final ChoiceDTO answer,
final ContentDTO explanation, final Boolean correctExact,
final Boolean correctSymbolic, final Boolean correctNumeric,
final Date dateAttempted) {
super(questionId, answer, correctSymbolic || correctNumeric, explanation, dateAttempted);
final Date dateAttempted, final Integer marks) {
super(questionId, answer, correctSymbolic || correctNumeric, explanation, dateAttempted, marks);
this.correctExact = correctExact;
this.correctSymbolic = correctSymbolic;
this.correctNumeric = correctNumeric;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ public ItemValidationResponseDTO() {
* @param itemsCorrect - ordered list of correctness status of each submitted item.
* @param explanation - explanation.
* @param dateAttempted - dateAttempted.
* @param marks - marks
*/
public ItemValidationResponseDTO(final String questionId, final ChoiceDTO answer,
final Boolean correct, final List<Boolean> itemsCorrect,
final ContentDTO explanation, final Date dateAttempted) {
super(questionId, answer, correct, explanation, dateAttempted);
final ContentDTO explanation, final Date dateAttempted, final Integer marks) {
super(questionId, answer, correct, explanation, dateAttempted, marks);
this.itemsCorrect = itemsCorrect;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
import java.util.List;

public class LLMFreeTextQuestionValidationResponseDTO extends QuestionValidationResponseDTO {
private Integer marksAwarded;
private List<LLMFreeTextMarkSchemeEntryDTO> markBreakdown;

public LLMFreeTextQuestionValidationResponseDTO() {
}

public Integer getMarksAwarded() {
return marksAwarded;
return super.getMarks();
}
public void setMarksAwarded(Integer marksAwarded) {
this.marksAwarded = marksAwarded;
super.setMarks(marksAwarded);
}

public List<LLMFreeTextMarkSchemeEntryDTO> getMarkBreakdown() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ public QuantityValidationResponseDTO() {
* -
* @param dateAttempted
* -
* @param marks
* -
*/
public QuantityValidationResponseDTO(final String questionId, final ChoiceDTO answer, final Boolean correct,
final ContentDTO explanation, final Boolean correctValue,
final Boolean correctUnits, final Date dateAttempted) {
super(questionId, answer, correct, explanation, dateAttempted);
final Boolean correctUnits, final Date dateAttempted, final Integer marks) {
super(questionId, answer, correct, explanation, dateAttempted, marks);
this.correctValue = correctValue;
this.correctUnits = correctUnits;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class QuestionValidationResponseDTO {
private Boolean correct;
private ContentDTO explanation;
private Date dateAttempted;
private Integer marks;

/**
* Default Constructor for mappers.
Expand All @@ -51,14 +52,17 @@ public QuestionValidationResponseDTO() {
* -
* @param dateAttempted
* -
* @param marks
* -
*/
public QuestionValidationResponseDTO(final String questionId, final ChoiceDTO answer, final Boolean correct,
final ContentDTO explanation, final Date dateAttempted) {
final ContentDTO explanation, final Date dateAttempted, final Integer marks) {
this.questionId = questionId;
this.answer = answer;
this.correct = correct;
this.explanation = explanation;
this.dateAttempted = dateAttempted;
this.marks = marks;
}

/**
Expand Down Expand Up @@ -156,9 +160,28 @@ public void setDateAttempted(final Date dateAttempted) {
this.dateAttempted = dateAttempted;
}

/**
* Gets the marks.
*
* @return the marks
*/
public Integer getMarks() {
return marks;
}

/**
* Sets the marks.
*
* @param marks
* the marks to set
*/
public void setMarks(final Integer marks) {
this.marks = marks;
}

@Override
public String toString() {
return "QuestionValidationResponseDTO [questionId=" + questionId + ", answer=" + answer + ", correct="
+ correct + ", explanation=" + explanation + ", dateAttempted=" + dateAttempted + "]";
+ correct + ", explanation=" + explanation + ", dateAttempted=" + dateAttempted + ", marks=" + marks + "]";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,13 @@ private int evaluateMarkTotal(final IsaacLLMFreeTextQuestion question, final Map
* @param question the question being marked so that we can return the mark scheme.
* @param answer the user's attempt at the question.
* @param awardedMarks the marks awarded for each field in the mark scheme according to the LLM response.
* @param markTotal the calculated mark value based on which individual marks were awarded
* @return a response to the user's attempt at the question.
*/
private LLMFreeTextQuestionValidationResponse generateQuestionValidationResponse(
final IsaacLLMFreeTextQuestion question, final Choice answer,
final Map<String, Integer> awardedMarks, final int markTotal) {
boolean isConsideredCorrect = markTotal > 0;
boolean isConsideredCorrect = markTotal >= question.getMaxMarks();

// We create a fresh copy of the mark scheme with the full description and the awarded mark values.
List<LLMFreeTextMarkSchemeEntry> markBreakdown = question.getMarkScheme().stream().map(mark -> {
Expand Down
18 changes: 13 additions & 5 deletions src/main/java/uk/ac/cam/cl/dtg/isaac/quiz/PgQuestionAttempts.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ public Map<String, Map<String, List<QuestionValidationResponse>>> getAnonymousQu
public void registerQuestionAttempt(final Long userId, final String questionPageId, final String fullQuestionId,
final QuestionValidationResponse questionAttempt) throws SegueDatabaseException {

String query = "INSERT INTO question_attempts(user_id, page_id, question_id, question_attempt, correct, \"timestamp\")"
+ " VALUES (?, ?, ?, ?::text::jsonb, ?, ?);";
String query = "INSERT INTO question_attempts(user_id, page_id, question_id, question_attempt, correct, \"timestamp\", marks)"
+ " VALUES (?, ?, ?, ?::text::jsonb, ?, ?, ?);";
try (Connection conn = database.getDatabaseConnection();
PreparedStatement pst = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
) {
Expand All @@ -201,8 +201,15 @@ public void registerQuestionAttempt(final Long userId, final String questionPage
} else {
pst.setNull(5, java.sql.Types.NULL);
}

pst.setTimestamp(6, new java.sql.Timestamp(questionAttempt.getDateAttempted().getTime()));

if (questionAttempt.getMarks() != null) {
pst.setInt(7, questionAttempt.getMarks());
} else {
pst.setInt(7, java.sql.Types.NULL);
}

if (pst.executeUpdate() == 0) {
throw new SegueDatabaseException("Unable to save question attempt.");
}
Expand Down Expand Up @@ -260,7 +267,7 @@ public Map<Long, Map<String, Map<String, List<LightweightQuestionValidationRespo
return Collections.emptyMap();
}

String query = "SELECT id, user_id, question_id, correct, timestamp FROM question_attempts"
String query = "SELECT id, user_id, question_id, correct, timestamp, marks FROM question_attempts"
+ " WHERE user_id = ANY(?) ORDER BY \"timestamp\" ASC";

Map<Long, Map<String, Map<String, List<LightweightQuestionValidationResponse>>>> mapToReturn
Expand Down Expand Up @@ -307,7 +314,7 @@ public Map<String, Map<String, List<LightweightQuestionValidationResponse>>> get
= userIds.stream().collect(Collectors.toMap(Function.identity(), k -> Maps.newHashMap()));;

try (Connection conn = database.getDatabaseConnection()) {
String query = "SELECT id, user_id, question_id, correct, timestamp FROM question_attempts"
String query = "SELECT id, user_id, question_id, correct, timestamp, marks FROM question_attempts"
+ " WHERE user_id = ANY(?) AND page_id = ANY(?)"
+ " ORDER BY \"timestamp\" ASC";

Expand Down Expand Up @@ -438,9 +445,10 @@ public Map<Date, Long> getQuestionAttemptCountForUserByDateRange(final Date from
private LightweightQuestionValidationResponse resultsToLightweightValidationResponse(final ResultSet results) throws SQLException {
LightweightQuestionValidationResponse partialQuestionAttempt = new QuestionValidationResponse();

partialQuestionAttempt.setCorrect(results.getBoolean("correct"));
partialQuestionAttempt.setCorrect(results.getInt("marks") > 0);
partialQuestionAttempt.setQuestionId(results.getString("question_id"));
partialQuestionAttempt.setDateAttempted(results.getTimestamp("timestamp"));
partialQuestionAttempt.setMarks(results.getInt("marks"));

return partialQuestionAttempt;
}
Expand Down
6 changes: 4 additions & 2 deletions src/main/resources/db_scripts/create_anonymous_database.sql
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ CREATE TABLE anonymous.question_attempts AS
question_id,
question_attempt,
correct,
timestamp
timestamp,
marks
FROM public.question_attempts;

CREATE TABLE anonymous.user_streak_freezes AS
Expand Down Expand Up @@ -200,7 +201,8 @@ CREATE TABLE anonymous.quiz_question_attempts AS
question_id,
question_attempt,
correct,
timestamp
timestamp,
marks
FROM public.quiz_question_attempts;

-- Logged events:
Expand Down
Loading
Loading