626 lines
36 KiB
Python
626 lines
36 KiB
Python
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]
|
|
|
|
# (kind, text, chain_order) -- chain_order is the sentence's position in the
|
|
# causal chain (0..n_related-1) for related sentences, None for distractors.
|
|
# Tracked explicitly rather than re-derived by text lookup, since two related
|
|
# sentences can render identical text (a "middle" phrasing can repeat when a
|
|
# long chain draws the same template variant twice).
|
|
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 # joining space
|
|
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")
|