Metadata-Version: 2.3
Name: health-code
Version: 0.1.0
Summary: Small helpers for preparing rounded, masked mortality tables for disclosure review.
Requires-Dist: polars>=1.36.1,<1.42
Requires-Dist: xlsxwriter>=3.2,<4
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# health-code

Small helpers for preparing a rounded, masked mortality table for disclosure review.
Install the distribution `health-code`; import it as `health`.

```python
from health import print_unique   # also: from health.helpers import print_unique
```

`print_unique` is copied, with the same signature and behavior, from the LTC code package
(`ltc_code.helpers.print_unique`) so you do not need to install LTC.

## Install

From PyPI:

```bash
uv pip install health-code
```

From a local wheel:

```bash
uv build                      # writes dist/health_code-0.1.0-py3-none-any.whl
uv pip install dist/health_code-0.1.0-py3-none-any.whl
```

Python 3.9 or newer. Dependencies: polars, xlsxwriter.

## The example

After installing, copy the example script next to your work (it refuses to overwrite):

```bash
health-example my_scripts/        # or: health-example my_scripts/prepare_table.py
```

Edit the `Settings` block at the top and run it. It does this:

1. Reads one parquet per year, 2001..2024, from `INPUT_FOLDER`. The file name comes from
   `FILENAME_PATTERN`, e.g. `mortality_{year}.parquet`. All 24 files must exist.
2. Keeps only the eight columns in `COLUMN_MAP` and renames them to `year`, `sex`,
   `income_percentile`, `age`, `mortality_rate`, `deaths`, `n`, `n_weighted`. Replace the
   `*_placeholder` names with your real column names.
3. Fails on: a missing file, a missing column, different column types across files, a row whose
   year is not its file's year, missing values, duplicate keys, ages outside `AGES`, a rate outside
   0..1, `deaths > n`, `n <= 0`, or non-positive `n_weighted`.
4. Rounds and masks (below) and writes the request folder.

You must set `RATE_KIND` (`"weighted"` or `"unweighted"`) and `REQUEST_DATE` (`YYYYMMDD`). Neither
has a default, because a wrong guess would change what is released.

The same steps are available as functions: `read_annual_parquets`, `prepare_disclosure`,
`write_disclosure`, and `round_count`.

## What gets written

Layout follows the FSRDC Disclosure Avoidance Review Procedures Handbook v7.0.0 (2026-08-03),
section III.C:

```text
YYYYMMDD/
  output/Table_1_T13T26.xlsx                      the file to submit (rounded, masked)
  support/Table_1_support_CUI_SP-CENS_T13T26.csv  exact combined data
  support/estimate_inventory_T13T26.xlsx          row and estimate counts
  support/rounding/README.txt                     how to run the official rounder
```

- Every xlsx has a first sheet named `CUI` holding only the CUI header text.
- The release is xlsx only, so the same estimates are not submitted in two formats. For a CSV copy
  (for your own use), set `RELEASE_CSV_FOLDER` to a folder outside the request folder.
- The support CSV holds exact counts, survivors and the suppression flags. It is for the `support`
  folder only. Only the eight mapped columns ever reach any output file.
- `estimate_inventory_T13T26.xlsx` leaves prior releases as `UNKNOWN`; fill them in from your
  project's output tracking sheet.
- Existing files are never replaced unless `OVERWRITE = True`. Nothing is deleted.
- `support/rounding/` is left for you: run the Census program `drb_rounder.py` following the DRB
  Rounder Tutorial on RDC Docs and keep its `_rounded.xlsx` output. This package does not fake it.

## Rounding and masking rules

From the FSRDC Disclosure Avoidance Methods Handbook (guidance current as of 2026-05-01),
section V.B. Each count is rounded by **its own size**:

| count | rounded to |
| --- | --- |
| under 15 | suppressed |
| 15 to 99 | nearest 10 |
| 100 to 999 | nearest 50 |
| 1,000 to 9,999 | nearest 100 |
| 10,000 to 99,999 | nearest 500 |
| 100,000 to 999,999 | nearest 1,000 |
| 1,000,000 or more | four significant figures |

Exact ties: counts below 1,000,000 round half to even (25 -> 20, as in
`oi_tools.drb.round_count_column`); four-significant-figure rounding at 1,000,000 or more rounds
ties away from zero (1,234,500 -> 1,235,000). The handbook does not specify tie-breaking, so run
the official Census rounder before release. Weighted `n_weighted` is rounded to four significant
figures.

- `"unweighted"`: released rate = rounded `deaths` / rounded `n`, to four significant figures.
- `"weighted"`: released rate = your input rate to four significant figures. Deaths are never rebuilt
  from a weighted rate.

**Suppression (a chosen conservative policy, not a universal Census minimum).** A row's four numbers
(`n`, `deaths`, `mortality_rate`, `n_weighted`) are all set to blank when `n < 15`, `deaths < 15`,
`n - deaths < 15`, or an optional `EXTRA_SUPPRESS` condition is true. Zeros are suppressed too.
Keys stay, so the table keeps its shape, and the release has no column saying why a row is blank.

This package is not an approval. It does not check prior releases, the 30:1 volume rule, the
required minimum entity counts for your geography, or anything the disclosure officer reviews.
Run the official `drb_rounder.py` and follow your FSRDC administrator's current instructions.

Why not depend on `oi-tools`: it requires Python 3.10 or newer and this package targets 3.9. The
count rounding is a few lines of polars, and the tests check its thresholds against an independent
reference. `round_proportion_column` in `oi-tools` is not used (outdated rule).

## Develop

```bash
uv sync
uv run python -m unittest discover -s tests -v
uv build
```

Tests use synthetic data only.
