git-fit 0.7.9 → 0.8.0

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/lib/git-fit.rb +44 -42
  3. data/lib/git_fit/cli/export.rb +8 -6
  4. data/lib/git_fit/cli/geo_cli.rb +10 -8
  5. data/lib/git_fit/cli/gh_cli.rb +24 -22
  6. data/lib/git_fit/cli/import_cli.rb +40 -38
  7. data/lib/git_fit/cli/install_cli.rb +10 -8
  8. data/lib/git_fit/cli/sync.rb +9 -7
  9. data/lib/git_fit/cli.rb +29 -27
  10. data/lib/git_fit/config.rb +42 -28
  11. data/lib/git_fit/config_template.rb +2 -0
  12. data/lib/git_fit/db/activity.rb +2 -0
  13. data/lib/git_fit/db/cli.rb +11 -9
  14. data/lib/git_fit/db/connection.rb +8 -6
  15. data/lib/git_fit/db/rebuild.rb +5 -3
  16. data/lib/git_fit/export/defaults.rb +14 -12
  17. data/lib/git_fit/export/json.rb +5 -3
  18. data/lib/git_fit/export.rb +4 -2
  19. data/lib/git_fit/fit/decoder.rb +9 -7
  20. data/lib/git_fit/fit.rb +3 -1
  21. data/lib/git_fit/geo/amap_client.rb +15 -13
  22. data/lib/git_fit/geo/coord_transform.rb +2 -0
  23. data/lib/git_fit/geo/division_detector.rb +31 -26
  24. data/lib/git_fit/geo/nominatim_client.rb +14 -12
  25. data/lib/git_fit/geo/polyline.rb +4 -2
  26. data/lib/git_fit/geo/reverse_geocode.rb +11 -9
  27. data/lib/git_fit/geo.rb +8 -6
  28. data/lib/git_fit/import/apple_health.rb +84 -82
  29. data/lib/git_fit/import/local_file.rb +45 -42
  30. data/lib/git_fit/import.rb +4 -2
  31. data/lib/git_fit/install/actions.rb +15 -13
  32. data/lib/git_fit/parser/base.rb +12 -10
  33. data/lib/git_fit/parser/fit.rb +11 -9
  34. data/lib/git_fit/parser/gpx.rb +17 -15
  35. data/lib/git_fit/parser/tcx.rb +22 -20
  36. data/lib/git_fit/parser.rb +6 -4
  37. data/lib/git_fit/privacy/polyline_filter.rb +6 -4
  38. data/lib/git_fit/source/base.rb +25 -23
  39. data/lib/git_fit/source/tally.rb +9 -7
  40. data/lib/git_fit/source.rb +4 -2
  41. data/lib/git_fit/sport_mapper.rb +23 -21
  42. data/lib/git_fit/sync/base.rb +29 -27
  43. data/lib/git_fit/sync/garmin.rb +5 -3
  44. data/lib/git_fit/sync/garmin_base.rb +145 -143
  45. data/lib/git_fit/sync/garmin_base_di.rb +42 -40
  46. data/lib/git_fit/sync/garmin_cn.rb +8 -6
  47. data/lib/git_fit/sync/igpsport.rb +43 -41
  48. data/lib/git_fit/sync/keep.rb +98 -96
  49. data/lib/git_fit/sync/lorem.rb +31 -29
  50. data/lib/git_fit/sync/runner.rb +14 -11
  51. data/lib/git_fit/sync/strava.rb +61 -52
  52. data/lib/git_fit/sync/xingzhe.rb +52 -50
  53. data/lib/git_fit/sync/xoss.rb +68 -66
  54. data/lib/git_fit/timezone/resolver.rb +5 -3
  55. data/lib/git_fit/util/tally.rb +10 -8
  56. data/lib/git_fit/util.rb +2 -0
  57. data/lib/git_fit/version.rb +3 -1
  58. metadata +1 -1
@@ -1,20 +1,22 @@
1
- require_relative "base"
2
- require "faraday"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require 'faraday'
3
5
 
4
6
  module GitFit
5
7
  module Sync
6
8
  class SyncError < RuntimeError; end unless const_defined?(:SyncError)
7
9
 
8
10
  class Strava < Base
