File size: 9,365 Bytes
ec24fb0 | 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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | """Passage pool for synthetic MCQ generation: Sinhala passages assigned to curriculum subjects.
Subjects are the published SinhalaMMLU subject list (Pramodya et al. 2025, Table 2), never test questions. Passages come
from the cleaned pretraining mix (data/corpus/cpt_v1: Wikipedia, FineWeb-2, NSINA; exam sites already removed).
Documents are split into 400-1200 character chunks; a chunk is assigned to the subject whose keyword list it matches
most (at least 2 distinct keywords). Wikipedia chunks are preferred.
python scripts/build_passage_pool.py --per-subject 6 --out data/synth/passages_pilot.jsonl
"""
from __future__ import annotations
import argparse
import json
import random
import re
import sys
from collections import Counter
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from eval.common import ROOT, write_jsonl # noqa: E402
# subject -> (level description used in the generation prompt, Sinhala keywords)
SUBJECTS = {
"History": ("grades 6-11", ["ඉතිහාස", "රජතුමා", "රාජධානි", "අනුරාධපුර", "පොළොන්නරු", "මහාවංශ", "වංශකථා", "ශිලා ලිපි", "යටත් විජිත", "පෘතුගීසි", "ලන්දේසි", "නිදහස් අරගල"]),
"Buddhism": ("grades 6-11", ["බුදුරජාණන්", "ධර්මය", "භික්ෂු", "සංඝ", "සූත්ර", "ත්රිපිටක", "නිර්වාණ", "පන්සිල්", "භාවනා", "ජාතක"]),
"Buddhist Civilization": ("grades 12-13 (A/L)", ["බෞද්ධ ශිෂ්ටාචාර", "ස්තූප", "දාගැබ", "චෛත්ය", "ආරාම", "ධාතු", "අභයගිරි", "මහාවිහාර", "ජේතවන"]),
"Catholicism": ("grades 6-11", ["කතෝලික", "පාප්වහන්සේ", "සාන්තුවර", "දිව්ය පූජා", "කන්යා මරියා", "රදගුරු"]),
"Christianity": ("grades 6-11", ["ක්රිස්තියානි", "යේසුස්", "බයිබලය", "ශුද්ධ", "ගිවිසුම", "දේවස්ථාන", "ගෝලයන්"]),
"Islam": ("grades 6-11", ["ඉස්ලාම්", "අල්ලාහ්", "නබි", "කුර්ආන්", "මුස්ලිම්", "රාමසාන්", "හජ්", "මස්ජිද්"]),
"Dancing": ("grades 6-13", ["නර්තන", "නැටුම", "උඩරට", "පහතරට", "සබරගමු", "වන්නම", "ගැටබෙර", "වෙස්", "තාලම්"]),
"Eastern Music": ("grades 6-11", ["සංගීත", "රාග", "තාල", "ස්වර", "වාදන", "සිතාර්", "තබ්ලා", "ගායන", "හින්දුස්ථානි"]),
"Drama and Theatre": ("grades 6-11", ["නාට්ය", "රංගන", "නාඩගම්", "කෝලම්", "සොකරි", "වේදිකා", "සරච්චන්ද්ර", "නළු"]),
"Arts": ("grades 6-8", ["චිත්ර", "කලාව", "මූර්ති", "බිතු සිතුවම්", "සීගිරි", "වර්ණ", "කලාකරු"]),
"Sinhala Language and Literature": ("grades 6-13", ["කාව්ය", "සාහිත්ය", "නවකතා", "කවි", "ව්යාකරණ", "සන්දේශ", "කෙටිකතා", "ලේඛක"]),
"Citizenship Education": ("grades 6-11", ["ආණ්ඩුක්රම", "පාර්ලිමේන්තු", "ප්රජාතන්ත්ර", "මූලික අයිතිවාසිකම්", "ඡන්ද", "පළාත් සභා", "පුරවැසි"]),
"Political Science": ("grades 12-13 (A/L)", ["දේශපාලන", "රාජ්යය", "ව්යවස්ථාව", "පරමාධිපත්ය", "ඒකීය", "ෆෙඩරල්", "විධායක"]),
"Geography": ("grades 6-13", ["භූගෝල", "දේශගුණ", "මෝසම්", "ගංගා", "කඳු", "වර්ෂාපතන", "සාගර", "පාංශු", "ජනගහන"]),
"Health and Physical Education": ("grades 6-11", ["සෞඛ්ය", "පෝෂණ", "රෝග", "ව්යායාම", "ක්රීඩා", "විටමින්", "ප්රතිශක්ති"]),
"Science": ("grades 6-11", ["විද්යාව", "පදාර්ථ", "ශාක", "සත්ත්ව", "විද්යුත්", "චුම්බක", "ආලෝකය", "පරිසර"]),
"Physics": ("grades 12-13 (A/L)", ["භෞතික", "බලය", "ප්රවේග", "ත්වරණ", "තරංග", "විද්යුත් ධාරා", "ගුරුත්වාකර්ෂණ", "ශක්තිය"]),
"Chemistry": ("grades 12-13 (A/L)", ["රසායන", "මූලද්රව්ය", "සංයෝග", "අණු", "පරමාණු", "අම්ල", "භෂ්ම", "ප්රතික්රියා"]),
"Biology": ("grades 12-13 (A/L)", ["ජීව විද්යා", "සෛල", "ජාන", "ප්රෝටීන", "එන්සයිම", "පරිණාම", "ප්රභාසංස්ලේෂණ", "ජීවීන්"]),
"Economics": ("grades 12-13 (A/L)", ["ආර්ථික", "ඉල්ලුම", "සැපයුම", "උද්ධමන", "දළ දේශීය", "බදු", "වෙළඳපොළ", "අපනයන"]),
"Business and Accounting Studies": ("grades 12-13 (A/L)", ["ව්යාපාර", "ගිණුම්", "ලාභ", "වත්කම්", "බැංකු", "කොටස්", "සමාගම", "ව්යවසාය"]),
"Agriculture and Food Technology": ("grades 9-13", ["කෘෂිකර්ම", "වගාව", "පොහොර", "බෝග", "කුඹුරු", "වී ", "පළිබෝධ", "ජලසම්පාදන"]),
"Home Economics": ("grades 9-13", ["ආහාර", "පෝෂණ", "ඇඳුම්", "නිවාස", "ගෘහ", "පවුල", "ආහාර පිසීම"]),
"Communication and Media Studies": ("grades 9-13", ["මාධ්ය", "ජනමාධ්ය", "පුවත්පත", "රූපවාහිනී", "ගුවන්විදුලි", "සන්නිවේදන"]),
"Design and Construction Technology": ("grades 9-11", ["ඉදිකිරීම්", "ගොඩනැගිලි", "කොන්ක්රීට්", "සැලසුම්", "ඉංජිනේරු", "ද්රව්ය"]),
}
def chunks(text: str, lo: int = 400, hi: int = 1200):
cur = ""
for para in re.split(r"\n\s*\n|\n", text):
para = para.strip()
if not para:
continue
if len(cur) + len(para) + 1 <= hi:
cur = f"{cur}\n{para}" if cur else para
else:
if len(cur) >= lo:
yield cur
cur = para[:hi]
if len(cur) >= lo:
yield cur
def assign(chunk: str) -> tuple[str | None, int]:
best, hits = None, 0
for subj, (_, kws) in SUBJECTS.items():
h = sum(kw in chunk for kw in kws)
if h > hits:
best, hits = subj, h
return (best, hits) if hits >= 2 else (None, hits)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--per-subject", type=int, default=6)
ap.add_argument("--out", default=str(ROOT / "data" / "synth" / "passages_pilot.jsonl"))
ap.add_argument("--exclude", help="earlier pool file: its passages are not reused")
ap.add_argument("--seed", type=int, default=1234)
args = ap.parse_args()
import pandas as pd
rng = random.Random(args.seed)
used = set()
if args.exclude and Path(args.exclude).exists():
used = {json.loads(l)["pid"] for l in open(args.exclude, encoding="utf-8")}
by_subject: dict[str, list[dict]] = {s: [] for s in SUBJECTS}
for source in ("wikipedia", "fineweb2", "nsina"):
df = pd.read_parquet(ROOT / "data" / "corpus" / "cpt_v1" / f"{source}.parquet", columns=["id", "text"])
docs = df.sample(frac=1, random_state=args.seed).itertuples()
for d in docs:
for k, c in enumerate(chunks(d.text)):
pid = f"{d.id}#{k}"
if pid in used:
continue
subj, hits = assign(c)
if subj and len(by_subject[subj]) < 50 * args.per_subject:
by_subject[subj].append({"pid": pid, "source": source, "subject": subj,
"level": SUBJECTS[subj][0], "keyword_hits": hits, "text": c})
if all(len(v) >= 50 * args.per_subject for v in by_subject.values()):
break
out = []
for subj, cands in by_subject.items():
cands.sort(key=lambda r: (r["source"] != "wikipedia", -r["keyword_hits"], rng.random()))
out += cands[: args.per_subject]
write_jsonl(args.out, out)
print(f"{len(out)} passages -> {args.out}")
print(dict(Counter(r["subject"] for r in out)))
print("by source:", dict(Counter(r["source"] for r in out)))
if __name__ == "__main__":
main()
|