Skip to content

Implement auditable UFC dataset fidelity pipeline #14

Description

@komaksym

Proposed spec for issue #12: make UFC dataset fidelity auditable

Problem Statement

People using the UFC DataLab dataset cannot reliably tell whether a value is an authoritative statistic, an OCR transcription, a derived value, or a value that failed a domain check.

The current collection combines UFCStats bout totals with UFC.com scorecards, but the public contract does not expose enough information to audit or safely join the data:

  • Scorecard values are produced by PaddleOCR and are published without a measured accuracy rate, a manually reviewed benchmark, field-level confidence, or a visible review/quarantine state.
  • The stats export is bout-level. Its round field is the finish round, not a per-round stat breakdown, so users can mistake a bout total for round-level data.
  • Rows are keyed primarily by fighter names, event names, and dates. The UFCStats fight, event, and fighter identifiers present in source URLs are not retained.
  • Existing structural checks catch missing required fields and unsupported result markers, but domain checks such as landed <= attempted, valid ranges, totals reconciliation, round sanity, and cross-source consistency are not part of the published data contract.
  • A suspicious row can therefore enter downstream modelling or analysis without a machine-readable explanation of what was checked, what failed, and whether the row was excluded.

Solution

Make data provenance and data quality first-class parts of the dataset pipeline.

The project will continue to use UFCStats for fight statistics and UFC.com for official scorecard images, but it will publish the source identifiers, a canonical per-round dataset, explicit validation outcomes, and an auditable OCR quality report.

The pipeline will:

  1. Capture UFCStats fight, event, and fighter IDs while scraping, and use the fight ID as the primary identity for deduplication, incremental updates, and joins.
  2. Extract UFCStats' existing per-round tables into a separate normalized per-round output while preserving the current fight-total outputs.
  3. Run one central quality-validation boundary before derived datasets are written. Use layered reconciliation: local round failures quarantine the whole round set, while independently valid fight totals remain usable with an explicit warning. An exact contradiction between fight totals and round sums quarantines both representations. Hard failures will be retained in a quarantine artifact with source provenance and check codes; they will not silently disappear or flow into analytical outputs.
  4. Treat OCR as an observed transcription process rather than ground truth. The scorecard parser will retain provenance and quality flags, and a stratified manually reviewed benchmark will publish empirical field/record accuracy with a confidence interval.
  5. Rebuild all tracked derived outputs and update the README/data dictionary so users can distinguish fight totals, per-round stats, OCR fields, stable identifiers, warnings, and quarantined records.

The highest test seam is the dataset-processing boundary that turns normalized stats, per-round stats, fighter details, and scorecard records into the published outputs and quality artifacts. Scraper and OCR parser tests remain narrow source-adapter tests for extraction behavior; they should not duplicate the domain rules.

Normative Reconciliation Contract

This section is normative. It resolves how incomplete, invalid, and contradictory evidence moves through the pipeline.

Validation grains and states

Validation is evaluated at three independent grains:

  • Fight total: one normalized fight record keyed by stable fight_id when available. A genuinely pre-ID row may use its documented legacy fallback key, but it must carry identity_status=legacy_fallback and cannot be mistaken for stable identity.
  • Round set: the complete observed set of round rows for one stable fight_id, not each surviving row independently. A round set without a stable fight ID cannot enter the certified per-round output.
  • Scorecard attachment: one scorecard record and its optional match to a fight.

The externally serialized states are:

  • check_result=pass: the rule was applicable, all required evidence existed, and the invariant held.
  • check_result=fail: the rule was applicable, sufficient evidence existed, and the invariant was violated.
  • check_result=not_checked: no conclusion was made because the rule was inapplicable or evidence was unavailable. A reason such as not_applicable or evidence_unavailable is required. Missing evidence that is itself mandatory also emits a separate failing requiredness check; not_checked must never disguise that failure.
  • record_state=valid: no hard failures and no material warning flags at that grain.
  • record_state=warning: usable at that grain, with no hard failure, but at least one warning-level failure or material not_checked result.
  • record_state=quarantined: a hard failure exists at that grain, or a hard contradiction propagates to it.