9
- BASE_URL = "https://www.strava.com/api/v3"
11
+ BASE_URL = 'https://www.strava.com/api/v3'
10
12
 
11
13
  register_adapter
12
14
  register_config :client_id, :client_secret, :refresh_token
13
15
 
14
16
  def authenticate
15
- return false unless @config["client_id"].to_s != "" &&
16
- @config["client_secret"].to_s != "" &&
17
- @config["refresh_token"].to_s != ""
17
+ return false unless @config['client_id'].to_s != '' &&
18
+ @config['client_secret'].to_s != '' &&
19
+ @config['refresh_token'].to_s != ''
18
20
  @access_token = refresh_access_token
19
21
  @access_token ? true : false
20
22
  end
@@ -27,9 +29,9 @@ module GitFit
27
29
  activities = fetch_activities(@access_token, page)
28
30
  break if activities.empty?
29
31
  activities.each do |act|
30
- act_id = act["id"].to_s
32
+ act_id = act['id'].to_s
31
33
  next if existing.include?(act_id) && raw_file_exists?(act_id)
32
- type = map_sport_type(act["type"], act["sport_type"])
34
+ type = map_sport_type(act['type'], act['sport_type'])
33
35
  next if skip_type?(type)
34
36
  ids << { id: act_id, type: type, summary: act }
35
37
  end
@@ -46,35 +48,41 @@ module GitFit
46
48
  private
47
49
 
48
50
  def refresh_access_token
49
- resp = Faraday.post("https://www.strava.com/oauth/token") do |req|
51
+ resp = Faraday.post('https://www.strava.com/oauth/token') do |req|
50
52
  req.body = {
51
- client_id: @config["client_id"],
52
- client_secret: @config["client_secret"],
53
- refresh_token: @config["refresh_token"],
54
- grant_type: "refresh_token"
53
+ client_id: @config['client_id'],
54
+ client_secret: @config['client_secret'],
55
+ refresh_token: @config['refresh_token'],
56
+ grant_type: 'refresh_token',
55
57
  }
56
58
  end
57
59
  raise SyncError, "Strava token refresh failed: #{resp.status}" unless resp.success?
58
- JSON.parse(resp.body)["access_token"]
60
+ JSON.parse(resp.body)['access_token']
59
61
  end
60
62
 
61
63
  def sync_activity(act, act_id, token)
62
- type = map_sport_type(act["type"], act["sport_type"])
64
+ type = map_sport_type(act['type'], act['sport_type'])
63
65
  return false if skip_type?(type)
64
66
 
65
67
  detail = fetch_detail(act_id, token)
66
68
  return false unless detail
67
69
 
68
- external_id = detail["external_id"]
70
+ external_id = detail['external_id']
69
71
 
70
72
  streams = fetch_streams(act_id, token)
71
73
 
72
- write_source_archive({ source: "strava", detail: detail, streams: streams }, act_id)
74
+ write_source_archive({ source: 'strava', detail: detail, streams: streams }, act_id)
73
75
 
74
76
  l2 = streams_to_l2(detail, streams)
75
77
  write_standardized_json(l2, act_id) if l2
76
78
 
77
- polyline = l2 ? Geo::Polyline.encode(l2.map { |pt| [pt[:positionLat], pt[:positionLong]] }) : act.dig("map", "summary_polyline")
79
+ polyline = if l2
80
+ Geo::Polyline.encode(l2.map do |pt|
81
+ [pt[:positionLat], pt[:positionLong]]
82
+ end)
83
+ else
84
+ act.dig('map', 'summary_polyline')
85
+ end
78
86
 
79
87
  attrs = build_attrs(act, type, polyline, external_id: external_id)
80
88
  upsert_activity(attrs)
@@ -83,7 +91,7 @@ module GitFit
83
91
 
84
92
  def fetch_activities(token, page)
85
93
  resp = Faraday.get("#{BASE_URL}/athlete/activities") do |req|
86
- req.headers["Authorization"] = "Bearer #{token}"
94
+ req.headers['Authorization'] = "Bearer #{token}"
87
95
  req.params = { page: page, per_page: 100 }
88
96
  end
89
97
  return [] unless resp.success?
@@ -92,7 +100,7 @@ module GitFit
92
100
 
