"""Task specs for webgym: every (kind, seed) deterministically yields labels, widget implementations, layout, target values, the natural-language goal and planner-style sub-goals. Seeds with seed % 10 == 0 are held out for evaluation. """ import datetime as dt import random CITIES = [("zrh", "Zürich", "Switzerland", "ZRH", "Zurich"), ("lon", "London", "United Kingdom", "LHR", ""), ("par", "Paris", "France", "CDG", ""), ("ber", "Berlin", "Germany", "BER", ""), ("mad", "Madrid", "Spain", "MAD", ""), ("rom", "Rome", "Italy", "FCO", "Roma"), ("ams", "Amsterdam", "Netherlands", "AMS", ""), ("vie", "Vienna", "Austria", "VIE", "Wien"), ("muc", "Munich", "Germany", "MUC", "München"), ("lis", "Lisbon", "Portugal", "LIS", "Lisboa"), ("prg", "Prague", "Czechia", "PRG", "Praha"), ("dub", "Dublin", "Ireland", "DUB", ""), ("osl", "Oslo", "Norway", "OSL", ""), ("hel", "Helsinki", "Finland", "HEL", ""), ("bcn", "Barcelona", "Spain", "BCN", ""), ("nyc", "New York", "United States", "JFK", ""), ("chi", "Chicago", "United States", "ORD", ""), ("tor", "Toronto", "Canada", "YYZ", ""), ("tyo", "Tokyo", "Japan", "HND", ""), ("sel", "Seoul", "South Korea", "ICN", ""), ("sin", "Singapore", "Singapore", "SIN", ""), ("bkk", "Bangkok", "Thailand", "BKK", ""), ("syd", "Sydney", "Australia", "SYD", ""), ("ist", "Istanbul", "Türkiye", "IST", ""), ("mil", "Milan", "Italy", "MXP", "Milano"), ("man", "Manchester", "United Kingdom", "MAN", ""), ("mia", "Miami", "United States", "MIA", ""), ("ath", "Athens", "Greece", "ATH", ""), ("waw", "Warsaw", "Poland", "WAW", ""), ("cph", "Copenhagen", "Denmark", "CPH", "")] TODAY = dt.date(2026, 9, 25) MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] def city_options(rng, airports): out = [] for key, name, country, code, alias in CITIES: out.append({"key": key, "label": f"{name}, {country}", "short": name, "alias": alias, "name": name}) if airports: out.append({"key": key + "-ap", "label": f"{name} Airport ({code})", "short": f"{name} ({code})", "alias": code, "name": name}) return out def say_date(d, rng): return rng.choice([f"{MONTHS[d.month-1]} {d.day}, {d.year}", f"{d.day} {MONTHS[d.month-1]} {d.year}", f"{MONTHS[d.month-1]} {d.day}"]) def pick(rng, xs): return rng.choice(xs) def impl(rng, choices, weights=None): return rng.choices(choices, weights=weights)[0] # ------------------------------------------------------------------------------------------------ flight def flight(seed): rng = random.Random(seed * 7919 + 1) trigger = rng.random() < 0.4 airports = rng.random() < 0.5 o, d = rng.sample(CITIES, 2) one_way = rng.random() < 0.65 dep = TODAY + dt.timedelta(days=rng.randint(2, 80)) ret = dep + dt.timedelta(days=rng.randint(2, 14)) adults = rng.choices([1, 2, 3, 4], weights=[5, 3, 1, 1])[0] cabin = rng.choices(["economy", "premium", "business", "first"], weights=[5, 2, 2, 1])[0] lab = dict(origin=pick(rng, ["Where from?", "From", "Origin", "Leaving from", "Departure airport", "Flying from"]), destination=pick(rng, ["Where to?", "To", "Destination", "Going to", "Arrival airport", "Flying to"]), trip=pick(rng, ["Trip type", "Ticket type", "Trip"]), date=pick(rng, ["Departure", "Departure", "Depart", "Departure date", "Outbound"]), ret=pick(rng, ["Return", "Return date", "Inbound"]), adults=pick(rng, ["Adults", "Passengers", "Travelers"]), cabin=pick(rng, ["Cabin", "Class", "Seating class", "Travel class"]), submit=pick(rng, ["Search", "Search flights", "Find flights", "Explore", "Show flights"])) ow_label = pick(rng, ["One way", "One-way"]) date_impl = impl(rng, ["picker", "typed"], [6, 4]) date_done = pick(rng, [None, "Done", "Apply"]) if date_impl == "picker" else None fields = [ {"w": "choice", "key": "trip", "label": lab["trip"], "impl": impl(rng, ["radio", "segmented", "select", "dropdown"]), "default": "round", "options": [{"key": "round", "label": pick(rng, ["Round trip", "Return"])}, {"key": "oneway", "label": ow_label}]}, {"w": "combo", "key": "origin", "label": lab["origin"], "trigger": trigger, "suggest": rng.random() < 0.9, "options": city_options(rng, airports), "placeholder": pick(rng, ["", "City or airport", "Leaving from"])}, {"w": "combo", "key": "destination", "label": lab["destination"], "trigger": trigger, "suggest": True, "options": city_options(rng, airports), "placeholder": pick(rng, ["", "City or airport", "Going to"])}, {"w": "date", "key": "date", "label": lab["date"], "impl": date_impl, "done": date_done, "start": [TODAY.year, TODAY.month - 1]}, {"w": "date", "key": "ret", "label": lab["ret"], "impl": date_impl, "done": date_done, "start": [TODAY.year, TODAY.month - 1], "only_round": True}, {"w": "count", "key": "adults", "label": lab["adults"], "impl": impl(rng, ["select", "stepper", "popup"]), "default": 1, "min": 1, "max": 9, "one": "adult", "many": "adults"}, {"w": "choice", "key": "cabin", "label": lab["cabin"], "impl": impl(rng, ["radio", "select", "dropdown", "segmented"], [2, 4, 4, 1]), "default": "economy", "options": [{"key": "economy", "label": "Economy"}, {"key": "premium", "label": "Premium economy"}, {"key": "business", "label": "Business"}, {"key": "first", "label": "First"}]}, ] target = {"trip": "oneway" if one_way else "round", "origin": o[0], "destination": d[0], "date": dep.isoformat(), "adults": adults, "cabin": cabin} if not one_way: target["ret"] = ret.isoformat() # goal text: every constraint that differs from a default, plus some stated defaults parts_trip = pick(rng, ["one-way flights", "a one-way flight", "one way flights"]) if one_way else pick(rng, ["round-trip flights", "a return flight", "round trip flights"]) who = {1: pick(rng, ["for one adult", "for 1 adult", ""]), 2: "for two adults", 3: "for 3 adults", 4: "for four adults"}[adults] cab = {"economy": pick(rng, ["in economy", ""]), "premium": "in premium economy", "business": "in business class", "first": "in first class"}[cabin] when = f"on {say_date(dep, rng)}" + ("" if one_way else f", returning {say_date(ret, rng)}") o_name = o[4] if o[4] and rng.random() < 0.5 else o[1] goal = " ".join(x for x in [pick(rng, ["Find", "Search for", "Look up", "Show me"]), parts_trip, f"from {o_name} to {d[1]}", when, who, cab] if x).strip() + "." if rng.random() < 0.5: goal += pick(rng, [" Stop when matching flight options are visible.", " Show the results.", " Stop at the results list."]) subgoals = [("trip", "Set the trip type to one-way" if one_way else "Set the trip type to round trip"), ("origin", f"Set where from (origin) to {o[1]}"), ("destination", f"Set where to (destination) to {d[1]}"), ("date", f"Set the departure date to {MONTHS[dep.month-1]} {dep.day}, {dep.year}")] if not one_way: subgoals.append(("ret", f"Set the return date to {MONTHS[ret.month-1]} {ret.day}, {ret.year}")) subgoals += [("adults", f"Set the number of adults to {adults}"), ("cabin", f"Set the cabin class to {dict(economy='economy', premium='premium economy', business='business', first='first')[cabin]}"), ("submit", "Search for flights and show the matching results")] if not one_way and rng.random() < 0.0: pass return common(rng, "flight", seed, fields, target, goal, subgoals, lab["submit"], results_title=f"{o[1]} to {d[1]}") # ------------------------------------------------------------------------------------------------ hotel def hotel(seed): rng = random.Random(seed * 104729 + 3) trigger = rng.random() < 0.3 c = rng.choice(CITIES) cin = TODAY + dt.timedelta(days=rng.randint(2, 70)); nights = rng.randint(1, 7); cout = cin + dt.timedelta(days=nights) guests = rng.choices([1, 2, 3, 4], weights=[2, 5, 1, 1])[0] date_done = pick(rng, [None, "Done", "Apply"]) hdate = impl(rng, ["picker", "typed"], [7, 3]) lab = dict(dest=pick(rng, ["Destination", "Where are you going?", "City", "Location", "Going to"]), cin=pick(rng, ["Check-in", "Check in", "Arrival"]), cout=pick(rng, ["Check-out", "Check out", "Departure"]), guests=pick(rng, ["Guests", "Adults", "Travelers"]), submit=pick(rng, ["Search", "Search hotels", "Find stays", "Show hotels"])) fields = [ {"w": "combo", "key": "dest", "label": lab["dest"], "trigger": trigger, "suggest": True, "options": city_options(rng, False), "placeholder": pick(rng, ["", "Where to?", "Search destinations"])}, {"w": "date", "key": "cin", "label": lab["cin"], "impl": hdate, "done": date_done, "start": [TODAY.year, TODAY.month - 1]}, {"w": "date", "key": "cout", "label": lab["cout"], "impl": hdate, "done": date_done, "start": [TODAY.year, TODAY.month - 1]}, {"w": "count", "key": "guests", "label": lab["guests"], "impl": impl(rng, ["select", "stepper", "popup"]), "default": 2, "min": 1, "max": 8, "one": "guest", "many": "guests"}, ] target = {"dest": c[0], "cin": cin.isoformat(), "cout": cout.isoformat(), "guests": guests} post = {} if rng.random() < 0.5: post["filter"] = rng.choice(["Free cancellation", "Breakfast included", "Pool", "Pet friendly"]) if rng.random() < 0.4: post["sort"] = rng.choice(["Price: low to high", "Guest rating", "Distance to center"]) who = {1: "for one guest", 2: pick(rng, ["for two guests", "for 2 adults", ""]), 3: "for three guests", 4: "for 4 guests"}[guests] goal = f"{pick(rng, ['Find a hotel', 'Search for hotels', 'Look for a place to stay', 'Find stays'])} in {c[1]} from {say_date(cin, rng)} to {say_date(cout, rng)} {who}".strip() if post.get("filter"): goal += f", {pick(rng, ['only showing', 'filtered to', 'with'])} {post['filter'].lower()}" if post.get("sort"): goal += f", sorted by {post['sort'].lower()}" goal += "." subgoals = [("dest", f"Set the destination (city, location) to {c[1]}"), ("cin", f"Set the check-in date to {MONTHS[cin.month-1]} {cin.day}, {cin.year}"), ("cout", f"Set the check-out date to {MONTHS[cout.month-1]} {cout.day}, {cout.year}"), ("guests", f"Set the number of guests to {guests}"), ("submit", "Search for hotels and show the results")] if post.get("filter"): subgoals.append(("filter", f"Turn on the '{post['filter']}' filter")) if post.get("sort"): subgoals.append(("sort", f"Sort the results by {post['sort'].lower()}")) return common(rng, "hotel", seed, fields, target, goal, subgoals, lab["submit"], results_title=f"Stays in {c[1]}", post=post) # ------------------------------------------------------------------------------------------------ shop listing BRANDS = ["Acme", "Globex", "Initech", "Umbrella", "Hooli", "Stark", "Wayne", "Tyrell"] PRODUCTS = ["Laptop", "Headphones", "Backpack", "Coffee maker", "Desk lamp", "Keyboard", "Monitor", "Water bottle", "Running shoes", "Camera", "Blender", "Jacket", "Tent", "Watch", "Speaker", "Router"] def shop(seed): rng = random.Random(seed * 15485863 + 5) cat = rng.choice(["Electronics", "Home", "Outdoor", "Fashion", "Kitchen"]) items = [] for i in range(96): r = random.Random(seed * 1000 + i) items.append({"id": i, "name": f"{r.choice(BRANDS)} {r.choice(PRODUCTS)} {r.choice(['Pro', 'Lite', 'Max', 'Mini', 'Plus', 'X', '2', 'S'])}", "brand": None, "price": round(r.uniform(9, 900), 2)}) items[-1]["brand"] = items[-1]["name"].split()[0] per_page = rng.choice([12, 16, 24, 32]) task = rng.choice(["page", "sort", "brand", "search", "search_sort", "brand_page", "open_on_page", "category", "category"]) post, goal, subgoals = {}, "", [] if task == "page": n = rng.choice([2, 2, 3]) post["page"] = n goal = pick(rng, [f"Go to page {n} of the products.", f"Show page {n}.", f"Open page {n} of the listing.", "Go to the next page of products." if n == 2 else f"Go to page {n}."]) subgoals = [("page", f"Go to page {n} of the product list")] elif task == "category": from app_cats import CATS c = rng.choice(CATS); post["cat"] = c goal = pick(rng, [f"Open the '{c}' category.", f"Show the {c} category.", f"Go to {c}.", f"Browse the {c} section."]) subgoals = [("cat", f"Open the '{c}' category")] elif task == "sort": s = rng.choice(["Price: low to high", "Price: high to low", "Newest", "Top rated"]) post["sort"] = s goal = pick(rng, [f"Sort the products by {s.lower()}.", f"Show the products sorted by {s.lower()}.", f"{s}, please."]) subgoals = [("sort", f"Sort the products by {s.lower()}")] elif task == "brand": b = rng.choice(BRANDS); post["brand"] = b goal = pick(rng, [f"Show only {b} products.", f"Filter the list to the brand {b}.", f"I only want to see {b} items."]) subgoals = [("brand", f"Turn on the brand filter for {b}")] elif task == "search": q = rng.choice(PRODUCTS); post["q"] = q.lower() goal = pick(rng, [f"Search the shop for '{q.lower()}'.", f"Find {q.lower()}s.", f"Search for {q.lower()} and show the results."]) subgoals = [("q", f"Search for '{q.lower()}' and show the results")] elif task == "search_sort": q = rng.choice(PRODUCTS); s = rng.choice(["Price: low to high", "Price: high to low"]); post.update(q=q.lower(), sort=s) goal = f"Search for {q.lower()} and sort the results by {s.lower()}." subgoals = [("q", f"Search for '{q.lower()}' and show the results"), ("sort", f"Sort the results by {s.lower()}")] elif task == "brand_page": counts = {x: sum(i["brand"] == x for i in items) for x in BRANDS} ok = [x for x in BRANDS if counts[x] > per_page] or [max(counts, key=counts.get)] b = rng.choice(ok); post.update(brand=b, page=2) if counts[b] <= per_page: # not enough items for a second page: make it a plain brand task post.pop("page"); goal = f"Show only {b} products."; subgoals = [("brand", f"Turn on the brand filter for {b}")] return shop_spec(rng, seed, cat, items, per_page, post, goal, subgoals) goal = f"Show only {b} products and go to page 2." subgoals = [("brand", f"Turn on the brand filter for {b}"), ("page", "Go to page 2 of the product list")] else: idx = rng.randint(per_page, min(95, 2 * per_page - 1)); post.update(page=2, open=idx) goal = f"Open the product '{items[idx]['name']}' (it is on page 2)." if rng.random() < 0.5 else f"Go to page 2 and open '{items[idx]['name']}'." subgoals = [("page", "Go to page 2 of the product list"), ("open", f"Open the product page of '{items[idx]['name']}'")] return shop_spec(rng, seed, cat, items, per_page, post, goal, subgoals) def shop_spec(rng, seed, cat, items, per_page, post, goal, subgoals): return {"kind": "shop", "seed": seed, "category": cat, "items": items, "per_page": per_page, "post": post, "goal": goal, "subgoals": subgoals, "search_button": rng.random() < 0.5, "sort_impl": impl(rng, ["select", "dropdown"]), "pager": pick(rng, ["numbers", "next", "both"]), "hero": rng.choice([0, 0, 200, 450]), "sidebar_cats": True if "cat" in post else rng.random() < 0.6, "cookie": rng.random() < 0.25, "held_out": seed % 10 == 0, "labels": {"search": pick(rng, ["Search products", "Search", "What are you looking for?"]), "sort": pick(rng, ["Sort by", "Sort", "Order by"]), "brand": pick(rng, ["Brand", "Brands", "Manufacturer"]), "next": pick(rng, ["Next", "Next ›", "Next page", "›"])}} # ------------------------------------------------------------------------------------------------ shared def common(rng, kind, seed, fields, target, goal, subgoals, submit, results_title, post=None): rng.shuffle(order := [f["key"] for f in fields]) # keep a natural order most of the time (trip type first, then places, dates, counts, class), shuffled otherwise natural = rng.random() < 0.7 return {"kind": kind, "seed": seed, "fields": fields, "target": target, "goal": goal, "subgoals": subgoals, "submit": submit, "order": [f["key"] for f in fields] if natural else order, "results_title": results_title, "post": post or {}, "hero": rng.choice([0, 0, 0, 300, 650]), "cookie": rng.random() < 0.25, "held_out": seed % 10 == 0, "decoys": rng.sample(["Deals", "Help", "Sign in", "Manage booking", "Explore", "Travel guides", "Gift cards", "Careers", "Blog"], 4)} # ------------------------------------------------------------------------------------------------ shared helpers for the new kinds def slug(s): import unicodedata s = unicodedata.normalize("NFKD", s).encode("ascii", "ignore").decode() return "".join(ch if ch.isalnum() else "-" for ch in s.lower()).strip("-") NAMES = [("Ada", "Lovelace"), ("Grace", "Hopper"), ("Alan", "Turing"), ("Marie", "Curie"), ("Linus", "Torvalds"), ("Katherine", "Johnson"), ("Tim", "Berners-Lee"), ("Hedy", "Lamarr"), ("Niklaus", "Wirth"), ("Barbara", "Liskov"), ("Dennis", "Ritchie"), ("Radia", "Perlman"), ("Margaret", "Hamilton"), ("Ken", "Thompson"), ("Frances", "Allen"), ("Edsger", "Dijkstra"), ("Mary", "Shelley"), ("Omar", "Haddad"), ("Priya", "Natarajan"), ("Yuki", "Tanaka"), ("Sofia", "Rossi"), ("Lukas", "Meier"), ("Amara", "Okafor"), ("Jonas", "Berg")] def person(rng): f, l = rng.choice(NAMES) ls = slug(l).replace("-", "") dom = rng.choice(["example.com", "example.org", "mail.test", "example.net"]) email = rng.choice([f"{f.lower()}@{dom}", f"{f.lower()}.{ls}@{dom}", f"{f[0].lower()}{ls}{rng.randint(1, 99)}@{dom}", f"{f.lower()}{rng.randint(10, 99)}@{dom}"]) phone = rng.choice([f"+41 79 {rng.randint(100, 999)} {rng.randint(10, 99)} {rng.randint(10, 99)}", f"+44 7700 {rng.randint(100000, 999999)}", f"+1 555 {rng.randint(100, 999)} {rng.randint(1000, 9999)}", f"0{rng.randint(150, 179)} {rng.randint(1000000, 9999999)}", f"+49 30 {rng.randint(1000000, 9999999)}"]) return f"{f} {l}", email, phone, f, l def time_options(hours): return [{"key": h.replace(":", ""), "label": h} for h in hours] def staged(rng, kind, seed, stages, target, goal, subgoals, site, headings): """A multi-page wizard: every stage is a form page whose committed values are carried in the URL to the next stage; the last stage submits to a confirmation page.""" fields = [f for s in stages for f in s["fields"]] for s in stages: keys = [f["key"] for f in s["fields"]] if rng.random() < 0.35: rng.shuffle(keys) s["order"] = keys return {"kind": kind, "seed": seed, "stages": stages, "fields": fields, "target": target, "goal": goal, "subgoals": subgoals, "site": site, "headings": headings, "hero": rng.choice([0, 0, 0, 260, 600]), "cookie": rng.random() < 0.25, "held_out": seed % 10 == 0, "decoys": rng.sample(["Deals", "Help", "Sign in", "About us", "Explore", "Gift cards", "Careers", "Blog", "Contact"], 4)} # ------------------------------------------------------------------------------------------------ car rental CAR_TYPES = ["Economy", "Compact", "SUV", "Van", "Luxury"] CAR_NAMES = {"Economy": ["Fiat 500", "Kia Picanto", "VW Up"], "Compact": ["VW Golf", "Ford Focus", "Toyota Corolla"], "SUV": ["Nissan Qashqai", "Toyota RAV4", "Kia Sportage", "VW Tiguan"], "Van": ["VW Transporter", "Ford Transit", "Renault Trafic"], "Luxury": ["BMW 5 Series", "Mercedes E-Class", "Audi A6"]} HOURS = [f"{h:02d}:00" for h in range(7, 23)] def car_list(seed): r = random.Random(seed * 1000 + 77) prices = r.sample(range(25, 200), 12) cars = [] for i in range(12): t = CAR_TYPES[(i * 3 + seed) % 5] cars.append({"id": i, "type": t, "name": r.choice(CAR_NAMES[t]) + (" or similar" if r.random() < 0.5 else ""), "trans": r.choice(["Automatic", "Automatic", "Manual"]), "price": prices[i], "seats": {"Economy": 4, "Compact": 5, "SUV": 5, "Van": 8, "Luxury": 5}[t]}) return cars def car(seed): rng = random.Random(seed * 32452843 + 7) trigger = rng.random() < 0.3 airports = rng.random() < 0.6 p, d = rng.sample(CITIES, 2) diff = rng.random() < 0.4 pdate = TODAY + dt.timedelta(days=rng.randint(2, 60)); rdate = pdate + dt.timedelta(days=rng.randint(1, 10)) ptime, rtime = rng.choice(HOURS[1:12]), rng.choice(HOURS[1:12]) age_impl = impl(rng, ["select", "stepper"]) age = rng.choice([21, 23, 25, 27, 30, 35, 40, 45, 55, 65]) if age_impl == "select" else rng.choice([26, 27, 28, 29, 31, 32, 33, 34, 35]) date_impl = impl(rng, ["picker", "typed"], [6, 4]); date_done = pick(rng, [None, "Done", "Apply"]) if date_impl == "picker" else None time_impl = impl(rng, ["select", "dropdown"]) lab = dict(pickup=pick(rng, ["Pick-up location", "Pick up", "Pick-up", "Where do you pick up?", "Rental location"]), diff=pick(rng, ["Return to a different location", "Different drop-off location", "Drop off at a different location"]), dropoff=pick(rng, ["Drop-off location", "Return location", "Drop off"]), pdate=pick(rng, ["Pick-up date", "Pick up date", "From"]), ptime=pick(rng, ["Pick-up time", "Pick up time"]), rdate=pick(rng, ["Return date", "Drop-off date", "Until"]), rtime=pick(rng, ["Return time", "Drop-off time"]), age=pick(rng, ["Driver's age", "Driver age", "Age of driver"]), submit=pick(rng, ["Search cars", "Search", "Find a car", "Show cars"])) fields = [ {"w": "combo", "key": "pickup", "label": lab["pickup"], "trigger": trigger, "suggest": True, "options": city_options(rng, airports), "placeholder": pick(rng, ["", "City or airport", "Pick-up city"])}, {"w": "toggle", "key": "diff", "label": lab["diff"], "impl": impl(rng, ["checkbox", "switch"]), "default": False}, {"w": "combo", "key": "dropoff", "label": lab["dropoff"], "trigger": trigger, "suggest": True, "options": city_options(rng, airports), "placeholder": pick(rng, ["", "City or airport"]), "show_if": ["diff", True]}, {"w": "date", "key": "pdate", "label": lab["pdate"], "impl": date_impl, "done": date_done, "start": [TODAY.year, TODAY.month - 1]}, {"w": "choice", "key": "ptime", "label": lab["ptime"], "impl": time_impl, "default": "1000", "options": time_options(HOURS)}, {"w": "date", "key": "rdate", "label": lab["rdate"], "impl": date_impl, "done": date_done, "start": [TODAY.year, TODAY.month - 1]}, {"w": "choice", "key": "rtime", "label": lab["rtime"], "impl": time_impl, "default": "1000", "options": time_options(HOURS)}, {"w": "count", "key": "age", "label": lab["age"], "impl": age_impl, "default": 30, "min": 18, "max": 75, "one": "year", "many": "years"}, ] target = {"pickup": p[0], "diff": diff, "pdate": pdate.isoformat(), "ptime": ptime.replace(":", ""), "rdate": rdate.isoformat(), "rtime": rtime.replace(":", ""), "age": age} if diff: target["dropoff"] = d[0] cars = car_list(seed) post = {} r = rng.random() if r < 0.22: post["ctype"] = rng.choice(CAR_TYPES) elif r < 0.36: post["trans"] = rng.choice(["Automatic", "Manual"]) elif r < 0.5: post["csort"] = rng.choice(["Price: low to high", "Price: high to low"]) elif r < 0.72: suvs = sorted([c for c in cars if c["type"] == "SUV"], key=lambda c: c["price"]) post.update(ctype="SUV", csort="Price: low to high", open=suvs[0]["id"]) ap = " Airport" if airports and rng.random() < 0.5 else "" p_name = (p[4] if p[4] and rng.random() < 0.4 else p[1]) + ap when = f"from {say_date(pdate, rng)} at {ptime} to {say_date(rdate, rng)} at {rtime}" goal = f"{pick(rng, ['Rent a car', 'Find a rental car', 'Search for a car rental', 'Book a hire car'])} in {p_name} {when}" if diff: goal += pick(rng, [f", returning it in {d[1]}", f" with drop-off in {d[1]}", f" (different drop-off: {d[1]})"]) goal += pick(rng, [f". The driver is {age} years old.", f"; driver age {age}.", f" for a {age}-year-old driver."]) if "open" in post: goal += pick(rng, [" Then show only SUVs sorted by price (low to high) and open the cheapest SUV.", " Filter to SUVs, sort by lowest price and open the cheapest one."]) elif "ctype" in post: goal += pick(rng, [f" Show only {post['ctype']} cars.", f" Then filter the results to the car type {post['ctype']}."]) elif "trans" in post: goal += pick(rng, [f" Only {post['trans'].lower()} transmission.", f" Then filter to {post['trans'].lower()} cars."]) elif "csort" in post: goal += f" Sort the results by {post['csort'].lower()}." subgoals = [("pickup", f"Set the pick-up location to {p[1]}")] if diff: subgoals += [("diff", "Turn on the option to return the car to a different location"), ("dropoff", f"Set the drop-off (return) location to {d[1]}")] subgoals += [("pdate", f"Set the pick-up date to {MONTHS[pdate.month-1]} {pdate.day}, {pdate.year}"), ("ptime", f"Set the pick-up time to {ptime}"), ("rdate", f"Set the return date to {MONTHS[rdate.month-1]} {rdate.day}, {rdate.year}"), ("rtime", f"Set the return time to {rtime}"), ("age", f"Set the driver's age to {age}"), ("submit", "Search for cars and show the results")] if "ctype" in post: subgoals.append(("ctype", f"Turn on the car type filter '{post['ctype']}'")) if "trans" in post: subgoals.append(("trans", f"Set the transmission filter to {post['trans']}")) if "csort" in post: subgoals.append(("csort", f"Sort the results by {post['csort'].lower()}")) if "open" in post: subgoals.append(("open", "Open the cheapest SUV in the results")) sp = common(rng, "car", seed, fields, target, goal, subgoals, lab["submit"], results_title=f"Cars in {p[1]}", post=post) sp["cars"] = cars return sp # ------------------------------------------------------------------------------------------------ restaurant reservation RESTAURANTS = ["Trattoria Luna", "The Golden Fork", "Bistro Verde", "Sakura House", "Le Petit Jardin", "Casa Miguel", "Nordic Table", "Spice Route"] SLOTS = ["17:00", "17:30", "18:00", "18:30", "19:00", "19:30", "20:00", "20:30", "21:00", "21:30"] REQUESTS = ["Window seat please", "Birthday celebration", "One high chair needed", "Wheelchair access needed", "We may be 10 minutes late", "Gluten-free options please", "Quiet table if possible", "Anniversary dinner"] def restaurant(seed): rng = random.Random(seed * 49979687 + 11) name = rng.choice(RESTAURANTS) party = rng.choices([1, 2, 3, 4, 5, 6, 8], weights=[1, 4, 3, 4, 2, 2, 1])[0] date = TODAY + dt.timedelta(days=rng.randint(1, 45)) closed = rng.sample(SLOTS, rng.randint(2, 4)); avail = [s for s in SLOTS if s not in closed]; slot = rng.choice(avail) seat_opts = [{"key": "any", "label": pick(rng, ["No preference", "Any"])}, {"key": "indoor", "label": pick(rng, ["Indoor", "Inside"])}, {"key": "outdoor", "label": pick(rng, ["Outdoor", "Terrace", "Patio"])}, {"key": "bar", "label": pick(rng, ["Bar seating", "Bar"])}] seat = rng.choices(["any", "indoor", "outdoor", "bar"], weights=[2, 3, 3, 2])[0] full, email, phone, _, _ = person(rng) req = rng.choice(REQUESTS) if rng.random() < 0.5 else None date_impl = impl(rng, ["picker", "typed"], [7, 3]) lab = dict(party=pick(rng, ["Party size", "Guests", "How many people?", "People"]), date=pick(rng, ["Date", "Reservation date", "When?"]), slot=pick(rng, ["Time", "Available times", "Choose a time"]), seat=pick(rng, ["Seating", "Seating preference", "Where would you like to sit?"]), name=pick(rng, ["Full name", "Name", "Your name"]), email=pick(rng, ["Email", "Email address", "E-mail"]), phone=pick(rng, ["Phone", "Phone number", "Mobile number"]), req=pick(rng, ["Special requests", "Notes for the restaurant", "Anything else?"]), agree=pick(rng, ["I agree to the terms and conditions", "I accept the cancellation policy", "I agree to the booking terms"]), go1=pick(rng, ["Find a table", "Continue", "Next", "Book this time"]), go2=pick(rng, ["Confirm reservation", "Book now", "Reserve", "Complete booking"])) s1 = [{"w": "count", "key": "party", "label": lab["party"], "impl": impl(rng, ["select", "stepper", "popup"]), "default": 2, "min": 1, "max": 10, "one": "person", "many": "people"}, {"w": "date", "key": "date", "label": lab["date"], "impl": date_impl, "done": pick(rng, [None, "Done"]) if date_impl == "picker" else None, "start": [TODAY.year, TODAY.month - 1]}, {"w": "slots", "key": "slot", "label": lab["slot"], "options": [{"key": s.replace(":", ""), "label": s, "disabled": s in closed} for s in SLOTS]}, {"w": "choice", "key": "seat", "label": lab["seat"], "impl": impl(rng, ["radio", "segmented", "select"], [4, 3, 1]), "default": "any", "options": seat_opts}] s2 = [{"w": "text", "key": "name", "label": lab["name"], "type": "text", "placeholder": pick(rng, ["", "First and last name"])}, {"w": "text", "key": "email", "label": lab["email"], "type": "email", "placeholder": pick(rng, ["", "you@example.com"])}, {"w": "text", "key": "phone", "label": lab["phone"], "type": "tel", "placeholder": pick(rng, ["", "+41 ..."])}, {"w": "text", "key": "req", "label": lab["req"], "type": "textarea", "placeholder": pick(rng, ["", "Allergies, occasions, ..."])}, {"w": "toggle", "key": "agree", "label": lab["agree"], "impl": impl(rng, ["checkbox", "switch"], [4, 1]), "default": False}] stages = [{"fields": s1, "submit": lab["go1"], "required": ["date", "slot"], "heading": pick(rng, ["Book a table", "Reserve a table", "Make a reservation"])}, {"fields": s2, "submit": lab["go2"], "required": ["name", "email", "phone", "agree"], "heading": pick(rng, ["Your details", "Guest details", "Almost there"])}] target = {"party": party, "date": date.isoformat(), "slot": slot.replace(":", ""), "seat": seat, "name": full, "email": email, "phone": phone, "agree": True} if req: target["req"] = req seat_txt = {"any": "", "indoor": pick(rng, [", indoor seating", " inside"]), "outdoor": pick(rng, [", outdoor seating", " on the terrace", ", outside"]), "bar": pick(rng, [", at the bar", ", bar seating"])}[seat] who = {1: "for one person", 2: pick(rng, ["for two", "for 2 people", "for two guests"])}.get(party, pick(rng, [f"for {party} people", f"for a party of {party}", f"for {party}"])) goal = f"{pick(rng, ['Reserve a table', 'Book a table', 'Make a reservation'])} at {name} {who} on {say_date(date, rng)} at {slot}{seat_txt}. " goal += pick(rng, [f"The booking is for {full} ({email}, {phone}).", f"Guest: {full}, email {email}, phone {phone}.", f"Book it under the name {full}, {email}, phone {phone}."]) if req: goal += pick(rng, [f" Add the special request '{req}'.", f" Note for the restaurant: '{req}'."]) goal += pick(rng, [" Accept the terms and confirm the reservation.", " Agree to the terms and complete the booking.", " Confirm the booking."]) subgoals = [("party", f"Set the party size to {party}"), ("date", f"Set the reservation date to {MONTHS[date.month-1]} {date.day}, {date.year}"), ("slot", f"Choose the {slot} time slot")] if seat != "any": subgoals.append(("seat", f"Set the seating preference to {dict(indoor='indoor', outdoor='outdoor', bar='bar')[seat]}")) subgoals += [("go1", "Continue to the guest details"), ("name", f"Enter the name {full}"), ("email", f"Enter the email address {email}"), ("phone", f"Enter the phone number {phone}")] if req: subgoals.append(("req", f"Enter the special request '{req}'")) subgoals += [("agree", "Accept the terms and conditions"), ("go2", "Confirm the reservation")] return staged(rng, "restaurant", seed, stages, target, goal, subgoals, site=name, headings={"done": pick(rng, ["Reservation confirmed", "You're booked!", "Table reserved"])}) # ------------------------------------------------------------------------------------------------ signup wizard COUNTRIES = sorted(set(c[2] for c in CITIES) | {"Belgium", "Sweden", "Brazil", "India", "Mexico", "New Zealand", "South Africa", "Argentina"}) INTERESTS = ["Music", "Hiking", "Cooking", "Photography", "Travel", "Gaming", "Reading", "Cycling", "Movies", "Gardening", "Yoga", "Art"] def signup(seed): rng = random.Random(seed * 67867967 + 13) full, email, phone, f, l = person(rng) ls = slug(l).replace("-", "") username = rng.choice([f"{f.lower()}{ls}", f"{f[0].lower()}{ls}", f"{f.lower()}_{rng.randint(10, 99)}", f"{f.lower()}.{ls}", f"{ls}{rng.randint(1, 999)}"]) country = rng.choice(COUNTRIES) birth = dt.date(rng.randint(1950, 2005), rng.randint(1, 12), rng.randint(1, 28)) shown = rng.sample(INTERESTS, 8) interests = rng.sample(shown, rng.randint(1, 3)) newsletter = rng.random() < 0.5 plan = rng.choices(["free", "pro", "team"], weights=[2, 3, 2])[0] country_impl = impl(rng, ["select", "dropdown", "combo"], [4, 2, 3]) birth_impl = impl(rng, ["typed", "selects"]) lab = dict(name=pick(rng, ["Full name", "Name", "Your name"]), email=pick(rng, ["Email", "Email address", "Work email"]), username=pick(rng, ["Username", "Choose a username", "Handle"]), country=pick(rng, ["Country", "Country of residence", "Where do you live?"]), birth=pick(rng, ["Date of birth", "Birthday", "Born on"]), interests=pick(rng, ["Interests", "What are you into?", "Topics you like"]), newsletter=pick(rng, ["Subscribe to the newsletter", "Send me product news", "Email me updates and offers"]), plan=pick(rng, ["Plan", "Choose a plan", "Subscription"]), nxt=pick(rng, ["Next", "Continue", "Next step"]), go3=pick(rng, ["Create account", "Create my account", "Finish", "Sign up"])) plan_opts = [{"key": "free", "label": pick(rng, ["Free", "Basic (free)"])}, {"key": "pro", "label": pick(rng, ["Pro", "Pro – $9/mo"])}, {"key": "team", "label": pick(rng, ["Team", "Team – $29/mo"])}] if country_impl == "combo": cf = {"w": "combo", "key": "country", "label": lab["country"], "trigger": False, "suggest": True, "placeholder": pick(rng, ["", "Start typing a country"]), "options": [{"key": slug(c), "label": c, "short": c, "alias": "", "name": c} for c in COUNTRIES]} else: cf = {"w": "choice", "key": "country", "label": lab["country"], "impl": country_impl, "default": None, "placeholder": pick(rng, ["Select a country", "Choose..."]), "options": [{"key": slug(c), "label": c} for c in COUNTRIES]} s1 = [{"w": "text", "key": "name", "label": lab["name"], "type": "text", "placeholder": ""}, {"w": "text", "key": "email", "label": lab["email"], "type": "email", "placeholder": pick(rng, ["", "name@company.com"])}, {"w": "text", "key": "username", "label": lab["username"], "type": "text", "placeholder": pick(rng, ["", "letters and numbers"])}] s2 = [cf, {"w": "date", "key": "birth", "label": lab["birth"], "impl": birth_impl, "years": [1945, 2008]}, {"w": "multi", "key": "interests", "label": lab["interests"], "impl": impl(rng, ["checkbox", "chips"]), "options": [{"key": slug(x), "label": x} for x in shown]}] s3 = [{"w": "toggle", "key": "newsletter", "label": lab["newsletter"], "impl": impl(rng, ["checkbox", "switch"]), "default": False}, {"w": "choice", "key": "plan", "label": lab["plan"], "impl": impl(rng, ["radio", "segmented", "select"], [4, 2, 1]), "default": "free", "options": plan_opts}] stages = [{"fields": s1, "submit": lab["nxt"], "required": ["name", "email", "username"], "heading": pick(rng, ["Create your account", "Account", "Step 1: Account"])}, {"fields": s2, "submit": lab["nxt"], "required": ["country", "birth"], "heading": pick(rng, ["Tell us about you", "Profile", "Step 2: Profile"])}, {"fields": s3, "submit": lab["go3"], "required": [], "heading": pick(rng, ["Preferences", "Almost done", "Step 3: Preferences"])}] target = {"name": full, "email": email, "username": username, "country": slug(country), "birth": birth.isoformat(), "interests": sorted(slug(x) for x in interests), "newsletter": newsletter, "plan": plan} ilist = " and ".join([", ".join(x.lower() for x in interests[:-1]), interests[-1].lower()]) if len(interests) > 1 else interests[0].lower() goal = pick(rng, [f"Sign up for an account for {full} ({email}), username {username}.", f"Create an account: name {full}, email {email}, username {username}.", f"Register {full} with the email {email} and the username {username}."]) goal += f" {pick(rng, ['Country', 'Lives in', 'Country of residence'])}: {country}; born {pick(rng, [f'{MONTHS[birth.month-1]} {birth.day}, {birth.year}', f'{birth.day} {MONTHS[birth.month-1]} {birth.year}', birth.strftime('%m/%d/%Y')])}; " goal += pick(rng, [f"interested in {ilist}.", f"interests: {ilist}."]) goal += " " + (pick(rng, ["Subscribe to the newsletter", "Opt in to the newsletter"]) if newsletter else pick(rng, ["Do not subscribe to the newsletter", "No newsletter"])) goal += f" and {pick(rng, ['choose', 'pick', 'select'])} the {plan.capitalize()} plan." subgoals = [("name", f"Enter the full name {full}"), ("email", f"Enter the email address {email}"), ("username", f"Enter the username {username}"), ("go1", "Continue to the profile step"), ("country", f"Set the country to {country}"), ("birth", f"Set the date of birth to {MONTHS[birth.month-1]} {birth.day}, {birth.year}"), ("interests", f"Select the interests: {ilist}"), ("go2", "Continue to the preferences step")] if newsletter: subgoals.append(("newsletter", "Turn on the newsletter subscription")) if plan != "free": subgoals.append(("plan", f"Choose the {plan.capitalize()} plan")) subgoals.append(("go3", "Create the account")) return staged(rng, "signup", seed, stages, target, goal, subgoals, site=pick(rng, ["Nimbus", "Orbital", "Papyrus", "Quill"]), headings={"done": pick(rng, ["Welcome aboard!", "Your account is ready", "Welcome"])}) # ------------------------------------------------------------------------------------------------ faceted listing COLORS = ["Black", "White", "Red", "Blue", "Green", "Grey"] SIZES = ["XS", "S", "M", "L", "XL"] FACETS = ["brand", "color", "size"] FILTER_SORTS = ["Featured", "Price: low to high", "Price: high to low", "Top rated"] def filter_items(seed): items = [] for i in range(120): r = random.Random(seed * 1000 + i + 500) b, c = r.choice(BRANDS), r.choice(COLORS) items.append({"id": i, "brand": b, "color": c, "size": r.choice(SIZES), "price": round(r.uniform(10, 400), 2), "rating": round(r.uniform(2.0, 5.0), 1), "name": f"{b} {r.choice(PRODUCTS)} {c}"}) return items def apply_listing(items, state): """The listing logic shared by the server and the task generator: facets, price range, rating, sort.""" out = items for fk in FACETS: vals = state.get(fk) or [] if vals: out = [i for i in out if i[fk] in vals] if state.get("pmin") not in (None, ""): out = [i for i in out if i["price"] >= float(state["pmin"])] if state.get("pmax") not in (None, ""): out = [i for i in out if i["price"] <= float(state["pmax"])] if state.get("rating"): out = [i for i in out if i["rating"] >= float(state["rating"])] s = state.get("sort") or "Featured" if s.startswith("Price: low"): out = sorted(out, key=lambda i: i["price"]) elif s.startswith("Price: high"): out = sorted(out, key=lambda i: -i["price"]) elif s == "Top rated": out = sorted(out, key=lambda i: (-i["rating"], i["id"])) return out def filter_(seed): rng = random.Random(seed * 86028121 + 17) items = filter_items(seed) per_page = rng.choice([12, 16, 24]) for attempt in range(40): pre = {} if rng.random() < 0.35: fk = rng.choice(["brand", "color"]); pre[fk] = [rng.choice(BRANDS if fk == "brand" else COLORS)] remove = bool(pre) and rng.random() < 0.6 cands = [c for c in ["pmax", "pmin", "rating", "brand", "color", "size", "sort"] if c not in pre] chosen = rng.sample(cands, max(1, rng.choice([2, 3, 3, 4, 4]) - (1 if remove else 0))) if "pmin" in chosen and "pmax" not in chosen and rng.random() < 0.5: chosen.append("pmax") target = {"brand": [], "color": [], "size": [], "pmin": None, "pmax": None, "rating": None, "sort": None, "page": 1} for k, v in pre.items(): target[k] = [] if remove else list(v) subgoals, parts = [], [] if remove: fk = next(iter(pre)); v = pre[fk][0] subgoals.append((f"rm_{fk}", f"Remove the applied {fk} filter '{v}'")) parts.append(pick(rng, [f"remove the {v} {fk} filter", f"clear the pre-applied {fk} filter ({v})", f"drop the '{v}' {fk} filter"])) for k in ["brand", "color", "size", "rating", "pmin", "pmax", "sort"]: if k not in chosen: continue if k in FACETS: pool = {"brand": BRANDS, "color": COLORS, "size": SIZES}[k] n = 1 if rng.random() < 0.75 else 2 vals = rng.sample(pool, n); target[k] = vals subgoals.append((k, f"Turn on the {k} filter{'s' if n > 1 else ''} {' and '.join(vals)}")) parts.append({"brand": pick(rng, [f"only {' and '.join(vals)} items", f"brand {' or '.join(vals)}", f"from {' or '.join(vals)}"]), "color": pick(rng, [f"in {' or '.join(vals).lower()}", f"in the color {' or '.join(vals).lower()}"]), "size": pick(rng, [f"size {' or '.join(vals)}", f"in size {' or '.join(vals)}"])}[k]) elif k == "rating": target["rating"] = rng.choice(["3", "4"]) subgoals.append(("rating", f"Show only items rated {target['rating']} stars and up")) parts.append(pick(rng, [f"rated {target['rating']} stars and up", f"with at least {target['rating']} stars", f"{target['rating']}+ stars"])) elif k == "pmin": target["pmin"] = str(rng.choice([20, 30, 50, 75, 100, 150])) elif k == "pmax": lo = int(target["pmin"] or 0) target["pmax"] = str(rng.choice([x for x in [50, 80, 100, 150, 200, 250, 300] if x > lo])) elif k == "sort": target["sort"] = rng.choice(FILTER_SORTS[1:]) if target["pmin"] or target["pmax"]: if target["pmin"] and target["pmax"]: subgoals.append(("price", f"Set the price range to ${target['pmin']} – ${target['pmax']}")) parts.append(pick(rng, [f"priced between ${target['pmin']} and ${target['pmax']}", f"from ${target['pmin']} to ${target['pmax']}"])) elif target["pmax"]: subgoals.append(("price", f"Set the maximum price to ${target['pmax']}")) parts.append(pick(rng, [f"under ${target['pmax']}", f"at most ${target['pmax']}", f"up to ${target['pmax']}"])) else: subgoals.append(("price", f"Set the minimum price to ${target['pmin']}")) parts.append(pick(rng, [f"over ${target['pmin']}", f"at least ${target['pmin']}"])) if target["sort"]: subgoals.append(("sort", f"Sort the results by {target['sort'].lower()}")) parts.append(f"sorted by {target['sort'].lower()}") res = apply_listing(items, target) if len(res) < 2: continue tail = "" if len(res) > per_page and rng.random() < 0.25: target["page"] = 2; subgoals.append(("page", "Go to page 2 of the results")); tail = pick(rng, [" Then go to page 2.", " Show the second page."]) elif rng.random() < 0.4: target["open"] = res[0]["id"]; subgoals.append(("open", "Open the first result")); tail = pick(rng, [" Then open the first result.", " Open the first product in the list."]) if len(subgoals) < 2: continue rm_part = parts.pop(0) if remove else None body = (", ".join(parts[:-1]) + (" and " if len(parts) > 1 else "") + parts[-1]) if parts else "" goal = pick(rng, ["Show", "Find", "Filter the products to", "I want to see"]) + " " + pick(rng, ["items", "products", "the products"]) + " " + body if remove: goal = rm_part[0].upper() + rm_part[1:] + (pick(rng, [", then show ", " and show ", "; show only "]) + pick(rng, ["items", "products"]) + " " + body if parts else pick(rng, [" and show all products", " so that all products are listed"])) goal = goal.strip() + "." + tail goal = goal.replace(" ", " ") return {"kind": "filter", "seed": seed, "items": items, "per_page": per_page, "pre": pre, "target": target, "goal": goal, "subgoals": subgoals, "sort_impl": impl(rng, ["select", "dropdown"]), "pager": pick(rng, ["numbers", "next", "both"]), "hero": rng.choice([0, 0, 220, 480]), "cookie": rng.random() < 0.25, "held_out": seed % 10 == 0, "show_n": {"brand": 4, "color": 4, "size": 5}, "labels": {"sort": pick(rng, ["Sort by", "Sort", "Order by"]), "brand": pick(rng, ["Brand", "Brands"]), "color": pick(rng, ["Color", "Colour"]), "size": "Size", "price": pick(rng, ["Price", "Price range", "Price ($)"]), "apply": pick(rng, ["Apply", "Go", "Set"]), "more": pick(rng, ["Show more", "More", "See all"]), "rating": pick(rng, ["Rating", "Customer rating", "Avg. rating"]), "next": pick(rng, ["Next", "Next ›", "Next page"]), "chips": pick(rng, ["Applied filters", "Filters", "Active filters"])}, "decoys": rng.sample(["Deals", "Help", "Sign in", "Cart", "New arrivals", "Gift cards", "Blog"], 4)} raise ValueError("no filter task") KINDS = {"flight": flight, "hotel": hotel, "shop": shop, "car": car, "restaurant": restaurant, "signup": signup, "filter": filter_} def make(kind, seed): return KINDS[kind](seed)