git-fit 0.17.8 → 0.17.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63105e40b78dfc16931efa9ffe2b88e9ac53b576f2ce14857401d818d6375c5a
4
- data.tar.gz: 792778796644fd2117a9e57b1cb330b2a337ac02010f4297e170639f64b9fa4b
3
+ metadata.gz: 49d811e8b7771ebd21053d0d274c9ac26b96f8d453bdc627623e5571689c5ccf
4
+ data.tar.gz: ececedeedda8e83e8689e9a2f63ba227bed19dc430600a385328586a01728b66
5
5
  SHA512:
6
- metadata.gz: e6787fb194f869eb5dd48226b617e2d9757cbfb9ee6cfc657018f9e0f868f3018bb66b3a63718ef9d919836298ce0f3e612504bf77fc033a2286dcaaf7c9dd35
7
- data.tar.gz: 1ef52c06c6a9ff330e7e22dd0bb89152bcbeeb861b430a04ffa1841e8e0280ff6e859e2b12007090784e0e692be2fa0b6a6488b1eae825ca44130a3de2d87a5b
6
+ metadata.gz: 29d188b5016c849a7770768de9ccd7c82df9755cbcc93e0f0ad98f9461acd85285dc269d3b42018064e9ed030dc309584c53c72161b7b62605f4d5ae864391f7
7
+ data.tar.gz: bce6161efcb1322a59def53da6636abe4877917b66c4ae420f110d6b7464774537881e64bcade700ae3c47240c1f13c93a07cc4a41660703298d8eee3d583254
@@ -5,6 +5,8 @@ module GitFit
5
5
  class Backfill
6
6
  SOURCES = %w[igpsport xoss strava garmin garmin_cn xingzhe].freeze
7
7
  BATCH_SIZE = 100
8
+ # Fallback threshold when detail.json elevation is absent (issue #83: baro 2m / GPS 3m).
9
+ ELEV_THRESHOLD = 2.0
8
10
 
9
11
  def initialize(db, force: false)
10
12
  @db = db
@@ -129,20 +131,47 @@ module GitFit
129
131
  end
130
132
 
131
133
  raw = JSON.parse(File.read(raw_path))
132
- alts = raw.dig('stream', 'altitude') || []
134
+ alts = (raw.dig('stream', 'altitude') || raw.dig('data', 'altitude') || []).map(&:to_f)
133
135
  return nil if alts.size < 2
134
136
 
137
+ # detail.json stores the inner API payload ({workout:...}); legacy wraps it in
138
+ # {code,data:{workout:...},msg}. Prefer xingzhe's own processed elevation.
139
+ detail = xingzhe_detail_workout(natural_id)
140
+ dg = detail && detail['elevation_gain']
141
+ dl = detail && detail['elevation_loss']
142
+ dm = detail && detail['max_altitude']
143
+
144
+ gain = detail_gain?(dg) ? dg : elevation_gain_from_alts(alts, threshold: ELEV_THRESHOLD)
145
+ loss = detail_gain?(dl) ? dl : elevation_loss_from_alts(alts, threshold: ELEV_THRESHOLD)
146
+
147
+ # max: detail max_altitude is reliable (matches stream max in samples); else raw max.
148
+ # min: stream is per-track baro-offset, so only drop low spikes, keep the bulk.
149
+ filtered = self.class.filter_low_outliers(alts)
135
150
  {
136
- elevation_gain: elevation_gain_from_alts(alts)&.round(1),
137
- elevation_loss: elevation_loss_from_alts(alts)&.round(1),
138
- elevation_min: alts.min&.round(1),
139
- elevation_max: alts.max&.round(1),
151
+ elevation_gain: gain&.to_f&.round(1),
152
+ elevation_loss: loss&.to_f&.round(1),
153
+ elevation_min: (filtered.min || alts.min)&.round(1),
154
+ elevation_max: (dm.is_a?(Numeric) && dm > 0 ? dm : alts.max)&.round(1),
140
155
  }