not_checked is a check result, never a fourth record state. Every check has a stable code and severity (hard or warning).

Use exactly these fight-level round_reconciliation_status values:

  • reconciled: the expected sequence is complete and all applicable hard round and round-to-total checks pass.
  • reconciled_with_not_checked: the round data is structurally usable and has no hard contradiction, but at least one reconciliation check could not be evaluated.
  • unavailable: no per-round source table was available.
  • incomplete: an expected observed round is absent.
  • invalid: a round row is malformed/impossible or a round key conflicts.
  • mismatch: an exact round-to-total invariant failed.

Propagation rules

The pipeline must preserve the narrowest valid evidence, except when an exact contradiction implicates both representations:

  1. A hard fight identity or canonical-outcome failure propagates to its dependent round sets and derived records. A child cannot claim attachment to an untrusted fight.
  2. A local round failure quarantines the entire round set for that fight. Do not publish apparently valid surviving rows from a partial or invalid set in the certified per-round output.
  3. A missing round table or missing expected round does not quarantine independently valid fight totals; it makes the fight-total record warning and the round set unavailable or incomplete.
  4. An exact aggregate contradiction, such as round significant strikes summing to 74 while the fight total is 81, quarantines both the fight-total record and the entire round set. Neither representation is automatically authoritative because both may have been misparsed.
  5. A scorecard mismatch, malformed OCR record, or ambiguous scorecard match quarantines only the scorecard attachment. It does not remove otherwise valid UFCStats fight or round statistics.
  6. Raw normalized observations remain available for every quarantine decision. Quarantine is exclusion from trusted analytical outputs, not deletion.

When multiple conditions apply, the strongest applicable propagation wins in this order: hard identity/outcome failure, exact total-to-round contradiction, local round/fight validation failure, then warning or not_checked. In particular, an invalid total that also contradicts complete round sums uses the symmetric mismatch rule rather than the independent-round exception.

Condition-to-result matrix

Condition Check result and severity Fight-total output Certified per-round output Status and propagation
No per-round table ROUND_TABLE_MISSING=fail (warning); round-dependent checks are not_checked Keep with warning No rows unavailable; no hard propagation
Complete, reconciling rounds Applicable checks pass Keep, normally valid Keep the entire set reconciled
Missing expected round ROUND_SEQUENCE_COMPLETE=fail (hard) Keep with warning Quarantine the entire set incomplete; do not quarantine totals solely for missing rows
Malformed or impossible round value Relevant check fail (hard) Keep with warning unless a shared invariant also fails Quarantine the entire set invalid; local to rounds unless identity/outcome is implicated
Conflicting duplicate (fight_id, round_number) ROUND_KEY_UNIQUE=fail (hard) Keep with warning Quarantine the entire set invalid; preserve both conflicting observations in raw/quarantine
Exact additive aggregate mismatch ROUND_TOTAL_RECONCILIATION=fail (hard) Quarantine Quarantine the entire set mismatch; symmetric propagation
Scheduled-round metadata unavailable Schedule checks not_checked (warning) Keep with warning Keep if all other checks pass, with warning reconciled_with_not_checked; never guess a three- or five-round format
Invalid fight-total metric Relevant total check fail (hard) Quarantine Keep only if independently complete and valid; total-dependent checks become not_checked with warning Fight totals are quarantined; valid round evidence is not discarded solely because one total field is bad
Scorecard mismatch or ambiguous attachment Scorecard check fail (hard) Unchanged Unchanged Quarantine only the scorecard attachment