93
101
  def fetch_detail(act_id, token)
94
102
  resp = Faraday.get("#{BASE_URL}/activities/#{act_id}") do |req|
95
- req.headers["Authorization"] = "Bearer #{token}"
103
+ req.headers['Authorization'] = "Bearer #{token}"
96
104
  end
97
105
  return nil unless resp.success?
98
106
  JSON.parse(resp.body)
@@ -100,8 +108,9 @@ module GitFit
100
108
 
101
109
  def fetch_streams(act_id, token)
102
110
  resp = Faraday.get("#{BASE_URL}/activities/#{act_id}/streams") do |req|
103
- req.headers["Authorization"] = "Bearer #{token}"
104
- req.params = { keys: "lat_lng,altitude,time,heartrate,cadence,watts,temp,velocity_smooth,distance", key_by_type: true }
111
+ req.headers['Authorization'] = "Bearer #{token}"
112
+ req.params = { keys: 'lat_lng,altitude,time,heartrate,cadence,watts,temp,velocity_smooth,distance',
113
+ key_by_type: true }
105
114
  end
106
115
  return {} unless resp.success?
107
116
  JSON.parse(resp.body)
@@ -117,20 +126,20 @@ module GitFit
117
126
  end
118
127
 
119
128
  def streams_to_l2(detail, streams)
120
- polyline = detail.dig("map", "polyline")
129
+ polyline = detail.dig('map', 'polyline')
121
130
  return nil unless polyline
122
131
 
123
132
  coords = Geo::Polyline.decode(polyline)
124
133
  return nil if coords.empty?
125
134
 
126
- alt_data = streams["altitude"]&.dig("data")
127
- time_data = streams["time"]&.dig("data")
128
- hr_data = streams["heartrate"]&.dig("data")
129
- cad_data = streams["cadence"]&.dig("data")
130
- watts_data = streams["watts"]&.dig("data")
131
- temp_data = streams["temp"]&.dig("data")
132
- speed_data = streams["velocity_smooth"]&.dig("data")
133
- dist_data = streams["distance"]&.dig("data")
135
+ alt_data = streams['altitude']&.dig('data')
136
+ time_data = streams['time']&.dig('data')
137
+ hr_data = streams['heartrate']&.dig('data')
138
+ cad_data = streams['cadence']&.dig('data')
139
+ watts_data = streams['watts']&.dig('data')
140
+ temp_data = streams['temp']&.dig('data')
141
+ speed_data = streams['velocity_smooth']&.dig('data')
142
+ dist_data = streams['distance']&.dig('data')
134
143
 
135
144
  coords.each_with_index.map do |latlng, i|
136
145
  pt = { positionLat: latlng[0], positionLong: latlng[1] }
@@ -147,33 +156,33 @@ module GitFit
147
156
  end
148
157
 
149
158
  def build_attrs(act, category, polyline = nil, external_id: nil)
150
- start_t = Time.parse(act["start_date"]) rescue nil
151
- local_t = Time.parse(act["start_date_local"]) rescue nil
159
+ start_t = Time.parse(act['start_date']) rescue nil
160
+ local_t = Time.parse(act['start_date_local']) rescue nil
152
161
 
153
162
  {
154
163
  run_id: "strava_#{act["id"]}",
155
- name: act["name"],
156
- distance: act["distance"],
157
- moving_time: act["moving_time"],
158
- elapsed_time: act["elapsed_time"],
164
+ name: act['name'],
165
+ distance: act['distance'],
166
+ moving_time: act['moving_time'],
167
+ elapsed_time: act['elapsed_time'],
159
168
  sport_category: category,
160
- sport_type: (act["sport_type"] || act["type"])&.downcase,
169
+ sport_type: (act['sport_type'] || act['type'])&.downcase,
161
170
  start_date: start_t&.iso8601,
162
171
  start_date_local: local_t&.iso8601,
163
- location_country: act["location_country"],
172
+ location_country: act['location_country'],
164
173
  summary_polyline: polyline,
165
- average_heartrate: act["average_heartrate"],
166
- max_heartrate: act["max_heartrate"],
167
- average_cadence: act["average_cadence"],
174
+ average_heartrate: act['average_heartrate'],
175
+ max_heartrate: act['max_heartrate'],
176
+ average_cadence: act['average_cadence'],
168
177
  max_cadence: nil,
169
- average_power: act["average_watts"],
170
- max_power: act["max_watts"],
171
- calories: act["calories"],
172
- average_temperature: act["average_temp"],
173
- average_speed: act["average_speed"],
174
- elevation_gain: act["total_elevation_gain"],
175
- source: "strava",
176
- external_id: external_id
178
+ average_power: act['average_watts'],
179
+ max_power: act['max_watts'],
180
+ calories: act['calories'],
181
+ average_temperature: act['average_temp'],
182
+ average_speed: act['average_speed'],
183
+ elevation_gain: act['total_elevation_gain'],
184
+ source: 'strava',
185
+ external_id: external_id,
177
186
  }
