async def register_answer(ans: CardAnswer, request: Request): """Register the answer an user gave to a card. This is used both to decide whether and when to show the card again and to collect information about which words and sentences are hard and which mistakes are the most common. """ current_user = request.session.get('id', 1) async with get_conn() as conn: await conn.execute( get_sql('insert_revlog'), ans.from_id, ans.to_id, current_user, ans.given_answers, ans.expected_answers, ans.correct ) # user 1 is the anonymous user, so there's no revision time update if current_user == 1: return 'OK' # repetitions in the same session do not affect further the state if not ans.repetition: if ans.correct: # correct, roughly equivalent to "Easy" in Anki # EF = EF + 0.15 # if new, EF is 2.5 + 0.15 = 2.65 # I = 1 iif new card # I = 6 iif I = 1 # I = round(I * EF) iif I > 1 and not new card # next review in I days await conn.execute( get_sql('reschedule_correct_card'), ans.from_id, ans.to_id, current_user ) else: # not correct, roughly equivalent to "Again" in Anki # EF = max(1.3, EF - 0.2) # if new, EF is assumed 2.5, so will become 2.5 - 0.2 = 2.3 # I = 1 await conn.execute( get_sql('reschedule_wrong_card'), ans.from_id, ans.to_id, current_user ) return 'OK'
async def stream_revlogs(current_user): print('Streaming for user', current_user) async with get_conn() as conn: async with conn.transaction(): nl = '\n'.encode() async for record in conn.cursor( get_sql('get_all_user_reviews'), current_user): print(record) yield orjson.dumps(dict(record)) + nl
async def my_revision_stats(request: Request): """Provide daily revision stats. The result is an array that for every day (UTC) reports how many reviews were there and how many were correct. """ current_user = request.session.get('id', 1) if current_user == 1: return JSONResponse( dict(error='Not logged in, cannot get statistics'), status_code=status.HTTP_403_UNAUTHORIZED, ) async with get_conn() as conn: return await conn.fetch( get_sql('get_user_stats'), current_user, )
async def take_note(note: NoteAboutCard, request: Request): """Store a note about a card. The user can register a note about a card, to be shown next time they get the same sentence. There are two notes: the hint, which is shown before an answer, and the explanation, shown after. """ current_user = request.session.get('id', 1) if current_user == 1: return JSONResponse( dict(error='Not logged in, cannot take notes'), status_code=status.HTTP_403_UNAUTHORIZED, ) async with get_conn() as conn: await conn.execute( get_sql('upsert_card_notes'), note.from_id, note.to_id, current_user, note.hint, note.explanation, )
async def draw_cards(qr: QuizRequest, request: Request): """Return the cards to test for this session. The selection contains both old cards to renew and a given number of brand new cards. """ current_user = request.session.get('id', 1) async with get_conn() as conn: cards_new = await conn.fetch( get_sql('draw_new_cards'), qr.target_lang, qr.source_langs, current_user) if current_user == 1: return cards_new # store the selected language so next time the menu can show it directly await conn.execute( 'DELETE FROM latest_language WHERE account_id=$1', current_user) await conn.execute( """ INSERT INTO latest_language( account_id, src_langs, tgt_lang ) VALUES ($1, $2, $3)""", current_user, qr.source_langs, qr.target_lang ) expired_cards = await conn.fetch( get_sql('get_expired_cards'), current_user) # TODO postgres insists in merging cards and languages before the join # with the user card state which is very selective # this is a workaround, the join is done in the backend languages_l = await conn.fetch( 'SELECT id, name, iso693_3 FROM language') src_langs = {} to_lang_id = set() to_lang_name = None for lng in languages_l: if lng['iso693_3'] == qr.target_lang: to_lang_id = lng['id'] to_lang_name = lng['name'] if lng['iso693_3'] in qr.source_langs: src_langs[lng['id']] = (lng['iso693_3'], lng['name']) expired_cards = [ dict(ec.items()) for ec in expired_cards if ec['from_lang'] in src_langs and ec['to_lang'] == to_lang_id] for ec in expired_cards: ec['from_language_code'] = src_langs[ec['from_lang']][0] ec['from_language'] = src_langs[ec['from_lang']][1] ec['to_language_code'] = qr.target_lang ec['to_language'] = to_lang_name del ec['from_lang'] del ec['to_lang'] return expired_cards + cards_new