rospatent 1.1.0 → 1.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -0
- data/README.md +1169 -241
- data/lib/generators/rospatent/install/templates/initializer.rb +39 -15
- data/lib/rospatent/client.rb +91 -23
- data/lib/rospatent/configuration.rb +10 -9
- data/lib/rospatent/input_validator.rb +235 -0
- data/lib/rospatent/search.rb +27 -21
- data/lib/rospatent/version.rb +1 -1
- metadata +10 -10
data/README.md
CHANGED
@@ -15,7 +15,7 @@ A comprehensive Ruby client for the Rospatent patent search API with advanced fe
|
|
15
15
|
- 📊 **Structured Logging** - JSON/text logging with request/response tracking
|
16
16
|
- 🚀 **Batch Operations** - Process multiple patents concurrently
|
17
17
|
- ⚙️ **Environment-Aware** - Different configurations for dev/staging/production
|
18
|
-
- 🧪 **Comprehensive Testing** -
|
18
|
+
- 🧪 **Comprehensive Testing** - 219 tests with 465 assertions, comprehensive integration testing
|
19
19
|
- 📚 **Excellent Documentation** - Detailed examples and API documentation
|
20
20
|
|
21
21
|
## Installation
|
@@ -41,7 +41,7 @@ $ gem install rospatent
|
|
41
41
|
## Quick Start
|
42
42
|
|
43
43
|
```ruby
|
44
|
-
#
|
44
|
+
# Minimal configuration
|
45
45
|
Rospatent.configure do |config|
|
46
46
|
config.token = "your_jwt_token"
|
47
47
|
end
|
@@ -101,25 +101,149 @@ Rospatent.configure do |config|
|
|
101
101
|
end
|
102
102
|
```
|
103
103
|
|
104
|
-
### Environment
|
104
|
+
### Environment-Specific Configuration
|
105
105
|
|
106
|
-
|
106
|
+
The gem automatically adjusts settings based on environment with sensible defaults:
|
107
|
+
|
108
|
+
#### Development Environment
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# Optimized for development
|
112
|
+
Rospatent.configure do |config|
|
113
|
+
config.environment = "development"
|
114
|
+
config.token = ENV['ROSPATENT_TOKEN']
|
115
|
+
config.log_level = :debug
|
116
|
+
config.log_requests = true
|
117
|
+
config.log_responses = true
|
118
|
+
config.cache_ttl = 60 # Short cache for development
|
119
|
+
config.timeout = 10 # Fast timeouts for quick feedback
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
#### Staging Environment
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
# Optimized for staging
|
127
|
+
Rospatent.configure do |config|
|
128
|
+
config.environment = "staging"
|
129
|
+
config.token = ENV['ROSPATENT_TOKEN']
|
130
|
+
config.log_level = :info
|
131
|
+
config.cache_ttl = 300 # Longer cache for performance
|
132
|
+
config.timeout = 45 # Longer timeouts for reliability
|
133
|
+
config.retry_count = 3 # More retries for resilience
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
#### Production Environment
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
# Optimized for production
|
141
|
+
Rospatent.configure do |config|
|
142
|
+
config.environment = "production"
|
143
|
+
config.token = ENV['ROSPATENT_TOKEN']
|
144
|
+
config.log_level = :warn
|
145
|
+
config.cache_ttl = 600 # Longer cache for performance
|
146
|
+
config.timeout = 60 # Longer timeouts for reliability
|
147
|
+
config.retry_count = 5 # More retries for resilience
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
### Environment Variables & Rails Integration
|
152
|
+
|
153
|
+
⚠️ **CRITICAL**: Understanding environment variable priority is essential to avoid configuration issues, especially in Rails applications.
|
154
|
+
|
155
|
+
#### Token Configuration Priority
|
156
|
+
|
157
|
+
1. **Rails credentials**: `Rails.application.credentials.rospatent_token`
|
158
|
+
2. **Primary environment variable**: `ROSPATENT_TOKEN`
|
159
|
+
3. **Legacy environment variable**: `ROSPATENT_API_TOKEN`
|
107
160
|
|
108
161
|
```bash
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
162
|
+
# Recommended approach
|
163
|
+
export ROSPATENT_TOKEN="your_jwt_token"
|
164
|
+
|
165
|
+
# Legacy support (still works)
|
166
|
+
export ROSPATENT_API_TOKEN="your_jwt_token"
|
167
|
+
```
|
168
|
+
|
169
|
+
#### Log Level Configuration Priority
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
# Environment variable takes precedence over Rails defaults
|
173
|
+
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
|
174
|
+
ENV["ROSPATENT_LOG_LEVEL"].to_sym
|
175
|
+
else
|
176
|
+
Rails.env.production? ? :warn : :debug
|
177
|
+
end
|
114
178
|
```
|
115
179
|
|
116
|
-
|
180
|
+
⚠️ **Common Issue**: Setting `ROSPATENT_LOG_LEVEL=debug` in production will override Rails-specific logic and cause DEBUG logs to appear in production!
|
181
|
+
|
182
|
+
#### Complete Environment Variables Reference
|
183
|
+
|
184
|
+
```bash
|
185
|
+
# Core configuration
|
186
|
+
ROSPATENT_TOKEN="your_jwt_token" # API authentication token
|
187
|
+
ROSPATENT_ENV="production" # Override Rails.env if needed
|
188
|
+
ROSPATENT_API_URL="custom_url" # Override default API URL
|
189
|
+
|
190
|
+
# Logging configuration
|
191
|
+
ROSPATENT_LOG_LEVEL="warn" # debug, info, warn, error
|
192
|
+
ROSPATENT_LOG_REQUESTS="false" # Log API requests
|
193
|
+
ROSPATENT_LOG_RESPONSES="false" # Log API responses
|
194
|
+
|
195
|
+
# Cache configuration
|
196
|
+
ROSPATENT_CACHE_ENABLED="true" # Enable/disable caching
|
197
|
+
ROSPATENT_CACHE_TTL="300" # Cache TTL in seconds
|
198
|
+
ROSPATENT_CACHE_MAX_SIZE="1000" # Maximum cache items
|
199
|
+
|
200
|
+
# Connection configuration
|
201
|
+
ROSPATENT_TIMEOUT="30" # Request timeout in seconds
|
202
|
+
ROSPATENT_RETRY_COUNT="3" # Number of retries
|
203
|
+
ROSPATENT_POOL_SIZE="5" # Connection pool size
|
204
|
+
ROSPATENT_KEEP_ALIVE="true" # Keep-alive connections
|
205
|
+
|
206
|
+
# Environment-specific overrides
|
207
|
+
ROSPATENT_DEV_API_URL="dev_url" # Development API URL
|
208
|
+
ROSPATENT_STAGING_API_URL="staging_url" # Staging API URL
|
209
|
+
```
|
210
|
+
|
211
|
+
#### Best Practices for Rails
|
212
|
+
|
213
|
+
1. **Use Rails credentials for tokens**:
|
214
|
+
```bash
|
215
|
+
rails credentials:edit
|
216
|
+
# Add: rospatent_token: your_jwt_token
|
217
|
+
```
|
218
|
+
|
219
|
+
2. **Set environment-specific variables**:
|
220
|
+
```bash
|
221
|
+
# config/environments/production.rb
|
222
|
+
ENV["ROSPATENT_LOG_LEVEL"] ||= "warn"
|
223
|
+
ENV["ROSPATENT_CACHE_ENABLED"] ||= "true"
|
224
|
+
```
|
225
|
+
|
226
|
+
3. **Avoid setting DEBUG level in production**:
|
227
|
+
```bash
|
228
|
+
# ❌ DON'T DO THIS in production
|
229
|
+
export ROSPATENT_LOG_LEVEL=debug
|
230
|
+
|
231
|
+
# ✅ DO THIS instead
|
232
|
+
export ROSPATENT_LOG_LEVEL=warn
|
233
|
+
```
|
117
234
|
|
118
|
-
|
235
|
+
### Configuration Validation
|
119
236
|
|
120
|
-
|
121
|
-
|
122
|
-
|
237
|
+
```ruby
|
238
|
+
# Validate current configuration
|
239
|
+
errors = Rospatent.validate_configuration
|
240
|
+
if errors.any?
|
241
|
+
puts "Configuration errors:"
|
242
|
+
errors.each { |error| puts " - #{error}" }
|
243
|
+
else
|
244
|
+
puts "Configuration is valid ✓"
|
245
|
+
end
|
246
|
+
```
|
123
247
|
|
124
248
|
## Basic Usage
|
125
249
|
|
@@ -137,20 +261,66 @@ results = client.search(qn: "rocket engine design")
|
|
137
261
|
# Advanced search with all options
|
138
262
|
results = client.search(
|
139
263
|
q: "ракета AND двигатель",
|
140
|
-
limit:
|
141
|
-
offset:
|
264
|
+
limit: 50,
|
265
|
+
offset: 100,
|
266
|
+
datasets: ["ru_since_1994"],
|
142
267
|
filter: {
|
143
268
|
"classification.ipc_group": { "values": ["F02K9"] },
|
144
|
-
"
|
269
|
+
"application.filing_date": { "range": { "gte": "20200101" } }
|
145
270
|
},
|
146
|
-
sort: :pub_date
|
147
|
-
group_by: :
|
271
|
+
sort: "publication_date:desc", # same as 'sort: :pub_date'; see Search#validate_sort_parameter for other sort options
|
272
|
+
group_by: "family:dwpi", # Patent family grouping: "family:docdb" or "family:dwpi"
|
148
273
|
include_facets: true,
|
149
|
-
|
274
|
+
pre_tag: "<mark>", # Both pre_tag and post_tag must be provided together
|
275
|
+
post_tag: "</mark>", # Can be strings or arrays for multi-color highlighting
|
276
|
+
highlight: { # Advanced highlight configuration (independent of pre_tag/post_tag)
|
277
|
+
"profiles" => [
|
278
|
+
{ "q" => "космическая", "pre_tag" => "<b>", "post_tag" => "</b>" },
|
279
|
+
"_searchquery_"
|
280
|
+
]
|
281
|
+
}
|
282
|
+
)
|
283
|
+
|
284
|
+
# Simple highlighting with tags (both pre_tag and post_tag required)
|
285
|
+
results = client.search(
|
286
|
+
q: "ракета",
|
150
287
|
pre_tag: "<mark>",
|
151
288
|
post_tag: "</mark>"
|
152
289
|
)
|
153
290
|
|
291
|
+
# Multi-color highlighting with arrays
|
292
|
+
results = client.search(
|
293
|
+
q: "космическая ракета",
|
294
|
+
pre_tag: ["<b>", "<i>"], # Round-robin highlighting
|
295
|
+
post_tag: ["</b>", "</i>"] # with different tags
|
296
|
+
)
|
297
|
+
|
298
|
+
# Advanced highlighting with profiles (independent of pre_tag/post_tag)
|
299
|
+
results = client.search(
|
300
|
+
q: "ракета",
|
301
|
+
highlight: {
|
302
|
+
"profiles" => [
|
303
|
+
{ "q" => "космическая", "pre_tag" => "<b>", "post_tag" => "</b>" },
|
304
|
+
"_searchquery_" # References main search query highlighting
|
305
|
+
]
|
306
|
+
}
|
307
|
+
)
|
308
|
+
|
309
|
+
# Patent family grouping (groups patents from the same invention)
|
310
|
+
results = client.search(
|
311
|
+
q: "rocket",
|
312
|
+
group_by: "family:docdb", # DOCDB simple patent families
|
313
|
+
datasets: ["dwpi"],
|
314
|
+
limit: 10
|
315
|
+
)
|
316
|
+
|
317
|
+
results = client.search(
|
318
|
+
q: "rocket",
|
319
|
+
group_by: "family:dwpi", # DWPI simple patent families
|
320
|
+
datasets: ["dwpi"],
|
321
|
+
limit: 10
|
322
|
+
)
|
323
|
+
|
154
324
|
# Process results
|
155
325
|
puts "Found #{results.total} total results (#{results.available} available)"
|
156
326
|
puts "Showing #{results.count} results"
|
@@ -158,12 +328,153 @@ puts "Showing #{results.count} results"
|
|
158
328
|
results.hits.each do |hit|
|
159
329
|
puts "ID: #{hit['id']}"
|
160
330
|
puts "Title: #{hit.dig('biblio', 'ru', 'title')}"
|
161
|
-
puts "Date: #{hit.dig('
|
162
|
-
puts "IPC: #{hit.dig('classification', 'ipc')}"
|
331
|
+
puts "Date: #{hit.dig('common', 'publication_date')}"
|
332
|
+
puts "IPC: #{hit.dig('common', 'classification', 'ipc')&.map {|c| c['fullname']}&.join('; ')}"
|
163
333
|
puts "---"
|
164
334
|
end
|
165
335
|
```
|
166
336
|
|
337
|
+
### Advanced Filter Parameters
|
338
|
+
|
339
|
+
The `filter` parameter supports complex filtering with automatic validation and format conversion:
|
340
|
+
|
341
|
+
#### List Filters (require `{"values": [...]}` format)
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
# Classification filters
|
345
|
+
results = client.search(
|
346
|
+
q: "artificial intelligence",
|
347
|
+
filter: {
|
348
|
+
"classification.ipc_group": { "values": ["G06N", "G06F"] },
|
349
|
+
"classification.cpc_group": { "values": ["G06N3/", "G06N20/"] }
|
350
|
+
}
|
351
|
+
)
|
352
|
+
|
353
|
+
# Author and patent holder filters
|
354
|
+
results = client.search(
|
355
|
+
q: "invention",
|
356
|
+
filter: {
|
357
|
+
"authors": { "values": ["Иванов И.И.", "Петров П.П."] },
|
358
|
+
"patent_holders": { "values": ["ООО Компания"] },
|
359
|
+
"country": { "values": ["RU", "US"] },
|
360
|
+
"kind": { "values": ["A1", "U1"] }
|
361
|
+
}
|
362
|
+
)
|
363
|
+
|
364
|
+
# Document ID filters
|
365
|
+
results = client.search(
|
366
|
+
q: "device",
|
367
|
+
filter: {
|
368
|
+
"ids": { "values": ["RU134694U1_20131120", "RU2358138C1_20090610"] }
|
369
|
+
}
|
370
|
+
)
|
371
|
+
```
|
372
|
+
|
373
|
+
#### Date Range Filters (require `{"range": {"operator": "YYYYMMDD"}}` format)
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
# Automatic date format conversion
|
377
|
+
results = client.search(
|
378
|
+
q: "innovation",
|
379
|
+
filter: {
|
380
|
+
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
|
381
|
+
"application.filing_date": { "range": { "gte": "2019-06-15" } }
|
382
|
+
}
|
383
|
+
)
|
384
|
+
|
385
|
+
# Direct API format (YYYYMMDD)
|
386
|
+
results = client.search(
|
387
|
+
q: "technology",
|
388
|
+
filter: {
|
389
|
+
"date_published": { "range": { "gte": "20200101", "lt": "20240101" } }
|
390
|
+
}
|
391
|
+
)
|
392
|
+
|
393
|
+
# Using Date objects (automatically converted)
|
394
|
+
results = client.search(
|
395
|
+
q: "patent",
|
396
|
+
filter: {
|
397
|
+
"application.filing_date": {
|
398
|
+
"range": {
|
399
|
+
"gte": Date.new(2020, 1, 1),
|
400
|
+
"lte": Date.new(2023, 12, 31)
|
401
|
+
}
|
402
|
+
}
|
403
|
+
}
|
404
|
+
)
|
405
|
+
```
|
406
|
+
|
407
|
+
**Supported date operators**: `gt`, `gte`, `lt`, `lte`
|
408
|
+
|
409
|
+
**Date format conversion**:
|
410
|
+
- `"2020-01-01"` → `"20200101"`
|
411
|
+
- `Date.new(2020, 1, 1)` → `"20200101"`
|
412
|
+
- `"20200101"` → `"20200101"` (no change)
|
413
|
+
|
414
|
+
#### Complex Multi-Field Filters
|
415
|
+
|
416
|
+
```ruby
|
417
|
+
# Comprehensive filter example
|
418
|
+
results = client.search(
|
419
|
+
q: "машинное обучение",
|
420
|
+
filter: {
|
421
|
+
# List filters
|
422
|
+
"classification.ipc_group": { "values": ["G06N", "G06F"] },
|
423
|
+
"country": { "values": ["RU", "US", "CN"] },
|
424
|
+
"kind": { "values": ["A1", "A2"] },
|
425
|
+
"authors": { "values": ["Иванов И.И."] },
|
426
|
+
|
427
|
+
# Date range filters
|
428
|
+
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
|
429
|
+
"application.filing_date": { "range": { "gte": "2019-01-01" } }
|
430
|
+
},
|
431
|
+
limit: 50
|
432
|
+
)
|
433
|
+
```
|
434
|
+
|
435
|
+
**Supported Filter Fields**:
|
436
|
+
|
437
|
+
*List filters (require `{"values": [...]}` format):*
|
438
|
+
- `authors` - Patent authors
|
439
|
+
- `patent_holders` - Patent holders/assignees
|
440
|
+
- `country` - Country codes
|
441
|
+
- `kind` - Document types
|
442
|
+
- `ids` - Specific document IDs
|
443
|
+
- `classification.ipc*` - IPC classification codes
|
444
|
+
- `classification.cpc*` - CPC classification codes
|
445
|
+
|
446
|
+
*Date filters (require `{"range": {"operator": "YYYYMMDD"}}` format):*
|
447
|
+
- `date_published` - Publication date
|
448
|
+
- `application.filing_date` - Application filing date
|
449
|
+
|
450
|
+
**Filter Validation**:
|
451
|
+
- ✅ Automatic field name validation
|
452
|
+
- ✅ Structure validation (list vs range format)
|
453
|
+
- ✅ Date format conversion and validation
|
454
|
+
- ✅ Operator validation for ranges
|
455
|
+
- ✅ Helpful error messages for invalid filters
|
456
|
+
|
457
|
+
```ruby
|
458
|
+
# These will raise ValidationError with specific messages:
|
459
|
+
client.search(
|
460
|
+
q: "test",
|
461
|
+
filter: { "invalid_field": { "values": ["test"] } }
|
462
|
+
)
|
463
|
+
# Error: "Invalid filter field: invalid_field"
|
464
|
+
|
465
|
+
client.search(
|
466
|
+
q: "test",
|
467
|
+
filter: { "authors": ["direct", "array"] } # Missing {"values": [...]} wrapper
|
468
|
+
)
|
469
|
+
# Error: "Filter 'authors' requires format: {\"values\": [...]}"
|
470
|
+
|
471
|
+
client.search(
|
472
|
+
q: "test",
|
473
|
+
filter: { "date_published": { "range": { "invalid_op": "20200101" } } }
|
474
|
+
)
|
475
|
+
# Error: "Invalid range operator: invalid_op. Supported: gt, gte, lt, lte"
|
476
|
+
```
|
477
|
+
|
167
478
|
### Retrieving Patent Documents
|
168
479
|
|
169
480
|
```ruby
|
@@ -172,7 +483,7 @@ patent_doc = client.patent("RU134694U1_20131120")
|
|
172
483
|
|
173
484
|
# Get patent by components
|
174
485
|
patent_doc = client.patent_by_components(
|
175
|
-
"RU",
|
486
|
+
"RU", # country_code
|
176
487
|
"134694", # number
|
177
488
|
"U1", # doc_type
|
178
489
|
Date.new(2013, 11, 20) # date (String or Date object)
|
@@ -230,20 +541,20 @@ Search within patent classification systems (IPC and CPC) and get detailed infor
|
|
230
541
|
```ruby
|
231
542
|
# Search for classification codes related to rockets in IPC
|
232
543
|
ipc_results = client.classification_search("ipc", query: "ракета", lang: "ru")
|
233
|
-
puts "Found #{ipc_results
|
544
|
+
puts "Found #{ipc_results.size} IPC codes"
|
234
545
|
|
235
|
-
ipc_results
|
236
|
-
puts "#{
|
546
|
+
ipc_results&.each do |result|
|
547
|
+
puts "#{result['Code']}: #{result['Description']}"
|
237
548
|
end
|
238
549
|
|
239
550
|
# Search for rocket-related codes in CPC using English
|
240
551
|
cpc_results = client.classification_search("cpc", query: "rocket", lang: "en")
|
241
552
|
|
242
553
|
# Get detailed information about a specific classification code
|
243
|
-
|
244
|
-
puts "Code: #{
|
245
|
-
puts "Description: #{
|
246
|
-
puts "Hierarchy: #{
|
554
|
+
code, info = client.classification_code("ipc", code: "F02K9/00", lang: "ru")&.first
|
555
|
+
puts "Code: #{code}"
|
556
|
+
puts "Description: #{info&.first['Description']}"
|
557
|
+
puts "Hierarchy: #{info&.map{|level| level['Code']}&.join(' → ')}"
|
247
558
|
|
248
559
|
# Get CPC code information in English
|
249
560
|
cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
|
@@ -257,12 +568,24 @@ cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
|
|
257
568
|
- `"ru"` - Russian
|
258
569
|
- `"en"` - English
|
259
570
|
|
571
|
+
### Available datasets list
|
572
|
+
|
573
|
+
```ruby
|
574
|
+
datasets = client.datasets_tree
|
575
|
+
datasets.each do |category|
|
576
|
+
puts "Category: #{category['name_en']}"
|
577
|
+
category.children.each do |dataset|
|
578
|
+
puts " #{dataset['id']}: #{dataset['name_en']}"
|
579
|
+
end
|
580
|
+
end
|
581
|
+
```
|
582
|
+
|
260
583
|
### Media and Documents
|
261
584
|
|
262
585
|
```ruby
|
263
586
|
# Download patent PDF
|
264
587
|
pdf_data = client.patent_media(
|
265
|
-
"National",
|
588
|
+
"National", # collection_id
|
266
589
|
"RU", # country_code
|
267
590
|
"U1", # doc_type
|
268
591
|
"2013/11/20", # pub_date
|
@@ -277,12 +600,6 @@ pdf_data = client.patent_media_by_id(
|
|
277
600
|
"National",
|
278
601
|
"document.pdf"
|
279
602
|
)
|
280
|
-
|
281
|
-
# Get available datasets
|
282
|
-
datasets = client.datasets_tree
|
283
|
-
datasets.each do |dataset|
|
284
|
-
puts "#{dataset['id']}: #{dataset['name']}"
|
285
|
-
end
|
286
603
|
```
|
287
604
|
|
288
605
|
## Advanced Features
|
@@ -361,7 +678,7 @@ shared_logger = Rospatent.shared_logger(level: :debug)
|
|
361
678
|
|
362
679
|
### Error Handling
|
363
680
|
|
364
|
-
Comprehensive error handling with specific error types:
|
681
|
+
Comprehensive error handling with specific error types and improved error message extraction:
|
365
682
|
|
366
683
|
```ruby
|
367
684
|
begin
|
@@ -383,6 +700,13 @@ rescue Rospatent::Errors::ConnectionError => e
|
|
383
700
|
puts "Connection error: #{e.message}"
|
384
701
|
puts "Original error: #{e.original_error}"
|
385
702
|
end
|
703
|
+
|
704
|
+
# Enhanced error message extraction
|
705
|
+
# The client automatically extracts error messages from various API response formats:
|
706
|
+
# - {"result": "Error message"} (Rospatent API format)
|
707
|
+
# - {"error": "Error message"} (Standard format)
|
708
|
+
# - {"message": "Error message"} (Alternative format)
|
709
|
+
# - {"details": "Validation details"} (Validation errors)
|
386
710
|
```
|
387
711
|
|
388
712
|
### Input Validation
|
@@ -422,64 +746,6 @@ puts "Cache enabled: #{global_stats[:configuration][:cache_enabled]}"
|
|
422
746
|
puts "API URL: #{global_stats[:configuration][:api_url]}"
|
423
747
|
```
|
424
748
|
|
425
|
-
## Environment Configuration
|
426
|
-
|
427
|
-
### Development Environment
|
428
|
-
|
429
|
-
```ruby
|
430
|
-
# Optimized for development
|
431
|
-
Rospatent.configure do |config|
|
432
|
-
config.environment = "development"
|
433
|
-
config.token = ENV['ROSPATENT_DEV_TOKEN']
|
434
|
-
config.log_level = :debug
|
435
|
-
config.log_requests = true
|
436
|
-
config.log_responses = true
|
437
|
-
config.cache_ttl = 60 # Short cache for development
|
438
|
-
config.timeout = 10 # Fast timeouts for quick feedback
|
439
|
-
end
|
440
|
-
```
|
441
|
-
|
442
|
-
### Staging Environment
|
443
|
-
|
444
|
-
```ruby
|
445
|
-
# Optimized for staging
|
446
|
-
Rospatent.configure do |config|
|
447
|
-
config.environment = "staging"
|
448
|
-
config.token = ENV['ROSPATENT_TOKEN']
|
449
|
-
config.log_level = :info
|
450
|
-
config.cache_ttl = 300 # Longer cache for performance
|
451
|
-
config.timeout = 45 # Longer timeouts for reliability
|
452
|
-
config.retry_count = 3 # More retries for resilience
|
453
|
-
end
|
454
|
-
```
|
455
|
-
|
456
|
-
### Production Environment
|
457
|
-
|
458
|
-
```ruby
|
459
|
-
# Optimized for production
|
460
|
-
Rospatent.configure do |config|
|
461
|
-
config.environment = "production"
|
462
|
-
config.token = ENV['ROSPATENT_TOKEN']
|
463
|
-
config.log_level = :warn
|
464
|
-
config.cache_ttl = 600 # Longer cache for performance
|
465
|
-
config.timeout = 60 # Longer timeouts for reliability
|
466
|
-
config.retry_count = 5 # More retries for resilience
|
467
|
-
end
|
468
|
-
```
|
469
|
-
|
470
|
-
### Configuration Validation
|
471
|
-
|
472
|
-
```ruby
|
473
|
-
# Validate current configuration
|
474
|
-
errors = Rospatent.validate_configuration
|
475
|
-
if errors.any?
|
476
|
-
puts "Configuration errors:"
|
477
|
-
errors.each { |error| puts " - #{error}" }
|
478
|
-
else
|
479
|
-
puts "Configuration is valid ✓"
|
480
|
-
end
|
481
|
-
```
|
482
|
-
|
483
749
|
## Rails Integration
|
484
750
|
|
485
751
|
### Generator
|
@@ -492,10 +758,23 @@ This creates `config/initializers/rospatent.rb`:
|
|
492
758
|
|
493
759
|
```ruby
|
494
760
|
Rospatent.configure do |config|
|
495
|
-
|
496
|
-
config.
|
761
|
+
# Token priority: Rails credentials > ROSPATENT_TOKEN > ROSPATENT_API_TOKEN
|
762
|
+
config.token = Rails.application.credentials.rospatent_token ||
|
763
|
+
ENV["ROSPATENT_TOKEN"] ||
|
764
|
+
ENV["ROSPATENT_API_TOKEN"]
|
765
|
+
|
766
|
+
# Environment configuration respects ROSPATENT_ENV
|
767
|
+
config.environment = ENV.fetch("ROSPATENT_ENV", Rails.env)
|
768
|
+
|
769
|
+
# CRITICAL: Environment variables take priority over Rails defaults
|
770
|
+
# This prevents DEBUG logs appearing in production if ROSPATENT_LOG_LEVEL=debug is set
|
771
|
+
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
|
772
|
+
ENV["ROSPATENT_LOG_LEVEL"].to_sym
|
773
|
+
else
|
774
|
+
Rails.env.production? ? :warn : :debug
|
775
|
+
end
|
776
|
+
|
497
777
|
config.cache_enabled = Rails.env.production?
|
498
|
-
config.log_level = Rails.env.production? ? :warn : :debug
|
499
778
|
end
|
500
779
|
```
|
501
780
|
|
@@ -615,8 +894,10 @@ The library uses **Faraday** as the HTTP client with redirect support for all en
|
|
615
894
|
|
616
895
|
⚠️ **Minor server-side limitations**:
|
617
896
|
- **Similar Patents by Text**: Occasionally returns `503 Service Unavailable` (a server-side issue, not a client implementation issue)
|
897
|
+
|
618
898
|
⚠️ **Documentation inconsistencies**:
|
619
899
|
- **Similar Patents**: According to the documentation, the array of hits is named `hits`, but the real implementation uses the name `data`
|
900
|
+
- **Available Datasets**: The `name` key in the real implementation has the localization suffix — `name_ru`, `name_en`
|
620
901
|
|
621
902
|
All core functionality works perfectly and is production-ready with a unified HTTP approach.
|
622
903
|
|
@@ -678,10 +959,7 @@ $ bundle exec rake cache:clear
|
|
678
959
|
$ bundle exec rake doc
|
679
960
|
|
680
961
|
# Run integration tests
|
681
|
-
$ bundle exec rake test_integration
|
682
|
-
|
683
|
-
# Performance benchmarks
|
684
|
-
$ bundle exec rake benchmark
|
962
|
+
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN='<your_jwt_token>' bundle exec rake test_integration
|
685
963
|
|
686
964
|
# Setup development environment
|
687
965
|
$ bundle exec rake setup
|
@@ -823,12 +1101,10 @@ $ bundle exec rake release
|
|
823
1101
|
- 📊 **Структурированное логирование** - JSON/текстовое логирование с отслеживанием запросов/ответов
|
824
1102
|
- 🚀 **Пакетные операции** - параллельная обработка множества патентов
|
825
1103
|
- ⚙️ **Адаптивные окружения** - различные конфигурации для development/staging/production
|
826
|
-
- 🧪 **Комплексное тестирование** -
|
1104
|
+
- 🧪 **Комплексное тестирование** - 219 тестов с 465 проверками, комплексное интеграционное тестирование
|
827
1105
|
- 📚 **Отличная документация** - подробные примеры и документация API
|
828
1106
|
|
829
|
-
##
|
830
|
-
|
831
|
-
### Установка
|
1107
|
+
## Установка
|
832
1108
|
|
833
1109
|
Добавьте в ваш Gemfile:
|
834
1110
|
|
@@ -836,75 +1112,502 @@ $ bundle exec rake release
|
|
836
1112
|
gem 'rospatent'
|
837
1113
|
```
|
838
1114
|
|
1115
|
+
Затем выполните:
|
1116
|
+
|
1117
|
+
```bash
|
1118
|
+
$ bundle install
|
1119
|
+
```
|
839
1120
|
Или установите напрямую:
|
840
1121
|
|
841
1122
|
```bash
|
842
1123
|
$ gem install rospatent
|
843
1124
|
```
|
844
1125
|
|
845
|
-
|
1126
|
+
## Быстрый старт
|
846
1127
|
|
847
1128
|
```ruby
|
848
|
-
|
849
|
-
|
850
|
-
# Настройка клиента
|
1129
|
+
# Минимальная конфигурация
|
851
1130
|
Rospatent.configure do |config|
|
852
1131
|
config.token = "ваш_jwt_токен"
|
853
1132
|
end
|
854
1133
|
|
855
|
-
# Создание клиента
|
1134
|
+
# Создание клиента и поиск
|
856
1135
|
client = Rospatent.client
|
1136
|
+
results = client.search(q: "ракета", limit: 10)
|
1137
|
+
|
1138
|
+
puts "Найдено #{results.total} результатов"
|
1139
|
+
results.hits.each do |hit|
|
1140
|
+
puts "Патент: #{hit['id']} - #{hit.dig('biblio', 'ru', 'title')}"
|
1141
|
+
end
|
857
1142
|
```
|
858
1143
|
|
859
|
-
##
|
1144
|
+
## Конфигурация
|
860
1145
|
|
861
|
-
###
|
1146
|
+
### Базовая настройка
|
862
1147
|
|
863
1148
|
```ruby
|
864
|
-
|
865
|
-
|
1149
|
+
Rospatent.configure do |config|
|
1150
|
+
# Обязательно
|
1151
|
+
config.token = "ваш_jwt_токен"
|
866
1152
|
|
867
|
-
#
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
offset: 100,
|
872
|
-
datasets: ["ru_since_1994"],
|
873
|
-
sort: "pub_date:desc",
|
874
|
-
highlight: true
|
875
|
-
)
|
1153
|
+
# Настройки API
|
1154
|
+
config.api_url = "https://searchplatform.rospatent.gov.ru/patsearch/v0.2"
|
1155
|
+
config.timeout = 30
|
1156
|
+
config.retry_count = 3
|
876
1157
|
|
877
|
-
#
|
878
|
-
|
879
|
-
results.hits.each do |patent|
|
880
|
-
puts "#{patent['id']}: #{patent['title']}"
|
1158
|
+
# Окружение (development, staging, production)
|
1159
|
+
config.environment = "production"
|
881
1160
|
end
|
882
1161
|
```
|
883
1162
|
|
884
|
-
###
|
1163
|
+
### Продвинутая настройка
|
885
1164
|
|
886
1165
|
```ruby
|
887
|
-
|
888
|
-
|
1166
|
+
Rospatent.configure do |config|
|
1167
|
+
config.token = "ваш_jwt_токен"
|
1168
|
+
|
1169
|
+
# Кеширование (включено по умолчанию)
|
1170
|
+
config.cache_enabled = true
|
1171
|
+
config.cache_ttl = 300 # 5 минут
|
1172
|
+
config.cache_max_size = 1000 # Максимум элементов кеша
|
1173
|
+
|
1174
|
+
# Логирование
|
1175
|
+
config.log_level = :info # :debug, :info, :warn, :error
|
1176
|
+
config.log_requests = true # Логировать API запросы
|
1177
|
+
config.log_responses = true # Логировать API ответы
|
889
1178
|
|
890
|
-
#
|
891
|
-
|
892
|
-
|
1179
|
+
# Настройки соединения
|
1180
|
+
config.connection_pool_size = 5
|
1181
|
+
config.connection_keep_alive = true
|
893
1182
|
|
894
|
-
|
895
|
-
|
1183
|
+
# Управление токенами
|
1184
|
+
config.token_expires_at = Time.now + 3600
|
1185
|
+
config.token_refresh_callback = -> { refresh_token! }
|
1186
|
+
end
|
896
1187
|
```
|
897
1188
|
|
898
|
-
###
|
1189
|
+
### Конфигурация для конкретных окружений
|
899
1190
|
|
900
|
-
|
901
|
-
# Поиск похожих патентов по ID
|
902
|
-
similar = client.similar_patents_by_id("RU134694U1_20131120", count: 50)
|
1191
|
+
Gem автоматически настраивается под окружение с разумными значениями по умолчанию:
|
903
1192
|
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
1193
|
+
#### Окружение разработки
|
1194
|
+
|
1195
|
+
```ruby
|
1196
|
+
# Оптимизировано для разработки
|
1197
|
+
Rospatent.configure do |config|
|
1198
|
+
config.environment = "development"
|
1199
|
+
config.token = ENV['ROSPATENT_TOKEN']
|
1200
|
+
config.log_level = :debug
|
1201
|
+
config.log_requests = true
|
1202
|
+
config.log_responses = true
|
1203
|
+
config.cache_ttl = 60 # Короткий кеш для разработки
|
1204
|
+
config.timeout = 10 # Быстрые таймауты для быстрой обратной связи
|
1205
|
+
end
|
1206
|
+
```
|
1207
|
+
|
1208
|
+
#### Окружение Staging
|
1209
|
+
|
1210
|
+
```ruby
|
1211
|
+
# Оптимизировано для staging
|
1212
|
+
Rospatent.configure do |config|
|
1213
|
+
config.environment = "staging"
|
1214
|
+
config.token = ENV['ROSPATENT_TOKEN']
|
1215
|
+
config.log_level = :info
|
1216
|
+
config.cache_ttl = 300 # Более длительный кеш для производительности
|
1217
|
+
config.timeout = 45 # Более длительные таймауты для надежности
|
1218
|
+
config.retry_count = 3 # Больше повторов для устойчивости
|
1219
|
+
end
|
1220
|
+
```
|
1221
|
+
|
1222
|
+
#### Продакшн окружение
|
1223
|
+
|
1224
|
+
```ruby
|
1225
|
+
# Оптимизировано для продакшна
|
1226
|
+
Rospatent.configure do |config|
|
1227
|
+
config.environment = "production"
|
1228
|
+
config.token = ENV['ROSPATENT_TOKEN']
|
1229
|
+
config.log_level = :warn
|
1230
|
+
config.cache_ttl = 600 # Более длительный кеш для производительности
|
1231
|
+
config.timeout = 60 # Более длительные таймауты для надежности
|
1232
|
+
config.retry_count = 5 # Больше повторов для устойчивости
|
1233
|
+
end
|
1234
|
+
```
|
1235
|
+
|
1236
|
+
### Переменные окружения и интеграция с Rails
|
1237
|
+
|
1238
|
+
⚠️ **КРИТИЧНО**: Понимание приоритета переменных окружения необходимо для избежания проблем конфигурации, особенно в Rails приложениях.
|
1239
|
+
|
1240
|
+
#### Приоритет конфигурации токена
|
1241
|
+
|
1242
|
+
1. **Rails credentials**: `Rails.application.credentials.rospatent_token`
|
1243
|
+
2. **Основная переменная окружения**: `ROSPATENT_TOKEN`
|
1244
|
+
3. **Устаревшая переменная окружения**: `ROSPATENT_API_TOKEN`
|
1245
|
+
|
1246
|
+
```bash
|
1247
|
+
# Рекомендуемый подход
|
1248
|
+
export ROSPATENT_TOKEN="your_jwt_token"
|
1249
|
+
|
1250
|
+
# Поддержка устаревшего формата (все еще работает)
|
1251
|
+
export ROSPATENT_API_TOKEN="your_jwt_token"
|
1252
|
+
```
|
1253
|
+
|
1254
|
+
#### Приоритет конфигурации уровня логирования
|
1255
|
+
|
1256
|
+
```ruby
|
1257
|
+
# Переменная окружения имеет приоритет над настройками Rails по умолчанию
|
1258
|
+
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
|
1259
|
+
ENV["ROSPATENT_LOG_LEVEL"].to_sym
|
1260
|
+
else
|
1261
|
+
Rails.env.production? ? :warn : :debug
|
1262
|
+
end
|
1263
|
+
```
|
1264
|
+
|
1265
|
+
⚠️ **Частая проблема**: Установка `ROSPATENT_LOG_LEVEL=debug` в продакшне переопределит логику Rails и приведёт к появлению DEBUG логов в продакшне!
|
1266
|
+
|
1267
|
+
#### Полный справочник переменных окружения
|
1268
|
+
|
1269
|
+
```bash
|
1270
|
+
# Основная конфигурация
|
1271
|
+
ROSPATENT_TOKEN="your_jwt_token" # Токен аутентификации API
|
1272
|
+
ROSPATENT_ENV="production" # Переопределить Rails.env при необходимости
|
1273
|
+
ROSPATENT_API_URL="custom_url" # Переопределить URL API по умолчанию
|
1274
|
+
|
1275
|
+
# Конфигурация логирования
|
1276
|
+
ROSPATENT_LOG_LEVEL="warn" # debug, info, warn, error
|
1277
|
+
ROSPATENT_LOG_REQUESTS="false" # Логировать API запросы
|
1278
|
+
ROSPATENT_LOG_RESPONSES="false" # Логировать API ответы
|
1279
|
+
|
1280
|
+
# Конфигурация кеша
|
1281
|
+
ROSPATENT_CACHE_ENABLED="true" # Включить/отключить кеширование
|
1282
|
+
ROSPATENT_CACHE_TTL="300" # TTL кеша в секундах
|
1283
|
+
ROSPATENT_CACHE_MAX_SIZE="1000" # Максимальное количество элементов кеша
|
1284
|
+
|
1285
|
+
# Конфигурация соединения
|
1286
|
+
ROSPATENT_TIMEOUT="30" # Таймаут запроса в секундах
|
1287
|
+
ROSPATENT_RETRY_COUNT="3" # Количество повторов
|
1288
|
+
ROSPATENT_POOL_SIZE="5" # Размер пула соединений
|
1289
|
+
ROSPATENT_KEEP_ALIVE="true" # Keep-alive соединения
|
1290
|
+
|
1291
|
+
# Переопределения для конкретных окружений
|
1292
|
+
ROSPATENT_DEV_API_URL="dev_url" # URL API для разработки
|
1293
|
+
ROSPATENT_STAGING_API_URL="staging_url" # URL API для staging
|
1294
|
+
```
|
1295
|
+
|
1296
|
+
#### Лучшие практики для Rails
|
1297
|
+
|
1298
|
+
1. **Используйте Rails credentials для токенов**:
|
1299
|
+
```bash
|
1300
|
+
rails credentials:edit
|
1301
|
+
# Добавьте: rospatent_token: your_jwt_token
|
1302
|
+
```
|
1303
|
+
|
1304
|
+
2. **Установите переменные для конкретных окружений**:
|
1305
|
+
```bash
|
1306
|
+
# config/environments/production.rb
|
1307
|
+
ENV["ROSPATENT_LOG_LEVEL"] ||= "warn"
|
1308
|
+
ENV["ROSPATENT_CACHE_ENABLED"] ||= "true"
|
1309
|
+
```
|
1310
|
+
|
1311
|
+
3. **Избегайте установки DEBUG уровня в продакшне**:
|
1312
|
+
```bash
|
1313
|
+
# ❌ НЕ ДЕЛАЙТЕ ТАК в продакшне
|
1314
|
+
export ROSPATENT_LOG_LEVEL=debug
|
1315
|
+
|
1316
|
+
# ✅ ДЕЛАЙТЕ ТАК
|
1317
|
+
export ROSPATENT_LOG_LEVEL=warn
|
1318
|
+
```
|
1319
|
+
|
1320
|
+
### Валидация конфигурации
|
1321
|
+
|
1322
|
+
```ruby
|
1323
|
+
# Валидация текущей конфигурации
|
1324
|
+
errors = Rospatent.validate_configuration
|
1325
|
+
if errors.any?
|
1326
|
+
puts "Ошибки конфигурации:"
|
1327
|
+
errors.each { |error| puts " - #{error}" }
|
1328
|
+
else
|
1329
|
+
puts "Конфигурация действительна ✓"
|
1330
|
+
end
|
1331
|
+
```
|
1332
|
+
|
1333
|
+
## Основное использование
|
1334
|
+
|
1335
|
+
### Поиск патентов
|
1336
|
+
|
1337
|
+
```ruby
|
1338
|
+
# Простой поиск
|
1339
|
+
results = client.search(q: "солнечная батарея")
|
1340
|
+
|
1341
|
+
# Поиск на естественном языке
|
1342
|
+
results = client.search(qn: "конструкция ракетного двигателя")
|
1343
|
+
|
1344
|
+
# Расширенный поиск с всеми опциями
|
1345
|
+
results = client.search(
|
1346
|
+
q: "искусственный интеллект AND нейронная сеть",
|
1347
|
+
limit: 50,
|
1348
|
+
offset: 100,
|
1349
|
+
datasets: ["ru_since_1994"],
|
1350
|
+
filter: {
|
1351
|
+
"classification.ipc_group": { "values": ["G06N"] },
|
1352
|
+
"application.filing_date": { "range": { "gte": "20200101" } }
|
1353
|
+
},
|
1354
|
+
sort: "publication_date:desc", # то же самое, что 'sort: :pub_date'; см. варианты параметров сортировки в Search#validate_sort_parameter
|
1355
|
+
group_by: "family:dwpi", # Группировка по семействам: "family:docdb" или "family:dwpi"
|
1356
|
+
include_facets: true,
|
1357
|
+
pre_tag: "<mark>", # Оба тега должны быть указаны вместе
|
1358
|
+
post_tag: "</mark>", # Могут быть строками или массивами
|
1359
|
+
highlight: { # Продвинутая настройка подсветки (независимо от тегов)
|
1360
|
+
"profiles" => [
|
1361
|
+
{ "q" => "нейронная сеть", "pre_tag" => "<b>", "post_tag" => "</b>" },
|
1362
|
+
"_searchquery_"
|
1363
|
+
]
|
1364
|
+
}
|
1365
|
+
)
|
1366
|
+
|
1367
|
+
# Простая подсветка с тегами (оба тега обязательны)
|
1368
|
+
results = client.search(
|
1369
|
+
q: "ракета",
|
1370
|
+
pre_tag: "<mark>",
|
1371
|
+
post_tag: "</mark>"
|
1372
|
+
)
|
1373
|
+
|
1374
|
+
# Многоцветная подсветка с массивами
|
1375
|
+
results = client.search(
|
1376
|
+
q: "космическая ракета",
|
1377
|
+
pre_tag: ["<b>", "<i>"], # Циклическая подсветка
|
1378
|
+
post_tag: ["</b>", "</i>"] # разными тегами
|
1379
|
+
)
|
1380
|
+
|
1381
|
+
# Продвинутая подсветка с использованием профилей (независимо от pre_tag/post_tag)
|
1382
|
+
results = client.search(
|
1383
|
+
q: "ракета",
|
1384
|
+
highlight: {
|
1385
|
+
"profiles" => [
|
1386
|
+
{ "q" => "космическая", "pre_tag" => "<b>", "post_tag" => "</b>" },
|
1387
|
+
"_searchquery_" # Ссылка на параметры подсветки основного поискового запроса
|
1388
|
+
]
|
1389
|
+
}
|
1390
|
+
)
|
1391
|
+
|
1392
|
+
# Группировка по семействам патентов (группирует патенты одного изобретения)
|
1393
|
+
results = client.search(
|
1394
|
+
q: "ракета",
|
1395
|
+
group_by: "family:docdb", # Простые семейства патентов DOCDB
|
1396
|
+
datasets: ["dwpi"],
|
1397
|
+
limit: 10
|
1398
|
+
)
|
1399
|
+
|
1400
|
+
results = client.search(
|
1401
|
+
q: "ракета",
|
1402
|
+
group_by: "family:dwpi", # Простые семейства патентов DWPI
|
1403
|
+
datasets: ["dwpi"],
|
1404
|
+
limit: 10
|
1405
|
+
)
|
1406
|
+
|
1407
|
+
# Обработка результатов
|
1408
|
+
puts "Найдено: #{results.total} патентов (доступно #{results.available})"
|
1409
|
+
puts "Показано: #{results.count}"
|
1410
|
+
|
1411
|
+
results.hits.each do |hit|
|
1412
|
+
puts "ID: #{hit['id']}"
|
1413
|
+
puts "Название: #{hit.dig('biblio', 'ru', 'title')}"
|
1414
|
+
puts "Дата: #{hit.dig('common', 'publication_date')}"
|
1415
|
+
puts "МПК: #{hit.dig('common', 'classification', 'ipc')&.map {|c| c['fullname']}&.join('; ')}"
|
1416
|
+
puts "---"
|
1417
|
+
end
|
1418
|
+
```
|
1419
|
+
|
1420
|
+
### Расширенные параметры фильтрации
|
1421
|
+
|
1422
|
+
Параметр `filter` поддерживает сложную фильтрацию с автоматической валидацией и преобразованием форматов:
|
1423
|
+
|
1424
|
+
#### Списочные фильтры (требуют формат `{"values": [...]}`)
|
1425
|
+
|
1426
|
+
```ruby
|
1427
|
+
# Фильтры по классификации
|
1428
|
+
results = client.search(
|
1429
|
+
q: "искусственный интеллект",
|
1430
|
+
filter: {
|
1431
|
+
"classification.ipc_group": { "values": ["G06N", "G06F"] },
|
1432
|
+
"classification.cpc_group": { "values": ["G06N3/", "G06N20/"] }
|
1433
|
+
}
|
1434
|
+
)
|
1435
|
+
|
1436
|
+
# Фильтры по авторам и патентообладателям
|
1437
|
+
results = client.search(
|
1438
|
+
q: "изобретение",
|
1439
|
+
filter: {
|
1440
|
+
"authors": { "values": ["Иванов И.И.", "Петров П.П."] },
|
1441
|
+
"patent_holders": { "values": ["ООО Компания"] },
|
1442
|
+
"country": { "values": ["RU", "US"] },
|
1443
|
+
"kind": { "values": ["A1", "U1"] }
|
1444
|
+
}
|
1445
|
+
)
|
1446
|
+
|
1447
|
+
# Фильтры по ID документов
|
1448
|
+
results = client.search(
|
1449
|
+
q: "устройство",
|
1450
|
+
filter: {
|
1451
|
+
"ids": { "values": ["RU134694U1_20131120", "RU2358138C1_20090610"] }
|
1452
|
+
}
|
1453
|
+
)
|
1454
|
+
```
|
1455
|
+
|
1456
|
+
#### Диапазонные фильтры по датам (требуют формат `{"range": {"operator": "YYYYMMDD"}}`)
|
1457
|
+
|
1458
|
+
```ruby
|
1459
|
+
# Автоматическое преобразование формата дат
|
1460
|
+
results = client.search(
|
1461
|
+
q: "инновация",
|
1462
|
+
filter: {
|
1463
|
+
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
|
1464
|
+
"application.filing_date": { "range": { "gte": "2019-06-15" } }
|
1465
|
+
}
|
1466
|
+
)
|
1467
|
+
|
1468
|
+
# Прямой формат API (YYYYMMDD)
|
1469
|
+
results = client.search(
|
1470
|
+
q: "технология",
|
1471
|
+
filter: {
|
1472
|
+
"date_published": { "range": { "gte": "20200101", "lt": "20240101" } }
|
1473
|
+
}
|
1474
|
+
)
|
1475
|
+
|
1476
|
+
# Использование объектов Date (автоматически конвертируются)
|
1477
|
+
results = client.search(
|
1478
|
+
q: "патент",
|
1479
|
+
filter: {
|
1480
|
+
"application.filing_date": {
|
1481
|
+
"range": {
|
1482
|
+
"gte": Date.new(2020, 1, 1),
|
1483
|
+
"lte": Date.new(2023, 12, 31)
|
1484
|
+
}
|
1485
|
+
}
|
1486
|
+
}
|
1487
|
+
)
|
1488
|
+
```
|
1489
|
+
|
1490
|
+
**Поддерживаемые операторы дат**: `gt`, `gte`, `lt`, `lte`
|
1491
|
+
|
1492
|
+
**Преобразование формата дат**:
|
1493
|
+
- `"2020-01-01"` → `"20200101"`
|
1494
|
+
- `Date.new(2020, 1, 1)` → `"20200101"`
|
1495
|
+
- `"20200101"` → `"20200101"` (без изменений)
|
1496
|
+
|
1497
|
+
#### Сложные составные фильтры
|
1498
|
+
|
1499
|
+
```ruby
|
1500
|
+
# Комплексный пример фильтра
|
1501
|
+
results = client.search(
|
1502
|
+
q: "машинное обучение",
|
1503
|
+
filter: {
|
1504
|
+
# Списочные фильтры
|
1505
|
+
"classification.ipc_group": { "values": ["G06N", "G06F"] },
|
1506
|
+
"country": { "values": ["RU", "US", "CN"] },
|
1507
|
+
"kind": { "values": ["A1", "A2"] },
|
1508
|
+
"authors": { "values": ["Иванов И.И."] },
|
1509
|
+
|
1510
|
+
# Диапазонные фильтры по датам
|
1511
|
+
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
|
1512
|
+
"application.filing_date": { "range": { "gte": "2019-01-01" } }
|
1513
|
+
},
|
1514
|
+
limit: 50
|
1515
|
+
)
|
1516
|
+
```
|
1517
|
+
|
1518
|
+
**Поддерживаемые поля фильтров**:
|
1519
|
+
|
1520
|
+
*Списочные фильтры (требуют формат `{"values": [...]}`)::*
|
1521
|
+
- `authors` - Авторы патентов
|
1522
|
+
- `patent_holders` - Патентообладатели/правопреемники
|
1523
|
+
- `country` - Коды стран
|
1524
|
+
- `kind` - Типы документов
|
1525
|
+
- `ids` - Конкретные ID документов
|
1526
|
+
- `classification.ipc*` - Коды классификации IPC
|
1527
|
+
- `classification.cpc*` - Коды классификации CPC
|
1528
|
+
|
1529
|
+
*Фильтры по датам (требуют формат `{"range": {"operator": "YYYYMMDD"}}`)::*
|
1530
|
+
- `date_published` - Дата публикации
|
1531
|
+
- `application.filing_date` - Дата подачи заявки
|
1532
|
+
|
1533
|
+
**Валидация фильтров**:
|
1534
|
+
- ✅ Автоматическая валидация названий полей
|
1535
|
+
- ✅ Валидация структуры (списочный vs диапазонный формат)
|
1536
|
+
- ✅ Преобразование и валидация формата дат
|
1537
|
+
- ✅ Валидация операторов для диапазонов
|
1538
|
+
- ✅ Полезные сообщения об ошибках для неверных фильтров
|
1539
|
+
|
1540
|
+
```ruby
|
1541
|
+
# Эти примеры вызовут ValidationError с конкретными сообщениями:
|
1542
|
+
client.search(
|
1543
|
+
q: "тест",
|
1544
|
+
filter: { "invalid_field": { "values": ["тест"] } }
|
1545
|
+
)
|
1546
|
+
# Ошибка: "Invalid filter field: invalid_field"
|
1547
|
+
|
1548
|
+
client.search(
|
1549
|
+
q: "тест",
|
1550
|
+
filter: { "authors": ["прямой", "массив"] } # Отсутствует обертка {"values": [...]}
|
1551
|
+
)
|
1552
|
+
# Ошибка: "Filter 'authors' requires format: {\"values\": [...]}"
|
1553
|
+
|
1554
|
+
client.search(
|
1555
|
+
q: "тест",
|
1556
|
+
filter: { "date_published": { "range": { "invalid_op": "20200101" } } }
|
1557
|
+
)
|
1558
|
+
# Ошибка: "Invalid range operator: invalid_op. Supported: gt, gte, lt, lte"
|
1559
|
+
```
|
1560
|
+
|
1561
|
+
### Получение документов патентов
|
1562
|
+
|
1563
|
+
```ruby
|
1564
|
+
# По идентификатору документа
|
1565
|
+
patent = client.patent("RU134694U1_20131120")
|
1566
|
+
|
1567
|
+
# По компонентам идентификатора
|
1568
|
+
patent_doc = client.patent_by_components(
|
1569
|
+
"RU", # country_code
|
1570
|
+
"134694", # number
|
1571
|
+
"U1", # doc_type
|
1572
|
+
Date.new(2013, 11, 20) # date (String или объект Date)
|
1573
|
+
)
|
1574
|
+
|
1575
|
+
# Доступ к данным патента
|
1576
|
+
title = patent_doc.dig('biblio', 'ru', 'title')
|
1577
|
+
abstract = patent_doc.dig('abstract', 'ru')
|
1578
|
+
inventors = patent_doc.dig('biblio', 'ru', 'inventor')
|
1579
|
+
```
|
1580
|
+
### Парсинг содержимого патента
|
1581
|
+
|
1582
|
+
Получение чистого текста или структурированного содержимого:
|
1583
|
+
|
1584
|
+
```ruby
|
1585
|
+
# Парсинг аннотации
|
1586
|
+
abstract_text = client.parse_abstract(patent_doc)
|
1587
|
+
abstract_html = client.parse_abstract(patent_doc, format: :html)
|
1588
|
+
abstract_ru = client.parse_abstract(patent_doc, language: "ru")
|
1589
|
+
|
1590
|
+
# Парсинг описания
|
1591
|
+
description_text = client.parse_description(patent_doc)
|
1592
|
+
description_html = client.parse_description(patent_doc, format: :html)
|
1593
|
+
|
1594
|
+
# Парсинг описания с разбивкой на секции
|
1595
|
+
sections = client.parse_description(patent_doc, format: :sections)
|
1596
|
+
sections.each do |section|
|
1597
|
+
puts "Секция #{section[:number]}: #{section[:content]}"
|
1598
|
+
end
|
1599
|
+
```
|
1600
|
+
|
1601
|
+
### Поиск похожих патентов
|
1602
|
+
|
1603
|
+
```ruby
|
1604
|
+
# Поиск похожих патентов по ID
|
1605
|
+
similar = client.similar_patents_by_id("RU134694U1_20131120", count: 50)
|
1606
|
+
|
1607
|
+
# Поиск похожих патентов по описанию текста
|
1608
|
+
similar = client.similar_patents_by_text(
|
1609
|
+
"Ракетный двигатель с улучшенной тягой ...", # минимум 50 слов в запросе
|
1610
|
+
count: 25
|
908
1611
|
)
|
909
1612
|
|
910
1613
|
# Обработка похожих патентов
|
@@ -920,20 +1623,20 @@ end
|
|
920
1623
|
```ruby
|
921
1624
|
# Поиск классификационных кодов, связанных с ракетами в IPC
|
922
1625
|
ipc_results = client.classification_search("ipc", query: "ракета", lang: "ru")
|
923
|
-
puts "Найдено #{ipc_results
|
1626
|
+
puts "Найдено #{ipc_results.size} кодов IPC"
|
924
1627
|
|
925
|
-
ipc_results
|
926
|
-
puts "#{
|
1628
|
+
ipc_results&.each do |result|
|
1629
|
+
puts "#{result['Code']}: #{result['Description']}"
|
927
1630
|
end
|
928
1631
|
|
929
1632
|
# Поиск кодов, связанных с ракетами в CPC на английском
|
930
1633
|
cpc_results = client.classification_search("cpc", query: "rocket", lang: "en")
|
931
1634
|
|
932
1635
|
# Получение подробной информации о конкретном классификационном коде
|
933
|
-
|
934
|
-
puts "Код: #{
|
935
|
-
puts "Описание: #{
|
936
|
-
puts "Иерархия: #{
|
1636
|
+
code, info = client.classification_code("ipc", code: "F02K9/00", lang: "ru")&.first
|
1637
|
+
puts "Код: #{code}"
|
1638
|
+
puts "Описание: #{info&.first['Description']}"
|
1639
|
+
puts "Иерархия: #{info&.map{|level| level['Code']}&.join(' → ')}"
|
937
1640
|
|
938
1641
|
# Получение информации о коде CPC на английском
|
939
1642
|
cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
|
@@ -947,12 +1650,24 @@ cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
|
|
947
1650
|
- `"ru"` - Русский
|
948
1651
|
- `"en"` - Английский
|
949
1652
|
|
1653
|
+
### Список доступных датасетов
|
1654
|
+
|
1655
|
+
```ruby
|
1656
|
+
datasets = client.datasets_tree
|
1657
|
+
datasets.each do |category|
|
1658
|
+
puts "Категория: #{category['name_ru']}"
|
1659
|
+
category.children.each do |dataset|
|
1660
|
+
puts " #{dataset['id']}: #{dataset['name_ru']}"
|
1661
|
+
end
|
1662
|
+
end
|
1663
|
+
```
|
1664
|
+
|
950
1665
|
### Медиафайлы и документы
|
951
1666
|
|
952
1667
|
```ruby
|
953
1668
|
# Скачивание PDF патента
|
954
1669
|
pdf_data = client.patent_media(
|
955
|
-
"National",
|
1670
|
+
"National", # collection_id
|
956
1671
|
"RU", # country_code
|
957
1672
|
"U1", # doc_type
|
958
1673
|
"2013/11/20", # pub_date
|
@@ -967,124 +1682,291 @@ pdf_data = client.patent_media_by_id(
|
|
967
1682
|
"National",
|
968
1683
|
"document.pdf"
|
969
1684
|
)
|
970
|
-
|
971
|
-
# Получение доступных датасетов
|
972
|
-
datasets = client.datasets_tree
|
973
|
-
datasets.each do |dataset|
|
974
|
-
puts "#{dataset['id']}: #{dataset['name']}"
|
975
|
-
end
|
976
1685
|
```
|
977
1686
|
|
978
1687
|
## Расширенные возможности
|
979
1688
|
|
980
1689
|
### Пакетные операции
|
981
1690
|
|
1691
|
+
Эффективная обработка множества патентов с параллельными запросами:
|
1692
|
+
|
982
1693
|
```ruby
|
983
|
-
|
1694
|
+
document_ids = ["RU134694U1_20131120", "RU2358138C1_20090610", "RU2756123C1_20210927"]
|
984
1695
|
|
985
|
-
|
986
|
-
|
987
|
-
|
1696
|
+
# Обработка патентов пакетами
|
1697
|
+
client.batch_patents(document_ids, batch_size: 5) do |patent_doc|
|
1698
|
+
if patent_doc[:error]
|
1699
|
+
puts "Ошибка для #{patent_doc[:document_id]}: #{patent_doc[:error]}"
|
1700
|
+
else
|
1701
|
+
puts "Получен патент: #{patent_doc['id']}"
|
1702
|
+
# Обработка документа патента
|
1703
|
+
end
|
988
1704
|
end
|
1705
|
+
|
1706
|
+
# Или сбор всех результатов
|
1707
|
+
patents = []
|
1708
|
+
client.batch_patents(document_ids) { |doc| patents << doc }
|
989
1709
|
```
|
990
1710
|
|
991
1711
|
### Кеширование
|
992
1712
|
|
1713
|
+
Автоматическое интеллектуальное кеширование улучшает производительность:
|
1714
|
+
|
993
1715
|
```ruby
|
994
|
-
#
|
995
|
-
|
996
|
-
|
997
|
-
config.cache_ttl = 600 # 10 минут
|
998
|
-
config.cache_max_size = 1000 # Максимум элементов
|
999
|
-
end
|
1716
|
+
# Кеширование автоматическое и прозрачное
|
1717
|
+
patent1 = client.patent("RU134694U1_20131120") # API вызов
|
1718
|
+
patent2 = client.patent("RU134694U1_20131120") # Кешированный результат
|
1000
1719
|
|
1001
|
-
#
|
1720
|
+
# Проверка статистики кеша
|
1002
1721
|
stats = client.statistics
|
1003
|
-
puts "
|
1722
|
+
puts "Процент попаданий в кеш: #{stats[:cache_stats][:hit_rate_percent]}%"
|
1723
|
+
puts "Всего запросов: #{stats[:requests_made]}"
|
1724
|
+
puts "Среднее время ответа: #{stats[:average_request_time]}с"
|
1725
|
+
|
1726
|
+
# Использование общего кеша между клиентами
|
1727
|
+
shared_cache = Rospatent.shared_cache
|
1728
|
+
client1 = Rospatent.client(cache: shared_cache)
|
1729
|
+
client2 = Rospatent.client(cache: shared_cache)
|
1730
|
+
|
1731
|
+
# Ручное управление кешем
|
1732
|
+
shared_cache.clear # Очистить все кешированные данные
|
1733
|
+
expired_count = shared_cache.cleanup_expired # Удалить истекшие записи
|
1734
|
+
cache_stats = shared_cache.statistics # Получить детальную статистику кеша
|
1735
|
+
```
|
1736
|
+
|
1737
|
+
### Настройка логирования
|
1738
|
+
|
1739
|
+
Настройка детального логирования для мониторинга и отладки:
|
1740
|
+
|
1741
|
+
```ruby
|
1742
|
+
# Создание собственного логгера
|
1743
|
+
logger = Rospatent::Logger.new(
|
1744
|
+
output: Rails.logger, # Или любой объект IO
|
1745
|
+
level: :info,
|
1746
|
+
formatter: :json # :json или :text
|
1747
|
+
)
|
1748
|
+
|
1749
|
+
client = Rospatent.client(logger: logger)
|
1750
|
+
|
1751
|
+
# Логи включают:
|
1752
|
+
# - API запросы/ответы с временными метками
|
1753
|
+
# - Операции кеша (попадания/промахи)
|
1754
|
+
# - Детали ошибок с контекстом
|
1755
|
+
# - Метрики производительности
|
1756
|
+
|
1757
|
+
# Доступ к общему логгеру
|
1758
|
+
shared_logger = Rospatent.shared_logger(level: :debug)
|
1004
1759
|
```
|
1005
1760
|
|
1006
1761
|
### Обработка ошибок
|
1007
1762
|
|
1763
|
+
Комплексная обработка ошибок с конкретными типами ошибок и улучшенным извлечением сообщений об ошибках:
|
1764
|
+
|
1008
1765
|
```ruby
|
1009
1766
|
begin
|
1010
|
-
|
1767
|
+
patent = client.patent("INVALID_ID")
|
1768
|
+
rescue Rospatent::Errors::ValidationError => e
|
1769
|
+
puts "Неверный ввод: #{e.message}"
|
1770
|
+
puts "Ошибки полей: #{e.errors}" if e.errors.any?
|
1771
|
+
rescue Rospatent::Errors::NotFoundError => e
|
1772
|
+
puts "Патент не найден: #{e.message}"
|
1773
|
+
rescue Rospatent::Errors::RateLimitError => e
|
1774
|
+
puts "Ограничение скорости. Повторить через: #{e.retry_after} секунд"
|
1011
1775
|
rescue Rospatent::Errors::AuthenticationError => e
|
1012
1776
|
puts "Ошибка аутентификации: #{e.message}"
|
1013
|
-
rescue Rospatent::Errors::RateLimitError => e
|
1014
|
-
puts "Превышен лимит запросов: #{e.message}"
|
1015
1777
|
rescue Rospatent::Errors::ApiError => e
|
1016
|
-
puts "Ошибка API: #{e.message}"
|
1778
|
+
puts "Ошибка API (#{e.status_code}): #{e.message}"
|
1779
|
+
puts "ID запроса: #{e.request_id}" if e.request_id
|
1780
|
+
retry if e.retryable?
|
1781
|
+
rescue Rospatent::Errors::ConnectionError => e
|
1782
|
+
puts "Ошибка соединения: #{e.message}"
|
1783
|
+
puts "Исходная ошибка: #{e.original_error}"
|
1017
1784
|
end
|
1785
|
+
|
1786
|
+
# Улучшенное извлечение сообщений об ошибках
|
1787
|
+
# Клиент автоматически извлекает сообщения об ошибках из различных форматов ответов API:
|
1788
|
+
# - {"result": "Сообщение об ошибке"} (формат API Роспатента)
|
1789
|
+
# - {"error": "Сообщение об ошибке"} (стандартный формат)
|
1790
|
+
# - {"message": "Сообщение об ошибке"} (альтернативный формат)
|
1791
|
+
# - {"details": "Детали валидации"} (ошибки валидации)
|
1018
1792
|
```
|
1019
1793
|
|
1020
|
-
|
1794
|
+
### Валидация входных данных
|
1021
1795
|
|
1022
|
-
|
1796
|
+
Все входные данные автоматически валидируются с полезными сообщениями об ошибках:
|
1023
1797
|
|
1024
1798
|
```ruby
|
1025
|
-
#
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1799
|
+
# Эти примеры вызовут ValidationError с конкретными сообщениями:
|
1800
|
+
client.search(limit: 0) # "Limit must be at least 1"
|
1801
|
+
client.patent("") # "Document_id cannot be empty"
|
1802
|
+
client.similar_patents_by_text("", count: -1) # Множественные ошибки валидации
|
1803
|
+
|
1804
|
+
# Валидация включает:
|
1805
|
+
# - Типы и форматы параметров
|
1806
|
+
# - Валидация формата ID патента
|
1807
|
+
# - Валидация формата даты
|
1808
|
+
# - Валидация значений перечислений
|
1809
|
+
# - Валидация обязательных полей
|
1035
1810
|
```
|
1036
1811
|
|
1037
|
-
###
|
1812
|
+
### Мониторинг производительности
|
1813
|
+
|
1814
|
+
Отслеживание производительности и статистики использования:
|
1038
1815
|
|
1039
1816
|
```ruby
|
1040
|
-
#
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1817
|
+
# Статистика конкретного клиента
|
1818
|
+
stats = client.statistics
|
1819
|
+
puts "Выполнено запросов: #{stats[:requests_made]}"
|
1820
|
+
puts "Общая продолжительность: #{stats[:total_duration_seconds]}с"
|
1821
|
+
puts "Среднее время запроса: #{stats[:average_request_time]}с"
|
1822
|
+
puts "Процент попаданий в кеш: #{stats[:cache_stats][:hit_rate_percent]}%"
|
1823
|
+
|
1824
|
+
# Глобальная статистика
|
1825
|
+
global_stats = Rospatent.statistics
|
1826
|
+
puts "Окружение: #{global_stats[:configuration][:environment]}"
|
1827
|
+
puts "Кеш включен: #{global_stats[:configuration][:cache_enabled]}"
|
1828
|
+
puts "URL API: #{global_stats[:configuration][:api_url]}"
|
1829
|
+
```
|
1830
|
+
|
1831
|
+
## Интеграция с Rails
|
1832
|
+
|
1833
|
+
### Генератор
|
1834
|
+
|
1835
|
+
```bash
|
1836
|
+
$ rails generate rospatent:install
|
1049
1837
|
```
|
1050
1838
|
|
1051
|
-
|
1839
|
+
Это создает `config/initializers/rospatent.rb`:
|
1052
1840
|
|
1053
1841
|
```ruby
|
1054
|
-
# Оптимизировано для продакшна
|
1055
1842
|
Rospatent.configure do |config|
|
1056
|
-
|
1057
|
-
config.token =
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1843
|
+
# Приоритет токена: Rails credentials > ROSPATENT_TOKEN > ROSPATENT_API_TOKEN
|
1844
|
+
config.token = Rails.application.credentials.rospatent_token ||
|
1845
|
+
ENV["ROSPATENT_TOKEN"] ||
|
1846
|
+
ENV["ROSPATENT_API_TOKEN"]
|
1847
|
+
|
1848
|
+
# Конфигурация окружения учитывает ROSPATENT_ENV
|
1849
|
+
config.environment = ENV.fetch("ROSPATENT_ENV", Rails.env)
|
1850
|
+
|
1851
|
+
# КРИТИЧНО: Переменные окружения имеют приоритет над настройками Rails
|
1852
|
+
# Это предотвращает появление DEBUG логов в продакшне при ROSPATENT_LOG_LEVEL=debug
|
1853
|
+
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
|
1854
|
+
ENV["ROSPATENT_LOG_LEVEL"].to_sym
|
1855
|
+
else
|
1856
|
+
Rails.env.production? ? :warn : :debug
|
1857
|
+
end
|
1858
|
+
|
1859
|
+
config.cache_enabled = Rails.env.production?
|
1062
1860
|
end
|
1063
1861
|
```
|
1064
1862
|
|
1065
|
-
|
1863
|
+
### Использование с логгером Rails
|
1066
1864
|
|
1067
1865
|
```ruby
|
1068
|
-
# config/initializers/rospatent.rb
|
1866
|
+
# В config/initializers/rospatent.rb
|
1069
1867
|
Rospatent.configure do |config|
|
1070
1868
|
config.token = Rails.application.credentials.rospatent_token
|
1071
|
-
config.environment = Rails.env
|
1072
|
-
config.cache_enabled = Rails.env.production?
|
1073
|
-
config.log_level = Rails.env.production? ? :warn : :debug
|
1074
1869
|
end
|
1075
1870
|
|
1076
|
-
#
|
1871
|
+
# Создание клиента с логгером Rails
|
1872
|
+
logger = Rospatent::Logger.new(
|
1873
|
+
output: Rails.logger,
|
1874
|
+
level: Rails.env.production? ? :warn : :debug,
|
1875
|
+
formatter: :text
|
1876
|
+
)
|
1877
|
+
|
1878
|
+
# Использование в контроллерах/сервисах
|
1077
1879
|
class PatentService
|
1078
1880
|
def initialize
|
1079
|
-
@client = Rospatent.client
|
1881
|
+
@client = Rospatent.client(logger: logger)
|
1882
|
+
end
|
1883
|
+
|
1884
|
+
def search_patents(query)
|
1885
|
+
@client.search(q: query, limit: 20)
|
1886
|
+
rescue Rospatent::Errors::ApiError => e
|
1887
|
+
Rails.logger.error "Поиск патентов не удался: #{e.message}"
|
1888
|
+
raise
|
1889
|
+
end
|
1890
|
+
end
|
1891
|
+
```
|
1892
|
+
|
1893
|
+
## Тестирование
|
1894
|
+
|
1895
|
+
### Запуск тестов
|
1896
|
+
|
1897
|
+
```bash
|
1898
|
+
# Запуск всех тестов
|
1899
|
+
$ bundle exec rake test
|
1900
|
+
|
1901
|
+
# Запуск конкретного тестового файла
|
1902
|
+
$ bundle exec ruby -Itest test/unit/client_test.rb
|
1903
|
+
|
1904
|
+
# Запуск интеграционных тестов (требуется API токен)
|
1905
|
+
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=ваш_токен bundle exec rake test_integration
|
1906
|
+
|
1907
|
+
# Запуск с покрытием
|
1908
|
+
$ bundle exec rake coverage
|
1909
|
+
```
|
1910
|
+
|
1911
|
+
### Настройка тестов
|
1912
|
+
|
1913
|
+
Для тестирования сбрасывайте и настраивайте в методе setup каждого теста:
|
1914
|
+
|
1915
|
+
```ruby
|
1916
|
+
# test/test_helper.rb - Базовая настройка для модульных тестов
|
1917
|
+
module Minitest
|
1918
|
+
class Test
|
1919
|
+
def setup
|
1920
|
+
Rospatent.reset # Чистое состояние между тестами
|
1921
|
+
Rospatent.configure do |config|
|
1922
|
+
config.token = ENV.fetch("ROSPATENT_TEST_TOKEN", "test_token")
|
1923
|
+
config.environment = "development"
|
1924
|
+
config.cache_enabled = false # Отключить кеш для предсказуемых тестов
|
1925
|
+
config.log_level = :error # Уменьшить шум тестов
|
1926
|
+
end
|
1927
|
+
end
|
1080
1928
|
end
|
1929
|
+
end
|
1930
|
+
|
1931
|
+
# Для интеграционных тестов - стабильная конфигурация, сброс не нужен
|
1932
|
+
class IntegrationTest < Minitest::Test
|
1933
|
+
def setup
|
1934
|
+
skip unless ENV["ROSPATENT_INTEGRATION_TESTS"]
|
1081
1935
|
|
1082
|
-
|
1083
|
-
|
1936
|
+
@token = ENV.fetch("ROSPATENT_TEST_TOKEN", nil)
|
1937
|
+
skip "ROSPATENT_TEST_TOKEN not set" unless @token
|
1938
|
+
|
1939
|
+
# Сброс не нужен - интеграционные тесты используют согласованную конфигурацию
|
1940
|
+
Rospatent.configure do |config|
|
1941
|
+
config.token = @token
|
1942
|
+
config.environment = "development"
|
1943
|
+
config.cache_enabled = true
|
1944
|
+
config.log_level = :debug
|
1945
|
+
end
|
1084
1946
|
end
|
1085
1947
|
end
|
1086
1948
|
```
|
1087
1949
|
|
1950
|
+
### Пользовательские проверки (Minitest)
|
1951
|
+
|
1952
|
+
```ruby
|
1953
|
+
# test/test_helper.rb
|
1954
|
+
module Minitest
|
1955
|
+
class Test
|
1956
|
+
def assert_valid_patent_id(patent_id, message = nil)
|
1957
|
+
message ||= "Ожидается #{patent_id} как действительный ID патента (формат: XX12345Y1_YYYYMMDD)"
|
1958
|
+
assert patent_id.match?(/^[A-Z]{2}[A-Z0-9]+[A-Z]\d*_\d{8}$/), message
|
1959
|
+
end
|
1960
|
+
end
|
1961
|
+
end
|
1962
|
+
|
1963
|
+
# Использование в тестах
|
1964
|
+
def test_patent_id_validation
|
1965
|
+
assert_valid_patent_id("RU134694U1_20131120")
|
1966
|
+
assert_valid_patent_id("RU134694A_20131120")
|
1967
|
+
end
|
1968
|
+
```
|
1969
|
+
|
1088
1970
|
## Известные ограничения API
|
1089
1971
|
|
1090
1972
|
Библиотека использует **Faraday** в качестве HTTP-клиента с поддержкой редиректов для всех endpoints:
|
@@ -1094,8 +1976,10 @@ end
|
|
1094
1976
|
|
1095
1977
|
⚠️ **Незначительные серверные ограничения**:
|
1096
1978
|
- **Поиск похожих патентов по тексту**: Иногда возвращает `503 Service Unavailable` (проблема сервера, не клиентской реализации)
|
1979
|
+
|
1097
1980
|
⚠️ **Неточности документации**:
|
1098
1981
|
- **Поиск похожих патентов**: Массив совпадений в документации назван `hits`, фактическая реализация использует `data`
|
1982
|
+
- **Перечень датасетов**: Ключ `name` в фактической реализации содержит признак локализации — `name_ru`, `name_en`
|
1099
1983
|
|
1100
1984
|
Вся основная функциональность реализована и готова для продакшена.
|
1101
1985
|
|
@@ -1141,41 +2025,29 @@ Rospatent::Errors::TimeoutError
|
|
1141
2025
|
Rospatent::Errors::ServiceUnavailableError
|
1142
2026
|
```
|
1143
2027
|
|
1144
|
-
##
|
2028
|
+
## Rake задачи
|
1145
2029
|
|
1146
|
-
|
2030
|
+
Полезные задачи для разработки и обслуживания:
|
1147
2031
|
|
1148
2032
|
```bash
|
1149
|
-
#
|
1150
|
-
$ bundle exec rake
|
2033
|
+
# Валидация конфигурации
|
2034
|
+
$ bundle exec rake validate
|
1151
2035
|
|
1152
|
-
#
|
1153
|
-
$ bundle exec
|
2036
|
+
# Управление кешем
|
2037
|
+
$ bundle exec rake cache:stats
|
2038
|
+
$ bundle exec rake cache:clear
|
1154
2039
|
|
1155
|
-
#
|
1156
|
-
$
|
2040
|
+
# Генерация документации
|
2041
|
+
$ bundle exec rake doc
|
1157
2042
|
|
1158
|
-
# Запуск
|
1159
|
-
$ bundle exec rake
|
1160
|
-
```
|
2043
|
+
# Запуск интеграционных тестов
|
2044
|
+
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN='<ваш_jwt_токен>' bundle exec rake test_integration
|
1161
2045
|
|
1162
|
-
|
2046
|
+
# Настройка среды разработки
|
2047
|
+
$ bundle exec rake setup
|
1163
2048
|
|
1164
|
-
|
1165
|
-
|
1166
|
-
module Minitest
|
1167
|
-
class Test
|
1168
|
-
def setup
|
1169
|
-
Rospatent.reset
|
1170
|
-
Rospatent.configure do |config|
|
1171
|
-
config.token = ENV.fetch("ROSPATENT_TEST_TOKEN", "test_token")
|
1172
|
-
config.environment = "development"
|
1173
|
-
config.cache_enabled = false
|
1174
|
-
config.log_level = :error
|
1175
|
-
end
|
1176
|
-
end
|
1177
|
-
end
|
1178
|
-
end
|
2049
|
+
# Проверки перед релизом
|
2050
|
+
$ bundle exec rake release_check
|
1179
2051
|
```
|
1180
2052
|
|
1181
2053
|
## Советы по производительности
|
@@ -1238,6 +2110,62 @@ Rospatent.configure do |config|
|
|
1238
2110
|
end
|
1239
2111
|
```
|
1240
2112
|
|
2113
|
+
## Разработка
|
2114
|
+
|
2115
|
+
После клонирования репозитория выполните `bin/setup` для установки зависимостей. Затем запустите `rake test` для выполнения тестов.
|
2116
|
+
|
2117
|
+
### Настройка разработки
|
2118
|
+
|
2119
|
+
```bash
|
2120
|
+
$ git clone https://hub.mos.ru/ad/rospatent.git
|
2121
|
+
$ cd rospatent
|
2122
|
+
$ bundle install
|
2123
|
+
$ bundle exec rake setup
|
2124
|
+
```
|
2125
|
+
|
2126
|
+
### Запуск тестов
|
2127
|
+
|
2128
|
+
```bash
|
2129
|
+
# Модульные тесты
|
2130
|
+
$ bundle exec rake test
|
2131
|
+
|
2132
|
+
# Интеграционные тесты (требуется API токен)
|
2133
|
+
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=ваш_токен bundle exec rake test_integration
|
2134
|
+
|
2135
|
+
# Стиль кода
|
2136
|
+
$ bundle exec rubocop
|
2137
|
+
|
2138
|
+
# Все проверки
|
2139
|
+
$ bundle exec rake ci
|
2140
|
+
```
|
2141
|
+
|
2142
|
+
### Интерактивная консоль
|
2143
|
+
|
2144
|
+
```bash
|
2145
|
+
$ bin/console
|
2146
|
+
```
|
2147
|
+
|
2148
|
+
## Содействие
|
2149
|
+
|
2150
|
+
Отчеты об ошибках и pull request приветствуются на MosHub по адресу https://hub.mos.ru/ad/rospatent.
|
2151
|
+
|
2152
|
+
### Руководство по разработке
|
2153
|
+
|
2154
|
+
1. **Пишите тесты**: Убедитесь, что все новые функции имеют соответствующие тесты
|
2155
|
+
2. **Следуйте стилю**: Выполните `rubocop` и исправьте любые проблемы стиля
|
2156
|
+
3. **Документируйте изменения**: Обновите README и CHANGELOG
|
2157
|
+
4. **Валидируйте конфигурацию**: Запустите `rake validate` перед отправкой
|
2158
|
+
|
2159
|
+
### Процесс релиза
|
2160
|
+
|
2161
|
+
```bash
|
2162
|
+
# Проверки перед релизом
|
2163
|
+
$ bundle exec rake release_check
|
2164
|
+
|
2165
|
+
# Обновление версии и релиз
|
2166
|
+
$ bundle exec rake release
|
2167
|
+
```
|
2168
|
+
|
1241
2169
|
---
|
1242
2170
|
|
1243
2171
|
## Changelog
|