178
187
  end
179
188
 
@@ -186,8 +195,8 @@ module GitFit
186
195
  end
187
196
 
188
197
  def existing_run_ids
189
- @db[:activities].where(source: "strava").select_map(:run_id)
190
- .map { |id| id.sub("strava_", "") }
198
+ @db[:activities].where(source: 'strava').select_map(:run_id)
199
+ .map { |id| id.sub('strava_', '') }
191
200
  end
192
201
  end
193
202
  end
@@ -1,33 +1,35 @@
1
- require_relative "base"
2
- require "fileutils"
3
- require "openssl"
4
- require "tzinfo"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require 'fileutils'
5
+ require 'openssl'
6
+ require 'tzinfo'
5
7
 
6
8
  module GitFit
7
9
  module Sync
8
10
  class XingZhe < Base
9
- BASE_HOST = "www.imxingzhe.com"
10
- LOGIN_PATH = "/api/v1/user/login/"
11
- WORKOUTS_PATH = "/api/v1/pgworkout/"
12
- STREAM_PATH = "/api/v1/pgworkout/"
13
- DETAIL_PATH = "/api/v1/pgworkout/"
14
- RSA_PUBKEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDmuQkBbijudDAJgfffDeeIButqWHZvUwcRuvWdg89393FSdz3IJUHc0rgI/S3WuU8N0VePJLmVAZtCOK4qe4FY/eKmWpJmn7JfXB4HTMWjPVoyRZmSYjW4L8GrWmh51Qj7DwpTADadF3aq04o+s1b8LXJa8r6+TIqqL5WUHtRqmQIDAQAB"
11
+ BASE_HOST = 'www.imxingzhe.com'
12
+ LOGIN_PATH = '/api/v1/user/login/'
13
+ WORKOUTS_PATH = '/api/v1/pgworkout/'
14
+ STREAM_PATH = '/api/v1/pgworkout/'
15
+ DETAIL_PATH = '/api/v1/pgworkout/'
16
+ RSA_PUBKEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDmuQkBbijudDAJgfffDeeIButqWHZvUwcRuvWdg89393FSdz3IJUHc0rgI/S3WuU8N0VePJLmVAZtCOK4qe4FY/eKmWpJmn7JfXB4HTMWjPVoyRZmSYjW4L8GrWmh51Qj7DwpTADadF3aq04o+s1b8LXJa8r6+TIqqL5WUHtRqmQIDAQAB'
15
17
 
16
18
  SPORT_MAP = {
17
- 3 => "ride", 1 => "run", 2 => "hike",
18
- 5 => "swim", 6 => "walk", 11 => "ski", 13 => "workout"
19
+ 3 => 'ride', 1 => 'run', 2 => 'hike',
20
+ 5 => 'swim', 6 => 'walk', 11 => 'ski', 13 => 'workout'
19
21
  }.freeze
20
22
 
21
- RAW_EXT = "json"
23
+ RAW_EXT = 'json'
22
24
 
23
25
  register_adapter
24
- config_key "xingzhe"
26
+ config_key 'xingzhe'
25
27
  register_config :email, :password
26
28
 
27
29
  def initialize(...)
28
30
  super
29
- @email = @config["email"]
30
- @password = @config["password"]
31
+ @email = @config['email']
32
+ @password = @config['password']
31
33
  end
32
34
 
33
35
  def before_call
