ox-tender-abstract 0.9.1 → 0.9.3
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/.rspec_status +18 -18
- data/CHANGELOG.md +11 -0
- data/README.md +35 -0
- data/lib/ox-tender-abstract.rb +154 -21
- data/lib/oxtenderabstract/archive_processor.rb +192 -76
- data/lib/oxtenderabstract/client.rb +170 -20
- data/lib/oxtenderabstract/configuration.rb +5 -1
- data/lib/oxtenderabstract/document_types.rb +72 -2
- data/lib/oxtenderabstract/errors.rb +21 -9
- data/lib/oxtenderabstract/version.rb +1 -1
- data/lib/oxtenderabstract/xml_parser.rb +164 -23
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9924cb49a0b35703d8e0093b3ae40b8b8556f7da398990b3ff5f84f185c7d944
|
4
|
+
data.tar.gz: 2598c2bb4af463aa3cbf1bde9c1cc76aac352f8e58a193152305dccc4a2cef8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c0a783ab40ca1f45be2d0d5c32db63345a40502bdf6140b446018686b73482afba84ce3097d7ab7afe821b106915e06ba32b03b66a35a19adf95fdc4ad2c404
|
7
|
+
data.tar.gz: 53a15a50dad376969b8ff1869e88197a669288bdd57b3931ce59105c8288b25f6f9a9768e03b9211f8284570e4bbf219daa98f2702270c39c967c5e1737c4ead
|
data/.rspec_status
CHANGED
@@ -49,26 +49,26 @@ example_id | status | run_time
|
|
49
49
|
./spec/oxtenderabstract/result_spec.rb[1:3:2] | passed | 0.00002 seconds |
|
50
50
|
./spec/oxtenderabstract/result_spec.rb[1:4:1] | passed | 0.00002 seconds |
|
51
51
|
./spec/oxtenderabstract/result_spec.rb[1:4:2] | passed | 0.00002 seconds |
|
52
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:1:1] | passed | 0.
|
53
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:2:1] | passed | 0.
|
54
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:1] | failed | 0.
|
55
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:2] | passed | 0.
|
56
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:3] | failed | 0.
|
57
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:4:1] | passed | 0.
|
58
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:4:2] | passed | 0.
|
59
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:4:3] | passed | 0.
|
60
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:5:1] | passed | 0.
|
61
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:6:1] | passed | 0.
|
62
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:7:1] | passed | 0.
|
63
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:2:1:1] | failed | 0.
|
64
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:2:2:1] | failed | 0.
|
65
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:2:3:1] | failed | 0.
|
52
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:1:1] | passed | 0.00192 seconds |
|
53
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:2:1] | passed | 0.00007 seconds |
|
54
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:1] | failed | 0.00615 seconds |
|
55
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:2] | passed | 0.00098 seconds |
|
56
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:3] | failed | 0.00089 seconds |
|
57
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:4:1] | passed | 0.00194 seconds |
|
58
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:4:2] | passed | 0.00168 seconds |
|
59
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:4:3] | passed | 0.00086 seconds |
|
60
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:5:1] | passed | 0.00008 seconds |
|
61
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:6:1] | passed | 0.00007 seconds |
|
62
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:1:7:1] | passed | 0.00007 seconds |
|
63
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:2:1:1] | failed | 0.00016 seconds |
|
64
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:2:2:1] | failed | 0.00011 seconds |
|
65
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:2:3:1] | failed | 0.00015 seconds |
|
66
66
|
./spec/oxtenderabstract/xml_parser_spec.rb[1:3:1] | passed | 0.00005 seconds |
|
67
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:3:2] | passed | 0.
|
67
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:3:2] | passed | 0.00004 seconds |
|
68
68
|
./spec/oxtenderabstract/xml_parser_spec.rb[1:3:3] | passed | 0.00004 seconds |
|
69
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:3:4] | passed | 0.
|
70
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:4:1:1] | failed | 0.
|
71
|
-
./spec/oxtenderabstract/xml_parser_spec.rb[1:4:2:1] |
|
69
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:3:4] | passed | 0.00006 seconds |
|
70
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:4:1:1] | failed | 0.00131 seconds |
|
71
|
+
./spec/oxtenderabstract/xml_parser_spec.rb[1:4:2:1] | passed | 0.00015 seconds |
|
72
72
|
./spec/oxtenderabstract_spec.rb[1:1:1] | passed | 0.00053 seconds |
|
73
73
|
./spec/oxtenderabstract_spec.rb[1:2:1] | passed | 0.00003 seconds |
|
74
74
|
./spec/oxtenderabstract_spec.rb[1:3:1] | passed | 0.00003 seconds |
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## [0.9.3] - 2025-07-27
|
2
|
+
|
3
|
+
- Added support for parsing tender documents
|
4
|
+
- Added support for parsing contract documents
|
5
|
+
- Added support for parsing organization documents
|
6
|
+
- Added support for parsing generic documents
|
7
|
+
- Added support for parsing attachments
|
8
|
+
- Added support for parsing tender documents
|
9
|
+
- Added support for parsing contract documents
|
10
|
+
- Added support for parsing organization documents
|
11
|
+
|
1
12
|
## [0.9.0] - 2025-07-15
|
2
13
|
|
3
14
|
- Initial release
|
data/README.md
CHANGED
@@ -273,6 +273,41 @@ puts result.data[:total_archives] # => 6
|
|
273
273
|
# Processing typically takes 10-15 seconds for a full day's data
|
274
274
|
```
|
275
275
|
|
276
|
+
## Error Handling
|
277
|
+
|
278
|
+
The library uses the `Result` pattern for error handling:
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
result = OxTenderAbstract.search_tenders(org_region: '77', exact_date: '2024-01-01')
|
282
|
+
|
283
|
+
if result.success?
|
284
|
+
puts "Found tenders: #{result.data[:tenders].size}"
|
285
|
+
else
|
286
|
+
puts "Error: #{result.error}"
|
287
|
+
|
288
|
+
# Check error type for special handling
|
289
|
+
if result.metadata[:error_type] == :blocked
|
290
|
+
retry_after = result.metadata[:retry_after] || 600
|
291
|
+
puts "API blocked for #{retry_after} seconds"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
```
|
295
|
+
|
296
|
+
### Handling API Blocks
|
297
|
+
|
298
|
+
When making frequent requests, the API may block archive downloads for 10 minutes. The library automatically detects such blocks:
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
result = OxTenderAbstract.search_tenders(org_region: '77', exact_date: '2024-01-01')
|
302
|
+
|
303
|
+
if result.failure? && result.metadata[:error_type] == :blocked
|
304
|
+
retry_after = result.metadata[:retry_after] # 600 seconds (10 minutes)
|
305
|
+
puts "Download blocked, retry in #{retry_after} seconds"
|
306
|
+
end
|
307
|
+
```
|
308
|
+
|
309
|
+
For detailed guidance on using with Sidekiq background jobs, see [SIDEKIQ_USAGE.md](SIDEKIQ_USAGE.md).
|
310
|
+
|
276
311
|
## Requirements
|
277
312
|
|
278
313
|
- Ruby >= 3.0.0
|
data/lib/ox-tender-abstract.rb
CHANGED
@@ -1,39 +1,172 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'oxtenderabstract/version'
|
4
|
-
require_relative 'oxtenderabstract/
|
4
|
+
require_relative 'oxtenderabstract/configuration'
|
5
5
|
require_relative 'oxtenderabstract/errors'
|
6
|
+
require_relative 'oxtenderabstract/logger'
|
6
7
|
require_relative 'oxtenderabstract/result'
|
7
8
|
require_relative 'oxtenderabstract/document_types'
|
8
|
-
require_relative 'oxtenderabstract/configuration'
|
9
|
-
require_relative 'oxtenderabstract/xml_parser'
|
10
9
|
require_relative 'oxtenderabstract/archive_processor'
|
10
|
+
require_relative 'oxtenderabstract/xml_parser'
|
11
11
|
require_relative 'oxtenderabstract/client'
|
12
12
|
|
13
13
|
# Main module for OxTenderAbstract library
|
14
14
|
module OxTenderAbstract
|
15
15
|
class Error < StandardError; end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
class << self
|
18
|
+
def configure
|
19
|
+
yield(configuration)
|
20
|
+
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
client.search_tenders(org_region: org_region, exact_date: exact_date, **options)
|
26
|
-
end
|
22
|
+
def configuration
|
23
|
+
@configuration ||= Configuration.new
|
24
|
+
end
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
def reset_configuration!
|
27
|
+
@configuration = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Convenience method for searching tenders in specific subsystem
|
31
|
+
def search_tenders(org_region:, exact_date:, subsystem_type: DocumentTypes::DEFAULT_SUBSYSTEM,
|
32
|
+
document_type: DocumentTypes::DEFAULT_DOCUMENT_TYPE)
|
33
|
+
client = Client.new
|
34
|
+
client.search_tenders(
|
35
|
+
org_region: org_region,
|
36
|
+
exact_date: exact_date,
|
37
|
+
subsystem_type: subsystem_type,
|
38
|
+
document_type: document_type
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Enhanced method for searching tenders across multiple subsystems
|
43
|
+
def search_all_tenders(org_region:, exact_date:, subsystems: nil, document_types: nil)
|
44
|
+
# Default subsystems to search
|
45
|
+
subsystems ||= %w[PRIZ RPEC RPGZ BTK UR RGK OD223 RD223]
|
46
|
+
|
47
|
+
client = Client.new
|
48
|
+
all_results = {}
|
49
|
+
total_tenders = []
|
50
|
+
total_archives = 0
|
51
|
+
|
52
|
+
subsystems.each do |subsystem_type|
|
53
|
+
# Get appropriate document types for this subsystem
|
54
|
+
available_types = DocumentTypes.document_types_for_subsystem(subsystem_type)
|
55
|
+
test_types = document_types || [available_types.first] # Test first type by default
|
56
|
+
|
57
|
+
subsystem_results = {
|
58
|
+
subsystem: subsystem_type,
|
59
|
+
description: DocumentTypes.description_for_subsystem(subsystem_type),
|
60
|
+
tenders: [],
|
61
|
+
archives: 0,
|
62
|
+
errors: []
|
63
|
+
}
|
64
|
+
|
65
|
+
test_types.each do |doc_type|
|
66
|
+
result = client.search_tenders(
|
67
|
+
org_region: org_region,
|
68
|
+
exact_date: exact_date,
|
69
|
+
subsystem_type: subsystem_type,
|
70
|
+
document_type: doc_type
|
71
|
+
)
|
72
|
+
|
73
|
+
if result.success?
|
74
|
+
tenders = result.data[:tenders] || []
|
75
|
+
archives = result.data[:total_archives] || 0
|
76
|
+
|
77
|
+
subsystem_results[:tenders].concat(tenders)
|
78
|
+
subsystem_results[:archives] += archives
|
79
|
+
total_archives += archives
|
80
|
+
|
81
|
+
# Add subsystem info to each tender
|
82
|
+
tenders.each do |tender|
|
83
|
+
tender[:subsystem_type] = subsystem_type
|
84
|
+
tender[:subsystem_description] = DocumentTypes.description_for_subsystem(subsystem_type)
|
85
|
+
tender[:document_type_used] = doc_type
|
86
|
+
end
|
87
|
+
|
88
|
+
total_tenders.concat(tenders)
|
89
|
+
else
|
90
|
+
subsystem_results[:errors] << "#{doc_type}: #{result.error}"
|
91
|
+
end
|
92
|
+
rescue StandardError => e
|
93
|
+
subsystem_results[:errors] << "#{doc_type}: #{e.message}"
|
94
|
+
end
|
95
|
+
|
96
|
+
all_results[subsystem_type] = subsystem_results
|
97
|
+
end
|
98
|
+
|
99
|
+
Result.success({
|
100
|
+
tenders: total_tenders,
|
101
|
+
total_archives: total_archives,
|
102
|
+
subsystem_results: all_results,
|
103
|
+
search_params: {
|
104
|
+
org_region: org_region,
|
105
|
+
exact_date: exact_date,
|
106
|
+
subsystems_searched: subsystems.size
|
107
|
+
},
|
108
|
+
processed_at: Time.now
|
109
|
+
})
|
110
|
+
end
|
111
|
+
|
112
|
+
# Get documents by registry number across subsystems
|
113
|
+
def get_docs_by_reestr_number(reestr_number:, subsystem_type: DocumentTypes::DEFAULT_SUBSYSTEM)
|
114
|
+
client = Client.new
|
115
|
+
client.get_docs_by_reestr_number(
|
116
|
+
reestr_number: reestr_number,
|
117
|
+
subsystem_type: subsystem_type
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Enhanced search with detailed information extraction
|
122
|
+
def enhanced_search_tenders(org_region:, exact_date:, subsystem_type: DocumentTypes::DEFAULT_SUBSYSTEM,
|
123
|
+
document_type: DocumentTypes::DEFAULT_DOCUMENT_TYPE,
|
124
|
+
include_attachments: true)
|
125
|
+
client = Client.new
|
126
|
+
client.enhanced_search_tenders(
|
127
|
+
org_region: org_region,
|
128
|
+
exact_date: exact_date,
|
129
|
+
subsystem_type: subsystem_type,
|
130
|
+
document_type: document_type,
|
131
|
+
include_attachments: include_attachments
|
132
|
+
)
|
133
|
+
end
|
33
134
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
135
|
+
# Search tenders with automatic wait on API blocks and resume capability
|
136
|
+
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)
|
138
|
+
client = Client.new
|
139
|
+
|
140
|
+
# Если есть состояние для продолжения
|
141
|
+
if resume_state
|
142
|
+
start_from = resume_state[:next_archive_index] || 0
|
143
|
+
client.search_tenders_with_resume(
|
144
|
+
org_region: org_region,
|
145
|
+
exact_date: exact_date,
|
146
|
+
subsystem_type: subsystem_type,
|
147
|
+
document_type: document_type,
|
148
|
+
start_from_archive: start_from,
|
149
|
+
resume_state: resume_state
|
150
|
+
)
|
151
|
+
else
|
152
|
+
# Используем обычный метод если авто-ожидание включено
|
153
|
+
if configuration.auto_wait_on_block
|
154
|
+
client.search_tenders(
|
155
|
+
org_region: org_region,
|
156
|
+
exact_date: exact_date,
|
157
|
+
subsystem_type: subsystem_type,
|
158
|
+
document_type: document_type
|
159
|
+
)
|
160
|
+
else
|
161
|
+
# Используем метод с возможностью продолжения
|
162
|
+
client.search_tenders_with_resume(
|
163
|
+
org_region: org_region,
|
164
|
+
exact_date: exact_date,
|
165
|
+
subsystem_type: subsystem_type,
|
166
|
+
document_type: document_type
|
167
|
+
)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
38
171
|
end
|
39
172
|
end
|