rospatent 1.2.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 +40 -0
- data/README.md +1150 -229
- data/lib/generators/rospatent/install/templates/initializer.rb +39 -15
- data/lib/rospatent/client.rb +12 -8
- 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 +8 -8
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)
|
@@ -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,14 +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.each do |category|
|
283
|
-
puts "Category: #{category['name_en']}"
|
284
|
-
category.children.each do |dataset|
|
285
|
-
puts " #{dataset['id']}: #{dataset['name_en']}"
|
286
|
-
end
|
287
|
-
end
|
288
603
|
```
|
289
604
|
|
290
605
|
## Advanced Features
|
@@ -363,7 +678,7 @@ shared_logger = Rospatent.shared_logger(level: :debug)
|
|
363
678
|
|
364
679
|
### Error Handling
|
365
680
|
|
366
|
-
Comprehensive error handling with specific error types:
|
681
|
+
Comprehensive error handling with specific error types and improved error message extraction:
|
367
682
|
|
368
683
|
```ruby
|
369
684
|
begin
|
@@ -385,6 +700,13 @@ rescue Rospatent::Errors::ConnectionError => e
|
|
385
700
|
puts "Connection error: #{e.message}"
|
386
701
|
puts "Original error: #{e.original_error}"
|
387
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)
|
388
710
|
```
|
389
711
|
|
390
712
|
### Input Validation
|
@@ -424,64 +746,6 @@ puts "Cache enabled: #{global_stats[:configuration][:cache_enabled]}"
|
|
424
746
|
puts "API URL: #{global_stats[:configuration][:api_url]}"
|
425
747
|
```
|
426
748
|
|
427
|
-
## Environment Configuration
|
428
|
-
|
429
|
-
### Development Environment
|
430
|
-
|
431
|
-
```ruby
|
432
|
-
# Optimized for development
|
433
|
-
Rospatent.configure do |config|
|
434
|
-
config.environment = "development"
|
435
|
-
config.token = ENV['ROSPATENT_DEV_TOKEN']
|
436
|
-
config.log_level = :debug
|
437
|
-
config.log_requests = true
|
438
|
-
config.log_responses = true
|
439
|
-
config.cache_ttl = 60 # Short cache for development
|
440
|
-
config.timeout = 10 # Fast timeouts for quick feedback
|
441
|
-
end
|
442
|
-
```
|
443
|
-
|
444
|
-
### Staging Environment
|
445
|
-
|
446
|
-
```ruby
|
447
|
-
# Optimized for staging
|
448
|
-
Rospatent.configure do |config|
|
449
|
-
config.environment = "staging"
|
450
|
-
config.token = ENV['ROSPATENT_TOKEN']
|
451
|
-
config.log_level = :info
|
452
|
-
config.cache_ttl = 300 # Longer cache for performance
|
453
|
-
config.timeout = 45 # Longer timeouts for reliability
|
454
|
-
config.retry_count = 3 # More retries for resilience
|
455
|
-
end
|
456
|
-
```
|
457
|
-
|
458
|
-
### Production Environment
|
459
|
-
|
460
|
-
```ruby
|
461
|
-
# Optimized for production
|
462
|
-
Rospatent.configure do |config|
|
463
|
-
config.environment = "production"
|
464
|
-
config.token = ENV['ROSPATENT_TOKEN']
|
465
|
-
config.log_level = :warn
|
466
|
-
config.cache_ttl = 600 # Longer cache for performance
|
467
|
-
config.timeout = 60 # Longer timeouts for reliability
|
468
|
-
config.retry_count = 5 # More retries for resilience
|
469
|
-
end
|
470
|
-
```
|
471
|
-
|
472
|
-
### Configuration Validation
|
473
|
-
|
474
|
-
```ruby
|
475
|
-
# Validate current configuration
|
476
|
-
errors = Rospatent.validate_configuration
|
477
|
-
if errors.any?
|
478
|
-
puts "Configuration errors:"
|
479
|
-
errors.each { |error| puts " - #{error}" }
|
480
|
-
else
|
481
|
-
puts "Configuration is valid ✓"
|
482
|
-
end
|
483
|
-
```
|
484
|
-
|
485
749
|
## Rails Integration
|
486
750
|
|
487
751
|
### Generator
|
@@ -494,10 +758,23 @@ This creates `config/initializers/rospatent.rb`:
|
|
494
758
|
|
495
759
|
```ruby
|
496
760
|
Rospatent.configure do |config|
|
497
|
-
|
498
|
-
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
|
+
|
499
777
|
config.cache_enabled = Rails.env.production?
|
500
|
-
config.log_level = Rails.env.production? ? :warn : :debug
|
501
778
|
end
|
502
779
|
```
|
503
780
|
|
@@ -617,6 +894,7 @@ The library uses **Faraday** as the HTTP client with redirect support for all en
|
|
617
894
|
|
618
895
|
⚠️ **Minor server-side limitations**:
|
619
896
|
- **Similar Patents by Text**: Occasionally returns `503 Service Unavailable` (a server-side issue, not a client implementation issue)
|
897
|
+
|
620
898
|
⚠️ **Documentation inconsistencies**:
|
621
899
|
- **Similar Patents**: According to the documentation, the array of hits is named `hits`, but the real implementation uses the name `data`
|
622
900
|
- **Available Datasets**: The `name` key in the real implementation has the localization suffix — `name_ru`, `name_en`
|
@@ -681,10 +959,7 @@ $ bundle exec rake cache:clear
|
|
681
959
|
$ bundle exec rake doc
|
682
960
|
|
683
961
|
# Run integration tests
|
684
|
-
$ bundle exec rake test_integration
|
685
|
-
|
686
|
-
# Performance benchmarks
|
687
|
-
$ bundle exec rake benchmark
|
962
|
+
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN='<your_jwt_token>' bundle exec rake test_integration
|
688
963
|
|
689
964
|
# Setup development environment
|
690
965
|
$ bundle exec rake setup
|
@@ -826,12 +1101,10 @@ $ bundle exec rake release
|
|
826
1101
|
- 📊 **Структурированное логирование** - JSON/текстовое логирование с отслеживанием запросов/ответов
|
827
1102
|
- 🚀 **Пакетные операции** - параллельная обработка множества патентов
|
828
1103
|
- ⚙️ **Адаптивные окружения** - различные конфигурации для development/staging/production
|
829
|
-
- 🧪 **Комплексное тестирование** -
|
1104
|
+
- 🧪 **Комплексное тестирование** - 219 тестов с 465 проверками, комплексное интеграционное тестирование
|
830
1105
|
- 📚 **Отличная документация** - подробные примеры и документация API
|
831
1106
|
|
832
|
-
##
|
833
|
-
|
834
|
-
### Установка
|
1107
|
+
## Установка
|
835
1108
|
|
836
1109
|
Добавьте в ваш Gemfile:
|
837
1110
|
|
@@ -839,70 +1112,497 @@ $ bundle exec rake release
|
|
839
1112
|
gem 'rospatent'
|
840
1113
|
```
|
841
1114
|
|
1115
|
+
Затем выполните:
|
1116
|
+
|
1117
|
+
```bash
|
1118
|
+
$ bundle install
|
1119
|
+
```
|
842
1120
|
Или установите напрямую:
|
843
1121
|
|
844
1122
|
```bash
|
845
1123
|
$ gem install rospatent
|
846
1124
|
```
|
847
1125
|
|
848
|
-
|
1126
|
+
## Быстрый старт
|
849
1127
|
|
850
1128
|
```ruby
|
851
|
-
|
852
|
-
|
853
|
-
# Настройка клиента
|
1129
|
+
# Минимальная конфигурация
|
854
1130
|
Rospatent.configure do |config|
|
855
1131
|
config.token = "ваш_jwt_токен"
|
856
1132
|
end
|
857
1133
|
|
858
|
-
# Создание клиента
|
1134
|
+
# Создание клиента и поиск
|
859
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
|
860
1142
|
```
|
861
1143
|
|
862
|
-
##
|
1144
|
+
## Конфигурация
|
863
1145
|
|
864
|
-
###
|
1146
|
+
### Базовая настройка
|
865
1147
|
|
866
1148
|
```ruby
|
867
|
-
|
868
|
-
|
1149
|
+
Rospatent.configure do |config|
|
1150
|
+
# Обязательно
|
1151
|
+
config.token = "ваш_jwt_токен"
|
869
1152
|
|
870
|
-
#
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
offset: 100,
|
875
|
-
datasets: ["ru_since_1994"],
|
876
|
-
sort: "pub_date:desc",
|
877
|
-
highlight: true
|
878
|
-
)
|
1153
|
+
# Настройки API
|
1154
|
+
config.api_url = "https://searchplatform.rospatent.gov.ru/patsearch/v0.2"
|
1155
|
+
config.timeout = 30
|
1156
|
+
config.retry_count = 3
|
879
1157
|
|
880
|
-
#
|
881
|
-
|
882
|
-
results.hits.each do |patent|
|
883
|
-
puts "#{patent['id']}: #{patent['title']}"
|
1158
|
+
# Окружение (development, staging, production)
|
1159
|
+
config.environment = "production"
|
884
1160
|
end
|
885
1161
|
```
|
886
1162
|
|
887
|
-
###
|
1163
|
+
### Продвинутая настройка
|
888
1164
|
|
889
1165
|
```ruby
|
890
|
-
|
891
|
-
|
1166
|
+
Rospatent.configure do |config|
|
1167
|
+
config.token = "ваш_jwt_токен"
|
892
1168
|
|
893
|
-
#
|
894
|
-
|
895
|
-
|
1169
|
+
# Кеширование (включено по умолчанию)
|
1170
|
+
config.cache_enabled = true
|
1171
|
+
config.cache_ttl = 300 # 5 минут
|
1172
|
+
config.cache_max_size = 1000 # Максимум элементов кеша
|
896
1173
|
|
897
|
-
|
898
|
-
|
899
|
-
|
1174
|
+
# Логирование
|
1175
|
+
config.log_level = :info # :debug, :info, :warn, :error
|
1176
|
+
config.log_requests = true # Логировать API запросы
|
1177
|
+
config.log_responses = true # Логировать API ответы
|
900
1178
|
|
901
|
-
|
1179
|
+
# Настройки соединения
|
1180
|
+
config.connection_pool_size = 5
|
1181
|
+
config.connection_keep_alive = true
|
902
1182
|
|
903
|
-
|
904
|
-
|
905
|
-
|
1183
|
+
# Управление токенами
|
1184
|
+
config.token_expires_at = Time.now + 3600
|
1185
|
+
config.token_refresh_callback = -> { refresh_token! }
|
1186
|
+
end
|
1187
|
+
```
|
1188
|
+
|
1189
|
+
### Конфигурация для конкретных окружений
|
1190
|
+
|
1191
|
+
Gem автоматически настраивается под окружение с разумными значениями по умолчанию:
|
1192
|
+
|
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)
|
906
1606
|
|
907
1607
|
# Поиск похожих патентов по описанию текста
|
908
1608
|
similar = client.similar_patents_by_text(
|
@@ -950,12 +1650,24 @@ cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
|
|
950
1650
|
- `"ru"` - Русский
|
951
1651
|
- `"en"` - Английский
|
952
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
|
+
|
953
1665
|
### Медиафайлы и документы
|
954
1666
|
|
955
1667
|
```ruby
|
956
1668
|
# Скачивание PDF патента
|
957
1669
|
pdf_data = client.patent_media(
|
958
|
-
"National",
|
1670
|
+
"National", # collection_id
|
959
1671
|
"RU", # country_code
|
960
1672
|
"U1", # doc_type
|
961
1673
|
"2013/11/20", # pub_date
|
@@ -970,127 +1682,291 @@ pdf_data = client.patent_media_by_id(
|
|
970
1682
|
"National",
|
971
1683
|
"document.pdf"
|
972
1684
|
)
|
973
|
-
|
974
|
-
# Получение доступных датасетов
|
975
|
-
datasets = client.datasets_tree
|
976
|
-
datasets.each do |category|
|
977
|
-
puts "Категория: #{category['name_ru']}"
|
978
|
-
category.children.each do |dataset|
|
979
|
-
puts " #{dataset['id']}: #{dataset['name_ru']}"
|
980
|
-
end
|
981
|
-
end
|
982
1685
|
```
|
983
1686
|
|
984
1687
|
## Расширенные возможности
|
985
1688
|
|
986
1689
|
### Пакетные операции
|
987
1690
|
|
1691
|
+
Эффективная обработка множества патентов с параллельными запросами:
|
1692
|
+
|
988
1693
|
```ruby
|
989
|
-
|
1694
|
+
document_ids = ["RU134694U1_20131120", "RU2358138C1_20090610", "RU2756123C1_20210927"]
|
990
1695
|
|
991
|
-
|
992
|
-
|
993
|
-
|
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
|
994
1704
|
end
|
1705
|
+
|
1706
|
+
# Или сбор всех результатов
|
1707
|
+
patents = []
|
1708
|
+
client.batch_patents(document_ids) { |doc| patents << doc }
|
995
1709
|
```
|
996
1710
|
|
997
1711
|
### Кеширование
|
998
1712
|
|
1713
|
+
Автоматическое интеллектуальное кеширование улучшает производительность:
|
1714
|
+
|
999
1715
|
```ruby
|
1000
|
-
#
|
1001
|
-
|
1002
|
-
|
1003
|
-
config.cache_ttl = 600 # 10 минут
|
1004
|
-
config.cache_max_size = 1000 # Максимум элементов
|
1005
|
-
end
|
1716
|
+
# Кеширование автоматическое и прозрачное
|
1717
|
+
patent1 = client.patent("RU134694U1_20131120") # API вызов
|
1718
|
+
patent2 = client.patent("RU134694U1_20131120") # Кешированный результат
|
1006
1719
|
|
1007
|
-
#
|
1720
|
+
# Проверка статистики кеша
|
1008
1721
|
stats = client.statistics
|
1009
|
-
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)
|
1010
1759
|
```
|
1011
1760
|
|
1012
1761
|
### Обработка ошибок
|
1013
1762
|
|
1763
|
+
Комплексная обработка ошибок с конкретными типами ошибок и улучшенным извлечением сообщений об ошибках:
|
1764
|
+
|
1014
1765
|
```ruby
|
1015
1766
|
begin
|
1016
|
-
|
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} секунд"
|
1017
1775
|
rescue Rospatent::Errors::AuthenticationError => e
|
1018
1776
|
puts "Ошибка аутентификации: #{e.message}"
|
1019
|
-
rescue Rospatent::Errors::RateLimitError => e
|
1020
|
-
puts "Превышен лимит запросов: #{e.message}"
|
1021
1777
|
rescue Rospatent::Errors::ApiError => e
|
1022
|
-
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}"
|
1023
1784
|
end
|
1785
|
+
|
1786
|
+
# Улучшенное извлечение сообщений об ошибках
|
1787
|
+
# Клиент автоматически извлекает сообщения об ошибках из различных форматов ответов API:
|
1788
|
+
# - {"result": "Сообщение об ошибке"} (формат API Роспатента)
|
1789
|
+
# - {"error": "Сообщение об ошибке"} (стандартный формат)
|
1790
|
+
# - {"message": "Сообщение об ошибке"} (альтернативный формат)
|
1791
|
+
# - {"details": "Детали валидации"} (ошибки валидации)
|
1024
1792
|
```
|
1025
1793
|
|
1026
|
-
|
1794
|
+
### Валидация входных данных
|
1027
1795
|
|
1028
|
-
|
1796
|
+
Все входные данные автоматически валидируются с полезными сообщениями об ошибках:
|
1029
1797
|
|
1030
1798
|
```ruby
|
1031
|
-
#
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
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
|
+
# - Валидация обязательных полей
|
1041
1810
|
```
|
1042
1811
|
|
1043
|
-
###
|
1812
|
+
### Мониторинг производительности
|
1813
|
+
|
1814
|
+
Отслеживание производительности и статистики использования:
|
1044
1815
|
|
1045
1816
|
```ruby
|
1046
|
-
#
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
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
|
1055
1837
|
```
|
1056
1838
|
|
1057
|
-
|
1839
|
+
Это создает `config/initializers/rospatent.rb`:
|
1058
1840
|
|
1059
1841
|
```ruby
|
1060
|
-
# Оптимизировано для продакшна
|
1061
1842
|
Rospatent.configure do |config|
|
1062
|
-
|
1063
|
-
config.token =
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
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?
|
1068
1860
|
end
|
1069
1861
|
```
|
1070
1862
|
|
1071
|
-
|
1863
|
+
### Использование с логгером Rails
|
1072
1864
|
|
1073
1865
|
```ruby
|
1074
|
-
# config/initializers/rospatent.rb
|
1866
|
+
# В config/initializers/rospatent.rb
|
1075
1867
|
Rospatent.configure do |config|
|
1076
1868
|
config.token = Rails.application.credentials.rospatent_token
|
1077
|
-
config.environment = Rails.env
|
1078
|
-
config.cache_enabled = Rails.env.production?
|
1079
|
-
config.log_level = Rails.env.production? ? :warn : :debug
|
1080
1869
|
end
|
1081
1870
|
|
1082
|
-
#
|
1871
|
+
# Создание клиента с логгером Rails
|
1872
|
+
logger = Rospatent::Logger.new(
|
1873
|
+
output: Rails.logger,
|
1874
|
+
level: Rails.env.production? ? :warn : :debug,
|
1875
|
+
formatter: :text
|
1876
|
+
)
|
1877
|
+
|
1878
|
+
# Использование в контроллерах/сервисах
|
1083
1879
|
class PatentService
|
1084
1880
|
def initialize
|
1085
|
-
@client = Rospatent.client
|
1881
|
+
@client = Rospatent.client(logger: logger)
|
1086
1882
|
end
|
1087
1883
|
|
1088
|
-
def search_patents(query
|
1089
|
-
@client.search(q: query,
|
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
|
1090
1889
|
end
|
1091
1890
|
end
|
1092
1891
|
```
|
1093
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
|
1928
|
+
end
|
1929
|
+
end
|
1930
|
+
|
1931
|
+
# Для интеграционных тестов - стабильная конфигурация, сброс не нужен
|
1932
|
+
class IntegrationTest < Minitest::Test
|
1933
|
+
def setup
|
1934
|
+
skip unless ENV["ROSPATENT_INTEGRATION_TESTS"]
|
1935
|
+
|
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
|
1946
|
+
end
|
1947
|
+
end
|
1948
|
+
```
|
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
|
+
|
1094
1970
|
## Известные ограничения API
|
1095
1971
|
|
1096
1972
|
Библиотека использует **Faraday** в качестве HTTP-клиента с поддержкой редиректов для всех endpoints:
|
@@ -1100,6 +1976,7 @@ end
|
|
1100
1976
|
|
1101
1977
|
⚠️ **Незначительные серверные ограничения**:
|
1102
1978
|
- **Поиск похожих патентов по тексту**: Иногда возвращает `503 Service Unavailable` (проблема сервера, не клиентской реализации)
|
1979
|
+
|
1103
1980
|
⚠️ **Неточности документации**:
|
1104
1981
|
- **Поиск похожих патентов**: Массив совпадений в документации назван `hits`, фактическая реализация использует `data`
|
1105
1982
|
- **Перечень датасетов**: Ключ `name` в фактической реализации содержит признак локализации — `name_ru`, `name_en`
|
@@ -1148,41 +2025,29 @@ Rospatent::Errors::TimeoutError
|
|
1148
2025
|
Rospatent::Errors::ServiceUnavailableError
|
1149
2026
|
```
|
1150
2027
|
|
1151
|
-
##
|
2028
|
+
## Rake задачи
|
1152
2029
|
|
1153
|
-
|
2030
|
+
Полезные задачи для разработки и обслуживания:
|
1154
2031
|
|
1155
2032
|
```bash
|
1156
|
-
#
|
1157
|
-
$ bundle exec rake
|
2033
|
+
# Валидация конфигурации
|
2034
|
+
$ bundle exec rake validate
|
1158
2035
|
|
1159
|
-
#
|
1160
|
-
$ bundle exec
|
2036
|
+
# Управление кешем
|
2037
|
+
$ bundle exec rake cache:stats
|
2038
|
+
$ bundle exec rake cache:clear
|
1161
2039
|
|
1162
|
-
#
|
1163
|
-
$
|
2040
|
+
# Генерация документации
|
2041
|
+
$ bundle exec rake doc
|
1164
2042
|
|
1165
|
-
# Запуск
|
1166
|
-
$ bundle exec rake
|
1167
|
-
```
|
2043
|
+
# Запуск интеграционных тестов
|
2044
|
+
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN='<ваш_jwt_токен>' bundle exec rake test_integration
|
1168
2045
|
|
1169
|
-
|
2046
|
+
# Настройка среды разработки
|
2047
|
+
$ bundle exec rake setup
|
1170
2048
|
|
1171
|
-
|
1172
|
-
|
1173
|
-
module Minitest
|
1174
|
-
class Test
|
1175
|
-
def setup
|
1176
|
-
Rospatent.reset
|
1177
|
-
Rospatent.configure do |config|
|
1178
|
-
config.token = ENV.fetch("ROSPATENT_TEST_TOKEN", "test_token")
|
1179
|
-
config.environment = "development"
|
1180
|
-
config.cache_enabled = false
|
1181
|
-
config.log_level = :error
|
1182
|
-
end
|
1183
|
-
end
|
1184
|
-
end
|
1185
|
-
end
|
2049
|
+
# Проверки перед релизом
|
2050
|
+
$ bundle exec rake release_check
|
1186
2051
|
```
|
1187
2052
|
|
1188
2053
|
## Советы по производительности
|
@@ -1245,6 +2110,62 @@ Rospatent.configure do |config|
|
|
1245
2110
|
end
|
1246
2111
|
```
|
1247
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
|
+
|
1248
2169
|
---
|
1249
2170
|
|
1250
2171
|
## Changelog
|