Normative arithmetic and expected rounds

  • Parse landed/attempted pairs as integers and require 0 <= landed <= attempted.
  • Per-round sums must equal fight totals exactly for additive fields when both representations exist: knockdowns, significant-strike landed/attempted, total-strike landed/attempted, takedown landed/attempted, submission attempts, reversals, and control seconds.
  • Significant-strike decomposition must reconcile independently for landed and attempted values: head + body + leg = significant strikes and distance + clinch + ground = significant strikes.
  • Store control time as integer seconds and require zero reconciliation tolerance. Do not widen the tolerance to hide contradictions; a documented source exception must be versioned explicitly.
  • Never sum percentages across rounds. Recompute them from counts using the source rule, currently equivalent to floor(100 * landed / attempted) for positive attempts. For 0 of 0, require landed = 0 but preserve whether the source reported 0% or an unavailable placeholder.
  • When a valid source-reported finish round F exists, the expected observed round keys are exactly 1..F. Do not fabricate post-finish rounds or distribute fight totals into missing rows.
  • When scheduled format metadata provides N, require 1 <= F <= N. Standard decisions should finish at F=N; stoppages and technical decisions may finish earlier. If the schedule cannot be interpreted safely, emit a warning and not_checked rather than assuming three or five rounds.

Output contract

  • Fight totals/all-bouts: include valid and warning fight-total records, preserve all four outcomes, and append source IDs, record_state, check information, and round_reconciliation_status. Exclude quarantined fight totals.
  • Certified per-round data: include only complete validated round sets with status reconciled or reconciled_with_not_checked. The latter carries record_state=warning. Never publish partial survivors from incomplete, invalid, or mismatch sets.
  • Decisive-only: derive from the already-filtered all-bouts fight totals, then select red_win/blue_win. Do not implement a second validation policy. Preserve fight_id and both source corner IDs.
  • Merged scorecards: start from trusted fight totals and attach only uniquely matched, non-quarantined scorecards. Keep the fight row when scorecard values are absent and expose the scorecard match/quality state.
  • Raw data: preserve source observations before domain filtering, including malformed values, duplicate observations, original orientation, URLs, and OCR evidence. Do not clip, impute, infer, or silently correct.
  • Quarantine: include record_type, grain key, source provenance, check code, severity, result, observed values, and propagation target. For a bad round set, preserve every observed round, not only the triggering row.
  • Quality summary: reconcile raw, trusted, warning, and quarantined counts; report failures by code, duplicate/missing IDs, round coverage/statuses, scorecard match states, and OCR benchmark metrics. Any accounting mismatch fails release validation.

