Skip to content

SHA-22: FILE_TYPE Course 规则(J) - #56

Merged
shaonianche merged 1 commit into
mainfrom
agent/code-expert/418241d9
Aug 1, 2026
Merged

SHA-22: FILE_TYPE Course 规则(J)#56
shaonianche merged 1 commit into
mainfrom
agent/code-expert/418241d9

Conversation

@shaonianche

Copy link
Copy Markdown
Owner

Summary

  • Add FILE_TYPE validation for file_id.type = Course (exactly one course, ≥1 lap, ≥1 record, timer start + stop events).
  • Required fields: course_name; lap summary fields; record positions (track geometry; timestamps optional); event timer fields; Course file_id without requiring product (Stages Link omits it).
  • Keep fail-closed for Workout and other unimplemented types; Activity rules unchanged.
  • Tests: synthetic Course builder cases + stagesLink_28832.fit fixture.
  • Docs: README capability matrix, design-doc remaining gaps (J Done), Towncrier news/SHA-22.feature.

Multica

  • Issue: SHA-22 (Stage 4 · J)
  • Parent: SHA-12

Test plan

  • uv run python -m pytest fit_tool/tests/ -q — 419 passed, 1 skipped
  • Course fixture stagesLink_28832.fit passes FILE_TYPE
  • Illegal Course structures produce clear FILE_TYPE errors
  • Activity FILE_TYPE still green; Workout still fail-closed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4dc589fac7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fit_tool/validation.py Outdated
