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.
- api581/__init__.py +80 -0
- api581/_amine_tables.py +105 -0
- api581/_h2sh2_tables.py +161 -0
- api581/_slabx_worker.py +174 -0
- api581/_sulfidic_tables.py +517 -0
- api581/_sulfuric_tables.py +328 -0
- api581/acid_sour_water.py +238 -0
- api581/acscc_screening.py +88 -0
- api581/alkaline_sour_water.py +267 -0
- api581/amine_corrosion.py +277 -0
- api581/amine_screening.py +103 -0
- api581/api/__init__.py +4 -0
- api581/api/api520.py +88 -0
- api581/api/bundle.py +121 -0
- api581/api/consequence.py +102 -0
- api581/api/damage.py +1831 -0
- api581/api/dispersion.py +121 -0
- api581/api/effects.py +318 -0
- api581/api/events.py +96 -0
- api581/api/financial.py +115 -0
- api581/api/frequency.py +40 -0
- api581/api/inspection.py +96 -0
- api581/api/level1.py +238 -0
- api581/api/level2.py +74 -0
- api581/api/planning.py +17 -0
- api581/api/prd.py +190 -0
- api581/api/properties.py +130 -0
- api581/api/release.py +252 -0
- api581/api/reliability.py +40 -0
- api581/api/risk.py +71 -0
- api581/api/tank.py +490 -0
- api581/api520.py +205 -0
- api581/area_contribution.py +72 -0
- api581/blast.py +232 -0
- api581/brittle.py +205 -0
- api581/bundle.py +300 -0
- api581/catalog.py +442 -0
- api581/caustic_screening.py +103 -0
- api581/clad_thinning.py +146 -0
- api581/clscc_screening.py +55 -0
- api581/co2_corrosion.py +313 -0
- api581/co2_screening.py +205 -0
- api581/consequences.py +250 -0
- api581/contracts.py +1038 -0
- api581/cooling_selection.py +204 -0
- api581/cooling_water.py +305 -0
- api581/core.py +126 -0
- api581/corrosion_rate.py +38 -0
- api581/cost_tables.py +175 -0
- api581/cracking.py +74 -0
- api581/data/coverage.json +21361 -0
- api581/data/digital-twin.schema.json +538 -0
- api581/data/direct-test-queue.json +7 -0
- api581/data/native-engine-evidence.json +44 -0
- api581/data/python-api.schema.json +92706 -0
- api581/data/reference-fixture-evidence.json +2956 -0
- api581/data/scope-boundaries.schema.json +58 -0
- api581/data/software-test-evidence.json +4278 -0
- api581/data/standard-metadata.json +1328 -0
- api581/data/standard.schema.json +233 -0
- api581/data/supporting-text-review.json +1319 -0
- api581/digital_twin.py +491 -0
- api581/dispersion.py +45 -0
- api581/effects.py +232 -0
- api581/evaporation.py +181 -0
- api581/evidence.py +81 -0
- api581/external_corrosion.py +148 -0
- api581/external_cracking.py +63 -0
- api581/fatigue.py +56 -0
- api581/fireball.py +163 -0
- api581/flash_pool.py +111 -0
- api581/flash_solver.py +122 -0
- api581/groundwater.py +101 -0
- api581/h2sh2_corrosion.py +128 -0
- api581/hic_screened.py +89 -0
- api581/htha.py +45 -0
- api581/hydrochloric_acid.py +272 -0
- api581/hydrofluoric_acid.py +236 -0
- api581/ignition.py +204 -0
- api581/inspection_effectiveness.py +601 -0
- api581/jet_handoff.py +60 -0
- api581/level1.py +311 -0
- api581/level1_summary.py +35 -0
- api581/level1_toxic.py +313 -0
- api581/level1_type1.py +91 -0
- api581/level2_summary.py +237 -0
- api581/lining.py +173 -0
- api581/native_toxic.py +67 -0
- api581/outputs.py +5672 -0
- api581/oxidation.py +145 -0
- api581/pascc_screening.py +78 -0
- api581/physical_explosion.py +190 -0
- api581/pof.py +207 -0
- api581/pool_fire.py +273 -0
- api581/portfolio.py +68 -0
- api581/prd.py +204 -0
- api581/prd_consequences.py +245 -0
- api581/prd_history.py +44 -0
- api581/py.typed +0 -0
- api581/release.py +307 -0
- api581/representative_fluids.py +172 -0
- api581/result.py +50 -0
- api581/risk.py +191 -0
- api581/saturation.py +66 -0
- api581/scc_mechanisms.py +242 -0
- api581/schema.py +45 -0
- api581/slabx_adapter.py +261 -0
- api581/soil_corrosion.py +191 -0
- api581/source_partition.py +169 -0
- api581/spatial.py +87 -0
- api581/ssc_screening.py +77 -0
- api581/standard.py +281 -0
- api581/storage.py +42 -0
- api581/sulfidic_corrosion.py +197 -0
- api581/sulfuric_acid.py +209 -0
- api581/tank.py +260 -0
- api581/tank_corrosion.py +256 -0
- api581/tank_environment.py +186 -0
- api581/tank_financial.py +202 -0
- api581/tank_integrated.py +51 -0
- api581/tank_shell.py +141 -0
- api581/tank_source.py +152 -0
- api581/tank_thinning.py +204 -0
- api581/temper.py +105 -0
- api581/thinning.py +172 -0
- api581/thinning_conditions.py +212 -0
- api581/thinning_history.py +118 -0
- api581/thinning_selection.py +81 -0
- api581/thinning_type.py +34 -0
- api581/toxic_criteria.py +111 -0
- api581/toxic_level2.py +233 -0
- api581/two_phase_events.py +120 -0
- api581/workflow.py +42 -0
- api581_engine-0.1.4.dist-info/METADATA +419 -0
- api581_engine-0.1.4.dist-info/RECORD +139 -0
- api581_engine-0.1.4.dist-info/WHEEL +5 -0
- api581_engine-0.1.4.dist-info/licenses/LICENSE +161 -0
- api581_engine-0.1.4.dist-info/licenses/NOTICE.md +18 -0
- api581_engine-0.1.4.dist-info/top_level.txt +1 -0
api581/corrosion_rate.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Measured corrosion-rate primitives from API 581 thinning guidance."""
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from .core import Model, Positive, NonNegative, calculation, ensure
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MeasuredCorrosionRate(Model):
|
|
9
|
+
initial_thickness_m: Positive
|
|
10
|
+
current_thickness_m: Positive
|
|
11
|
+
elapsed_years: Positive
|
|
12
|
+
measurement_basis: str = Field(min_length=1, max_length=1000)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@calculation(
|
|
16
|
+
"damage/measured-corrosion-rate",
|
|
17
|
+
"Measured thickness-loss corrosion rate",
|
|
18
|
+
MeasuredCorrosionRate,
|
|
19
|
+
["Part 2 Annex B §1.1-§1.2; Part 2 §4.5.5, PDF pp143-152"],
|
|
20
|
+
origin="api581_measured_thickness_loss_rate",
|
|
21
|
+
limitations=[
|
|
22
|
+
"Computes the direct constant-rate interpretation (initial thickness minus current thickness divided by elapsed time). It does not select inspection history, uncertainty distributions, mechanism applicability or a future thinning rate.",
|
|
23
|
+
"Thickness increase is rejected as a measurement/baseline inconsistency rather than silently converted to zero corrosion. The caller must qualify comparable locations, units, dates and measurement quality."
|
|
24
|
+
],
|
|
25
|
+
)
|
|
26
|
+
def measured_corrosion_rate(x):
|
|
27
|
+
ensure(x.current_thickness_m <= x.initial_thickness_m,
|
|
28
|
+
"Current thickness cannot exceed the qualified initial thickness", "inconsistent_thickness_baseline")
|
|
29
|
+
loss = x.initial_thickness_m - x.current_thickness_m
|
|
30
|
+
return {
|
|
31
|
+
"metal_loss_m": loss,
|
|
32
|
+
"corrosion_rate_m_year": loss / x.elapsed_years,
|
|
33
|
+
"initial_thickness_m": x.initial_thickness_m,
|
|
34
|
+
"current_thickness_m": x.current_thickness_m,
|
|
35
|
+
"elapsed_years": x.elapsed_years,
|
|
36
|
+
"measurement_basis": x.measurement_basis,
|
|
37
|
+
"rate_basis": "direct_two_thickness_difference",
|
|
38
|
+
}
|
api581/cost_tables.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Part 3 Tables 4.15-4.18M and associated financial calculations."""
|
|
2
|
+
import math
|
|
3
|
+
from typing import Literal
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
from .core import Model, Positive, NonNegative, Fraction, calculation, ensure
|
|
6
|
+
from .pof import Component, generic, HOLES
|
|
7
|
+
|
|
8
|
+
REPAIR = {
|
|
9
|
+
"COMPC":(10000,20000,100000,300000),"COMPR":(5000,10000,50000,100000),
|
|
10
|
+
"HEXSS":(1000,2000,20000,60000),"HEXTS":(1000,2000,20000,60000),"HEXTUBE":(1000,2000,20000,60000),
|
|
11
|
+
"PIPE-1":(5,0,0,20),"PIPE-2":(5,0,0,40),"PIPE-4":(5,10,0,60),"PIPE-6":(5,20,0,120),
|
|
12
|
+
"PIPE-8":(5,30,60,180),"PIPE-10":(5,40,80,240),"PIPE-12":(5,60,120,360),"PIPE-16":(5,80,160,500),"PIPEGT16":(10,120,240,700),
|
|
13
|
+
"PUMP2S":(1000,2500,5000,5000),"PUMP1S":(1000,2500,5000,5000),"PUMPR":(1000,2500,5000,10000),
|
|
14
|
+
"TANKBOTTOM":(5000,0,0,120000),"COURSE-1-10":(5000,12000,20000,40000),
|
|
15
|
+
"KODRUM":(5000,12000,20000,40000),"DRUM":(5000,12000,20000,40000),"FINFAN":(1000,2000,20000,60000),
|
|
16
|
+
"FILTER":(1000,2000,4000,10000),"REACTOR":(10000,24000,40000,80000),
|
|
17
|
+
"COLTOP":(10000,25000,50000,100000),"COLMID":(10000,25000,50000,100000),"COLBTM":(10000,25000,50000,100000)}
|
|
18
|
+
|
|
19
|
+
MATERIAL = {"carbon_steel":1,"1.25Cr-0.5Mo":1.3,"2.25Cr-1Mo":1.7,"5Cr-0.5Mo":1.7,"7Cr-0.5Mo":2,
|
|
20
|
+
"clad_304_ss":2.1,"polypropylene_lined":2.5,"9Cr-1Mo":2.6,"405_ss":2.8,"410_ss":2.8,"304_ss":3.2,
|
|
21
|
+
"clad_316_ss":3.3,"cs_saran_lined":3.4,"cs_rubber_lined":4.4,"316_ss":4.8,"cs_glass_lined":5.8,"clad_alloy_400":6.4,
|
|
22
|
+
"90_10_CuNi":6.8,"clad_alloy_600":7,"cs_teflon_lined":7.8,"clad_nickel":8,"alloy_800":8.4,"70_30_CuNi":8.5,
|
|
23
|
+
"904L":8.8,"alloy_20":11,"alloy_400":15,"alloy_600":15,"nickel":18,"alloy_625":26,"titanium":28,
|
|
24
|
+
"alloy_C":29,"zirconium":34,"alloy_B":36,"tantalum":535}
|
|
25
|
+
|
|
26
|
+
OUTAGE = {"COMPC":(None,3,7,None),"COMPR":(None,3,7,None),"HEXSS":(2,3,3,10),"HEXTS":(2,3,3,10),
|
|
27
|
+
"HEXTUBE":(None,None,None,None),"PIPE-1":(0,None,None,1),"PIPE-2":(0,None,None,1),"PIPE-4":(0,1,None,2),
|
|
28
|
+
"PIPE-6":(0,1,2,3),"PIPE-8":(0,2,2,3),"PIPE-10":(0,2,2,4),"PIPE-12":(1,3,4,4),"PIPE-16":(1,3,4,5),"PIPEGT16":(1,4,5,7),
|
|
29
|
+
"PUMP2S":(0,0,0,None),"PUMPR":(0,0,0,None),"PUMP1S":(0,0,0,None),"TANKBOTTOM":(5,None,None,50),
|
|
30
|
+
"COURSE-1-10":(2,3,3,14),"KODRUM":(2,3,3,10),"FINFAN":(0,0,2,3),"FILTER":(0,1,2,3),"DRUM":(2,3,3,10),
|
|
31
|
+
"REACTOR":(4,6,6,21),"COLTOP":(3,4,5,21),"COLMID":(3,4,5,21),"COLBTM":(3,4,5,21)}
|
|
32
|
+
|
|
33
|
+
# MW kg/kmol, tabulated liquid density kg/m3, NBP Celsius, fraction evaporated in 24h.
|
|
34
|
+
LEAK = {"C1-C2":(23,250.513,-125,1),"C3-C5":(58,580.012,-1,1),"C6-C8":(100,684.020,99,.9),
|
|
35
|
+
"C9-C12":(149,734.014,184,.5),"C13-C16":(205,764.529,261,.1),"C17-C25":(280,775.021,344,.05),"C25+":(422,900.029,527,.02),
|
|
36
|
+
"Acid":(18,997.950,100,.9),"H2":(2,71.010,-253,1),"H2S":(34,993.032,-59,1),"HF":(20,967.034,20,1),
|
|
37
|
+
"CO":(28,800.923,-191,1),"DEE":(74,720.831,35,1),"HCL":(36,1185.366,-85,1),"Nitric Acid":(63,1521.754,121,.8),
|
|
38
|
+
"NO2":(90,929.071,135,.75),"Phosgene":(99,1377.588,83,1),"TDI":(174,1217.403,251,.15),"Methanol":(32,800.923,65,1),
|
|
39
|
+
"PO":(58,832.960,34,1),"Styrene":(104,683.988,145,.6),"EEA":(132,977.126,156,.65),"EE":(90,929.071,135,.75),
|
|
40
|
+
"EG":(62,1105.274,197,.45),"EO":(44,881.015,0,1)}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@calculation("financial/repair-table", "2001 USD component repair cost table", Component,
|
|
44
|
+
["Part 3 Table 4.15; PDF p532"], limitations=["Historical 2001 USD, not current prices. COURSE-1-10 normalizes source COURSES-10 label."])
|
|
45
|
+
def repair_table(x):
|
|
46
|
+
ensure(x.component_type in REPAIR,"Unsupported repair cost component")
|
|
47
|
+
return {"cost_by_hole_usd_2001":dict(zip(HOLES,REPAIR[x.component_type])),"cost_basis_year":2001,"currency":"USD"}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Material(Model):
|
|
51
|
+
material_id: Literal[tuple(MATERIAL)]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@calculation("financial/material-factor", "Material repair cost multiplier", Material,["Part 3 Table 4.16; PDF p533"])
|
|
55
|
+
def material_factor(x):
|
|
56
|
+
ensure(x.material_id in MATERIAL,"Unsupported material_id; inspect MODULE_CATALOG or table constants")
|
|
57
|
+
return {"material_factor":MATERIAL[x.material_id]}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@calculation("financial/outage-table", "Estimated equipment outage table", Component,
|
|
61
|
+
["Part 3 Table 4.17; PDF p534"], limitations=["NA is returned as null, never zero. Historical estimates require owner review."])
|
|
62
|
+
def outage_table(x):
|
|
63
|
+
ensure(x.component_type in OUTAGE,"Unsupported outage component")
|
|
64
|
+
return {"days_by_hole":dict(zip(HOLES,OUTAGE[x.component_type]))}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RepairCost(Model):
|
|
68
|
+
component_type: str
|
|
69
|
+
material_id: Literal[tuple(MATERIAL)]
|
|
70
|
+
price_factor_since_2001: Positive
|
|
71
|
+
usd_to_output_currency: Positive
|
|
72
|
+
cost_basis_year: int = Field(ge=2001,le=2200)
|
|
73
|
+
currency: str = Field(pattern=r"^[A-Z]{3}$")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@calculation("financial/component-repair", "GFF-weighted material-adjusted component repair cost", RepairCost,
|
|
77
|
+
["Part 3 eq (3.86), Tables 4.15/4.16"], limitations=["Inflation and currency conversion are explicit caller assumptions, not fetched market data. Non-tank GFF support only."])
|
|
78
|
+
def repair_cost(x):
|
|
79
|
+
gff=generic(Component(component_type=x.component_type))
|
|
80
|
+
costs=repair_table(Component(component_type=x.component_type))["cost_by_hole_usd_2001"]
|
|
81
|
+
factor=material_factor(Material(material_id=x.material_id))["material_factor"]
|
|
82
|
+
base=math.fsum(gff["by_hole_per_year"][h]*costs[h] for h in HOLES)/gff["total_per_year"]*factor
|
|
83
|
+
return {"weighted_repair_cost_usd_2001":base,"cost":base*x.price_factor_since_2001*x.usd_to_output_currency,
|
|
84
|
+
"material_factor":factor,"cost_basis_year":x.cost_basis_year,"currency":x.currency}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ComponentOutage(Model):
|
|
88
|
+
component_type: str
|
|
89
|
+
outage_multiplier: Positive
|
|
90
|
+
override_days: dict[Literal["small","medium","large","rupture"],NonNegative] = Field(default_factory=dict)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@calculation("financial/component-outage", "GFF-weighted component outage duration", ComponentOutage,
|
|
94
|
+
["Part 3 eq (3.88), Table 4.17"], limitations=["A nonzero-frequency hole with NA outage requires an explicit owner-supplied override; NA is not silently filled."])
|
|
95
|
+
def component_outage(x):
|
|
96
|
+
gff=generic(Component(component_type=x.component_type))
|
|
97
|
+
days=outage_table(Component(component_type=x.component_type))["days_by_hole"]
|
|
98
|
+
days.update(x.override_days)
|
|
99
|
+
terms=[]
|
|
100
|
+
for h in HOLES:
|
|
101
|
+
frequency=gff["by_hole_per_year"][h]
|
|
102
|
+
ensure(days[h] is not None or frequency==0,f"Outage NA for nonzero-frequency hole {h}; supply override_days", "missing_outage_override")
|
|
103
|
+
terms.append(frequency*(days[h] if days[h] is not None else 0))
|
|
104
|
+
return {"component_outage_days":math.fsum(terms)/gff["total_per_year"]*x.outage_multiplier,"effective_days_by_hole":days}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class SurroundingOutage(Model):
|
|
108
|
+
surrounding_damage_cost_usd: NonNegative
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@calculation("financial/surrounding-outage", "Surrounding equipment repair outage correlation", SurroundingOutage,
|
|
112
|
+
["Part 3 eq (3.89); PDF p506"], limitations=["USD cost input only; correlation monetary basis must be consistent with the source methodology. Zero damage cost returns the continuous limit 0 days."])
|
|
113
|
+
def surrounding_outage(x):
|
|
114
|
+
# Algebraic power form equals 10**(1.242+.585*log10(cost*1e-6)), and handles cost=0.
|
|
115
|
+
return {"surrounding_outage_days":10**1.242*(x.surrounding_damage_cost_usd/1e6)**.585}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class LeakProperties(Model):
|
|
119
|
+
fluid: Literal[tuple(LEAK)]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@calculation("properties/leak-table", "Tabulated SI leak/remediation properties", LeakProperties,
|
|
123
|
+
["Part 3 Table 4.18M; PDF p536"], limitations=["Table liquid density for remediation, not operating gas density or a thermodynamic property solver. Source C3-C5 label is retained and not silently aliased to C3-C4."])
|
|
124
|
+
def leak_properties(x):
|
|
125
|
+
ensure(x.fluid in LEAK,"Unsupported leak-properties fluid")
|
|
126
|
+
mw,density,nbp,evap=LEAK[x.fluid]
|
|
127
|
+
return {"molecular_weight_kg_kmol":mw,"tabulated_liquid_density_kg_m3":density,"normal_boiling_point_k":nbp+273.15,
|
|
128
|
+
"evaporated_fraction_24h":evap}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class CleanupEligibility(Model):
|
|
132
|
+
release_phase: Literal["gas","liquid"]
|
|
133
|
+
normal_boiling_point_k: Positive
|
|
134
|
+
autoignition_likely: bool
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@calculation("financial/cleanup-eligibility", "Level 1 environmental cleanup screening", CleanupEligibility,
|
|
138
|
+
["Part 3 §4.12.6"], limitations=["Uses the text's rounded 93 Celsius threshold; source also states 200 Fahrenheit. Screening only; no cost or evaporation-rate model."])
|
|
139
|
+
def cleanup_eligibility(x):
|
|
140
|
+
eligible=x.release_phase=="liquid" and x.normal_boiling_point_k>=366.15 and not x.autoignition_likely
|
|
141
|
+
return {"include_cleanup_cost":eligible}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class FinancialConsequence(Model):
|
|
145
|
+
repair: RepairCost
|
|
146
|
+
outage: ComponentOutage
|
|
147
|
+
equipment_area_m2: NonNegative
|
|
148
|
+
personnel_area_m2: NonNegative
|
|
149
|
+
equipment_cost_per_m2: NonNegative
|
|
150
|
+
production_cost_per_day: NonNegative
|
|
151
|
+
population_density_per_m2: NonNegative
|
|
152
|
+
cost_per_injury: NonNegative
|
|
153
|
+
weighted_cleanup_volume_m3: NonNegative
|
|
154
|
+
cleanup_cost_per_m3: NonNegative
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@calculation("financial/level1-consequence", "Compose five Level 1 financial consequence categories", FinancialConsequence,
|
|
158
|
+
["Part 3 §§4.12.2-4.12.7 eqs (3.85)-(3.94)"],
|
|
159
|
+
limitations=["Input areas and remediation volume must already be hole-weighted and screened. All user monetary inputs use repair.currency and repair.cost_basis_year. Surrounding-outage correlation receives equivalent 2001 USD via the explicit price/currency factors."])
|
|
160
|
+
def financial_consequence(x):
|
|
161
|
+
ensure(x.repair.component_type==x.outage.component_type,"Repair and outage component types must match")
|
|
162
|
+
repair=repair_cost(x.repair)
|
|
163
|
+
outage=component_outage(x.outage)
|
|
164
|
+
surrounding=x.equipment_area_m2*x.equipment_cost_per_m2
|
|
165
|
+
reference_cost=surrounding/x.repair.usd_to_output_currency/x.repair.price_factor_since_2001
|
|
166
|
+
downtime=surrounding_outage(SurroundingOutage(surrounding_damage_cost_usd=reference_cost))["surrounding_outage_days"]
|
|
167
|
+
production=(outage["component_outage_days"]+downtime)*x.production_cost_per_day
|
|
168
|
+
injury=x.personnel_area_m2*x.population_density_per_m2*x.cost_per_injury
|
|
169
|
+
environment=x.weighted_cleanup_volume_m3*x.cleanup_cost_per_m3
|
|
170
|
+
return {"component_repair_cost":repair["cost"],"surrounding_equipment_cost":surrounding,
|
|
171
|
+
"business_interruption_cost":production,"injury_cost":injury,"environmental_cost":environment,
|
|
172
|
+
"component_outage_days":outage["component_outage_days"],"surrounding_outage_days":downtime,
|
|
173
|
+
"surrounding_cost_reference_usd_2001":reference_cost,
|
|
174
|
+
"financial_consequence":math.fsum([repair["cost"],surrounding,production,injury,environment]),
|
|
175
|
+
"currency":x.repair.currency,"cost_basis_year":x.repair.cost_basis_year}
|
api581/cracking.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""SCC base table and caustic escalation; susceptibility screening is external."""
|
|
2
|
+
from typing import Literal
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from .core import Model, NonNegative, calculation, ensure
|
|
5
|
+
|
|
6
|
+
# Columns are D,C,B,A for each of 1..6 inspections. PDF p175 visually checked.
|
|
7
|
+
SCC = {
|
|
8
|
+
0: [(0, 0, 0, 0)] * 6,
|
|
9
|
+
1: [(1, 1, 1, 1)] * 6,
|
|
10
|
+
10: [(8, 3, 1, 1), (6, 2, 1, 1), (4, 1, 1, 1), (2, 1, 1, 1), (1, 1, 1, 1), (1, 1, 1, 1)],
|
|
11
|
+
50: [(40, 17, 5, 3), (30, 10, 2, 1), (20, 5, 1, 1), (10, 2, 1, 1), (5, 1, 1, 1), (1, 1, 1, 1)],
|
|
12
|
+
100: [(80, 33, 10, 5), (60, 20, 4, 1), (40, 10, 2, 1), (20, 5, 1, 1), (10, 2, 1, 1), (5, 1, 1, 1)],
|
|
13
|
+
500: [(400, 170, 50, 25), (300, 100, 20, 5), (200, 50, 8, 1), (100, 25, 2, 1), (50, 10, 1, 1), (25, 5, 1, 1)],
|
|
14
|
+
1000: [(800, 330, 100, 50), (600, 200, 40, 10), (400, 100, 16, 2), (200, 50, 5, 1), (100, 25, 2, 1), (50, 10, 1, 1)],
|
|
15
|
+
5000: [(4000, 1670, 500, 250), (3000, 1000, 250, 50), (2000, 500, 80, 10), (1000, 250, 25, 2), (500, 125, 5, 1), (250, 50, 2, 1)],
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SccBase(Model):
|
|
20
|
+
severity_index: Literal[0, 1, 10, 50, 100, 500, 1000, 5000]
|
|
21
|
+
inspection_grade: Literal["A", "B", "C", "D", "E"]
|
|
22
|
+
inspection_count: int = Field(ge=0, le=6)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@calculation("damage/scc-base", "SCC base damage factor lookup", SccBase,
|
|
26
|
+
["Part 2 Table 6.3; PDF p175"],
|
|
27
|
+
limitations=["Severity and highest equivalent inspection category supplied by caller. Count >6 is not extrapolated. Ambiguous E/500 cell is rejected."])
|
|
28
|
+
def scc_base(x):
|
|
29
|
+
if x.inspection_grade == "E":
|
|
30
|
+
ensure(x.inspection_count == 0, "E means no effective inspections; count must be 0")
|
|
31
|
+
ensure(x.severity_index != 500, "Published E/500 cell is ambiguous; requires source reconciliation", "ambiguous_source")
|
|
32
|
+
value = x.severity_index
|
|
33
|
+
else:
|
|
34
|
+
ensure(x.inspection_count >= 1, "A-D lookup requires at least one inspection")
|
|
35
|
+
value = SCC[x.severity_index][x.inspection_count - 1]["DCBA".index(x.inspection_grade)]
|
|
36
|
+
return {"base_damage_factor": value}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CausticSeverity(Model):
|
|
40
|
+
susceptibility: Literal["none", "low", "medium", "high"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@calculation("damage/caustic-severity", "Caustic cracking severity index", CausticSeverity,
|
|
44
|
+
["Part 2 Table 6.2; PDF p174"], limitations=["This low-level severity lookup accepts qualified susceptibility; use damage/caustic-screening, damage/caustic-figure-susceptibility or damage/caustic-screened-assessment for the implemented material/exposure and Figure 6.2 routing. It does not infer applicability from chemistry."])
|
|
45
|
+
def caustic_severity(x):
|
|
46
|
+
return {"severity_index": {"none": 0, "low": 50, "medium": 500, "high": 5000}[x.susceptibility]}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class CausticEscalation(Model):
|
|
50
|
+
base_damage_factor: NonNegative
|
|
51
|
+
years_since_qualifying_inspection: NonNegative
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@calculation("damage/caustic-escalation", "Caustic cracking time escalation", CausticEscalation,
|
|
55
|
+
["Part 2 §6.6.3 eq (2.25); PDF p173"],
|
|
56
|
+
limitations=["Age must follow qualifying A/B/C inspection or repair criteria; detected unrepaired cracks require a separate FFS evaluation."])
|
|
57
|
+
def caustic_escalation(x):
|
|
58
|
+
return {"damage_factor": x.base_damage_factor * max(x.years_since_qualifying_inspection, 1) ** 1.1}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class LiningAdjustment(Model):
|
|
62
|
+
base_damage_factor: NonNegative
|
|
63
|
+
condition: Literal["good", "average", "poor"]
|
|
64
|
+
effective_online_monitoring: bool
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@calculation("damage/lining-adjust", "Lining condition and monitoring adjustment", LiningAdjustment,
|
|
68
|
+
["Part 2 eq (2.24), Table 5.6; PDF pp167,171"],
|
|
69
|
+
limitations=["Base DF supplied by caller; lining-type/age Tables 5.4/5.5 not selected automatically."])
|
|
70
|
+
def lining_adjust(x):
|
|
71
|
+
condition = {"good": 1, "average": 2, "poor": 10}[x.condition]
|
|
72
|
+
monitoring = .1 if x.effective_online_monitoring else 1
|
|
73
|
+
return {"condition_factor": condition, "monitoring_factor": monitoring,
|
|
74
|
+
"lining_damage_factor": x.base_damage_factor * condition * monitoring}
|