141
156
  rescue StandardError => e
142
157
  warn "Elevation backfill [xingzhe][#{natural_id}]: #{e.message}"
143
158
  nil
144
159
  end
145
160
 
161
+ def xingzhe_detail_workout(natural_id)
162
+ path = File.join('data', 'raw', 'xingzhe', natural_id, 'detail.json')
163
+ return nil unless File.exist?(path)
164
+
165
+ d = JSON.parse(File.read(path))
166
+ d['workout'] || d.dig('data', 'workout')
167
+ rescue StandardError
168
+ nil
169
+ end
170
+
171
+ def detail_gain?(value)
172
+ value.is_a?(Numeric) && !value.zero?
173
+ end
174
+
146
175
  def fit_path_for(source, natural_id)
147
176
  case source
148
177
  when 'garmin', 'garmin_cn'
@@ -152,19 +181,19 @@ module GitFit
152
181
  end
153
182
  end
154
183
 
155
- def elevation_gain_from_alts(alts)
184
+ def elevation_gain_from_alts(alts, threshold: 0.0)
156
185
  return 0.0 if alts.size < 2
157
186
  (1...alts.size).sum do |i|
158
187
  d = alts[i] - alts[i - 1]
159
- d > 0 ? d : 0.0
188
+ d > threshold ? d : 0.0
160
189
  end
161
190
  end
162
191
 
163
- def elevation_loss_from_alts(alts)
192
+ def elevation_loss_from_alts(alts, threshold: 0.0)
164
193
  return 0.0 if alts.size < 2
165
194
  (1...alts.size).sum do |i|
166
195
  d = alts[i - 1] - alts[i]
167
- d > 0 ? d : 0.0
196
+ d > threshold ? d : 0.0
168
197
  end
169
198
  end
170
199
 
@@ -182,6 +211,32 @@ module GitFit
182
211
  def skip_row(_run_id)
183
212
  @skipped += 1
184
213
  end
214
+
215
+ class << self
216
+ # Drop only implausibly-low altitude spikes (baro dropouts), leaving the
217
+ # genuinely shifted bulk intact. Lower fence = median - factor*1.4826*MAD.
218
+ def filter_low_outliers(altitudes, factor = 2.0)
219
+ return altitudes if altitudes.size < 4
220
+
221
+ med = median(altitudes)
222
+ m = mad(altitudes)
223
+ return altitudes if m.zero?
224
+
225
+ fence = med - factor * 1.4826 * m
226
+ altitudes.reject { |x| x < fence }
227
+ end
228
+
229
+ def median(values)
230
+ s = values.sort
231
+ n = s.size
232
+ n.odd? ? s[n / 2] : (s[n / 2 - 1] + s[n / 2]) / 2.0
233
+ end
234
+
235
+ def mad(values)
236
+ med = median(values)
237
+ median(values.map { |x| (x - med).abs })
238
+ end
239
+ end
185
240
  end
186
241
  end
187
242
  end
@@ -352,8 +352,9 @@ module GitFit
352
352
  end
353
353
 
354
354
  def derive_elevation_min(stream, detail_workout)
355
- if stream && (alt = stream['altitude']) && alt.is_a?(Array) && alt.any?
356
- v = alt.compact.min
355
+ if stream && (alt = stream['altitude']) && alt.is_a?(Array) && alt.size >= 2
356
+ alts = alt.compact.map(&:to_f)
357
+ v = GitFit::Elevation::Backfill.filter_low_outliers(alts).min
357
358
  return v.to_f if v
358
359
  end
359
360
  if detail_workout && (segs = detail_workout['segments_km']) && segs.is_a?(Array) && segs.any?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitFit
4
- VERSION = '0.17.8'
4
+ VERSION = '0.17.9'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-fit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.8
4
+ version: 0.17.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lax