p = 'backend/app/services/ai_mode_service.py'
s = open(p).read()

old = s[s.index('def practice_answer('):]
new = '''def _topic_words(topic: str) -> str:
    """The topic as it should read in a sentence.

    A learner typing in capitals is not shouting a proper noun, and "There are
    20 questions on PEDIATRIC INFECTIOUS DISEASE here" reads like the tutor
    shouting back. Anything already mixed case is left exactly as written —
    "DKA" in a normal sentence is an abbreviation and must survive.
    """
    text = " ".join((topic or "").split())
    return text.lower() if text.isupper() else text


def practice_count(db: Session, user: User, category_ids: list[int]) -> int:
    """How many questions this learner can actually sit on a topic.

    Counted from the categories the reading belongs to, not from the length of
    a retrieval shortlist. The shortlist is capped at twenty and is never
    empty, so counting it said "20 questions" for every topic including ones
    with none — a confident number that happened to be the cap.
    """
    from app.models.question_category import QuestionCategoryLink

    if not category_ids:
        return 0
    direct = db.query(Question.id).filter(Question.question_category_id.in_(category_ids))
    linked = db.query(QuestionCategoryLink.question_id).filter(
        QuestionCategoryLink.category_id.in_(category_ids))
    ids = {row[0] for row in direct.all()} | {row[0] for row in linked.all()}
    if not ids:
        return 0
    # Through the bank's own visibility rules, so a count is a count of what
    # this person can open.
    visible = db.query(Question.id).filter(
        Question.id.in_(ids), bank_question_predicate(user)).count()
    return int(visible)


def practice_answer(db: Session, user: User, question: str) -> tuple[str, list[dict], list[int]]:
    """The reply to a request for practice: what there is, and where to start.

    Deterministic, and no model call. A learner asking for questions on a
    topic wants a count and a button, and the two ways this can go wrong — a
    refusal, or an invented number — are both removed by not asking a model.

    Returns the prose, the citations to show under it, and the ids the
    Practise button should build a session from.
    """
    topic = _topic_words(practice_topic(question) or (question or ""))
    sources = retrieve(db, user, topic)

    # A reading is only offered when the library is actually close to the
    # topic. Retrieval always returns something — asked for hyperkalaemia it
    # returned a section of the Kawasaki article — and a confident link to the
    # wrong article is worse than no link.
    near = closeness(db, topic)
    close_enough = near is None or near >= ADJACENT_MATCH
    reading = next((s for s in sources if s.get("kind") in ("article", "section")),
                   None) if close_enough else None

    categories: list[int] = []
    if reading:
        article = db.get(Article, int(reading["id"]))
        if article and article.category_id:
            categories = [article.category_id]
    count = practice_count(db, user, categories)
    ids = practice_ids(db, user, [], topic) if count else []

    citations = []
    if reading:
        citations = [{
            "marker": f"[[{reading['kind']}:{reading['ref']}]]",
            "kind": reading["kind"], "id": reading["id"],
            "section_id": reading.get("section_id"),
            "title": reading["title"], "curated": bool(reading.get("curated")),
        }]

    where = f" The reading is [[{reading['kind']}:{reading['ref']}]]." if reading else ""
    if count:
        many = "question" if count == 1 else "questions"
        return (
            f"There {'is' if count == 1 else 'are'} {count} {many} on {topic} here."
            f"{where} Press Practise this to start, or ask me about any part of it "
            f"first — the mechanism, or where people go wrong.",
            citations, ids,
        )

    # Nothing to practise. Directive rather than apologetic: say what to do
    # instead, in one sentence, and point at the nearest thing that exists.
    nearest = nearest_topic(db, topic)
    if reading:
        return (
            f"There are no questions on {topic} yet.{where} Read that first, then "
            + (f"practise {nearest}, which is the nearest topic with questions."
               if nearest else "practise a neighbouring topic from the question bank."),
            citations, [],
        )
    if nearest:
        return (
            f"There are no questions on {topic} yet. Practise {nearest} instead — "
            f"it is the nearest thing in the bank.",
            [], [],
        )
    return (f"There are no questions on {topic} yet. Ask me about it and I will "
            f"teach it, or pick a topic from the question bank.", [], [])
'''
s = s.replace(old, new)

# ── adjacent mode: stop the model editorialising about coverage ──────────────
s = s.replace('''            "The sources below are related to the question but may not answer "
            "it directly.\\n\\n"
            "Answer using them where they help, citing them, and from general "
            "knowledge where they do not. Do not comment on what the library "
            "does or does not cover.\\n\\n"''',
'''            "Sources below.\\n\\n"
            "Answer the question. Use the sources where they help and cite "
            "them; where they do not, answer from what you know and cite "
            "nothing for that part. Never mention the library, the sources, "
            "what is or is not in them, or what you could not find — not in "
            "the first sentence, not in the last. The learner asked a "
            "question; give them the answer.\\n\\n"''')
s = s.replace('''            "You have no sources for this one.\\n\\n"
            "Answer from general knowledge, briefly and plainly, and say "
            "nothing at all about what the library does or does not contain. "
            "Cite nothing: there is nothing here to cite, and a marker you "
            "invent points nowhere."''',
'''            "Answer from what you know, briefly and plainly. Never mention "
            "the library, sources, coverage, or what you could not find — not "
            "in the first sentence, not in the last. Cite nothing: there is "
            "nothing here to cite, and a marker you invent points nowhere."''')
open(p, 'w').write(s)
print('written')
