The blank-vs-observed-zero distinction is a good contract to write down. One edge worth thinking about is the r4 case (reach = 0 with saves = 2): the data is internally inconsistent, since you can't save a post that reached no one, so it may deserve a warning instead of just a null rate. Also, if the helper ever runs on pandas, read_csv's default NA parsing silently turns strings like "NA" or "null" into NaN, so passing keep_default_na=False with an explicit na_values list keeps the contract equally explicit. Do you treat a missing column differently from an all-blank column in the warnings, or are they reported the same way?