File size: 9,962 Bytes
68a82f9 | 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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | """Synthetic stand-in for Kodep/jev-topic-data (which is private and not yet pushed).
Same structure as the spec: 56 topics with descriptions, 279 train / 85 val / 105 test
items, skewed class counts, 14 topics with no training items, hard pairs sharing
vocabulary. The TOPICS are ours and the text is machine-generated — treat the metrics
as pipeline validation, not as a benchmark.
"""
import json, os, random
TOPICS = [
# (name, description, keywords) — pairs listed in config hard_pairs share keywords
("soccer", "soccer practice, games and team news", ["soccer", "practice", "field", "game"]),
("sports_general", "general sports and athletic program news", ["sports", "practice", "game", "athletic"]),
("swim_club", "swim club sessions, meets and pool rules", ["swim", "pool", "meet", "lap"]),
("basketball", "basketball schedule and gym bookings", ["basketball", "gym", "court", "tournament"]),
("athletics_office", "athletics office announcements", ["athletic", "office", "booster"]),
("field_trip", "field trip itineraries, buses and permission", ["field trip", "bus", "itinerary", "chaperone"]),
("permission_slip", "permission slips and signature reminders", ["permission", "slip", "signature", "consent"]),
("lunch_menu", "weekly lunch menu and meal options", ["lunch", "menu", "meal", "cafeteria"]),
("cafeteria_rules", "cafeteria expectations and seating", ["cafeteria", "seating", "rules", "lunch"]),
("bake_sale", "bake sales and baked goods fundraisers", ["bake sale", "cookies", "sale", "fundraiser"]),
("fundraiser", "fundraisers, drives and donation goals", ["fundraiser", "donation", "goal", "sale"]),
("art_club", "art club projects, medium and shows", ["art", "paint", "project", "exhibit"]),
("crafts_club", "crafts club projects and supplies", ["crafts", "glue", "project", "supplies"]),
("drama_club", "drama club rehearsals and tickets", ["drama", "rehearsal", "play", "tickets"]),
("music_band", "concert band rehearsals and concerts", ["band", "concert", "performance", "instrument"]),
("choir", "choir practices and winter concert", ["choir", "concert", "performance", "sing"]),
("orchestra", "orchestra sectionals and repertoire", ["orchestra", "sectional", "violin", "concert"]),
("reading_week", "reading week challenges and logs", ["reading", "challenge", "log", "books"]),
("library", "library hours, returns and fines", ["library", "return", "books", "due"]),
("spelling_bee", "spelling bee rounds and registration", ["spelling", "bee", "round", "word"]),
("math_club", "math club contests and puzzles", ["math", "contest", "puzzle", "problem"]),
("math_olympiad", "math olympiad training and teams", ["math", "olympiad", "team", "contest"]),
("science_fair", "science fair projects and judging", ["science", "fair", "project", "judging"]),
("robotics", "robotics builds and competition", ["robotics", "robot", "build", "competition"]),
("coding_club", "coding club sessions and projects", ["coding", "app", "scratch", "project"]),
("history_day", "history day topics and papers", ["history", "paper", "topic", "exhibit"]),
("debate_club", "debate rounds and motions", ["debate", "round", "motion", "argument"]),
("chess_club", "chess club ladder and matches", ["chess", "match", "ladder", "tournament"]),
("homework", "homework policies and support", ["homework", "assignment", "due", "tutoring"]),
("grades", "grade reporting and conferences", ["grades", "report card", "conference", "gpa"]),
("attendance", "attendance windows and absences", ["attendance", "absence", "tardy", "excused"]),
("late_pickup", "late pickup procedures and fees", ["pickup", "late", "fee", "after school"]),
("early_dismissal", "early dismissal and snow schedules", ["dismissal", "early", "schedule", "snow"]),
("weather_closing", "weather closings and delays", ["closing", "delay", "weather", "snow"]),
("bus_route", "bus route changes and stops", ["bus", "route", "stop", "ride"]),
("carpool", "carpool coordination boards", ["carpool", "drivers", "roster", "pickup"]),
("flu_report", "illness, flu and nurse notes", ["flu", "nurse", "illness", "outbreak"]),
("allergies", "allergy alerts and food policies", ["allergy", "food", "nurse", "epipen"]),
("photo_day", "school photos and portrait packages", ["photo", "portrait", "package", "picture"]),
("dentist_note", "dental screening and vision checks", ["dental", "vision", "screening", "note"]),
("volunteer", "volunteer hours and signups", ["volunteer", "hours", "signup", "helper"]),
("pta_meeting", "PTA meetings and officers", ["pta", "meeting", "officer", "dues"]),
("textbook", "textbook and supply lists", ["textbook", "supply", "list", "checkout"]),
("locker", "locker assignments and rules", ["locker", "combination", "assignment", "hall"]),
("dress_code", "dress code and spirit days", ["dress code", "spirit", "attire", "jeans"]),
("concert_tickets", "event ticket sales and seating", ["tickets", "seating", "presale", "entry"]),
("yearbook", "yearbook ads, photos and delivery", ["yearbook", "ad", "delivery", "super senior"]),
("litter_pickup", "litter and grounds volunteer days", ["litter", "grounds", "cleanup", "volunteer"]),
("garden", "school garden planting and care", ["garden", "plant", "soil", "harvest"]),
("astronomy_night", "stargazing and planetarium trips", ["astronomy", "stars", "planetarium", "telescope"]),
("museums_late", "museum nights and docent tours", ["museum", "docent", "tour", "gallery"]),
("wellness", "wellness checks and health screenings", ["wellness", "health", "screening", "check"]),
("tech_help", "device help and wifi support", ["device", "wifi", "chromebook", "support"]),
("hall_pass", "hall pass and study hall policies", ["hall pass", "study hall", "pass", "policy"]),
("lunch_duty", "duty rosters and coverage", ["duty", "lunch duty", "coverage", "roster"]),
("staff_appreciation", "staff thank-you and Appreciation Week", ["staff", "appreciation", "thank you", "week"]),
]
HARD = {"soccer", "sports_general", "swim_club", "art_club", "crafts_club", "music_band",
"choir", "flu_report", "allergies", "bake_sale", "fundraiser"}
FILLERS = [
"Please reply-all if anything changed since last week.",
"Mrs. Delgado left this note for the whole distribution list.",
"Reminder forwarded from the office with no further edits.",
"The link in the original message was left out of this copy.",
"This item repeats in three separate newsletters this month.",
"A follow-up message may or may not arrive later.",
"Someone on the list has asked to be removed after the fact.",
"The date appears twice and the two dates disagree.",
]
EVENTS = ["reminder", "update", "signup now", "schedule change", "final call", "quick note"]
def make_text(rng, topic):
kws = TOPIC_KW[topic]
bits = [rng.choice(kws), rng.choice(kws)]
rng.shuffle(bits)
text = f"{topic.replace('_', ' ').title()}: {rng.choice(EVENTS)} - {bits[0]} and {bits[1]} this week."
if rng.random() < 0.35:
text += f" Also mention {rng.choice(kws)} briefly near the end."
if rng.random() < 0.30:
text += " " + rng.choice(FILLERS)
if rng.random() < 0.12:
text += " " + " ".join([rng.choice(FILLERS), rng.choice(FILLERS)])
return text
def main(out_dir):
rng = random.Random(42)
os.makedirs(out_dir, exist_ok=True)
global TOPIC_KW
TOPIC_KW = {t[0]: t[2] for t in TOPICS}
names = [t[0] for t in TOPICS]
assert len(names) == 56, len(names)
taxonomy = {name: desc for name, desc, _ in TOPICS}
with open(os.path.join(out_dir, "topic_taxonomy.json"), "w") as f:
json.dump(taxonomy, f, indent=2)
# skewed train counts: a few big topics, a long tail, 14 topics with no train items
train_pool = [n for n in names if n not in rng.sample(names, 14)]
counts = {}
remaining = 279
for i, n in enumerate(train_pool):
w = rng.choice([1, 1, 2, 2, 3, 5]) if n in HARD else rng.choice([1, 1, 1, 2, 2, 4, 7])
counts[n] = max(1, w)
scale = remaining / sum(counts.values())
counts = {n: max(1, int(c * scale + 0.5)) for n, c in counts.items()}
# land exactly on 279, keeping every kept topic >= 1 item
order = [n for n in train_pool if counts[n] > 1]
i = 0
while sum(counts.values()) != 279:
n = order[i % len(order)]
if sum(counts.values()) < 279:
counts[n] += 1
else:
counts[n] -= 1
i += 1
idx = 0
def emit(split, topic):
nonlocal idx
idx += 1
return {"id": f"toy-{idx:04d}", "email_id": f"email-{idx:03d}",
"text": make_text(rng, topic), "jev_topic": topic, "human_topic": None}
train = [emit("train", n) for n, c in counts.items() for _ in range(c)]
assert len(train) == 279, len(train)
rng.shuffle(train)
# val + test: cover all 56 topics; unseen topics appear only here
val, test = [], []
for n in names:
for _ in range(1):
val.append(emit("validation", n))
while len(val) < 85:
val.append(emit("validation", rng.choice(names)))
for n in names:
test.append(emit("test", n))
while len(test) < 105:
test.append(emit("test", rng.choice(names)))
for split, rows in [("train", train), ("validation", val), ("test", test)]:
with open(os.path.join(out_dir, f"{split}.jsonl"), "w") as f:
for r in rows:
f.write(json.dumps(r) + "\n")
print(split, len(rows))
big = sorted(counts.items(), key=lambda kv: -kv[1])[:5]
print("busiest topics:", big, "| train topics:", len(counts), "/56")
if __name__ == "__main__":
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("--out", default="data")
main(ap.parse_args().out)
|