Metadata-Version: 2.5
Name: hazus-loss
Version: 1.0.0
Summary: Traceable Hazus 6.1 floorwise loss assessment for a single earthquake
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# hazus-loss

基于 HAZUS 6.1 的单次地震逐层条件期望损失评估程序。结构和非结构构件均逐层计算，之后叠加金额与伤亡期望人数；不进行蒙特卡洛模拟。

## 安装与示例

从 PyPI 安装（`1.0.0` 发布后可用；Python 3.10 及以上）：

```powershell
pip install hazus-loss==1.0.0
```

发行包名为 `hazus-loss`，Python 导入名为 `hazus_loss`。从源码开发和运行示例：

```powershell
uv sync --group dev
uv run python -X utf8 examples/single_event.py
uv run pytest -q
```

`uv run python -X utf8 main.py` 运行相同示例。示例会打印重置成本与损失，生成 `output/single_event.html` 网页报告。示例费用均为调用脚本给定的演示数值，正式计算时应替换为项目数据。`-X utf8` 避免 Windows 重定向输出时的中文编码差异。

## 在脚本中给定费用并计算

所有费用由调用者明确给定，不读取用途费用预设。**整栋建筑重置成本**通过 `BuildingInput.replacement_cost` 一次性传入，需在调用 `evaluate_loss` 前计算好。程序不根据面积、单位价或支撑价格推算重置成本，仅按成本权重分摊到各层。每层内容物价值也由调用者明确给定。

```python
from hazus_loss import (
    BuildingInput, CasualtyUnitCosts, CollapseFragility, DemolitionFragility, EconomicParameters,
    FloorInput, evaluate_loss, print_loss_summary,
)

# 以下是演示费用，正式使用时替换为项目数据。
economics = EconomicParameters(
    rent_per_m2_day=0.5,
    disruption_cost_per_m2=2.0,
    owner_occupied_fraction=0.6,
    income_per_m2_day=3.0,
    recapture_fraction=0.2,
    occupants_per_m2=0.1,
    casualty_unit_costs=CasualtyUnitCosts(
        severity_2=100.0, severity_3=1000.0, severity_4=10000.0,
    ),
)

areas_m2 = [1200, 1200, 1000, 800]
peak_drifts = [0.008, 0.012, 0.010, 0.006]
accelerations_g = [0.35, 0.45, 0.60, 0.75]

# 重置成本在调用程序之前计算。可按自己的计价方式包含结构、非结构及支撑。
unit_cost = 2000.0
brace_costs = [50000, 50000, 50000, 50000]
building_replacement_cost = sum(area * unit_cost + brace
                                for area, brace in zip(areas_m2, brace_costs, strict=True))
contents_values = [600000, 600000, 500000, 400000]

floors = [
    FloorInput(
        area_m2=area,
        peak_drift=drift, peak_floor_acceleration_g=acceleration,
        contents_value=contents,
    )
    for area, drift, acceleration, contents in zip(
        areas_m2, peak_drifts, accelerations_g,
        contents_values, strict=True,
    )
]
building = BuildingInput(
    structural_type="S2M", design_level="high", occupancy="COM4",
    floors=floors, max_residual_drift=0.004,
    demolition_fragility=DemolitionFragility(median_residual_drift=0.005, beta=0.3),
    collapse_fragility=CollapseFragility(median_peak_drift=0.05, beta=0.4),
    economics=economics,
    replacement_cost=building_replacement_cost,
    # 不传则各层成本权重相同。可选：
    # floor_cost_weights=[0.3, 0.3, 0.2, 0.2],
)

# 不传 output_path 时，只计算并返回结果，不创建文件。
result = evaluate_loss(building, output_path="output/building_report.html")
print_loss_summary(result)
print(result.replacement_cost)  # 调用者传入的整栋重置成本
snapshot = result.to_dict()
```

输入单位为：位移角小数（`0.01` 表示 `1%`）、峰值绝对楼面加速度 g、面积 m²、租金与收入为金额/(m²·日)、人员密度为人/m²。`beta` 是自然对数空间标准差。楼层从底层向顶层排列，每层记录对应其顶部楼面的绝对加速度。

`FloorInput.economics` 可覆盖该层费用与人员参数；`occupants` 可直接给该层人数，否则按面积与明确给定的人员密度计算。所有金额由调用者在输入前统一计价单位与价格基础，程序不接收币种、价格年份或费用来源说明，也不进行换算。楼层按输入顺序自动标识为 `1F`、`2F` 等，无需填写名称。费用参数不依赖 `COM4`，对数据库支持的其他用途也使用同一输入方式。

