ox-tender-abstract 0.9.3 → 0.9.5
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 +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +18 -0
- data/SIDEKIQ_USAGE.md +341 -0
- data/lib/ox-tender-abstract.rb +14 -8
- data/lib/oxtenderabstract/archive_processor.rb +37 -25
- data/lib/oxtenderabstract/client.rb +20 -2
- data/lib/oxtenderabstract/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29f0092ce09cc7fd85cfdea5489dbe645e1d28f99a12a2f98e57a48761bf2f13
|
4
|
+
data.tar.gz: abb88a96187a2a0e11e16efdf067368e5e4cd20504e080384789504a8a530c21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7351bf939d3e42d07ae65f313e5cb0621732cfa5c9c4cc3d0bfa77581fd9c4b51d284844901dcd50eed4e93d1dddff6a79a8055b4f79c45c1fbbeca2efc24129
|
7
|
+
data.tar.gz: 8df04c15cd6381544847fce45624f148b1ea824812aa7717ff52a1fcf304e030cc8beb910b02bbfd294d84411397d10cd1e03fac7a90d061b3c600f41c76276c
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.4.
|
1
|
+
3.4.5
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
## [0.9.5] - 2025-08-06
|
2
|
+
|
3
|
+
### Fixed
|
4
|
+
|
5
|
+
- Fixed encoding compatibility error when processing API blocking messages
|
6
|
+
- Fixed missing `include_attachments` parameter in `search_tenders` and `search_tenders_with_auto_wait` methods
|
7
|
+
- Improved error handling for binary response data from API
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
|
11
|
+
- Enhanced encoding handling in `ArchiveProcessor` to safely handle UTF-8 and BINARY responses
|
12
|
+
- Restored `include_attachments` parameter to all search methods with default value `true`
|
13
|
+
|
14
|
+
### Added
|
15
|
+
|
16
|
+
- Better error logging for encoding issues
|
17
|
+
- Safe encoding conversion using `force_encoding('UTF-8').scrub`
|
18
|
+
|
1
19
|
## [0.9.3] - 2025-07-27
|
2
20
|
|
3
21
|
- Added support for parsing tender documents
|
data/SIDEKIQ_USAGE.md
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
# Использование OxTenderAbstract с Sidekiq
|
2
|
+
|
3
|
+
## Обработка блокировок API в отложенных задачах
|
4
|
+
|
5
|
+
При частых запросах к API zakupki.gov.ru сервер может заблокировать загрузку архивов на 10 минут. Библиотека теперь правильно обрабатывает такие блокировки и возвращает специальные результаты.
|
6
|
+
|
7
|
+
## Рекомендуемый Sidekiq Worker с автоматическим ожиданием
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class TenderImportWorker
|
11
|
+
include Sidekiq::Worker
|
12
|
+
|
13
|
+
# Простая настройка - библиотека сама управляет блокировками
|
14
|
+
sidekiq_options retry: 3
|
15
|
+
|
16
|
+
def perform(region, date, subsystem_type = 'PRIZ', document_type = 'epNotificationEF2020', resume_state = nil)
|
17
|
+
# Используем новый метод с автоматическим ожиданием
|
18
|
+
result = OxTenderAbstract.search_tenders_with_auto_wait(
|
19
|
+
org_region: region,
|
20
|
+
exact_date: date,
|
21
|
+
subsystem_type: subsystem_type,
|
22
|
+
document_type: document_type,
|
23
|
+
resume_state: resume_state
|
24
|
+
)
|
25
|
+
|
26
|
+
if result.failure?
|
27
|
+
# Обрабатываем только критические ошибки
|
28
|
+
handle_failure(result, region, date, subsystem_type, document_type)
|
29
|
+
else
|
30
|
+
process_tenders(result.data[:tenders])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def handle_failure(result, region, date, subsystem_type, document_type)
|
37
|
+
# С автоматическим ожиданием блокировки обрабатываются автоматически
|
38
|
+
# Нужно обрабатывать только реальные ошибки
|
39
|
+
logger.error "Tender import failed: #{result.error}"
|
40
|
+
raise StandardError, result.error
|
41
|
+
end
|
42
|
+
|
43
|
+
def process_tenders(tenders)
|
44
|
+
tenders.each do |tender|
|
45
|
+
save_tender_to_database(tender)
|
46
|
+
end
|
47
|
+
|
48
|
+
logger.info "Processed #{tenders.size} tenders"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
## Альтернативный Worker с ручным управлением
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class TenderImportWorkerManual
|
57
|
+
include Sidekiq::Worker
|
58
|
+
|
59
|
+
# Настраиваем повторные попытки с увеличенной задержкой для блокировок
|
60
|
+
sidekiq_options retry: 5
|
61
|
+
|
62
|
+
def perform(region, date, subsystem_type = 'PRIZ', document_type = 'epNotificationEF2020', resume_state = nil)
|
63
|
+
# Отключаем автоматическое ожидание для ручного управления
|
64
|
+
OxTenderAbstract.configure do |config|
|
65
|
+
config.auto_wait_on_block = false
|
66
|
+
end
|
67
|
+
|
68
|
+
result = OxTenderAbstract.search_tenders_with_auto_wait(
|
69
|
+
org_region: region,
|
70
|
+
exact_date: date,
|
71
|
+
subsystem_type: subsystem_type,
|
72
|
+
document_type: document_type,
|
73
|
+
resume_state: resume_state
|
74
|
+
)
|
75
|
+
|
76
|
+
if result.failure?
|
77
|
+
handle_failure(result, region, date, subsystem_type, document_type)
|
78
|
+
else
|
79
|
+
process_tenders(result.data[:tenders])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def handle_failure(result, region, date, subsystem_type, document_type)
|
86
|
+
# Проверяем тип ошибки
|
87
|
+
if result.metadata[:error_type] == :blocked
|
88
|
+
# API заблокировал доступ на 10 минут
|
89
|
+
retry_after = result.metadata[:retry_after] || 600
|
90
|
+
|
91
|
+
logger.warn "Archive download blocked, retrying in #{retry_after} seconds"
|
92
|
+
|
93
|
+
# Перепланируем задачу через указанное время
|
94
|
+
TenderImportWorker.perform_in(
|
95
|
+
retry_after.seconds + 30, # +30 секунд для гарантии
|
96
|
+
region, date, subsystem_type, document_type
|
97
|
+
)
|
98
|
+
else
|
99
|
+
# Обычная ошибка - логируем и возможно повторяем стандартно
|
100
|
+
logger.error "Tender import failed: #{result.error}"
|
101
|
+
raise StandardError, result.error
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def process_tenders(tenders)
|
106
|
+
tenders.each do |tender|
|
107
|
+
# Обработка каждого тендера
|
108
|
+
save_tender_to_database(tender)
|
109
|
+
end
|
110
|
+
|
111
|
+
logger.info "Processed #{tenders.size} tenders"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
## Конфигурация автоматического ожидания
|
117
|
+
|
118
|
+
Библиотека теперь поддерживает встроенное автоматическое ожидание при блокировках:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
# config/initializers/ox_tender_abstract.rb
|
122
|
+
OxTenderAbstract.configure do |config|
|
123
|
+
config.token = ENV['ZAKUPKI_API_TOKEN']
|
124
|
+
|
125
|
+
# Настройки автоматического ожидания
|
126
|
+
config.auto_wait_on_block = true # Автоматически ждать при блокировке (по умолчанию true)
|
127
|
+
config.block_wait_time = 610 # Время ожидания в секундах (10 мин + 10 сек)
|
128
|
+
config.max_wait_time = 900 # Максимальное время ожидания (15 мин)
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
### Режимы работы
|
133
|
+
|
134
|
+
1. **Автоматическое ожидание** (`auto_wait_on_block = true`) - библиотека сама ждет и продолжает
|
135
|
+
2. **Ручное управление** (`auto_wait_on_block = false`) - возвращает состояние для продолжения в Sidekiq
|
136
|
+
|
137
|
+
## Настройка Sidekiq для обработки блокировок
|
138
|
+
|
139
|
+
### 1. Кастомная стратегия повторов
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
# config/initializers/sidekiq.rb
|
143
|
+
|
144
|
+
# Кастомная стратегия повторов для API блокировок
|
145
|
+
class TenderRetryStrategy
|
146
|
+
def call(worker, job, queue)
|
147
|
+
# Извлекаем информацию об ошибке
|
148
|
+
exception = job['error_message']
|
149
|
+
|
150
|
+
if exception&.include?('blocked')
|
151
|
+
# Для блокировок используем фиксированную задержку
|
152
|
+
return 600 # 10 минут
|
153
|
+
else
|
154
|
+
# Стандартная экспоненциальная задержка
|
155
|
+
return (job['retry_count'] ** 4) + 15
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
Sidekiq.configure_server do |config|
|
161
|
+
config.death_handlers << lambda do |job, ex|
|
162
|
+
# Логируем окончательно проваленные задачи
|
163
|
+
Rails.logger.error "Sidekiq job #{job['class']} failed permanently: #{ex.message}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
### 2. Настройка очередей с приоритетами
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
# config/sidekiq.yml
|
172
|
+
:queues:
|
173
|
+
- [critical, 2]
|
174
|
+
- [tenders_import, 1]
|
175
|
+
- [tenders_retry, 1]
|
176
|
+
- [default, 1]
|
177
|
+
```
|
178
|
+
|
179
|
+
### 3. Worker с интеллектуальными повторами
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
class SmartTenderImportWorker
|
183
|
+
include Sidekiq::Worker
|
184
|
+
|
185
|
+
sidekiq_options queue: 'tenders_import', retry: 3
|
186
|
+
|
187
|
+
# Кастомная логика повторов
|
188
|
+
sidekiq_retry_in do |count, exception|
|
189
|
+
case exception.message
|
190
|
+
when /blocked/
|
191
|
+
# Для блокировок ждем 10 минут
|
192
|
+
600
|
193
|
+
when /network error/i
|
194
|
+
# Для сетевых ошибок короткая задержка
|
195
|
+
30 * (count + 1)
|
196
|
+
else
|
197
|
+
# Стандартная задержка
|
198
|
+
60 * (count + 1)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def perform(params)
|
203
|
+
with_error_handling do
|
204
|
+
import_tenders(params)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def with_error_handling
|
211
|
+
yield
|
212
|
+
rescue => e
|
213
|
+
if e.message.include?('blocked')
|
214
|
+
# Перемещаем в специальную очередь для повторов
|
215
|
+
SmartTenderImportWorker.set(queue: 'tenders_retry')
|
216
|
+
.perform_in(610.seconds, { retry: true }.merge(params))
|
217
|
+
else
|
218
|
+
raise e
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
```
|
223
|
+
|
224
|
+
## Мониторинг и отладка
|
225
|
+
|
226
|
+
### 1. Логирование блокировок
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
class TenderImportLogger
|
230
|
+
def self.log_blocked_request(region, date, retry_after)
|
231
|
+
Rails.logger.warn {
|
232
|
+
"[TENDER_BLOCKED] Region: #{region}, Date: #{date}, Retry after: #{retry_after}s"
|
233
|
+
}
|
234
|
+
|
235
|
+
# Отправка в системы мониторинга
|
236
|
+
StatsD.increment('tender_import.blocked')
|
237
|
+
StatsD.histogram('tender_import.retry_delay', retry_after)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
```
|
241
|
+
|
242
|
+
### 2. Метрики для мониторинга
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
# В worker'е
|
246
|
+
def perform(params)
|
247
|
+
start_time = Time.current
|
248
|
+
|
249
|
+
begin
|
250
|
+
result = import_tenders(params)
|
251
|
+
StatsD.increment('tender_import.success')
|
252
|
+
StatsD.histogram('tender_import.duration', Time.current - start_time)
|
253
|
+
rescue => e
|
254
|
+
StatsD.increment('tender_import.error')
|
255
|
+
StatsD.increment("tender_import.error.#{error_type(e)}")
|
256
|
+
raise
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def error_type(exception)
|
261
|
+
case exception.message
|
262
|
+
when /blocked/ then 'blocked'
|
263
|
+
when /network/ then 'network'
|
264
|
+
when /parse/ then 'parse'
|
265
|
+
else 'unknown'
|
266
|
+
end
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
## Рекомендации
|
271
|
+
|
272
|
+
1. **Используйте разные очереди** для обычных и повторных задач
|
273
|
+
2. **Мониторьте частоту блокировок** - если они частые, уменьшите нагрузку
|
274
|
+
3. **Настройте алерты** на высокий процент блокировок
|
275
|
+
4. **Кэшируйте результаты** где возможно, чтобы уменьшить количество запросов
|
276
|
+
5. **Используйте rate limiting** на уровне приложения
|
277
|
+
|
278
|
+
## Пример полной настройки
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
# app/workers/tender_import_worker.rb
|
282
|
+
class TenderImportWorker
|
283
|
+
include Sidekiq::Worker
|
284
|
+
include Sidekiq::Throttled::Worker
|
285
|
+
|
286
|
+
# Ограничиваем количество одновременных запросов
|
287
|
+
sidekiq_throttle(
|
288
|
+
threshold: { limit: 5, period: 1.minute },
|
289
|
+
key: ->(region, date) { "tender_import:#{region}" }
|
290
|
+
)
|
291
|
+
|
292
|
+
sidekiq_options queue: 'tenders', retry: 5
|
293
|
+
|
294
|
+
def perform(region, date, options = {})
|
295
|
+
TenderImportService.new(region, date, options).call
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# app/services/tender_import_service.rb
|
300
|
+
class TenderImportService
|
301
|
+
def initialize(region, date, options = {})
|
302
|
+
@region = region
|
303
|
+
@date = date
|
304
|
+
@options = options
|
305
|
+
end
|
306
|
+
|
307
|
+
def call
|
308
|
+
result = OxTenderAbstract.search_tenders(
|
309
|
+
org_region: @region,
|
310
|
+
exact_date: @date,
|
311
|
+
subsystem_type: @options[:subsystem_type] || 'PRIZ'
|
312
|
+
)
|
313
|
+
|
314
|
+
if result.failure?
|
315
|
+
handle_error(result)
|
316
|
+
else
|
317
|
+
process_success(result)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
private
|
322
|
+
|
323
|
+
def handle_error(result)
|
324
|
+
case result.metadata[:error_type]
|
325
|
+
when :blocked
|
326
|
+
schedule_retry(result.metadata[:retry_after])
|
327
|
+
when :network
|
328
|
+
raise NetworkError, result.error
|
329
|
+
else
|
330
|
+
raise StandardError, result.error
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def schedule_retry(retry_after)
|
335
|
+
TenderImportWorker.perform_in(
|
336
|
+
(retry_after + 30).seconds,
|
337
|
+
@region, @date, @options.merge(retry: true)
|
338
|
+
)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
```
|
data/lib/ox-tender-abstract.rb
CHANGED
@@ -29,18 +29,19 @@ module OxTenderAbstract
|
|
29
29
|
|
30
30
|
# Convenience method for searching tenders in specific subsystem
|
31
31
|
def search_tenders(org_region:, exact_date:, subsystem_type: DocumentTypes::DEFAULT_SUBSYSTEM,
|
32
|
-
document_type: DocumentTypes::DEFAULT_DOCUMENT_TYPE)
|
32
|
+
document_type: DocumentTypes::DEFAULT_DOCUMENT_TYPE, include_attachments: true)
|
33
33
|
client = Client.new
|
34
34
|
client.search_tenders(
|
35
35
|
org_region: org_region,
|
36
36
|
exact_date: exact_date,
|
37
37
|
subsystem_type: subsystem_type,
|
38
|
-
document_type: document_type
|
38
|
+
document_type: document_type,
|
39
|
+
include_attachments: include_attachments
|
39
40
|
)
|
40
41
|
end
|
41
42
|
|
42
43
|
# Enhanced method for searching tenders across multiple subsystems
|
43
|
-
def search_all_tenders(org_region:, exact_date:, subsystems: nil, document_types: nil)
|
44
|
+
def search_all_tenders(org_region:, exact_date:, subsystems: nil, document_types: nil, include_attachments: true)
|
44
45
|
# Default subsystems to search
|
45
46
|
subsystems ||= %w[PRIZ RPEC RPGZ BTK UR RGK OD223 RD223]
|
46
47
|
|
@@ -67,7 +68,8 @@ module OxTenderAbstract
|
|
67
68
|
org_region: org_region,
|
68
69
|
exact_date: exact_date,
|
69
70
|
subsystem_type: subsystem_type,
|
70
|
-
document_type: doc_type
|
71
|
+
document_type: doc_type,
|
72
|
+
include_attachments: include_attachments
|
71
73
|
)
|
72
74
|
|
73
75
|
if result.success?
|
@@ -134,7 +136,8 @@ module OxTenderAbstract
|
|
134
136
|
|
135
137
|
# Search tenders with automatic wait on API blocks and resume capability
|
136
138
|
def search_tenders_with_auto_wait(org_region:, exact_date:, subsystem_type: DocumentTypes::DEFAULT_SUBSYSTEM,
|
137
|
-
document_type: DocumentTypes::DEFAULT_DOCUMENT_TYPE, resume_state: nil
|
139
|
+
document_type: DocumentTypes::DEFAULT_DOCUMENT_TYPE, resume_state: nil,
|
140
|
+
include_attachments: true)
|
138
141
|
client = Client.new
|
139
142
|
|
140
143
|
# Если есть состояние для продолжения
|
@@ -146,7 +149,8 @@ module OxTenderAbstract
|
|
146
149
|
subsystem_type: subsystem_type,
|
147
150
|
document_type: document_type,
|
148
151
|
start_from_archive: start_from,
|
149
|
-
resume_state: resume_state
|
152
|
+
resume_state: resume_state,
|
153
|
+
include_attachments: include_attachments
|
150
154
|
)
|
151
155
|
else
|
152
156
|
# Используем обычный метод если авто-ожидание включено
|
@@ -155,7 +159,8 @@ module OxTenderAbstract
|
|
155
159
|
org_region: org_region,
|
156
160
|
exact_date: exact_date,
|
157
161
|
subsystem_type: subsystem_type,
|
158
|
-
document_type: document_type
|
162
|
+
document_type: document_type,
|
163
|
+
include_attachments: include_attachments
|
159
164
|
)
|
160
165
|
else
|
161
166
|
# Используем метод с возможностью продолжения
|
@@ -163,7 +168,8 @@ module OxTenderAbstract
|
|
163
168
|
org_region: org_region,
|
164
169
|
exact_date: exact_date,
|
165
170
|
subsystem_type: subsystem_type,
|
166
|
-
document_type: document_type
|
171
|
+
document_type: document_type,
|
172
|
+
include_attachments: include_attachments
|
167
173
|
)
|
168
174
|
end
|
169
175
|
end
|
@@ -88,8 +88,12 @@ module OxTenderAbstract
|
|
88
88
|
log_warn "Download attempt #{attempt} failed: #{last_error}"
|
89
89
|
end
|
90
90
|
rescue StandardError => e
|
91
|
-
last_error =
|
92
|
-
|
91
|
+
last_error = begin
|
92
|
+
e.message.force_encoding('UTF-8').scrub
|
93
|
+
rescue StandardError
|
94
|
+
e.message.to_s
|
95
|
+
end
|
96
|
+
log_error "Download error details: #{e.class} - #{last_error}"
|
93
97
|
end
|
94
98
|
|
95
99
|
if attempt < MAX_RETRY_ATTEMPTS
|
@@ -128,34 +132,42 @@ module OxTenderAbstract
|
|
128
132
|
unless response.is_a?(Net::HTTPSuccess)
|
129
133
|
error_msg = "HTTP error: #{response.code} #{response.message}"
|
130
134
|
if response.body && !response.body.empty?
|
131
|
-
# Log first part of response body for debugging
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
+
# Log first part of response body for debugging - safely handle encoding
|
136
|
+
begin
|
137
|
+
body_preview = response.body.force_encoding('UTF-8').scrub[0..500]
|
138
|
+
log_error "Response body preview: #{body_preview}"
|
139
|
+
error_msg += ". Response: #{body_preview[0..100]}"
|
140
|
+
rescue StandardError => e
|
141
|
+
log_error "Response body encoding error: #{e.message}"
|
142
|
+
error_msg += '. Response body unreadable (encoding issue)'
|
143
|
+
end
|
135
144
|
end
|
136
145
|
return Result.failure(error_msg)
|
137
146
|
end
|
138
147
|
|
139
|
-
# Check for download blocking message in successful response
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
148
|
+
# Check for download blocking message in successful response - safely handle encoding
|
149
|
+
begin
|
150
|
+
response_text = response.body&.force_encoding('UTF-8')&.scrub
|
151
|
+
if response_text&.include?('Скачивание архива по данной ссылке заблокировано')
|
152
|
+
if OxTenderAbstract.configuration.auto_wait_on_block
|
153
|
+
wait_time = OxTenderAbstract.configuration.block_wait_time
|
154
|
+
log_error "Archive download blocked. Auto-waiting for #{wait_time} seconds..."
|
155
|
+
|
156
|
+
# Показываем прогресс ожидания
|
157
|
+
show_wait_progress(wait_time)
|
158
|
+
|
159
|
+
log_info 'Wait completed, retrying download...'
|
160
|
+
# Рекурсивно повторяем попытку после ожидания
|
161
|
+
return download_to_memory(url)
|
162
|
+
else
|
163
|
+
# Возвращаем специальную ошибку блокировки для ручной обработки
|
164
|
+
return Result.failure('Archive download blocked for 10 minutes',
|
165
|
+
ArchiveBlockedError.new('Archive download blocked', 600))
|
166
|
+
end
|
158
167
|
end
|
168
|
+
rescue StandardError => e
|
169
|
+
log_error "Encoding error when checking for blocking message: #{e.message}"
|
170
|
+
# Продолжаем обработку, так как это может быть просто архив
|
159
171
|
end
|
160
172
|
|
161
173
|
content = response.body
|
@@ -76,7 +76,7 @@ module OxTenderAbstract
|
|
76
76
|
|
77
77
|
# Search tenders with full workflow: API -> Archive -> Parse
|
78
78
|
def search_tenders(org_region:, exact_date:, subsystem_type: DocumentTypes::DEFAULT_SUBSYSTEM,
|
79
|
-
document_type: DocumentTypes::DEFAULT_DOCUMENT_TYPE)
|
79
|
+
document_type: DocumentTypes::DEFAULT_DOCUMENT_TYPE, include_attachments: true)
|
80
80
|
log_info "Starting tender search for region #{org_region}, date #{exact_date}, subsystem: #{subsystem_type}, type: #{document_type}"
|
81
81
|
|
82
82
|
# Step 1: Get archive URLs from API
|
@@ -133,6 +133,15 @@ module OxTenderAbstract
|
|
133
133
|
tender_data = parse_result.data[:content]
|
134
134
|
next if tender_data[:reestr_number].nil? || tender_data[:reestr_number].empty?
|
135
135
|
|
136
|
+
# Extract attachments if requested
|
137
|
+
if include_attachments
|
138
|
+
attachments_result = extract_attachments_from_xml(file_data[:content])
|
139
|
+
if attachments_result.success?
|
140
|
+
tender_data[:attachments] = attachments_result.data[:attachments]
|
141
|
+
tender_data[:attachments_count] = attachments_result.data[:total_count]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
136
145
|
# Add metadata
|
137
146
|
tender_data[:source_file] = file_name
|
138
147
|
tender_data[:archive_url] = archive_url
|
@@ -240,7 +249,7 @@ module OxTenderAbstract
|
|
240
249
|
# Позволяет продолжить загрузку с места паузы при блокировках API
|
241
250
|
def search_tenders_with_resume(org_region:, exact_date:, subsystem_type: DocumentTypes::DEFAULT_SUBSYSTEM,
|
242
251
|
document_type: DocumentTypes::DEFAULT_DOCUMENT_TYPE,
|
243
|
-
start_from_archive: 0, resume_state: nil)
|
252
|
+
start_from_archive: 0, resume_state: nil, include_attachments: true)
|
244
253
|
log_info "Starting tender search with resume capability for region #{org_region}, date #{exact_date}"
|
245
254
|
log_info "Starting from archive #{start_from_archive}" if start_from_archive > 0
|
246
255
|
|
@@ -330,6 +339,15 @@ module OxTenderAbstract
|
|
330
339
|
tender_data = parse_result.data[:content]
|
331
340
|
next if tender_data[:reestr_number].nil? || tender_data[:reestr_number].empty?
|
332
341
|
|
342
|
+
# Extract attachments if requested
|
343
|
+
if include_attachments
|
344
|
+
attachments_result = extract_attachments_from_xml(file_data[:content])
|
345
|
+
if attachments_result.success?
|
346
|
+
tender_data[:attachments] = attachments_result.data[:attachments]
|
347
|
+
tender_data[:attachments_count] = attachments_result.data[:total_count]
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
333
351
|
# Add metadata
|
334
352
|
tender_data[:source_file] = file_name
|
335
353
|
tender_data[:archive_url] = archive_url
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ox-tender-abstract
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- smolev
|
@@ -187,6 +187,7 @@ files:
|
|
187
187
|
- LICENSE
|
188
188
|
- README.md
|
189
189
|
- Rakefile
|
190
|
+
- SIDEKIQ_USAGE.md
|
190
191
|
- lib/ox-tender-abstract.rb
|
191
192
|
- lib/oxtenderabstract/archive_processor.rb
|
192
193
|
- lib/oxtenderabstract/client.rb
|
@@ -222,7 +223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
222
223
|
- !ruby/object:Gem::Version
|
223
224
|
version: '0'
|
224
225
|
requirements: []
|
225
|
-
rubygems_version: 3.
|
226
|
+
rubygems_version: 3.7.1
|
226
227
|
specification_version: 4
|
227
228
|
summary: Ruby library for working with Russian tender system (zakupki.gov.ru) SOAP
|
228
229
|
API
|