Skip to content

Commit

Permalink
Move StoryBookCreateFromEPubController method to a new Util class (#1919
Browse files Browse the repository at this point in the history
)
  • Loading branch information
nya-elimu authored Oct 29, 2024
2 parents 81eb6af + 4db44a8 commit 55112e6
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 40 deletions.
32 changes: 32 additions & 0 deletions src/main/java/ai/elimu/util/ReadingLevelConstants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ai.elimu.util;

public class ReadingLevelConstants {

public static class READING_LEVEL_CONSTANTS {

/**
* Key for the number of chapters in a book
*/
public static final String CHAPTER_COUNT_KEY = "chapter_count";

/**
* Key for the number of paragraphs in a book
*/
public static final String PARAGRAPH_COUNT_KEY = "paragraph_count";

/**
* Key for the total word count in a book
*/
public static final String WORD_COUNT_KEY = "word_count";

/**
* Key for the predicted reading level output
*/
public static final String READING_LEVEL_KEY = "LEVEL";

/**
* Reading level file pmml path key
*/
public static final String READING_LEVEL_MODEL_FILE_PATH_KEY = "src/main/resources/ai/elimu/web/content/storybook/step2_2_model.pmml";
}
}
64 changes: 64 additions & 0 deletions src/main/java/ai/elimu/util/ml/ReadingLevelUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package ai.elimu.util.ml;

import ai.elimu.model.v2.enums.ReadingLevel;
import org.pmml4s.model.Model;

import java.util.Arrays;
import java.util.Map;

import static ai.elimu.util.ReadingLevelConstants.READING_LEVEL_CONSTANTS.*;