User Stories

  1. As a data analyst, I want to know whether a statistic is a fight total or a per-round value, so that I do not accidentally model the finish round as a statistical breakdown.
  2. As a data analyst, I want a canonical per-round record for each observed fight round, so that I can study pacing, round-level performance, and late-fight changes.
  3. As a data analyst, I want the fight-total view to remain available, so that existing analyses do not need to reconstruct totals from round rows.
  4. As a machine-learning researcher, I want a stable fight identifier, so that I can join fight data across refreshes without relying on spelling-sensitive names.
  5. As a machine-learning researcher, I want stable UFCStats identifiers for each event and each fighter corner, so that I can build fighter histories and event joins without name collisions.
  6. As a data maintainer, I want rematches on the same card to remain separate records, so that incremental refreshes do not collapse two bouts into one.
  7. As a data maintainer, I want incremental updates to deduplicate on the source fight ID, so that rerunning a scrape is idempotent and does not create duplicate bouts.
  8. As a data maintainer, I want legacy records without source IDs to be visibly marked as legacy or incomplete, so that they are not mistaken for stable-identity records.
  9. As a user joining scorecards to fight statistics, I want the stats-side fight ID carried into the merged scorecard output, so that downstream joins can use a stable key after the scorecard match has been audited.
  10. As a user joining scorecards to fight statistics, I want ambiguous or unmatched scorecards surfaced explicitly, so that a fuzzy name/date match cannot silently attach a scorecard to the wrong bout.
  11. As a data maintainer, I want source URLs or equivalent source provenance retained for quarantined records, so that every failure can be traced back to the page or image that produced it.
  12. As an OCR reviewer, I want the parsed fighter names, date, judge totals, image provenance, and OCR confidence information together, so that I can review a scorecard without reconstructing context from several files.
  13. As an OCR reviewer, I want malformed scorecards routed to a review queue, so that a missing or transposed judge score does not become a trusted numeric value.
  14. As a dataset user, I want scorecard accuracy reported from a manually reviewed sample, so that I can judge whether OCR-derived fields are appropriate for my analysis.
  15. As a dataset user, I want the OCR report to distinguish field-level accuracy from whole-record accuracy, so that one correct name does not hide an incorrect judge total.
  16. As a dataset user, I want an empirical confidence interval and the benchmark sampling method documented, so that the reported error rate is not presented as an unsupported universal guarantee.
  17. As a data maintainer, I want accepted OCR rows to preserve the parser/benchmark version and quality state, so that a future OCR engine change can be compared with the current release.
  18. As a data analyst, I want landed/attempted values checked for arithmetic validity, so that impossible strike or takedown counts are not treated as real performance.
  19. As a data analyst, I want percentages checked against their valid range and their source counts where applicable, so that values such as negative percentages or percentages above 100 are caught.
  20. As a data analyst, I want fight-level totals checked against per-round totals, so that incomplete or misaligned round extraction is visible before I aggregate it.
  21. As a data analyst, I want round numbers and elapsed times checked against the scheduled format and finish metadata, so that impossible round/time combinations are not silently published.
  22. As a data analyst, I want canonical result markers and fight_outcome checked together, so that red/blue results cannot contradict the fight-level outcome.
  23. As a data analyst, I want legitimate zeros and source placeholders distinguished from invalid missing values, so that 0 of 0 and unavailable statistics are not incorrectly flagged as the same thing.
  24. As a data maintainer, I want validation rules to have stable check codes and severities, so that failures can be counted, filtered, regression-tested, and explained to contributors.
  25. As a data maintainer, I want a row-level quarantine artifact that contains failed checks and observed values, so that invalid records are preserved for repair instead of silently dropped.
  26. As a dataset user, I want warnings to remain visible on otherwise usable records, so that I can choose whether to include them rather than losing useful but imperfect data.
  27. As a dataset user, I want hard-invalid records excluded from analytical outputs while remaining in the raw/quarantine layer, so that derived tables are safer without erasing source evidence.
  28. As a data maintainer, I want a machine-readable quality summary for every regeneration, so that I can compare row counts, failure counts, warning counts, duplicate counts, and OCR review coverage between releases.
  29. As a data maintainer, I want the full regeneration to be reproducible from the tracked source inputs and documented commands, so that a published refresh can be audited or repeated.
  30. As a contributor, I want fixture-based tests for decisive, draw, no-contest, early-finish, five-round, missing-round, OCR-error, and duplicate-ID cases, so that changes are safe without depending on live websites.
  31. As a contributor, I want scraper tests to prove that IDs and per-round tables are extracted from the correct HTML nodes, so that selector changes fail close to the source adapter.
  32. As a contributor, I want OCR tests to use deterministic OCR-result fixtures, so that quality rules can be tested without starting PaddleOCR or depending on model output variability.
  33. As a contributor, I want the existing all-bouts and decisive-only contracts preserved, so that the Issue Missing draws and no contests #7 handling of draws and no contests is not regressed while fidelity improves.
  34. As a project reviewer, I want the README to state source coverage, refresh date, identity semantics, round-level limitations, OCR quality, and quarantine behavior, so that a new user can assess fitness for purpose before downloading data.
  35. As a project reviewer, I want a high-level data-flow description from source pages/images through validation to published tables, so that the system-level behavior is understandable without reading scraper internals.

