Spaces:
Sleeping
Sleeping
File size: 17,450 Bytes
e6a02dd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | # salespath_env/server/task_bank.py
"""
Prospect profiles, organised by difficulty.
Per arXiv:2408.10215 §3 ("Reward shaping cannot fix data scarcity"),
the training distribution must be wide enough that the policy cannot
overfit to a handful of memorised episodes. We expand to ~20 profiles
per level and reserve the last 4 of each level as a held-out eval set.
Public API
----------
sample_profile(difficulty, split="train", rng=None)
Sample a profile for online training/eval.
iter_eval_profiles(difficulty)
Iterate over the held-out eval profiles.
iter_train_profiles(difficulty)
Iterate over the training profiles.
"""
from __future__ import annotations
import random
from dataclasses import dataclass
from typing import Iterator, List, Optional
@dataclass
class ProspectProfile:
company_name: str
company_size: str # small / medium / enterprise
industry: str
budget_signal: str # high / medium / low / unknown
pain_points: List[str]
decision_maker: bool
# Hidden values — never exposed directly to the agent.
true_budget: float # 0.0 → 1.0
close_threshold: float
stall_probability: float
# ---------------------------------------------------------------------------
# LEVEL 1 — Easy
# Budget known, decision maker present, close should succeed.
# ---------------------------------------------------------------------------
PROFILES_L1: List[ProspectProfile] = [
ProspectProfile("Meridian Retail", "medium", "retail", "high", ["manual inventory tracking", "slow reporting"], True, 0.80, 0.50, 0.0),
ProspectProfile("Northline Foods", "small", "food distribution", "medium", ["supplier delays", "inventory mismatch"], True, 0.60, 0.50, 0.0),
ProspectProfile("Crestline Auto", "medium", "automotive parts", "high", ["parts forecasting", "warehouse turnover"], True, 0.75, 0.50, 0.0),
ProspectProfile("HarborGoods", "small", "consumer goods", "high", ["channel reporting", "stockout alerts"], True, 0.72, 0.50, 0.0),
ProspectProfile("Ironclad Tools", "medium", "industrial supply", "high", ["catalog updates", "B2B quoting"], True, 0.78, 0.50, 0.0),
ProspectProfile("Greenway Grocer", "medium", "grocery", "medium", ["expiration tracking", "cold-chain visibility"], True, 0.62, 0.50, 0.0),
ProspectProfile("BlueRiver Pharma", "medium", "pharmacy retail", "high", ["compliance forms", "expiry alerts"], True, 0.70, 0.50, 0.0),
ProspectProfile("Stride Apparel", "small", "apparel", "medium", ["sizing variants", "returns workflow"], True, 0.58, 0.50, 0.0),
ProspectProfile("Summit Hardware", "medium", "hardware retail", "high", ["SKU bloat", "POS integration"], True, 0.74, 0.50, 0.0),
ProspectProfile("Pinecrest Books", "small", "books", "medium", ["seasonal demand", "inventory shrinkage"], True, 0.55, 0.50, 0.0),
ProspectProfile("Lakeside Resort", "medium", "hospitality", "high", ["guest preference data", "F&B inventory"], True, 0.68, 0.50, 0.0),
ProspectProfile("Granite Coffee", "small", "F&B chain", "medium", ["multi-location SKU sync", "shrinkage"], True, 0.60, 0.50, 0.0),
ProspectProfile("Horizon Outdoor", "medium", "sporting goods", "high", ["seasonal kitting", "regional demand"], True, 0.71, 0.50, 0.0),
ProspectProfile("Cobalt Components","medium", "electronics dist.", "high", ["BOM management", "lead-time variance"], True, 0.77, 0.50, 0.0),
ProspectProfile("Verdant Garden", "small", "garden centre", "medium", ["seasonal stock", "weather-driven demand"], True, 0.56, 0.50, 0.0),
# ---- eval split (last 4) -----------------------------------------------
ProspectProfile("Falcon Sports", "medium", "sporting goods", "high", ["return rate spikes", "regional sizing"], True, 0.69, 0.50, 0.0),
ProspectProfile("Maple & Co", "small", "specialty grocery", "medium", ["organic inventory", "seasonal sourcing"], True, 0.57, 0.50, 0.0),
ProspectProfile("Skyline Pet", "medium", "pet supplies", "high", ["food expiration", "subscription kits"], True, 0.73, 0.50, 0.0),
ProspectProfile("Helix Beauty", "small", "beauty retail", "medium", ["palette variants", "promo windows"], True, 0.61, 0.50, 0.0),
]
# ---------------------------------------------------------------------------
# LEVEL 2 — Medium
# Budget hidden initially, one objection expected, demo required for close.
# ---------------------------------------------------------------------------
PROFILES_L2: List[ProspectProfile] = [
ProspectProfile("Apex Logistics", "enterprise", "logistics", "unknown", ["route optimization", "driver coordination", "fuel tracking"], True, 0.70, 0.50, 0.0),
ProspectProfile("Vertex Supply", "medium", "manufacturing", "unknown", ["vendor visibility", "purchase delays"], True, 0.55, 0.50, 0.0),
ProspectProfile("Polaris Freight", "enterprise", "freight", "unknown", ["dispatch SLA", "fleet maintenance"], True, 0.66, 0.50, 0.0),
ProspectProfile("Cobra Builders", "medium", "construction", "unknown", ["project costing", "subcontractor coordination"], True, 0.60, 0.50, 0.0),
ProspectProfile("Aegis Energy", "enterprise", "utilities", "unknown", ["asset uptime", "grid analytics"], True, 0.71, 0.50, 0.0),
ProspectProfile("Crystal Foods", "medium", "food processing", "unknown", ["batch traceability", "regulatory reporting"], True, 0.58, 0.50, 0.0),
ProspectProfile("Atlas Steel", "enterprise", "metals", "unknown", ["yield optimization", "downtime reduction"], True, 0.65, 0.50, 0.0),
ProspectProfile("Quartz Mobility", "medium", "mobility tech", "unknown", ["fleet utilization", "telematics ingest"], True, 0.59, 0.50, 0.0),
ProspectProfile("Beacon Insure", "enterprise", "insurance", "unknown", ["claims triage", "fraud signals"], True, 0.72, 0.50, 0.0),
ProspectProfile("Tesseract Bio", "medium", "biotech", "unknown", ["lab inventory", "experiment tracking"], True, 0.62, 0.50, 0.0),
ProspectProfile("Pivot Media", "enterprise", "media", "unknown", ["content rights", "campaign attribution"], True, 0.69, 0.50, 0.0),
ProspectProfile("Solstice Travel", "medium", "travel", "unknown", ["booking variance", "supplier API churn"], True, 0.57, 0.50, 0.0),
ProspectProfile("Anvil Robotics", "enterprise", "robotics", "unknown", ["fleet calibration", "OTA updates"], True, 0.74, 0.50, 0.0),
ProspectProfile("Pacific Marine", "medium", "shipping", "unknown", ["port turnaround", "container visibility"], True, 0.61, 0.50, 0.0),
ProspectProfile("Lumen Telecom", "enterprise", "telecom", "unknown", ["service incidents", "field tech routing"], True, 0.68, 0.50, 0.0),
# ---- eval split --------------------------------------------------------
ProspectProfile("Onyx Logistics", "enterprise", "logistics", "unknown", ["last-mile delays", "warehouse handoffs"], True, 0.67, 0.50, 0.0),
ProspectProfile("Sigma Industrial", "medium", "industrial", "unknown", ["MRO inventory", "supplier OTIF"], True, 0.56, 0.50, 0.0),
ProspectProfile("Kepler Insurance", "enterprise", "insurance", "unknown", ["renewal forecasting", "policy ops"], True, 0.70, 0.50, 0.0),
ProspectProfile("Mosaic Energy", "enterprise", "energy", "unknown", ["asset health", "predictive maintenance"], True, 0.66, 0.50, 0.0),
]
# ---------------------------------------------------------------------------
# LEVEL 3 — Hard
# Budget hidden, two objections, possible stalling, decision maker may be absent.
# ---------------------------------------------------------------------------
PROFILES_L3: List[ProspectProfile] = [
ProspectProfile("Nova Financial", "enterprise", "finance", "unknown", ["compliance reporting", "audit trails", "data silos"], False, 0.60, 0.55, 0.30),
ProspectProfile("Atlas Health", "enterprise", "healthcare", "unknown", ["patient workflow delays", "reporting compliance"], False, 0.65, 0.55, 0.25),
ProspectProfile("Citadel Bank", "enterprise", "banking", "unknown", ["KYC automation", "fraud detection lag"], False, 0.62, 0.55, 0.30),
ProspectProfile("Helios Hospitals", "enterprise", "healthcare", "unknown", ["EHR fragmentation", "billing reconciliation"], False, 0.58, 0.55, 0.30),
ProspectProfile("Orion Asset Mgmt", "enterprise", "asset mgmt", "unknown", ["risk reporting", "ESG data ingestion"], False, 0.66, 0.55, 0.25),
ProspectProfile("Sable Pharma", "enterprise", "pharma", "unknown", ["GxP traceability", "trial data integrity"], False, 0.61, 0.55, 0.30),
ProspectProfile("Magellan Travel", "enterprise", "travel ops", "unknown", ["disruption response", "loyalty data"], False, 0.59, 0.55, 0.30),
ProspectProfile("Crucible Defense", "enterprise", "defense", "unknown", ["clearance workflow", "supply chain audit"], False, 0.63, 0.55, 0.25),
ProspectProfile("Seraphim Care", "enterprise", "elder care", "unknown", ["caregiver scheduling", "regulatory reporting"], False, 0.57, 0.55, 0.30),
ProspectProfile("Polaris Reinsure", "enterprise", "reinsurance", "unknown", ["catastrophe modeling", "loss aggregation"], False, 0.64, 0.55, 0.30),
ProspectProfile("Vanguard Edu", "enterprise", "education", "unknown", ["enrollment ops", "compliance audits"], False, 0.55, 0.55, 0.25),
ProspectProfile("Aurora Telecom", "enterprise", "telecom", "unknown", ["spectrum analytics", "tower asset mgmt"], False, 0.60, 0.55, 0.30),
ProspectProfile("Trident Marine", "enterprise", "marine", "unknown", ["fleet compliance", "fuel arbitrage"], False, 0.58, 0.55, 0.30),
ProspectProfile("Granite Mining", "enterprise", "mining", "unknown", ["asset uptime", "ESG reporting"], False, 0.62, 0.55, 0.30),
ProspectProfile("Echelon Health", "enterprise", "health-ins", "unknown", ["claims adjudication", "provider network"], False, 0.59, 0.55, 0.30),
# ---- eval split --------------------------------------------------------
ProspectProfile("Castle Securities", "enterprise", "securities", "unknown", ["trade surveillance", "settlement breaks"], False, 0.61, 0.55, 0.30),
ProspectProfile("Lighthouse Care", "enterprise", "elder care", "unknown", ["staffing variance", "incident reporting"], False, 0.56, 0.55, 0.25),
ProspectProfile("Crown Reinsurance", "enterprise", "reinsurance", "unknown", ["catastrophe modeling", "treaty management"], False, 0.63, 0.55, 0.30),
ProspectProfile("Apex Pharma", "enterprise", "pharma", "unknown", ["clinical-trial reporting", "supply chain audit"], False, 0.60, 0.55, 0.30),
]
# ---------------------------------------------------------------------------
# LEVEL 4 — Adversarial
# Misleading "high" budget signal but actual budget < threshold,
# OR decision maker absent. Correct action is DISQUALIFY.
# ---------------------------------------------------------------------------
PROFILES_L4: List[ProspectProfile] = [
ProspectProfile("Cipher Tech", "small", "technology", "high", ["security", "compliance"], False, 0.20, 0.50, 0.50),
ProspectProfile("BluePeak Studio", "small", "creative agency", "high", ["project visibility", "client reporting"], False, 0.25, 0.50, 0.40),
ProspectProfile("Nimbus Labs", "small", "research", "high", ["grant reporting", "experiment tracking"], False, 0.18, 0.50, 0.45),
ProspectProfile("Halo Consulting", "small", "consulting", "high", ["billable utilization", "client deliverables"], False, 0.22, 0.50, 0.45),
ProspectProfile("Spire Architects", "small", "architecture", "high", ["drawing revisions", "permit tracking"], False, 0.24, 0.50, 0.40),
ProspectProfile("Quill Publishing", "small", "publishing", "high", ["royalty tracking", "rights management"], False, 0.17, 0.50, 0.50),
ProspectProfile("Onyx Boutique", "small", "fashion boutique", "high", ["trend forecasting", "supplier mix"], False, 0.21, 0.50, 0.45),
ProspectProfile("Topaz Cinema", "small", "indie film", "high", ["distribution rights", "festival logistics"], False, 0.19, 0.50, 0.50),
ProspectProfile("Mariner Charter", "small", "yacht charter", "high", ["seasonal demand", "crew scheduling"], False, 0.23, 0.50, 0.45),
ProspectProfile("Velvet Catering", "small", "catering", "high", ["event variance", "ingredient costing"], False, 0.16, 0.50, 0.50),
ProspectProfile("Echo Photography", "small", "studio", "high", ["project pipelines", "asset licensing"], False, 0.20, 0.50, 0.45),
ProspectProfile("Stellar Wellness", "small", "wellness", "high", ["membership churn", "class scheduling"], False, 0.22, 0.50, 0.45),
ProspectProfile("Drift Digital", "small", "agency", "high", ["campaign attribution", "creative asset library"], False, 0.19, 0.50, 0.50),
ProspectProfile("Ember Theater", "small", "performing arts", "high", ["production budgeting", "ticket allocation"], False, 0.18, 0.50, 0.45),
ProspectProfile("Halcyon Crafts", "small", "artisan retail", "high", ["maker payouts", "fulfilment SLA"], False, 0.21, 0.50, 0.50),
# ---- eval split --------------------------------------------------------
ProspectProfile("Onyx Tech", "small", "technology", "high", ["zero-trust rollout", "compliance"], False, 0.19, 0.50, 0.50),
ProspectProfile("Haven Studio", "small", "creative agency", "high", ["client-asset versioning", "billing transparency"], False, 0.23, 0.50, 0.40),
ProspectProfile("Beacon Indie", "small", "publishing", "high", ["distribution rights", "royalty splits"], False, 0.17, 0.50, 0.50),
ProspectProfile("Kindled Catering", "small", "catering", "high", ["event variance", "menu engineering"], False, 0.22, 0.50, 0.45),
]
# ---------------------------------------------------------------------------
# Splits
# ---------------------------------------------------------------------------
# Last `_EVAL_SIZE` of each list is the held-out eval split.
_EVAL_SIZE = 4
def _split(profiles: List[ProspectProfile]) -> tuple[list, list]:
return profiles[:-_EVAL_SIZE], profiles[-_EVAL_SIZE:]
_TRAIN_L1, _EVAL_L1 = _split(PROFILES_L1)
_TRAIN_L2, _EVAL_L2 = _split(PROFILES_L2)
_TRAIN_L3, _EVAL_L3 = _split(PROFILES_L3)
_TRAIN_L4, _EVAL_L4 = _split(PROFILES_L4)
TRAIN_PROFILES = {1: _TRAIN_L1, 2: _TRAIN_L2, 3: _TRAIN_L3, 4: _TRAIN_L4}
EVAL_PROFILES = {1: _EVAL_L1, 2: _EVAL_L2, 3: _EVAL_L3, 4: _EVAL_L4}
ALL_PROFILES = {1: PROFILES_L1, 2: PROFILES_L2, 3: PROFILES_L3, 4: PROFILES_L4}
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
def sample_profile(
difficulty: int,
split: str = "train",
rng: Optional[random.Random] = None,
) -> ProspectProfile:
"""
Sample one profile from the requested split.
Parameters
----------
difficulty : int (1..4)
split : "train" | "eval" | "all"
rng : optional pre-seeded RNG for reproducibility
"""
if difficulty not in TRAIN_PROFILES:
difficulty = 1
pool: List[ProspectProfile]
if split == "eval":
pool = EVAL_PROFILES[difficulty]
elif split == "all":
pool = ALL_PROFILES[difficulty]
else:
pool = TRAIN_PROFILES[difficulty]
return (rng or random).choice(pool)
def iter_train_profiles(difficulty: int) -> Iterator[ProspectProfile]:
yield from TRAIN_PROFILES[difficulty]
def iter_eval_profiles(difficulty: int) -> Iterator[ProspectProfile]:
yield from EVAL_PROFILES[difficulty]
|