diff --git a/urbanstats-persistent-data/urbanstats_persistent_data/juxtastat_stats.py b/urbanstats-persistent-data/urbanstats_persistent_data/juxtastat_stats.py index 67eb34b86..1217a4da5 100644 --- a/urbanstats-persistent-data/urbanstats_persistent_data/juxtastat_stats.py +++ b/urbanstats-persistent-data/urbanstats_persistent_data/juxtastat_stats.py @@ -2,6 +2,8 @@ import time from typing import List, Tuple +from .utils import corrects_to_bytes + table_for_quiz_kind = { "juxtastat": "JuxtaStatIndividualStats", "retrostat": "JuxtaStatIndividualStatsRetrostat", @@ -25,6 +27,12 @@ def table(): """CREATE TABLE IF NOT EXISTS JuxtaStatIndividualStatsRetrostat (user integer, week integer, corrects integer, time integer, PRIMARY KEY (user, week))""" ) + # juxtastat infinite + c.execute( + """CREATE TABLE IF NOT EXISTS JuxtaStatInfiniteStats + (user integer, seed string, version integer, corrects varbinary(128), score integer, num_answers integer, time integer, PRIMARY KEY (user, seed, version))""" + ) + # user to domain name c.execute( """ @@ -158,6 +166,38 @@ def store_user_stats_retrostat(user, week_stats: List[Tuple[int, List[bool]]]): store_user_stats_into_table(user, week_stats, "JuxtaStatIndividualStatsRetrostat") +def has_infinite_stats(user, seeds_versions): + user = int(user, 16) + _, c = table() + c.execute( + "SELECT seed, version FROM JuxtaStatInfiniteStats WHERE user=?", + (user,), + ) + results = c.fetchall() + results = set(results) + return [(seed, version) in results for seed, version in seeds_versions] + + +def store_user_stats_infinite(user, seed, version, corrects: List[bool]): + user = int(user, 16) + conn, c = table() + correctBytes = corrects_to_bytes(corrects) + time_unix_millis = round(time.time() * 1000) + c.execute( + "INSERT OR REPLACE INTO JuxtaStatInfiniteStats VALUES (?, ?, ?, ?, ?, ?, ?)", + ( + user, + seed, + version, + correctBytes, + sum(corrects), + len(corrects), + time_unix_millis, + ), + ) + conn.commit() + + def get_per_question_stats_from_table(day, table_name, column): day = int(day) _, c = table() @@ -245,6 +285,14 @@ def todays_score_for(requestee, requesters, date, quiz_kind): ) +def infinite_results(requestee, requesters, seed, version): + """ + For each `requseter` returns the pattern of correct answers if `(requester, requestee)` is a friend pair. + """ + + return _compute_friend_results(requestee, requesters, compute_fn=lambda c, for_user: _infinite_results(c, for_user, seed, version)) + + def _compute_friend_results(requestee, requesters, compute_fn): requestee = int(requestee, 16) @@ -287,3 +335,32 @@ def _compute_daily_score(date, quiz_kind, c, for_user): return dict(corrects=None) else: return dict(corrects=bitvector_to_corrects(res[0])) + +def _infinite_results(c, for_user, seed, version): + """ + Returns the result for the given user for the given seed and version, as well + as the maximum score and corresponding seed and version. + """ + + c.execute( + "SELECT score FROM JuxtaStatInfiniteStats WHERE user=? AND seed=? AND version=?", + (for_user, seed, version), + ) + res = c.fetchone() + for_this_seed = None if res is None else res[0] + + c.execute( + "SELECT seed, version, score FROM JuxtaStatInfiniteStats WHERE user=? AND score=(SELECT MAX(score) FROM JuxtaStatInfiniteStats WHERE user=?)", + (for_user, for_user), + ) + res = c.fetchone() + max_score_seed = None if res is None else res[0] + max_score_version = None if res is None else res[1] + max_score = None if res is None else res[2] + + return dict( + forThisSeed=for_this_seed, + maxScore=max_score, + maxScoreSeed=max_score_seed, + maxScoreVersion=max_score_version + ) diff --git a/urbanstats-persistent-data/urbanstats_persistent_data/main.py b/urbanstats-persistent-data/urbanstats_persistent_data/main.py index 7f42e483b..cd0dbb4dc 100644 --- a/urbanstats-persistent-data/urbanstats_persistent_data/main.py +++ b/urbanstats-persistent-data/urbanstats_persistent_data/main.py @@ -10,11 +10,14 @@ friend_request, get_per_question_stats, get_per_question_stats_retrostat, + has_infinite_stats, + infinite_results, latest_day, latest_week_retrostat, register_user, store_user_stats, get_full_database, + store_user_stats_infinite, store_user_stats_retrostat, todays_score_for, unfriend, @@ -80,6 +83,7 @@ def get_authenticated_user(additional_required_params=()): required_params = ["user", "secureID"] + list(additional_required_params) if not all([param in form for param in required_params]): + print("NEEDS PARAMS", required_params, "GOT", form.keys()) return False, ( flask.jsonify( { @@ -101,10 +105,13 @@ def authenticate(fields): def decorator(fn): @functools.wraps(fn) def wrapper(): + print("AUTHENTICATE", flask_form()) success, error = get_authenticated_user(fields) if not success: + print("AUTHENTICATE ERROR", error) return error return fn() + return wrapper return decorator @@ -145,6 +152,28 @@ def juxtastat_store_user_stats_request(): return flask.jsonify(dict()) +@app.route("/juxtastat_infinite/has_infinite_stats", methods=["POST"]) +@authenticate(["seedVersions"]) +def juxtastat_infinite_has_infinite_stats_request(): + form = flask_form() + print("HAS INFINITE STATS", form) + res = dict(has=has_infinite_stats(form["user"], form["seedVersions"])) + print("HAS INFINITE STATS", res) + return flask.jsonify( + res + ) + + +@app.route("/juxtastat_infinite/store_user_stats", methods=["POST"]) +@authenticate(["seed", "version", "corrects"]) +def juxtastat_infinite_store_user_stats_request(): + form = flask_form() + store_user_stats_infinite( + form["user"], form["seed"], form["version"], form["corrects"] + ) + return flask.jsonify(dict()) + + @app.route("/retrostat/store_user_stats", methods=["POST"]) @authenticate(["day_stats"]) def retrostat_store_user_stats_request(): @@ -216,6 +245,19 @@ def juxtastat_todays_score_for(): return flask.jsonify(res) +@app.route("/juxtastat/infinite_results", methods=["POST"]) +@authenticate(["requesters", "seed", "version"]) +def juxtastat_infinite_results(): + form = flask_form() + res = dict( + results=infinite_results( + form["user"], form["requesters"], form["seed"], form["version"] + ) + ) + print("INFINITE RESULTS FOR", res) + return flask.jsonify(res) + + import logging logging.getLogger("flask_cors").level = logging.DEBUG diff --git a/urbanstats-persistent-data/urbanstats_persistent_data/utils.py b/urbanstats-persistent-data/urbanstats_persistent_data/utils.py new file mode 100644 index 000000000..013051478 --- /dev/null +++ b/urbanstats-persistent-data/urbanstats_persistent_data/utils.py @@ -0,0 +1,12 @@ +from typing import List + + +def corrects_to_bytes(corrects: List[bool]) -> bytes: + result = [] + for i in range(0, len(corrects), 8): + byte = 0 + for j in range(8): + if i + j < len(corrects) and corrects[i + j]: + byte |= 1 << j + result.append(byte) + return bytes(result)