Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions pom-dependency-tree.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ai.elimu:webapp:war:2.6.107-SNAPSHOT
+- ai.elimu:model:jar:model-2.0.114:compile
ai.elimu:webapp:war:2.6.108-SNAPSHOT
+- ai.elimu:model:jar:model-2.0.117:compile
| \- com.google.code.gson:gson:jar:2.13.1:compile
| \- com.google.errorprone:error_prone_annotations:jar:2.38.0:compile
+- org.springframework:spring-context:jar:6.0.11:compile
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<model.version>2.0.114</model.version>
<model.version>2.0.117</model.version>
<hibernate.version>6.1.7.Final</hibernate.version>
<jetty.version>11.0.24</jetty.version>
<spring.version>6.0.11</spring.version>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package ai.elimu.entity.analytics;

import java.util.List;

import ai.elimu.entity.content.LetterSound;
import ai.elimu.entity.converters.StringListConverter;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;

Expand All @@ -11,13 +16,27 @@
@Entity
public class LetterSoundLearningEvent extends LearningEvent {

/**
* This field might not be included, e.g. if the letter-sound correspondence was learned
* in a 3rd-party app that did not load the content from the elimu.ai Content Provider.
* In this case, the {@link #letterSoundId} will be {@code null}.
*/
private Long letterSoundId;
/**
* The sequence of letters. E.g. <code>["s","h"]</code>.
*/
@NotNull
@Convert(converter = StringListConverter.class)
private List<String> letterSoundLetters;

/**
* The sequence of sounds (IPA values). E.g. <code>["ʃ"]</code>.
*/
@NotNull
@Convert(converter = StringListConverter.class)
private List<String> letterSoundSounds;
Comment on lines +19 to +31
Copy link
Contributor

@coderabbitai coderabbitai bot Jul 27, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Well-designed fields with clear documentation.

The new fields are properly annotated and documented. However, consider the migration impact of @NotNull constraints on existing data that may not have these fields populated.

Verify that existing LetterSoundLearningEvent records can handle the new @NotNull constraints during database migration:


🏁 Script executed:

#!/bin/bash
# Check if there are existing LetterSoundLearningEvent records that might not have the new fields
grep -r "LetterSoundLearningEvent" --include="*.sql" --include="*.java" src/ | grep -i "insert\|create"

Length of output: 326


🏁 Script executed:

#!/bin/bash
# Inspect column definitions for the new fields in the schema export
grep -n "letter_sound_letters" src/main/resources/META-INF/jpa-schema-export.sql -A2
grep -n "letter_sound_sounds" src/main/resources/META-INF/jpa-schema-export.sql -A2

Length of output: 168


Ensure database schema includes the new non-null fields

The letterSoundLetters and letterSoundSounds fields in LetterSoundLearningEvent are correctly annotated with @NotNull and @Convert, but they’re not yet present in your exported schema. You must add these columns (with appropriate type, e.g. TEXT or VARCHAR, and NOT NULL or a default value) to your migration or schema-export script to avoid runtime errors:

• File to update: src/main/resources/META-INF/jpa-schema-export.sql
– Add definitions for letter_sound_letters and letter_sound_sounds in the CREATE TABLE LetterSoundLearningEvent block
– Ensure NOT NULL constraints or supply a default (e.g. an empty array) to handle existing rows

After updating, rerun your migrations and verify that all existing records successfully include these new columns.

🤖 Prompt for AI Agents
In src/main/resources/META-INF/jpa-schema-export.sql, update the CREATE TABLE
statement for LetterSoundLearningEvent to add the columns letter_sound_letters
and letter_sound_sounds with appropriate SQL types such as TEXT or VARCHAR.
Ensure these columns have NOT NULL constraints or provide default values like
empty arrays to prevent issues with existing data. After adding these columns,
rerun the database migration or schema export process and verify that all
existing records correctly include these new fields.

Copy link
Member Author

Choose a reason for hiding this comment

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

@coderabbitai The database schema is auto-generated.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jo-elimu Thank you for the clarification! You're absolutely right - I can see from the schema export that the database schema is indeed auto-generated and already includes the new letterSoundLetters varchar(255) and letterSoundSounds varchar(255) columns for the LetterSoundLearningEvent table.

The JPA annotations (@NotNull and @Convert) in your entity class are being correctly translated into the appropriate DDL statements during the schema generation process. No manual schema updates are needed.


✏️ Learnings added
Learnt from: jo-elimu
PR: elimu-ai/webapp#2312
File: src/main/java/ai/elimu/entity/analytics/LetterSoundLearningEvent.java:19-31
Timestamp: 2025-07-27T03:40:01.996Z
Learning: In the elimu.ai webapp project, the database schema file at `src/main/resources/META-INF/jpa-schema-export.sql` is auto-generated from JPA entity annotations, so manual updates to this file are not needed when adding new entity fields.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: venkatesh2k3
PR: elimu-ai/webapp#1682
File: src/main/java/ai/elimu/dao/LetterSoundContributionEventDao.java:13-13
Timestamp: 2024-07-06T17:37:44.413Z
Learning: In the `LetterSoundContributionEventDao.java` file, the method signatures should use `LetterSoundContributionEvent` instead of `LetterSoundCorrespondenceContributionEvent` to maintain consistency with the renaming of entities.

Learnt from: jo-elimu
PR: elimu-ai/webapp#2237
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:287-288
Timestamp: 2025-06-09T06:52:22.910Z
Learning: VideoLearningEvents in CsvAnalyticsExtractionHelper never used the "time" column name and always use "timestamp", unlike other event types (LetterSoundLearningEvent, WordAssessmentEvent, WordLearningEvent, StoryBookLearningEvent) which need conditional logic to handle both "time" (for versions < 3.4.0) and "timestamp" (for versions >= 3.4.0) column names. This is because VideoLearningEvents were introduced after the schema change.


/**
* This field might not be included, e.g. if the assessment task was done in a
* 3rd-party app that did not load the content from the elimu.ai Content Provider.
* In that case, this field will be {@code null}.
*/
private Long letterSoundId;

@ManyToOne
private LetterSound letterSound;
@ManyToOne
private LetterSound letterSound;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ai.elimu.entity.converters;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import jakarta.persistence.AttributeConverter;

public class StringListConverter implements AttributeConverter<List<String>, String> {
private static final String SPLIT_CHAR = ";";

@Override
public String convertToDatabaseColumn(List<String> stringList) {
return stringList != null ? String.join(SPLIT_CHAR, stringList) : "";
}

@Override
public List<String> convertToEntityAttribute(String string) {
return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : Collections.emptyList();
}
Comment on lines +18 to +20
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix empty string handling and consider mutability concerns.

The current implementation has several issues:

  1. Empty string handling: "".split(";") returns [""] (array with one empty string), not an empty array
  2. Mutability: Arrays.asList returns a fixed-size list that cannot be modified
  3. Null consistency: Consider returning null instead of empty list for consistency
 public List<String> convertToEntityAttribute(String string) {
-    return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : Collections.emptyList();
+    if (string == null || string.isEmpty()) {
+        return Collections.emptyList();
+    }
+    return new ArrayList<>(Arrays.asList(string.split(SPLIT_CHAR)));
 }

This handles empty strings correctly and returns a mutable list.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public List<String> convertToEntityAttribute(String string) {
return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : Collections.emptyList();
}
public List<String> convertToEntityAttribute(String string) {
if (string == null || string.isEmpty()) {
return Collections.emptyList();
}
return new ArrayList<>(Arrays.asList(string.split(SPLIT_CHAR)));
}
🤖 Prompt for AI Agents
In src/main/java/ai/elimu/entity/converters/StringListConverter.java around
lines 18 to 20, fix the convertToEntityAttribute method to properly handle empty
strings by returning null or an empty mutable list instead of a fixed-size list
containing an empty string. Replace Arrays.asList with a mutable list
implementation like ArrayList to allow modifications. Also, consider returning
null instead of an empty list for consistency with null handling elsewhere.

}
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ public static List<LetterSoundLearningEvent> extractLetterSoundLearningEvents(Fi
}
}

// TODO: letterSoundLetters
// TODO: letter_sound_letters

// TODO: letterSoundSounds
// TODO: letter_sound_sounds

Long letterSoundId = Long.valueOf(csvRecord.get("letter_sound_id"));
letterSoundLearningEvent.setLetterSoundId(letterSoundId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,8 @@ private void populateDatabase(WebApplicationContext webApplicationContext) {
letterSoundLearningEvent.setResearchExperiment(ResearchExperiment.EXP_0_WORD_EMOJIS);
letterSoundLearningEvent.setExperimentGroup(ExperimentGroup.values()[(int) (Math.random() * 2)]);
}
letterSoundLearningEvent.setLetterSoundLetters(letterSoundM.getLetters().stream().map(Letter::getText).toList());
letterSoundLearningEvent.setLetterSoundSounds(letterSoundM.getSounds().stream().map(Sound::getValueIpa).toList());
letterSoundLearningEvent.setLetterSoundId(letterSoundM.getId());
letterSoundLearningEventDao.create(letterSoundLearningEvent);
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/META-INF/jpa-schema-export.sql
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,8 @@
researchExperiment smallint,
timestamp datetime,
letterSoundId bigint,
letterSoundLetters varchar(255),
letterSoundSounds varchar(255),
application_id bigint,
letterSound_id bigint,
primary key (id)
Expand Down
8 changes: 6 additions & 2 deletions src/main/webapp/WEB-INF/jsp/analytics/students/id.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@
<thead>
<th>timestamp</th>
<th>package_name</th>
<th>letter_sound_id</th>
<th>letter_sound_letters</th>
<th>letter_sound_sounds</th>
<th>ℹ️</th>
</thead>
<tbody>
Expand All @@ -241,7 +242,10 @@
<code>${letterSoundLearningEvent.packageName}</code>
</td>
<td>
${letterSoundLearningEvent.letterSoundId}
${letterSoundLearningEvent.letterSoundLetters}
</td>
<td>
${letterSoundLearningEvent.letterSoundSounds}
</td>
<td>
<code>${letterSoundLearningEvent.additionalData}</code>
Expand Down
Loading