`COM4` 是建筑用途，只用于查询损失比例、恢复时间等 HAZUS 参数。结构类型（例如 `S2M`）和设计等级必须另行提供。

## 楼层成本占比权重

默认每层权重均为 `1 / 楼层数`，与面积无关。可在 `BuildingInput` 中传入 `floor_cost_weights`，例如四层建筑使用 `[0.3, 0.3, 0.2, 0.2]`。权重按楼层输入顺序排列，必须与楼层数量一致，每项为有限非负数，合计为 1（浮点容差 `1e-10`）；不符合要求时报错，不自动将任意权重归一化。

每层计损用的重置成本为“整栋重置成本 × 该层权重”，分摊金额之和保持整栋输入总额。结果中的 `floor.cost_weight` 与 `floor.replacement_cost` 分别为实际权重及分摊金额。该权重只用于分摊建筑重置成本，不修改楼层面积、内容物价值、人数或其他明确给定的费用。

## 网页报告与打印输出

`evaluate_loss(building, output_path=...)` 的 `output_path` 为可选参数：

- 传入 `.html` 或 `.htm` 文件路径时写入该文件；父目录自动创建。
- 传入目录（包括尚不存在且没有扩展名的路径）时生成其中的 `index.html`。
- 不传或传 `None` 时不生成文件。

返回结果的 `report_path` 为报告绝对路径。报告是独立 UTF-8 HTML 文件，内嵌图表和样式，不依赖联网或额外图片文件，包含：

- 建筑总重置成本、期望损失及损失/重置成本比例。
- 八类损失金额和占比条形图，以及各楼层的损失分类对比图。
- 建筑用途、结构分类、设计等级、地震响应、楼层成本权重、分摊重置成本与内容物价值。
- 各层实际使用的费用、人员及易损性参数。
- 损伤、倒塌、拆除、伤亡结果及完整可核查的 JSON 快照。

`print_loss_summary(result)` 显式打印整栋重置成本和各层分摊金额、八类损失、合计、伤亡期望人数和报告路径。计算函数本身不隐式打印。已有结果也可通过 `write_html_report(result, "output/another.html")` 单独输出报告。

## 结果字段

| 字段 | 含义 |
|---|---|
| `result.replacement_cost` | 调用者直接输入的整栋重置成本 |
| `result.contents_value` | 各层输入内容物价值之和 |
| `result.losses.structural_repair` | 三种整栋分支加权后的结构损失 |
| `result.losses.nonstructural_drift_repair` | 位移敏感非结构损失 |
| `result.losses.nonstructural_acceleration_repair` | 加速度敏感非结构损失 |
| `result.losses.contents` | 内容物损失 |
| `result.losses.relocation` | 搬迁费用 |
| `result.losses.income` | 营业收入损失 |
| `result.losses.rental` | 租金收入损失 |
| `result.losses.casualties` | 严重度 2/3/4 的伤亡货币损失 |
| `result.losses.total` | 上述八项之和 |
| `result.losses.direct` | 结构、两类非结构及内容物损失之和 |

`result.floors` 仅包含“整栋未倒塌且未拆除”分支的逐层计算：`conditional_losses` 为给定该分支时的条件损失，`losses` 为乘以整栋分支概率后的贡献；不再有楼层倒塌或拆除概率。`result.casualties` 是三种整栋分支加权后的分级人数与金额。`to_dict()` 包含输入快照、实际参数及 HAZUS 来源，可直接 JSON 序列化。

## 整栋倒塌、拆除与逐层计算

`CollapseFragility(median_peak_drift=..., beta=...)` 必须由用户提供。中值为小数位移角，beta 是自然对数标准差。倒塌需求取所有楼层 `peak_drift` 的最大值，不取平均、不按楼层独立判定，也不再使用 HAZUS 完全损伤条件倒塌比例。

三个互斥的整栋分支为：

- `Pc = collapse_probability(max(peak_drifts), collapse_fragility)`。
- `q = demolition_probability(max_residual_drift, demolition_fragility)`，表示未倒塌条件下的拆除概率。
- 整栋拆除概率 `Pd = (1-Pc)*q`。
- 未倒塌且未拆除概率 `Pr = (1-Pc)*(1-q)`；`Pc + Pd + Pr = 1`。

只有 `Pr > 0` 时才进行 HAZUS 逐层损伤与损失计算，所有楼层使用相同的 Pr。若 `Pr == 0`，跳过逐层易损性查询和计算，楼层 `damage`、`conditional_losses` 为 `None`，楼层 `losses` 为零。

整栋总损失为 `Pc*L_collapse + Pd*L_demolition + Pr*sum(L_floor_given_no_event)`，不会将倒塌/拆除损失重复计入各层。

