Trusting a number¶
Check a field’s status, coverage and reader checks before relying on it. These answer different questions: what a value means, how often it is present, and whether the reader passed its checks.
Verified or unconfirmed¶
Verified means the value was checked against what the game shows. Unconfirmed means its meaning has not been confirmed on a game screen.
import fmsave
fmsave.field_status(fmsave.Player, "attributes.finishing") # "verified"
fmsave.field_status(fmsave.Player, "contract.wage") # "unconfirmed"
Dots reach into groups. A field with no registered status raises KeyError.
An unconfirmed field needs independent evidence before you treat its meaning as established.
Coverage measures presence¶
coverage gives the share of records whose value is not None, per flat column.
with fmsave.open("career.fm") as career_save:
squad = career_save.players().where(club_uid=career_save.managed_clubs()[0].club_uid)
squad.coverage["contract_wage"]
squad.coverage["loan_parent_club_name"]
Missing values can mean the save omitted data, a join could not resolve, or fmsave could not decode it. Coverage alone cannot distinguish these. See What a save holds. A missing match score does not mean a 0-0 result.
Reader checks¶
Checks test completeness, joins and expected ranges. A failed check produces a
ReaderCheckWarning and returns the table. Some range checks may fail on a valid save unlike the
reference careers; investigate the failed check before using its output.
Use strict mode when your pipeline should stop on a failed check:
with fmsave.open("career.fm", strict=True) as career_save:
squad = career_save.players() # raises ReaderCheckError on a failed check
A structural failure that prevents a table from being decoded raises ReaderCheckError in either
mode. Empty output can be valid: player_match_stats() accepts it when every player explicitly
has no stored history. Missing or incomplete histories still fail checks.
Validation reports¶
validate_save runs every reader and collects its outcome without emitting reader warnings or
raising reader errors.
with fmsave.open("career.fm") as career_save:
report = fmsave.validate_save(career_save)
for reader in report.readers:
print(reader.reader, reader.status, reader.record_count)
# "players" "ok" 124310
Club uid 0 is valid. A club reference is missing when it is None, so use is not None
to keep valid references in filters.
A reader’s status is ok, failed (a check did not pass) or error (it raised).
Each ReaderValidation also carries gates, coverage and anomalies.
Passing checks is useful evidence, but does not confirm every field’s meaning or guarantee full
historical coverage.
The same report is available from the command line:
fmsave validate career.fm
fmsave validate career.fm --json
players: ok (124310 records)
injuries: failed (630 records)
injury_log_dates = 0.71 expected 0.9..1
game FM26, build 26.1.0+1234567
The report contains checks, counts and rates, without names, uids or text from your save. Paste it into a GitHub issue when a reader misbehaves. Do not attach or link the save itself. To share it, email a link to the address on the maintainer’s GitHub profile.
Money and game builds¶
Money uses the save’s stored unit. Wages, values and balances are not converted to the currency the game displays. Check that unit before interpreting a difference.
UnknownBuildWarning means fmsave does not recognize that FM26 build. Readers still run; inspect
their checks before relying on the output. fmsave also issues it for every FM27 save, because
FM27 coverage is partial, and for an FM27 build with no registered layouts it also says that
fmsave borrows the registered build’s layouts where section schemas match.
FM27 coverage¶
FM27 build 27.0.0+2486695 opens normally and reports known_build true: every reader has a
layout registered for it, which does not confirm any field’s meaning.
Other FM27 builds open as unverified builds and report known_build false; fmsave info shows
them as “unverified build”. For each section whose schema equals the one registered for
27.0.0+2486695, fmsave borrows that build’s layout. A reader of a section whose schema
differs raises UnsupportedGameError naming the section, schema and build, and the other
readers still run. The save summary and game_info must fit a registered layout, or the save
is refused. Regions with no schema number, such as the one fixtures, league tables and season
statistics come from, are borrowed too; their readers locate their data by stored markers and
check what they read. FM26 layouts are never used for FM27 saves, and FM28 or later saves are
refused. Please report any problem with an unverified build.
Failed checks always stop an FM27 reader, even with strict=False. The validation
report lists unsupported readers and marks every FM27 field unconfirmed. Global field-status
labels describe FM26; they do not certify FM27 meanings.
fmsave sets no fixed size limit on a save, for FM26 or FM27, so a large database opens however
big it grows. To refuse a damaged or crafted file before memory is spent, a part of a save
past 64 MiB may decompress to at most 64 times its compressed size, and a whole save to at
most 32 times its file size. Real saves stay far below both: no part over 1 MiB expands more
than about 41 times, and no save more than about 5 times. A save past either bound raises
CorruptSaveError, whose message gives the ratio and asks for a report if the game opens it.
Finance rows describe historical snapshots, not current-month live totals; stored money is
not converted to the game’s display currency. Match the period and units before comparing.