Skip to content

Instantly share code, notes, and snippets.

@abhiyerra
Last active April 6, 2021 13:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save abhiyerra/7a7c31d7e695010e0bd0669c7cf18885 to your computer and use it in GitHub Desktop.
Save abhiyerra/7a7c31d7e695010e0bd0669c7cf18885 to your computer and use it in GitHub Desktop.
from airtable import Airtable
from flask import Flask, redirect, request, url_for
import json
import markdown
from datetime import datetime, timedelta, timezone
# https://gist.github.com/doctorpangloss/13ab29abd087dc1927475e560f876797
def sm2(x: [int], a=6.0, b=-0.8, c=0.28, d=0.02, theta=0.2) -> float:
"""
Returns the number of days to delay the next review of an item by, fractionally, based on the history of answers x to
a given question, where
x == 0: Incorrect, Hardest
x == 1: Incorrect, Hard
x == 2: Incorrect, Medium
x == 3: Correct, Medium
x == 4: Correct, Easy
x == 5: Correct, Easiest
@param x The history of answers in the above scoring.
@param theta When larger, the delays for correct answers will increase.
"""
assert all(0 <= x_i <= 5 for x_i in x)
correct_x = [x_i >= 3 for x_i in x]
# If you got the last question incorrect, just return 1
if not correct_x[-1]:
return 1.0
# Calculate the latest consecutive answer streak
num_consecutively_correct = 0
for correct in reversed(correct_x):
if correct:
num_consecutively_correct += 1
else:
break
return a*(max(1.3, 2.5 + sum(b+c*x_i+d*x_i*x_i for x_i in x)))**(theta*num_consecutively_correct)
app = Flask(__name__)
@app.route("/")
def show():
recalls = Airtable('', 'Recalls', api_key='').get_all(view="Review", maxRecords=1)
recall = None
if len(recalls) > 0:
recall = recalls[0]
else:
return "Nothing to Review"
script = '''
$(document).ready(function() {
$("#show").click(function(){
$("#back").removeClass("d-none");
$("#show").addClass("d-none");
});
});
'''
return f'''
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>TotalRecall</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</link>
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<script>
{script}
</script>
</head>
<body>
<div class="container mt-5">
<div class="row">
<div class="col text-center" id="front">
{recall['fields']['Front']}
</div>
</div>
<div class="row mt-5">
<div class="col text-center">
<button id="show" class="btn btn-primary text-white text-center">Show</button>
</div>
</div>
<div class="row mt-5">
<div class="col text-center d-none" id="back">
{markdown.markdown(recall['fields']['Back'])}
<br/>
</div>
</div>
<form action="update/{recall['id']}" method="POST">
<div class="row mt-5">
<div class="col">
<input type="submit" class="btn btn-primary text-white text-center" name="recall_score" value="1">
Incorrect, Hardest
</div>
<div class="col">
<input type="submit" class="btn btn-primary text-white text-center" name="recall_score" value="2">
Incorrect, Hard
</div>
<div class="col">
<input type="submit" class="btn btn-primary text-white text-center" name="recall_score" value="3">
Incorrect, Medium
</div>
</div>
<div class="row mt-5">
<div class="col">
<input type="submit" class="btn btn-primary text-white text-center" name="recall_score" value="4">
Correct, Medium
</div>
<div class="col">
<input type="submit" class="btn btn-primary text-white text-center" name="recall_score" value="5">
Correct, Easy
</div>
<div class="col">
<input type="submit" class="btn btn-primary text-white text-center" name="recall_score" value="6">
Correct, Easiest
</div>
</div>
</form>
</div>
</body>
</html>
'''
@app.route("/update/<airtableId>", methods=['POST'])
def update(airtableId=None):
recalls = Airtable('', 'Recalls', api_key='')
recall = recalls.get(airtableId)
scores = []
if 'Scores' in recall['fields']:
scores = [int(i) for i in recall['fields']['Scores'].split(",")]
current_score = request.form['recall_score']
scores += [current_score]
next_review_at = datetime.now(timezone.utc) + timedelta(days=sm2([int(i) for i in scores]))
recalls.update(airtableId, {
'NextReviewAt': next_review_at.isoformat(),
'Scores': ','.join([str(i) for i in scores])
})
return redirect(url_for('show'))
def sync_handler(event, context):
recalls = Airtable('', 'Recalls', api_key='')
records = recalls.get_all(view="Grid")
for record in records:
if 'NextReviewAt' not in record['fields']:
recalls.update(record['id'], {
'NextReviewAt': datetime.now(timezone.utc).isoformat()
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment