Changelog
This page contains information about each release of heat_helper.
v0.3.0
Release date: 2026-07-26
⚠️ Breaking changes
- Warnings are now shown by default; the package is no longer completely silent.
heat_helperwarns about problems that can corrupt a HEAT upload — the same HEAT Student ID matched to two different students, duplicate HEAT records, a date column converted automatically — and these were invisible unless you had calledenable_logging(), which most users never did. The package still adds no handler and configures nothing: it simply no longer suppresses Python's built-in fallback for unhandled warnings. INFO and DEBUG are unchanged and stay silent until you ask for them. If you had already configured logging, nothing changes and nothing is shown twice. To silence the package completely, addlogging.NullHandler()to the"heat_helper"logger. Note thatdisable_logging()now means "back to default" rather than "silent", so warnings survive it. perform_school_age_range_fuzzy_matchno longer discards matches that share a HEAT record. Previously, when two rows both matched the same HEAT record, only the highest-scoring row was returned and the other was pushed into the remaining unmatched DataFrame — where it read as "not found in HEAT", risking a duplicate HEAT record being created for a student who already had one. Both rows are now returned as matches and a warning names the shared HEAT ID instead. This matters because the same student legitimately appears on more than one row when your data covers several activities, and every one of those rows should carry the same HEAT Student ID. If you relied on the old behaviour to de-duplicate, do that on the returned matches DataFrame instead.perform_exact_matchnow raisesValueErrorifheat_id_colalready exists inunmatched_df. Matched IDs come back under theHEAT:prefix, so an existing column of the same name was ambiguous. Drop or rename it before calling.perform_fuzzy_matchnow raisesFilterColumnMismatchErrorfor empty filter columns. Passing empty lists toleft_filter_cols/right_filter_colspreviously ran with no blocking at all; at least one column is now required.perform_school_age_range_fuzzy_matchvalidates all its columns before doing any work, includingheat_id_col, which was not checked at all. Missing columns now raiseColumnDoesNotExistErrorrather than failing later with a less helpful error.- Booleans are now rejected as year groups.
clean_year_groupandcalculate_dob_range_from_year_groupraiseTypeErrorforTrue/False, which were previously treated as the integers 1 and 0. clean_year_group(errors='ignore')returns your original value unchanged. It previously converted it to a string, so an unrecognised integer came back as'12'rather than12.
Bug fixes
perform_fuzzy_matchnow warns when one HEAT record is matched by several rows. It previously assigned the same HEAT ID to multiple rows silently, so two different students could be given the same ID with nothing to flag it. Matching results are unchanged — the warning is new. Turn on logging withhh.enable_logging()to see it.- Both fuzzy matching functions now handle a HEAT export with a duplicate index. Previously this produced malformed rows in the matches DataFrame. The index of your HEAT DataFrame is no longer used for anything visible in the output.
create_error_reportno longer misaligns results on filtered DataFrames. The report columns were built with a fresh index, so validating a DataFrame whose index was not0, 1, 2...(for example one you had filtered) attached error details to the wrong rows or produced blanks. Results now keep your DataFrame's own index.create_error_reportgives clearer errors for a badModel. Passing an instance of a Pydantic model, or a class that is not a Pydantic model, now produces messages that say which of the two is wrong.find_duplicatesworks with non-string ID columns. The grouping logic joins IDs into strings, so passing an integerid_colpreviously produced incorrect duplicate lists. IDs are now normalised internally and your column is returned untouched.find_duplicatesnumbers rows correctly on filtered DataFrames. The generatedDuplicate IDcolumn was built without reference to your index, so on a DataFrame whose index was not0, 1, 2...the IDs were misaligned or missing.calculate_dob_range_from_year_grouphandles an empty column. Passing an empty Series now raisesValueError(or returnsNoneundererrors='coerce'/'ignore') rather than failing unclearly.format_namehandles apostrophes correctly. Possessives and initials are no longer given a stray capital —"JAMES'S"returns"James's", not"James'S"— whileO'ReillyandMcDonaldare still preserved. There is deliberately no rule forMacnames, as capitalisation of the following letter is inconsistent and cannot be inferred.enable_loggingdocstring no longer shows a misleading example, and the explanation of the package's logging conventions inlogger.pyis now a real module docstring.- The
heat_helperlogger no longer has a handler attached at import. This was what suppressed warnings for unconfigured users; see the breaking change above.
New features
- Optional
heat_id_colargument onperform_fuzzy_match. Not used for matching — it only lets the shared-HEAT-record warning name the HEAT IDs affected rather than reporting a count. Omitting it leaves the function's behaviour exactly as before. heat_helper.create_error_reportis now resolved lazily. Importingheat_helperno longer pulls invalidation.py(and therefore the optionalpydanticdependency) until you actually use the function, and the function keeps its own signature and docstring.
Documentation
- Corrected the argument names (
unmatched_df, notnew_df) in theperform_exact_matchdocumentation, and documented the exceptions each matching function raises. - Documented the behaviour of
find_duplicateswith missing dates of birth or postcodes, and the fact that it returns rows in sorted order rather than input order. Corrected the threshold range and the example output in the duplicates usage page. - Clarified
format_postcode's length rule (5–7 characters once spaces are removed) and that it also checks the UK postcode format. - Noted that
create_error_reportreturns an empty DataFrame unchanged, with no added columns. - Fixed broken links in the changelog, and corrected the data validation summary in the README.
v0.2.0
Release date: 2026-07-25
⚠️ Breaking changes
create_full_namenow validates its inputs instead of failing silently. Previously, passing an unsupported type returnedNonewith no explanation. It now raises:TypeErroriffirst_nameandlast_namearen't bothstror bothpd.Series, or ifmiddle_namedoesn't match that mode;ValueErrorif Series arguments differ in length, or don't share an index.
create_full_namereturn values changed. Empty rows in Series mode are nowpd.NArather than"", and scalar mode returnsNonerather than""when the result is empty. Downstream checks likeif name == ""need updating topd.isna(name).middle_namenow defaults toNone(was""). Omitting it, or passingNone/NaN, all mean "no middle name" in both modes.- Non-whole floats in year groups now raise
InvalidYearGroupError, notTypeError. Affectsclean_year_groupandcalculate_dob_range_from_year_groupwhen called witherrors='raise'. Code usingerrors='coerce'or'ignore'is unaffected — both exception types were already handled.
Bug fixes
- Year groups read from Excel now work. Whole-number floats such as
7.0are coerced to7instead of raisingTypeError— these are common when pandas reads a numeric column containing blanks.NaNstill raisesTypeError, and genuinely fractional values like6.245are rejected as invalid year groups. perform_school_age_range_fuzzy_matchno longer modifies your DataFrame. Whenheat_dob_colheld strings, the automatic datetime conversion was writing back into the caller's frame. Input frames are now copied before any conversion happens.create_full_namehandles missing data properly. Nulls no longer poison a whole row via string concatenation, and the literal string"nan"is treated as empty. A row with a missing middle name now yields"Ann Smith"rather thanNaN.format_postcoderejects malformed input earlier. Added an explicit 5–7 character length guard, so inputs that are too short or too long raiseInvalidPostcodeErrordirectly rather than being reformatted into something invalid first.
Other changes
- Fixed the type hint for
find_duplicates(id_col=...):str→str | None. - Removed a dead condition in
create_error_report's date handling. The oldtype(value) is not datetime.datecheck compared a class against a method object and was alwaysTrue; behaviour is unchanged, the code is just no longer misleading. - Added
DEBUGlogging to the internal year-group parser, recording when a string or float input is coerced to an integer.
v.0.1.3
Release date: 2026-07-24
- Implemented Logging: 'heat_helper' now has native logging which you can figure with the built-in 'enable_logging' function or the standard 'logging' module. See usage documentation or API documentation.
v0.1.2
Release date: 2026-02-13
- Bug Fix: fixed an error with the clean year group function which meant error behaviour wasn't working correctly when run on FE Levels.
v0.1.1
Release date: 2026-02-12
First update to heat_helper.
- Data Validation:
pydanticis now an optional dependency. This gives you access to a function which generates an error report by passing your data to apydanticmodel. See usage documentation or API documentation. - Bug fixes:
- fixed some minor issues with the duplicates function which used incorrect variable names;
- improved error handling in name functions;
- added a length guard to format_postcode;
- update functions now copy the DataFrame rather than editing in place.
- Optimisations:
- improved column processing in convert_to_snake_case function;
- get_contextual_updates now takes any Iterable for bad_values (type hints and docs updated);
- adding column name variables used by matching functions as constants.
- duplicates function now has an optional twin_protection_threshold (default is 70);
- custom exceptions updated for clarity and consistency.
- Documentation Improvements: reviewed docstrings and documentation for small errors, typos, and clarity and fixed all identified issues.
v0.1.0
Release date: 2026-01-16
Initial release of heat_helper.