File size: 2,484 Bytes
b96156b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""
option_generator.py

Generates the 4-option list for an MCQ.

Changes from original:
  - Accepts pos_tag and forwards it to generate_distractors so the
    auto_register / cluster routing uses POS for better sub-cluster matching.
  - All options (including the answer) are root forms — lemmatization has
    already happened upstream in process_pipeline.py.
"""

import random
import sys
import os

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

from question_generation.cluster_store import generate_distractors


def generate_options(answer_word: str,
                     sentence_words: list[str],
                     pos_tags: list[tuple] | None = None,
                     n: int = 3,
                     domain: str | None = None,
                     question: str = "",
                     context: str = "") -> list[str]:
    """
    Return a shuffled list of 4 options (3 distractors + 1 correct answer).
    All items are root forms.

    Parameters
    ----------
    answer_word    : root form of the correct answer
    sentence_words : tokenised words from the sentence (used to avoid
                     accidentally selecting a word already in the question)
    pos_tags       : list of (word, upos) pairs from Stanza — used to look
                     up the POS of answer_word for better cluster routing
    n              : number of distractors to generate (default 3)
    question       : MCQ question string (for keyword-based cluster detection)
    context        : original sentence (for domain detection)
    """

    # Derive POS tag for the answer word from pos_tags list
    pos_tag = "NOUN"  # safe default
    if pos_tags:
        pos_lookup = {word: tag for word, tag in pos_tags}
        # pos_tags uses surface (inflected) forms; answer_word is root form.
        # Try direct match first, then check if any surface form shares the root.
        if answer_word in pos_lookup:
            pos_tag = pos_lookup[answer_word]
        else:
            # answer_word is the root; surface form may differ — use first NOUN/PROPN
            for word, tag in pos_tags:
                if tag in ("NOUN", "PROPN"):
                    pos_tag = tag
                    break

    distractors = generate_distractors(
        answer=answer_word,
        question=question,
        context=context,
        k=n,
    )

    options = distractors[:n] + [answer_word]
    random.shuffle(options)
    return options