整栋倒塌和拆除分支采用建筑完全重置损失，结构与两类非结构完全损伤比例之和为 1；内容物损失采用 HAZUS 完全损伤比例（50%），间接损失采用完全损伤恢复时间。倒塌伤亡按全楼人员和倒塌伤亡率计算；拆除分支不新增伤亡。无倒塌、无拆除分支中的结构完全损伤伤亡率使用“未倒塌完全损伤”一列，不再二次划分倒塌。

结果通过 `result.events` 返回整栋概率，通过 `result.branches['collapse'/'demolition'/'repair']` 返回各分支的条件损失、加权贡献与伤亡。网页及终端将整栋事件概率和逐层贡献分开显示。

## HAZUS 参数查询

```python
from hazus_loss import HazusDatabase

db = HazusDatabase()
print(db.list_structural_types())
print(db.list_occupancies())
print(db.list_design_levels())
print(db.get_fragility("S2M", "high", "structural").to_dict())
print(db.get_consequence("COM4", "recovery_days").to_dict())
# db.get_collapse("S2M") 仅查询历史 HAZUS 表值，当前评估不使用该比例。
```

数据库包含一般建筑模型的 36 种结构、33 种用途、六个等级：`severe`、`very_high`、`high`、`moderate`、`low`、`pre`，共 648 组易损性。高度分类按整栋层数选择（例如 S2L 为1–3层、S2M 为4–7层、S2H 为8层及以上），不会将每层都改为低层类型。原表不适用组合保留并在计算时拒绝使用，可通过 `get_fragility(..., allow_unavailable=True)` 审查原始记录。

易损性采用 HAZUS 表中总对数标准差。不同损伤状态的曲线可能在尾部交叉：负概率不超过 `1e-12` 时进行数值修正并写入该层诊断，实质性交叉抛出 `FragilityCrossingError`。其他输入与查询错误分别为 `InputValidationError`、`ParameterLookupError`、`UnsupportedCombinationError`，均继承 `HazusLossError`。

## 数据审查与维护

可审查的 CSV/JSON、原始行、来源 SHA-256 与勘误同 SQLite 一并分发。运行时不依赖 `dev` 文件夹或原始 PDF。

- 易损性：HAZUS 表 5-17 至 5-34，非结构位移阈值见表 5-23。
- 损失与时间：表 11-2 至 11-5、11-7 至 11-9；搬迁排除规则见 §11.2.5。
- 伤亡与条件倒塌：表 12-3 至 12-8。

从数据重建 SQLite：

```powershell
uv run python tools/build_database.py
```

维护数据时从对应版本手册重新提取：

```powershell
uv sync --group data
uv run --group data python tools/extract_hazus_tables.py "dev/fema_hazus-earthquake-model-technical-manual-6-1.pdf"
uv run python tools/build_database.py
uv run pytest -q
```

提取器针对本次提供的477页 July 2024修订手册；维护时应审查 CSV 与勘误差异。公式与边界详见 [计算方法](docs/methodology.md)，独立手算例见 [数值核验](docs/verification.md)。

## 与旧项目比较

使用当前四层示例的相同响应和费用，对比另一个项目的原始计算类：

```powershell
uv run --group comparison python -X utf8 tools/compare_projects.py --legacy-project "F:/Projects/HAZUS-loss-assessment"
```

旧项目保持只读。输出位于 `output/project_comparison/`：`comparison.md` 提供逐项金额、参数与公式差异、顺序差额分解及代码定位；`metrics.csv` 和 `results.json` 保存可复核数值；`current_building.html` 为当前项目的完整网页结果。比较保留双方各自易损性参数，并另行统一参数核验计算公式；旧项目缺少的指标标为“未实现”。

## Python 版本兼容性

第三方库最低支持 **Python 3.10**，由 `pyproject.toml` 的 `requires-python = ">=3.10"` 声明。计算、数据库查询和离线报告只使用 Python 标准库，不需要安装 NumPy、SciPy、pandas 或 matplotlib。旧项目对比工具单独使用 `comparison` 依赖组，不影响第三方库用户安装。

最低版本依据包括 Python 3.10 引入的 `zip(strict=True)`、`itertools.pairwise` 和 `dataclass(kw_only=True)`。如需继续兼容 Python 3.9 或更旧版本，必须先替换这些接口并重新测试，不能只修改版本声明。Ruff 的目标版本同步设为 `py310`。

已在 Windows 的 Python 3.10、3.11、3.12、3.13 和 3.14 上分别运行现有完整测试，每个版本均为 **81 项通过**，覆盖计算、数据库、报告及输入校验。测试直接加载源码，没有执行项目打包或发布。复核某个版本时可运行下面的 PowerShell 命令，并替换 `--python` 后的版本号：

