infralab 1.0.0__tar.gz

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 (168) hide show
  1. infralab-1.0.0/LICENSE +21 -0
  2. infralab-1.0.0/PKG-INFO +240 -0
  3. infralab-1.0.0/README.md +195 -0
  4. infralab-1.0.0/infralab/__init__.py +24 -0
  5. infralab-1.0.0/infralab/agents/__init__.py +1 -0
  6. infralab-1.0.0/infralab/agents/adapters.py +61 -0
  7. infralab-1.0.0/infralab/agents/agentic.py +350 -0
  8. infralab-1.0.0/infralab/agents/basic.py +354 -0
  9. infralab-1.0.0/infralab/agents/copilot.py +116 -0
  10. infralab-1.0.0/infralab/agents/legacy.py +1116 -0
  11. infralab-1.0.0/infralab/agents/protocol.py +375 -0
  12. infralab-1.0.0/infralab/agents/sandbox.py +448 -0
  13. infralab-1.0.0/infralab/agents/scripted.py +112 -0
  14. infralab-1.0.0/infralab/analysis.py +352 -0
  15. infralab-1.0.0/infralab/api.py +514 -0
  16. infralab-1.0.0/infralab/assets/__init__.py +14 -0
  17. infralab-1.0.0/infralab/assets/base.py +83 -0
  18. infralab-1.0.0/infralab/assets/bridges/__init__.py +44 -0
  19. infralab-1.0.0/infralab/assets/bridges/limit_states.py +102 -0
  20. infralab-1.0.0/infralab/assets/bridges/profiles.py +54 -0
  21. infralab-1.0.0/infralab/assets/buildings/__init__.py +54 -0
  22. infralab-1.0.0/infralab/assets/buildings/limit_states.py +161 -0
  23. infralab-1.0.0/infralab/assets/buildings/profiles.py +80 -0
  24. infralab-1.0.0/infralab/assets/registry.py +26 -0
  25. infralab-1.0.0/infralab/assets/retaining_walls/__init__.py +50 -0
  26. infralab-1.0.0/infralab/assets/retaining_walls/limit_states.py +122 -0
  27. infralab-1.0.0/infralab/assets/retaining_walls/profiles.py +59 -0
  28. infralab-1.0.0/infralab/assets/solar_trackers/__init__.py +52 -0
  29. infralab-1.0.0/infralab/assets/solar_trackers/limit_states.py +127 -0
  30. infralab-1.0.0/infralab/assets/solar_trackers/profiles.py +79 -0
  31. infralab-1.0.0/infralab/assets/wind_turbines/__init__.py +57 -0
  32. infralab-1.0.0/infralab/assets/wind_turbines/limit_states.py +162 -0
  33. infralab-1.0.0/infralab/assets/wind_turbines/profiles.py +88 -0
  34. infralab-1.0.0/infralab/benchmarks/__init__.py +0 -0
  35. infralab-1.0.0/infralab/benchmarks/regbench/configurations.yaml +602 -0
  36. infralab-1.0.0/infralab/benchmarks/regbench/degradation/markov_nbi.py +16 -0
  37. infralab-1.0.0/infralab/benchmarks/regbench/degradation/power.py +11 -0
  38. infralab-1.0.0/infralab/benchmarks/regbench/laws/D0.yaml +22 -0
  39. infralab-1.0.0/infralab/benchmarks/regbench/laws/D1.yaml +24 -0
  40. infralab-1.0.0/infralab/benchmarks/regbench/laws/D2.yaml +26 -0
  41. infralab-1.0.0/infralab/benchmarks/regbench/laws/D3.yaml +27 -0
  42. infralab-1.0.0/infralab/benchmarks/regbench/laws/G0.yaml +33 -0
  43. infralab-1.0.0/infralab/benchmarks/regbench/laws/G1.yaml +36 -0
  44. infralab-1.0.0/infralab/benchmarks/regbench/laws/G2.yaml +37 -0
  45. infralab-1.0.0/infralab/benchmarks/regbench/laws/G3.yaml +40 -0
  46. infralab-1.0.0/infralab/benchmarks/regbench/laws/J0.yaml +33 -0
  47. infralab-1.0.0/infralab/benchmarks/regbench/laws/J1.yaml +36 -0
  48. infralab-1.0.0/infralab/benchmarks/regbench/laws/K0.yaml +24 -0
  49. infralab-1.0.0/infralab/benchmarks/regbench/laws/N24.yaml +24 -0
  50. infralab-1.0.0/infralab/benchmarks/regbench/scenarios/aged-147.json +1 -0
  51. infralab-1.0.0/infralab/benchmarks/regbench/scenarios/aged-30.json +1 -0
  52. infralab-1.0.0/infralab/benchmarks/scenarios.json +1254 -0
  53. infralab-1.0.0/infralab/calibration_profiles.py +287 -0
  54. infralab-1.0.0/infralab/cli.py +73 -0
  55. infralab-1.0.0/infralab/codes/__init__.py +14 -0
  56. infralab-1.0.0/infralab/codes/base.py +45 -0
  57. infralab-1.0.0/infralab/codes/frameworks.py +81 -0
  58. infralab-1.0.0/infralab/codes/registry.py +24 -0
  59. infralab-1.0.0/infralab/core/__init__.py +0 -0
  60. infralab-1.0.0/infralab/core/degradation.py +57 -0
  61. infralab-1.0.0/infralab/core/environment.py +3614 -0
  62. infralab-1.0.0/infralab/core/hazards/__init__.py +0 -0
  63. infralab-1.0.0/infralab/core/hazards/earthquake.py +371 -0
  64. infralab-1.0.0/infralab/core/hazards/progressive_events.py +1071 -0
  65. infralab-1.0.0/infralab/core/hazards/regional_events.py +399 -0
  66. infralab-1.0.0/infralab/core/hazards/seismic_hazard.py +72 -0
  67. infralab-1.0.0/infralab/core/hazards/sudden_damage.py +633 -0
  68. infralab-1.0.0/infralab/core/inspection.py +54 -0
  69. infralab-1.0.0/infralab/core/reliability.py +687 -0
  70. infralab-1.0.0/infralab/data/__init__.py +0 -0
  71. infralab-1.0.0/infralab/data/excel_import.py +232 -0
  72. infralab-1.0.0/infralab/data/nbi.py +265 -0
  73. infralab-1.0.0/infralab/data/nbi_calibration.py +361 -0
  74. infralab-1.0.0/infralab/lab/__init__.py +3 -0
  75. infralab-1.0.0/infralab/lab/__main__.py +3 -0
  76. infralab-1.0.0/infralab/lab/arena.py +477 -0
  77. infralab-1.0.0/infralab/lab/build.py +66 -0
  78. infralab-1.0.0/infralab/lab/cli.py +79 -0
  79. infralab-1.0.0/infralab/lab/hydraulic_rate.py +90 -0
  80. infralab-1.0.0/infralab/lab/io.py +58 -0
  81. infralab-1.0.0/infralab/lab/leak_check.py +1047 -0
  82. infralab-1.0.0/infralab/lab/provenance.py +40 -0
  83. infralab-1.0.0/infralab/lab/runner.py +192 -0
  84. infralab-1.0.0/infralab/lab/spec.py +152 -0
  85. infralab-1.0.0/infralab/llm/__init__.py +2 -0
  86. infralab-1.0.0/infralab/llm/anthropic_backend.py +53 -0
  87. infralab-1.0.0/infralab/llm/openai_backend.py +58 -0
  88. infralab-1.0.0/infralab/llm/player.py +143 -0
  89. infralab-1.0.0/infralab/mcp_server.py +132 -0
  90. infralab-1.0.0/infralab/persistence.py +619 -0
  91. infralab-1.0.0/infralab/plugins/__init__.py +19 -0
  92. infralab-1.0.0/infralab/plugins/loader.py +127 -0
  93. infralab-1.0.0/infralab/plugins/registry.py +116 -0
  94. infralab-1.0.0/infralab/plugins/templates.py +88 -0
  95. infralab-1.0.0/infralab/regimes/__init__.py +16 -0
  96. infralab-1.0.0/infralab/regimes/base.py +350 -0
  97. infralab-1.0.0/infralab/regimes/builtin/hyp_risk_based_existing.yaml +21 -0
  98. infralab-1.0.0/infralab/regimes/builtin/it_lg2020_forced.yaml +19 -0
  99. infralab-1.0.0/infralab/regimes/builtin/it_lg2020_sanctioned.yaml +25 -0
  100. infralab-1.0.0/infralab/regimes/builtin/it_lg2020_signal.yaml +20 -0
  101. infralab-1.0.0/infralab/regimes/registry.py +66 -0
  102. infralab-1.0.0/infralab/settings/__init__.py +14 -0
  103. infralab-1.0.0/infralab/settings/config.py +856 -0
  104. infralab-1.0.0/infralab/study/__init__.py +12 -0
  105. infralab-1.0.0/infralab/study/battery.py +67 -0
  106. infralab-1.0.0/infralab/study/outcomes.py +72 -0
  107. infralab-1.0.0/infralab/study/questionnaire.py +122 -0
  108. infralab-1.0.0/infralab/study/stats.py +178 -0
  109. infralab-1.0.0/infralab/toolkit.py +155 -0
  110. infralab-1.0.0/infralab/ui/__init__.py +0 -0
  111. infralab-1.0.0/infralab/ui/auth.py +151 -0
  112. infralab-1.0.0/infralab/ui/gis_bim.py +709 -0
  113. infralab-1.0.0/infralab/ui/pages_legacy.py +2501 -0
  114. infralab-1.0.0/infralab/views.py +120 -0
  115. infralab-1.0.0/infralab.egg-info/PKG-INFO +240 -0
  116. infralab-1.0.0/infralab.egg-info/SOURCES.txt +166 -0
  117. infralab-1.0.0/infralab.egg-info/dependency_links.txt +1 -0
  118. infralab-1.0.0/infralab.egg-info/entry_points.txt +3 -0
  119. infralab-1.0.0/infralab.egg-info/requires.txt +35 -0
  120. infralab-1.0.0/infralab.egg-info/top_level.txt +1 -0
  121. infralab-1.0.0/pyproject.toml +47 -0
  122. infralab-1.0.0/setup.cfg +4 -0
  123. infralab-1.0.0/tests/test_agentic.py +166 -0
  124. infralab-1.0.0/tests/test_assessment_confidence.py +58 -0
  125. infralab-1.0.0/tests/test_assessment_decomposition.py +71 -0
  126. infralab-1.0.0/tests/test_asset_registry.py +119 -0
  127. infralab-1.0.0/tests/test_backend_api.py +330 -0
  128. infralab-1.0.0/tests/test_battery.py +208 -0
  129. infralab-1.0.0/tests/test_codes.py +53 -0
  130. infralab-1.0.0/tests/test_config_validation.py +72 -0
  131. infralab-1.0.0/tests/test_copilot.py +155 -0
  132. infralab-1.0.0/tests/test_custom_scenarios.py +109 -0
  133. infralab-1.0.0/tests/test_degradation_weibull.py +107 -0
  134. infralab-1.0.0/tests/test_detour_model.py +41 -0
  135. infralab-1.0.0/tests/test_env_clone_state.py +145 -0
  136. infralab-1.0.0/tests/test_env_snapshot.py +108 -0
  137. infralab-1.0.0/tests/test_excel_import.py +164 -0
  138. infralab-1.0.0/tests/test_experiment_platform.py +188 -0
  139. infralab-1.0.0/tests/test_floor_oracle.py +264 -0
  140. infralab-1.0.0/tests/test_floor_response.py +67 -0
  141. infralab-1.0.0/tests/test_hardening.py +107 -0
  142. infralab-1.0.0/tests/test_huggingface_and_arena.py +128 -0
  143. infralab-1.0.0/tests/test_lab_runner.py +201 -0
  144. infralab-1.0.0/tests/test_leak_sudden.py +237 -0
  145. infralab-1.0.0/tests/test_library_api.py +167 -0
  146. infralab-1.0.0/tests/test_linee_guida.py +148 -0
  147. infralab-1.0.0/tests/test_nbi_and_sustainability.py +113 -0
  148. infralab-1.0.0/tests/test_nbi_calibration.py +137 -0
  149. infralab-1.0.0/tests/test_new_asset_types.py +179 -0
  150. infralab-1.0.0/tests/test_obligation_enforcement.py +79 -0
  151. infralab-1.0.0/tests/test_persistence.py +126 -0
  152. infralab-1.0.0/tests/test_physics_extensions.py +169 -0
  153. infralab-1.0.0/tests/test_planner_belief.py +292 -0
  154. infralab-1.0.0/tests/test_plugins.py +239 -0
  155. infralab-1.0.0/tests/test_policy_sandbox.py +464 -0
  156. infralab-1.0.0/tests/test_profile_appoggi.py +15 -0
  157. infralab-1.0.0/tests/test_progressive_events.py +827 -0
  158. infralab-1.0.0/tests/test_regimes.py +446 -0
  159. infralab-1.0.0/tests/test_regional_events.py +277 -0
  160. infralab-1.0.0/tests/test_reliability_fosm.py +196 -0
  161. infralab-1.0.0/tests/test_retaining_walls.py +81 -0
  162. infralab-1.0.0/tests/test_revenue_source.py +29 -0
  163. infralab-1.0.0/tests/test_strict_information.py +251 -0
  164. infralab-1.0.0/tests/test_study.py +157 -0
  165. infralab-1.0.0/tests/test_sudden_damage.py +374 -0
  166. infralab-1.0.0/tests/test_truncated_ln.py +162 -0
  167. infralab-1.0.0/tests/test_typical_owner_options.py +169 -0
  168. infralab-1.0.0/tests/test_validation_options.py +161 -0
infralab-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Giulio Mariniello
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.4
2
+ Name: infralab
3
+ Version: 1.0.0
4
+ Summary: Open library for bridge management: exchangeable stocks, degradation laws, regulations and players, compared on common random numbers.
5
+ Author: Giulio Mariniello
6
+ License: MIT
7
+ Keywords: bridge management,infrastructure,benchmark,regulation,reinforcement learning,MCP,language-model agents
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Topic :: Scientific/Engineering
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: numpy>=1.26
16
+ Requires-Dist: scipy>=1.14
17
+ Requires-Dist: pandas>=2.2
18
+ Requires-Dist: PyYAML>=6.0
19
+ Requires-Dist: filelock>=3.13
20
+ Provides-Extra: mcp
21
+ Requires-Dist: mcp>=1.2; extra == "mcp"
22
+ Provides-Extra: anthropic
23
+ Requires-Dist: anthropic>=0.60; extra == "anthropic"
24
+ Provides-Extra: openai
25
+ Requires-Dist: openai>=1.40; extra == "openai"
26
+ Provides-Extra: llm
27
+ Requires-Dist: anthropic>=0.60; extra == "llm"
28
+ Requires-Dist: openai>=1.40; extra == "llm"
29
+ Requires-Dist: google-generativeai>=0.8; extra == "llm"
30
+ Requires-Dist: huggingface_hub>=0.28; extra == "llm"
31
+ Provides-Extra: web
32
+ Requires-Dist: fastapi>=0.110; extra == "web"
33
+ Requires-Dist: uvicorn[standard]>=0.27; extra == "web"
34
+ Requires-Dist: streamlit>=1.40; extra == "web"
35
+ Requires-Dist: plotly>=5; extra == "web"
36
+ Requires-Dist: matplotlib>=3.8; extra == "web"
37
+ Requires-Dist: seaborn>=0.13; extra == "web"
38
+ Requires-Dist: openpyxl>=3.1; extra == "web"
39
+ Requires-Dist: XlsxWriter>=3.2; extra == "web"
40
+ Requires-Dist: pyarrow>=15; extra == "web"
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=8; extra == "dev"
43
+ Requires-Dist: pytest-timeout>=2.3; extra == "dev"
44
+ Dynamic: license-file
45
+
46
+ # InfraLab
47
+
48
+ InfraLab is an open Python library in which the management of a bridge stock is a game played quarter by
49
+ quarter under partial information, a budget and a regulation. The four choices behind a bridge-management
50
+ study are exchangeable pieces:
51
+
52
+ | Piece | Form | Argument |
53
+ |---|---|---|
54
+ | Stock | a file of bridges with their inventory attributes (an importer reads National Bridge Inventory files) | `scenario` |
55
+ | Degradation law | a Python function `degrade(ctx)` that returns the new condition of a component | `degradation` |
56
+ | Regulation | a YAML file of duties: trigger, action, deadline, enforcement | `law` |
57
+ | Player | a function from the observation to a plan | `policy` |
58
+
59
+ Every random draw is keyed to seed, process, bridge and quarter, so two settings meet the same events and are
60
+ compared seed by seed in one call. A game is replayed exactly from its settings, its seed and the library version.
61
+
62
+ ## Install
63
+
64
+ ```bash
65
+ pip install infralab # core: numpy, scipy, pandas, PyYAML
66
+ pip install "infralab[mcp]" # MCP server
67
+ pip install "infralab[anthropic]" # Claude players
68
+ pip install "infralab[openai]" # OpenAI and compatible endpoints
69
+ pip install "infralab[web]" # web platform (board for human players)
70
+ ```
71
+
72
+ From the source: `pip install .` in the root of the repository.
73
+
74
+ ## Quick start
75
+
76
+ ```python
77
+ import infralab
78
+
79
+ env = infralab.make("regbench/aged-30", law="regbench/D1", seed=0, horizon_years=10)
80
+ obs, info = env.reset()
81
+ while not env.done:
82
+ plan = {} # {asset_id: action_name}; empty = no action
83
+ obs, reward, terminated, truncated, info = env.step(plan)
84
+ print(env.summary())
85
+ ```
86
+
87
+ `env.compact_observation()` gives what the owner knows in a few hundred characters per asset;
88
+ `env.feasible_actions(asset_id)` the actions available with their cost; `env.rules()` the rules of the run;
89
+ `info["report"]` what happened in the quarter (accepted and dropped requests, inspections forced by the
90
+ regulation, interventions of the ethical floor, collapses).
91
+
92
+ ## Outcomes
93
+
94
+ `run`, `Env.summary` and `compare_laws` report outcomes computed on the true state, never shown to a player:
95
+
96
+ | Key | Meaning |
97
+ |---|---|
98
+ | `S1` | mean over bridges of the lowest true annual reliability index reached during the game |
99
+ | `expected_collapses` | expected strength collapses: sum over bridges and quarters of the quarterly failure probability of the true state |
100
+ | `expected_hydraulic_collapses` | expected collapses from progressive hydraulic defects that matured unarrested |
101
+ | `bridges_below_limit`, `bridge_years_below_limit` | bridges, and bridge-years, with a true annual index below 3.02 |
102
+ | `collapses` | collapses drawn in the game |
103
+ | `cash_spend`, `user_cost`, `closed_share` | what the owner paid, the extra travel cost of road users, the closed share of bridge-years |
104
+
105
+ ## Change one piece
106
+
107
+ ### The player
108
+
109
+ ```python
110
+ def inspect_then_fix(observation):
111
+ bridges = observation["my_bridges"]
112
+ plan = {b["id"]: "inspect" for b in bridges if b["inspection_overdue"]}
113
+ free = [b for b in bridges if b["id"] not in plan and b["active_maintenance"] is None]
114
+ if free:
115
+ worst = min(free, key=lambda b: b["min_observed_condition"])
116
+ if worst["min_observed_condition"] < 0.3:
117
+ plan[worst["id"]] = "restore"
118
+ return plan
119
+
120
+ infralab.run(inspect_then_fix, "regbench/aged-30", law="regbench/D1", config="paper-short")
121
+ ```
122
+
123
+ `infralab.policies()` lists the supplied players (`passive`, `worst_first`, `typical`, `diligent`, `heuristic`,
124
+ `monte_carlo`, `q_learning`, `genetic`, `memetic`); `basic:<rule>` reaches the other fixed rules
125
+ (`infralab.basic_policies()`); `infralab.actions()` lists the action names accepted in a plan.
126
+
127
+ ### The regulation
128
+
129
+ ```python
130
+ r = infralab.compare_laws("regbench/D0", "regbench/D1", policy="passive",
131
+ scenario="regbench/aged-147", config="paper",
132
+ params={"ethical_floor_enabled": True}, n_seeds=10, workers=5)
133
+ r["difference_b_minus_a"]["S1"] # mean paired difference, lower and upper bound (95 percent)
134
+ ```
135
+
136
+ The two regulations are played on the same stock, player and random draws. A regulation is a YAML file:
137
+ pass a path instead of an identifier to test your own (`examples/04_inspection_interval.py` writes nine).
138
+
139
+ ### The degradation law
140
+
141
+ ```python
142
+ # markov.py: a Markov chain on inventory ratings
143
+ P = {9: 0.1483, 8: 0.0672, 7: 0.0334, 6: 0.0321, 5: 0.0355, 4: 0.0421, 3: 0.0428, 2: 0.0852, 1: 0.0193}
144
+ D = {9: 1.12, 8: 1.06, 7: 1.08, 6: 1.14, 5: 1.27, 4: 1.35, 3: 2.22, 2: 1.80, 1: 1.00}
145
+
146
+ def degrade(ctx):
147
+ c = ctx["condition"]
148
+ if ctx["age_increment_years"] <= 0:
149
+ return c
150
+ r = min(9, max(1, round(9 * c)))
151
+ if ctx["rand"]() < 1 - (1 - P[r]) ** 0.25:
152
+ c -= D[r] / 9
153
+ return c
154
+ ```
155
+
156
+ ```python
157
+ infralab.compare_laws("regbench/D0", "regbench/D1", policy="passive", scenario="regbench/aged-147",
158
+ config="paper", degradation="markov.py", n_seeds=10, workers=5)
159
+ ```
160
+
161
+ `infralab.degradation_laws()` lists the laws supplied: `weibull` (default), `gamma`, `regbench/power`,
162
+ `regbench/markov-nbi`.
163
+
164
+ ### The stock
165
+
166
+ `infralab.data.nbi` reads a National Bridge Inventory file and builds a scenario
167
+ (`python -m infralab.data.nbi --help`); `infralab.load_scenario(path)` reads any scenario file.
168
+
169
+ ## The benchmark of the paper
170
+
171
+ | Item | Identifiers |
172
+ |---|---|
173
+ | Stocks | `regbench/aged-147`, `regbench/aged-30`: aged samples of the 2023 Delaware inventory |
174
+ | Regulations | `regbench/D0` no obligation; `regbench/N24` routine inspection every 24 months, 12 for rating 3 or lower; `regbench/D1` intervals graded by attention class; `D2` post-event; `D3` both; `K0` flat biennial; `G0`–`G3`, `J0`–`J1` with a practice rule |
175
+ | Configurations | `paper` (147 bridges, 50 years; default), `paper-short` (30 bridges, 5 years), `paper-less-optimistic` (sensitivity of the reliability calibration); earlier configurations `first`, `second`, `third`, `calibrated`, `calibrated-short` |
176
+
177
+ `examples/` holds the examples of the paper: `01_change_the_regulation.py`, `02_change_the_degradation_law.py`,
178
+ `03_change_the_player.py` and `04_inspection_interval.py`. Called as in these scripts, the library reproduces
179
+ the stored games of the paper to the last digit (`tests/paper_reproduction_check.py`).
180
+
181
+ The parameter values are plausible and, apart from the matching of the reliability index to load ratings, not
182
+ calibrated. The reliability index serves paired comparisons between settings, not levels; results carry to a
183
+ real stock only through a stock and a degradation law fitted to it.
184
+
185
+ ## Interfaces
186
+
187
+ **Command line**
188
+
189
+ ```bash
190
+ infralab list
191
+ infralab run worst_first --law regbench/D1 --years 10
192
+ infralab compare regbench/D0 regbench/D1 --policy passive --seeds 10 --years 10
193
+ ```
194
+
195
+ **MCP server.** Any Model Context Protocol client can manage a stock through tools.
196
+
197
+ ```bash
198
+ infralab-mcp # stdio
199
+ infralab-mcp --transport sse # HTTP
200
+ ```
201
+
202
+ ```json
203
+ {"mcpServers": {"infralab": {"command": "infralab-mcp", "env": {"INFRALAB_RUN_DIR": "runs"}}}}
204
+ ```
205
+
206
+ Tools: `list_scenarios`, `list_laws`, `describe_law`, `list_policies`, `new_run`, `rules`, `observe`,
207
+ `asset_actions`, `action_catalogue`, `step`, `score`, `run_policy`, `compare_laws`. The server holds no key and
208
+ calls no model. During a run it returns only what the owner may know; the outcomes computed on the true state
209
+ are released when the run is over.
210
+
211
+ **Language-model players.**
212
+
213
+ ```python
214
+ from infralab.llm import play
215
+
216
+ play("anthropic:claude-opus-5-5", "regbench/aged-30", law="regbench/D1", seed=0)
217
+ play("openai:gpt-4.1-mini", "regbench/aged-30", law="regbench/D1", seed=0)
218
+ play("openai:llama3.1", base_url="http://localhost:11434/v1") # any endpoint with the same protocol
219
+ ```
220
+
221
+ Keys are read from the environment (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or the variable named with `api_key_env`).
222
+
223
+ **Web platform.** A board for human players (FastAPI backend and React frontend, see `docs/WEBAPP.md`), and the
224
+ Streamlit interface `app.py`.
225
+
226
+ ## Tests
227
+
228
+ ```bash
229
+ pip install ".[dev]"
230
+ pytest
231
+ ```
232
+
233
+ ## Citation
234
+
235
+ See `CITATION.cff`. Model documentation: `docs/MODEL_REPORT.md`; numerical changes to the engine:
236
+ `docs/CHANGELOG_MODEL.md`.
237
+
238
+ ## License
239
+
240
+ MIT, see `LICENSE`.
@@ -0,0 +1,195 @@
1
+ # InfraLab
2
+
3
+ InfraLab is an open Python library in which the management of a bridge stock is a game played quarter by
4
+ quarter under partial information, a budget and a regulation. The four choices behind a bridge-management
5
+ study are exchangeable pieces:
6
+
7
+ | Piece | Form | Argument |
8
+ |---|---|---|
9
+ | Stock | a file of bridges with their inventory attributes (an importer reads National Bridge Inventory files) | `scenario` |
10
+ | Degradation law | a Python function `degrade(ctx)` that returns the new condition of a component | `degradation` |
11
+ | Regulation | a YAML file of duties: trigger, action, deadline, enforcement | `law` |
12
+ | Player | a function from the observation to a plan | `policy` |
13
+
14
+ Every random draw is keyed to seed, process, bridge and quarter, so two settings meet the same events and are
15
+ compared seed by seed in one call. A game is replayed exactly from its settings, its seed and the library version.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install infralab # core: numpy, scipy, pandas, PyYAML
21
+ pip install "infralab[mcp]" # MCP server
22
+ pip install "infralab[anthropic]" # Claude players
23
+ pip install "infralab[openai]" # OpenAI and compatible endpoints
24
+ pip install "infralab[web]" # web platform (board for human players)
25
+ ```
26
+
27
+ From the source: `pip install .` in the root of the repository.
28
+
29
+ ## Quick start
30
+
31
+ ```python
32
+ import infralab
33
+
34
+ env = infralab.make("regbench/aged-30", law="regbench/D1", seed=0, horizon_years=10)
35
+ obs, info = env.reset()
36
+ while not env.done:
37
+ plan = {} # {asset_id: action_name}; empty = no action
38
+ obs, reward, terminated, truncated, info = env.step(plan)
39
+ print(env.summary())
40
+ ```
41
+
42
+ `env.compact_observation()` gives what the owner knows in a few hundred characters per asset;
43
+ `env.feasible_actions(asset_id)` the actions available with their cost; `env.rules()` the rules of the run;
44
+ `info["report"]` what happened in the quarter (accepted and dropped requests, inspections forced by the
45
+ regulation, interventions of the ethical floor, collapses).
46
+
47
+ ## Outcomes
48
+
49
+ `run`, `Env.summary` and `compare_laws` report outcomes computed on the true state, never shown to a player:
50
+
51
+ | Key | Meaning |
52
+ |---|---|
53
+ | `S1` | mean over bridges of the lowest true annual reliability index reached during the game |
54
+ | `expected_collapses` | expected strength collapses: sum over bridges and quarters of the quarterly failure probability of the true state |
55
+ | `expected_hydraulic_collapses` | expected collapses from progressive hydraulic defects that matured unarrested |
56
+ | `bridges_below_limit`, `bridge_years_below_limit` | bridges, and bridge-years, with a true annual index below 3.02 |
57
+ | `collapses` | collapses drawn in the game |
58
+ | `cash_spend`, `user_cost`, `closed_share` | what the owner paid, the extra travel cost of road users, the closed share of bridge-years |
59
+
60
+ ## Change one piece
61
+
62
+ ### The player
63
+
64
+ ```python
65
+ def inspect_then_fix(observation):
66
+ bridges = observation["my_bridges"]
67
+ plan = {b["id"]: "inspect" for b in bridges if b["inspection_overdue"]}
68
+ free = [b for b in bridges if b["id"] not in plan and b["active_maintenance"] is None]
69
+ if free:
70
+ worst = min(free, key=lambda b: b["min_observed_condition"])
71
+ if worst["min_observed_condition"] < 0.3:
72
+ plan[worst["id"]] = "restore"
73
+ return plan
74
+
75
+ infralab.run(inspect_then_fix, "regbench/aged-30", law="regbench/D1", config="paper-short")
76
+ ```
77
+
78
+ `infralab.policies()` lists the supplied players (`passive`, `worst_first`, `typical`, `diligent`, `heuristic`,
79
+ `monte_carlo`, `q_learning`, `genetic`, `memetic`); `basic:<rule>` reaches the other fixed rules
80
+ (`infralab.basic_policies()`); `infralab.actions()` lists the action names accepted in a plan.
81
+
82
+ ### The regulation
83
+
84
+ ```python
85
+ r = infralab.compare_laws("regbench/D0", "regbench/D1", policy="passive",
86
+ scenario="regbench/aged-147", config="paper",
87
+ params={"ethical_floor_enabled": True}, n_seeds=10, workers=5)
88
+ r["difference_b_minus_a"]["S1"] # mean paired difference, lower and upper bound (95 percent)
89
+ ```
90
+
91
+ The two regulations are played on the same stock, player and random draws. A regulation is a YAML file:
92
+ pass a path instead of an identifier to test your own (`examples/04_inspection_interval.py` writes nine).
93
+
94
+ ### The degradation law
95
+
96
+ ```python
97
+ # markov.py: a Markov chain on inventory ratings
98
+ P = {9: 0.1483, 8: 0.0672, 7: 0.0334, 6: 0.0321, 5: 0.0355, 4: 0.0421, 3: 0.0428, 2: 0.0852, 1: 0.0193}
99
+ D = {9: 1.12, 8: 1.06, 7: 1.08, 6: 1.14, 5: 1.27, 4: 1.35, 3: 2.22, 2: 1.80, 1: 1.00}
100
+
101
+ def degrade(ctx):
102
+ c = ctx["condition"]
103
+ if ctx["age_increment_years"] <= 0:
104
+ return c
105
+ r = min(9, max(1, round(9 * c)))
106
+ if ctx["rand"]() < 1 - (1 - P[r]) ** 0.25:
107
+ c -= D[r] / 9
108
+ return c
109
+ ```
110
+
111
+ ```python
112
+ infralab.compare_laws("regbench/D0", "regbench/D1", policy="passive", scenario="regbench/aged-147",
113
+ config="paper", degradation="markov.py", n_seeds=10, workers=5)
114
+ ```
115
+
116
+ `infralab.degradation_laws()` lists the laws supplied: `weibull` (default), `gamma`, `regbench/power`,
117
+ `regbench/markov-nbi`.
118
+
119
+ ### The stock
120
+
121
+ `infralab.data.nbi` reads a National Bridge Inventory file and builds a scenario
122
+ (`python -m infralab.data.nbi --help`); `infralab.load_scenario(path)` reads any scenario file.
123
+
124
+ ## The benchmark of the paper
125
+
126
+ | Item | Identifiers |
127
+ |---|---|
128
+ | Stocks | `regbench/aged-147`, `regbench/aged-30`: aged samples of the 2023 Delaware inventory |
129
+ | Regulations | `regbench/D0` no obligation; `regbench/N24` routine inspection every 24 months, 12 for rating 3 or lower; `regbench/D1` intervals graded by attention class; `D2` post-event; `D3` both; `K0` flat biennial; `G0`–`G3`, `J0`–`J1` with a practice rule |
130
+ | Configurations | `paper` (147 bridges, 50 years; default), `paper-short` (30 bridges, 5 years), `paper-less-optimistic` (sensitivity of the reliability calibration); earlier configurations `first`, `second`, `third`, `calibrated`, `calibrated-short` |
131
+
132
+ `examples/` holds the examples of the paper: `01_change_the_regulation.py`, `02_change_the_degradation_law.py`,
133
+ `03_change_the_player.py` and `04_inspection_interval.py`. Called as in these scripts, the library reproduces
134
+ the stored games of the paper to the last digit (`tests/paper_reproduction_check.py`).
135
+
136
+ The parameter values are plausible and, apart from the matching of the reliability index to load ratings, not
137
+ calibrated. The reliability index serves paired comparisons between settings, not levels; results carry to a
138
+ real stock only through a stock and a degradation law fitted to it.
139
+
140
+ ## Interfaces
141
+
142
+ **Command line**
143
+
144
+ ```bash
145
+ infralab list
146
+ infralab run worst_first --law regbench/D1 --years 10
147
+ infralab compare regbench/D0 regbench/D1 --policy passive --seeds 10 --years 10
148
+ ```
149
+
150
+ **MCP server.** Any Model Context Protocol client can manage a stock through tools.
151
+
152
+ ```bash
153
+ infralab-mcp # stdio
154
+ infralab-mcp --transport sse # HTTP
155
+ ```
156
+
157
+ ```json
158
+ {"mcpServers": {"infralab": {"command": "infralab-mcp", "env": {"INFRALAB_RUN_DIR": "runs"}}}}
159
+ ```
160
+
161
+ Tools: `list_scenarios`, `list_laws`, `describe_law`, `list_policies`, `new_run`, `rules`, `observe`,
162
+ `asset_actions`, `action_catalogue`, `step`, `score`, `run_policy`, `compare_laws`. The server holds no key and
163
+ calls no model. During a run it returns only what the owner may know; the outcomes computed on the true state
164
+ are released when the run is over.
165
+
166
+ **Language-model players.**
167
+
168
+ ```python
169
+ from infralab.llm import play
170
+
171
+ play("anthropic:claude-opus-5-5", "regbench/aged-30", law="regbench/D1", seed=0)
172
+ play("openai:gpt-4.1-mini", "regbench/aged-30", law="regbench/D1", seed=0)
173
+ play("openai:llama3.1", base_url="http://localhost:11434/v1") # any endpoint with the same protocol
174
+ ```
175
+
176
+ Keys are read from the environment (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or the variable named with `api_key_env`).
177
+
178
+ **Web platform.** A board for human players (FastAPI backend and React frontend, see `docs/WEBAPP.md`), and the
179
+ Streamlit interface `app.py`.
180
+
181
+ ## Tests
182
+
183
+ ```bash
184
+ pip install ".[dev]"
185
+ pytest
186
+ ```
187
+
188
+ ## Citation
189
+
190
+ See `CITATION.cff`. Model documentation: `docs/MODEL_REPORT.md`; numerical changes to the engine:
191
+ `docs/CHANGELOG_MODEL.md`.
192
+
193
+ ## License
194
+
195
+ MIT, see `LICENSE`.
@@ -0,0 +1,24 @@
1
+ """InfraLab — laboratorio virtuale per la gestione di asset infrastrutturali.
2
+
3
+ Pacchetto nato dal refactoring di BridgeManager: motore di affidabilità
4
+ FOSM/Bayesiano generico, simulatore a turni con degrado stocastico, agenti
5
+ decisionali (euristici, ottimizzazione, LLM) e UI Streamlit per esperimenti
6
+ con soggetti umani e campagne computazionali.
7
+ """
8
+
9
+ __version__ = "1.0.0"
10
+
11
+ # API pubblica della libreria (infralab.api): import pigri, cosi' `import infralab` resta leggero.
12
+ _API = ("make", "run", "actions", "compare_laws", "scenarios", "load_scenario", "laws", "describe_law", "configurations",
13
+ "configuration", "degradation_laws", "resolve_degradation", "policies", "basic_policies", "policy_config", "child_seeds", "Env")
14
+
15
+
16
+ def __getattr__(name):
17
+ if name in _API:
18
+ from infralab import api
19
+ return getattr(api, name)
20
+ raise AttributeError(f"module 'infralab' has no attribute {name!r}")
21
+
22
+
23
+ def __dir__():
24
+ return sorted(list(globals()) + list(_API))
@@ -0,0 +1 @@
1
+ from infralab.agents.legacy import * # noqa: F401,F403
@@ -0,0 +1,61 @@
1
+ """Adapter tra gli agenti legacy (AgentInterface.decide(env)) e il protocollo gym-like.
2
+
3
+ Gli agenti storici decidono leggendo l'intero BridgeEnv; il protocollo passa
4
+ l'osservazione e mette l'ambiente in info["env"] al reset. L'adapter conserva
5
+ quel riferimento e delega a decide().
6
+
7
+ Con policy_sandbox=True l'adapter (lato harness) resta l'unico a tenere il motore:
8
+ alla policy passa, a ogni decisione, un PlanningHandle nuovo (infralab.agents.sandbox).
9
+ Un accesso vietato (SandboxViolation) non ferma la partita: la decisione del
10
+ trimestre diventa "nessuna azione" e l'accesso e' registrato in `sandbox_violations`.
11
+ """
12
+ from typing import Any, Dict, List
13
+
14
+ from infralab.agents.legacy import AgentInterface, agent_factory
15
+ from infralab.agents.sandbox import PlanningHandle, SandboxViolation, sandbox_enabled
16
+
17
+
18
+ class LegacyAgentAdapter:
19
+ """Avvolge un AgentInterface legacy nel protocollo Agent (reset/act)."""
20
+
21
+ def __init__(self, legacy_agent: AgentInterface, **decide_kwargs: Any):
22
+ self.legacy_agent = legacy_agent
23
+ self.decide_kwargs = decide_kwargs
24
+ self._env = None
25
+ self.last_rationale: Any = None
26
+ # Diagnostica del sandbox: [{"quarter", "access"}] (vuota senza sandbox).
27
+ self.sandbox_violations: List[Dict[str, Any]] = []
28
+
29
+ @property
30
+ def name(self) -> str:
31
+ return self.legacy_agent.name
32
+
33
+ def reset(self, observation: Dict[str, Any], info: Dict[str, Any]) -> None:
34
+ self._env = info.get("env")
35
+ if self._env is None:
36
+ raise ValueError("LegacyAgentAdapter richiede info['env'] al reset.")
37
+
38
+ def act(self, observation: Dict[str, Any]) -> Dict[str, Any]:
39
+ if self._env is None:
40
+ raise RuntimeError("Chiamare reset() prima di act().")
41
+ if not sandbox_enabled(self._env):
42
+ plan, rationale = self.legacy_agent.decide(self._env, **self.decide_kwargs)
43
+ self.last_rationale = rationale
44
+ return plan or {}
45
+ handle = PlanningHandle.build(self._env, self.name)
46
+ try:
47
+ plan, rationale = self.legacy_agent.decide(handle, **self.decide_kwargs)
48
+ except SandboxViolation as e:
49
+ plan, rationale = {}, ("sandbox_violation", str(e))
50
+ for access in handle.violations:
51
+ self.sandbox_violations.append({"quarter": handle.quarter, "access": access})
52
+ self.last_rationale = rationale
53
+ return plan if isinstance(plan, dict) else {}
54
+
55
+
56
+ def make_agent(name: str, config: Dict[str, Any], **decide_kwargs: Any) -> LegacyAgentAdapter:
57
+ """Costruisce un agente protocol-compatibile dalla config legacy.
58
+
59
+ config: {"type": "Euristico" | "LLM" | "GA Orizzonte Variabile" | ...}
60
+ """
61
+ return LegacyAgentAdapter(agent_factory(name, config), **decide_kwargs)