public class ReadingLevelUtil {

/**
* Predicts the reading level based on chapter count, paragraph count, and word count using a machine learning model.
*
* <p>This method loads a pre-trained machine learning model and predicts the reading level by passing the
* given chapter count, paragraph count, and word count as input features. The model returns a numeric
* prediction, which is then converted into a corresponding {@link ReadingLevel} enum.</p>
*
* @param chapterCount The number of chapters in the text. Must be an integer value.
* @param paragraphCount The number of paragraphs in the text. Must be an integer value.
* @param wordCount The number of words in the text. Must be an integer value.
*
* @return The predicted {@link ReadingLevel} based on the input features.
*
* <p>Example usage:</p>
* <pre>
* {@code
* int chapterCount = 10;
* int paragraphCount = 50;
* int wordCount = 300;
* ReadingLevel readingLevel = PredictionUtils.predictReadingLevel(chapterCount, paragraphCount, wordCount);
* System.out.println("Predicted Reading Level: " + readingLevel);
* }
* </pre>
*/
public static ReadingLevel predictReadingLevel(
int chapterCount,
int paragraphCount,
int wordCount
) {

Model model = Model.fromFile(READING_LEVEL_MODEL_FILE_PATH_KEY);
Map<String, Double> features = Map.of(
CHAPTER_COUNT_KEY, (double) chapterCount,
PARAGRAPH_COUNT_KEY, (double) paragraphCount,
WORD_COUNT_KEY, (double) wordCount
);

Object[] valuesMap = Arrays.stream(model.inputNames())
.map(features::get)
.toArray();

Object[] results = model.predict(valuesMap);

Object result = results[0];
Double resultAsDouble = (Double) result;
int resultAsInteger = resultAsDouble.intValue();

String readingLevelAsString = READING_LEVEL_KEY + resultAsInteger;
return ReadingLevel.valueOf(readingLevelAsString);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import ai.elimu.util.epub.EPubImageExtractionHelper;
import ai.elimu.util.epub.EPubMetadataExtractionHelper;
import ai.elimu.util.epub.EPubParagraphExtractionHelper;
import ai.elimu.util.ml.ReadingLevelUtil;
import ai.elimu.web.context.EnvironmentContextLoaderListener;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
Expand Down Expand Up @@ -52,11 +53,7 @@
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

Expand Down Expand Up @@ -542,44 +539,17 @@ private void storeImageContributionEvent(Image image, HttpSession session, HttpS
}

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

// Load the machine learning model (https://github.com/elimu-ai/ml-storybook-reading-level)
String modelFilePath = getClass().getResource("step2_2_model.pmml").getFile();
logger.info("modelFilePath: " + modelFilePath);
org.pmml4s.model.Model model = org.pmml4s.model.Model.fromFile(modelFilePath);
logger.info("model: " + model);

// Prepare values (features) to pass to the model
Map<String, Double> values = Map.of(
"chapter_count", Double.valueOf(chapterCount),
"paragraph_count", Double.valueOf(paragraphCount),
"word_count", Double.valueOf(wordCount)

logger.info(
"Predicting reading level for chapter: {}, paragraph: {}, word: {} ",
chapterCount, paragraphCount, wordCount
);
logger.info("values: " + values);

// Make prediction
logger.info("Arrays.toString(model.inputNames()): " + Arrays.toString(model.inputNames()));
Object[] valuesMap = Arrays.stream(model.inputNames())
.map(values::get)
.toArray();
logger.info("valuesMap: " + valuesMap);
Object[] results = model.predict(valuesMap);
logger.info("results: " + results);
logger.info("Arrays.toString(results): " + Arrays.toString(results));
Object result = results[0];
logger.info("result: " + result);
logger.info("result.getClass().getSimpleName(): " + result.getClass().getSimpleName());
Double resultAsDouble = (Double) result;
logger.info("resultAsDouble: " + resultAsDouble);
Integer resultAsInteger = resultAsDouble.intValue();
logger.info("resultAsInteger: " + resultAsInteger);

// Convert from number to ReadingLevel enum (e.g. "LEVEL2")
String readingLevelAsString = "LEVEL" + resultAsInteger;
logger.info("readingLevelAsString: " + readingLevelAsString);
ReadingLevel readingLevel = ReadingLevel.valueOf(readingLevelAsString);
logger.info("readingLevel: " + readingLevel);

ReadingLevel readingLevel = ReadingLevelUtil.predictReadingLevel(chapterCount, paragraphCount, wordCount);
logger.info("Predicted reading level: {}", readingLevel);

return readingLevel;
}
}
57 changes: 57 additions & 0 deletions src/test/java/ai/elimu/util/ml/ReadingLevelUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package ai.elimu.util.ml;

import ai.elimu.model.v2.enums.ReadingLevel;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class ReadingLevelUtilTest {

@Test
public void testPredictReadingLevel_Level1() {

int chapterCount = 12;
int paragraphCount = 18;
int wordCount = 150;

ReadingLevel result = ReadingLevelUtil.predictReadingLevel(chapterCount, paragraphCount, wordCount);
assertEquals(ReadingLevel.LEVEL1, result, "Expected ReadingLevel to be LEVEL1, but got: " + result);

}

@Test
public void testPredictReadingLevel_Level2() {

int chapterCount = 20;
int paragraphCount = 30;
int wordCount = 300;

ReadingLevel result = ReadingLevelUtil.predictReadingLevel(chapterCount, paragraphCount, wordCount);
assertEquals(ReadingLevel.LEVEL2, result, "Expected ReadingLevel to be LEVEL2, but got: " + result);

}

@Test
public void testPredictReadingLevel_Level3() {

int chapterCount = 25;
int paragraphCount = 40;
int wordCount = 350;

ReadingLevel result = ReadingLevelUtil.predictReadingLevel(chapterCount, paragraphCount, wordCount);
assertEquals(ReadingLevel.LEVEL3, result, "Expected ReadingLevel to be LEVEL3, but got: " + result);

}

@Test
public void testPredictReadingLevel_Level4() {

int chapterCount = 15;
int paragraphCount = 45;
int wordCount = 559;

ReadingLevel result = ReadingLevelUtil.predictReadingLevel(chapterCount, paragraphCount, wordCount);
assertEquals(ReadingLevel.LEVEL4, result, "Expected ReadingLevel to be LEVEL4, but got: " + result);

}
}

0 comments on commit 55112e6

Please sign in to comment.