"""Number the board review sets instead of dating them. A year in the title says when the questions were published, which is not something a learner chooses a set by, and dates material that is otherwise timeless. Numbering them in order keeps the sequence without the implication. Both the quizzes and the study plans built from the same material are renamed, so a learner does not meet "Board Review 2019" in one place and "Board Review VIII" in another. docker compose exec backend python -m scripts.number_board_reviews docker compose exec backend python -m scripts.number_board_reviews --apply """ import re import sys from sqlalchemy import text as sa_text from app.database import SessionLocal NUMERALS = [ (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"), ] def roman(number: int) -> str: out = [] for value, symbol in NUMERALS: while number >= value: out.append(symbol) number -= value return "".join(out) def main(): apply_changes = "--apply" in sys.argv db = SessionLocal() try: # Oldest set becomes I, so the numbering follows the order they were sat. years = sorted({int(m.group(1)) for (title,) in db.execute(sa_text( "SELECT title FROM quizzes WHERE title ~ 'Board Review [0-9]{4}'")).fetchall() if (m := re.search(r"(\d{4})", title))}) if not years: print(" Nothing to renumber.") return 0 numbering = {year: roman(index) for index, year in enumerate(years, start=1)} print(f" sets found: {len(years)}\n") for year, numeral in numbering.items(): print(f" Board Review {year} -> Board Review {numeral}") if not apply_changes: print("\n Re-run with --apply.") return 0 quizzes = renamed_plans = 0 for year, numeral in numbering.items(): quizzes += db.execute(sa_text(""" UPDATE quizzes SET title = replace(title, :old, :new) WHERE title LIKE :like """), {"old": f"Board Review {year}", "new": f"Board Review {numeral}", "like": f"%Board Review {year}%"}).rowcount or 0 renamed_plans += db.execute(sa_text(""" UPDATE study_plans SET name = :new, slug = :slug WHERE name = :old """), {"old": f"Board Review {year}", "new": f"Board Review {numeral}", "slug": f"board-review-{numeral.lower()}"}).rowcount or 0 db.commit() print(f"\n quizzes renamed : {quizzes}") print(f" study plans renamed : {renamed_plans}") finally: db.close() return 0 if __name__ == "__main__": sys.exit(main())