| """Seeded value variants for Flatmate RL scenarios. |
| |
| Variants intentionally preserve episode flow: same task id, post ids, required |
| tools, required bookings, feasible slots, and phase transitions. Only safe |
| surface values are shifted so train/test episodes can differ without changing |
| the canonical solution structure. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import random |
| from copy import deepcopy |
| from typing import Any |
|
|
|
|
| OCCUPATIONS = [ |
| "software engineer at a startup", |
| "backend engineer at a fintech company", |
| "data engineer at a healthtech company", |
| "platform engineer at a SaaS company", |
| ] |
|
|
| RENT_DELTAS = [-1200, -800, -500, 0, 500, 800, 1200] |
|
|
|
|
| def _format_rs(amount: int) -> str: |
| return f"{amount:,}" |
|
|
|
|
| def _buyer_message(scenario: dict[str, Any]) -> str: |
| buyer = scenario["buyer_profile"] |
| areas = " or ".join(buyer["areas"]) |
| budget = _format_rs(int(buyer["budget_max"])) |
| occupation = buyer["occupation"] |
| availability = " or ".join(buyer["visit_availability"]) |
| task_id = scenario["task_id"] |
|
|
| if task_id == "task_visit_multi": |
| return ( |
| "Hi, I want to line up visits for at least two good flatmate-share options before deciding. " |
| f"My budget is Rs. {budget}, I'm focused on {areas}, and I work in Goregaon East as a {occupation}." |
| ) |
| if task_id == "task_visit_single_seller_followup": |
| return ( |
| f"Hi, I'm looking for a flatmate-share in {areas}. My budget is Rs. {budget}, " |
| f"I work in Goregaon East as a {occupation}, and {availability} are the only times I can visit." |
| ) |
| if "visit_availability" in buyer.get("initial_disclosure_fields", []): |
| return ( |
| f"Hi, I'm looking for a flatmate-share around {areas}. My budget is Rs. {budget}, " |
| f"I work in Goregaon East as a {occupation}, and {availability} is the slot I can do right now." |
| ) |
| return ( |
| "Hi, I'm looking for a flatmate-share near Goregaon East. " |
| f"My budget is up to Rs. {budget} and I'm mainly considering {areas} " |
| f"because I work as a {occupation}." |
| ) |
|
|
|
|
| def _seller_message(scenario: dict[str, Any]) -> str: |
| seller = scenario.get("seller_profile") |
| if not seller: |
| return scenario.get("seller_initial_message", "") |
| return ( |
| f"Hi, I want help listing a new flatmate-share opening in {seller['area']}. " |
| f"The rent is around Rs. {_format_rs(int(seller['rent']))} for a {seller['listing_type']}. " |
| "I can tell you more about the listing and available visit times." |
| ) |
|
|
|
|
| def _shift_amount(value: Any, delta: int) -> Any: |
| if value is None: |
| return None |
| return max(1, int(value) + delta) |
|
|
|
|
| def apply_seed_variant( |
| scenario: dict[str, Any], |
| posts: dict[str, dict[str, Any]], |
| seed: int | None, |
| ) -> tuple[dict[str, Any], dict[str, dict[str, Any]]]: |
| """Return seeded copies of a scenario and its posts. |
| |
| The same seed always produces the same value variant for a scenario. A |
| missing seed returns the original values, preserving existing tests and |
| default behavior. |
| """ |
|
|
| variant_scenario = deepcopy(scenario) |
| variant_posts = deepcopy(posts) |
| if seed is None: |
| return variant_scenario, variant_posts |
|
|
| rng = random.Random(f"{variant_scenario['task_id']}:{seed}") |
| rent_delta = rng.choice(RENT_DELTAS) |
| occupation = rng.choice(OCCUPATIONS) |
|
|
| buyer = variant_scenario["buyer_profile"] |
| buyer["budget_max"] = _shift_amount(buyer["budget_max"], rent_delta) |
| if buyer.get("hidden_budget_ceiling") is not None: |
| buyer["hidden_budget_ceiling"] = _shift_amount(buyer["hidden_budget_ceiling"], rent_delta) |
| buyer["occupation"] = occupation |
|
|
| expected_answers = variant_scenario["scenario_creation_config"].get("expected_answers", {}) |
| if "budget_max" in expected_answers: |
| expected_answers["budget_max"] = buyer["budget_max"] |
| if "occupation" in expected_answers: |
| expected_answers["occupation"] = buyer["occupation"] |
|
|
| negotiation_config = variant_scenario["scenario_creation_config"].get("negotiation_config", {}) |
| for key in ("buyer_ceiling", "seller_floor"): |
| if key in negotiation_config: |
| negotiation_config[key] = _shift_amount(negotiation_config[key], rent_delta) |
|
|
| for post in variant_posts.values(): |
| post["rent"] = _shift_amount(post["rent"], rent_delta) |
|
|
| seller = variant_scenario.get("seller_profile") |
| if seller: |
| seller["rent"] = _shift_amount(seller["rent"], rent_delta) |
| followup = variant_scenario["scenario_creation_config"].get("followup_seller_expected_answers", {}) |
| if "rent" in followup: |
| followup["rent"] = seller["rent"] |
|
|
| variant_scenario["initial_user_message"] = _buyer_message(variant_scenario) |
| if seller: |
| variant_scenario["seller_initial_message"] = _seller_message(variant_scenario) |
| variant_scenario["scenario_creation_config"]["variant"] = { |
| "seed": seed, |
| "rent_delta": rent_delta, |
| "occupation": occupation, |
| } |
| return variant_scenario, variant_posts |
|
|