View raw

1 (** Parses a native Hevy CSV export into a {!Workout_import.batch}. 2 3 This is a web-layer adapter: it turns untrusted export text into the 4 application's import use case. It does not construct canonical evidence or 5 apply Heavy Duty judgment. It never depends on [hito.persistence]. The whole 6 export text is retained verbatim through 7 {!Hito_app.Workout_import.make_batch}, so provenance survives the parse. 8 9 The adapter enforces the strict native schema. The header must carry all 14 10 verified columns, in any order. Rows the adapter cannot promote — warmups, 11 non-strength data, unknown set types, malformed fields — become 12 {!Workout_import.warning} values, not sets. A row is dropped, never guessed 13 at. Rows group into workouts by the exact [(title, start_time, end_time)] 14 triple, in first-appearance order. *) 15 16 type error = 17 | Malformed_csv of { record : int; field : int; detail : string } 18 (** The CSV text does not conform to RFC4180. [record] and [field] are 19 1-based, as {!Csv.Failure} reports them. *) 20 | Missing_column of string 21 (** A required column is absent from the header. The first missing column 22 in canonical order is reported. *) 23 | Empty_file (** The export has no rows at all, not even a header. *) 24 25 val pp_error : Format.formatter -> error -> unit 26 27 val parse : 28 batch_id:Hito_app.Workout_import.batch_id -> 29 fingerprint:string -> 30 imported_at:Recovery.timestamp -> 31 utc_offset_seconds:int -> 32 string -> 33 (Hito_app.Workout_import.batch, error) result 34 (** [parse ~batch_id ~fingerprint ~imported_at ~utc_offset_seconds source] 35 parses [source] as a native Hevy export. 36 37 [utc_offset_seconds] is the offset of the wall-clock times in the export. 38 Hevy datetimes are local wall time, so UTC seconds = civil seconds - offset. 39 40 Returns [Error Empty_file] for empty input, [Error (Missing_column _)] when 41 the header lacks a required column, and [Error (Malformed_csv _)] when the 42 CSV text is not well formed. On success the batch retains [source] exactly. 43 Rows the adapter cannot use appear in {!Workout_import.batch_warnings} in 44 source order. *) 45