summaryrefslogtreecommitdiff
path: root/src/main/java/com/p4square/grow/tools
diff options
context:
space:
mode:
authorJesse Morgan <jesse@jesterpm.net>2016-04-09 15:53:24 -0700
committerJesse Morgan <jesse@jesterpm.net>2016-04-09 15:53:24 -0700
commit371ccae3d1f31ec38f4af77fb7fcd175d49b3cd5 (patch)
tree38c4f1e8828f9af9c4b77a173bee0d312b321698 /src/main/java/com/p4square/grow/tools
parentbbf907e51dfcf157bdee24dead1d531122aa25db (diff)
parent3102d8bce3426d9cf41aeaf201c360d342677770 (diff)
Merge pull request #10 from PuyallupFoursquare/maven
Switching from Ivy+Ant to Maven.
Diffstat (limited to 'src/main/java/com/p4square/grow/tools')
-rw-r--r--src/main/java/com/p4square/grow/tools/AssessmentStats.java218
-rw-r--r--src/main/java/com/p4square/grow/tools/AttributeBackfillTool.java268
-rw-r--r--src/main/java/com/p4square/grow/tools/AttributeTool.java184
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());
+ }
+ }
+}