rospatent 1.0.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.
data/README.md ADDED
@@ -0,0 +1,1247 @@
1
+ # Rospatent
2
+
3
+ A comprehensive Ruby client for the Rospatent patent search API with advanced features including intelligent caching, input validation, structured logging, and robust error handling.
4
+
5
+ > πŸ‡·πŸ‡Ί **[ДокумСнтация Π½Π° русском языкС](#-докумСнтация-Π½Π°-русском-языкС)** доступна Π½ΠΈΠΆΠ΅
6
+
7
+ ## ✨ Key Features
8
+
9
+ - πŸ” **Complete API Coverage** - Search, retrieve patents, media files, and datasets
10
+ - πŸ›‘οΈ **Robust Error Handling** - Comprehensive error types with detailed context
11
+ - ⚑ **Intelligent Caching** - In-memory caching with TTL and LRU eviction
12
+ - βœ… **Input Validation** - Automatic parameter validation with helpful error messages
13
+ - πŸ“Š **Structured Logging** - JSON/text logging with request/response tracking
14
+ - πŸš€ **Batch Operations** - Process multiple patents concurrently
15
+ - βš™οΈ **Environment-Aware** - Different configurations for dev/staging/production
16
+ - πŸ§ͺ **Comprehensive Testing** - 96% test coverage with integration tests
17
+ - πŸ“š **Excellent Documentation** - Detailed examples and API documentation
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'rospatent'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ ```bash
30
+ $ bundle install
31
+ ```
32
+
33
+ Or install it yourself as:
34
+
35
+ ```bash
36
+ $ gem install rospatent
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```ruby
42
+ # Basic configuration
43
+ Rospatent.configure do |config|
44
+ config.token = "your_jwt_token"
45
+ end
46
+
47
+ # Create a client and search
48
+ client = Rospatent.client
49
+ results = client.search(q: "Ρ€Π°ΠΊΠ΅Ρ‚Π°", limit: 10)
50
+
51
+ puts "Found #{results.total} results"
52
+ results.hits.each do |hit|
53
+ puts "Patent: #{hit['id']} - #{hit.dig('biblio', 'ru', 'title')}"
54
+ end
55
+ ```
56
+
57
+ ## Configuration
58
+
59
+ ### Basic Configuration
60
+
61
+ ```ruby
62
+ Rospatent.configure do |config|
63
+ # Required
64
+ config.token = "your_jwt_token"
65
+
66
+ # API settings
67
+ config.api_url = "https://searchplatform.rospatent.gov.ru/patsearch/v0.2"
68
+ config.timeout = 30
69
+ config.retry_count = 3
70
+
71
+ # Environment (development, staging, production)
72
+ config.environment = "production"
73
+ end
74
+ ```
75
+
76
+ ### Advanced Configuration
77
+
78
+ ```ruby
79
+ Rospatent.configure do |config|
80
+ config.token = "your_jwt_token"
81
+
82
+ # Caching (enabled by default)
83
+ config.cache_enabled = true
84
+ config.cache_ttl = 300 # 5 minutes
85
+ config.cache_max_size = 1000 # Maximum cached items
86
+
87
+ # Logging
88
+ config.log_level = :info # :debug, :info, :warn, :error
89
+ config.log_requests = true # Log API requests
90
+ config.log_responses = true # Log API responses
91
+
92
+ # Connection settings
93
+ config.connection_pool_size = 5
94
+ config.connection_keep_alive = true
95
+
96
+ # Token management
97
+ config.token_expires_at = Time.now + 3600
98
+ config.token_refresh_callback = -> { refresh_token! }
99
+ end
100
+ ```
101
+
102
+ ### Environment Variables
103
+
104
+ Configure via environment variables:
105
+
106
+ ```bash
107
+ ROSPATENT_ENV=production
108
+ ROSPATENT_CACHE_ENABLED=true
109
+ ROSPATENT_CACHE_TTL=600
110
+ ROSPATENT_LOG_LEVEL=info
111
+ ROSPATENT_POOL_SIZE=10
112
+ ```
113
+
114
+ ### Environment-Specific Defaults
115
+
116
+ The gem automatically adjusts settings based on environment:
117
+
118
+ - **Development**: Fast timeouts, verbose logging, short cache TTL
119
+ - **Staging**: Moderate settings for testing
120
+ - **Production**: Longer timeouts, optimized for reliability
121
+
122
+ ## Basic Usage
123
+
124
+ ### Searching Patents
125
+
126
+ ```ruby
127
+ client = Rospatent.client
128
+
129
+ # Simple text search
130
+ results = client.search(q: "Ρ€Π°ΠΊΠ΅Ρ‚Π°")
131
+
132
+ # Natural language search
133
+ results = client.search(qn: "rocket engine design")
134
+
135
+ # Advanced search with all options
136
+ results = client.search(
137
+ q: "Ρ€Π°ΠΊΠ΅Ρ‚Π° AND Π΄Π²ΠΈΠ³Π°Ρ‚Π΅Π»ΡŒ",
138
+ limit: 20,
139
+ offset: 0,
140
+ filter: {
141
+ "classification.ipc_group": { "values": ["F02K9"] },
142
+ "biblio.application_date": { "from": "2020-01-01" }
143
+ },
144
+ sort: :pub_date,
145
+ group_by: :patent_family,
146
+ include_facets: true,
147
+ highlight: true,
148
+ pre_tag: "<mark>",
149
+ post_tag: "</mark>"
150
+ )
151
+
152
+ # Process results
153
+ puts "Found #{results.total} total results (#{results.available} available)"
154
+ puts "Showing #{results.count} results"
155
+
156
+ results.hits.each do |hit|
157
+ puts "ID: #{hit['id']}"
158
+ puts "Title: #{hit.dig('biblio', 'ru', 'title')}"
159
+ puts "Date: #{hit.dig('biblio', 'publication_date')}"
160
+ puts "IPC: #{hit.dig('classification', 'ipc')}"
161
+ puts "---"
162
+ end
163
+ ```
164
+
165
+ ### Retrieving Patent Documents
166
+
167
+ ```ruby
168
+ # Get patent by document ID
169
+ patent_doc = client.patent("RU134694U1_20131120")
170
+
171
+ # Get patent by components
172
+ patent_doc = client.patent_by_components(
173
+ "RU", # country_code
174
+ "134694", # number
175
+ "U1", # doc_type
176
+ Date.new(2013, 11, 20) # date (String or Date object)
177
+ )
178
+
179
+ # Access patent data
180
+ title = patent_doc.dig('biblio', 'ru', 'title')
181
+ abstract = patent_doc.dig('abstract', 'ru')
182
+ inventors = patent_doc.dig('biblio', 'ru', 'inventor')
183
+ ```
184
+
185
+ ### Parsing Patent Content
186
+
187
+ Extract clean text or structured content from patents:
188
+
189
+ ```ruby
190
+ # Parse abstract
191
+ abstract_text = client.parse_abstract(patent_doc)
192
+ abstract_html = client.parse_abstract(patent_doc, format: :html)
193
+ abstract_en = client.parse_abstract(patent_doc, language: "en")
194
+
195
+ # Parse description
196
+ description_text = client.parse_description(patent_doc)
197
+ description_html = client.parse_description(patent_doc, format: :html)
198
+
199
+ # Get structured sections
200
+ sections = client.parse_description(patent_doc, format: :sections)
201
+ sections.each do |section|
202
+ puts "Section #{section[:number]}: #{section[:content]}"
203
+ end
204
+ ```
205
+
206
+ ### Finding Similar Patents
207
+
208
+ ```ruby
209
+ # Find similar patents by ID
210
+ similar = client.similar_patents_by_id("RU134694U1_20131120", count: 50)
211
+
212
+ # Find similar patents by text description
213
+ similar = client.similar_patents_by_text(
214
+ "Π Π°ΠΊΠ΅Ρ‚Π½Ρ‹ΠΉ Π΄Π²ΠΈΠ³Π°Ρ‚Π΅Π»ΡŒ с ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½Π½ΠΎΠΉ тягой",
215
+ count: 25
216
+ )
217
+
218
+ # Process similar patents
219
+ similar["hits"]&.each do |patent|
220
+ puts "Similar: #{patent['id']} (score: #{patent['_score']})"
221
+ end
222
+ ```
223
+
224
+ ### Classification Search
225
+
226
+ Search within patent classification systems (IPC and CPC) and get detailed information about classification codes:
227
+
228
+ ```ruby
229
+ # Search for classification codes related to rockets in IPC
230
+ ipc_results = client.classification_search("ipc", query: "Ρ€Π°ΠΊΠ΅Ρ‚Π°", lang: "ru")
231
+ puts "Found #{ipc_results['total']} IPC codes"
232
+
233
+ ipc_results["hits"]&.each do |hit|
234
+ puts "#{hit['code']}: #{hit['description']}"
235
+ end
236
+
237
+ # Search for rocket-related codes in CPC using English
238
+ cpc_results = client.classification_search("cpc", query: "rocket", lang: "en")
239
+
240
+ # Get detailed information about a specific classification code
241
+ code_info = client.classification_code("ipc", code: "F02K9/00", lang: "ru")
242
+ puts "Code: #{code_info['code']}"
243
+ puts "Description: #{code_info['description']}"
244
+ puts "Hierarchy: #{code_info['hierarchy']&.join(' β†’ ')}"
245
+
246
+ # Get CPC code information in English
247
+ cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
248
+ ```
249
+
250
+ **Supported Classification Systems:**
251
+ - `"ipc"` - International Patent Classification (МПК)
252
+ - `"cpc"` - Cooperative Patent Classification (БПК)
253
+
254
+ **Supported Languages:**
255
+ - `"ru"` - Russian
256
+ - `"en"` - English
257
+
258
+ ### Media and Documents
259
+
260
+ ```ruby
261
+ # Download patent PDF
262
+ pdf_data = client.patent_media(
263
+ "National", # collection_id
264
+ "RU", # country_code
265
+ "U1", # doc_type
266
+ "2013/11/20", # pub_date
267
+ "134694", # pub_number
268
+ "document.pdf" # filename
269
+ )
270
+ File.write("patent.pdf", pdf_data)
271
+
272
+ # Simplified method using patent ID
273
+ pdf_data = client.patent_media_by_id(
274
+ "RU134694U1_20131120",
275
+ "National",
276
+ "document.pdf"
277
+ )
278
+
279
+ # Get available datasets
280
+ datasets = client.datasets_tree
281
+ datasets.each do |dataset|
282
+ puts "#{dataset['id']}: #{dataset['name']}"
283
+ end
284
+ ```
285
+
286
+ ## Advanced Features
287
+
288
+ ### Batch Operations
289
+
290
+ Process multiple patents efficiently with concurrent requests:
291
+
292
+ ```ruby
293
+ document_ids = ["RU134694U1_20131120", "RU2358138C1_20090610", "RU2756123C1_20210927"]
294
+
295
+ # Process patents in batches
296
+ client.batch_patents(document_ids, batch_size: 5) do |patent_doc|
297
+ if patent_doc[:error]
298
+ puts "Error for #{patent_doc[:document_id]}: #{patent_doc[:error]}"
299
+ else
300
+ puts "Retrieved patent: #{patent_doc['id']}"
301
+ # Process patent document
302
+ end
303
+ end
304
+
305
+ # Or collect all results
306
+ patents = []
307
+ client.batch_patents(document_ids) { |doc| patents << doc }
308
+ ```
309
+
310
+ ### Caching
311
+
312
+ Automatic intelligent caching improves performance:
313
+
314
+ ```ruby
315
+ # Caching is automatic and transparent
316
+ patent1 = client.patent("RU134694U1_20131120") # API call
317
+ patent2 = client.patent("RU134694U1_20131120") # Cached result
318
+
319
+ # Check cache statistics
320
+ stats = client.statistics
321
+ puts "Cache hit rate: #{stats[:cache_stats][:hit_rate_percent]}%"
322
+ puts "Total requests: #{stats[:requests_made]}"
323
+ puts "Average response time: #{stats[:average_request_time]}s"
324
+
325
+ # Use shared cache across clients
326
+ shared_cache = Rospatent.shared_cache
327
+ client1 = Rospatent.client(cache: shared_cache)
328
+ client2 = Rospatent.client(cache: shared_cache)
329
+
330
+ # Manual cache management
331
+ shared_cache.clear # Clear all cached data
332
+ expired_count = shared_cache.cleanup_expired # Remove expired entries
333
+ cache_stats = shared_cache.statistics # Get detailed cache statistics
334
+ ```
335
+
336
+ ### Custom Logging
337
+
338
+ Configure detailed logging for monitoring and debugging:
339
+
340
+ ```ruby
341
+ # Create custom logger
342
+ logger = Rospatent::Logger.new(
343
+ output: Rails.logger, # Or any IO object
344
+ level: :info,
345
+ formatter: :json # :json or :text
346
+ )
347
+
348
+ client = Rospatent.client(logger: logger)
349
+
350
+ # Logs include:
351
+ # - API requests/responses with timing
352
+ # - Cache operations (hits/misses)
353
+ # - Error details with context
354
+ # - Performance metrics
355
+
356
+ # Access shared logger
357
+ shared_logger = Rospatent.shared_logger(level: :debug)
358
+ ```
359
+
360
+ ### Error Handling
361
+
362
+ Comprehensive error handling with specific error types:
363
+
364
+ ```ruby
365
+ begin
366
+ patent = client.patent("INVALID_ID")
367
+ rescue Rospatent::Errors::ValidationError => e
368
+ puts "Invalid input: #{e.message}"
369
+ puts "Field errors: #{e.errors}" if e.errors.any?
370
+ rescue Rospatent::Errors::NotFoundError => e
371
+ puts "Patent not found: #{e.message}"
372
+ rescue Rospatent::Errors::RateLimitError => e
373
+ puts "Rate limited. Retry after: #{e.retry_after} seconds"
374
+ rescue Rospatent::Errors::AuthenticationError => e
375
+ puts "Authentication failed: #{e.message}"
376
+ rescue Rospatent::Errors::ApiError => e
377
+ puts "API error (#{e.status_code}): #{e.message}"
378
+ puts "Request ID: #{e.request_id}" if e.request_id
379
+ retry if e.retryable?
380
+ rescue Rospatent::Errors::ConnectionError => e
381
+ puts "Connection error: #{e.message}"
382
+ puts "Original error: #{e.original_error}"
383
+ end
384
+ ```
385
+
386
+ ### Input Validation
387
+
388
+ All inputs are automatically validated with helpful error messages:
389
+
390
+ ```ruby
391
+ # These will raise ValidationError with specific messages:
392
+ client.search(limit: 0) # "Limit must be at least 1"
393
+ client.patent("") # "Document_id cannot be empty"
394
+ client.similar_patents_by_text("", count: -1) # Multiple validation errors
395
+
396
+ # Validation includes:
397
+ # - Parameter types and formats
398
+ # - Patent ID format validation
399
+ # - Date format validation
400
+ # - Enum value validation
401
+ # - Required field validation
402
+ ```
403
+
404
+ ### Performance Monitoring
405
+
406
+ Track performance and usage statistics:
407
+
408
+ ```ruby
409
+ # Client-specific statistics
410
+ stats = client.statistics
411
+ puts "Requests made: #{stats[:requests_made]}"
412
+ puts "Total duration: #{stats[:total_duration_seconds]}s"
413
+ puts "Average request time: #{stats[:average_request_time]}s"
414
+ puts "Cache hit rate: #{stats[:cache_stats][:hit_rate_percent]}%"
415
+
416
+ # Global statistics
417
+ global_stats = Rospatent.statistics
418
+ puts "Environment: #{global_stats[:configuration][:environment]}"
419
+ puts "Cache enabled: #{global_stats[:configuration][:cache_enabled]}"
420
+ puts "API URL: #{global_stats[:configuration][:api_url]}"
421
+ ```
422
+
423
+ ## Environment Configuration
424
+
425
+ ### Development Environment
426
+
427
+ ```ruby
428
+ # Optimized for development
429
+ Rospatent.configure do |config|
430
+ config.environment = "development"
431
+ config.token = ENV['ROSPATENT_DEV_TOKEN']
432
+ config.log_level = :debug
433
+ config.log_requests = true
434
+ config.log_responses = true
435
+ config.cache_ttl = 60 # Short cache for development
436
+ config.timeout = 10 # Fast timeouts for quick feedback
437
+ end
438
+ ```
439
+
440
+ ### Production Environment
441
+
442
+ ```ruby
443
+ # Optimized for production
444
+ Rospatent.configure do |config|
445
+ config.environment = "production"
446
+ config.token = ENV['ROSPATENT_TOKEN']
447
+ config.log_level = :warn
448
+ config.cache_ttl = 600 # Longer cache for performance
449
+ config.timeout = 60 # Longer timeouts for reliability
450
+ config.retry_count = 5 # More retries for resilience
451
+ end
452
+ ```
453
+
454
+ ### Configuration Validation
455
+
456
+ ```ruby
457
+ # Validate current configuration
458
+ errors = Rospatent.validate_configuration
459
+ if errors.any?
460
+ puts "Configuration errors:"
461
+ errors.each { |error| puts " - #{error}" }
462
+ else
463
+ puts "Configuration is valid βœ“"
464
+ end
465
+ ```
466
+
467
+ ## Rails Integration
468
+
469
+ ### Generator
470
+
471
+ ```bash
472
+ $ rails generate rospatent:install
473
+ ```
474
+
475
+ This creates `config/initializers/rospatent.rb`:
476
+
477
+ ```ruby
478
+ Rospatent.configure do |config|
479
+ config.token = Rails.application.credentials.rospatent_token
480
+ config.environment = Rails.env
481
+ config.cache_enabled = Rails.env.production?
482
+ config.log_level = Rails.env.production? ? :warn : :debug
483
+ end
484
+ ```
485
+
486
+ ### Using with Rails Logger
487
+
488
+ ```ruby
489
+ # In config/initializers/rospatent.rb
490
+ Rospatent.configure do |config|
491
+ config.token = Rails.application.credentials.rospatent_token
492
+ end
493
+
494
+ # Create client with Rails logger
495
+ logger = Rospatent::Logger.new(
496
+ output: Rails.logger,
497
+ level: Rails.env.production? ? :warn : :debug,
498
+ formatter: :text
499
+ )
500
+
501
+ # Use in controllers/services
502
+ class PatentService
503
+ def initialize
504
+ @client = Rospatent.client(logger: logger)
505
+ end
506
+
507
+ def search_patents(query)
508
+ @client.search(q: query, limit: 20)
509
+ rescue Rospatent::Errors::ApiError => e
510
+ Rails.logger.error "Patent search failed: #{e.message}"
511
+ raise
512
+ end
513
+ end
514
+ ```
515
+
516
+ ## Testing
517
+
518
+ ### Running Tests
519
+
520
+ ```bash
521
+ # Run all tests
522
+ $ bundle exec rake test
523
+
524
+ # Run specific test file
525
+ $ bundle exec ruby -Itest test/unit/client_test.rb
526
+
527
+ # Run integration tests (requires API token)
528
+ $ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=your_token bundle exec rake test_integration
529
+
530
+ # Run with coverage
531
+ $ bundle exec rake coverage
532
+ ```
533
+
534
+ ### Test Configuration
535
+
536
+ For testing, reset and configure in each test's setup method:
537
+
538
+ ```ruby
539
+ # test/test_helper.rb - Base setup for unit tests
540
+ module Minitest
541
+ class Test
542
+ def setup
543
+ Rospatent.reset # Clean state between tests
544
+ Rospatent.configure do |config|
545
+ config.token = ENV.fetch("ROSPATENT_TEST_TOKEN", "test_token")
546
+ config.environment = "development"
547
+ config.cache_enabled = false # Disable cache for predictable tests
548
+ config.log_level = :error # Reduce test noise
549
+ end
550
+ end
551
+ end
552
+ end
553
+
554
+ # For integration tests - stable config, no reset needed
555
+ class IntegrationTest < Minitest::Test
556
+ def setup
557
+ skip unless ENV["ROSPATENT_INTEGRATION_TESTS"]
558
+
559
+ @token = ENV.fetch("ROSPATENT_TEST_TOKEN", nil)
560
+ skip "ROSPATENT_TEST_TOKEN not set" unless @token
561
+
562
+ # No reset needed - integration tests use consistent configuration
563
+ Rospatent.configure do |config|
564
+ config.token = @token
565
+ config.environment = "development"
566
+ config.cache_enabled = true
567
+ config.log_level = :debug
568
+ end
569
+ end
570
+ end
571
+ ```
572
+
573
+ ### Custom Assertions (Minitest)
574
+
575
+ ```ruby
576
+ # test/test_helper.rb
577
+ module Minitest
578
+ class Test
579
+ def assert_valid_patent_id(patent_id, message = nil)
580
+ message ||= "Expected #{patent_id} to be a valid patent ID (format: XX12345Y1_YYYYMMDD)"
581
+ assert patent_id.match?(/^[A-Z]{2}[A-Z0-9]+[A-Z]\d*_\d{8}$/), message
582
+ end
583
+ end
584
+ end
585
+
586
+ # Usage in tests
587
+ def test_patent_id_validation
588
+ assert_valid_patent_id("RU134694U1_20131120")
589
+ assert_valid_patent_id("RU134694A_20131120")
590
+ end
591
+ ```
592
+
593
+ ## Known API Limitations
594
+
595
+ The library uses **Faraday** as the HTTP client with redirect support for all endpoints:
596
+
597
+ - **All endpoints** (`/search`, `/docs/{id}`, `/similar_search`, `/datasets/tree`, etc.) - βœ… Working perfectly with Faraday
598
+ - **Redirect handling**: Configured with `faraday-follow_redirects` middleware to handle server redirects automatically
599
+
600
+ ⚠️ **Minor server-side limitation**:
601
+ - **Similar Patents by Text**: Occasionally returns `503 Service Unavailable` (server-side issue, not client implementation)
602
+
603
+ All core functionality works perfectly and is production-ready with a unified HTTP approach.
604
+
605
+ ## Error Reference
606
+
607
+ ### Error Hierarchy
608
+
609
+ ```
610
+ Rospatent::Errors::Error (base)
611
+ β”œβ”€β”€ MissingTokenError
612
+ β”œβ”€β”€ ApiError
613
+ β”‚ β”œβ”€β”€ AuthenticationError (401)
614
+ β”‚ β”œβ”€β”€ NotFoundError (404)
615
+ β”‚ β”œβ”€β”€ RateLimitError (429)
616
+ β”‚ └── ServiceUnavailableError (503)
617
+ β”œβ”€β”€ ConnectionError
618
+ β”‚ └── TimeoutError
619
+ β”œβ”€β”€ InvalidRequestError
620
+ └── ValidationError
621
+ ```
622
+
623
+ ### Common Error Scenarios
624
+
625
+ ```ruby
626
+ # Missing or invalid token
627
+ Rospatent::Errors::MissingTokenError
628
+ Rospatent::Errors::AuthenticationError
629
+
630
+ # Invalid input parameters
631
+ Rospatent::Errors::ValidationError
632
+
633
+ # Resource not found
634
+ Rospatent::Errors::NotFoundError
635
+
636
+ # Rate limiting
637
+ Rospatent::Errors::RateLimitError # Check retry_after
638
+
639
+ # Network issues
640
+ Rospatent::Errors::ConnectionError
641
+ Rospatent::Errors::TimeoutError
642
+
643
+ # Server problems
644
+ Rospatent::Errors::ServiceUnavailableError
645
+ ```
646
+
647
+ ## Rake Tasks
648
+
649
+ Useful development and maintenance tasks:
650
+
651
+ ```bash
652
+ # Validate configuration
653
+ $ bundle exec rake validate
654
+
655
+ # Cache management
656
+ $ bundle exec rake cache:stats
657
+ $ bundle exec rake cache:clear
658
+
659
+ # Generate documentation
660
+ $ bundle exec rake doc
661
+
662
+ # Run integration tests
663
+ $ bundle exec rake test_integration
664
+
665
+ # Performance benchmarks
666
+ $ bundle exec rake benchmark
667
+
668
+ # Setup development environment
669
+ $ bundle exec rake setup
670
+
671
+ # Pre-release checks
672
+ $ bundle exec rake release_check
673
+ ```
674
+
675
+ ## Performance Tips
676
+
677
+ 1. **Use Caching**: Enable caching for repeated requests
678
+ 2. **Batch Operations**: Use `batch_patents` for multiple documents
679
+ 3. **Appropriate Limits**: Don't request more data than needed
680
+ 4. **Connection Reuse**: Use the same client instance when possible
681
+ 5. **Environment Configuration**: Use production settings in production
682
+
683
+ ```ruby
684
+ # Good: Reuse client instance
685
+ client = Rospatent.client
686
+ patents = patent_ids.map { |id| client.patent(id) }
687
+
688
+ # Better: Use batch operations
689
+ patents = []
690
+ client.batch_patents(patent_ids) { |doc| patents << doc }
691
+
692
+ # Best: Use caching with shared instance
693
+ shared_client = Rospatent.client(cache: Rospatent.shared_cache)
694
+ ```
695
+
696
+ ## Troubleshooting
697
+
698
+ ### Common Issues
699
+
700
+ **Authentication Errors**:
701
+ ```ruby
702
+ # Check token validity
703
+ errors = Rospatent.validate_configuration
704
+ puts errors if errors.any?
705
+ ```
706
+
707
+ **Network Timeouts**:
708
+ ```ruby
709
+ # Increase timeout for slow connections
710
+ Rospatent.configure do |config|
711
+ config.timeout = 120
712
+ config.retry_count = 5
713
+ end
714
+ ```
715
+
716
+ **Memory Usage**:
717
+ ```ruby
718
+ # Limit cache size for memory-constrained environments
719
+ Rospatent.configure do |config|
720
+ config.cache_max_size = 100
721
+ config.cache_ttl = 300
722
+ end
723
+ ```
724
+
725
+ **Debug API Calls**:
726
+ ```ruby
727
+ # Enable detailed logging
728
+ Rospatent.configure do |config|
729
+ config.log_level = :debug
730
+ config.log_requests = true
731
+ config.log_responses = true
732
+ end
733
+ ```
734
+
735
+ ## Development
736
+
737
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
738
+
739
+ ### Development Setup
740
+
741
+ ```bash
742
+ $ git clone https://hub.mos.ru/ad/rospatent.git
743
+ $ cd rospatent
744
+ $ bundle install
745
+ $ bundle exec rake setup
746
+ ```
747
+
748
+ ### Running Tests
749
+
750
+ ```bash
751
+ # Unit tests
752
+ $ bundle exec rake test
753
+
754
+ # Integration tests (requires API token)
755
+ $ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=your_token bundle exec rake test_integration
756
+
757
+ # Code style
758
+ $ bundle exec rubocop
759
+
760
+ # All checks
761
+ $ bundle exec rake ci
762
+ ```
763
+
764
+ ### Interactive Console
765
+
766
+ ```bash
767
+ $ bin/console
768
+ ```
769
+
770
+ ## Contributing
771
+
772
+ Bug reports and pull requests are welcome on MosHub at https://hub.mos.ru/ad/rospatent.
773
+
774
+ ### Development Guidelines
775
+
776
+ 1. **Write Tests**: Ensure all new features have corresponding tests
777
+ 2. **Follow Style**: Run `rubocop` and fix any style issues
778
+ 3. **Document Changes**: Update README and CHANGELOG
779
+ 4. **Validate Configuration**: Run `rake validate` before submitting
780
+
781
+ ### Release Process
782
+
783
+ ```bash
784
+ # Pre-release checks
785
+ $ bundle exec rake release_check
786
+
787
+ # Update version and release
788
+ $ bundle exec rake release
789
+ ```
790
+
791
+ ---
792
+
793
+ # πŸ“– ДокумСнтация Π½Π° русском языкС
794
+
795
+ ## ОписаниС
796
+
797
+ **Rospatent** β€” это комплСксный Ruby-ΠΊΠ»ΠΈΠ΅Π½Ρ‚ для взаимодСйствия с API поиска ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ² РоспатСнта. Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° прСдоставляСт ΡƒΠ΄ΠΎΠ±Π½Ρ‹ΠΉ интСрфСйс для поиска, получСния ΠΈ Π°Π½Π°Π»ΠΈΠ·Π° ΠΏΠ°Ρ‚Π΅Π½Ρ‚Π½ΠΎΠΉ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ с автоматичСским ΠΊΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ, Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠ΅ΠΉ запросов ΠΈ ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½Ρ‹ΠΌ Π»ΠΎΠ³ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ.
798
+
799
+ ## ✨ ΠšΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ возмоТности
800
+
801
+ - πŸ” **ПолноС ΠΏΠΎΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ API** - поиск, ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ², ΠΌΠ΅Π΄ΠΈΠ°Ρ„Π°ΠΉΠ»Ρ‹ ΠΈ датасСты
802
+ - πŸ›‘οΈ **НадСТная ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок** - комплСксныС Ρ‚ΠΈΠΏΡ‹ ошибок с Π΄Π΅Ρ‚Π°Π»ΡŒΠ½Ρ‹ΠΌ контСкстом
803
+ - ⚑ **Π˜Π½Ρ‚Π΅Π»Π»Π΅ΠΊΡ‚ΡƒΠ°Π»ΡŒΠ½ΠΎΠ΅ ΠΊΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅** - ΠΊΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π² памяти с TTL ΠΈ LRU ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ΠΌ
804
+ - βœ… **Валидация Π²Ρ…ΠΎΠ΄Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ…** - автоматичСская валидация ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² с ΠΏΠΎΠ»Π΅Π·Π½Ρ‹ΠΌΠΈ сообщСниями
805
+ - πŸ“Š **Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€ΠΈΡ€ΠΎΠ²Π°Π½Π½ΠΎΠ΅ Π»ΠΎΠ³ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅** - JSON/тСкстовоС Π»ΠΎΠ³ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ с отслСТиваниСм запросов/ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ²
806
+ - πŸš€ **ΠŸΠ°ΠΊΠ΅Ρ‚Π½Ρ‹Π΅ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ** - ΠΏΠ°Ρ€Π°Π»Π»Π΅Π»ΡŒΠ½Π°Ρ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° мноТСства ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ²
807
+ - βš™οΈ **АдаптивныС окруТСния** - Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ для development/staging/production
808
+ - πŸ§ͺ **КомплСксноС тСстированиС** - 96% ΠΏΠΎΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ тСстами с ΠΈΠ½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΠΎΠ½Π½Ρ‹ΠΌΠΈ тСстами
809
+ - πŸ“š **ΠžΡ‚Π»ΠΈΡ‡Π½Π°Ρ докумСнтация** - ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½Ρ‹Π΅ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΈ докумСнтация API
810
+
811
+ ## Быстрый старт
812
+
813
+ ### Установка
814
+
815
+ Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ Π² ваш Gemfile:
816
+
817
+ ```ruby
818
+ gem 'rospatent'
819
+ ```
820
+
821
+ Или установитС Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ:
822
+
823
+ ```bash
824
+ $ gem install rospatent
825
+ ```
826
+
827
+ ### Базовая настройка
828
+
829
+ ```ruby
830
+ require 'rospatent'
831
+
832
+ # Настройка ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°
833
+ Rospatent.configure do |config|
834
+ config.token = "ваш_jwt_Ρ‚ΠΎΠΊΠ΅Π½"
835
+ end
836
+
837
+ # Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°
838
+ client = Rospatent.client
839
+ ```
840
+
841
+ ## ОсновноС использованиС
842
+
843
+ ### Поиск ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ²
844
+
845
+ ```ruby
846
+ # ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ поиск
847
+ results = client.search(q: "солнСчная батарСя")
848
+
849
+ # Π Π°ΡΡˆΠΈΡ€Π΅Π½Π½Ρ‹ΠΉ поиск с Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°ΠΌΠΈ
850
+ results = client.search(
851
+ q: "искусствСнный ΠΈΠ½Ρ‚Π΅Π»Π»Π΅ΠΊΡ‚",
852
+ limit: 50,
853
+ offset: 100,
854
+ datasets: ["ru_since_1994"],
855
+ sort: "pub_date:desc",
856
+ highlight: true
857
+ )
858
+
859
+ # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ²
860
+ puts "НайдСно: #{results.total} ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ²"
861
+ results.hits.each do |patent|
862
+ puts "#{patent['id']}: #{patent['title']}"
863
+ end
864
+ ```
865
+
866
+ ### ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ²
867
+
868
+ ```ruby
869
+ # По ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρƒ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°
870
+ patent = client.patent("RU134694U1_20131120")
871
+
872
+ # ΠŸΠ°Ρ€ΡΠΈΠ½Π³ содСрТимого
873
+ abstract = client.parse_abstract(patent)
874
+ description = client.parse_description(patent, format: :text)
875
+
876
+ puts "Π Π΅Ρ„Π΅Ρ€Π°Ρ‚: #{abstract}"
877
+ puts "ОписаниС: #{description}"
878
+ ```
879
+
880
+ ### Поиск ΠΏΠΎΡ…ΠΎΠΆΠΈΡ… ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ²
881
+
882
+ ```ruby
883
+ # Поиск ΠΏΠΎΡ…ΠΎΠΆΠΈΡ… ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ² ΠΏΠΎ ID
884
+ similar = client.similar_patents_by_id("RU134694U1_20131120", count: 50)
885
+
886
+ # Поиск ΠΏΠΎΡ…ΠΎΠΆΠΈΡ… ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ² ΠΏΠΎ описанию тСкста
887
+ similar = client.similar_patents_by_text(
888
+ "Π Π°ΠΊΠ΅Ρ‚Π½Ρ‹ΠΉ Π΄Π²ΠΈΠ³Π°Ρ‚Π΅Π»ΡŒ с ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½Π½ΠΎΠΉ тягой",
889
+ count: 25
890
+ )
891
+
892
+ # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ΠΏΠΎΡ…ΠΎΠΆΠΈΡ… ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ²
893
+ similar["hits"]&.each do |patent|
894
+ puts "ΠŸΠΎΡ…ΠΎΠΆΠΈΠΉ: #{patent['id']} (ΠΎΡ†Π΅Π½ΠΊΠ°: #{patent['_score']})"
895
+ end
896
+ ```
897
+
898
+ ### Поиск ΠΏΠΎ классификаторам
899
+
900
+ Поиск Π² систСмах ΠΏΠ°Ρ‚Π΅Π½Ρ‚Π½ΠΎΠΉ классификации (IPC ΠΈ CPC) ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠΉ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ классификационных ΠΊΠΎΠ΄Π°Ρ…:
901
+
902
+ ```ruby
903
+ # Поиск классификационных ΠΊΠΎΠ΄ΠΎΠ², связанных с Ρ€Π°ΠΊΠ΅Ρ‚Π°ΠΌΠΈ Π² IPC
904
+ ipc_results = client.classification_search("ipc", query: "Ρ€Π°ΠΊΠ΅Ρ‚Π°", lang: "ru")
905
+ puts "НайдСно #{ipc_results['total']} кодов IPC"
906
+
907
+ ipc_results["hits"]&.each do |hit|
908
+ puts "#{hit['code']}: #{hit['description']}"
909
+ end
910
+
911
+ # Поиск ΠΊΠΎΠ΄ΠΎΠ², связанных с Ρ€Π°ΠΊΠ΅Ρ‚Π°ΠΌΠΈ Π² CPC Π½Π° английском
912
+ cpc_results = client.classification_search("cpc", query: "rocket", lang: "en")
913
+
914
+ # ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠΉ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΌ классификационном ΠΊΠΎΠ΄Π΅
915
+ code_info = client.classification_code("ipc", code: "F02K9/00", lang: "ru")
916
+ puts "Код: #{code_info['code']}"
917
+ puts "ОписаниС: #{code_info['description']}"
918
+ puts "Π˜Π΅Ρ€Π°Ρ€Ρ…ΠΈΡ: #{code_info['hierarchy']&.join(' β†’ ')}"
919
+
920
+ # ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ ΠΊΠΎΠ΄Π΅ CPC Π½Π° английском
921
+ cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
922
+ ```
923
+
924
+ **ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅ΠΌΡ‹Π΅ систСмы классификации:**
925
+ - `"ipc"` - ΠœΠ΅ΠΆΠ΄ΡƒΠ½Π°Ρ€ΠΎΠ΄Π½Π°Ρ патСнтная классификация (МПК)
926
+ - `"cpc"` - БовмСстная патСнтная классификация (БПК)
927
+
928
+ **ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅ΠΌΡ‹Π΅ языки:**
929
+ - `"ru"` - Русский
930
+ - `"en"` - Английский
931
+
932
+ ### ΠœΠ΅Π΄ΠΈΠ°Ρ„Π°ΠΉΠ»Ρ‹ ΠΈ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹
933
+
934
+ ```ruby
935
+ # Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅ PDF ΠΏΠ°Ρ‚Π΅Π½Ρ‚Π°
936
+ pdf_data = client.patent_media(
937
+ "National", # collection_id
938
+ "RU", # country_code
939
+ "U1", # doc_type
940
+ "2013/11/20", # pub_date
941
+ "134694", # pub_number
942
+ "document.pdf" # filename
943
+ )
944
+ File.write("patent.pdf", pdf_data)
945
+
946
+ # Π£ΠΏΡ€ΠΎΡ‰Π΅Π½Π½Ρ‹ΠΉ ΠΌΠ΅Ρ‚ΠΎΠ΄ с использованиСм ID ΠΏΠ°Ρ‚Π΅Π½Ρ‚Π°
947
+ pdf_data = client.patent_media_by_id(
948
+ "RU134694U1_20131120",
949
+ "National",
950
+ "document.pdf"
951
+ )
952
+
953
+ # ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ доступных датасСтов
954
+ datasets = client.datasets_tree
955
+ datasets.each do |dataset|
956
+ puts "#{dataset['id']}: #{dataset['name']}"
957
+ end
958
+ ```
959
+
960
+ ## Π Π°ΡΡˆΠΈΡ€Π΅Π½Π½Ρ‹Π΅ возмоТности
961
+
962
+ ### ΠŸΠ°ΠΊΠ΅Ρ‚Π½Ρ‹Π΅ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ
963
+
964
+ ```ruby
965
+ patent_ids = ["RU134694U1_20131120", "RU2358138C1_20090610"]
966
+
967
+ client.batch_patents(patent_ids) do |patent|
968
+ puts "ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°: #{patent['id']}"
969
+ # Π’Π°ΡˆΠ° Π»ΠΎΠ³ΠΈΠΊΠ° ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ
970
+ end
971
+ ```
972
+
973
+ ### ΠšΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅
974
+
975
+ ```ruby
976
+ # ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ кСша
977
+ Rospatent.configure do |config|
978
+ config.cache_enabled = true
979
+ config.cache_ttl = 600 # 10 ΠΌΠΈΠ½ΡƒΡ‚
980
+ config.cache_max_size = 1000 # ΠœΠ°ΠΊΡΠΈΠΌΡƒΠΌ элСмСнтов
981
+ end
982
+
983
+ # Бтатистика кСша
984
+ stats = client.statistics
985
+ puts "Попаданий в кСш: #{stats[:cache_stats][:hits]}"
986
+ ```
987
+
988
+ ### ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок
989
+
990
+ ```ruby
991
+ begin
992
+ results = client.search(q: "поисковый запрос")
993
+ rescue Rospatent::Errors::AuthenticationError => e
994
+ puts "Ошибка Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ: #{e.message}"
995
+ rescue Rospatent::Errors::RateLimitError => e
996
+ puts "ΠŸΡ€Π΅Π²Ρ‹ΡˆΠ΅Π½ Π»ΠΈΠΌΠΈΡ‚ запросов: #{e.message}"
997
+ rescue Rospatent::Errors::ApiError => e
998
+ puts "Ошибка API: #{e.message}"
999
+ end
1000
+ ```
1001
+
1002
+ ## Настройка окруТСния
1003
+
1004
+ ### Π Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°
1005
+
1006
+ ```ruby
1007
+ Rospatent.configure do |config|
1008
+ config.environment = "development"
1009
+ config.token = ENV['ROSPATENT_DEV_TOKEN']
1010
+ config.log_level = :debug
1011
+ config.log_requests = true
1012
+ config.cache_ttl = 60
1013
+ end
1014
+ ```
1015
+
1016
+ ### ΠŸΡ€ΠΎΠ΄Π°ΠΊΡˆΠ½
1017
+
1018
+ ```ruby
1019
+ Rospatent.configure do |config|
1020
+ config.environment = "production"
1021
+ config.token = ENV['ROSPATENT_TOKEN']
1022
+ config.log_level = :warn
1023
+ config.cache_ttl = 600
1024
+ config.timeout = 60
1025
+ config.retry_count = 5
1026
+ end
1027
+ ```
1028
+
1029
+ ## Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΡ с Rails
1030
+
1031
+ ```ruby
1032
+ # config/initializers/rospatent.rb
1033
+ Rospatent.configure do |config|
1034
+ config.token = Rails.application.credentials.rospatent_token
1035
+ config.environment = Rails.env
1036
+ config.cache_enabled = Rails.env.production?
1037
+ config.log_level = Rails.env.production? ? :warn : :debug
1038
+ end
1039
+
1040
+ # Π’ ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»Π»Π΅Ρ€Π΅ ΠΈΠ»ΠΈ сСрвисС
1041
+ class PatentService
1042
+ def initialize
1043
+ @client = Rospatent.client
1044
+ end
1045
+
1046
+ def search_patents(query, **options)
1047
+ @client.search(q: query, **options)
1048
+ end
1049
+ end
1050
+ ```
1051
+
1052
+ ## Π˜Π·Π²Π΅ΡΡ‚Π½Ρ‹Π΅ ограничСния API
1053
+
1054
+ Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ **Faraday** Π² качСствС HTTP-ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° с ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠΎΠΉ Ρ€Π΅Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΎΠ² для всСх endpoints:
1055
+
1056
+ - **ВсС endpoints** (`/search`, `/docs/{id}`, `/similar_search`, `/datasets/tree`, ΠΈ Ρ‚.Π΄.) - βœ… Π Π°Π±ΠΎΡ‚Π°ΡŽΡ‚ идСально с Faraday
1057
+ - **ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Ρ€Π΅Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΎΠ²**: НастроСна с middleware `faraday-follow_redirects` для автоматичСской ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ сСрвСрных Ρ€Π΅Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΎΠ²
1058
+
1059
+ ⚠️ **ΠΠ΅Π·Π½Π°Ρ‡ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ΅ сСрвСрноС ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΠ΅**:
1060
+ - **Поиск ΠΏΠΎΡ…ΠΎΠΆΠΈΡ… ΠΏΠ°Ρ‚Π΅Π½Ρ‚ΠΎΠ² ΠΏΠΎ тСксту**: Иногда Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ `503 Service Unavailable` (ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠ° сСрвСра, Π½Π΅ клиСнтской Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ)
1061
+
1062
+ Вся основная Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½Π° ΠΈ Π³ΠΎΡ‚ΠΎΠ²Π° для ΠΏΡ€ΠΎΠ΄Π°ΠΊΡˆΠ΅Π½Π°.
1063
+
1064
+ ## Π‘ΠΏΡ€Π°Π²ΠΎΡ‡Π½ΠΈΠΊ ошибок
1065
+
1066
+ ### Π˜Π΅Ρ€Π°Ρ€Ρ…ΠΈΡ ошибок
1067
+
1068
+ ```
1069
+ Rospatent::Errors::Error (базовая)
1070
+ β”œβ”€β”€ MissingTokenError
1071
+ β”œβ”€β”€ ApiError
1072
+ β”‚ β”œβ”€β”€ AuthenticationError (401)
1073
+ β”‚ β”œβ”€β”€ NotFoundError (404)
1074
+ β”‚ β”œβ”€β”€ RateLimitError (429)
1075
+ β”‚ └── ServiceUnavailableError (503)
1076
+ β”œβ”€β”€ ConnectionError
1077
+ β”‚ └── TimeoutError
1078
+ β”œβ”€β”€ InvalidRequestError
1079
+ └── ValidationError
1080
+ ```
1081
+
1082
+ ### РаспространСнныС сцСнарии ошибок
1083
+
1084
+ ```ruby
1085
+ # ΠžΡ‚ΡΡƒΡ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ ΠΈΠ»ΠΈ Π½Π΅Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Ρ‚ΠΎΠΊΠ΅Π½
1086
+ Rospatent::Errors::MissingTokenError
1087
+ Rospatent::Errors::AuthenticationError
1088
+
1089
+ # ΠΠ΅Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Π²Ρ…ΠΎΠ΄Π½Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹
1090
+ Rospatent::Errors::ValidationError
1091
+
1092
+ # РСсурс Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½
1093
+ Rospatent::Errors::NotFoundError
1094
+
1095
+ # ΠžΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΠ΅ скорости
1096
+ Rospatent::Errors::RateLimitError # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ retry_after
1097
+
1098
+ # ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ΡΠ΅Ρ‚ΡŒΡŽ
1099
+ Rospatent::Errors::ConnectionError
1100
+ Rospatent::Errors::TimeoutError
1101
+
1102
+ # ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ сСрвСра
1103
+ Rospatent::Errors::ServiceUnavailableError
1104
+ ```
1105
+
1106
+ ## ВСстированиС
1107
+
1108
+ ### Запуск тСстов
1109
+
1110
+ ```bash
1111
+ # ВсС тСсты
1112
+ $ bundle exec rake test
1113
+
1114
+ # ΠšΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹ΠΉ тСстовый Ρ„Π°ΠΉΠ»
1115
+ $ bundle exec ruby -Itest test/unit/client_test.rb
1116
+
1117
+ # Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΠΎΠ½Π½Ρ‹Π΅ тСсты (трСбуСтся API Ρ‚ΠΎΠΊΠ΅Π½)
1118
+ $ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=ваш_Ρ‚ΠΎΠΊΠ΅Π½ bundle exec rake test_integration
1119
+
1120
+ # Запуск с ΠΏΠΎΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ΠΌ
1121
+ $ bundle exec rake coverage
1122
+ ```
1123
+
1124
+ ### Настройка тСстов
1125
+
1126
+ ```ruby
1127
+ # test/test_helper.rb
1128
+ module Minitest
1129
+ class Test
1130
+ def setup
1131
+ Rospatent.reset
1132
+ Rospatent.configure do |config|
1133
+ config.token = ENV.fetch("ROSPATENT_TEST_TOKEN", "test_token")
1134
+ config.environment = "development"
1135
+ config.cache_enabled = false
1136
+ config.log_level = :error
1137
+ end
1138
+ end
1139
+ end
1140
+ end
1141
+ ```
1142
+
1143
+ ## Π‘ΠΎΠ²Π΅Ρ‚Ρ‹ ΠΏΠΎ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ
1144
+
1145
+ 1. **Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΊΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅**: Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅ ΠΊΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ для ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΡΡŽΡ‰ΠΈΡ…ΡΡ запросов
1146
+ 2. **ΠŸΠ°ΠΊΠ΅Ρ‚Π½Ρ‹Π΅ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ**: Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ `batch_patents` для мноТСства Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²
1147
+ 3. **ΠŸΠΎΠ΄Ρ…ΠΎΠ΄ΡΡ‰ΠΈΠ΅ Π»ΠΈΠΌΠΈΡ‚Ρ‹**: НС Π·Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°ΠΉΡ‚Π΅ большС Π΄Π°Π½Π½Ρ‹Ρ…, Ρ‡Π΅ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ
1148
+ 4. **ΠŸΠ΅Ρ€Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ соСдинСний**: Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΎΠ΄ΠΈΠ½ экзСмпляр ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° ΠΊΠΎΠ³Π΄Π° Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ
1149
+ 5. **ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ окруТСния**: Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΏΡ€ΠΎΠ΄Π°ΠΊΡˆΠ½ настройки Π² ΠΏΡ€ΠΎΠ΄Π°ΠΊΡˆΠ½Π΅
1150
+
1151
+ ```ruby
1152
+ # Π₯ΠΎΡ€ΠΎΡˆΠΎ: ΠŸΠ΅Ρ€Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ экзСмпляра ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°
1153
+ client = Rospatent.client
1154
+ patents = patent_ids.map { |id| client.patent(id) }
1155
+
1156
+ # Π›ΡƒΡ‡ΡˆΠ΅: ИспользованиС ΠΏΠ°ΠΊΠ΅Ρ‚Π½Ρ‹Ρ… ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ
1157
+ patents = []
1158
+ client.batch_patents(patent_ids) { |doc| patents << doc }
1159
+
1160
+ # ΠžΡ‚Π»ΠΈΡ‡Π½ΠΎ: ИспользованиС ΠΊΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΡ с ΠΎΠ±Ρ‰ΠΈΠΌ экзСмпляром
1161
+ shared_client = Rospatent.client(cache: Rospatent.shared_cache)
1162
+ ```
1163
+
1164
+ ## УстранСниС Π½Π΅ΠΏΠΎΠ»Π°Π΄ΠΎΠΊ
1165
+
1166
+ ### ЧастыС ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹
1167
+
1168
+ **Ошибки Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ**:
1169
+ ```ruby
1170
+ # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° валидности Ρ‚ΠΎΠΊΠ΅Π½Π°
1171
+ errors = Rospatent.validate_configuration
1172
+ puts errors if errors.any?
1173
+ ```
1174
+
1175
+ **Π’Π°ΠΉΠΌΠ°ΡƒΡ‚Ρ‹ сСти**:
1176
+ ```ruby
1177
+ # Π£Π²Π΅Π»ΠΈΡ‡Π΅Π½ΠΈΠ΅ Ρ‚Π°ΠΉΠΌΠ°ΡƒΡ‚Π° для ΠΌΠ΅Π΄Π»Π΅Π½Π½Ρ‹Ρ… соСдинСний
1178
+ Rospatent.configure do |config|
1179
+ config.timeout = 120
1180
+ config.retry_count = 5
1181
+ end
1182
+ ```
1183
+
1184
+ **ИспользованиС памяти**:
1185
+ ```ruby
1186
+ # ΠžΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΠ΅ Ρ€Π°Π·ΠΌΠ΅Ρ€Π° кСша для ΠΎΠΊΡ€ΡƒΠΆΠ΅Π½ΠΈΠΉ с ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½Π½ΠΎΠΉ ΠΏΠ°ΠΌΡΡ‚ΡŒΡŽ
1187
+ Rospatent.configure do |config|
1188
+ config.cache_max_size = 100
1189
+ config.cache_ttl = 300
1190
+ end
1191
+ ```
1192
+
1193
+ **ΠžΡ‚Π»Π°Π΄ΠΊΠ° API Π²Ρ‹Π·ΠΎΠ²ΠΎΠ²**:
1194
+ ```ruby
1195
+ # Π’ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ³ΠΎ логирования
1196
+ Rospatent.configure do |config|
1197
+ config.log_level = :debug
1198
+ config.log_requests = true
1199
+ config.log_responses = true
1200
+ end
1201
+ ```
1202
+
1203
+ ---
1204
+
1205
+ ## Changelog
1206
+
1207
+ See [CHANGELOG.md](CHANGELOG.md) for detailed version history.
1208
+
1209
+ ## License
1210
+
1211
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
1212
+
1213
+ ---
1214
+
1215
+ ## API Reference
1216
+
1217
+ For detailed API documentation, see the [generated documentation](https://rubydoc.info/gems/rospatent) or run:
1218
+
1219
+ ```bash
1220
+ $ bundle exec rake doc
1221
+ $ open doc/index.html
1222
+ ```
1223
+
1224
+ **Key Classes**:
1225
+ - `Rospatent::Client` - Main API client
1226
+ - `Rospatent::Configuration` - Configuration management
1227
+ - `Rospatent::Cache` - Caching system
1228
+ - `Rospatent::Logger` - Structured logging
1229
+ - `Rospatent::SearchResult` - Search result wrapper
1230
+ - `Rospatent::PatentParser` - Patent content parsing
1231
+
1232
+ **Classification Features**:
1233
+ - Classification system search (IPC/CPC)
1234
+ - Detailed classification code information
1235
+ - Multi-language support (Russian/English)
1236
+ - Automatic caching of classification data
1237
+
1238
+ **Patent Features**:
1239
+ - Patent search by text
1240
+ - Patent details retrieval
1241
+ - Patent classification retrieval
1242
+ - Patent content parsing
1243
+ - Patent media retrieval
1244
+ - Patent similarity search by text
1245
+ - Patent similarity search by ID
1246
+
1247
+ **Supported Ruby Versions**: Ruby 3.3.0+