```powershell
$env:PYTHONPATH = (Join-Path $PWD "src")
uv run --isolated --no-project --python 3.10 --with "pytest>=8.4" python -m pytest -q
```

`.python-version` 保留为 `3.14`，仅指定本项目默认的开发解释器；发布脚本同样使用 Python 3.14 构建。生成的是纯 Python 的 `py3-none-any` wheel，用户安装时受 `requires-python` 约束，不需要与构建时使用相同的 Python 小版本。发布校验工具使用 Python 3.11 才提供的 `tomllib`，由脚本通过 Python 3.14 运行，不属于第三方库的运行模块。

## 双击打包并发布到 PyPI

双击项目根目录的 `publish_pypi.bat`。脚本固定发布 **hazus-loss 1.0.0** 到正式 PyPI；它会构建、校验并上传，不需要先激活虚拟环境。首次运行需要联网下载构建工具。

- 只使用 PATH 中支持 `build` / `publish` 的 uv：`uv build` 通过 Hatchling 生成 wheel 和源码包，`uv publish` 上传到 PyPI。uv 可按需下载构建所用的 Python 3.14；找不到可用 uv 时直接停止并提示。
- 每次在 `dist/release-1.0.0-随机数-随机数/` 中生成新文件，不删除原有产物，只上传本次的 `hazus_loss-1.0.0-py3-none-any.whl` 和 `hazus_loss-1.0.0.tar.gz`。
- 发布前检查包名和版本、锁文件版本及 Python 范围、数据库完整性，以及两个归档内的 Python 模块、SQLite、CSV/JSON 和 `py.typed`；通过 `uv tool run` 执行 `twine check --strict` 检查 PyPI 描述和元数据，twine 仅用于校验。任一步失败即停止，窗口会保留错误信息。

先在 [PyPI](https://pypi.org/account/register/) 准备账号并开启双重验证，在 [API tokens](https://pypi.org/manage/account/token/) 创建有相应发布权限的 token。首次创建项目可使用账号级 token，项目创建后可改用项目级 token。出现认证提示时，用户名填写 `__token__`，密码处粘贴完整的 `pypi-...` token（输入不回显）。也可预先设置 `UV_PUBLISH_TOKEN`；不要将 token 写入脚本、源码或提交到版本库。

### 将 PyPI token 保存到 Windows 环境变量

`uv publish` 原生读取 `UV_PUBLISH_TOKEN`，无需在批处理中手动展开 token 或添加 `--token` 参数。脚本只提示是否检测到该变量，不打印 token 内容。

为了后续双击发布时自动认证，可设置当前用户的永久环境变量：按 `Win + R`，输入 `sysdm.cpl`，选择“高级 → 环境变量”，在“用户变量”中新建 `UV_PUBLISH_TOKEN`，值填写完整的 `pypi-...` token。保存后，新启动的程序读取新变量；已打开的终端需要关闭重开。如果双击脚本仍提示未检测到变量，可注销并重新登录 Windows 后再运行。环境变量以明文保存，只在自己的受信任电脑上配置，不要截图或共享其值。

也可以在 PowerShell 中执行以下配置命令；token 在隐藏输入提示中粘贴，不会成为命令历史的一部分。这些命令仅保存配置，不执行打包或上传：

```powershell
$secret = Read-Host "请输入完整的 PyPI API token" -AsSecureString
$token = [System.Net.NetworkCredential]::new("", $secret).Password
[Environment]::SetEnvironmentVariable("UV_PUBLISH_TOKEN", $token, "User")
$env:UV_PUBLISH_TOKEN = $token
Remove-Variable secret, token
```

这同时设置永久用户变量和当前 PowerShell 的环境变量。若稍后从该 PowerShell 启动 `publish_pypi.bat`，会立即继承变量；资源管理器中双击启动时，须确保资源管理器已取得更新后的环境。发布时无需另外设置用户名或密码变量。

只检查是否配置成功、不显示 token 内容：

```powershell
if ($env:UV_PUBLISH_TOKEN) { "当前窗口已配置 token" } else { "当前窗口未配置 token" }
```

PyPI 上的 `hazus-loss` 名称必须可用，或属于有权发布的账号；已有同名项目且无权限时不能发布。同一版本的文件不能覆盖。若上传中途失败，请先查看 PyPI 上该版本的文件状态再处理；脚本不会忽略上传失败。脚本只做发布所需的归档与元数据检查，不自动运行计算测试；维护者可在发布前自行运行上文的测试命令。
