| import json |
| import random |
|
|
| TOPICS = { |
| "agriculture": dict( |
| tuples=[ |
| ("a wheat field", "nitrogen runoff from the upper terrace", "soil microbes convert the excess nitrogen into nitrous oxide", |
| "the crop canopy yellows and grain yield drops", "kg of nitrogen per hectare", 180), |
| ("a dairy herd", "a switch to a higher-protein feed mix", "rumen bacteria shift their fermentation balance", |
| "milk yield per cow rises measurably", "grams of crude protein per kilogram of feed", 160), |
| ("an orchard", "three consecutive nights below freezing", "cell walls in the blossom tissue rupture as ice forms", |
| "the season's fruit set collapses", "consecutive hours below zero Celsius", 5), |
| ], |
| distractors=[ |
| "Crop rotation schedules are usually planned a full year in advance.", |
| "Most combine harvesters are serviced at the start of the season.", |
| "Farmers markets in the region open every Saturday morning.", |
| "Irrigation pipes are typically replaced every eight to ten years.", |
| "The regional agricultural fair is held in the first week of autumn.", |
| "Tractor fuel prices are tracked weekly by the local cooperative.", |
| "Seed catalogs are printed and distributed each winter.", |
| "The county extension office publishes a soil-testing guide annually.", |
| ], |
| ), |
| "archaeology": dict( |
| tuples=[ |
| ("a coastal midden site", "a drop in sea level exposing a wider tidal flat", "shellfish became easier to harvest in bulk", |
| "the shell-deposit layer thickens abruptly in the stratigraphy", "meters of relative sea level change", 2), |
| ("a burial mound", "the introduction of bronze tools to the region", "grave goods shift from stone to metal implements", |
| "the mound's later strata show a marked wealth increase", "years after the estimated trade contact date", 50), |
| ("a hilltop settlement", "a prolonged drought recorded in nearby pollen cores", "residents abandoned the lower fields for terraced ones", |
| "terrace wall construction spikes in the same layer", "consecutive dry seasons in the pollen record", 4), |
| ], |
| distractors=[ |
| "Radiocarbon dating labs typically report results within a few weeks.", |
| "Site excavation permits must be renewed every field season.", |
| "Most trowels used on digs are a standard four-inch size.", |
| "The museum's ceramics collection is catalogued by region.", |
| "Field notebooks are archived at the university at the end of each dig.", |
| "Ground-penetrating radar surveys are usually done before excavation begins.", |
| "Volunteer diggers are trained during the first two days on site.", |
| "The site's screening station uses quarter-inch mesh sieves.", |
| ], |
| ), |
| "architecture": dict( |
| tuples=[ |
| ("a steel-framed tower", "a redesign that widened the base footprint", "wind load transfers more evenly through the lower trusses", |
| "the building's sway amplitude drops in monitoring data", "millimeters of lateral sway per floor", 12), |
| ("an old masonry chapel", "decades of rising groundwater", "the foundation stone slowly wicks moisture upward", |
| "salt crystallization begins spalling the lower courses", "percent moisture content in the stone", 6), |
| ("a glass atrium", "the addition of a double-skin facade", "trapped air between the layers buffers temperature swings", |
| "interior heating costs fall noticeably each winter", "degrees Celsius of daily temperature swing", 8), |
| ], |
| distractors=[ |
| "Blueprints are typically archived digitally after project completion.", |
| "Most building codes are revised on a multi-year cycle.", |
| "Scaffolding rental rates vary by region and season.", |
| "Structural steel is usually delivered pre-cut to the site.", |
| "Site inspections are scheduled at fixed construction milestones.", |
| "Architectural firms often specialize by building type.", |
| "Concrete curing typically takes about four weeks to reach full strength.", |
| "Elevator installation is usually one of the last interior trades.", |
| ], |
| ), |
| "astronomy": dict( |
| tuples=[ |
| ("a red giant star", "the exhaustion of core hydrogen fuel", "the core contracts while the outer layers expand and cool", |
| "the star's luminosity climbs while its surface temperature falls", "solar radii of expansion", 100), |
| ("a binary star system", "tidal forces from the closer companion", "the primary star's rotation gradually locks to the orbit", |
| "the system's brightness curve becomes strictly periodic", "days of orbital period", 3), |
| ("a protoplanetary disk", "a nearby supernova shockwave passing through", "dust grains compress into denser clumps", |
| "planetesimal formation accelerates in the disk's outer edge", "astronomical units from the shock front", 50), |
| ], |
| distractors=[ |
| "Most large telescopes are scheduled for observing time months ahead.", |
| "Star catalogs are updated periodically as new surveys complete.", |
| "Amateur astronomy clubs often meet on the new moon each month.", |
| "Satellite launch windows depend on orbital mechanics constraints.", |
| "Observatory domes are usually repainted every several years.", |
| "Telescope mirrors are re-coated with aluminum periodically.", |
| "Astronomical almanacs are published a year in advance.", |
| "Radio telescope arrays require precise time synchronization.", |
| ], |
| ), |
| "automotive": dict( |
| tuples=[ |
| ("a turbocharged engine", "prolonged high-load highway driving", "exhaust gas temperatures stay elevated for longer stretches", |
| "turbocharger bearing wear accelerates measurably", "degrees Celsius above the rated exhaust temperature", 50), |
| ("an electric vehicle battery pack", "repeated fast-charging cycles", "internal cell resistance rises with each cycle", |
| "usable range drops noticeably over the following months", "fast-charge cycles logged", 200), |
| ("a timing belt", "an extended interval past the service recommendation", "the rubber compound stiffens and micro-cracks form", |
| "the belt eventually skips a tooth under load", "kilometers past the recommended interval", 20000), |
| ], |
| distractors=[ |
| "Tire rotations are usually recommended every ten thousand kilometers.", |
| "Most dealerships offer scheduled maintenance reminders by text.", |
| "Car paint colors are mixed to match a standard code system.", |
| "Windshield wiper blades are typically replaced once a year.", |
| "Vehicle registration renewals are handled annually in most regions.", |
| "Cabin air filters are usually swapped during routine servicing.", |
| "Most modern cars log diagnostic codes automatically.", |
| "Showroom floor models are rotated seasonally.", |
| ], |
| ), |
| "aviation": dict( |
| tuples=[ |
| ("a commercial airliner wing", "repeated pressurization cycles over its service life", "microscopic fatigue cracks accumulate near rivet holes", |
| "scheduled inspections start flagging crack growth", "pressurization cycles logged", 20000), |
| ("a turboprop engine", "prolonged operation in dusty air", "sand particles erode the compressor blade edges", |
| "fuel efficiency drops across the fleet's logbooks", "flight hours in high-dust conditions", 500), |
| ("a glider", "a thermal column strengthening over open ground", "rising warm air lifts the aircraft faster than sink rate", |
| "the pilot gains several hundred meters of altitude quickly", "meters per second of climb rate", 3), |
| ], |
| distractors=[ |
| "Airport runway markings are repainted on a routine schedule.", |
| "Most airlines rotate cabin crew assignments monthly.", |
| "Boarding passes are typically issued starting ninety minutes before departure.", |
| "Aircraft livery redesigns happen every several years.", |
| "Baggage handling systems are inspected regularly by ground staff.", |
| "Flight simulators are recalibrated on a fixed maintenance cycle.", |
| "Air traffic control shifts typically rotate every eight hours.", |
| "Most airports publish noise abatement procedures publicly.", |
| ], |
| ), |
| "chemistry": dict( |
| tuples=[ |
| ("a buffered solution", "the slow addition of a strong acid", "the buffer's conjugate base is gradually consumed", |
| "the pH begins to drop sharply past the buffer capacity", "moles of acid added per liter", 0.5), |
| ("a polymer sample", "prolonged exposure to ultraviolet light", "chain scission breaks the polymer backbone", |
| "the material becomes visibly brittle within weeks", "hours of UV exposure", 200), |
| ("a metal alloy", "repeated thermal cycling in a furnace", "microstructural grain boundaries migrate and coarsen", |
| "the alloy's tensile strength falls measurably", "thermal cycles applied", 100), |
| ], |
| distractors=[ |
| "Laboratory glassware is calibrated according to a fixed standard.", |
| "Most reagent bottles include a manufacture and expiry date.", |
| "Fume hood airflow is checked on a routine safety schedule.", |
| "Chemical inventories are typically audited once a year.", |
| "Titration equipment is rinsed with distilled water between trials.", |
| "Safety data sheets are updated when formulations change.", |
| "Most balances are recalibrated with certified reference weights.", |
| "Waste solvent is collected in labeled containers by class.", |
| ], |
| ), |
| "cooking": dict( |
| tuples=[ |
| ("a pot of caramelizing sugar", "the temperature crossing into the amber range", "sucrose molecules break down into hundreds of new flavor compounds", |
| "the sugar rapidly darkens and turns bitter if left further", "degrees Celsius on the sugar thermometer", 170), |
| ("a sourdough starter", "several days of regular feeding at room temperature", "wild yeast and lactobacilli populations stabilize", |
| "the dough begins to reliably double in size", "consecutive days of feeding", 5), |
| ("a batch of bread dough", "over-proofing past the recommended window", "the gluten network stretches until it can no longer hold gas", |
| "the loaf collapses instead of rising in the oven", "minutes past the recommended proof time", 45), |
| ], |
| distractors=[ |
| "Most home ovens run a few degrees off their dial setting.", |
| "Recipe books are often organized by course or season.", |
| "Kitchen knives should be sharpened on a regular schedule.", |
| "Farmers markets tend to have the freshest produce early in the day.", |
| "Most stand mixers include several interchangeable attachments.", |
| "Spice jars are typically restocked every few months in a busy kitchen.", |
| "Cast iron pans need occasional reseasoning to stay non-stick.", |
| "Restaurant menus are usually revised on a seasonal basis.", |
| ], |
| ), |
| "energy": dict( |
| tuples=[ |
| ("a wind farm", "a sustained increase in average wind speed", "turbine blades pass into a higher-efficiency operating range", |
| "the farm's total output climbs sharply that week", "meters per second of average wind speed", 12), |
| ("a lithium-ion grid battery", "back-to-back deep discharge cycles", "internal electrode stress accumulates with each cycle", |
| "the battery's usable capacity fades measurably", "deep discharge cycles logged", 300), |
| ("a solar array", "a thin layer of dust accumulating on the panels", "less sunlight reaches the photovoltaic cells", |
| "the array's daily output drops steadily until cleaning", "days since the last panel cleaning", 30), |
| ], |
| distractors=[ |
| "Utility meters are typically read on a monthly cycle.", |
| "Most power plants schedule maintenance outages seasonally.", |
| "Energy tariffs are usually reviewed by regulators annually.", |
| "Transmission towers are inspected on a routine rotation.", |
| "Grid operators publish demand forecasts a day ahead.", |
| "Substation transformers are tested periodically for insulation health.", |
| "Most utilities offer time-of-use billing plans.", |
| "Renewable energy certificates are tracked in a public registry.", |
| ], |
| ), |
| "finance": dict( |
| tuples=[ |
| ("a corporate bond fund", "a central bank rate hike announcement", "existing bond prices adjust downward to match new yields", |
| "the fund's net asset value drops within the trading day", "basis points of rate increase", 50), |
| ("a retail brokerage", "a surge in first-time account signups", "order flow volume exceeds the matching engine's normal capacity", |
| "trade execution latency spikes during peak hours", "thousands of new accounts opened", 50), |
| ("a commodity futures desk", "a supply disruption reported upstream", "traders rapidly reprice the expected scarcity", |
| "the futures contract gaps up at the next open", "percent reported supply shortfall", 10), |
| ], |
| distractors=[ |
| "Most stock exchanges publish a holiday trading calendar yearly.", |
| "Quarterly earnings reports are typically released on a set schedule.", |
| "Bank branch hours vary somewhat by location.", |
| "Most brokerages send account statements on a monthly basis.", |
| "Credit rating agencies review issuers periodically.", |
| "Tax filing deadlines are set annually by the relevant authority.", |
| "Financial regulators publish guidance documents from time to time.", |
| "Most index funds rebalance their holdings quarterly.", |
| ], |
| ), |
| "gardening": dict( |
| tuples=[ |
| ("a raised vegetable bed", "a stretch of days above the seedlings' heat tolerance", "leaf stomata close to conserve water", |
| "growth visibly stalls until temperatures ease", "consecutive days above the tolerance threshold", 3), |
| ("a rose bush", "a sudden increase in humid, still air", "fungal spores find ideal conditions on the leaf surface", |
| "black spot lesions spread across the foliage within days", "percent relative humidity sustained", 85), |
| ("a compost pile", "the pile crossing its optimal carbon-to-nitrogen ratio", "thermophilic bacteria multiply rapidly", |
| "the pile's internal temperature rises sharply", "carbon-to-nitrogen ratio by weight", 30), |
| ], |
| distractors=[ |
| "Garden hoses are usually stored indoors over winter.", |
| "Most seed packets list a recommended planting depth.", |
| "Compost bins come in a variety of standard sizes.", |
| "Pruning shears should be cleaned between uses on different plants.", |
| "Garden centers restock seasonal plants on a rotating basis.", |
| "Mulch is typically reapplied once or twice a year.", |
| "Most watering cans hold around two gallons.", |
| "Raised beds are commonly built from untreated cedar.", |
| ], |
| ), |
| "hardware": dict( |
| tuples=[ |
| ("a data center rack", "sustained load pushing CPUs past their thermal design point", "cooling fans ramp to maximum and can no longer keep pace", |
| "the rack's servers begin throttling clock speed", "degrees Celsius above the thermal design point", 15), |
| ("a solid-state drive", "a high volume of small random writes", "flash cell wear accumulates unevenly across blocks", |
| "write latency climbs noticeably as the drive ages", "terabytes written past the rated endurance", 100), |
| ("a mechanical keyboard switch", "tens of thousands of actuations", "the internal spring gradually loses tension", |
| "the keypress force needed drops noticeably", "thousands of actuations logged", 50), |
| ], |
| distractors=[ |
| "Most warehouses stock components in labeled bins by part number.", |
| "Firmware updates are typically released on an irregular schedule.", |
| "Server racks are usually bolted to the floor for stability.", |
| "Cable management trays come in several standard widths.", |
| "Most retailers offer a standard return window on hardware.", |
| "Component datasheets are published by the manufacturer.", |
| "Static-safe packaging is standard for sensitive electronics.", |
| "Warehouse inventory counts are usually done quarterly.", |
| ], |
| ), |
| "marine": dict( |
| tuples=[ |
| ("a coral reef", "a sustained rise in sea surface temperature", "the coral expels its symbiotic algae under heat stress", |
| "large sections of the reef turn visibly white within weeks", "degrees Celsius above the seasonal average", 2), |
| ("a container ship's hull", "months of continuous operation without dry-docking", "barnacles and algae accumulate along the waterline", |
| "fuel consumption per nautical mile rises steadily", "months since the last hull cleaning", 8), |
| ("a coastal fishery", "a multi-year drop in plankton density", "juvenile fish find less food during their critical growth window", |
| "the following season's catch reports decline sharply", "percent drop in plankton density", 30), |
| ], |
| distractors=[ |
| "Most harbors schedule dredging on a multi-year cycle.", |
| "Fishing licenses are typically renewed annually.", |
| "Cargo ships file manifests before entering port.", |
| "Marine weather buoys report data at regular intervals.", |
| "Lighthouse maintenance is usually handled by a coastal authority.", |
| "Most ports have a designated pilot service for large vessels.", |
| "Nautical charts are updated periodically by hydrographic offices.", |
| "Ferry schedules are adjusted seasonally in most regions.", |
| ], |
| ), |
| "music": dict( |
| tuples=[ |
| ("a violin's spruce top", "a sustained drop in the practice room's humidity", "the wood fibers contract unevenly across the grain", |
| "the instrument's tone becomes noticeably brighter and thinner", "percent relative humidity drop", 20), |
| ("a concert hall", "an increase in the audience's seat occupancy", "sound absorption from clothing and bodies rises", |
| "the room's reverberation time shortens measurably", "percent of seats occupied", 80), |
| ("a guitar string", "hundreds of hours of playing", "metal fatigue builds at the string's contact points", |
| "intonation drifts noticeably higher up the neck", "hours of cumulative playing time", 100), |
| ], |
| distractors=[ |
| "Most concert venues sell tickets starting months in advance.", |
| "Instrument cases are typically lined with soft padding.", |
| "Sheet music publishers release new arrangements regularly.", |
| "Orchestras usually rehearse on a fixed weekly schedule.", |
| "Most recording studios book sessions by the hour.", |
| "Music festivals are often scheduled around the summer season.", |
| "Piano tuners typically visit on a seasonal basis.", |
| "Streaming platforms update their charts weekly.", |
| ], |
| ), |
| "photography": dict( |
| tuples=[ |
| ("a camera sensor", "a long exposure in warm ambient conditions", "thermal noise accumulates across the sensor's photosites", |
| "grain becomes visibly obvious in the shadow regions", "seconds of exposure time", 30), |
| ("a batch of darkroom prints", "developer chemistry used past its stated capacity", "silver halide reduction slows unevenly across the tray", |
| "the later prints come out visibly flatter in contrast", "prints processed past rated capacity", 20), |
| ("a telephoto lens", "repeated rapid temperature changes between indoor and outdoor shoots", "internal condensation forms between the glass elements", |
| "autofocus accuracy degrades until the lens fully equalizes", "degrees Celsius of temperature swing", 15), |
| ], |
| distractors=[ |
| "Most camera bodies receive firmware updates periodically.", |
| "Photography clubs often meet monthly to review member work.", |
| "Memory cards should be formatted in-camera before a shoot.", |
| "Studio lighting kits usually include several modifiers.", |
| "Print labs typically offer same-day service for standard sizes.", |
| "Lens caps come in a range of standard thread sizes.", |
| "Most galleries rotate their exhibited work seasonally.", |
| "Camera straps are usually sold separately from the body.", |
| ], |
| ), |
| "physiology": dict( |
| tuples=[ |
| ("a marathon runner's muscles", "the glycogen stores dropping past a critical level", "the body shifts toward burning fat for fuel less efficiently", |
| "pace drops sharply in the final miles", "percent of glycogen stores remaining", 20), |
| ("the human eye", "a sudden move from bright sunlight into a dark room", "rod cells slowly regenerate their light-sensitive pigment", |
| "night vision gradually improves over several minutes", "minutes spent in the dark", 10), |
| ("a person's core temperature", "prolonged exposure to cold water", "peripheral blood vessels constrict to preserve heat", |
| "shivering intensifies sharply once core temperature drops further", "degrees Celsius drop in core temperature", 2), |
| ], |
| distractors=[ |
| "Most clinics schedule routine checkups on an annual basis.", |
| "Blood donation centers typically operate by appointment.", |
| "Standard hospital shifts often run twelve hours.", |
| "Vaccination schedules are usually published by health authorities.", |
| "Most gyms offer orientation sessions for new members.", |
| "Medical textbooks are revised every several years.", |
| "Physical therapy sessions are commonly booked weekly.", |
| "Hospitals typically restock supplies on a set schedule.", |
| ], |
| ), |
| "sports": dict( |
| tuples=[ |
| ("a marathon field of runners", "an unexpected rise in race-day temperature", "the body's cooling demand outpaces sweat evaporation", |
| "average finishing times slow noticeably across the field", "degrees Celsius above the forecast temperature", 8), |
| ("a basketball team's shooting", "a long losing streak affecting practice attendance", "shot repetition drops below the usual weekly volume", |
| "free-throw percentage declines over the following games", "percent drop in practice attendance", 30), |
| ("a cyclist's legs", "repeated high-intensity interval sessions", "muscle fibers adapt by increasing capillary density", |
| "sustained power output improves over the following weeks", "interval sessions completed", 12), |
| ], |
| distractors=[ |
| "Most stadiums are inspected before the season opener.", |
| "Ticket prices for major events often rise closer to game day.", |
| "League schedules are typically published months in advance.", |
| "Team equipment is usually replaced on a yearly cycle.", |
| "Most broadcasters rotate commentary teams by season.", |
| "Referee certifications are renewed periodically.", |
| "Youth leagues commonly practice twice a week.", |
| "Sports medicine clinics often see a rise in visits after tournaments.", |
| ], |
| ), |
| "textiles": dict( |
| tuples=[ |
| ("a wool sweater", "repeated washing in hot water", "the fiber's scale structure locks together under agitation", |
| "the garment shrinks noticeably after a handful of washes", "hot washes completed", 5), |
| ("a length of dyed cotton", "extended exposure to direct sunlight", "UV energy breaks down the dye's chromophore bonds", |
| "the fabric visibly fades within a season outdoors", "weeks of continuous sun exposure", 8), |
| ("a synthetic rope", "sustained load near its rated breaking strength", "polymer chains slowly creep and elongate under tension", |
| "the rope's diameter thins measurably before failure", "percent of rated breaking strength sustained", 80), |
| ], |
| distractors=[ |
| "Most fabric mills run on a standard weekday production schedule.", |
| "Sewing machines require periodic oiling for smooth operation.", |
| "Textile trade shows are usually held twice a year.", |
| "Dye lots can vary slightly between production batches.", |
| "Most tailors take measurements at the first fitting.", |
| "Fabric bolts are typically sold by the meter or yard.", |
| "Washing labels are sewn in during final garment assembly.", |
| "Cotton harvests are usually processed within weeks of picking.", |
| ], |
| ), |
| "weather": dict( |
| tuples=[ |
| ("a coastal town", "a sustained drop in atmospheric pressure offshore", "warm moist air rises rapidly ahead of the front", |
| "rainfall rates spike sharply within the hour", "hectopascals of pressure drop", 10), |
| ("a mountain valley", "cold air pooling overnight in the low ground", "a temperature inversion traps surface pollutants", |
| "morning visibility drops sharply until the sun burns it off", "degrees Celsius of inversion strength", 6), |
| ("a wheat-growing region", "several consecutive weeks without meaningful rainfall", "topsoil moisture falls below the crop's wilting point", |
| "regional yield forecasts are revised downward", "consecutive weeks without rain", 6), |
| ], |
| distractors=[ |
| "Weather stations typically report data every ten minutes.", |
| "Most forecasting models are updated several times a day.", |
| "Meteorologists often specialize in a particular region.", |
| "Weather balloons are usually launched twice daily.", |
| "Storm shutters are commonly installed before the season starts.", |
| "Local news broadcasts typically include a weather segment nightly.", |
| "Rain gauges need periodic calibration to stay accurate.", |
| "Climate records are archived by national weather agencies.", |
| ], |
| ), |
| "wildlife": dict( |
| tuples=[ |
| ("a migratory bird population", "an early snowmelt shifting the season's timing", "insect hatches occur before the birds arrive to feed on them", |
| "chick survival rates drop that breeding season", "days earlier than the historical snowmelt date", 12), |
| ("a deer herd", "a sharp increase in a nearby predator's population", "deer shift toward denser cover during daylight hours", |
| "browsing damage patterns move deeper into the forest interior", "percent increase in predator sightings", 40), |
| ("a coral-reef fish population", "a marine heatwave lasting several weeks", "the fish's usual prey species retreat to deeper water", |
| "the population's average body condition declines that season", "weeks of sustained heatwave conditions", 6), |
| ], |
| distractors=[ |
| "Wildlife surveys are typically conducted on a seasonal schedule.", |
| "Most nature reserves restrict access during nesting season.", |
| "Camera traps are usually checked every few weeks.", |
| "Conservation permits are renewed on an annual basis.", |
| "Field biologists often tag animals for long-term tracking.", |
| "National parks publish visitor guidelines each season.", |
| "Most zoos rotate enrichment activities for their animals.", |
| "Migration routes are mapped using satellite tracking data.", |
| ], |
| ), |
| } |
|
|
| TEMPLATES = { |
| "A": dict( |
| label="Threshold-based change", |
| opening=[ |
| "In {subject}, {driver} began building up over time.", |
| "{subject_cap} experienced a steady buildup driven by {driver}.", |
| ], |
| middle=[ |
| "As the buildup continued, {mechanism}.", |
| "Over the following period, {mechanism}.", |
| "This kept accumulating until {mechanism}.", |
| ], |
| closing=[ |
| "Once the value passed roughly {threshold} {unit}, {outcome}.", |
| "After crossing about {threshold} {unit}, {outcome}.", |
| ], |
| ), |
| "B": dict( |
| label="Comparative experiments", |
| opening=[ |
| "Researchers compared {subject} under two conditions after introducing {driver}.", |
| "A side-by-side trial on {subject} isolated the effect of {driver}.", |
| ], |
| middle=[ |
| "In the treated condition, {mechanism}.", |
| "Compared to the control group, {mechanism}.", |
| "The treated samples showed that {mechanism}.", |
| ], |
| closing=[ |
| "By the time the difference reached about {threshold} {unit}, {outcome}.", |
| "Once the measured gap hit roughly {threshold} {unit}, {outcome}.", |
| ], |
| ), |
| "C": dict( |
| label="Cumulative exposure", |
| opening=[ |
| "{subject_cap} was subjected to repeated exposure to {driver}.", |
| "Over many repeated cycles, {subject} was affected by {driver}.", |
| ], |
| middle=[ |
| "With each additional cycle, {mechanism}.", |
| "The cumulative effect meant that {mechanism}.", |
| "Repetition gradually meant that {mechanism}.", |
| ], |
| closing=[ |
| "After accumulating about {threshold} {unit}, {outcome}.", |
| "Once the cumulative total reached {threshold} {unit}, {outcome}.", |
| ], |
| ), |
| "D": dict( |
| label="Sudden spike events", |
| opening=[ |
| "A sudden spike in {driver} hit {subject} without much warning.", |
| "{subject_cap} was hit by an abrupt spike tied to {driver}.", |
| ], |
| middle=[ |
| "In the immediate aftermath, {mechanism}.", |
| "Within a short window, {mechanism}.", |
| "The sudden change meant that {mechanism}.", |
| ], |
| closing=[ |
| "Since the spike exceeded {threshold} {unit}, {outcome}.", |
| "Because the spike passed {threshold} {unit}, {outcome}.", |
| ], |
| ), |
| "E": dict( |
| label="Threshold-crossing dynamics", |
| opening=[ |
| "{subject_cap} was tracked closely as {driver} slowly changed.", |
| "Monitoring of {subject} focused on the slow trend in {driver}.", |
| ], |
| middle=[ |
| "As the trend continued, {mechanism}.", |
| "Approaching the critical point, {mechanism}.", |
| "Just before the crossing, {mechanism}.", |
| ], |
| closing=[ |
| "The moment it crossed {threshold} {unit}, {outcome}.", |
| "Right after crossing {threshold} {unit}, {outcome}.", |
| ], |
| ), |
| "F": dict( |
| label="Feedback loops", |
| opening=[ |
| "A feedback loop formed in {subject} once {driver} took hold.", |
| "{subject_cap} developed a self-reinforcing loop starting from {driver}.", |
| ], |
| middle=[ |
| "Each pass through the loop meant that {mechanism}.", |
| "The loop reinforced itself as {mechanism}.", |
| "With every iteration, {mechanism}.", |
| ], |
| closing=[ |
| "Once the loop's strength passed {threshold} {unit}, {outcome}.", |
| "After the feedback exceeded {threshold} {unit}, {outcome}.", |
| ], |
| ), |
| } |
|
|
| DIFFICULTIES = {"easy": 2, "medium": 3, "hard": 4} |
|
|
| QUESTION_FORMS = [ |
| "What was the underlying cause behind the change described for {subject}?", |
| "Which factor most directly explains the outcome observed in {subject}?", |
| "According to the passage, what triggered the eventual change in {subject}?", |
| ] |
|
|
|
|
| def _cap(s): |
| return s[0].upper() + s[1:] |
|
|
|
|
| def _jitter(value): |
| if isinstance(value, float): |
| return round(value * random.uniform(0.85, 1.15), 2) |
| return max(1, int(round(value * random.uniform(0.85, 1.15)))) |
|
|
|
|
| def _render_chain(topic, template_id, n_edges, rng): |
| tpl = TEMPLATES[template_id] |
| subj, driver, mech, outcome, unit, threshold = rng.choice(TOPICS[topic]["tuples"]) |
| threshold = _jitter(threshold) |
| slots = dict(subject=subj, subject_cap=_cap(subj), driver=driver, mechanism=mech, |
| outcome=outcome, unit=unit, threshold=threshold) |
|
|
| n_nodes = n_edges + 1 |
| sentences = [rng.choice(tpl["opening"]).format(**slots)] |
| for _ in range(n_nodes - 2): |
| sentences.append(rng.choice(tpl["middle"]).format(**slots)) |
| sentences.append(rng.choice(tpl["closing"]).format(**slots)) |
| return sentences, slots |
|
|
|
|
| def generate_item(item_id, rng): |
| topic = rng.choice(list(TOPICS.keys())) |
| template_id = rng.choice(list(TEMPLATES.keys())) |
| difficulty = rng.choice(list(DIFFICULTIES.keys())) |
| n_edges = DIFFICULTIES[difficulty] |
|
|
| related_sentences, slots = _render_chain(topic, template_id, n_edges, rng) |
| n_related = len(related_sentences) |
|
|
| bank = list(TOPICS[topic]["distractors"]) |
| rng.shuffle(bank) |
| distractor_sentences = bank[:n_related] if len(bank) >= n_related else (bank * 2)[:n_related] |
|
|
| |
| |
| |
| |
| |
| seg_specs = [] |
| for order, s in enumerate(related_sentences): |
| seg_specs.append(("related", s, order)) |
| for s in distractor_sentences: |
| seg_specs.append(("distractor", s, None)) |
| rng.shuffle(seg_specs) |
|
|
| segments = [] |
| context_parts = [] |
| cursor = 0 |
| related_ids, unrelated_ids = [], [] |
| related_seg_id_in_order = [] |
| for i, (kind, text, chain_order) in enumerate(seg_specs): |
| if context_parts: |
| cursor += 1 |
| start = cursor |
| end = start + len(text) |
| seg_id = i |
| segments.append({"id": seg_id, "charStart": start, "charEnd": end}) |
| if kind == "related": |
| related_ids.append(seg_id) |
| related_seg_id_in_order.append((chain_order, seg_id)) |
| else: |
| unrelated_ids.append(seg_id) |
| context_parts.append(text) |
| cursor = end |
|
|
| context = " ".join(context_parts) |
|
|
| related_seg_id_in_order.sort() |
| ordered_related_ids = [seg_id for _, seg_id in related_seg_id_in_order] |
| key_link_pairs = [[ordered_related_ids[i], ordered_related_ids[i + 1]] for i in range(len(ordered_related_ids) - 1)] |
|
|
| question = rng.choice(QUESTION_FORMS).format(subject=slots["subject"]) |
| reference_answer = f"{_cap(slots['driver'])}, via {slots['mechanism']}, led to {slots['outcome']}." |
|
|
| return { |
| "id": f"synth-{item_id:06d}", |
| "topic": topic, |
| "topicLabel": topic.capitalize(), |
| "templateId": template_id, |
| "difficulty": difficulty, |
| "question": question, |
| "referenceAnswer": reference_answer, |
| "context": context, |
| "segments": segments, |
| "relatedSegmentIds": related_ids, |
| "unrelatedSegmentIds": unrelated_ids, |
| "keyLinkPairs": key_link_pairs, |
| "meta": { |
| "subject": slots["subject"], "driver": slots["driver"], "mechanism": slots["mechanism"], |
| "outcome": slots["outcome"], "threshold": slots["threshold"], "unit": slots["unit"], |
| }, |
| } |
|
|
|
|
| def generate_dataset(n, seed=0): |
| rng = random.Random(seed) |
| return [generate_item(i, rng) for i in range(n)] |
|
|
|
|
| if __name__ == "__main__": |
| import sys |
| n = int(sys.argv[1]) if len(sys.argv) > 1 else 10 |
| items = generate_dataset(n, seed=42) |
| for it in items[:3]: |
| print(json.dumps(it, indent=2)) |
| print(f"\ngenerated {len(items)} items") |
|
|