Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion pom-dependency-tree.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ai.elimu:webapp:war:2.6.56-SNAPSHOT
ai.elimu:webapp:war:2.6.58-SNAPSHOT
+- ai.elimu:model:jar:model-2.0.111:compile
| \- com.google.code.gson:gson:jar:2.13.1:compile
| \- com.google.errorprone:error_prone_annotations:jar:2.38.0:compile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public LetterSoundAssessmentEvent read(Calendar timestamp, String androidId, Str
.setParameter("packageName", packageName)
.getSingleResult();
} catch (NoResultException e) {
logger.info("LetterSoundAssessmentEvent (" + timestamp.getTimeInMillis() + ", " + androidId + ", \"" + packageName + "\") was not found");
logger.info("LetterSoundAssessmentEvent (" + timestamp.getTime() + ", \"" + androidId + "\", \"" + packageName + "\") was not found");
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public LetterSoundLearningEvent read(Calendar timestamp, String androidId, Strin
.setParameter("packageName", packageName)
.getSingleResult();
} catch (NoResultException e) {
logger.info("LetterSoundLearningEvent (" + timestamp.getTimeInMillis() + ", " + androidId + ", \"" + packageName + "\") was not found");
logger.info("LetterSoundLearningEvent (" + timestamp.getTime() + ", \"" + androidId + "\", \"" + packageName + "\") was not found");
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public NumberLearningEvent read(Calendar timestamp, String androidId, String pac
.setParameter("packageName", packageName)
.getSingleResult();
} catch (NoResultException e) {
logger.info("NumberLearningEvent (" + timestamp.getTimeInMillis() + ", \"" + androidId + "\", \"" + packageName + "\") was not found");
logger.info("NumberLearningEvent (" + timestamp.getTime() + ", \"" + androidId + "\", \"" + packageName + "\") was not found");
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public StoryBookLearningEvent read(Calendar timestamp, String androidId, String
.setParameter("packageName", packageName)
.getSingleResult();
} catch (NoResultException e) {
logger.info("StoryBookLearningEvent (" + timestamp.getTimeInMillis() + ", " + androidId + ", \"" + packageName + "\") was not found");
logger.info("StoryBookLearningEvent (" + timestamp.getTime() + ", \"" + androidId + "\", \"" + packageName + "\") was not found");
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public VideoLearningEvent read(Calendar timestamp, String androidId, String pack
.setParameter("packageName", packageName)
.getSingleResult();
} catch (NoResultException e) {
logger.info("VideoLearningEvent (" + timestamp.getTimeInMillis() + ", " + androidId + ", " + packageName + ") was not found");
logger.info("VideoLearningEvent (" + timestamp.getTime() + ", \"" + androidId + "\", " + packageName + ") was not found");
Comment thread
jo-elimu marked this conversation as resolved.
Outdated
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public WordAssessmentEvent read(Calendar timestamp, String androidId, String pac
.setParameter("packageName", packageName)
.getSingleResult();
} catch (NoResultException e) {
logger.info("WordAssessmentEvent (" + timestamp.getTimeInMillis() + ", " + androidId + ", \"" + packageName + "\") was not found");
logger.info("WordAssessmentEvent (" + timestamp.getTime() + ", \"" + androidId + "\", \"" + packageName + "\") was not found");
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public WordLearningEvent read(Calendar timestamp, String androidId, String packa
.setParameter("packageName", packageName)
.getSingleResult();
} catch (NoResultException e) {
logger.info("WordLearningEvent (" + timestamp.getTimeInMillis() + ", " + androidId + ", \"" + packageName + "\") was not found");
logger.info("WordLearningEvent (" + timestamp.getTime() + ", \"" + androidId + "\", \"" + packageName + "\") was not found");
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public static List<LetterSoundLearningEvent> extractLetterSoundLearningEvents(Fi
// https://github.com/elimu-ai/analytics/releases/tag/3.4.0
timestampColumnName = "timestamp";
}
long timestampInMillis = Long.valueOf(csvRecord.get(timestampColumnName));
long timestampInMillis = Long.valueOf(csvRecord.get(timestampColumnName).substring(0, 10)) * 1_000;
Calendar timestamp = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
timestamp.setTimeInMillis(timestampInMillis);
letterSoundLearningEvent.setTimestamp(timestamp);
Comment on lines +127 to 130
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against short or malformed timestamp strings & remove duplication

Each extractor now does

long ts = Long.valueOf(csvRecord.get(col).substring(0, 10)) * 1_000;

Issues:

  1. If the field has < 10 characters (e.g. empty or corrupted) substring throws StringIndexOutOfBoundsException.
  2. The same parsing logic is duplicated six times.

Proposed refactor (utility method + bounds check):

+    private static Calendar toUtcCalendarFromEpochSeconds(String raw) {
+        if (raw == null || raw.length() < 10) {
+            throw new IllegalArgumentException("Invalid epoch string: " + raw);
+        }
+        long millis = Long.parseLong(raw.substring(0, 10)) * 1_000L;
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        cal.setTimeInMillis(millis);
+        return cal;
+    }

Then replace every block with:

- long timestampInMillis = Long.valueOf(csvRecord.get(col).substring(0, 10)) * 1_000;
- Calendar timestamp = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
- timestamp.setTimeInMillis(timestampInMillis);
+ Calendar timestamp = toUtcCalendarFromEpochSeconds(csvRecord.get(col));

This removes repetition and makes failures explicit.

Also applies to: 174-177, 240-243, 306-309, 369-372, 430-433

🤖 Prompt for AI Agents
In src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java around
lines 127 to 130, the code extracts a timestamp substring without checking its
length, risking StringIndexOutOfBoundsException if the string is shorter than 10
characters, and this parsing logic is duplicated multiple times. To fix this,
create a utility method that safely checks the length of the timestamp string
before extracting the substring and converting it to milliseconds, throwing a
clear exception if invalid. Replace all occurrences of the direct substring
parsing with calls to this utility method to remove duplication and improve
error handling. Apply the same refactor to lines 174-177, 240-243, 306-309,
369-372, and 430-433.

Expand Down Expand Up @@ -171,7 +171,7 @@ public static List<NumberLearningEvent> extractNumberLearningEvents(File csvFile

NumberLearningEvent numberLearningEvent = new NumberLearningEvent();

long timestampInMillis = Long.valueOf(csvRecord.get("timestamp"));
long timestampInMillis = Long.valueOf(csvRecord.get("timestamp").substring(0, 10)) * 1_000;
Calendar timestamp = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
timestamp.setTimeInMillis(timestampInMillis);
numberLearningEvent.setTimestamp(timestamp);
Expand Down Expand Up @@ -237,7 +237,7 @@ public static List<WordAssessmentEvent> extractWordAssessmentEvents(File csvFile
// https://github.com/elimu-ai/analytics/releases/tag/3.4.0
timestampColumnName = "timestamp";
}
long timestampInMillis = Long.valueOf(csvRecord.get(timestampColumnName));
long timestampInMillis = Long.valueOf(csvRecord.get(timestampColumnName).substring(0, 10)) * 1_000;
Calendar timestamp = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
timestamp.setTimeInMillis(timestampInMillis);
wordAssessmentEvent.setTimestamp(timestamp);
Expand Down Expand Up @@ -303,7 +303,7 @@ public static List<WordLearningEvent> extractWordLearningEvents(File csvFile) {
// https://github.com/elimu-ai/analytics/releases/tag/3.4.0
timestampColumnName = "timestamp";
}
long timestampInMillis = Long.valueOf(csvRecord.get(timestampColumnName));
long timestampInMillis = Long.valueOf(csvRecord.get(timestampColumnName).substring(0, 10)) * 1_000;
Calendar timestamp = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
timestamp.setTimeInMillis(timestampInMillis);
wordLearningEvent.setTimestamp(timestamp);
Expand Down Expand Up @@ -366,7 +366,7 @@ public static List<StoryBookLearningEvent> extractStoryBookLearningEvents(File c
// https://github.com/elimu-ai/analytics/releases/tag/3.4.0
timestampColumnName = "timestamp";
}
long timestampInMillis = Long.valueOf(csvRecord.get(timestampColumnName));
long timestampInMillis = Long.valueOf(csvRecord.get(timestampColumnName).substring(0, 10)) * 1_000;
Calendar timestamp = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
timestamp.setTimeInMillis(timestampInMillis);
storyBookLearningEvent.setTimestamp(timestamp);
Expand Down Expand Up @@ -427,7 +427,7 @@ public static List<VideoLearningEvent> extractVideoLearningEvents(File csvFile)

VideoLearningEvent videoLearningEvent = new VideoLearningEvent();

long timestampInMillis = Long.valueOf(csvRecord.get("timestamp"));
long timestampInMillis = Long.valueOf(csvRecord.get("timestamp").substring(0, 10)) * 1_000;
Calendar timestamp = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
timestamp.setTimeInMillis(timestampInMillis);
videoLearningEvent.setTimestamp(timestamp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public void handleRequest(

csvPrinter.printRecord(
letterSoundAssessmentEvent.getId(),
letterSoundAssessmentEvent.getTimestamp().getTimeInMillis(),
letterSoundAssessmentEvent.getTimestamp().getTimeInMillis() / 1_000,
letterSoundAssessmentEvent.getPackageName(),
letterSoundAssessmentEvent.getMasteryScore(),
letterSoundAssessmentEvent.getTimeSpentMs(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public void handleRequest(

csvPrinter.printRecord(
letterSoundLearningEvent.getId(),
letterSoundLearningEvent.getTimestamp().getTimeInMillis(),
letterSoundLearningEvent.getTimestamp().getTimeInMillis() / 1_000,
letterSoundLearningEvent.getPackageName(),
// letterSoundLearningEvent.getLetterSoundLetters(),
// letterSoundLearningEvent.getLetterSoundSounds(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void handleRequest(

csvPrinter.printRecord(
numberLearningEvent.getId(),
numberLearningEvent.getTimestamp().getTimeInMillis(),
numberLearningEvent.getTimestamp().getTimeInMillis() / 1_000,
numberLearningEvent.getPackageName(),
numberLearningEvent.getAdditionalData(),
numberLearningEvent.getNumberValue(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void handleRequest(

csvPrinter.printRecord(
storyBookLearningEvent.getId(),
storyBookLearningEvent.getTimestamp().getTimeInMillis(),
storyBookLearningEvent.getTimestamp().getTimeInMillis() / 1_000,
storyBookLearningEvent.getPackageName(),
storyBookLearningEvent.getStoryBookTitle(),
storyBookLearningEvent.getStoryBookId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public void handleRequest(

csvPrinter.printRecord(
videoLearningEvent.getId(),
videoLearningEvent.getTimestamp().getTimeInMillis(),
videoLearningEvent.getTimestamp().getTimeInMillis() / 1_000,
videoLearningEvent.getPackageName(),
videoLearningEvent.getVideoTitle(),
videoLearningEvent.getVideoId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void handleRequest(

csvPrinter.printRecord(
wordAssessmentEvent.getId(),
wordAssessmentEvent.getTimestamp().getTimeInMillis(),
wordAssessmentEvent.getTimestamp().getTimeInMillis() / 1_000,
wordAssessmentEvent.getPackageName(),
wordAssessmentEvent.getMasteryScore(),
wordAssessmentEvent.getTimeSpentMs(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public void handleRequest(

csvPrinter.printRecord(
wordLearningEvent.getId(),
wordLearningEvent.getTimestamp().getTimeInMillis(),
wordLearningEvent.getTimestamp().getTimeInMillis() / 1_000,
wordLearningEvent.getPackageName(),
wordLearningEvent.getWordText(),
(wordLearningEvent.getWord() == null) ? null : wordLearningEvent.getWord().getId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void testExtractLetterSoundLearningEvents_v3002014() throws IOException {
assertEquals(6, letterSoundLearningEvents.size());

LetterSoundLearningEvent letterSoundLearningEvent = letterSoundLearningEvents.get(0);
assertEquals(1746952025058L, letterSoundLearningEvent.getTimestamp().getTimeInMillis());
assertEquals(1746952025000L, letterSoundLearningEvent.getTimestamp().getTimeInMillis());
assertEquals("5b7c682a12ecbe2e", letterSoundLearningEvent.getAndroidId());
assertEquals("ai.elimu.herufi.debug", letterSoundLearningEvent.getPackageName());
assertEquals(300, letterSoundLearningEvent.getLetterSoundId());
Expand All @@ -54,7 +54,7 @@ public void testExtractWordAssessmentEvents_v3001030() throws IOException {
assertEquals(1, wordAssessmentEvents.size());

WordAssessmentEvent wordAssessmentEvent = wordAssessmentEvents.get(0);
assertEquals(1742402392907L, wordAssessmentEvent.getTimestamp().getTimeInMillis());
assertEquals(1742402392000L, wordAssessmentEvent.getTimestamp().getTimeInMillis());
assertEquals("1bb5b718814899b5", wordAssessmentEvent.getAndroidId());
assertEquals("ai.elimu.kukariri.debug", wordAssessmentEvent.getPackageName());
assertEquals("aso", wordAssessmentEvent.getWordText());
Expand All @@ -77,7 +77,7 @@ public void testExtractVideoLearningEvents_v3001018() throws IOException {
assertEquals(6, videoLearningEvents.size());

VideoLearningEvent videoLearningEvent = videoLearningEvents.get(0);
assertEquals(1728486312687L, videoLearningEvent.getTimestamp().getTimeInMillis());
assertEquals(1728486312000L, videoLearningEvent.getTimestamp().getTimeInMillis());
assertEquals("e387e38700000001", videoLearningEvent.getAndroidId());
assertEquals("ai.elimu.filamu", videoLearningEvent.getPackageName());
assertEquals("akili and me - the rectangle song", videoLearningEvent.getVideoTitle());
Expand All @@ -99,7 +99,7 @@ public void testExtractWordLearningEvents_v3001030() throws IOException {
assertEquals(143, wordLearningEvents.size());

WordLearningEvent wordLearningEvent = wordLearningEvents.get(0);
assertEquals(1742293958238L, wordLearningEvent.getTimestamp().getTimeInMillis());
assertEquals(1742293958000L, wordLearningEvent.getTimestamp().getTimeInMillis());
assertEquals("5b7c682a12ecbe2e", wordLearningEvent.getAndroidId());
assertEquals("ai.elimu.vitabu.debug", wordLearningEvent.getPackageName());
assertEquals("ฉัน", wordLearningEvent.getWordText());
Expand All @@ -121,7 +121,7 @@ public void testExtractStoryBookLearningEvents_v3001030() throws IOException {
assertEquals(8, storyBookLearningEvents.size());

StoryBookLearningEvent storyBookLearningEvent = storyBookLearningEvents.get(0);
assertEquals(1742293901485L, storyBookLearningEvent.getTimestamp().getTimeInMillis());
assertEquals(1742293901000L, storyBookLearningEvent.getTimestamp().getTimeInMillis());
assertEquals("5b7c682a12ecbe2e", storyBookLearningEvent.getAndroidId());
assertEquals("ai.elimu.vitabu.debug", storyBookLearningEvent.getPackageName());
assertEquals("", storyBookLearningEvent.getStoryBookTitle());
Expand All @@ -143,7 +143,7 @@ public void testExtractStoryBookLearningEvents_v3002014() throws IOException {
assertEquals(2, storyBookLearningEvents.size());

StoryBookLearningEvent storyBookLearningEvent = storyBookLearningEvents.get(0);
assertEquals(1748252197301L, storyBookLearningEvent.getTimestamp().getTimeInMillis());
assertEquals(1748252197000L, storyBookLearningEvent.getTimestamp().getTimeInMillis());
assertEquals("5b7c682a12ecbe2e", storyBookLearningEvent.getAndroidId());
assertEquals("ai.elimu.vitabu.debug", storyBookLearningEvent.getPackageName());
assertEquals("", storyBookLearningEvent.getStoryBookTitle());
Expand All @@ -165,7 +165,7 @@ public void testExtractStoryBookLearningEvents_v3003000() throws IOException {
assertEquals(2, storyBookLearningEvents.size());

StoryBookLearningEvent storyBookLearningEvent = storyBookLearningEvents.get(0);
assertEquals(1748252197301L, storyBookLearningEvent.getTimestamp().getTimeInMillis());
assertEquals(1748252197000L, storyBookLearningEvent.getTimestamp().getTimeInMillis());
assertEquals("5b7c682a12ecbe2e", storyBookLearningEvent.getAndroidId());
assertEquals("ai.elimu.vitabu.debug", storyBookLearningEvent.getPackageName());
assertEquals("กลโกงเจ้าจิ้งจอก", storyBookLearningEvent.getStoryBookTitle());
Expand Down