@@ -48,9 +50,9 @@ module GitFit
48
50
  acts = fetch_page(offset, limit)
49
51
  break if acts.nil? || acts.empty?
50
52
  acts.each do |act|
51
- aid = act["id"].to_s
53
+ aid = act['id'].to_s
52
54
  next if existing.include?(aid) && raw_file_exists?(aid)
53
- type = map_sport(act["sport"])
55
+ type = map_sport(act['sport'])
54
56
  next if skip_type?(type)
55
57
  ids << { id: aid, type: type, summary: act }
56
58
  end
@@ -88,17 +90,17 @@ module GitFit
88
90
  encrypted = rsa_encrypt(@password)
89
91
  resp = http_request(:post, LOGIN_PATH,
90
92
  body: JSON.generate({ account: @email, password: encrypted }),
91
- headers: { "Content-Type" => "application/json" }
93
+ headers: { 'Content-Type' => 'application/json' }
92
94
  )
93
95
  return false unless resp.code.to_i == 200
94
96
 
95
97
  data = JSON.parse(resp.body)
96
- return false unless data["code"] == 0
98
+ return false unless data['code'] == 0
97
99
 
98
100
  cookie = extract_cookie(resp)
99
101
  return false unless cookie
100
102
 
101
- @headers = { "Cookie" => cookie, "Accept" => "application/json" }
103
+ @headers = { 'Cookie' => cookie, 'Accept' => 'application/json' }
102
104
  true
103
105
  end
104
106
 
@@ -109,7 +111,7 @@ module GitFit
109
111
  end
110
112
 
111
113
  def extract_cookie(resp)
112
- set_cookie = resp["set-cookie"]
114
+ set_cookie = resp['set-cookie']
113
115
  return nil unless set_cookie
114
116
  m = set_cookie.match(/sessionid=([^;]+)/)
115
117
  m ? "sessionid=#{m[1]}" : nil
@@ -126,7 +128,7 @@ module GitFit
126
128
  )
127
129
  return nil unless resp.code.to_i == 200
128
130
 
129
- JSON.parse(resp.body).dig("data", "data")
131
+ JSON.parse(resp.body).dig('data', 'data')
130
132
  end
131
133
 
132
134
  def tz_resolver
@@ -142,48 +144,48 @@ module GitFit
142
144
  tz_string = tz_resolver.resolve(lat, lng)
143
145
  tz = TZInfo::Timezone.get(tz_string)
144
146
  tz.utc_to_local(start_utc).iso8601
145
- rescue => e
147
+ rescue StandardError => e
146
148
  warn "XingZhe: timezone resolution failed: #{e.message}"
147
149
  start_utc.iso8601
148
150
  end
149
151
 
150
152
  def process_activity(act, type)
151
- aid = act["id"].to_s
152
- start_t = Time.at(act["start_time"] / 1000.0).utc rescue nil
153
+ aid = act['id'].to_s
154
+ start_t = Time.at(act['start_time'] / 1000.0).utc rescue nil
153
155
  return false unless start_t
154
156
 
155
157
  attrs = {
156
158
  run_id: "xingzhe_#{aid}",
157
- name: act["title"] || "XingZhe Activity",
158
- distance: act["distance"].to_f,
159
- moving_time: (t = act["duration"].to_i) > 0 ? t : nil,
159
+ name: act['title'] || 'XingZhe Activity',
160
+ distance: act['distance'].to_f,
161
+ moving_time: (t = act['duration'].to_i) > 0 ? t : nil,
160
162
  elapsed_time: t > 0 ? t : nil,
161
163
  sport_category: type,
162
- sport_type: act["sport"].to_s,
164
+ sport_type: act['sport'].to_s,
163
165
  start_date: start_t.iso8601,
164
166
  start_date_local: start_t.iso8601,
165
- average_speed: (act["avg_speed"].to_f / 3.6).round(2).nonzero?,
166
- elevation_gain: act["elevation_gain"].to_f.nonzero?,
167
- source: "xingzhe"
167
+ average_speed: (act['avg_speed'].to_f / 3.6).round(2).nonzero?,
168
+ elevation_gain: act['elevation_gain'].to_f.nonzero?,
169
+ source: 'xingzhe',
168
170
  }
169
171
 
170
172
  pts = nil