omit timestamps. Course name is the ``course_name`` property (field ``name``).
"""
required_fields = {
MesgNum.COURSE.value: ('course_name',),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require the course sport field

When a Course message has course_name but leaves sport unset, FILE_TYPE validation reports no errors and FitFileBuilder(strict=True) serializes the file, even though sport is required Course metadata (and the repository's Course authoring path sets it). Include sport in this tuple so strict validation does not accept malformed Course files.

Useful? React with 👍 / 👎.

Comment thread fit_tool/validation.py Outdated
Comment on lines +601 to +607
has_timer_start = any(
_is_course_timer_event(message, frozenset({EventType.START.value}))
for message in data_messages
)
has_timer_stop = any(
_is_course_timer_event(message, _COURSE_TIMER_STOP_TYPES)
for message in data_messages

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the ordering of Course timer events

When a timer stop precedes the timer start—or both events occur before all track records—these independent presence checks still accept the Course, so strict mode can emit a file whose timer interval does not delimit the course activity. Track the matching event positions/timestamps and require the start to precede the records and terminal stop.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.30435% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.83%. Comparing base (98a80b8) to head (960bf95).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
fit_tool/validation.py 91.30% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #56      +/-   ##
==========================================
- Coverage   93.25%   92.83%   -0.43%     
==========================================
  Files          35       35              
  Lines        2581     2625      +44     
  Branches      421      437      +16     
==========================================
+ Hits         2407     2437      +30     
- Misses        105      112       +7     
- Partials       69       76       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

Rebased onto main after Workout (#55). Course structure + fields,
course.sport required, timer start/stop ordering around track records.

Co-authored-by: multica-agent <github@multica.ai>
@shaonianche
shaonianche force-pushed the agent/code-expert/418241d9 branch from d97750e to 960bf95 Compare August 1, 2026 13:44

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 960bf95fd7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fit_tool/validation.py
Comment on lines +754 to +758
# Ordering: timer start before track records and before terminal timer stop
# (message order; timestamps may be equal in real Garmin exports).
if start_positions and stop_positions:
first_start = min(start_positions)
last_stop = max(stop_positions)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject chronologically reversed Course timers

When messages are physically ordered start → records → stop but their timestamps are reversed, such as start=200 and stop=100, all three position checks pass and strict mode accepts a chronologically impossible timer interval. Equal timestamps can remain legal while still requiring the start timestamp to be no later than the terminal stop timestamp. Fresh evidence beyond the earlier ordering comment is that this newly added block explicitly checks only message positions even though timer timestamps are required.

Useful? React with 👍 / 👎.

Comment on lines +67 to +69
def add_minimal_course_messages(builder, *, include_course=True, include_lap=True,
include_record=True, include_timer_start=True,
include_timer_stop=True, course_name='test course'):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore Workout validation regression coverage

Adding the Course helper here replaces add_minimal_workout_messages rather than supplementing it, and the same diff deletes the positive strict/report Workout tests, the missing-workout/step test, and the missing-field case. Consequently, no test now exercises successful or failed Workout FILE_TYPE validation, so a regression to the SHA-21 behavior can pass the full suite; retain the Workout helper and cases alongside the new Course coverage.

AGENTS.md reference: AGENTS.md:L144-L145

Useful? React with 👍 / 👎.

Comment thread README.md
| **Supported** | Common Activity and Workout read/write via typed profile messages; `FitFileBuilder` encode path; **header CRC** (14-byte headers) and **file-level CRC** on load / stream exhaustion (`check_crc=True` default); developer fields for common declaration patterns; streaming iterators (`FitFile.iter_file` / `iter_stream`); CSV export (`to_csv` / `to_rows`); Course and similar message types when you construct them yourself; **chained multi-segment** FIT decode via `from_bytes` / `from_file` (all segments projected into `records`); **compressed timestamp** reconstruction into field 253; **subfield resolution** (ref-field match → type / scale / offset / units; multi-ref AND; first match wins); **component expansion** for all Profile main-field sources (generated registry) **and** components on the active subfield; nested expansion + accumulator rollover; **unknown field ids** on known messages as `UnknownField` (decoded values + `raw_bytes`); **encode modes** `EncodeMode.PRESERVE` (default; unedited bit-identical + post-edit dirty re-project) and `EncodeMode.CANONICAL` (full re-project, normalized sizes/CRCs; optional `strict=True` precheck) — see [Encode policies](#encode-policies) |
| **Partial** | Unknown global messages via `GenericMessage` (readable; unedited preserve keeps wire bytes; post-edit re-encodes dirty records only); composable validation API (`validate_fit_file` / `FitFile.validate`) with WIRE + PROFILE + **Activity and Workout** FILE_TYPE levels — **PROFILE validation is CORE today** (developer-field subset + **ambiguous subfield** ERROR); opt-in **PRESERVATION** level reports unknown-field `raw_bytes` loss after edits; architecture decision **O1** keeps bundled `Profile.xlsx` as the full metadata source of truth with future DOMAIN/FULL scopes (FULL opt-in, not default strict) — see [`docs/FIT_CONFORMANCE_DESIGN.md`](docs/FIT_CONFORMANCE_DESIGN.md) §3.1; Builder `strict=True` is a thin wrapper over the same checks (WIRE+PROFILE+FILE_TYPE only) |
| **Not supported / incomplete** | Full PROFILE semantics (native field requirements, enums, units beyond subfield scale/units); file-type rules for types other than Activity/Workout (e.g. Course FILE_TYPE fails closed); intentional `repair()` API (strict path never silent-repairs); bit-identical rewrite of compressed-timestamp dirty records when field 253 is not on the definition (strict raises; non-strict keeps compressed header) |
| **Partial** | Unknown global messages via `GenericMessage` (readable; unedited preserve keeps wire bytes; post-edit re-encodes dirty records only); composable validation API (`validate_fit_file` / `FitFile.validate`) with WIRE + PROFILE + **Activity, Workout, and Course** FILE_TYPE levels — **PROFILE validation is CORE today** (developer-field subset + **ambiguous subfield** ERROR); opt-in **PRESERVATION** level reports unknown-field `raw_bytes` loss after edits; architecture decision **O1** keeps bundled `Profile.xlsx` as the full metadata source of truth with future DOMAIN/FULL scopes (FULL opt-in, not default strict) — see [`docs/FIT_CONFORMANCE_DESIGN.md`](docs/FIT_CONFORMANCE_DESIGN.md) §3.1; Builder `strict=True` is a thin wrapper over the same checks (WIRE+PROFILE+FILE_TYPE only) |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Synchronize the linked Course conformance status

After this row advertises Course FILE_TYPE validation as implemented, the linked docs/FIT_CONFORMANCE_DESIGN.md still states at lines 25–26, 50, 542–555, and 741–742 that Course validation is a gap and fails closed. This leaves the repository's status source directly contradicting the newly published capability matrix; update those entries when flipping the README claim, as the design document itself requires at line 51.

Useful? React with 👍 / 👎.

@shaonianche
shaonianche merged commit 4339fd3 into main Aug 1, 2026
7 of 9 checks passed
shaonianche added a commit that referenced this pull request Aug 1, 2026
ProfileScope CORE/DOMAIN/FULL, gen field catalog, native checks.

Codex P2: exclude activity_class (ranged/bitfield) from closed-enum catalog.
Rebased onto main after #55/#56.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant