Skip to content

Commit 1a03cae

Browse files
-Move StoryBookCreateFromEPubController method to a new Util class (Closes elimu-ai#1825)
1 parent e65a7d4 commit 1a03cae

File tree

5 files changed

+165
-40
lines changed

5 files changed

+165
-40
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ai.elimu.util;
2+
3+
public class AppConstants {
4+
5+
public static class READING_LEVEL_CONSTANTS {
6+
public static final String CHAPTER_COUNT_KEY = "chapter_count";
7+
public static final String PARAGRAPH_COUNT_KEY = "paragraph_count";
8+
public static final String WORD_COUNT_KEY = "word_count";
9+
public static final String LEVEL = "LEVEL";
10+
}
11+
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package ai.elimu.util.ml;
2+
3+
import ai.elimu.model.v2.enums.ReadingLevel;
4+
import org.pmml4s.model.Model;
5+
6+
import java.util.Arrays;
7+
import java.util.Map;
8+
9+
import static ai.elimu.util.AppConstants.READING_LEVEL_CONSTANTS.*;
10+
11+
public class ReadingLevelUtil {
12+
13+
public static ReadingLevel predictReadingLevel(
14+
int chapterCount,
15+
int paragraphCount,
16+
int wordCount,
17+
String modelFilePath
18+
) {
19+
20+
Model model = Model.fromFile(modelFilePath);
21+
Map<String, Double> features = Map.of(
22+
CHAPTER_COUNT_KEY, (double) chapterCount,
23+
PARAGRAPH_COUNT_KEY, (double) paragraphCount,
24+
WORD_COUNT_KEY, (double) wordCount
25+
);
26+
27+
Object[] valuesMap = Arrays.stream(model.inputNames())
28+
.map(features::get)
29+
.toArray();
30+
31+
Object[] results = model.predict(valuesMap);
32+
33+
Object result = results[0];
34+
Double resultAsDouble = (Double) result;
35+
int resultAsInteger = resultAsDouble.intValue();
36+
37+
String readingLevelAsString = LEVEL + resultAsInteger;
38+
return ReadingLevel.valueOf(readingLevelAsString);
39+
40+
}
41+
}

src/main/java/ai/elimu/web/content/storybook/StoryBookCreateFromEPubController.java

+12-40
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import ai.elimu.util.epub.EPubImageExtractionHelper;
2424
import ai.elimu.util.epub.EPubMetadataExtractionHelper;
2525
import ai.elimu.util.epub.EPubParagraphExtractionHelper;
26+
import ai.elimu.util.ml.ReadingLevelUtil;
2627
import ai.elimu.web.context.EnvironmentContextLoaderListener;
2728
import org.apache.commons.io.FileUtils;
2829
import org.apache.commons.io.IOUtils;
@@ -52,11 +53,7 @@
5253
import java.io.InputStream;
5354
import java.net.URI;
5455
import java.net.URL;
55-
import java.util.ArrayList;
56-
import java.util.Arrays;
57-
import java.util.Calendar;
58-
import java.util.List;
59-
import java.util.Map;
56+
import java.util.*;
6057
import java.util.zip.ZipEntry;
6158
import java.util.zip.ZipInputStream;
6259

@@ -542,44 +539,19 @@ private void storeImageContributionEvent(Image image, HttpSession session, HttpS
542539
}
543540

544541
private ReadingLevel predictReadingLevel(int chapterCount, int paragraphCount, int wordCount) {
545-
logger.info("predictReadingLevel");
546542

547543
// Load the machine learning model (https://github.com/elimu-ai/ml-storybook-reading-level)
548-
String modelFilePath = getClass().getResource("step2_2_model.pmml").getFile();
549-
logger.info("modelFilePath: " + modelFilePath);
550-
org.pmml4s.model.Model model = org.pmml4s.model.Model.fromFile(modelFilePath);
551-
logger.info("model: " + model);
552-
553-
// Prepare values (features) to pass to the model
554-
Map<String, Double> values = Map.of(
555-
"chapter_count", Double.valueOf(chapterCount),
556-
"paragraph_count", Double.valueOf(paragraphCount),
557-
"word_count", Double.valueOf(wordCount)
544+
545+
String modelFilePath = Objects.requireNonNull(getClass().getResource("step2_2_model.pmml")).getFile();
546+
547+
logger.info(
548+
"Predicting reading level for chapter: {}, paragraph: {}, word: {}, modelPath: {} ",
549+
chapterCount, paragraphCount, wordCount, modelFilePath
558550
);
559-
logger.info("values: " + values);
560-
561-
// Make prediction
562-
logger.info("Arrays.toString(model.inputNames()): " + Arrays.toString(model.inputNames()));
563-
Object[] valuesMap = Arrays.stream(model.inputNames())
564-
.map(values::get)
565-
.toArray();
566-
logger.info("valuesMap: " + valuesMap);
567-
Object[] results = model.predict(valuesMap);
568-
logger.info("results: " + results);
569-
logger.info("Arrays.toString(results): " + Arrays.toString(results));
570-
Object result = results[0];
571-
logger.info("result: " + result);
572-
logger.info("result.getClass().getSimpleName(): " + result.getClass().getSimpleName());
573-
Double resultAsDouble = (Double) result;
574-
logger.info("resultAsDouble: " + resultAsDouble);
575-
Integer resultAsInteger = resultAsDouble.intValue();
576-
logger.info("resultAsInteger: " + resultAsInteger);
577-
578-
// Convert from number to ReadingLevel enum (e.g. "LEVEL2")
579-
String readingLevelAsString = "LEVEL" + resultAsInteger;
580-
logger.info("readingLevelAsString: " + readingLevelAsString);
581-
ReadingLevel readingLevel = ReadingLevel.valueOf(readingLevelAsString);
582-
logger.info("readingLevel: " + readingLevel);
551+
552+
ReadingLevel readingLevel = ReadingLevelUtil.predictReadingLevel(chapterCount, paragraphCount, wordCount, modelFilePath);
553+
logger.info("Predicted reading level: {}", readingLevel);
554+
583555
return readingLevel;
584556
}
585557
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package ai.elimu.util.ml;
2+
3+
import ai.elimu.model.v2.enums.ReadingLevel;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.io.IOException;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertThrows;
10+
11+
public class ReadingLevelUtilTest {
12+
13+
@Test
14+
public void testPredictReadingLevel_Level1() {
15+
16+
String modelFilePath = "src/test/resources/ai/elimu/util/reading_level/model1.pmml";
17+
int chapterCount = 5;
18+
int paragraphCount = 20;
19+
int wordCount = 100;
20+
21+
ReadingLevel result = ReadingLevelUtil.predictReadingLevel(chapterCount, paragraphCount, wordCount, modelFilePath);
22+
assertEquals(ReadingLevel.LEVEL1, result, "Expected ReadingLevel to be LEVEL1, but got: " + result);
23+
24+
}
25+
26+
@Test
27+
public void testPredictReadingLevel_Level2() {
28+
29+
String modelFilePath = "src/test/resources/ai/elimu/util/reading_level/model1.pmml";
30+
int chapterCount = 12;
31+
int paragraphCount = 22;
32+
int wordCount = 250;
33+
34+
ReadingLevel result = ReadingLevelUtil.predictReadingLevel(chapterCount, paragraphCount, wordCount, modelFilePath);
35+
assertEquals(ReadingLevel.LEVEL2, result, "Expected ReadingLevel to be LEVEL2, but got: " + result);
36+
37+
}
38+
39+
@Test
40+
public void testPredictReadingLevel_Level3() {
41+
42+
String modelFilePath = "src/test/resources/ai/elimu/util/reading_level/model1.pmml";
43+
int chapterCount = 12;
44+
int paragraphCount = 25;
45+
int wordCount = 350;
46+
47+
ReadingLevel result = ReadingLevelUtil.predictReadingLevel(chapterCount, paragraphCount, wordCount, modelFilePath);
48+
assertEquals(ReadingLevel.LEVEL3, result, "Expected ReadingLevel to be LEVEL3, but got: " + result);
49+
50+
}
51+
52+
@Test
53+
public void testPredictReadingLevel_InvalidModelFile() {
54+
55+
assertThrows(IOException.class, () -> {
56+
ReadingLevelUtil.predictReadingLevel(1, 1, 1, "invalidPath");
57+
}, "Expected IOException when loading an invalid model file path");
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<PMML version="4.4" xmlns="http://www.dmg.org/PMML-4_4">
3+
<Header>
4+
<Application name="Decision Tree Regressor" version="1.0"/>
5+
</Header>
6+
<DataDictionary numberOfFields="4">
7+
<DataField name="chapter_count" optype="continuous" dataType="integer"/>
8+
<DataField name="paragraph_count" optype="continuous" dataType="integer"/>
9+
<DataField name="word_count" optype="continuous" dataType="integer"/>
10+
<DataField name="reading_level" optype="continuous" dataType="double"/>
11+
</DataDictionary>
12+
<TreeModel functionName="regression" algorithmName="DecisionTree" missingValueStrategy="none">
13+
<MiningSchema>
14+
<MiningField name="chapter_count"/>
15+
<MiningField name="paragraph_count"/>
16+
<MiningField name="word_count"/>
17+
<MiningField name="reading_level" usageType="target"/>
18+
</MiningSchema>
19+
<Node score="1.0">
20+
<True/>
21+
<Node score="1.0">
22+
<SimplePredicate field="chapter_count" operator="lessThan" value="10"/>
23+
<Node score="1.0">
24+
<SimplePredicate field="paragraph_count" operator="lessOrEqual" value="20"/>
25+
</Node>
26+
<Node score="2.0">
27+
<SimplePredicate field="paragraph_count" operator="greaterThan" value="20"/>
28+
</Node>
29+
</Node>
30+
<Node score="2.0">
31+
<SimplePredicate field="chapter_count" operator="greaterOrEqual" value="10"/>
32+
<Node score="2.0">
33+
<SimplePredicate field="word_count" operator="lessThan" value="300"/>
34+
</Node>
35+
<Node score="3.0">
36+
<SimplePredicate field="word_count" operator="greaterOrEqual" value="300"/>
37+
</Node>
38+
</Node>
39+
</Node>
40+
</TreeModel>
41+
</PMML>

0 commit comments

Comments
 (0)