diff options
Diffstat (limited to 'src/main/java/com/p4square/grow/tools')
3 files changed, 670 insertions, 0 deletions
diff --git a/src/main/java/com/p4square/grow/tools/AssessmentStats.java b/src/main/java/com/p4square/grow/tools/AssessmentStats.java new file mode 100644 index 0000000..ca83411 --- /dev/null +++ b/src/main/java/com/p4square/grow/tools/AssessmentStats.java @@ -0,0 +1,218 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package com.p4square.grow.tools; + + +import java.util.Map; +import java.util.HashMap; +import java.util.Queue; +import java.util.List; +import java.util.LinkedList; +import java.io.IOException; + +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.p4square.grow.model.Answer; +import com.p4square.grow.model.Question; +import com.p4square.grow.model.RecordedAnswer; +import com.p4square.grow.model.Score; +import com.p4square.grow.provider.Provider; +import com.p4square.grow.provider.JsonEncodedProvider; + +/** + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class AssessmentStats { + public static void main(String... args) throws Exception { + if (args.length == 0) { + System.out.println("Usage: AssessmentStats directory firstQuestionId"); + System.exit(1); + } + + Map<String, Question> questions; + questions = loadQuestions(args[0], args[1]); + + // Find the highest possible score + List<AnswerPath> scores = findHighestFromId(questions, args[1]); + + // Print Results + System.out.printf("Found %d different paths.\n", scores.size()); + int i = 0; + for (AnswerPath path : scores) { + Score s = path.mScore; + System.out.printf("Path %d: %f points, %d questions. Score: %f (%s)\n", + i++, s.getSum(), s.getCount(), s.getScore(), s.toString()); + System.out.println(" " + path.mPath); + System.out.println(" " + path.mScores); + } + } + + private static Map<String, Question> loadQuestions(String baseDir, String firstId) throws IOException { + FileQuestionProvider provider = new FileQuestionProvider(baseDir); + + // Questions to find... + Queue<String> queue = new LinkedList<>(); + queue.offer(firstId); + + Map<String, Question> questions = new HashMap<>(); + + + while (!queue.isEmpty()) { + Question q = provider.get(queue.poll()); + questions.put(q.getId(), q); + + if (q.getNextQuestion() != null) { + queue.offer(q.getNextQuestion()); + + } + + for (Answer a : q.getAnswers().values()) { + if (a.getNextQuestion() != null) { + queue.offer(a.getNextQuestion()); + } + } + + // Quick Sanity check + if (q.getPreviousQuestion() != null) { + if (questions.get(q.getPreviousQuestion()) == null) { + throw new IllegalStateException("Haven't seen previous question??"); + } + } + } + + return questions; + } + + private static List<AnswerPath> findHighestFromId(Map<String, Question> questions, String id) { + List<AnswerPath> scores = new LinkedList<>(); + doFindHighestFromId(questions, id, scores, new AnswerPath()); + return scores; + } + + private static void doFindHighestFromId(Map<String, Question> questions, String id, List<AnswerPath> scores, AnswerPath path) { + if (id == null) { + // End of the road! Save the score and return. + scores.add(path); + return; + } + + Question q = questions.get(id); + + // Find the best answer following this path and find other paths. + Score maxScore = path.mScore; + double max = 0; + + int answerCount = 1; + for (Map.Entry<String, Answer> entry : q.getAnswers().entrySet()) { + Answer a = entry.getValue(); + RecordedAnswer userAnswer = new RecordedAnswer(); + + if (q.getType() == Question.QuestionType.SLIDER) { + // Special Case + userAnswer.setAnswerId(String.valueOf((float) answerCount / q.getAnswers().size())); + + } else { + userAnswer.setAnswerId(entry.getKey()); + } + + Score tempScore = new Score(path.mScore); // Always start with the initial score. + boolean endOfRoad = !q.scoreAnswer(tempScore, userAnswer); + double thisScore = tempScore.getSum() - path.mScore.getSum(); + + if (endOfRoad) { + // End of Road is a fork too. Record and pick another answer. + AnswerPath fork = new AnswerPath(path); + fork.update(id, tempScore); + scores.add(fork); + + } else if (a.getNextQuestion() != null) { + // Found a new path, follow it. + // Remember to count this answer in the score. + AnswerPath fork = new AnswerPath(path); + fork.update(id, tempScore); + doFindHighestFromId(questions, a.getNextQuestion(), scores, fork); + + } else if (thisScore > max) { + // Found a higher option that isn't a new path. + maxScore = tempScore; + max = thisScore; + } + + answerCount++; + } + + path.update(id, maxScore); + doFindHighestFromId(questions, q.getNextQuestion(), scores, path); + } + + private static class FileQuestionProvider extends JsonEncodedProvider<Question> implements Provider<String, Question> { + private String mBaseDir; + + public FileQuestionProvider(String directory) { + super(Question.class); + mBaseDir = directory; + } + + @Override + public Question get(String key) throws IOException { + Path qfile = FileSystems.getDefault().getPath(mBaseDir, key + ".json"); + byte[] blob = Files.readAllBytes(qfile); + return decode(new String(blob)); + } + + @Override + public void put(String key, Question obj) throws IOException { + throw new UnsupportedOperationException("Not Implemented"); + } + } + + private static class AnswerPath { + String mPath; + String mScores; + Score mScore; + + public AnswerPath() { + mPath = null; + mScores = null; + mScore = new Score(); + } + + public AnswerPath(AnswerPath other) { + mPath = other.mPath; + mScores = other.mScores; + mScore = other.mScore; + } + + public void update(String questionId, Score newScore) { + String value; + + if (mScore.getCount() == newScore.getCount()) { + value = "n/a"; + + } else { + double delta = newScore.getSum() - mScore.getSum(); + if (delta < 0) { + value = "TRUMP"; + } else { + value = String.valueOf(delta); + } + } + + if (mPath == null) { + mPath = questionId; + mScores = value; + + } else { + mPath += ", " + questionId; + mScores += " + " + value; + } + + mScore = newScore; + } + } +} diff --git a/src/main/java/com/p4square/grow/tools/AttributeBackfillTool.java b/src/main/java/com/p4square/grow/tools/AttributeBackfillTool.java new file mode 100644 index 0000000..d7fd2ff --- /dev/null +++ b/src/main/java/com/p4square/grow/tools/AttributeBackfillTool.java @@ -0,0 +1,268 @@ +/* + * Copyright 2014 Jesse Morgan + */ + +package com.p4square.grow.tools; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.restlet.Client; +import org.restlet.Context; +import org.restlet.data.Protocol; + +import com.p4square.f1oauth.Attribute; +import com.p4square.f1oauth.F1API; +import com.p4square.f1oauth.F1Access; +import com.p4square.f1oauth.F1Exception; +import com.p4square.restlet.oauth.OAuthUser; + +import com.p4square.grow.backend.dynamo.DynamoDatabase; +import com.p4square.grow.backend.dynamo.DynamoKey; + +import com.p4square.grow.config.Config; + +import com.p4square.grow.model.Chapter; +import com.p4square.grow.model.Playlist; +import com.p4square.grow.model.TrainingRecord; +import com.p4square.grow.model.VideoRecord; +import com.p4square.grow.provider.JsonEncodedProvider; + +/** + * This utility is used to backfill F1 Attributes from the GROW database into F1. + * + * This tool currently reads from Dynamo directly. It should probably access the + * backend or use the {@link com.p4square.grow.backend.GrowData} abstraction instead. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class AttributeBackfillTool { + + private static Config mConfig; + private static F1API mF1API; + private static DynamoDatabase mDatabase; + + public static void usage() { + System.out.println("java com.p4square.grow.tools.AttributeBackfillTool <command>...\n"); + System.out.println("Commands:"); + System.out.println("\t--domain <domain> Set config domain"); + System.out.println("\t--dev Set config domain to dev"); + System.out.println("\t--config <file> Merge in config file"); + System.out.println("\t--assessments Backfill All Assessments"); + System.out.println("\t--training Backfill All Training Records"); + } + + public static void main(String... args) { + if (args.length == 0) { + usage(); + System.exit(1); + } + + mConfig = new Config(); + + try { + mConfig.updateConfig(AttributeTool.class.getResourceAsStream("/grow.properties")); + + int offset = 0; + while (offset < args.length) { + if ("--domain".equals(args[offset])) { + mConfig.setDomain(args[offset + 1]); + mF1API = null; + mDatabase = null; + offset += 2; + + } else if ("--dev".equals(args[offset])) { + mConfig.setDomain("dev"); + mF1API = null; + mDatabase = null; + offset += 1; + + } else if ("--config".equals(args[offset])) { + mConfig.updateConfig(args[offset + 1]); + mF1API = null; + mDatabase = null; + offset += 2; + + } else if ("--assessments".equals(args[offset])) { + offset = assessments(args, ++offset); + + } else if ("--training".equals(args[offset])) { + offset = training(args, ++offset); + + } else { + throw new IllegalArgumentException("Unknown command " + args[offset]); + } + } + } catch (Exception e) { + e.printStackTrace(); + System.exit(2); + } + } + + private static F1API getF1API() throws Exception { + if (mF1API == null) { + Context context = new Context(); + Client client = new Client(context, Arrays.asList(Protocol.HTTP, Protocol.HTTPS)); + context.setClientDispatcher(client); + + F1Access f1Access = new F1Access(context, + mConfig.getString("f1ConsumerKey"), + mConfig.getString("f1ConsumerSecret"), + mConfig.getString("f1BaseUrl"), + mConfig.getString("f1ChurchCode"), + F1Access.UserType.WEBLINK); + + // Gather Username and Password + String username = System.console().readLine("F1 Username: "); + char[] password = System.console().readPassword("F1 Password: "); + + OAuthUser user = f1Access.getAccessToken(username, new String(password)); + Arrays.fill(password, ' '); // Lost cause, but I'll still try. + + mF1API = f1Access.getAuthenticatedApi(user); + } + + return mF1API; + } + + private static DynamoDatabase getDatabase() { + if (mDatabase == null) { + mDatabase = new DynamoDatabase(mConfig); + } + + return mDatabase; + } + + private static int assessments(String[] args, int offset) throws Exception { + final F1API f1 = getF1API(); + final DynamoDatabase db = getDatabase(); + + DynamoKey key = DynamoKey.newKey("assessments", null); + + while (key != null) { + Map<DynamoKey, Map<String, String>> rows = db.getAll(key); + + key = null; + + for (Map.Entry<DynamoKey, Map<String, String>> row : rows.entrySet()) { + key = row.getKey(); + + String userId = key.getHashKey(); + + String summaryString = row.getValue().get("summary"); + if (summaryString == null || summaryString.length() == 0) { + System.out.printf("%s assessment incomplete\n", userId); + continue; + } + + try { + Map summary = JsonEncodedProvider.MAPPER.readValue(summaryString, Map.class); + + String result = (String) summary.get("result"); + if (result == null) { + System.out.printf("%s assessment incomplete\n", userId); + continue; + } + + String attributeName = "Assessment Complete - " + result; + + // Check if the user already has the attribute. + List<Attribute> attributes = f1.getAttribute(userId, attributeName); + + if (attributes.size() == 0) { + Attribute attribute = new Attribute(attributeName); + attribute.setStartDate(new Date()); + attribute.setComment(summaryString); + + if (f1.addAttribute(userId, attribute)) { + System.out.printf("%s attribute added\n", userId); + } else { + System.out.printf("%s failed to add attribute\n", userId); + } + } else { + System.out.printf("%s already has attribute\n", userId); + } + } catch (Exception e) { + System.out.printf("%s exception: %s\n", userId, e.getMessage()); + } + } + } + + return offset; + } + + private static int training(String[] args, int offset) throws Exception { + final F1API f1 = getF1API(); + final DynamoDatabase db = getDatabase(); + + DynamoKey key = DynamoKey.newKey("training", null); + + while (key != null) { + Map<DynamoKey, Map<String, String>> rows = db.getAll(key); + + key = null; + + for (Map.Entry<DynamoKey, Map<String, String>> row : rows.entrySet()) { + key = row.getKey(); + + String userId = key.getHashKey(); + + String valueString = row.getValue().get("value"); + if (valueString == null || valueString.length() == 0) { + System.out.printf("%s empty training record\n", userId); + continue; + } + + try { + TrainingRecord record = + JsonEncodedProvider.MAPPER.readValue(valueString, TrainingRecord.class); + Playlist playlist = record.getPlaylist(); + +chapters: + for (Map.Entry<String, Chapter> entry : playlist.getChaptersMap().entrySet()) { + Chapter chapter = entry.getValue(); + + // Find completion date + Date complete = new Date(0); + for (VideoRecord vr : chapter.getVideos().values()) { + if (!vr.getComplete()) { + continue chapters; + } + + Date recordCompletion = vr.getCompletionDate(); + if (recordCompletion != null && complete.before(recordCompletion)) { + complete = vr.getCompletionDate(); + } + } + + String attributeName = "Training Complete - " + entry.getKey(); + + // Check if the user already has the attribute. + List<Attribute> attributes = f1.getAttribute(userId, attributeName); + + if (attributes.size() == 0) { + Attribute attribute = new Attribute(attributeName); + attribute.setStartDate(complete); + + if (f1.addAttribute(userId, attribute)) { + System.out.printf("%s added %s\n", userId, attributeName); + } else { + System.out.printf("%s failed to add %s\n", userId, attributeName); + } + } else { + System.out.printf("%s already has %s\n", userId, attributeName); + } + } + + } catch (Exception e) { + System.out.printf("%s exception: %s\n", userId, e.getMessage()); + e.printStackTrace(); + } + } + } + + return offset; + } +} diff --git a/src/main/java/com/p4square/grow/tools/AttributeTool.java b/src/main/java/com/p4square/grow/tools/AttributeTool.java new file mode 100644 index 0000000..8e0540a --- /dev/null +++ b/src/main/java/com/p4square/grow/tools/AttributeTool.java @@ -0,0 +1,184 @@ +/* + * Copyright 2014 Jesse Morgan + */ + +package com.p4square.grow.tools; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.restlet.Client; +import org.restlet.Context; +import org.restlet.data.Protocol; + +import com.p4square.grow.config.Config; +import com.p4square.f1oauth.Attribute; +import com.p4square.f1oauth.F1Access; +import com.p4square.f1oauth.F1API; +import com.p4square.f1oauth.F1Exception; +import com.p4square.restlet.oauth.OAuthUser; + +/** + * Tool for manipulating F1 Attributes. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class AttributeTool { + + private static Config mConfig; + private static F1API mF1API; + + public static void usage() { + System.out.println("java com.p4square.grow.tools.AttributeTool <command>...\n"); + System.out.println("Commands:"); + System.out.println("\t--domain <domain> Set config domain"); + System.out.println("\t--dev Set config domain to dev"); + System.out.println("\t--config <file> Merge in config file"); + System.out.println("\t--list List all attributes"); + System.out.println("\t--assign <userId> <attribute> <comment> Assign an attribute"); + System.out.println("\t--getall <userId> Get an attribute"); + System.out.println("\t--get <userId> <attribute> Get an attribute"); + } + + public static void main(String... args) { + if (args.length == 0) { + usage(); + System.exit(1); + } + + mConfig = new Config(); + + try { + mConfig.updateConfig(AttributeTool.class.getResourceAsStream("/grow.properties")); + + int offset = 0; + while (offset < args.length) { + if ("--domain".equals(args[offset])) { + mConfig.setDomain(args[offset + 1]); + mF1API = null; + offset += 2; + + } else if ("--dev".equals(args[offset])) { + mConfig.setDomain("dev"); + mF1API = null; + offset += 1; + + } else if ("--config".equals(args[offset])) { + mConfig.updateConfig(args[offset + 1]); + mF1API = null; + offset += 2; + + } else if ("--list".equals(args[offset])) { + offset = list(args, ++offset); + + } else if ("--assign".equals(args[offset])) { + offset = assign(args, ++offset); + + } else if ("--getall".equals(args[offset])) { + offset = getall(args, ++offset); + + } else if ("--get".equals(args[offset])) { + offset = get(args, ++offset); + + } else { + throw new IllegalArgumentException("Unknown command " + args[offset]); + } + } + } catch (Exception e) { + e.printStackTrace(); + System.exit(2); + } + } + + private static F1API getF1API() throws Exception { + if (mF1API == null) { + Context context = new Context(); + Client client = new Client(context, Arrays.asList(Protocol.HTTP, Protocol.HTTPS)); + context.setClientDispatcher(client); + + F1Access f1Access = new F1Access(context, + mConfig.getString("f1ConsumerKey"), + mConfig.getString("f1ConsumerSecret"), + mConfig.getString("f1BaseUrl"), + mConfig.getString("f1ChurchCode"), + F1Access.UserType.WEBLINK); + + // Gather Username and Password + String username = System.console().readLine("F1 Username: "); + char[] password = System.console().readPassword("F1 Password: "); + + OAuthUser user = f1Access.getAccessToken(username, new String(password)); + Arrays.fill(password, ' '); // Lost cause, but I'll still try. + + mF1API = f1Access.getAuthenticatedApi(user); + } + + return mF1API; + } + + private static int list(String[] args, int offset) throws Exception { + final F1API f1 = getF1API(); + + final Map<String, String> attributes = f1.getAttributeList(); + System.out.printf("%7s %s\n", "ID", "Name"); + for (Map.Entry<String, String> entry : attributes.entrySet()) { + System.out.printf("%7s %s\n", entry.getValue(), entry.getKey()); + } + + return offset; + } + + private static int assign(String[] args, int offset) throws Exception { + final String userId = args[offset++]; + final String attributeName = args[offset++]; + final String comment = args[offset++]; + + final F1API f1 = getF1API(); + + Attribute attribute = new Attribute(attributeName); + attribute.setStartDate(new Date()); + attribute.setComment(comment); + + if (f1.addAttribute(userId, attribute)) { + System.out.println("Added attribute " + attributeName + " for " + userId); + } else { + System.out.println("Failed to add attribute " + attributeName + " for " + userId); + } + + return offset; + } + + private static int getall(String[] args, int offset) throws Exception { + final String userId = args[offset++]; + + doGet(userId, null); + + return offset; + } + + private static int get(String[] args, int offset) throws Exception { + final String userId = args[offset++]; + final String attributeName = args[offset++]; + + doGet(userId, attributeName); + + return offset; + } + + private static void doGet(final String userId, final String attributeName) throws Exception { + final F1API f1 = getF1API(); + + List<Attribute> attributes = f1.getAttribute(userId, attributeName); + for (Attribute attribute : attributes) { + System.out.printf("%s %s %s %s %s\n%s\n\n", + userId, + attribute.getAttributeName(), + attribute.getId(), + attribute.getStartDate(), + attribute.getEndDate(), + attribute.getComment()); + } + } +} |