Implementation Decisions

  • Keep UFCStats as the source for fight statistics and UFC.com as the source for official scorecard images. Do not switch providers as part of this work; improve provenance and defensive processing around the existing sources.
  • Extend the scraped fight item with source identity and provenance: the UFCStats fight identifier, event identifier, red-fighter identifier, blue-fighter identifier, and the corresponding source URLs where available. These are source identifiers, not locally generated surrogate keys.
  • Derive identifiers from the source links already present in event and fight pages. Do not derive stable identity from normalized names, event labels, or dates.
  • Make the source fight identifier the primary identity used by incremental merging and duplicate detection. A new scraped row without fight_id is a hard identity failure. Preserve the existing composite fallback only for genuinely pre-ID legacy records, mark them identity_status=legacy_fallback and record_state=warning, and never use the fallback to silently assign identity to a new row. Identical duplicate stable-ID observations may be deterministically coalesced while remaining visible in raw evidence; conflicting observations sharing a stable ID quarantine every conflicting version.
  • Preserve the existing red/blue orientation and canonical outcome values (red_win, blue_win, draw, and no_contest). IDs, names, and results must remain aligned with their corner.
  • Add a separate per-round dataset rather than embedding variable-length lists in the fight-level CSV. It will contain one row per source fight ID and observed round number, retain event and corner identity, and expose the per-round metrics available from UFCStats: knockdowns, significant strikes, significant-strike percentage, total strikes, takedowns and takedown percentage, submission attempts, reversals, control time, and the per-round significant-strike target/position breakdown.
  • Treat the current fight-level stats output as totals. Its round field continues to mean the source-reported finish round; documentation must explicitly say it is not a per-round observation.
  • For an early finish, publish only the rounds present in the source page. Never fabricate a missing round by distributing fight totals. A missing table or missing expected round keeps independently valid fight totals as warning, but the certified per-round output contains no rows for that incomplete set. An exact total-to-round contradiction quarantines both the fight-total record and the entire round set; neither source representation is presumed authoritative.
  • Carry the stable fight/event/corner identifiers through the all-bouts processed view, the decisive-only winner/loser view, and the merged stats-plus-scorecards view. The decisive-only view may continue to project winner/loser columns, but it must retain enough identifiers to identify the original fight and both source corners.
  • Keep scorecard extraction as a separate source adapter. A scorecard record will retain its source image/provenance, normalized fighter names and date, the judge-score vectors, parser/benchmark version, OCR confidence summary where available, and a quality state.
  • Make scorecard judge totals variable-length at the parser boundary and validate that both fighters have the same number of judge totals. Do not silently truncate, pad, or assume a fixed count when the source image disagrees with the configured scorecard template.
  • Use a deterministic scorecard-to-fight matching contract: normalize names and dates, require a unique candidate, preserve orientation, and record the matched fight ID. Unmatched or ambiguous candidates go to review/quarantine. Do not silently accept a fuzzy match.
  • Introduce one shared validation boundary after source normalization and before derived dataset generation. The validator returns per-grain quality states, stable check codes, severity, check results, quarantine records, and propagation targets rather than only raising an exception or dropping a row.
  • Use the exact quality semantics in the Normative Reconciliation Contract: valid means no hard failures or material flags, warning means usable with no hard failure but at least one warning or material not_checked, and quarantined means a hard failure exists at that grain or has propagated to it. not_checked is a check result, never a record state.
  • Implement hard checks for required identity fields, unique source fight IDs, distinct red/blue fighter IDs, supported result/outcome combinations, parseable numeric ratios, non-negative values, landed-not-greater-than-attempted, percentages in the valid range, structurally complete per-round rows, and unique (fight_id, round_number) keys.
  • Implement the exact reconciliation and propagation rules in the Normative Reconciliation Contract. Local round failures quarantine the whole round set; exact additive contradictions quarantine both the fight-total and round-set grains; hard identity/outcome failures propagate to dependent records; scorecard failures quarantine only the scorecard attachment.
  • Implement semantic sanity checks for finish round, elapsed time, scheduled format, control time, and method/outcome combinations. Use the source-reported finish round as the primary expected sequence, add schedule checks when N is known, and emit warning/not_checked when schedule evidence is unavailable rather than guessing.
  • Treat legitimate source placeholders and zero-denominator values according to the existing repository semantics. A missing statistic, a real zero, and a failed parse must remain distinguishable in validation output and documented in the data dictionary.
  • Keep the complete normalized source-level record available even when it is quarantined. Hard-invalid records must be excluded from analytical processed outputs, while the quarantine artifact retains the source key, source provenance, failed check codes, severity, and the observed values needed for repair.
  • Publish a row-level quarantine artifact and a machine-readable validation summary. The summary must include input/output/quarantine counts, counts by check code and severity, duplicate-ID counts, missing-ID counts, unmatched/ambiguous scorecards, per-round coverage, and OCR review/accuracy metrics.
  • Do not claim a universal OCR error bound from PaddleOCR confidence alone. Build a manually adjudicated, stratified benchmark covering the available scorecard layouts, years, image naming versions, and representative decision score patterns. Compare OCR output with the adjudicated values at both field and whole-record level.
  • Report exact agreement rates and a 95% confidence interval for the benchmark, including sample size and selection method. The release documentation must state that this is an empirical estimate for the sampled population, not a guarantee for every future image.
  • Route any scorecard with missing names/date, malformed judge-score vectors, impossible score values, low/insufficient OCR confidence, or cross-source mismatch to review/quarantine. Do not auto-correct likely transpositions such as 10-9 becoming 10-8 without a reviewable source-backed correction.
  • Regenerate all tracked datasets after the schema and validation changes. Preserve the current all-bouts output for decisive, draw, and no-contest rows, preserve the decisive-only analytical projection, and add the new per-round and quality artifacts without making users infer quality from row absence.
  • Update the README/data dictionary and regeneration instructions with the source boundary, identifier definitions, fight-total versus per-round distinction, quality states, check severity policy, scorecard benchmark methodology, quarantine behavior, and the date/coverage of the regenerated release.
  • Prefer the existing Python, pandas, Scrapy, PaddleOCR, and pytest stack. Do not add a production dependency unless the source parser or quality report cannot be implemented with the current dependencies.

