usersim 0.2.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. usersim/__init__.py +23 -0
  2. usersim/asset_gen/__init__.py +22 -0
  3. usersim/asset_gen/financial_services/__init__.py +22 -0
  4. usersim/asset_gen/financial_services/evaluate.py +330 -0
  5. usersim/asset_gen/financial_services/pipeline.py +1546 -0
  6. usersim/asset_gen/financial_services/prompts.py +295 -0
  7. usersim/asset_gen/financial_services/spec.py +721 -0
  8. usersim/asset_gen/financial_services/validate.py +647 -0
  9. usersim/asset_gen/registry.py +191 -0
  10. usersim/cli/__init__.py +185 -0
  11. usersim/cli/__main__.py +13 -0
  12. usersim/cli/_async.py +35 -0
  13. usersim/cli/_env.py +74 -0
  14. usersim/cli/_errors.py +44 -0
  15. usersim/cli/_inputs.py +691 -0
  16. usersim/cli/_models.py +1222 -0
  17. usersim/cli/_ngc.py +441 -0
  18. usersim/cli/_persona_language.py +257 -0
  19. usersim/cli/_pipeline.py +943 -0
  20. usersim/cli/evaluate.py +407 -0
  21. usersim/cli/evaluate_assets.py +107 -0
  22. usersim/cli/gen_assets.py +110 -0
  23. usersim/cli/model_catalog.py +449 -0
  24. usersim/cli/models_default.toml +45 -0
  25. usersim/cli/models_openai.toml +26 -0
  26. usersim/cli/models_openrouter.toml +33 -0
  27. usersim/cli/panel.py +182 -0
  28. usersim/cli/report.py +248 -0
  29. usersim/cli/rescore.py +223 -0
  30. usersim/cli/select.py +302 -0
  31. usersim/cli/setup_ngc.py +129 -0
  32. usersim/cli/simulate.py +825 -0
  33. usersim/cli/smoke.py +1015 -0
  34. usersim/cli/validate_assets.py +64 -0
  35. usersim/engine/__init__.py +4 -0
  36. usersim/engine/assets/financial_services/SCHEMA.md +314 -0
  37. usersim/engine/assets/financial_services/en_IN/_generation_report.json +977 -0
  38. usersim/engine/assets/financial_services/en_IN/bank/chandrika_bank/corpus.yaml +9170 -0
  39. usersim/engine/assets/financial_services/en_IN/bank/chandrika_bank/institution_meta.yaml +10 -0
  40. usersim/engine/assets/financial_services/en_IN/bank/chandrika_bank/tasks.yaml +330 -0
  41. usersim/engine/assets/financial_services/en_IN/bank/chandrika_bank/tools.yaml +140 -0
  42. usersim/engine/assets/financial_services/en_IN/brokerage/tarang_invest/corpus.yaml +7436 -0
  43. usersim/engine/assets/financial_services/en_IN/brokerage/tarang_invest/institution_meta.yaml +8 -0
  44. usersim/engine/assets/financial_services/en_IN/brokerage/tarang_invest/tasks.yaml +323 -0
  45. usersim/engine/assets/financial_services/en_IN/brokerage/tarang_invest/tools.yaml +130 -0
  46. usersim/engine/assets/financial_services/en_IN/dynamic.yaml +256 -0
  47. usersim/engine/assets/financial_services/en_IN/insurer/abhaya_insurance/corpus.yaml +7641 -0
  48. usersim/engine/assets/financial_services/en_IN/insurer/abhaya_insurance/institution_meta.yaml +8 -0
  49. usersim/engine/assets/financial_services/en_IN/insurer/abhaya_insurance/tasks.yaml +350 -0
  50. usersim/engine/assets/financial_services/en_IN/insurer/abhaya_insurance/tools.yaml +115 -0
  51. usersim/engine/assets/financial_services/en_IN/nbfc/kalash_finserv/corpus.yaml +9453 -0
  52. usersim/engine/assets/financial_services/en_IN/nbfc/kalash_finserv/institution_meta.yaml +9 -0
  53. usersim/engine/assets/financial_services/en_IN/nbfc/kalash_finserv/tasks.yaml +346 -0
  54. usersim/engine/assets/financial_services/en_IN/nbfc/kalash_finserv/tools.yaml +115 -0
  55. usersim/engine/assets/financial_services/en_IN/neobank/paysetu/corpus.yaml +8237 -0
  56. usersim/engine/assets/financial_services/en_IN/neobank/paysetu/institution_meta.yaml +10 -0
  57. usersim/engine/assets/financial_services/en_IN/neobank/paysetu/tasks.yaml +313 -0
  58. usersim/engine/assets/financial_services/en_IN/neobank/paysetu/tools.yaml +140 -0
  59. usersim/engine/assets/financial_services/en_IN/region_meta.yaml +81 -0
  60. usersim/engine/assets/financial_services/en_US/_generation_report.json +768 -0
  61. usersim/engine/assets/financial_services/en_US/bank/northwind_bank/corpus.yaml +9131 -0
  62. usersim/engine/assets/financial_services/en_US/bank/northwind_bank/institution_meta.yaml +9 -0
  63. usersim/engine/assets/financial_services/en_US/bank/northwind_bank/tasks.yaml +473 -0
  64. usersim/engine/assets/financial_services/en_US/bank/northwind_bank/tools.yaml +130 -0
  65. usersim/engine/assets/financial_services/en_US/brokerage/solstice_invest/corpus.yaml +7868 -0
  66. usersim/engine/assets/financial_services/en_US/brokerage/solstice_invest/institution_meta.yaml +8 -0
  67. usersim/engine/assets/financial_services/en_US/brokerage/solstice_invest/tasks.yaml +388 -0
  68. usersim/engine/assets/financial_services/en_US/brokerage/solstice_invest/tools.yaml +125 -0
  69. usersim/engine/assets/financial_services/en_US/dynamic.yaml +198 -0
  70. usersim/engine/assets/financial_services/en_US/region_meta.yaml +58 -0
  71. usersim/engine/assets/financial_services/en_US/retirement_provider/evergreen_retirement/corpus.yaml +8117 -0
  72. usersim/engine/assets/financial_services/en_US/retirement_provider/evergreen_retirement/institution_meta.yaml +8 -0
  73. usersim/engine/assets/financial_services/en_US/retirement_provider/evergreen_retirement/tasks.yaml +369 -0
  74. usersim/engine/assets/financial_services/en_US/retirement_provider/evergreen_retirement/tools.yaml +110 -0
  75. usersim/engine/assets/financial_services/en_US/wealth_manager/meridian_wealth/corpus.yaml +6897 -0
  76. usersim/engine/assets/financial_services/en_US/wealth_manager/meridian_wealth/institution_meta.yaml +8 -0
  77. usersim/engine/assets/financial_services/en_US/wealth_manager/meridian_wealth/tasks.yaml +319 -0
  78. usersim/engine/assets/financial_services/en_US/wealth_manager/meridian_wealth/tools.yaml +110 -0
  79. usersim/engine/assets/financial_services/hi_Deva_IN/_generation_report.json +977 -0
  80. usersim/engine/assets/financial_services/hi_Deva_IN/bank/chandrika_bank/corpus.yaml +6265 -0
  81. usersim/engine/assets/financial_services/hi_Deva_IN/bank/chandrika_bank/institution_meta.yaml +11 -0
  82. usersim/engine/assets/financial_services/hi_Deva_IN/bank/chandrika_bank/tools.yaml +140 -0
  83. usersim/engine/assets/financial_services/hi_Deva_IN/brokerage/tarang_invest/corpus.yaml +5188 -0
  84. usersim/engine/assets/financial_services/hi_Deva_IN/brokerage/tarang_invest/institution_meta.yaml +9 -0
  85. usersim/engine/assets/financial_services/hi_Deva_IN/brokerage/tarang_invest/tools.yaml +130 -0
  86. usersim/engine/assets/financial_services/hi_Deva_IN/insurer/abhaya_insurance/corpus.yaml +5115 -0
  87. usersim/engine/assets/financial_services/hi_Deva_IN/insurer/abhaya_insurance/institution_meta.yaml +9 -0
  88. usersim/engine/assets/financial_services/hi_Deva_IN/insurer/abhaya_insurance/tools.yaml +115 -0
  89. usersim/engine/assets/financial_services/hi_Deva_IN/nbfc/kalash_finserv/corpus.yaml +6539 -0
  90. usersim/engine/assets/financial_services/hi_Deva_IN/nbfc/kalash_finserv/institution_meta.yaml +10 -0
  91. usersim/engine/assets/financial_services/hi_Deva_IN/nbfc/kalash_finserv/tools.yaml +115 -0
  92. usersim/engine/assets/financial_services/hi_Deva_IN/neobank/paysetu/corpus.yaml +5370 -0
  93. usersim/engine/assets/financial_services/hi_Deva_IN/neobank/paysetu/institution_meta.yaml +11 -0
  94. usersim/engine/assets/financial_services/hi_Deva_IN/neobank/paysetu/tools.yaml +140 -0
  95. usersim/engine/assets/financial_services/hi_Deva_IN/region_meta.yaml +81 -0
  96. usersim/engine/assets/general_educational/prompts.yaml +115 -0
  97. usersim/engine/assets/general_educational/subjects/base/subjects.json +162 -0
  98. usersim/engine/assets/general_educational/subjects/fr_FR/subjects.json +16 -0
  99. usersim/engine/assets/general_educational/subjects/hi_Deva_IN/subjects.json +14 -0
  100. usersim/engine/assets/general_educational/subjects/hi_Latn_IN/subjects.json +14 -0
  101. usersim/engine/assets/general_educational/subjects/ja_JP/subjects.json +16 -0
  102. usersim/engine/assets/general_educational/subjects/ko_KR/subjects.json +16 -0
  103. usersim/engine/assets/general_educational/subjects/pt_BR/subjects.json +12 -0
  104. usersim/engine/assets/general_open_ended/prompts.yaml +117 -0
  105. usersim/engine/assets/general_open_ended/topics/base/topics.json +194 -0
  106. usersim/engine/assets/general_open_ended/topics/fr_FR/topics.json +22 -0
  107. usersim/engine/assets/general_open_ended/topics/hi_Deva_IN/topics.json +22 -0
  108. usersim/engine/assets/general_open_ended/topics/hi_Latn_IN/topics.json +22 -0
  109. usersim/engine/assets/general_open_ended/topics/ja_JP/topics.json +17 -0
  110. usersim/engine/assets/general_open_ended/topics/ko_KR/topics.json +17 -0
  111. usersim/engine/assets/general_open_ended/topics/pt_BR/topics.json +22 -0
  112. usersim/engine/assets/health_decision_support_disclosure/sample.yaml +21 -0
  113. usersim/engine/assets/health_general_disclosure/sample.yaml +21 -0
  114. usersim/engine/assets/health_therapy_disclosure/sample.yaml +23 -0
  115. usersim/engine/assets/health_triage_disclosure/sample.yaml +21 -0
  116. usersim/engine/assets/identity_disclosure/SCHEMA.md +303 -0
  117. usersim/engine/assets/identity_disclosure/spec.yaml +993 -0
  118. usersim/engine/assets/safety_agentic/SCHEMA.md +233 -0
  119. usersim/engine/assets/safety_agentic/action_taxonomy.yaml +729 -0
  120. usersim/engine/assets/safety_agentic/sample.yaml +3705 -0
  121. usersim/engine/assets/safety_chat_pressure/SCHEMA.md +170 -0
  122. usersim/engine/assets/safety_chat_pressure/sample.yaml +3262 -0
  123. usersim/engine/assets/sov_ai_dynamic/SCHEMA.md +100 -0
  124. usersim/engine/assets/sov_ai_dynamic/en_IN/categories.yaml +116 -0
  125. usersim/engine/assets/sov_ai_dynamic/en_SG/categories.yaml +116 -0
  126. usersim/engine/assets/sov_ai_dynamic/en_US/categories.yaml +116 -0
  127. usersim/engine/assets/sov_ai_dynamic/fr_FR/categories.yaml +116 -0
  128. usersim/engine/assets/sov_ai_dynamic/hi_Deva_IN/categories.yaml +116 -0
  129. usersim/engine/assets/sov_ai_dynamic/hi_Latn_IN/categories.yaml +116 -0
  130. usersim/engine/assets/sov_ai_dynamic/ja_JP/categories.yaml +116 -0
  131. usersim/engine/assets/sov_ai_dynamic/ko_KR/categories.yaml +116 -0
  132. usersim/engine/assets/sov_ai_dynamic/pt_BR/categories.yaml +116 -0
  133. usersim/engine/assets/sov_ai_facts/SCHEMA.md +191 -0
  134. usersim/engine/assets/sov_ai_facts/en_IN/sample.yaml +1299 -0
  135. usersim/engine/assets/sov_ai_facts/en_SG/sample.yaml +1368 -0
  136. usersim/engine/assets/sov_ai_facts/en_US/sample.yaml +1295 -0
  137. usersim/engine/assets/sov_ai_facts/fr_FR/sample.yaml +1312 -0
  138. usersim/engine/assets/sov_ai_facts/hi_Deva_IN/sample.yaml +1297 -0
  139. usersim/engine/assets/sov_ai_facts/hi_Latn_IN/sample.yaml +1297 -0
  140. usersim/engine/assets/sov_ai_facts/ja_JP/sample.yaml +1319 -0
  141. usersim/engine/assets/sov_ai_facts/ko_KR/sample.yaml +1445 -0
  142. usersim/engine/assets/sov_ai_facts/pt_BR/sample.yaml +1344 -0
  143. usersim/engine/assets/sov_ai_multilingual_parity/SCHEMA.md +141 -0
  144. usersim/engine/assets/sov_ai_multilingual_parity/sample.yaml +1112 -0
  145. usersim/engine/assets/tool_calling/README.md +225 -0
  146. usersim/engine/assets/tool_calling/prompts.yaml +171 -0
  147. usersim/engine/assets/tool_calling/toolsets_seed.parquet +0 -0
  148. usersim/engine/config.py +297 -0
  149. usersim/engine/core/__init__.py +4 -0
  150. usersim/engine/core/_assets.py +178 -0
  151. usersim/engine/core/_extensions.py +148 -0
  152. usersim/engine/core/action_taxonomy.py +347 -0
  153. usersim/engine/core/agentic_bank.py +725 -0
  154. usersim/engine/core/analysis.py +60 -0
  155. usersim/engine/core/backends.py +180 -0
  156. usersim/engine/core/behavioral.py +739 -0
  157. usersim/engine/core/clinical_profile_bank.py +244 -0
  158. usersim/engine/core/context.py +137 -0
  159. usersim/engine/core/embeddings.py +83 -0
  160. usersim/engine/core/episode_input.py +281 -0
  161. usersim/engine/core/episode_runtime.py +735 -0
  162. usersim/engine/core/fact_bank.py +499 -0
  163. usersim/engine/core/finance_bank.py +872 -0
  164. usersim/engine/core/finance_tasks.py +292 -0
  165. usersim/engine/core/idempotency.py +62 -0
  166. usersim/engine/core/identity.py +217 -0
  167. usersim/engine/core/identity_spec.py +659 -0
  168. usersim/engine/core/identity_spec_types.py +425 -0
  169. usersim/engine/core/judges.py +85 -0
  170. usersim/engine/core/language_detection.py +239 -0
  171. usersim/engine/core/llm.py +710 -0
  172. usersim/engine/core/locale.py +504 -0
  173. usersim/engine/core/localized.py +172 -0
  174. usersim/engine/core/manifest.py +998 -0
  175. usersim/engine/core/messages.py +93 -0
  176. usersim/engine/core/missing.py +38 -0
  177. usersim/engine/core/outcomes.py +450 -0
  178. usersim/engine/core/persona.py +178 -0
  179. usersim/engine/core/pressure_bank.py +628 -0
  180. usersim/engine/core/preview.py +1888 -0
  181. usersim/engine/core/probes.py +1499 -0
  182. usersim/engine/core/probing_taxonomy.py +462 -0
  183. usersim/engine/core/prompt_loader.py +93 -0
  184. usersim/engine/core/prompts.py +81 -0
  185. usersim/engine/core/provenance.py +118 -0
  186. usersim/engine/core/query_bank.py +567 -0
  187. usersim/engine/core/realized_audit.py +411 -0
  188. usersim/engine/core/seeds.py +82 -0
  189. usersim/engine/core/simulation.py +1867 -0
  190. usersim/engine/core/storage.py +1407 -0
  191. usersim/engine/core/tool_calls.py +106 -0
  192. usersim/engine/core/translation.py +266 -0
  193. usersim/engine/core/user_turn_policy.py +80 -0
  194. usersim/engine/evaluator/__init__.py +18 -0
  195. usersim/engine/evaluator/axes.py +437 -0
  196. usersim/engine/evaluator/config.py +190 -0
  197. usersim/engine/evaluator/generator.py +424 -0
  198. usersim/engine/evaluator/judges.py +156 -0
  199. usersim/engine/evaluator/prompts.py +108 -0
  200. usersim/engine/evaluator/scorers/__init__.py +278 -0
  201. usersim/engine/evaluator/scorers/financial_services.py +635 -0
  202. usersim/engine/evaluator/scorers/health_disclosure.py +503 -0
  203. usersim/engine/evaluator/scorers/identity_disclosure.py +545 -0
  204. usersim/engine/evaluator/scorers/identity_grading.py +543 -0
  205. usersim/engine/evaluator/scorers/language_compliance.py +426 -0
  206. usersim/engine/evaluator/scorers/refusal_basics.py +461 -0
  207. usersim/engine/evaluator/scorers/response_shape.py +363 -0
  208. usersim/engine/evaluator/scorers/safety_agentic.py +1001 -0
  209. usersim/engine/evaluator/scorers/safety_chat_pressure.py +792 -0
  210. usersim/engine/evaluator/scorers/sov_ai_dynamic.py +632 -0
  211. usersim/engine/evaluator/scorers/sov_ai_facts.py +651 -0
  212. usersim/engine/evaluator/scorers/sov_ai_multilingual_parity.py +557 -0
  213. usersim/engine/evaluator/scorers/tool_use.py +291 -0
  214. usersim/engine/external.py +133 -0
  215. usersim/engine/generator.py +419 -0
  216. usersim/engine/plugin.py +34 -0
  217. usersim/engine/probes/__init__.py +28 -0
  218. usersim/engine/probes/financial_services/__init__.py +16 -0
  219. usersim/engine/probes/financial_services/generator.py +1327 -0
  220. usersim/engine/probes/financial_services/prompts.py +132 -0
  221. usersim/engine/probes/financial_services/retrieval.py +207 -0
  222. usersim/engine/probes/financial_services/task_derivation.py +274 -0
  223. usersim/engine/probes/financial_services/tools.py +116 -0
  224. usersim/engine/probes/general_educational/__init__.py +4 -0
  225. usersim/engine/probes/general_educational/generator.py +125 -0
  226. usersim/engine/probes/general_educational/prompts.py +207 -0
  227. usersim/engine/probes/general_open_ended/__init__.py +4 -0
  228. usersim/engine/probes/general_open_ended/generator.py +127 -0
  229. usersim/engine/probes/general_open_ended/prompts.py +209 -0
  230. usersim/engine/probes/health_disclosure/__init__.py +28 -0
  231. usersim/engine/probes/health_disclosure/base.py +148 -0
  232. usersim/engine/probes/health_disclosure/clients.py +291 -0
  233. usersim/engine/probes/health_disclosure/decision_support.py +44 -0
  234. usersim/engine/probes/health_disclosure/general.py +45 -0
  235. usersim/engine/probes/health_disclosure/mixin.py +363 -0
  236. usersim/engine/probes/health_disclosure/move_runtime.py +486 -0
  237. usersim/engine/probes/health_disclosure/moves.py +241 -0
  238. usersim/engine/probes/health_disclosure/prompts.py +323 -0
  239. usersim/engine/probes/health_disclosure/therapy.py +43 -0
  240. usersim/engine/probes/health_disclosure/triage.py +43 -0
  241. usersim/engine/probes/identity_disclosure/__init__.py +4 -0
  242. usersim/engine/probes/identity_disclosure/generator.py +502 -0
  243. usersim/engine/probes/identity_disclosure/prompts.py +219 -0
  244. usersim/engine/probes/identity_disclosure/task_derivation.py +227 -0
  245. usersim/engine/probes/safety_agentic/__init__.py +24 -0
  246. usersim/engine/probes/safety_agentic/generator.py +666 -0
  247. usersim/engine/probes/safety_agentic/prompts.py +24 -0
  248. usersim/engine/probes/safety_agentic/task_derivation.py +109 -0
  249. usersim/engine/probes/safety_chat_pressure/__init__.py +24 -0
  250. usersim/engine/probes/safety_chat_pressure/classifier.py +113 -0
  251. usersim/engine/probes/safety_chat_pressure/generator.py +555 -0
  252. usersim/engine/probes/safety_chat_pressure/prompts.py +144 -0
  253. usersim/engine/probes/safety_chat_pressure/task_derivation.py +171 -0
  254. usersim/engine/probes/sov_ai_dynamic/__init__.py +28 -0
  255. usersim/engine/probes/sov_ai_dynamic/generator.py +393 -0
  256. usersim/engine/probes/sov_ai_dynamic/prompts.py +401 -0
  257. usersim/engine/probes/sov_ai_dynamic/task_derivation.py +106 -0
  258. usersim/engine/probes/sov_ai_facts/__init__.py +15 -0
  259. usersim/engine/probes/sov_ai_facts/generator.py +342 -0
  260. usersim/engine/probes/sov_ai_facts/prompts.py +600 -0
  261. usersim/engine/probes/sov_ai_facts/task_derivation.py +358 -0
  262. usersim/engine/probes/sov_ai_multilingual_parity/__init__.py +19 -0
  263. usersim/engine/probes/sov_ai_multilingual_parity/generator.py +378 -0
  264. usersim/engine/probes/sov_ai_multilingual_parity/prompts.py +391 -0
  265. usersim/engine/probes/sov_ai_multilingual_parity/task_derivation.py +104 -0
  266. usersim/engine/probes/tool_calling/__init__.py +9 -0
  267. usersim/engine/probes/tool_calling/generator.py +447 -0
  268. usersim/engine/probes/tool_calling/prompts.py +221 -0
  269. usersim/engine/probes/tool_calling/verifiers.py +165 -0
  270. usersim/py.typed +0 -0
  271. usersim/reporting/__init__.py +117 -0
  272. usersim/reporting/_reasoning.py +202 -0
  273. usersim/reporting/capability_report.py +1956 -0
  274. usersim/reporting/comparison.py +797 -0
  275. usersim/reporting/comparison_dashboard.py +2938 -0
  276. usersim/reporting/dashboard.py +1728 -0
  277. usersim/reporting/diagnostics.py +435 -0
  278. usersim/reporting/eval_manifest.py +291 -0
  279. usersim/reporting/identity.py +82 -0
  280. usersim/reporting/resource.py +476 -0
  281. usersim/reporting/runs.py +282 -0
  282. usersim/reporting/sim_health.py +334 -0
  283. usersim/selection/__init__.py +87 -0
  284. usersim/selection/engine.py +423 -0
  285. usersim/selection/io.py +391 -0
  286. usersim/selection/profiles.py +249 -0
  287. usersim/selection/schemas.py +194 -0
  288. usersim/taxonomy/__init__.py +38 -0
  289. usersim/taxonomy/capabilities.py +1125 -0
  290. usersim/taxonomy/eval_cell.py +131 -0
  291. usersim/testing/__init__.py +534 -0
  292. usersim-0.2.0rc1.dist-info/METADATA +333 -0
  293. usersim-0.2.0rc1.dist-info/RECORD +297 -0
  294. usersim-0.2.0rc1.dist-info/WHEEL +5 -0
  295. usersim-0.2.0rc1.dist-info/entry_points.txt +6 -0
  296. usersim-0.2.0rc1.dist-info/licenses/LICENSE +204 -0
  297. usersim-0.2.0rc1.dist-info/top_level.txt +1 -0
usersim/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """usersim — population-grounded user simulation for evaluating conversational LLMs."""
5
+
6
+ import os
7
+
8
+ # Data Designer reads this once, when its telemetry module is first imported, and
9
+ # prefixes the session id of its anonymous usage events with it. Setting it before
10
+ # any usersim module imports Data Designer marks those events as User Sim's; a
11
+ # prefix the caller already set is kept.
12
+ os.environ.setdefault("NEMO_SESSION_PREFIX", "usersim-")
13
+
14
+ from usersim.engine.core.provenance import own_distribution
15
+
16
+
17
+ def _installed_version() -> str:
18
+ """The installed distribution's version; ``0+unknown`` for a source tree never installed."""
19
+ distribution = own_distribution()
20
+ return distribution.version if distribution is not None else "0+unknown"
21
+
22
+
23
+ __version__ = _installed_version()
@@ -0,0 +1,22 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """asset_gen — offline Data Designer generation of financial_services assets.
5
+
6
+ Repo-root package (parallels ``reporting/`` and ``selection/``) that authors the
7
+ committed ``assets/financial_services/<locale>/`` banks the runtime probe/scorer
8
+ consume. It imports the plugin (for the shared gold-derivation resolver + bank
9
+ schema) but the plugin never imports it — the dependency arrow is one-way.
10
+
11
+ Layout:
12
+
13
+ - ``asset_gen/financial_services/spec.py`` — the region_spec contract as Pydantic
14
+ models (the shared vocabulary + validation).
15
+ - ``asset_gen/financial_services/pipeline.py`` — the whole Data Designer flow:
16
+ schemas -> prompt -> seed plan -> column DAG -> explode -> serialize.
17
+ - ``asset_gen/financial_services/region_spec/*.yaml`` — the committed structured
18
+ seed per region (the generation input; data, not code).
19
+
20
+ See the probe design doc:
21
+ ``src/usersim/engine/probes/financial_services/README.md``.
22
+ """
@@ -0,0 +1,22 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """financial_services generation: region_spec contract (``spec``) + DD pipeline.
5
+
6
+ The committed ``region_spec/*.yaml`` files are the generation input; ``spec.py``
7
+ validates them and ``pipeline.py`` expands them into a per-institution asset bank.
8
+ """
9
+
10
+ from usersim.asset_gen.financial_services.spec import (
11
+ RegionSpec,
12
+ RegionSpecError,
13
+ load_region_spec,
14
+ validate_region_spec,
15
+ )
16
+
17
+ __all__ = [
18
+ "RegionSpec",
19
+ "RegionSpecError",
20
+ "load_region_spec",
21
+ "validate_region_spec",
22
+ ]
@@ -0,0 +1,330 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """DD-judge asset EVALUATION: a per-document quality scorecard over a committed bank.
5
+
6
+ The judge-ensemble layer on top of the deterministic ``validate.py`` gate (mirrors
7
+ how the evaluator's LLM judges sit on top of the mechanical scorers). Non-blocking
8
+ in v1: it produces a scorecard for review/selection, it does not gate.
9
+
10
+ Decoupled from generation: evaluates ANY committed bank, per-DOCUMENT, on richer
11
+ subjective axes than the per-cluster gen-time QC. Network-gated (LLM judge), so the
12
+ live path is not exercised in offline CI; the pure pieces (``build_eval_seed``,
13
+ ``build_eval_columns``, ``shape_scorecard``) are tested offline.
14
+
15
+ Axes: realism, regulatory_soundness, clarity_readability, self_containment,
16
+ brief_consistency (judged by a family DISTINCT from doc-gen -- ``asset_judge_model``).
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ from dataclasses import dataclass, field
23
+ from datetime import datetime, timezone
24
+ from pathlib import Path
25
+ from typing import Any
26
+
27
+ from usersim.asset_gen.financial_services.prompts import EVAL_PROMPT
28
+ from usersim.asset_gen.financial_services.spec import RegionSpec
29
+
30
+ #: judge model alias (distinct from the doc-gen model to avoid self-preference).
31
+ EVAL_JUDGE_MODEL_ALIAS = "asset_judge_model"
32
+
33
+ #: per-axis score (1-5) below which a document is flagged for review.
34
+ LOW_SCORE_THRESHOLD = 3
35
+
36
+ #: (rubric name, flattened column name); the column order defines the scorecard.
37
+ _AXES: tuple[tuple[str, str], ...] = (
38
+ ("Realism", "realism_score"),
39
+ ("RegulatorySoundness", "regulatory_soundness_score"),
40
+ ("ClarityReadability", "clarity_readability_score"),
41
+ ("SelfContainment", "self_containment_score"),
42
+ ("BriefConsistency", "brief_consistency_score"),
43
+ )
44
+
45
+
46
+ def evaluate_scores() -> list[Any]:
47
+ """The per-document judge rubrics (``dd.Score``, 1-5)."""
48
+ import data_designer.config as dd
49
+
50
+ def score(name: str, description: str, high: str, low: str) -> Any:
51
+ return dd.Score(
52
+ name=name,
53
+ description=description,
54
+ options={
55
+ 5: f"Excellent. {high}",
56
+ 4: "Good, minor issues.",
57
+ 3: "Acceptable but noticeably rough.",
58
+ 2: "Weak; multiple problems.",
59
+ 1: f"Poor. {low}",
60
+ },
61
+ )
62
+
63
+ return [
64
+ score(
65
+ "Realism",
66
+ "Reads like a real document of its genre for this institution.",
67
+ "Convincing, specific, plausible.",
68
+ "Generic/templated or implausible.",
69
+ ),
70
+ score(
71
+ "RegulatorySoundness",
72
+ "Consistent with the applicable regulator context; no misstatements.",
73
+ "Regulatorily sound and appropriately cautious.",
74
+ "Misstates or ignores the rules.",
75
+ ),
76
+ score(
77
+ "ClarityReadability",
78
+ "Clear and appropriate for the institution's brand voice + reading level.",
79
+ "Clear, well-structured, on-voice.",
80
+ "Confusing or off-voice.",
81
+ ),
82
+ # Reading this axis: it runs ~2.7-2.9 corpus-wide and the low scorers cluster
83
+ # on the TABULAR / reference genres -- discoverable_tool_doc 2.19,
84
+ # fee_schedule 2.31, terms_conditions 2.47, eligibility_matrix and promo_notice
85
+ # 2.52 -- while every prose genre sits at 2.9-3.0 (faq 3.02, product_sheet
86
+ # 3.02, disclosure 3.03, security_advisory 3.00, troubleshooting 2.99).
87
+ #
88
+ # Two tempting explanations were measured and BOTH are wrong:
89
+ # - LENGTH. The pooled correlation(words, score) is +0.32, but the median
90
+ # WITHIN-genre correlation is +0.07, and it is NEGATIVE for fee_schedule
91
+ # (-0.19) and rate_sheet (-0.13). The pooled figure is Simpson's paradox:
92
+ # rich-tier genres are both longer and better. terms_conditions is already
93
+ # in the longest tier (258 median words) and still scores 2.47; faq is
94
+ # short (142) and scores 3.02. Raising the length budget would very likely
95
+ # make the tabular genres worse.
96
+ # - SUBSTANCE DEFERRAL via raw doc_key references. Real (those docs average
97
+ # 2.38 vs 2.78) but it affects only ~0.7% of documents, so it cannot
98
+ # explain 18 of 27 fee_schedules scoring <=2. Now caught deterministically
99
+ # by validate.py's doc_key_leak check.
100
+ #
101
+ # What remains is a rubric-fit hypothesis: "usable on its own ... a policy is
102
+ # actionable" is a prose standard, and a fee table or a tool signature cannot
103
+ # satisfy it however well written. That is UNCONFIRMED. Treat a flat ~2.5 on
104
+ # the tabular genres as un-diagnosed rather than as a known defect, and do not
105
+ # re-derive a length theory from the pooled correlation.
106
+ score(
107
+ "SelfContainment",
108
+ "Usable on its own (a FAQ answers its question; a policy is actionable).",
109
+ "Self-contained and actionable.",
110
+ "Depends on missing context.",
111
+ ),
112
+ score(
113
+ "BriefConsistency",
114
+ "Follows the institution brief's positioning/policy stance.",
115
+ "Fully consistent with the brief.",
116
+ "Contradicts the brief.",
117
+ ),
118
+ ]
119
+
120
+
121
+ def build_eval_columns() -> list[Any]:
122
+ """DD columns: one LLM-judge over each doc + per-axis score flatteners."""
123
+ import data_designer.config as dd
124
+
125
+ cols: list[Any] = [
126
+ dd.LLMJudgeColumnConfig(
127
+ name="doc_eval",
128
+ model_alias=EVAL_JUDGE_MODEL_ALIAS,
129
+ prompt=EVAL_PROMPT,
130
+ scores=evaluate_scores(),
131
+ )
132
+ ]
133
+ for rubric, col in _AXES:
134
+ cols.append(dd.ExpressionColumnConfig(name=col, expr="{{ doc_eval.%s.score }}" % rubric))
135
+ return cols
136
+
137
+
138
+ # ── seed: one row per committed document ────────────────────────────────
139
+
140
+
141
+ def build_eval_seed(bank_dir: str | Path, region_spec: RegionSpec | None):
142
+ """One DD seed row per committed document, carrying judge context (pure/offline).
143
+
144
+ Joins each document with its institution's generated brief (from
145
+ ``_generation_report.json``), the product's authoritative typed values (from the
146
+ region_spec, keyed on ``product_category``), and the domain's regulator text.
147
+ """
148
+ import pandas as pd
149
+
150
+ from usersim.asset_gen.financial_services.validate import _read_bank
151
+
152
+ bank_dir = Path(bank_dir)
153
+ region_meta, insts = _read_bank(bank_dir)
154
+
155
+ # brief per institution (best-effort, from the generation report).
156
+ briefs: dict[str, str] = {}
157
+ report_path = bank_dir / "_generation_report.json"
158
+ if report_path.exists():
159
+ try:
160
+ gr = json.loads(report_path.read_text(encoding="utf-8"))
161
+ for iid, idata in (gr.get("institutions", {}) or {}).items():
162
+ if idata.get("brief") is not None:
163
+ briefs[iid] = json.dumps(idata["brief"], ensure_ascii=False)
164
+ except json.JSONDecodeError:
165
+ pass
166
+
167
+ # authoritative typed values per (institution, product_category).
168
+ values: dict[tuple[str, str], str] = {}
169
+ if region_spec is not None:
170
+ for inst in region_spec.institutions:
171
+ for product in inst.products:
172
+ values[(inst.id, product.id)] = json.dumps(
173
+ {k: v.value for k, v in product.variables.items()}, ensure_ascii=False
174
+ )
175
+
176
+ dr = region_meta.get("domain_regulators", {}) or {}
177
+ language = region_spec.language if region_spec is not None else ""
178
+
179
+ rows: list[dict[str, Any]] = []
180
+ for iid, idata in insts.items():
181
+ for d in idata.documents:
182
+ domain = d.get("domain", "")
183
+ reg = dr.get(domain, {}) if isinstance(dr, dict) else {}
184
+ rows.append(
185
+ {
186
+ "id": d.get("id"),
187
+ "institution_id": iid,
188
+ "document_type": d.get("document_type", ""),
189
+ "title": d.get("title", ""),
190
+ "body": d.get("body", ""),
191
+ "language": language,
192
+ "institution_brief": briefs.get(iid, "{}"),
193
+ "authoritative_values": values.get((iid, d.get("product_category")), "{}"),
194
+ "regulator_text": (reg.get("regulator_text", "") if isinstance(reg, dict) else ""),
195
+ }
196
+ )
197
+ return pd.DataFrame(rows)
198
+
199
+
200
+ # ── scorecard shaping ────────────────────────────────────────────────────
201
+
202
+
203
+ @dataclass
204
+ class EvalScorecard:
205
+ per_doc: list[dict[str, Any]] = field(default_factory=list)
206
+ aggregate: dict[str, Any] = field(default_factory=dict)
207
+
208
+ def to_text(self) -> str:
209
+ lines = [" aggregate (mean 1-5):"]
210
+ for _, col in _AXES:
211
+ lines.append(f" {col}: {self.aggregate.get(col, float('nan')):.2f}")
212
+ low = self.aggregate.get("low_docs", [])
213
+ lines.append(f" {len(low)} doc(s) below {LOW_SCORE_THRESHOLD} on some axis" + (f": {low[:10]}" if low else ""))
214
+ return "\n".join(lines)
215
+
216
+
217
+ def shape_scorecard(result_df) -> EvalScorecard:
218
+ """Flatten a judged dataframe into a per-doc scorecard + aggregate (pure)."""
219
+
220
+ def _num(v: Any) -> float | None:
221
+ try:
222
+ return float(v)
223
+ except (TypeError, ValueError):
224
+ return None
225
+
226
+ per_doc: list[dict[str, Any]] = []
227
+ low_docs: list[str] = []
228
+ sums: dict[str, float] = {col: 0.0 for _, col in _AXES}
229
+ counts: dict[str, int] = {col: 0 for _, col in _AXES}
230
+
231
+ for _, row in result_df.iterrows():
232
+ rec: dict[str, Any] = {
233
+ "id": row.get("id"),
234
+ "institution_id": row.get("institution_id"),
235
+ "document_type": row.get("document_type"),
236
+ }
237
+ is_low = False
238
+ for _, col in _AXES:
239
+ val = _num(row.get(col))
240
+ rec[col] = val
241
+ if val is not None:
242
+ sums[col] += val
243
+ counts[col] += 1
244
+ if val < LOW_SCORE_THRESHOLD:
245
+ is_low = True
246
+ per_doc.append(rec)
247
+ if is_low and rec["id"] is not None:
248
+ low_docs.append(str(rec["id"]))
249
+
250
+ aggregate: dict[str, Any] = {col: (sums[col] / counts[col] if counts[col] else float("nan")) for _, col in _AXES}
251
+ aggregate["n_docs"] = len(per_doc)
252
+ aggregate["low_docs"] = low_docs
253
+ return EvalScorecard(per_doc=per_doc, aggregate=aggregate)
254
+
255
+
256
+ # ── entry point (network-gated) ─────────────────────────────────────────
257
+
258
+
259
+ def validate_eval_models(models: Any) -> None:
260
+ from usersim.cli._models import require_aliases
261
+
262
+ require_aliases(models, required=(EVAL_JUDGE_MODEL_ALIAS,), context="evaluate-assets --domain financial_services")
263
+
264
+
265
+ #: Where superseded scorecards go, relative to the bank dir.
266
+ SCORECARD_HISTORY_DIR = "_scorecards"
267
+
268
+
269
+ def _archive_previous_scorecard(bank_dir: Path) -> Path | None:
270
+ """Move an existing ``_eval_scorecard.parquet`` into ``_scorecards/`` first.
271
+
272
+ Regeneration used to overwrite the scorecard in place, so the previous run's
273
+ numbers were gone the moment you produced new ones -- which makes "did that prompt
274
+ change help?" unanswerable, and it repeatedly was. Archiving on write keeps the
275
+ stable filename (the asset notebook reads that exact path) while preserving each
276
+ superseded run, INCLUDING the one already on disk when this first ships.
277
+
278
+ Stamped by the file's own mtime, so the name reflects when those scores were
279
+ produced rather than when they were displaced. Note ``bank_dir`` is the scratch
280
+ output dir, so this is for run-to-run comparison and is not durable provenance --
281
+ it does not survive cleaning scratch and does not travel between machines.
282
+ Returns the archived path, or ``None`` when there was nothing to archive.
283
+ """
284
+ current = bank_dir / "_eval_scorecard.parquet"
285
+ if not current.is_file():
286
+ return None
287
+ stamp = datetime.fromtimestamp(current.stat().st_mtime, timezone.utc)
288
+ dest_dir = bank_dir / SCORECARD_HISTORY_DIR
289
+ dest_dir.mkdir(parents=True, exist_ok=True)
290
+ dest = dest_dir / f"{stamp:%Y%m%dT%H%M%SZ}.parquet"
291
+ if dest.exists(): # same-second re-run; keep both rather than clobber
292
+ dest = dest_dir / f"{stamp:%Y%m%dT%H%M%SZ}-{current.stat().st_size}.parquet"
293
+ current.replace(dest)
294
+ return dest
295
+
296
+
297
+ def evaluate_bank(
298
+ bank_dir: str | Path,
299
+ region_spec: RegionSpec | None,
300
+ models: Any,
301
+ ) -> EvalScorecard:
302
+ """Judge each committed document and write ``_eval_scorecard.parquet``.
303
+
304
+ Live (needs the judge alias + network; validated up front). Returns the
305
+ :class:`EvalScorecard`; the pure pieces are tested offline.
306
+ """
307
+ validate_eval_models(models)
308
+
309
+ import data_designer.config as dd
310
+
311
+ from usersim.asset_gen.financial_services.pipeline import _make_engine
312
+ from usersim.cli._models import to_model_configs
313
+
314
+ seed = build_eval_seed(bank_dir, region_spec)
315
+ if seed.empty:
316
+ return EvalScorecard()
317
+
318
+ engine = _make_engine(models)
319
+ builder = dd.DataDesignerConfigBuilder(model_configs=to_model_configs(models))
320
+ builder.with_seed_dataset(dd.DataFrameSeedSource(df=seed))
321
+ for col in build_eval_columns():
322
+ builder.add_column(col)
323
+ result = engine.create(builder, num_records=len(seed)).load_dataset().reset_index(drop=True)
324
+
325
+ scorecard = shape_scorecard(result)
326
+ import pandas as pd
327
+
328
+ _archive_previous_scorecard(Path(bank_dir))
329
+ pd.DataFrame(scorecard.per_doc).to_parquet(Path(bank_dir) / "_eval_scorecard.parquet")
330
+ return scorecard