p = 'backend/app/routers/flashcards.py'
s = open(p).read()

s = s.replace('''class CardVerdict(BaseModel):
    """What a learner said about a card. Two answers, deliberately."""

    outcome: Literal["known", "again"]''',
'''class CardVerdict(BaseModel):
    """What a learner said about a card.

    Two shapes accepted. `rating` is the four-point scale the study page now
    sends — Again, Hard, Good, Easy — and the one that schedules the card.
    `outcome` is what it sent before, kept so a tab left open across the
    deploy does not start failing; it maps to Again and Good.
    """

    outcome: Literal["known", "again"] | None = None
    rating: int | None = None

    @field_validator("rating")
    @classmethod
    def known_rating(cls, value):
        if value is not None and value not in scheduling.RATINGS:
            raise ValueError("rating must be 1 Again, 2 Hard, 3 Good or 4 Easy")
        return value''')

s = s.replace('''    card_review.record(db, current_user.id, card_id, data.outcome)
    return {"card_id": card_id, "outcome": data.outcome}''',
'''    rating = data.rating
    if rating is None:
        if not data.outcome:
            raise HTTPException(status_code=422, detail="Send a rating")
        rating = scheduling.AGAIN if data.outcome == "again" else scheduling.GOOD
    return card_review.rate(db, current_user.id, card_id, rating,
                            exam_on=user_settings.exam_date(current_user.id))''')

s = s.replace('''    cards = db.query(Flashcard).filter(Flashcard.deck_id == deck_id).order_by(Flashcard.id).all()
    ordered, counts = card_review.study_order(db, current_user.id, cards)
    return {
        "deck": {"id": deck.id, "title": deck.title, "card_count": len(cards)},
        **counts,
        "cards": [{"id": card.id, "front": card.front, "back": card.back,
                   "image_path": card.image_path} for card in ordered],
    }''',
'''    cards = db.query(Flashcard).filter(Flashcard.deck_id == deck_id).order_by(Flashcard.id).all()
    ordered, counts = card_review.study_order(db, current_user.id, cards)
    exam_on = user_settings.exam_date(current_user.id)
    plan = card_review.schedules(db, current_user.id, [card.id for card in ordered])

    def card_json(card):
        row = plan.get(card.id)
        return {
            "id": card.id, "front": card.front, "back": card.back,
            "image_path": card.image_path,
            # What each button costs, so the four labels mean something. A
            # learner choosing between four words with no idea of the
            # intervals is guessing, and the ratings become noise.
            "intervals": scheduling.preview(card_review.state_of(row), exam_on=exam_on),
            "lapses": row.lapses if row else 0,
            "is_leech": bool(row.is_leech) if row else False,
        }

    return {
        "deck": {"id": deck.id, "title": deck.title, "card_count": len(cards),
                 "source_article_id": deck.source_article_id,
                 "article_changed": _article_moved_on(db, deck)},
        **counts,
        "cards": [card_json(card) for card in ordered],
    }


def _article_moved_on(db: Session, deck: FlashcardDeck) -> bool:
    """Whether the article a deck came from has been edited since.

    A badge, never a regeneration. Rebuilding the cards would throw away every
    learner's schedule on that deck to fix a paragraph, so the decision stays
    with the person who can read both.
    """
    if not deck.source_article_id or not deck.source_synced_at:
        return False
    article = db.get(Article, deck.source_article_id)
    return bool(article and article.updated_at and article.updated_at > deck.source_synced_at)''')

s = s.replace('from app.utils.auth import get_current_user, require_admin, require_moderator',
              'from app.services import scheduling, user_settings\nfrom app.utils.auth import get_current_user, require_admin, require_moderator')
open(p, 'w').write(s)
print('written')