Testing Decisions

  • The primary test seam is the normalized dataset-processing boundary. Given small in-memory stats, per-round, fighter-detail, and scorecard fixtures, tests should assert the externally visible published frames and quality artifacts. They should not assert helper call order or private implementation details.
  • Add valid and invalid fixture cases for every hard validation rule: landed greater than attempted, negative values, percentages outside 0–100, invalid ratio syntax, duplicate fight IDs, identical corner IDs, missing IDs, unsupported result pairs, and conflicting derived outcomes.
  • Add reconciliation fixtures for every row in the condition-to-result matrix: no round table, complete/reconciling rounds, missing expected round, malformed/impossible round value, conflicting duplicate round key, exact additive mismatch, unavailable schedule metadata, invalid fight totals, and scorecard mismatch. Assert the exact check_result, severity, record_state, round_reconciliation_status, output inclusion, and propagation target for each case.
  • Add a symmetric-contradiction fixture where independently valid-looking fight totals and round rows disagree. Verify that both the fight-total record and the entire round set are quarantined, while both original observations remain in raw/quarantine. Add separate fixtures proving that local round incompleteness does not quarantine valid fight totals and that a scorecard failure does not remove UFCStats statistics.
  • Add outcome fixtures for red wins, blue wins, draws, and no contests, including the existing result-marker aliases. Verify that all-bouts data remains complete and decisive-only data remains limited to decisive outcomes.
  • Add identity fixtures for two bouts with the same fighter names on the same event, repeated incremental input, and legacy rows with no IDs. Verify source-ID deduplication, rematch preservation, and visible legacy fallback status.
  • Add per-round fixtures for an early finish, a full three-round bout, a full five-round bout, a missing per-round table, a missing expected round, a malformed row, a conflicting duplicate key, a round with zero activity, and a total/round contradiction. Verify round numbering, corner alignment, no fabricated rows, whole-set quarantine, exact count/control-time arithmetic, percentage recomputation, and fight-total preservation or symmetric quarantine as required.
  • Extend the existing stats scraper HTML fixtures to assert extraction of fight, event, and fighter IDs and the per-round tables. Keep these tests at the HTML-adapter boundary; the shared validation tests should consume normalized records instead of HTML.
  • Extend the existing stats pipeline tests to assert normalization, quality-state assignment, hard-failure quarantine behavior, warning retention, and stable check codes. A failing record should be observable through the returned quality artifact rather than only through a log message or an exception.
  • Add deterministic OCR-result fixtures that represent correct output, a transposed digit, a missing judge score, an extra score, invalid score text, and low-confidence tokens. Test parser output and validation without invoking live model inference.
  • Add scorecard matching fixtures for a unique match, reversed corner orientation, no match, and multiple candidate matches. Verify that only the unique audited match receives a fight ID and that the other cases are reviewable.
  • Add benchmark-report tests that verify field-level accuracy, whole-record accuracy, sample counts, and confidence-interval metadata are computed from adjudicated fixtures. Tests must not assert a particular accuracy percentage for the live corpus.
  • Add output-contract tests that compare generated column presence/order and row semantics for the raw/all-bouts, decisive-only, certified per-round, merged, quarantine, and quality-summary outputs. Verify that warning fight totals remain present, quarantined totals do not, partial/invalid/mismatched round sets never leak partial rows, and quality accounting reconciles. Existing schema tests are the prior art for this contract.
  • Add a fixture-only regeneration smoke test through the public dataset-writing entry point. It should produce all expected artifacts, preserve source rows in raw/quarantine, exclude hard-invalid rows from analytical outputs, and be repeatable with byte-stable or semantically stable results.
  • Keep live UFCStats/UFC.com scraping and the full historical OCR run out of ordinary unit tests. A maintainer may run an explicit release-validation command against live inputs, but network/model variability must not make the normal test suite flaky.
  • The minimum release validation is the targeted pytest suite, then the repository's available lint/type/test checks, then fixture regeneration and quality-summary inspection. The release should fail if schema generation, identifier uniqueness, quarantine accounting, or documented row counts do not reconcile.

