api581-engine 0.1.4__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 (139) hide show
  1. api581/__init__.py +80 -0
  2. api581/_amine_tables.py +105 -0
  3. api581/_h2sh2_tables.py +161 -0
  4. api581/_slabx_worker.py +174 -0
  5. api581/_sulfidic_tables.py +517 -0
  6. api581/_sulfuric_tables.py +328 -0
  7. api581/acid_sour_water.py +238 -0
  8. api581/acscc_screening.py +88 -0
  9. api581/alkaline_sour_water.py +267 -0
  10. api581/amine_corrosion.py +277 -0
  11. api581/amine_screening.py +103 -0
  12. api581/api/__init__.py +4 -0
  13. api581/api/api520.py +88 -0
  14. api581/api/bundle.py +121 -0
  15. api581/api/consequence.py +102 -0
  16. api581/api/damage.py +1831 -0
  17. api581/api/dispersion.py +121 -0
  18. api581/api/effects.py +318 -0
  19. api581/api/events.py +96 -0
  20. api581/api/financial.py +115 -0
  21. api581/api/frequency.py +40 -0
  22. api581/api/inspection.py +96 -0
  23. api581/api/level1.py +238 -0
  24. api581/api/level2.py +74 -0
  25. api581/api/planning.py +17 -0
  26. api581/api/prd.py +190 -0
  27. api581/api/properties.py +130 -0
  28. api581/api/release.py +252 -0
  29. api581/api/reliability.py +40 -0
  30. api581/api/risk.py +71 -0
  31. api581/api/tank.py +490 -0
  32. api581/api520.py +205 -0
  33. api581/area_contribution.py +72 -0
  34. api581/blast.py +232 -0
  35. api581/brittle.py +205 -0
  36. api581/bundle.py +300 -0
  37. api581/catalog.py +442 -0
  38. api581/caustic_screening.py +103 -0
  39. api581/clad_thinning.py +146 -0
  40. api581/clscc_screening.py +55 -0
  41. api581/co2_corrosion.py +313 -0
  42. api581/co2_screening.py +205 -0
  43. api581/consequences.py +250 -0
  44. api581/contracts.py +1038 -0
  45. api581/cooling_selection.py +204 -0
  46. api581/cooling_water.py +305 -0
  47. api581/core.py +126 -0
  48. api581/corrosion_rate.py +38 -0
  49. api581/cost_tables.py +175 -0
  50. api581/cracking.py +74 -0
  51. api581/data/coverage.json +21361 -0
  52. api581/data/digital-twin.schema.json +538 -0
  53. api581/data/direct-test-queue.json +7 -0
  54. api581/data/native-engine-evidence.json +44 -0
  55. api581/data/python-api.schema.json +92706 -0
  56. api581/data/reference-fixture-evidence.json +2956 -0
  57. api581/data/scope-boundaries.schema.json +58 -0
  58. api581/data/software-test-evidence.json +4278 -0
  59. api581/data/standard-metadata.json +1328 -0
  60. api581/data/standard.schema.json +233 -0
  61. api581/data/supporting-text-review.json +1319 -0
  62. api581/digital_twin.py +491 -0
  63. api581/dispersion.py +45 -0
  64. api581/effects.py +232 -0
  65. api581/evaporation.py +181 -0
  66. api581/evidence.py +81 -0
  67. api581/external_corrosion.py +148 -0
  68. api581/external_cracking.py +63 -0
  69. api581/fatigue.py +56 -0
  70. api581/fireball.py +163 -0
  71. api581/flash_pool.py +111 -0
  72. api581/flash_solver.py +122 -0
  73. api581/groundwater.py +101 -0
  74. api581/h2sh2_corrosion.py +128 -0
  75. api581/hic_screened.py +89 -0
  76. api581/htha.py +45 -0
  77. api581/hydrochloric_acid.py +272 -0
  78. api581/hydrofluoric_acid.py +236 -0
  79. api581/ignition.py +204 -0
  80. api581/inspection_effectiveness.py +601 -0
  81. api581/jet_handoff.py +60 -0
  82. api581/level1.py +311 -0
  83. api581/level1_summary.py +35 -0
  84. api581/level1_toxic.py +313 -0
  85. api581/level1_type1.py +91 -0
  86. api581/level2_summary.py +237 -0
  87. api581/lining.py +173 -0
  88. api581/native_toxic.py +67 -0
  89. api581/outputs.py +5672 -0
  90. api581/oxidation.py +145 -0
  91. api581/pascc_screening.py +78 -0
  92. api581/physical_explosion.py +190 -0
  93. api581/pof.py +207 -0
  94. api581/pool_fire.py +273 -0
  95. api581/portfolio.py +68 -0
  96. api581/prd.py +204 -0
  97. api581/prd_consequences.py +245 -0
  98. api581/prd_history.py +44 -0
  99. api581/py.typed +0 -0
  100. api581/release.py +307 -0
  101. api581/representative_fluids.py +172 -0
  102. api581/result.py +50 -0
  103. api581/risk.py +191 -0
  104. api581/saturation.py +66 -0
  105. api581/scc_mechanisms.py +242 -0
  106. api581/schema.py +45 -0
  107. api581/slabx_adapter.py +261 -0
  108. api581/soil_corrosion.py +191 -0
  109. api581/source_partition.py +169 -0
  110. api581/spatial.py +87 -0
  111. api581/ssc_screening.py +77 -0
  112. api581/standard.py +281 -0
  113. api581/storage.py +42 -0
  114. api581/sulfidic_corrosion.py +197 -0
  115. api581/sulfuric_acid.py +209 -0
  116. api581/tank.py +260 -0
  117. api581/tank_corrosion.py +256 -0
  118. api581/tank_environment.py +186 -0
  119. api581/tank_financial.py +202 -0
  120. api581/tank_integrated.py +51 -0
  121. api581/tank_shell.py +141 -0
  122. api581/tank_source.py +152 -0
  123. api581/tank_thinning.py +204 -0
  124. api581/temper.py +105 -0
  125. api581/thinning.py +172 -0
  126. api581/thinning_conditions.py +212 -0
  127. api581/thinning_history.py +118 -0
  128. api581/thinning_selection.py +81 -0
  129. api581/thinning_type.py +34 -0
  130. api581/toxic_criteria.py +111 -0
  131. api581/toxic_level2.py +233 -0
  132. api581/two_phase_events.py +120 -0
  133. api581/workflow.py +42 -0
  134. api581_engine-0.1.4.dist-info/METADATA +419 -0
  135. api581_engine-0.1.4.dist-info/RECORD +139 -0
  136. api581_engine-0.1.4.dist-info/WHEEL +5 -0
  137. api581_engine-0.1.4.dist-info/licenses/LICENSE +161 -0
  138. api581_engine-0.1.4.dist-info/licenses/NOTICE.md +18 -0
  139. api581_engine-0.1.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,328 @@
1
+ """2016 sulfuric source tables; see scripts/extract_sulfuric_source_fixture.py."""
2
+ TABLES = {'carbon_steel_printed_us_converted': {'concentrations': [1.0,
3
+ 0.98,
4
+ 0.96,
5
+ 0.935,
6
+ 0.91,
7
+ 0.87,
8
+ 0.83,
9
+ 0.78,
10
+ 0.725,
11
+ 0.67,
12
+ 0.62],
13
+ 'temperatures_k': [278.7055555555555,
14
+ 288.42777777777775,
15
+ 305.92777777777775,
16
+ 323.42777777777775],
17
+ 'velocities_m_s': [0.0,
18
+ 0.3048,
19
+ 0.6096,
20
+ 0.9144000000000001,
21
+ 1.3716000000000002,
22
+ 1.9812,
23
+ 2.5908,
24
+ 3.5052000000000003,
25
+ 3.6576000000000004],
26
+ 'rates': [[[5.0, 7.0, 9.0, 12.0, 45.0, 60.0, 75.0, 95.0, 120.0],
27
+ [12.0, 14.0, 17.0, 20.0, 65.0, 85.0, 110.0, 140.0, 170.0],
28
+ [50.0, 55.0, 60.0, 70.0, 270.0, 360.0, 450.0, 580.0, 720.0],
29
+ [100.0, 150.0, 200.0, 300.0, 999.0, 999.0, 999.0, 999.0, 999.0]],
30
+ [[4.0, 6.0, 8.0, 10.0, 35.0, 45.0, 60.0, 75.0, 90.0],
31
+ [5.0, 10.0, 15.0, 20.0, 80.0, 110.0, 140.0, 180.0, 220.0],
32
+ [15.0, 25.0, 40.0, 60.0, 290.0, 390.0, 490.0, 640.0, 780.0],
33
+ [40.0, 80.0, 120.0, 250.0, 999.0, 999.0, 999.0, 999.0, 999.0]],
34
+ [[8.0, 10.0, 12.0, 15.0, 60.0, 80.0, 110.0, 130.0, 160.0],
35
+ [15.0, 20.0, 25.0, 40.0, 170.0, 220.0, 270.0, 350.0, 430.0],
36
+ [25.0, 40.0, 60.0, 100.0, 500.0, 650.0, 820.0, 999.0, 999.0],
37
+ [50.0, 100.0, 200.0, 500.0, 999.0, 999.0, 999.0, 999.0, 999.0]],
38
+ [[10.0, 15.0, 20.0, 25.0, 120.0, 160.0, 200.0, 260.0, 330.0],
39
+ [20.0, 25.0, 40.0, 70.0, 340.0, 450.0, 570.0, 740.0, 910.0],
40
+ [30.0, 40.0, 75.0, 130.0, 640.0, 850.0, 999.0, 999.0, 999.0],
41
+ [60.0, 120.0, 250.0, 600.0, 999.0, 999.0, 999.0, 999.0, 999.0]],
42
+ [[15.0, 25.0, 45.0, 70.0, 320.0, 430.0, 540.0, 710.0, 870.0],
43
+ [25.0, 40.0, 80.0, 120.0, 700.0, 940.0, 999.0, 999.0, 999.0],
44
+ [35.0, 60.0, 100.0, 200.0, 940.0, 999.0, 999.0, 999.0, 999.0],
45
+ [70.0, 150.0, 300.0, 800.0, 999.0, 999.0, 999.0, 999.0, 999.0]],
46
+ [[20.0, 30.0, 50.0, 80.0, 380.0, 500.0, 630.0, 810.0, 999.0],
47
+ [30.0, 160.0, 300.0, 420.0, 690.0, 920.0, 999.0, 999.0, 999.0],
48
+ [45.0, 450.0, 850.0, 999.0, 999.0, 999.0, 999.0, 999.0, 999.0],
49
+ [80.0, 999.0, 999.0, 999.0, 999.0, 999.0, 999.0, 999.0, 999.0]],
50
+ [[20.0, 25.0, 35.0, 45.0, 210.0, 280.0, 350.0, 460.0, 570.0],
51
+ [30.0, 50.0, 100.0, 150.0, 680.0, 910.0, 999.0, 999.0, 999.0],
52
+ [40.0, 100.0, 200.0, 400.0, 999.0, 999.0, 999.0, 999.0, 999.0],
53
+ [80.0, 200.0, 400.0, 999.0, 999.0, 999.0, 999.0, 999.0, 999.0]],
54
+ [[15.0, 20.0, 20.0, 25.0, 110.0, 150.0, 190.0, 250.0, 300.0],
55
+ [20.0, 40.0, 70.0, 120.0, 570.0, 760.0, 950.0, 999.0, 999.0],
56
+ [30.0, 60.0, 120.0, 250.0, 999.0, 999.0, 999.0, 999.0, 999.0],
57
+ [60.0, 120.0, 300.0, 900.0, 999.0, 999.0, 999.0, 999.0, 999.0]],
58
+ [[10.0, 15.0, 20.0, 25.0, 130.0, 170.0, 220.0, 280.0, 350.0],
59
+ [15.0, 30.0, 50.0, 100.0, 490.0, 650.0, 810.0, 999.0, 999.0],
60
+ [25.0, 50.0, 100.0, 200.0, 980.0, 999.0, 999.0, 999.0, 999.0],
61
+ [50.0, 100.0, 250.0, 800.0, 999.0, 999.0, 999.0, 999.0, 999.0]],
62
+ [[20.0, 30.0, 40.0, 60.0, 280.0, 370.0, 460.0, 600.0, 740.0],
63
+ [30.0, 50.0, 100.0, 170.0, 830.0, 999.0, 999.0, 999.0, 999.0],
64
+ [50.0, 100.0, 180.0, 300.0, 999.0, 999.0, 999.0, 999.0, 999.0],
65
+ [100.0, 200.0, 400.0, 999.0, 999.0, 999.0, 999.0, 999.0, 999.0]],
66
+ [[75.0, 85.0, 100.0, 120.0, 570.0, 760.0, 950.0, 999.0, 999.0],
67
+ [120.0, 170.0, 250.0, 400.0, 999.0, 999.0, 999.0, 999.0, 999.0],
68
+ [200.0, 300.0, 600.0, 900.0, 999.0, 999.0, 999.0, 999.0, 999.0],
69
+ [500.0, 750.0, 999.0, 999.0, 999.0, 999.0, 999.0, 999.0, 999.0]]]},
70
+ 'carbon_steel_printed_si': {'concentrations': [1.0, 0.98, 0.96, 0.935, 0.91, 0.87, 0.83, 0.78, 0.725, 0.67, 0.62],
71
+ 'temperatures_k': [279.15, 288.15, 306.15, 323.15],
72
+ 'velocities_m_s': [0, 1, 2, 3, 4.5, 6.5, 8.5, 11.5, 12],
73
+ 'rates': [[[0.13, 0.18, 0.23, 0.3, 1.14, 1.52, 1.91, 2.41, 3.05],
74
+ [0.3, 0.36, 0.43, 0.51, 1.65, 2.16, 2.79, 3.56, 4.32],
75
+ [1.27, 1.4, 1.52, 1.78, 6.86, 9.14, 11.43, 14.73, 18.29],
76
+ [2.54, 3.81, 5.08, 7.62, 25.37, 25.37, 25.37, 25.37, 25.37]],
77
+ [[0.1, 0.15, 0.2, 0.25, 0.89, 1.14, 1.52, 1.91, 2.29],
78
+ [0.13, 0.25, 0.38, 0.51, 2.03, 2.79, 3.56, 4.57, 5.59],
79
+ [0.38, 0.64, 1.02, 1.52, 7.37, 9.91, 12.45, 16.26, 19.81],
80
+ [1.02, 2.03, 3.05, 6.35, 25.37, 25.37, 25.37, 25.37, 25.37]],
81
+ [[0.2, 0.25, 0.3, 0.38, 1.52, 2.03, 2.79, 3.3, 4.06],
82
+ [0.38, 0.51, 0.64, 1.02, 4.32, 5.59, 6.86, 8.89, 10.92],
83
+ [0.64, 1.02, 1.52, 2.54, 12.7, 16.51, 20.83, 25.37, 25.37],
84
+ [1.27, 2.54, 5.08, 12.7, 25.37, 25.37, 25.37, 25.37, 25.37]],
85
+ [[0.25, 0.38, 0.51, 0.64, 3.05, 4.06, 5.08, 6.6, 8.38],
86
+ [0.51, 0.64, 1.02, 1.78, 8.64, 11.43, 14.48, 18.8, 23.11],
87
+ [0.76, 1.02, 1.91, 3.3, 16.26, 21.59, 25.37, 25.37, 25.37],
88
+ [1.52, 3.05, 6.35, 15.24, 25.37, 25.37, 25.37, 25.37, 25.37]],
89
+ [[0.38, 0.64, 1.14, 1.78, 8.13, 10.92, 13.72, 18.03, 22.1],
90
+ [0.64, 1.02, 2.03, 3.05, 17.78, 23.88, 25.37, 25.37, 25.37],
91
+ [0.89, 1.52, 2.54, 5.08, 23.88, 25.37, 25.37, 25.37, 25.37],
92
+ [1.78, 3.81, 7.62, 20.32, 25.37, 25.37, 25.37, 25.37, 25.37]],
93
+ [[0.51, 0.76, 1.27, 2.03, 9.65, 12.7, 16.0, 20.57, 25.37],
94
+ [0.76, 4.06, 7.62, 10.67, 17.53, 23.37, 25.37, 25.37, 25.37],
95
+ [1.14, 11.43, 21.59, 25.37, 25.37, 25.37, 25.37, 25.37, 25.37],
96
+ [2.03, 25.37, 25.37, 25.37, 25.37, 25.37, 25.37, 25.37, 25.37]],
97
+ [[0.51, 0.64, 0.89, 1.14, 5.33, 7.11, 8.89, 11.68, 14.48],
98
+ [0.76, 1.27, 2.54, 3.81, 17.27, 23.11, 25.37, 25.37, 25.37],
99
+ [1.02, 2.54, 5.08, 10.16, 25.37, 25.37, 25.37, 25.37, 25.37],
100
+ [2.03, 5.08, 10.16, 25.37, 25.37, 25.37, 25.37, 25.37, 25.37]],
101
+ [[0.38, 0.51, 0.51, 0.64, 2.79, 3.81, 4.83, 6.35, 7.62],
102
+ [0.51, 1.02, 1.78, 3.05, 14.48, 19.3, 24.13, 25.37, 25.37],
103
+ [0.76, 1.52, 3.05, 6.35, 25.37, 25.37, 25.37, 25.37, 25.37],
104
+ [1.52, 3.05, 7.62, 22.86, 25.37, 25.37, 25.37, 25.37, 25.37]],
105
+ [[0.25, 0.38, 0.51, 0.64, 3.3, 4.32, 5.59, 7.11, 8.89],
106
+ [0.38, 0.76, 1.27, 2.54, 12.45, 16.51, 20.57, 25.37, 25.37],
107
+ [0.64, 1.27, 2.54, 5.08, 24.89, 25.37, 25.37, 25.37, 25.37],
108
+ [1.27, 2.54, 6.35, 20.32, 25.37, 25.37, 25.37, 25.37, 25.37]],
109
+ [[0.51, 0.76, 1.02, 1.52, 7.11, 9.4, 11.68, 15.24, 18.8],
110
+ [0.76, 1.27, 2.54, 4.32, 21.08, 25.37, 25.37, 25.37, 25.37],
111
+ [1.27, 2.54, 4.57, 7.62, 25.37, 25.37, 25.37, 25.37, 25.37],
112
+ [2.54, 5.08, 10.16, 25.37, 25.37, 25.37, 25.37, 25.37, 25.37]],
113
+ [[1.91, 2.16, 2.54, 3.05, 14.48, 19.3, 24.13, 25.37, 25.37],
114
+ [3.05, 4.32, 6.35, 10.16, 25.37, 25.37, 25.37, 25.37, 25.37],
115
+ [5.08, 7.62, 15.24, 22.86, 25.37, 25.37, 25.37, 25.37, 25.37],
116
+ [12.7, 19.05, 25.37, 25.37, 25.37, 25.37, 25.37, 25.37, 25.37]]]},
117
+ 'ss_304_printed_us_converted': {'concentrations': [0.98,
118
+ 0.925,
119
+ 0.87,
120
+ 0.82,
121
+ 0.75,
122
+ 0.65,
123
+ 0.5,
124
+ 0.3,
125
+ 0.15,
126
+ 0.08,
127
+ 0.035,
128
+ 0.02],
129
+ 'temperatures_k': [303.15, 313.42777777777775, 333.42777777777775],
130
+ 'velocities_m_s': [0.6096, 1.8288000000000002, 2.1336],
131
+ 'rates': [[[5.0, 10.0, 15.0], [20.0, 40.0, 60.0], [200.0, 400.0, 600.0]],
132
+ [[20.0, 40.0, 60.0], [40.0, 80.0, 120.0], [500.0, 999.0, 999.0]],
133
+ [[40.0, 80.0, 120.0], [80.0, 160.0, 240.0], [999.0, 999.0, 999.0]],
134
+ [[100.0, 200.0, 300.0], [500.0, 999.0, 999.0], [999.0, 999.0, 999.0]],
135
+ [[500.0, 999.0, 999.0], [999.0, 999.0, 99.0], [999.0, 999.0, 999.0]],
136
+ [[999.0, 999.0, 999.0], [999.0, 999.0, 999.0], [999.0, 999.0, 999.0]],
137
+ [[999.0, 999.0, 999.0], [999.0, 999.0, 999.0], [999.0, 999.0, 999.0]],
138
+ [[999.0, 999.0, 999.0], [999.0, 999.0, 999.0], [999.0, 999.0, 999.0]],
139
+ [[400.0, 800.0, 999.0], [999.0, 999.0, 999.0], [999.0, 999.0, 999.0]],
140
+ [[200.0, 400.0, 600.0], [800.0, 999.0, 999.0], [999.0, 999.0, 999.0]],
141
+ [[50.0, 100.0, 150.0], [200.0, 400.0, 600.0], [500.0, 999.0, 999.0]],
142
+ [[20.0, 40.0, 60.0], [70.0, 140.0, 210.0], [200.0, 400.0, 600.0]]]},
143
+ 'ss_304_printed_si': {'concentrations': [0.98, 0.925, 0.87, 0.82, 0.75, 0.65, 0.5, 0.3, 0.15, 0.08, 0.035, 0.02],
144
+ 'temperatures_k': [303.15, 313.15, 333.15],
145
+ 'velocities_m_s': [0.61, 1.83, 2.13],
146
+ 'rates': [[[0.13, 0.25, 0.38], [0.51, 1.02, 1.52], [5.08, 10.16, 15.24]],
147
+ [[0.51, 1.02, 1.52], [1.02, 2.03, 3.05], [12.7, 25.37, 25.37]],
148
+ [[1.02, 2.03, 3.05], [2.03, 4.06, 6.1], [25.37, 25.37, 25.37]],
149
+ [[2.54, 5.08, 7.62], [12.7, 25.37, 25.37], [25.37, 25.37, 25.37]],
150
+ [[12.7, 25.37, 25.37], [25.37, 25.37, 2.51], [25.37, 25.37, 25.37]],
151
+ [[25.37, 25.37, 25.37], [25.37, 25.37, 25.37], [25.37, 25.37, 25.37]],
152
+ [[25.37, 25.37, 25.37], [25.37, 25.37, 25.37], [25.37, 25.37, 25.37]],
153
+ [[25.37, 25.37, 25.37], [25.37, 25.37, 25.37], [25.37, 25.37, 25.37]],
154
+ [[10.16, 20.32, 25.37], [25.37, 25.37, 25.37], [25.37, 25.37, 25.37]],
155
+ [[5.08, 10.16, 15.24], [20.32, 25.37, 25.37], [25.37, 25.37, 25.37]],
156
+ [[1.27, 2.54, 3.81], [5.08, 10.16, 15.24], [12.7, 25.37, 25.37]],
157
+ [[0.51, 1.02, 1.52], [1.78, 3.56, 5.33], [5.08, 10.16, 15.24]]]},
158
+ 'ss_316_printed_us_converted': {'concentrations': [0.98,
159
+ 0.925,
160
+ 0.87,
161
+ 0.82,
162
+ 0.75,
163
+ 0.65,
164
+ 0.5,
165
+ 0.3,
166
+ 0.15,
167
+ 0.08,
168
+ 0.035,
169
+ 0.02],
170
+ 'temperatures_k': [303.15, 313.42777777777775, 333.42777777777775],
171
+ 'velocities_m_s': [0.6096, 1.8288000000000002, 2.1336],
172
+ 'rates': [[[5.0, 10.0, 15.0], [15.0, 30.0, 45.0], [100.0, 200.0, 300.0]],
173
+ [[10.0, 20.0, 30.0], [30.0, 60.0, 90.0], [400.0, 800.0, 999.0]],
174
+ [[20.0, 40.0, 60.0], [50.0, 100.0, 150.0], [800.0, 999.0, 999.0]],
175
+ [[50.0, 100.0, 150.0], [400.0, 800.0, 999.0], [999.0, 999.0, 999.0]],
176
+ [[300.0, 600.0, 900.0], [999.0, 999.0, 999.0], [999.0, 999.0, 999.0]],
177
+ [[600.0, 999.0, 999.0], [999.0, 999.0, 999.0], [999.0, 999.0, 999.0]],
178
+ [[900.0, 999.0, 999.0], [999.0, 999.0, 999.0], [999.0, 999.0, 999.0]],
179
+ [[200.0, 400.0, 600.0], [999.0, 999.0, 999.0], [999.0, 999.0, 999.0]],
180
+ [[30.0, 60.0, 90.0], [60.0, 120.0, 180.0], [200.0, 400.0, 600.0]],
181
+ [[10.0, 20.0, 30.0], [30.0, 60.0, 90.0], [80.0, 160.0, 240.0]],
182
+ [[5.0, 10.0, 15.0], [20.0, 40.0, 60.0], [40.0, 80.0, 120.0]],
183
+ [[5.0, 10.0, 15.0], [5.0, 10.0, 15.0], [10.0, 20.0, 30.0]]]},
184
+ 'ss_316_printed_si': {'concentrations': [0.98, 0.925, 0.87, 0.82, 0.75, 0.65, 0.5, 0.3, 0.15, 0.08, 0.035, 0.02],
185
+ 'temperatures_k': [303.15, 313.15, 333.15],
186
+ 'velocities_m_s': [0.61, 1.83, 2.13],
187
+ 'rates': [[[0.13, 0.25, 0.38], [0.38, 0.76, 1.14], [2.54, 5.08, 7.62]],
188
+ [[0.25, 0.51, 0.76], [0.76, 1.52, 2.29], [10.16, 20.32, 25.37]],
189
+ [[0.51, 1.02, 1.52], [1.27, 2.54, 3.81], [20.32, 25.37, 25.37]],
190
+ [[1.27, 2.54, 3.81], [10.16, 20.32, 25.37], [25.37, 25.37, 25.37]],
191
+ [[7.62, 15.24, 22.86], [25.37, 25.37, 25.37], [25.37, 25.37, 25.37]],
192
+ [[15.24, 25.37, 25.37], [25.37, 25.37, 25.37], [25.37, 25.37, 25.37]],
193
+ [[22.86, 25.37, 25.37], [25.37, 25.37, 25.37], [25.37, 25.37, 25.37]],
194
+ [[5.08, 10.16, 15.24], [25.37, 25.37, 25.37], [25.37, 25.37, 25.37]],
195
+ [[0.76, 1.52, 2.29], [1.52, 3.05, 4.57], [5.08, 10.16, 15.24]],
196
+ [[0.25, 0.51, 0.76], [0.76, 1.52, 2.29], [2.03, 4.06, 6.1]],
197
+ [[0.13, 0.25, 0.38], [0.51, 1.02, 1.52], [1.02, 2.03, 3.05]],
198
+ [[0.13, 0.25, 0.38], [0.13, 0.25, 0.38], [0.25, 0.51, 0.76]]]},
199
+ 'alloy_20_printed_us_converted': {'concentrations': [0.98, 0.925, 0.85, 0.7, 0.55, 0.45, 0.35, 0.25, 0.15, 0.08, 0.05],
200
+ 'temperatures_k': [310.92777777777775,
201
+ 324.81666666666666,
202
+ 346.2055555555555,
203
+ 363.9833333333333],
204
+ 'velocities_m_s': [0.9144000000000001, 2.5908, 3.048],
205
+ 'rates': [[[2.0, 4.0, 6.0],
206
+ [5.0, 10.0, 15.0],
207
+ [15.0, 30.0, 45.0],
208
+ [40.0, 80.0, 120.0]],
209
+ [[3.0, 6.0, 9.0],
210
+ [10.0, 20.0, 30.0],
211
+ [25.0, 50.0, 75.0],
212
+ [50.0, 100.0, 150.0]],
213
+ [[3.0, 6.0, 9.0],
214
+ [10.0, 20.0, 30.0],
215
+ [30.0, 60.0, 90.0],
216
+ [60.0, 120.0, 180.0]],
217
+ [[3.0, 6.0, 9.0],
218
+ [15.0, 30.0, 45.0],
219
+ [50.0, 100.0, 150.0],
220
+ [100.0, 200.0, 300.0]],
221
+ [[3.0, 6.0, 9.0],
222
+ [10.0, 20.0, 30.0],
223
+ [30.0, 60.0, 90.0],
224
+ [60.0, 120.0, 180.0]],
225
+ [[3.0, 6.0, 9.0],
226
+ [10.0, 20.0, 30.0],
227
+ [30.0, 60.0, 90.0],
228
+ [50.0, 100.0, 150.0]],
229
+ [[3.0, 6.0, 9.0],
230
+ [10.0, 20.0, 30.0],
231
+ [25.0, 50.0, 75.0],
232
+ [40.0, 80.0, 120.0]],
233
+ [[2.0, 4.0, 6.0],
234
+ [5.0, 10.0, 15.0],
235
+ [20.0, 40.0, 60.0],
236
+ [40.0, 80.0, 120.0]],
237
+ [[2.0, 4.0, 6.0],
238
+ [5.0, 10.0, 15.0],
239
+ [20.0, 40.0, 60.0],
240
+ [35.0, 70.0, 105.0]],
241
+ [[2.0, 4.0, 6.0], [3.0, 6.0, 9.0], [5.0, 10.0, 15.0], [25.0, 50.0, 75.0]],
242
+ [[2.0, 4.0, 6.0], [3.0, 6.0, 9.0], [3.0, 6.0, 9.0], [20.0, 40.0, 60.0]]]},
243
+ 'alloy_20_printed_si': {'concentrations': [0.98, 0.925, 0.85, 0.7, 0.55, 0.45, 0.35, 0.25, 0.15, 0.08, 0.05],
244
+ 'temperatures_k': [311.15, 325.15, 343.15, 364.15],
245
+ 'velocities_m_s': [0.91, 2.59, 3.05],
246
+ 'rates': [[[0.05, 0.1, 0.15], [0.13, 0.25, 0.38], [0.38, 0.76, 1.14], [1.02, 2.03, 3.05]],
247
+ [[0.08, 0.15, 0.23], [0.25, 0.51, 0.76], [0.64, 1.27, 1.91], [1.27, 2.54, 3.81]],
248
+ [[0.08, 0.15, 0.23], [0.25, 0.51, 0.76], [0.76, 1.52, 2.29], [1.52, 3.05, 4.57]],
249
+ [[0.08, 0.15, 0.23], [0.38, 0.76, 1.14], [1.27, 2.54, 3.81], [2.54, 5.08, 7.62]],
250
+ [[0.08, 0.15, 0.23], [0.25, 0.51, 0.76], [0.76, 1.52, 2.29], [1.52, 3.05, 4.57]],
251
+ [[0.08, 0.15, 0.23], [0.25, 0.51, 0.76], [0.76, 1.52, 2.29], [1.27, 2.54, 3.81]],
252
+ [[0.08, 0.15, 0.23], [0.25, 0.51, 0.76], [0.64, 1.27, 1.91], [1.02, 2.03, 3.05]],
253
+ [[0.05, 0.1, 0.15], [0.13, 0.25, 0.38], [0.51, 1.02, 1.52], [1.02, 2.03, 3.05]],
254
+ [[0.05, 0.1, 0.15], [0.13, 0.25, 0.38], [0.51, 1.02, 1.52], [0.89, 1.78, 2.67]],
255
+ [[0.05, 0.1, 0.15], [0.08, 0.15, 0.23], [0.13, 0.25, 0.38], [0.64, 1.27, 1.91]],
256
+ [[0.05, 0.1, 0.15], [0.08, 0.15, 0.23], [0.08, 0.15, 0.23], [0.51, 1.02, 1.52]]]},
257
+ 'alloy_c276_printed_us_converted': {'concentrations': [0.98, 0.925, 0.85, 0.75, 0.55, 0.2, 0.08, 0.05],
258
+ 'temperatures_k': [324.81666666666666,
259
+ 331.76111111111106,
260
+ 345.65,
261
+ 359.5388888888889],
262
+ 'velocities_m_s': [0.9144000000000001, 2.5908, 3.048],
263
+ 'rates': [[[3.0, 6.0, 9.0],
264
+ [4.0, 8.0, 12.0],
265
+ [5.0, 10.0, 15.0],
266
+ [20.0, 40.0, 60.0]],
267
+ [[4.0, 8.0, 12.0],
268
+ [5.0, 10.0, 15.0],
269
+ [20.0, 40.0, 60.0],
270
+ [50.0, 100.0, 150.0]],
271
+ [[5.0, 10.0, 15.0],
272
+ [10.0, 20.0, 30.0],
273
+ [20.0, 40.0, 60.0],
274
+ [60.0, 120.0, 180.0]],
275
+ [[5.0, 10.0, 15.0],
276
+ [10.0, 20.0, 30.0],
277
+ [20.0, 40.0, 60.0],
278
+ [50.0, 100.0, 150.0]],
279
+ [[5.0, 10.0, 15.0],
280
+ [10.0, 20.0, 30.0],
281
+ [15.0, 30.0, 45.0],
282
+ [40.0, 80.0, 120.0]],
283
+ [[4.0, 8.0, 12.0],
284
+ [5.0, 10.0, 15.0],
285
+ [15.0, 30.0, 45.0],
286
+ [40.0, 80.0, 120.0]],
287
+ [[4.0, 8.0, 12.0],
288
+ [5.0, 10.0, 15.0],
289
+ [10.0, 20.0, 30.0],
290
+ [30.0, 60.0, 90.0]],
291
+ [[3.0, 6.0, 9.0],
292
+ [4.0, 8.0, 12.0],
293
+ [5.0, 10.0, 15.0],
294
+ [15.0, 30.0, 45.0]]]},
295
+ 'alloy_c276_printed_si': {'concentrations': [0.98, 0.925, 0.85, 0.75, 0.55, 0.2, 0.08, 0.05],
296
+ 'temperatures_k': [325.15, 332.15, 346.15, 359.15],
297
+ 'velocities_m_s': [0.91, 2.59, 3.05],
298
+ 'rates': [[[0.08, 0.15, 0.23], [0.1, 0.2, 0.3], [0.13, 0.25, 0.38], [0.51, 1.02, 1.52]],
299
+ [[0.1, 0.2, 0.3], [0.13, 0.25, 0.38], [0.51, 1.02, 1.52], [1.27, 2.54, 3.81]],
300
+ [[0.13, 0.25, 0.38], [0.25, 0.51, 0.76], [0.51, 1.02, 1.52], [1.52, 3.05, 4.57]],
301
+ [[0.13, 0.25, 0.38], [0.25, 0.51, 0.76], [0.51, 1.02, 1.52], [1.27, 2.54, 3.81]],
302
+ [[0.13, 0.25, 0.38], [0.25, 0.51, 0.76], [0.38, 0.76, 1.14], [1.02, 2.03, 3.05]],
303
+ [[0.1, 0.2, 0.3], [0.13, 0.25, 0.38], [0.38, 0.76, 1.14], [1.02, 2.03, 3.05]],
304
+ [[0.1, 0.2, 0.3], [0.13, 0.25, 0.38], [0.25, 0.51, 0.76], [0.76, 1.52, 2.29]],
305
+ [[0.08, 0.15, 0.23], [0.1, 0.2, 0.3], [0.13, 0.25, 0.38], [0.38, 0.76, 1.14]]]},
306
+ 'alloy_b2_printed_us_converted': {'concentrations': [0.75, 0.45, 0.325, 0.25],
307
+ 'temperatures_k': [324.81666666666666,
308
+ 331.76111111111106,
309
+ 345.65,
310
+ 359.5388888888889],
311
+ 'velocities_m_s': [0.9144000000000001, 2.5908, 3.048],
312
+ 'rates': [[[2.0, 4.0, 6.0], [3.0, 6.0, 9.0], [4.0, 8.0, 12.0], [5.0, 10.0, 15.0]],
313
+ [[3.0, 6.0, 9.0], [4.0, 8.0, 12.0], [4.0, 8.0, 12.0], [5.0, 10.0, 15.0]],
314
+ [[4.0, 8.0, 12.0],
315
+ [5.0, 10.0, 15.0],
316
+ [5.0, 10.0, 15.0],
317
+ [5.0, 10.0, 15.0]],
318
+ [[5.0, 10.0, 15.0],
319
+ [10.0, 20.0, 30.0],
320
+ [10.0, 20.0, 30.0],
321
+ [10.0, 20.0, 30.0]]]},
322
+ 'alloy_b2_printed_si': {'concentrations': [0.75, 0.45, 0.325, 0.25],
323
+ 'temperatures_k': [325.15, 332.15, 346.15, 359.15],
324
+ 'velocities_m_s': [0.91, 2.59, 3.05],
325
+ 'rates': [[[0.05, 0.1, 0.15], [0.08, 0.15, 0.23], [0.1, 0.2, 0.3], [0.13, 0.25, 0.38]],
326
+ [[0.08, 0.15, 0.23], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.13, 0.25, 0.38]],
327
+ [[0.1, 0.2, 0.3], [0.13, 0.25, 0.38], [0.13, 0.25, 0.38], [0.13, 0.25, 0.38]],
328
+ [[0.13, 0.25, 0.38], [0.25, 0.51, 0.76], [0.25, 0.51, 0.76], [0.25, 0.51, 0.76]]]}}
@@ -0,0 +1,238 @@
1
+ """2016 Annex2.B section10 acid sour water routing, rate and thinning."""
2
+ from bisect import bisect_left
3
+ from typing import Literal
4
+ from pydantic import Field
5
+ from .core import Model, NonNegative, Positive, Fraction, calculation, ensure, finite_tree
6
+ from .thinning import Posterior, WallLoss
7
+ from .clad_thinning import PressureStrength, CodeStrength
8
+ from .thinning_conditions import ThinningConditions, QualifiedUnclad, qualified_unclad
9
+
10
+ Units = Literal["printed_si","printed_us_converted"]
11
+ _PH = (4.75,5.25,5.75,6.25,6.75)
12
+ _SI = ((.03,.08,.13,.18),(.02,.05,.08,.1),(.01,.04,.05,.08),(.01,.03,.04,.05),(.01,.01,.02,.03))
13
+ _US = ((1,3,5,7),(.7,2,3,4),(.4,1.5,2,3),(.3,1,1.5,2),(.2,.5,.7,1))
14
+
15
+
16
+ class AcidSourRoute(Model):
17
+ free_water_present: bool
18
+ ph: float | None = Field(default=None,ge=0,le=14,strict=True,allow_inf_nan=False)
19
+ chlorides_present: bool | None = None
20
+ chloride_concentration_ppm: NonNegative | None = None
21
+ chloride_policy: Literal["figure_any_presence_routes_hcl","qualified_below_50ppm"] | None = None
22
+ chloride_qualification_basis: str | None = None
23
+ neutral_ph_policy: Literal["detailed_body_inclusive","unclassified_at_neutral"] | None = None
24
+ qualification_basis: str = Field(min_length=1)
25
+
26
+
27
+ @calculation("damage/acid-sour-water-route", "Acid sour water free-water pH and chloride route", AcidSourRoute,
28
+ ["Part2 Annex2.B Table1.1,§10.1-10.3,Figure10.1; PDF pp322,392-393,396"],limitations=[
29
+ "No free water gives source-specific dry branch. Detailed pH<4.5 routesHCl,>7 routes alkaline sour water, otherwise acid sour. Overview sayspH<7: exact7 can follow explicit detailed-body inclusion or remain unclassified, not guessed. Figure any chloride presence routesHCl, while Annex screen permits<50ppm and body assumes insignificant chloride: qualified-below50ppm policy requires concentration and basis. Route result does not execute another mechanism or assert zero total damage; use lowest separated-water-phase pH, not unqualified bulk average."])
30
+ def route(x):
31
+ ensure(bool(x.qualification_basis.strip()),"Water/pH/source qualification required")
32
+ if not x.free_water_present:
33
+ ensure(all(v is None for v in (x.ph,x.chlorides_present,x.chloride_concentration_ppm,x.chloride_policy,x.chloride_qualification_basis,x.neutral_ph_policy)),
34
+ "Dry branch cannot carry unused water-phase screening inputs")
35
+ decision = "no_free_water"
36
+ else:
37
+ ensure(x.ph is not None and x.neutral_ph_policy is not None,"Wet route requires pH and explicit neutral boundary policy")
38
+ ensure(x.chlorides_present is not None and x.chloride_policy is not None,"Wet route requires explicit chloride screening")
39
+ if x.chloride_concentration_ppm is not None:
40
+ ensure((x.chloride_concentration_ppm > 0) == x.chlorides_present,"Chloride presence and supplied concentration must agree")
41
+ qualified = x.chloride_policy == "qualified_below_50ppm"
42
+ ensure((x.chloride_qualification_basis is not None and bool(x.chloride_qualification_basis.strip()) and x.chloride_concentration_ppm is not None) if qualified else x.chloride_qualification_basis is None,
43
+ "Qualified chloride policy requires concentration and insignificant-corrosive basis")
44
+ chloride_routes = (x.chloride_concentration_ppm >= 50) if qualified else x.chlorides_present
45
+ if x.ph > 7:
46
+ decision = "alkaline_sour_water"
47
+ elif x.ph < 4.5:
48
+ decision = "hydrochloric_acid"
49
+ elif x.ph == 7 and x.neutral_ph_policy == "unclassified_at_neutral":
50
+ decision = "neutral_boundary_unclassified"
51
+ else:
52
+ decision = "hydrochloric_acid" if chloride_routes else "acid_sour_water"
53
+ return {"corrosion_route":decision}
54
+
55
+
56
+ class AcidSourBase(Model):
57
+ ph: float = Field(ge=4.5,le=7,strict=True,allow_inf_nan=False)
58
+ maximum_water_temperature_k: Positive
59
+ unit_basis: Units
60
+ lookup_policy: Literal["exact_nodes_only","bilinear","maximum_bracketing_value"]
61
+ ph_edge_policy: Literal["reject_unprinted_ph","qualified_nearest_edge_row"]
62
+ ph_edge_qualification_basis: str | None = None
63
+
64
+
65
+ def _bracket(nodes,value):
66
+ value = next((n for n in nodes if abs(n-value) <= 1e-9),value)
67
+ ensure(nodes[0] <= value <= nodes[-1],"Outside acid sour source table; no extrapolation")
68
+ i = bisect_left(nodes,value)
69
+ return (i,i,0.) if value == nodes[i] else (i-1,i,(value-nodes[i-1])/(nodes[i]-nodes[i-1]))
70
+
71
+
72
+ @calculation("damage/acid-sour-water-base-rate", "Acid sour water pH and temperature base corrosion rate", AcidSourBase,
73
+ ["Part2 Tables2.B.10.2/10.2M; PDF pp394-395"],limitations=[
74
+ "20SI and20US cells; pH4.75..6.75,SI38/52/79/93C orUS100/125/175/200F. Explicit basis preserves rounded temperatures/rates. Detailed scope4.5..7 extends beyond table: missing pH edge bands reject or use qualified nearest-edge row, flagged, never silent clamping. Temperature never extrapolated. Between-node handling exact/bilinear/max-bracketing explicit numerical policy, not source instruction. Lowest separated aqueous-phase pH and maximum water temperature, including local heating, must be qualified."])
75
+ def base_rate(x):
76
+ qualified = x.ph_edge_policy == "qualified_nearest_edge_row"
77
+ ensure((x.ph_edge_qualification_basis is not None and bool(x.ph_edge_qualification_basis.strip())) if qualified else x.ph_edge_qualification_basis is None,"pH edge policy and basis must agree")
78
+ edge = x.ph < 4.75 or x.ph > 6.75
79
+ ensure(not edge or qualified,"pH in source-scope edge band lacks printed rate row","acid_sour_ph_edge")
80
+ ph = min(max(x.ph,4.75),6.75)
81
+ temps = tuple(c+273.15 for c in (38,52,79,93)) if x.unit_basis == "printed_si" else tuple((f-32)*5/9+273.15 for f in (100,125,175,200))
82
+ a,b,wp = _bracket(_PH,ph)
83
+ c,d,wt = _bracket(temps,x.maximum_water_temperature_k)
84
+ ensure(x.lookup_policy != "exact_nodes_only" or (a == b and c == d),"Exact-node policy does not interpolate")
85
+ table = _SI if x.unit_basis == "printed_si" else _US
86
+ if x.lookup_policy == "maximum_bracketing_value":
87
+ native = max(table[a][c],table[a][d],table[b][c],table[b][d])
88
+ else:
89
+ lower = table[a][c]+wt*(table[a][d]-table[a][c])
90
+ upper = table[b][c]+wt*(table[b][d]-table[b][c])
91
+ native = lower+wp*(upper-lower)
92
+ return {"base_corrosion_rate_m_year":native*(.001 if x.unit_basis == "printed_si" else .0000254),"ph_for_lookup":ph,"ph_edge_row_used":edge}
93
+
94
+
95
+ class AcidSourOxygen(Model):
96
+ oxygen_content_ppb: NonNegative
97
+
98
+
99
+ @calculation("damage/acid-sour-water-oxygen-factor", "Acid sour water oxygen adjustment factor", AcidSourOxygen,
100
+ ["Part2 Table2.B.10.3; PDF p395"],limitations=[
101
+ "Table oxygen<=50ppb factor1,>50ppb factor2. Detailed scope says normal<50ppb; equality follows explicit table. High oxygen can introduce localized/pitting attack: factor2 is source general-rate correction, not a separate pitting prediction or wetH2S cracking assessment."])
102
+ def oxygen(x):
103
+ return {"oxygen_factor":1. if x.oxygen_content_ppb <= 50 else 2.,"high_oxygen_localized_review":x.oxygen_content_ppb > 50}
104
+
105
+
106
+ class AcidSourVelocity(Model):
107
+ velocity_m_s: NonNegative
108
+ equation_basis: Units
109
+
110
+
111
+ @calculation("damage/acid-sour-water-velocity-factor", "Acid sour water piecewise velocity factor", AcidSourVelocity,
112
+ ["Part2 eqs(2.B.2)-(2.B.7); PDF p393"],limitations=[
113
+ "SI: below1.83m/s factor1,1.83..6.10 inclusive .82*v-.5,above6.10 factor5. US: below6ft/s1,6..20 inclusive .25*v-.5,above20 factor5. Publicm/s, explicit basis; equality and discontinuity at high threshold retained, not smoothed. High velocity requires localized-attack review; factor5 is not erosion/CFD or cracking model."])
114
+ def velocity(x):
115
+ lo,hi = (1.83,6.10) if x.equation_basis == "printed_si" else (6*.3048,20*.3048)
116
+ middle = .82*x.velocity_m_s-.5 if x.equation_basis == "printed_si" else .25*(x.velocity_m_s/.3048)-.5
117
+ value = 1. if x.velocity_m_s < lo else middle if x.velocity_m_s <= hi else 5.
118
+ return {"velocity_factor":value,"high_velocity_localized_review":x.velocity_m_s > lo}
119
+
120
+
121
+ class AcidSourEquation(Model):
122
+ base_corrosion_rate_m_year: NonNegative
123
+ oxygen_factor: Positive
124
+ velocity_factor: Positive
125
+
126
+
127
+ @calculation("damage/acid-sour-water-rate-equation", "Acid sour water base rate times oxygen and velocity", AcidSourEquation,
128
+ ["Part2 Annex2.B §10.3 eq(2.B.1); PDF p393"],limitations=["CR=CRpH*FO*FV. Context-specific duplicate equation number is Annex2.B §10, not other source equations2.B.1. Applicability, water/pH/chloride and material qualification are separate."])
129
+ def rate_equation(x):
130
+ value = x.base_corrosion_rate_m_year*x.oxygen_factor*x.velocity_factor
131
+ finite_tree(value)
132
+ ensure(value > 0 or x.base_corrosion_rate_m_year == 0,"Acid sour corrosion product underflow","numerical_failure")
133
+ return {"corrosion_rate_m_year":value}
134
+
135
+
136
+ class AcidSourOtherMaterial(Model):
137
+ service_suitability_qualified: bool
138
+ other_degradation_assessed: bool
139
+ rate_policy: Literal["body_upper_bound_screening","figure_nominal_2mpy"]
140
+ body_unit_basis: Units | None = None
141
+ qualification_basis: str = Field(min_length=1)
142
+
143
+
144
+ @calculation("damage/acid-sour-water-other-material-rate", "Acid sour water suitable-other-material source estimate", AcidSourOtherMaterial,
145
+ ["Part2 §10.1.2(c),Figure10.1; PDF pp392,396"],limitations=[
146
+ "Body assumes suitable other material corrosion will not exceed.05mm/y(2mpy); figure assignsCR=2mpy. Explicit choice returns body upper-bound screening or figure nominal point. Rounded bodySI.00005m/y differs from exact2mpy=.0000508. Requires actual suitability and other-degradation assessment; no alloy-name immunity to wetH2S cracking/ClSCC/dealloying. No oxygen/velocity correction automatically applied to separate other-material branch."])
147
+ def other_material(x):
148
+ ensure(x.service_suitability_qualified and x.other_degradation_assessed and bool(x.qualification_basis.strip()),"Other-material source estimate requires qualified suitability and separate degradation review")
149
+ bound = x.rate_policy == "body_upper_bound_screening"
150
+ ensure((x.body_unit_basis is not None) if bound else x.body_unit_basis is None,"Body unit basis applies only to body bound policy")
151
+ value = .00005 if bound and x.body_unit_basis == "printed_si" else 2*.0000254
152
+ return {"corrosion_rate_m_year":value,"selected_rate_is_screening_bound":bound}
153
+
154
+
155
+ class AcidSourCarbon(Model):
156
+ maximum_water_temperature_k: Positive
157
+ unit_basis: Units
158
+ lookup_policy: Literal["exact_nodes_only","bilinear","maximum_bracketing_value"]
159
+ ph_edge_policy: Literal["reject_unprinted_ph","qualified_nearest_edge_row"]
160
+ ph_edge_qualification_basis: str | None = None
161
+ oxygen_content_ppb: NonNegative
162
+ velocity_m_s: NonNegative
163
+ velocity_equation_basis: Units
164
+ qualification_basis: str = Field(min_length=1)
165
+
166
+
167
+ class AcidSourAssessment(Model):
168
+ screening: AcidSourRoute
169
+ material: Literal["carbon_steel","low_alloy_steel","other_material"]
170
+ total_alloy_mass_fraction: Fraction | None = None
171
+ primary_h2s_and_insignificant_other_corrosives_qualified: bool
172
+ applicability_basis: str = Field(min_length=1)
173
+ carbon_model: AcidSourCarbon | None = None
174
+ other_material_model: AcidSourOtherMaterial | None = None
175
+
176
+
177
+ @calculation("damage/acid-sour-water-assessment", "Acid sour water routed material and corrected corrosion estimate", AcidSourAssessment,
178
+ ["Part2 Annex2.B §10,Tables10.1-10.3,Figure10.1; PDF pp392-396"],origin="api581_composition_with_explicit_source_policies",limitations=[
179
+ "Free-water/pH/chloride routing first. HCl/alkaline/neutral-unclassified routes error rather than fake zero or silently calculate acid-sour rate. Dry branch0 is acid-sour-specific only, with null uncomputed factors. Wet acid branch requires primaryH2S and insignificant ammonia/CO2/cyanides/other-corrosive qualification; no actual mixed-chemistry model. Body quantitativeCS scope versus tablelow-alloy<=6% preserved through explicit low-alloy fraction. Other material requires qualified separate fallback policy. High oxygen/velocity flags localized-attack review; no automatic pit multiplier or wetH2S crackingDF. General thinning source classification is not proof localized attack absent."])
180
+ def assessment(x):
181
+ ensure(bool(x.applicability_basis.strip()),"Acid-sour applicability basis required")
182
+ routed = route(x.screening)
183
+ decision = routed["corrosion_route"]
184
+ if decision == "no_free_water":
185
+ ensure(x.carbon_model is None and x.other_material_model is None and x.total_alloy_mass_fraction is None,"Dry branch cannot carry unused material-rate inputs")
186
+ return {**routed,"corrosion_rate_m_year":0.,"base_corrosion_rate_m_year":None,"ph_for_lookup":None,"ph_edge_row_used":False,
187
+ "oxygen_factor":None,"velocity_factor":None,"localized_attack_review_required":False,"selected_rate_is_screening_bound":False,"default_thinning_type":"general"}
188
+ ensure(decision == "acid_sour_water","Source routes to another or unresolved corrosion mechanism; acid-sour model not evaluated","different_corrosion_mechanism")
189
+ ensure(x.primary_h2s_and_insignificant_other_corrosives_qualified,"Mixed-corrosive scope not qualified for acid-sour model")
190
+ if x.material == "other_material":
191
+ ensure(x.other_material_model is not None and x.carbon_model is None and x.total_alloy_mass_fraction is None,"Other material requires only qualified other-material model")
192
+ return {**routed,**other_material(x.other_material_model),"base_corrosion_rate_m_year":None,"ph_for_lookup":None,"ph_edge_row_used":False,
193
+ "oxygen_factor":None,"velocity_factor":None,"localized_attack_review_required":False,"default_thinning_type":"general"}
194
+ ensure(x.other_material_model is None and x.carbon_model is not None,"Carbon/low-alloy branch requires only carbon rate model")
195
+ if x.material == "low_alloy_steel":
196
+ ensure(x.total_alloy_mass_fraction is not None and x.total_alloy_mass_fraction <= .06,"Table10.1 low-alloy scope requires known total alloy<=6mass%")
197
+ else:
198
+ ensure(x.total_alloy_mass_fraction is None,"Carbon-steel branch cannot carry unused low-alloy fraction")
199
+ m = x.carbon_model
200
+ ensure(bool(m.qualification_basis.strip()),"Lowest aqueous pH/maximum water temperature qualification required")
201
+ base = base_rate(AcidSourBase(ph=x.screening.ph,maximum_water_temperature_k=m.maximum_water_temperature_k,unit_basis=m.unit_basis,
202
+ lookup_policy=m.lookup_policy,ph_edge_policy=m.ph_edge_policy,ph_edge_qualification_basis=m.ph_edge_qualification_basis))
203
+ oxy,vel = oxygen(AcidSourOxygen(oxygen_content_ppb=m.oxygen_content_ppb)),velocity(AcidSourVelocity(velocity_m_s=m.velocity_m_s,equation_basis=m.velocity_equation_basis))
204
+ rate = rate_equation(AcidSourEquation(base_corrosion_rate_m_year=base["base_corrosion_rate_m_year"],oxygen_factor=oxy["oxygen_factor"],velocity_factor=vel["velocity_factor"]))
205
+ return {**routed,**base,**rate,"oxygen_factor":oxy["oxygen_factor"],"velocity_factor":vel["velocity_factor"],
206
+ "localized_attack_review_required":oxy["high_oxygen_localized_review"] or vel["high_velocity_localized_review"],
207
+ "selected_rate_is_screening_bound":False,"default_thinning_type":"general"}
208
+
209
+
210
+ class AcidSourThinning(Model):
211
+ corrosion: AcidSourAssessment
212
+ assessment_distribution: Literal["general","local"]
213
+ localized_attack_review_basis: str | None = None
214
+ starting_thickness_m: Positive
215
+ elapsed_years: NonNegative
216
+ strength: PressureStrength | CodeStrength = Field(discriminator="method")
217
+ inspections: Posterior
218
+ conditions: ThinningConditions
219
+ qualification_basis: str = Field(min_length=1)
220
+
221
+
222
+ @calculation("damage/acid-sour-water-thinning-assessment", "Acid sour water rate through qualified non-AST thinning DF", AcidSourThinning,
223
+ ["Part2 Annex2.B §10; Part2 §4.5.7,Table4.8"],origin="api581_composition_with_explicit_source_policies",limitations=[
224
+ "Unclad non-AST component; same selected rate and thickness drive loss/strength. General source classification versus owner general/local inspection coverage explicit. High oxygen/velocity requires additional localized-attack review basis; no unsupported pit multiplier. Uses sour-water monitoring row with same measured velocity, never unrelatedHCl credit; monitoring source threshold20ft/s differs from acid-sour velocity correction6ft/s threshold. Other-material nominal estimate does not exclude other DFs. No all-mechanism DF, wetH2S cracking, AST, cladding, code/FFS or actual chemistry qualification."])
225
+ def acid_sour_thinning(x):
226
+ corrosion = assessment(x.corrosion)
227
+ if corrosion["localized_attack_review_required"]:
228
+ ensure(x.localized_attack_review_basis is not None and bool(x.localized_attack_review_basis.strip()),"High oxygen/velocity requires qualified localized-attack review before thinning composition")
229
+ else:
230
+ ensure(x.localized_attack_review_basis is None,"Localized-attack review field is unused without source high-attack flag")
231
+ monitor = x.conditions.monitoring.row
232
+ ensure(monitor.mechanism == "sour_water","Acid sour water monitoring must use source sour-water row")
233
+ if x.corrosion.carbon_model is not None:
234
+ ensure(monitor.velocity_m_s == x.corrosion.carbon_model.velocity_m_s,"Sour-water rate and monitoring velocities must agree")
235
+ result = qualified_unclad(QualifiedUnclad(assessment_scope="non_ast_unclad",qualification_basis=x.qualification_basis,
236
+ wall=WallLoss(starting_thickness_m=x.starting_thickness_m,corrosion_rate_m_year=corrosion["corrosion_rate_m_year"],elapsed_years=x.elapsed_years),
237
+ strength=x.strength,inspections=x.inspections,conditions=x.conditions))
238
+ return {**corrosion,**result,"assessment_distribution":x.assessment_distribution}