171
173
  stream = fetch_stream(aid)
172
174
  if stream
173
175
  write_source_archive({
174
- source: "xingzhe",
175
- fetchedAt: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
176
- stream: stream
176
+ source: 'xingzhe',
177
+ fetchedAt: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
178
+ stream: stream,
177
179
  }, aid)
178
- if (locs = stream["location"]) && locs.size >= 2
180
+ if (locs = stream['location']) && locs.size >= 2
179
181
  pts = locs.map { |lng, lat| [lat, lng] }
180
182
  write_std(pts, stream, aid)
181
183
  attrs[:summary_polyline] = stream_to_polyline(aid)
182
184
  end
183
185
  end
184
186
 
185
- if attrs[:summary_polyline].nil? && (detail = fetch_activity_detail(aid)) && (segments = detail["segments_km"])
186
- pts = segments.map { |s| [s["latitude"], s["longitude"]] }
187
+ if attrs[:summary_polyline].nil? && (detail = fetch_activity_detail(aid)) && (segments = detail['segments_km'])
188
+ pts = segments.map { |s| [s['latitude'], s['longitude']] }
187
189
  attrs[:summary_polyline] = Geo::Polyline.encode(pts) if pts.size >= 2
188
190
  end
189
191
 
@@ -201,7 +203,7 @@ module GitFit
201
203
  resp = http_request(:get, "#{STREAM_PATH}#{id}/stream/", headers: @headers || {})
202
204
  return nil unless resp.code.to_i == 200
203
205
 
204
- JSON.parse(resp.body).dig("data")
206
+ JSON.parse(resp.body).dig('data')
205
207
  rescue JSON::ParserError
206
208
  nil
207
209
  end
@@ -209,17 +211,17 @@ module GitFit
209
211
  def write_std(pts, stream, id)
210
212
  dir = std_dir
211
213
  FileUtils.mkdir_p(dir)
212
- ts_arr = stream["timestamp"] || []
213
- alt_arr = stream["altitude"] || []
214
- spd_arr = stream["speed"] || []
215
- dst_arr = stream["distance"] || []
214
+ ts_arr = stream['timestamp'] || []
215
+ alt_arr = stream['altitude'] || []
216
+ spd_arr = stream['speed'] || []
217
+ dst_arr = stream['distance'] || []
216
218
 
217
219
  extended = pts.each_with_index.map do |(lat, lng), i|
218
220
  ts_val = ts_arr[i]
219
221
  {
220
222
  latitude: lat, longitude: lng,
221
223
  altitude: alt_arr[i],
222
- timestamp: ts_val ? Time.at(ts_val).utc.strftime("%Y-%m-%dT%H:%M:%SZ") : nil,
224
+ timestamp: ts_val ? Time.at(ts_val).utc.strftime('%Y-%m-%dT%H:%M:%SZ') : nil,
223
225
  speed: spd_arr[i],
224
226
  distance: dst_arr[i]
225
227
  }
@@ -233,7 +235,7 @@ module GitFit
233
235
 
234
236
  def stream_to_polyline(id)
235
237
  raw = JSON.parse(File.read(raw_path(id)))
236
- locs = raw.dig("stream", "location") || []
238
+ locs = raw.dig('stream', 'location') || []
237
239
  return nil if locs.size < 2
238
240
 
239
241
  pts = locs.map { |lng, lat| [lat, lng] }
@@ -247,16 +249,16 @@ module GitFit
247
249
  return nil unless resp.code.to_i == 200
248
250
 
249
251
  data = JSON.parse(resp.body)
250
- data.dig("data", "workout")
252
+ data.dig('data', 'workout')
251
253
  end
252
254
 
253
255
  def map_sport(sport)
254
- SPORT_MAP[sport] || "other"
256
+ SPORT_MAP[sport] || 'other'
255
257
  end
256
258
 
257
259
  def existing_run_ids
258
- @db[:activities].where(source: "xingzhe").select_map(:run_id)
259
- .map { |id| id.sub("xingzhe_", "") }
260
+ @db[:activities].where(source: 'xingzhe').select_map(:run_id)
261
+ .map { |id| id.sub('xingzhe_', '') }
260
262
  end
261
263
  end
262
264
  end