Out of Scope

  • Replacing UFCStats or UFC.com with a different provider.
  • Building a web dashboard, annotation UI, or hosted data-quality service.
  • Reconstructing historical per-round data when the source page does not provide it.
  • Guessing missing IDs from names, event labels, dates, or third-party fuzzy matching.
  • Automatically correcting OCR output based only on heuristics or model confidence.
  • Reprocessing every historical image by hand. Manual work is limited to the benchmark sample and records routed to review.
  • Guaranteeing that every source value is factually correct beyond the documented source and validation checks.
  • Adding unrelated fighter rankings, betting odds, judging analysis, or new derived performance metrics.
  • Changing the meaning of the existing decisive-only analytical table or removing the all-bouts treatment introduced for draws and no contests.
  • Introducing a database, API, schema migration, or production infrastructure change. CSV/data artifacts and the existing Python pipeline remain the delivery surface.

Further Notes

The issue's example of impossible negative-looking values is exactly the kind of failure this work must make visible. The right outcome is not to silently clip or overwrite the value; it is to preserve the source observation, identify the failed check, exclude it from trusted analytical outputs, and make the source record available for repair.

The reconciliation principle is: incompleteness stays local; contradiction is symmetric. A missing or malformed child round does not automatically destroy an independently valid fight total. But when fight totals and round rows both exist and violate an exact invariant, the pipeline cannot know which representation is wrong, so neither may enter trusted analytical outputs.

The system-level flow is:

UFCStats pages ──┐
                 ├─> normalized source records ──> identity + domain validation ──┬─> trusted derived tables
UFC.com images ──┘                                                               ├─> warning flags
                                                                                 ├─> quarantine records
                                                                                 └─> quality/OCR report

The release should include a short data-quality snapshot in the README: source coverage dates, number of bouts and round rows, number of records with stable IDs, number of warnings/quarantines by check code, scorecard match coverage, and the benchmark's field/record accuracy with its confidence interval.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentunplannedValid work, but not scheduled — author is currently time-limited

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions