Rospatent
A comprehensive Ruby client for the Rospatent patent search API with advanced features including intelligent caching, input validation, structured logging, and robust error handling.
π·πΊ ΠΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ Π½Π° ΡΡΡΡΠΊΠΎΠΌ ΡΠ·ΡΠΊΠ΅ Π΄ΠΎΡΡΡΠΏΠ½Π° Π½ΠΈΠΆΠ΅
β¨ Key Features
- π Complete API Coverage - Search, retrieve patents, media files, and datasets
- π‘οΈ Robust Error Handling - Comprehensive error types with detailed context
- β‘ Intelligent Caching - In-memory caching with TTL and LRU eviction
- β Input Validation - Automatic parameter validation with helpful error messages
- π Structured Logging - JSON/text logging with request/response tracking
- π Batch Operations - Process multiple patents concurrently
- βοΈ Environment-Aware - Different configurations for dev/staging/production
- π§ͺ Comprehensive Testing - Extensive unit and integration test coverage with robust error handling validation
- π Excellent Documentation - Detailed examples and API documentation
Installation
Add this line to your application's Gemfile:
gem 'rospatent'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install rospatent
Quick Start
# Minimal configuration
Rospatent.configure do |config|
config.token = "your_jwt_token"
end
# Create a client and search
client = Rospatent.client
results = client.search(q: "ΡΠ°ΠΊΠ΅ΡΠ°", limit: 10)
puts "Found #{results.total} results"
results.hits.each do |hit|
puts "Patent: #{hit['id']} - #{hit.dig('biblio', 'ru', 'title')}"
end
Configuration
Basic Configuration
Rospatent.configure do |config|
# Required
config.token = "your_jwt_token"
# API settings
config.api_url = "https://searchplatform.rospatent.gov.ru/patsearch/v0.2"
config.timeout = 30
config.retry_count = 3
# Environment (development, staging, production)
config.environment = "production"
end
Advanced Configuration
Rospatent.configure do |config|
config.token = "your_jwt_token"
# Caching (enabled by default)
config.cache_enabled = true
config.cache_ttl = 300 # 5 minutes
config.cache_max_size = 1000 # Maximum cached items
# Logging
config.log_level = :info # :debug, :info, :warn, :error
config.log_requests = true # Log API requests
config.log_responses = true # Log API responses
# Connection settings
config.connection_pool_size = 5
config.connection_keep_alive = true
# Token management
config.token_expires_at = Time.now + 3600
config.token_refresh_callback = -> { refresh_token! }
end
Environment-Specific Configuration
The gem automatically adjusts settings based on environment with sensible defaults:
Development Environment
# Optimized for development
Rospatent.configure do |config|
config.environment = "development"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :debug
config.log_requests = true
config.log_responses = true
config.cache_ttl = 60 # Short cache for development
config.timeout = 10 # Fast timeouts for quick feedback
end
Staging Environment
# Optimized for staging
Rospatent.configure do |config|
config.environment = "staging"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :info
config.cache_ttl = 300 # Longer cache for performance
config.timeout = 45 # Longer timeouts for reliability
config.retry_count = 3 # More retries for resilience
end
Production Environment
# Optimized for production
Rospatent.configure do |config|
config.environment = "production"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :warn
config.cache_ttl = 600 # Longer cache for performance
config.timeout = 60 # Longer timeouts for reliability
config.retry_count = 5 # More retries for resilience
end
Environment Variables & Rails Integration
β οΈ CRITICAL: Understanding environment variable priority is essential to avoid configuration issues, especially in Rails applications.
Token Configuration Priority
-
Rails credentials:
Rails.application.credentials.rospatent_token
-
Primary environment variable:
ROSPATENT_TOKEN
-
Legacy environment variable:
ROSPATENT_API_TOKEN
# Recommended approach
export ROSPATENT_TOKEN="your_jwt_token"
# Legacy support (still works)
export ROSPATENT_API_TOKEN="your_jwt_token"
Log Level Configuration Priority
# Environment variable takes precedence over Rails defaults
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
ENV["ROSPATENT_LOG_LEVEL"].to_sym
else
Rails.env.production? ? :warn : :debug
end
β οΈ Common Issue: Setting ROSPATENT_LOG_LEVEL=debug
in production will override Rails-specific logic and cause DEBUG logs to appear in production!
Complete Environment Variables Reference
# Core configuration
ROSPATENT_TOKEN="your_jwt_token" # API authentication token
ROSPATENT_ENV="production" # Override Rails.env if needed
ROSPATENT_API_URL="custom_url" # Override default API URL
# Logging configuration
ROSPATENT_LOG_LEVEL="warn" # debug, info, warn, error
ROSPATENT_LOG_REQUESTS="false" # Log API requests
ROSPATENT_LOG_RESPONSES="false" # Log API responses
# Cache configuration
ROSPATENT_CACHE_ENABLED="true" # Enable/disable caching
ROSPATENT_CACHE_TTL="300" # Cache TTL in seconds
ROSPATENT_CACHE_MAX_SIZE="1000" # Maximum cache items
# Connection configuration
ROSPATENT_TIMEOUT="30" # Request timeout in seconds
ROSPATENT_RETRY_COUNT="3" # Number of retries
ROSPATENT_POOL_SIZE="5" # Connection pool size
ROSPATENT_KEEP_ALIVE="true" # Keep-alive connections
# Environment-specific overrides
ROSPATENT_DEV_API_URL="dev_url" # Development API URL
ROSPATENT_STAGING_API_URL="staging_url" # Staging API URL
Best Practices for Rails
-
Use Rails credentials for tokens:
rails credentials:edit # Add: rospatent_token: your_jwt_token
-
Set environment-specific variables:
# config/environments/production.rb ENV["ROSPATENT_LOG_LEVEL"] ||= "warn" ENV["ROSPATENT_CACHE_ENABLED"] ||= "true"
-
Avoid setting DEBUG level in production:
# β DON'T DO THIS in production export ROSPATENT_LOG_LEVEL=debug # β DO THIS instead export ROSPATENT_LOG_LEVEL=warn
Configuration Validation
# Validate current configuration
errors = Rospatent.validate_configuration
if errors.any?
puts "Configuration errors:"
errors.each { |error| puts " - #{error}" }
else
puts "Configuration is valid β"
end
Basic Usage
Searching Patents
client = Rospatent.client
# Simple text search
results = client.search(q: "ΡΠ°ΠΊΠ΅ΡΠ°")
# Natural language search
results = client.search(qn: "rocket engine design")
# Advanced search with all options
results = client.search(
q: "ΡΠ°ΠΊΠ΅ΡΠ° AND Π΄Π²ΠΈΠ³Π°ΡΠ΅Π»Ρ",
limit: 50,
offset: 100,
datasets: ["ru_since_1994"],
filter: {
"classification.ipc_group": { "values": ["F02K9"] },
"application.filing_date": { "range": { "gte": "20200101" } }
},
sort: "publication_date:desc", # same as 'sort: :pub_date'; see Search#validate_sort_parameter for other sort options
group_by: "family:dwpi", # Patent family grouping: "family:docdb" or "family:dwpi"
include_facets: true, # Boolean: true/false (automatically converted to 1/0 for API)
pre_tag: "<mark>", # Both pre_tag and post_tag must be provided together
post_tag: "</mark>", # Can be strings or arrays for multi-color highlighting
highlight: { # Advanced highlight configuration (independent of pre_tag/post_tag)
"profiles" => [
{ "q" => "ΠΊΠΎΡΠΌΠΈΡΠ΅ΡΠΊΠ°Ρ", "pre_tag" => "<b>", "post_tag" => "</b>" },
"_searchquery_"
]
}
)
# Simple highlighting with tags (both pre_tag and post_tag required)
results = client.search(
q: "ΡΠ°ΠΊΠ΅ΡΠ°",
pre_tag: "<mark>",
post_tag: "</mark>"
)
# Multi-color highlighting with arrays
results = client.search(
q: "ΠΊΠΎΡΠΌΠΈΡΠ΅ΡΠΊΠ°Ρ ΡΠ°ΠΊΠ΅ΡΠ°",
pre_tag: ["<b>", "<i>"], # Round-robin highlighting
post_tag: ["</b>", "</i>"] # with different tags
)
# Advanced highlighting with profiles (independent of pre_tag/post_tag)
results = client.search(
q: "ΡΠ°ΠΊΠ΅ΡΠ°",
highlight: {
"profiles" => [
{ "q" => "ΠΊΠΎΡΠΌΠΈΡΠ΅ΡΠΊΠ°Ρ", "pre_tag" => "<b>", "post_tag" => "</b>" },
"_searchquery_" # References main search query highlighting
]
}
)
# Patent family grouping (groups patents from the same invention)
results = client.search(
q: "rocket",
group_by: "family:docdb", # DOCDB simple patent families
datasets: ["dwpi"],
limit: 10
)
results = client.search(
q: "rocket",
group_by: "family:dwpi", # DWPI simple patent families
datasets: ["dwpi"],
limit: 10
)
# Process results
puts "Found #{results.total} total results (#{results.available} available)"
puts "Showing #{results.count} results"
results.hits.each do |hit|
puts "ID: #{hit['id']}"
puts "Title: #{hit.dig('biblio', 'ru', 'title')}"
puts "Date: #{hit.dig('common', 'publication_date')}"
puts "IPC: #{hit.dig('common', 'classification', 'ipc')&.map {|c| c['fullname']}&.join('; ')}"
puts "---"
end
Advanced Filter Parameters
The filter
parameter supports complex filtering with automatic validation and format conversion:
List Filters (require {"values": [...]}
format)
# Classification filters
results = client.search(
q: "artificial intelligence",
filter: {
"classification.ipc_group": { "values": ["G06N", "G06F"] },
"classification.cpc_group": { "values": ["G06N3/", "G06N20/"] }
}
)
# Author and patent holder filters
results = client.search(
q: "invention",
filter: {
"authors": { "values": ["ΠΠ²Π°Π½ΠΎΠ² Π.Π.", "ΠΠ΅ΡΡΠΎΠ² Π.Π."] },
"patent_holders": { "values": ["ΠΠΠ ΠΠΎΠΌΠΏΠ°Π½ΠΈΡ"] },
"country": { "values": ["RU", "US"] },
"kind": { "values": ["A1", "U1"] }
}
)
# Document ID filters
results = client.search(
q: "device",
filter: {
"ids": { "values": ["RU134694U1_20131120", "RU2358138C1_20090610"] }
}
)
Date Range Filters (require {"range": {"operator": "YYYYMMDD"}}
format)
# Automatic date format conversion
results = client.search(
q: "innovation",
filter: {
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
"application.filing_date": { "range": { "gte": "2019-06-15" } }
}
)
# Direct API format (YYYYMMDD)
results = client.search(
q: "technology",
filter: {
"date_published": { "range": { "gte": "20200101", "lt": "20240101" } }
}
)
# Using Date objects (automatically converted)
results = client.search(
q: "patent",
filter: {
"application.filing_date": {
"range": {
"gte": Date.new(2020, 1, 1),
"lte": Date.new(2023, 12, 31)
}
}
}
)
Supported date operators: gt
, gte
, lt
, lte
Date format conversion:
-
"2020-01-01"
β"20200101"
-
Date.new(2020, 1, 1)
β"20200101"
-
"20200101"
β"20200101"
(no change)
Complex Multi-Field Filters
# Comprehensive filter example
results = client.search(
q: "ΠΌΠ°ΡΠΈΠ½Π½ΠΎΠ΅ ΠΎΠ±ΡΡΠ΅Π½ΠΈΠ΅",
filter: {
# List filters
"classification.ipc_group": { "values": ["G06N", "G06F"] },
"country": { "values": ["RU", "US", "CN"] },
"kind": { "values": ["A1", "A2"] },
"authors": { "values": ["ΠΠ²Π°Π½ΠΎΠ² Π.Π."] },
# Date range filters
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
"application.filing_date": { "range": { "gte": "2019-01-01" } }
},
limit: 50
)
Supported Filter Fields:
List filters (require {"values": [...]}
format):
-
authors
- Patent authors -
patent_holders
- Patent holders/assignees -
country
- Country codes -
kind
- Document types -
ids
- Specific document IDs -
classification.ipc*
- IPC classification codes -
classification.cpc*
- CPC classification codes
Date filters (require {"range": {"operator": "YYYYMMDD"}}
format):
-
date_published
- Publication date -
application.filing_date
- Application filing date
Filter Validation:
- β Automatic field name validation
- β Structure validation (list vs range format)
- β Date format conversion and validation
- β Operator validation for ranges
- β Helpful error messages for invalid filters
# These will raise ValidationError with specific messages:
client.search(
q: "test",
filter: { "invalid_field": { "values": ["test"] } }
)
# Error: "Invalid filter field: invalid_field"
client.search(
q: "test",
filter: { "authors": ["direct", "array"] } # Missing {"values": [...]} wrapper
)
# Error: "Filter 'authors' requires format: {\"values\": [...]}"
client.search(
q: "test",
filter: { "date_published": { "range": { "invalid_op": "20200101" } } }
)
# Error: "Invalid range operator: invalid_op. Supported: gt, gte, lt, lte"
Retrieving Patent Documents
# Get patent by document ID
patent_doc = client.patent("RU134694U1_20131120")
# Get patent by components
patent_doc = client.patent_by_components(
"RU", # country_code
"134694", # number
"U1", # doc_type
Date.new(2013, 11, 20) # date (String or Date object)
)
# Access patent data
title = patent_doc.dig('biblio', 'ru', 'title')
abstract = patent_doc.dig('abstract', 'ru')
inventors = patent_doc.dig('biblio', 'ru', 'inventor')
Parsing Patent Content
Extract clean text or structured content from patents:
# Parse abstract
abstract_text = client.parse_abstract(patent_doc)
abstract_html = client.parse_abstract(patent_doc, format: :html)
abstract_en = client.parse_abstract(patent_doc, language: "en")
# Parse description
description_text = client.parse_description(patent_doc)
description_html = client.parse_description(patent_doc, format: :html)
# Get structured sections
sections = client.parse_description(patent_doc, format: :sections)
sections.each do |section|
puts "Section #{section[:number]}: #{section[:content]}"
end
Finding Similar Patents
# Find similar patents by ID
similar = client.similar_patents_by_id("RU134694U1_20131120", count: 50)
# Find similar patents by text description
similar = client.similar_patents_by_text(
"Π Π°ΠΊΠ΅ΡΠ½ΡΠΉ Π΄Π²ΠΈΠ³Π°ΡΠ΅Π»Ρ Ρ ΡΠ»ΡΡΡΠ΅Π½Π½ΠΎΠΉ ΡΡΠ³ΠΎΠΉ ...", # 50 words in request minimum
count: 25
)
# Process similar patents
similar["data"]&.each do |patent|
puts "Similar: #{patent['id']} (score: #{patent['similarity']} (#{patent['similarity_norm']}))"
end
Classification Search
Search within patent classification systems (IPC and CPC) and get detailed information about classification codes:
# Search for classification codes related to rockets in IPC
ipc_results = client.classification_search("ipc", query: "ΡΠ°ΠΊΠ΅ΡΠ°", lang: "ru")
puts "Found #{ipc_results.size} IPC codes"
ipc_results&.each do |result|
puts "#{result['Code']}: #{result['Description']}"
end
# Search for rocket-related codes in CPC using English
cpc_results = client.classification_search("cpc", query: "rocket", lang: "en")
# Get detailed information about a specific classification code
code, info = client.classification_code("ipc", code: "F02K9/00", lang: "ru")&.first
puts "Code: #{code}"
puts "Description: #{info&.first['Description']}"
puts "Hierarchy: #{info&.map{|level| level['Code']}&.join(' β ')}"
# Get CPC code information in English
cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
Supported Classification Systems:
-
"ipc"
- International Patent Classification (ΠΠΠ) -
"cpc"
- Cooperative Patent Classification (Π‘ΠΠ)
Supported Languages:
-
"ru"
- Russian -
"en"
- English
Available datasets list
datasets = client.datasets_tree
datasets.each do |category|
puts "Category: #{category['name_en']}"
category.children.each do |dataset|
puts " #{dataset['id']}: #{dataset['name_en']}"
end
end
Media and Documents
# β
Recommended: Download patent PDF with auto-generated filename
# Automatically uses the formatted publication number (e.g., "0000134694.pdf")
pdf_data = client.patent_media(
"National", # collection_id
"RU", # country_code
"U1", # doc_type
"2013/11/20", # pub_date
"134694" # pub_number (filename auto-generated)
)
client.save_binary_file(pdf_data, "patent.pdf")
# β
Alternative: Download with explicit filename
pdf_data = client.patent_media(
"National", # collection_id
"RU", # country_code
"U1", # doc_type
"2013/11/20", # pub_date
"134694", # pub_number
"document.pdf" # explicit filename
)
client.save_binary_file(pdf_data, "patent_explicit.pdf")
# β
Simplified method using patent ID (auto-generated filename)
pdf_data = client.patent_media_by_id(
"RU134694U1_20131120",
"National" # filename auto-generated as "0000134694.pdf"
)
client.save_binary_file(pdf_data, "patent_by_id.pdf")
# β
Or with explicit filename for specific files
image_data = client.patent_media_by_id(
"RU134694U1_20131120",
"National",
"image.png" # explicit filename for non-PDF files
)
client.save_binary_file(image_data, "patent_image.png")
# β
Safe file saving options:
File.binwrite("patent.pdf", pdf_data) # Manual binary write
# β Avoid: File.write can cause encoding errors with binary data
# File.write("patent.pdf", pdf_data) # This may fail!
Advanced Features
Batch Operations
Process multiple patents efficiently with concurrent requests:
document_ids = ["RU134694U1_20131120", "RU2358138C1_20090610", "RU2756123C1_20210927"]
# Process patents in batches
client.batch_patents(document_ids, batch_size: 5) do |patent_doc|
if patent_doc[:error]
puts "Error for #{patent_doc[:document_id]}: #{patent_doc[:error]}"
else
puts "Retrieved patent: #{patent_doc['id']}"
# Process patent document
end
end
# Or collect all results
patents = []
client.batch_patents(document_ids) { |doc| patents << doc }
Caching
Automatic intelligent caching improves performance:
# Caching is automatic and transparent
patent1 = client.patent("RU134694U1_20131120") # API call
patent2 = client.patent("RU134694U1_20131120") # Cached result
# Check cache statistics
stats = client.statistics
puts "Cache hit rate: #{stats[:cache_stats][:hit_rate_percent]}%"
puts "Total requests: #{stats[:requests_made]}"
puts "Average response time: #{stats[:average_request_time]}s"
# Use shared cache across clients
shared_cache = Rospatent.shared_cache
client1 = Rospatent.client(cache: shared_cache)
client2 = Rospatent.client(cache: shared_cache)
# Manual cache management
shared_cache.clear # Clear all cached data
expired_count = shared_cache.cleanup_expired # Remove expired entries
cache_stats = shared_cache.statistics # Get detailed cache statistics
Custom Logging
Configure detailed logging for monitoring and debugging:
# Create custom logger
logger = Rospatent::Logger.new(
output: Rails.logger, # Or any IO object
level: :info,
formatter: :json # :json or :text
)
client = Rospatent.client(logger: logger)
# Logs include:
# - API requests/responses with timing
# - Cache operations (hits/misses)
# - Error details with context
# - Performance metrics
# Access shared logger
shared_logger = Rospatent.shared_logger(level: :debug)
Notes:
- When using
Rails.logger
, formatting is controlled by Rails configuration,formatter
parameter ignored - When using IO objects,
formatter
parameter controls output format
Error Handling
Comprehensive error handling with specific error types and improved error message extraction:
begin
patent = client.patent("INVALID_ID")
rescue Rospatent::Errors::ValidationError => e
puts "Invalid input: #{e.message}"
puts "Field errors: #{e.errors}" if e.errors.any?
rescue Rospatent::Errors::NotFoundError => e
puts "Patent not found: #{e.message}"
rescue Rospatent::Errors::RateLimitError => e
puts "Rate limited. Retry after: #{e.retry_after} seconds"
rescue Rospatent::Errors::AuthenticationError => e
puts "Authentication failed: #{e.message}"
rescue Rospatent::Errors::ApiError => e
puts "API error (#{e.status_code}): #{e.message}"
puts "Request ID: #{e.request_id}" if e.request_id
retry if e.retryable?
rescue Rospatent::Errors::ConnectionError => e
puts "Connection error: #{e.message}"
puts "Original error: #{e.original_error}"
end
# Enhanced error message extraction
# The client automatically extracts error messages from various API response formats:
# - {"result": "Error message"} (Rospatent API format)
# - {"error": "Error message"} (Standard format)
# - {"message": "Error message"} (Alternative format)
# - {"details": "Validation details"} (Validation errors)
Input Validation
All inputs are automatically validated with helpful error messages:
# These will raise ValidationError with specific messages:
client.search(limit: 0) # "Limit must be at least 1"
client.patent("") # "Document_id cannot be empty"
client.similar_patents_by_text("", count: -1) # Multiple validation errors
# Validation includes:
# - Parameter types and formats
# - Patent ID format validation
# - Date format validation
# - Enum value validation
# - Required field validation
Performance Monitoring
Track performance and usage statistics:
# Client-specific statistics
stats = client.statistics
puts "Requests made: #{stats[:requests_made]}"
puts "Total duration: #{stats[:total_duration_seconds]}s"
puts "Average request time: #{stats[:average_request_time]}s"
puts "Cache hit rate: #{stats[:cache_stats][:hit_rate_percent]}%"
# Global statistics
global_stats = Rospatent.statistics
puts "Environment: #{global_stats[:configuration][:environment]}"
puts "Cache enabled: #{global_stats[:configuration][:cache_enabled]}"
puts "API URL: #{global_stats[:configuration][:api_url]}"
Rails Integration
Generator
$ rails generate rospatent:install
This creates config/initializers/rospatent.rb
:
Rospatent.configure do |config|
# Token priority: Rails credentials > ROSPATENT_TOKEN > ROSPATENT_API_TOKEN
config.token = Rails.application.credentials.rospatent_token ||
ENV["ROSPATENT_TOKEN"] ||
ENV["ROSPATENT_API_TOKEN"]
# Environment configuration respects ROSPATENT_ENV
config.environment = ENV.fetch("ROSPATENT_ENV", Rails.env)
# CRITICAL: Environment variables take priority over Rails defaults
# This prevents DEBUG logs appearing in production if ROSPATENT_LOG_LEVEL=debug is set
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
ENV["ROSPATENT_LOG_LEVEL"].to_sym
else
Rails.env.production? ? :warn : :debug
end
config.cache_enabled = Rails.env.production?
end
Using with Rails Logger
# In config/initializers/rospatent.rb
Rospatent.configure do |config|
config.token = Rails.application.credentials.rospatent_token
end
# Create client with Rails logger
logger = Rospatent::Logger.new(
output: Rails.logger,
level: Rails.env.production? ? :warn : :debug,
formatter: :text
)
# Use in controllers/services
class PatentService
def initialize
@client = Rospatent.client(logger: logger)
end
def search_patents(query)
@client.search(q: query, limit: 20)
rescue Rospatent::Errors::ApiError => e
Rails.logger.error "Patent search failed: #{e.message}"
raise
end
end
Testing
Running Tests
# Run all tests
$ bundle exec rake test
# Run specific test file
$ bundle exec ruby -Itest test/unit/client_test.rb
# Run integration tests (requires API token)
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=your_token bundle exec rake test_integration
# Run with coverage
$ bundle exec rake coverage
Test Configuration
For testing, reset and configure in each test's setup method:
# test/test_helper.rb - Base setup for unit tests
module Minitest
class Test
def setup
Rospatent.reset # Clean state between tests
Rospatent.configure do |config|
config.token = ENV.fetch("ROSPATENT_TEST_TOKEN", "test_token")
config.environment = "development"
config.cache_enabled = false # Disable cache for predictable tests
config.log_level = :error # Reduce test noise
end
end
end
end
# For integration tests - stable config, no reset needed
class IntegrationTest < Minitest::Test
def setup
skip unless ENV["ROSPATENT_INTEGRATION_TESTS"]
@token = ENV.fetch("ROSPATENT_TEST_TOKEN", nil)
skip "ROSPATENT_TEST_TOKEN not set" unless @token
# No reset needed - integration tests use consistent configuration
Rospatent.configure do |config|
config.token = @token
config.environment = "development"
config.cache_enabled = true
config.log_level = :debug
end
end
end
Custom Assertions (Minitest)
# test/test_helper.rb
module Minitest
class Test
def assert_valid_patent_id(patent_id, message = nil)
message ||= "Expected #{patent_id} to be a valid patent ID (format: XX12345Y1_YYYYMMDD)"
assert patent_id.match?(/^[A-Z]{2}[A-Z0-9]+[A-Z]\d*_\d{8}$/), message
end
end
end
# Usage in tests
def test_patent_id_validation
assert_valid_patent_id("RU134694U1_20131120")
assert_valid_patent_id("RU134694A_20131120")
end
Known API Limitations
The library uses Faraday as the HTTP client with redirect support for all endpoints:
-
All endpoints (
/search
,/docs/{id}
,/similar_search
,/datasets/tree
, etc.) - β Working perfectly with Faraday -
Redirect handling: Configured with
faraday-follow_redirects
middleware to handle server redirects automatically
β οΈ Minor server-side limitations:
-
Similar Patents by Text: Occasionally returns
503 Service Unavailable
(a server-side issue, not a client implementation issue)
β οΈ Documentation inconsistencies:
-
Similar Patents: According to the documentation, the array of hits is named
hits
, but the real implementation uses the namedata
-
Available Datasets: The
name
key in the real implementation has the localization suffix βname_ru
,name_en
All core functionality works perfectly and is production-ready with a unified HTTP approach.
Error Reference
Error Hierarchy
Rospatent::Errors::Error (base)
βββ MissingTokenError
βββ ApiError
β βββ AuthenticationError (401)
β βββ NotFoundError (404)
β βββ RateLimitError (429)
β βββ ServiceUnavailableError (503)
βββ ConnectionError
β βββ TimeoutError
βββ InvalidRequestError
βββ ValidationError
Common Error Scenarios
# Missing or invalid token
Rospatent::Errors::MissingTokenError
Rospatent::Errors::AuthenticationError
# Invalid input parameters
Rospatent::Errors::ValidationError
# Resource not found
Rospatent::Errors::NotFoundError
# Rate limiting
Rospatent::Errors::RateLimitError # Check retry_after
# Network issues
Rospatent::Errors::ConnectionError
Rospatent::Errors::TimeoutError
# Server problems
Rospatent::Errors::ServiceUnavailableError
Rake Tasks
Useful development and maintenance tasks:
# Validate configuration
$ bundle exec rake validate
# Cache management
$ bundle exec rake cache:stats
$ bundle exec rake cache:clear
# Generate documentation
$ bundle exec rake doc
# Run integration tests
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN='<your_jwt_token>' bundle exec rake test_integration
# Setup development environment
$ bundle exec rake setup
# Pre-release checks
$ bundle exec rake release_check
Performance Tips
- Use Caching: Enable caching for repeated requests
-
Batch Operations: Use
batch_patents
for multiple documents - Appropriate Limits: Don't request more data than needed
- Connection Reuse: Use the same client instance when possible
- Environment Configuration: Use production settings in production
# Good: Reuse client instance
client = Rospatent.client
patents = patent_ids.map { |id| client.patent(id) }
# Better: Use batch operations
patents = []
client.batch_patents(patent_ids) { |doc| patents << doc }
# Best: Use caching with shared instance
shared_client = Rospatent.client(cache: Rospatent.shared_cache)
Troubleshooting
Common Issues
Authentication Errors:
# Check token validity
errors = Rospatent.validate_configuration
puts errors if errors.any?
Network Timeouts:
# Increase timeout for slow connections
Rospatent.configure do |config|
config.timeout = 120
config.retry_count = 5
end
Memory Usage:
# Limit cache size for memory-constrained environments
Rospatent.configure do |config|
config.cache_max_size = 100
config.cache_ttl = 300
end
Debug API Calls:
# Enable detailed logging
Rospatent.configure do |config|
config.log_level = :debug
config.log_requests = true
config.log_responses = true
end
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
to run the tests.
Development Setup
$ git clone https://hub.mos.ru/ad/rospatent.git
$ cd rospatent
$ bundle install
$ bundle exec rake setup
Running Tests
# Unit tests
$ bundle exec rake test
# Integration tests (requires API token)
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=your_token bundle exec rake test_integration
# Code style
$ bundle exec rubocop
# All checks
$ bundle exec rake ci
Interactive Console
$ bin/console
Contributing
Bug reports and pull requests are welcome on MosHub at https://hub.mos.ru/ad/rospatent.
Development Guidelines
- Write Tests: Ensure all new features have corresponding tests
-
Follow Style: Run
rubocop
and fix any style issues - Document Changes: Update README and CHANGELOG
-
Validate Configuration: Run
rake validate
before submitting
Release Process
# Pre-release checks
$ bundle exec rake release_check
# Update version and release
$ bundle exec rake release
π ΠΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ Π½Π° ΡΡΡΡΠΊΠΎΠΌ ΡΠ·ΡΠΊΠ΅
ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅
Rospatent β ΡΡΠΎ ΠΊΠΎΠΌΠΏΠ»Π΅ΠΊΡΠ½ΡΠΉ Ruby-ΠΊΠ»ΠΈΠ΅Π½Ρ Π΄Π»Ρ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΡ Ρ API ΠΏΠΎΠΈΡΠΊΠ° ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² Π ΠΎΡΠΏΠ°ΡΠ΅Π½ΡΠ°. ΠΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ° ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ ΡΠ΄ΠΎΠ±Π½ΡΠΉ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ Π΄Π»Ρ ΠΏΠΎΠΈΡΠΊΠ°, ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ ΠΈ Π°Π½Π°Π»ΠΈΠ·Π° ΠΏΠ°ΡΠ΅Π½ΡΠ½ΠΎΠΉ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈΠΌ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ, Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠ΅ΠΉ Π·Π°ΠΏΡΠΎΡΠΎΠ² ΠΈ ΠΏΠΎΠ΄ΡΠΎΠ±Π½ΡΠΌ Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ.
β¨ ΠΠ»ΡΡΠ΅Π²ΡΠ΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ
- π ΠΠΎΠ»Π½ΠΎΠ΅ ΠΏΠΎΠΊΡΡΡΠΈΠ΅ API - ΠΏΠΎΠΈΡΠΊ, ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ², ΠΌΠ΅Π΄ΠΈΠ°ΡΠ°ΠΉΠ»Ρ ΠΈ Π΄Π°ΡΠ°ΡΠ΅ΡΡ
- π‘οΈ ΠΠ°Π΄Π΅ΠΆΠ½Π°Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΎΡΠΈΠ±ΠΎΠΊ - ΠΊΠΎΠΌΠΏΠ»Π΅ΠΊΡΠ½ΡΠ΅ ΡΠΈΠΏΡ ΠΎΡΠΈΠ±ΠΎΠΊ Ρ Π΄Π΅ΡΠ°Π»ΡΠ½ΡΠΌ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡΠΎΠΌ
- β‘ ΠΠ½ΡΠ΅Π»Π»Π΅ΠΊΡΡΠ°Π»ΡΠ½ΠΎΠ΅ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ - ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π² ΠΏΠ°ΠΌΡΡΠΈ Ρ TTL ΠΈ LRU ΠΈΡΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ΠΌ
- β ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ Π²Ρ ΠΎΠ΄Π½ΡΡ Π΄Π°Π½Π½ΡΡ - Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠ°Ρ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΡ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠ² Ρ ΠΏΠΎΠ»Π΅Π·Π½ΡΠΌΠΈ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡΠΌΠΈ
- π Π‘ΡΡΡΠΊΡΡΡΠΈΡΠΎΠ²Π°Π½Π½ΠΎΠ΅ Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ - JSON/ΡΠ΅ΠΊΡΡΠΎΠ²ΠΎΠ΅ Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Ρ ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ΠΌ Π·Π°ΠΏΡΠΎΡΠΎΠ²/ΠΎΡΠ²Π΅ΡΠΎΠ²
- π ΠΠ°ΠΊΠ΅ΡΠ½ΡΠ΅ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ - ΠΏΠ°ΡΠ°Π»Π»Π΅Π»ΡΠ½Π°Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²Π° ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ²
- βοΈ ΠΠ΄Π°ΠΏΡΠΈΠ²Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ - ΡΠ°Π·Π»ΠΈΡΠ½ΡΠ΅ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ Π΄Π»Ρ development/staging/production
- π§ͺ ΠΠΎΠΌΠΏΠ»Π΅ΠΊΡΠ½ΠΎΠ΅ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ - ΠΠ±ΡΠΈΡΠ½ΠΎΠ΅ ΠΏΠΎΠΊΡΡΡΠΈΠ΅ ΠΌΠΎΠ΄ΡΠ»ΡΠ½ΡΠΌΠΈ ΠΈ ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΠΎΠ½Π½ΡΠΌΠΈ ΡΠ΅ΡΡΠ°ΠΌΠΈ Ρ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠ΅ΠΉ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ
- π ΠΡΠ»ΠΈΡΠ½Π°Ρ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ - ΠΏΠΎΠ΄ΡΠΎΠ±Π½ΡΠ΅ ΠΏΡΠΈΠΌΠ΅ΡΡ ΠΈ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ API
Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ°
ΠΠΎΠ±Π°Π²ΡΡΠ΅ Π² Π²Π°Ρ Gemfile:
gem 'rospatent'
ΠΠ°ΡΠ΅ΠΌ Π²ΡΠΏΠΎΠ»Π½ΠΈΡΠ΅:
$ bundle install
ΠΠ»ΠΈ ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΠ΅ Π½Π°ΠΏΡΡΠΌΡΡ:
$ gem install rospatent
ΠΡΡΡΡΡΠΉ ΡΡΠ°ΡΡ
# ΠΠΈΠ½ΠΈΠΌΠ°Π»ΡΠ½Π°Ρ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ
Rospatent.configure do |config|
config.token = "Π²Π°Ρ_jwt_ΡΠΎΠΊΠ΅Π½"
end
# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΊΠ»ΠΈΠ΅Π½ΡΠ° ΠΈ ΠΏΠΎΠΈΡΠΊ
client = Rospatent.client
results = client.search(q: "ΡΠ°ΠΊΠ΅ΡΠ°", limit: 10)
puts "ΠΠ°ΠΉΠ΄Π΅Π½ΠΎ #{results.total} ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠ²"
results.hits.each do |hit|
puts "ΠΠ°ΡΠ΅Π½Ρ: #{hit['id']} - #{hit.dig('biblio', 'ru', 'title')}"
end
ΠΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ
ΠΠ°Π·ΠΎΠ²Π°Ρ Π½Π°ΡΡΡΠΎΠΉΠΊΠ°
Rospatent.configure do |config|
# ΠΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ
config.token = "Π²Π°Ρ_jwt_ΡΠΎΠΊΠ΅Π½"
# ΠΠ°ΡΡΡΠΎΠΉΠΊΠΈ API
config.api_url = "https://searchplatform.rospatent.gov.ru/patsearch/v0.2"
config.timeout = 30
config.retry_count = 3
# ΠΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅ (development, staging, production)
config.environment = "production"
end
ΠΡΠΎΠ΄Π²ΠΈΠ½ΡΡΠ°Ρ Π½Π°ΡΡΡΠΎΠΉΠΊΠ°
Rospatent.configure do |config|
config.token = "Π²Π°Ρ_jwt_ΡΠΎΠΊΠ΅Π½"
# ΠΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ (Π²ΠΊΠ»ΡΡΠ΅Π½ΠΎ ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ)
config.cache_enabled = true
config.cache_ttl = 300 # 5 ΠΌΠΈΠ½ΡΡ
config.cache_max_size = 1000 # ΠΠ°ΠΊΡΠΈΠΌΡΠΌ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΠΊΠ΅ΡΠ°
# ΠΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅
config.log_level = :info # :debug, :info, :warn, :error
config.log_requests = true # ΠΠΎΠ³ΠΈΡΠΎΠ²Π°ΡΡ API Π·Π°ΠΏΡΠΎΡΡ
config.log_responses = true # ΠΠΎΠ³ΠΈΡΠΎΠ²Π°ΡΡ API ΠΎΡΠ²Π΅ΡΡ
# ΠΠ°ΡΡΡΠΎΠΉΠΊΠΈ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΡ
config.connection_pool_size = 5
config.connection_keep_alive = true
# Π£ΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠΎΠΊΠ΅Π½Π°ΠΌΠΈ
config.token_expires_at = Time.now + 3600
config.token_refresh_callback = -> { refresh_token! }
end
ΠΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ Π΄Π»Ρ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΡ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠΉ
Gem Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ Π½Π°ΡΡΡΠ°ΠΈΠ²Π°Π΅ΡΡΡ ΠΏΠΎΠ΄ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅ Ρ ΡΠ°Π·ΡΠΌΠ½ΡΠΌΠΈ Π·Π½Π°ΡΠ΅Π½ΠΈΡΠΌΠΈ ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ:
ΠΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ
# ΠΠΏΡΠΈΠΌΠΈΠ·ΠΈΡΠΎΠ²Π°Π½ΠΎ Π΄Π»Ρ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ
Rospatent.configure do |config|
config.environment = "development"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :debug
config.log_requests = true
config.log_responses = true
config.cache_ttl = 60 # ΠΠΎΡΠΎΡΠΊΠΈΠΉ ΠΊΠ΅Ρ Π΄Π»Ρ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ
config.timeout = 10 # ΠΡΡΡΡΡΠ΅ ΡΠ°ΠΉΠΌΠ°ΡΡΡ Π΄Π»Ρ Π±ΡΡΡΡΠΎΠΉ ΠΎΠ±ΡΠ°ΡΠ½ΠΎΠΉ ΡΠ²ΡΠ·ΠΈ
end
ΠΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅ Staging
# ΠΠΏΡΠΈΠΌΠΈΠ·ΠΈΡΠΎΠ²Π°Π½ΠΎ Π΄Π»Ρ staging
Rospatent.configure do |config|
config.environment = "staging"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :info
config.cache_ttl = 300 # ΠΠΎΠ»Π΅Π΅ Π΄Π»ΠΈΡΠ΅Π»ΡΠ½ΡΠΉ ΠΊΠ΅Ρ Π΄Π»Ρ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ
config.timeout = 45 # ΠΠΎΠ»Π΅Π΅ Π΄Π»ΠΈΡΠ΅Π»ΡΠ½ΡΠ΅ ΡΠ°ΠΉΠΌΠ°ΡΡΡ Π΄Π»Ρ Π½Π°Π΄Π΅ΠΆΠ½ΠΎΡΡΠΈ
config.retry_count = 3 # ΠΠΎΠ»ΡΡΠ΅ ΠΏΠΎΠ²ΡΠΎΡΠΎΠ² Π΄Π»Ρ ΡΡΡΠΎΠΉΡΠΈΠ²ΠΎΡΡΠΈ
end
ΠΡΠΎΠ΄Π°ΠΊΡΠ½ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅
# ΠΠΏΡΠΈΠΌΠΈΠ·ΠΈΡΠΎΠ²Π°Π½ΠΎ Π΄Π»Ρ ΠΏΡΠΎΠ΄Π°ΠΊΡΠ½Π°
Rospatent.configure do |config|
config.environment = "production"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :warn
config.cache_ttl = 600 # ΠΠΎΠ»Π΅Π΅ Π΄Π»ΠΈΡΠ΅Π»ΡΠ½ΡΠΉ ΠΊΠ΅Ρ Π΄Π»Ρ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ
config.timeout = 60 # ΠΠΎΠ»Π΅Π΅ Π΄Π»ΠΈΡΠ΅Π»ΡΠ½ΡΠ΅ ΡΠ°ΠΉΠΌΠ°ΡΡΡ Π΄Π»Ρ Π½Π°Π΄Π΅ΠΆΠ½ΠΎΡΡΠΈ
config.retry_count = 5 # ΠΠΎΠ»ΡΡΠ΅ ΠΏΠΎΠ²ΡΠΎΡΠΎΠ² Π΄Π»Ρ ΡΡΡΠΎΠΉΡΠΈΠ²ΠΎΡΡΠΈ
end
ΠΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ ΠΈ ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΡ Ρ Rails
β οΈ ΠΠ ΠΠ’ΠΠ§ΠΠ: ΠΠΎΠ½ΠΈΠΌΠ°Π½ΠΈΠ΅ ΠΏΡΠΈΠΎΡΠΈΡΠ΅ΡΠ° ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠΎ Π΄Π»Ρ ΠΈΠ·Π±Π΅ΠΆΠ°Π½ΠΈΡ ΠΏΡΠΎΠ±Π»Π΅ΠΌ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ, ΠΎΡΠΎΠ±Π΅Π½Π½ΠΎ Π² Rails ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡΡ .
ΠΡΠΈΠΎΡΠΈΡΠ΅Ρ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ ΡΠΎΠΊΠ΅Π½Π°
-
Rails credentials:
Rails.application.credentials.rospatent_token
-
ΠΡΠ½ΠΎΠ²Π½Π°Ρ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ:
ROSPATENT_TOKEN
-
Π£ΡΡΠ°ΡΠ΅Π²ΡΠ°Ρ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ:
ROSPATENT_API_TOKEN
# Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΠ΅ΠΌΡΠΉ ΠΏΠΎΠ΄Ρ
ΠΎΠ΄
export ROSPATENT_TOKEN="your_jwt_token"
# ΠΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠ° ΡΡΡΠ°ΡΠ΅Π²ΡΠ΅Π³ΠΎ ΡΠΎΡΠΌΠ°ΡΠ° (Π²ΡΠ΅ Π΅ΡΠ΅ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ)
export ROSPATENT_API_TOKEN="your_jwt_token"
ΠΡΠΈΠΎΡΠΈΡΠ΅Ρ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ ΡΡΠΎΠ²Π½Ρ Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΡ
# ΠΠ΅ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ ΠΈΠΌΠ΅Π΅Ρ ΠΏΡΠΈΠΎΡΠΈΡΠ΅Ρ Π½Π°Π΄ Π½Π°ΡΡΡΠΎΠΉΠΊΠ°ΠΌΠΈ Rails ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
ENV["ROSPATENT_LOG_LEVEL"].to_sym
else
Rails.env.production? ? :warn : :debug
end
β οΈ Π§Π°ΡΡΠ°Ρ ΠΏΡΠΎΠ±Π»Π΅ΠΌΠ°: Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° ROSPATENT_LOG_LEVEL=debug
Π² ΠΏΡΠΎΠ΄Π°ΠΊΡΠ½Π΅ ΠΏΠ΅ΡΠ΅ΠΎΠΏΡΠ΅Π΄Π΅Π»ΠΈΡ Π»ΠΎΠ³ΠΈΠΊΡ Rails ΠΈ ΠΏΡΠΈΠ²Π΅Π΄ΡΡ ΠΊ ΠΏΠΎΡΠ²Π»Π΅Π½ΠΈΡ DEBUG Π»ΠΎΠ³ΠΎΠ² Π² ΠΏΡΠΎΠ΄Π°ΠΊΡΠ½Π΅!
ΠΠΎΠ»Π½ΡΠΉ ΡΠΏΡΠ°Π²ΠΎΡΠ½ΠΈΠΊ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ
# ΠΡΠ½ΠΎΠ²Π½Π°Ρ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ
ROSPATENT_TOKEN="your_jwt_token" # Π’ΠΎΠΊΠ΅Π½ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ API
ROSPATENT_ENV="production" # ΠΠ΅ΡΠ΅ΠΎΠΏΡΠ΅Π΄Π΅Π»ΠΈΡΡ Rails.env ΠΏΡΠΈ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ
ROSPATENT_API_URL="custom_url" # ΠΠ΅ΡΠ΅ΠΎΠΏΡΠ΅Π΄Π΅Π»ΠΈΡΡ URL API ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ
# ΠΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΡ
ROSPATENT_LOG_LEVEL="warn" # debug, info, warn, error
ROSPATENT_LOG_REQUESTS="false" # ΠΠΎΠ³ΠΈΡΠΎΠ²Π°ΡΡ API Π·Π°ΠΏΡΠΎΡΡ
ROSPATENT_LOG_RESPONSES="false" # ΠΠΎΠ³ΠΈΡΠΎΠ²Π°ΡΡ API ΠΎΡΠ²Π΅ΡΡ
# ΠΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ ΠΊΠ΅ΡΠ°
ROSPATENT_CACHE_ENABLED="true" # ΠΠΊΠ»ΡΡΠΈΡΡ/ΠΎΡΠΊΠ»ΡΡΠΈΡΡ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅
ROSPATENT_CACHE_TTL="300" # TTL ΠΊΠ΅ΡΠ° Π² ΡΠ΅ΠΊΡΠ½Π΄Π°Ρ
ROSPATENT_CACHE_MAX_SIZE="1000" # ΠΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΠΎΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΠΊΠ΅ΡΠ°
# ΠΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΡ
ROSPATENT_TIMEOUT="30" # Π’Π°ΠΉΠΌΠ°ΡΡ Π·Π°ΠΏΡΠΎΡΠ° Π² ΡΠ΅ΠΊΡΠ½Π΄Π°Ρ
ROSPATENT_RETRY_COUNT="3" # ΠΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΠΏΠΎΠ²ΡΠΎΡΠΎΠ²
ROSPATENT_POOL_SIZE="5" # Π Π°Π·ΠΌΠ΅Ρ ΠΏΡΠ»Π° ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΠΉ
ROSPATENT_KEEP_ALIVE="true" # Keep-alive ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΡ
# ΠΠ΅ΡΠ΅ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½ΠΈΡ Π΄Π»Ρ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΡ
ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠΉ
ROSPATENT_DEV_API_URL="dev_url" # URL API Π΄Π»Ρ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ
ROSPATENT_STAGING_API_URL="staging_url" # URL API Π΄Π»Ρ staging
ΠΡΡΡΠΈΠ΅ ΠΏΡΠ°ΠΊΡΠΈΠΊΠΈ Π΄Π»Ρ Rails
-
ΠΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ Rails credentials Π΄Π»Ρ ΡΠΎΠΊΠ΅Π½ΠΎΠ²:
rails credentials:edit # ΠΠΎΠ±Π°Π²ΡΡΠ΅: rospatent_token: your_jwt_token
-
Π£ΡΡΠ°Π½ΠΎΠ²ΠΈΡΠ΅ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ Π΄Π»Ρ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΡ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠΉ:
# config/environments/production.rb ENV["ROSPATENT_LOG_LEVEL"] ||= "warn" ENV["ROSPATENT_CACHE_ENABLED"] ||= "true"
-
ΠΠ·Π±Π΅Π³Π°ΠΉΡΠ΅ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ DEBUG ΡΡΠΎΠ²Π½Ρ Π² ΠΏΡΠΎΠ΄Π°ΠΊΡΠ½Π΅:
# β ΠΠ ΠΠΠΠΠΠ’Π Π’ΠΠ Π² ΠΏΡΠΎΠ΄Π°ΠΊΡΠ½Π΅ export ROSPATENT_LOG_LEVEL=debug # β ΠΠΠΠΠΠ’Π Π’ΠΠ export ROSPATENT_LOG_LEVEL=warn
ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ
# ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΡΠ΅ΠΊΡΡΠ΅ΠΉ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ
errors = Rospatent.validate_configuration
if errors.any?
puts "ΠΡΠΈΠ±ΠΊΠΈ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ:"
errors.each { |error| puts " - #{error}" }
else
puts "ΠΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ Π΄Π΅ΠΉΡΡΠ²ΠΈΡΠ΅Π»ΡΠ½Π° β"
end
ΠΡΠ½ΠΎΠ²Π½ΠΎΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅
ΠΠΎΠΈΡΠΊ ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ²
# ΠΡΠΎΡΡΠΎΠΉ ΠΏΠΎΠΈΡΠΊ
results = client.search(q: "ΡΠΎΠ»Π½Π΅ΡΠ½Π°Ρ Π±Π°ΡΠ°ΡΠ΅Ρ")
# ΠΠΎΠΈΡΠΊ Π½Π° Π΅ΡΡΠ΅ΡΡΠ²Π΅Π½Π½ΠΎΠΌ ΡΠ·ΡΠΊΠ΅
results = client.search(qn: "ΠΊΠΎΠ½ΡΡΡΡΠΊΡΠΈΡ ΡΠ°ΠΊΠ΅ΡΠ½ΠΎΠ³ΠΎ Π΄Π²ΠΈΠ³Π°ΡΠ΅Π»Ρ")
# Π Π°ΡΡΠΈΡΠ΅Π½Π½ΡΠΉ ΠΏΠΎΠΈΡΠΊ Ρ Π²ΡΠ΅ΠΌΠΈ ΠΎΠΏΡΠΈΡΠΌΠΈ
results = client.search(
q: "ΠΈΡΠΊΡΡΡΡΠ²Π΅Π½Π½ΡΠΉ ΠΈΠ½ΡΠ΅Π»Π»Π΅ΠΊΡ AND Π½Π΅ΠΉΡΠΎΠ½Π½Π°Ρ ΡΠ΅ΡΡ",
limit: 50,
offset: 100,
datasets: ["ru_since_1994"],
filter: {
"classification.ipc_group": { "values": ["G06N"] },
"application.filing_date": { "range": { "gte": "20200101" } }
},
sort: "publication_date:desc", # ΡΠΎ ΠΆΠ΅ ΡΠ°ΠΌΠΎΠ΅, ΡΡΠΎ 'sort: :pub_date'; ΡΠΌ. Π²Π°ΡΠΈΠ°Π½ΡΡ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠ² ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΠΈ Π² Search#validate_sort_parameter
group_by: "family:dwpi", # ΠΡΡΠΏΠΏΠΈΡΠΎΠ²ΠΊΠ° ΠΏΠΎ ΡΠ΅ΠΌΠ΅ΠΉΡΡΠ²Π°ΠΌ: "family:docdb" ΠΈΠ»ΠΈ "family:dwpi"
include_facets: true, # Boolean: true/false (Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΠΊΠΎΠ½Π²Π΅ΡΡΠΈΡΡΠ΅ΡΡΡ Π² 1/0 Π΄Π»Ρ API)
pre_tag: "<mark>", # ΠΠ±Π° ΡΠ΅Π³Π° Π΄ΠΎΠ»ΠΆΠ½Ρ Π±ΡΡΡ ΡΠΊΠ°Π·Π°Π½Ρ Π²ΠΌΠ΅ΡΡΠ΅
post_tag: "</mark>", # ΠΠΎΠ³ΡΡ Π±ΡΡΡ ΡΡΡΠΎΠΊΠ°ΠΌΠΈ ΠΈΠ»ΠΈ ΠΌΠ°ΡΡΠΈΠ²Π°ΠΌΠΈ
highlight: { # ΠΡΠΎΠ΄Π²ΠΈΠ½ΡΡΠ°Ρ Π½Π°ΡΡΡΠΎΠΉΠΊΠ° ΠΏΠΎΠ΄ΡΠ²Π΅ΡΠΊΠΈ (Π½Π΅Π·Π°Π²ΠΈΡΠΈΠΌΠΎ ΠΎΡ ΡΠ΅Π³ΠΎΠ²)
"profiles" => [
{ "q" => "Π½Π΅ΠΉΡΠΎΠ½Π½Π°Ρ ΡΠ΅ΡΡ", "pre_tag" => "<b>", "post_tag" => "</b>" },
"_searchquery_"
]
}
)
# ΠΡΠΎΡΡΠ°Ρ ΠΏΠΎΠ΄ΡΠ²Π΅ΡΠΊΠ° Ρ ΡΠ΅Π³Π°ΠΌΠΈ (ΠΎΠ±Π° ΡΠ΅Π³Π° ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½Ρ)
results = client.search(
q: "ΡΠ°ΠΊΠ΅ΡΠ°",
pre_tag: "<mark>",
post_tag: "</mark>"
)
# ΠΠ½ΠΎΠ³ΠΎΡΠ²Π΅ΡΠ½Π°Ρ ΠΏΠΎΠ΄ΡΠ²Π΅ΡΠΊΠ° Ρ ΠΌΠ°ΡΡΠΈΠ²Π°ΠΌΠΈ
results = client.search(
q: "ΠΊΠΎΡΠΌΠΈΡΠ΅ΡΠΊΠ°Ρ ΡΠ°ΠΊΠ΅ΡΠ°",
pre_tag: ["<b>", "<i>"], # Π¦ΠΈΠΊΠ»ΠΈΡΠ΅ΡΠΊΠ°Ρ ΠΏΠΎΠ΄ΡΠ²Π΅ΡΠΊΠ°
post_tag: ["</b>", "</i>"] # ΡΠ°Π·Π½ΡΠΌΠΈ ΡΠ΅Π³Π°ΠΌΠΈ
)
# ΠΡΠΎΠ΄Π²ΠΈΠ½ΡΡΠ°Ρ ΠΏΠΎΠ΄ΡΠ²Π΅ΡΠΊΠ° Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ ΠΏΡΠΎΡΠΈΠ»Π΅ΠΉ (Π½Π΅Π·Π°Π²ΠΈΡΠΈΠΌΠΎ ΠΎΡ pre_tag/post_tag)
results = client.search(
q: "ΡΠ°ΠΊΠ΅ΡΠ°",
highlight: {
"profiles" => [
{ "q" => "ΠΊΠΎΡΠΌΠΈΡΠ΅ΡΠΊΠ°Ρ", "pre_tag" => "<b>", "post_tag" => "</b>" },
"_searchquery_" # Π‘ΡΡΠ»ΠΊΠ° Π½Π° ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ ΠΏΠΎΠ΄ΡΠ²Π΅ΡΠΊΠΈ ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠ³ΠΎ ΠΏΠΎΠΈΡΠΊΠΎΠ²ΠΎΠ³ΠΎ Π·Π°ΠΏΡΠΎΡΠ°
]
}
)
# ΠΡΡΠΏΠΏΠΈΡΠΎΠ²ΠΊΠ° ΠΏΠΎ ΡΠ΅ΠΌΠ΅ΠΉΡΡΠ²Π°ΠΌ ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² (Π³ΡΡΠΏΠΏΠΈΡΡΠ΅Ρ ΠΏΠ°ΡΠ΅Π½ΡΡ ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΈΠ·ΠΎΠ±ΡΠ΅ΡΠ΅Π½ΠΈΡ)
results = client.search(
q: "ΡΠ°ΠΊΠ΅ΡΠ°",
group_by: "family:docdb", # ΠΡΠΎΡΡΡΠ΅ ΡΠ΅ΠΌΠ΅ΠΉΡΡΠ²Π° ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² DOCDB
datasets: ["dwpi"],
limit: 10
)
results = client.search(
q: "ΡΠ°ΠΊΠ΅ΡΠ°",
group_by: "family:dwpi", # ΠΡΠΎΡΡΡΠ΅ ΡΠ΅ΠΌΠ΅ΠΉΡΡΠ²Π° ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² DWPI
datasets: ["dwpi"],
limit: 10
)
# ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠ²
puts "ΠΠ°ΠΉΠ΄Π΅Π½ΠΎ: #{results.total} ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² (Π΄ΠΎΡΡΡΠΏΠ½ΠΎ #{results.available})"
puts "ΠΠΎΠΊΠ°Π·Π°Π½ΠΎ: #{results.count}"
results.hits.each do |hit|
puts "ID: #{hit['id']}"
puts "ΠΠ°Π·Π²Π°Π½ΠΈΠ΅: #{hit.dig('biblio', 'ru', 'title')}"
puts "ΠΠ°ΡΠ°: #{hit.dig('common', 'publication_date')}"
puts "ΠΠΠ: #{hit.dig('common', 'classification', 'ipc')&.map {|c| c['fullname']}&.join('; ')}"
puts "---"
end
Π Π°ΡΡΠΈΡΠ΅Π½Π½ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ ΡΠΈΠ»ΡΡΡΠ°ΡΠΈΠΈ
ΠΠ°ΡΠ°ΠΌΠ΅ΡΡ filter
ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅Ρ ΡΠ»ΠΎΠΆΠ½ΡΡ ΡΠΈΠ»ΡΡΡΠ°ΡΠΈΡ Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠΉ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠ΅ΠΉ ΠΈ ΠΏΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ ΡΠΎΡΠΌΠ°ΡΠΎΠ²:
Π‘ΠΏΠΈΡΠΎΡΠ½ΡΠ΅ ΡΠΈΠ»ΡΡΡΡ (ΡΡΠ΅Π±ΡΡΡ ΡΠΎΡΠΌΠ°Ρ {"values": [...]}
)
# Π€ΠΈΠ»ΡΡΡΡ ΠΏΠΎ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ
results = client.search(
q: "ΠΈΡΠΊΡΡΡΡΠ²Π΅Π½Π½ΡΠΉ ΠΈΠ½ΡΠ΅Π»Π»Π΅ΠΊΡ",
filter: {
"classification.ipc_group": { "values": ["G06N", "G06F"] },
"classification.cpc_group": { "values": ["G06N3/", "G06N20/"] }
}
)
# Π€ΠΈΠ»ΡΡΡΡ ΠΏΠΎ Π°Π²ΡΠΎΡΠ°ΠΌ ΠΈ ΠΏΠ°ΡΠ΅Π½ΡΠΎΠΎΠ±Π»Π°Π΄Π°ΡΠ΅Π»ΡΠΌ
results = client.search(
q: "ΠΈΠ·ΠΎΠ±ΡΠ΅ΡΠ΅Π½ΠΈΠ΅",
filter: {
"authors": { "values": ["ΠΠ²Π°Π½ΠΎΠ² Π.Π.", "ΠΠ΅ΡΡΠΎΠ² Π.Π."] },
"patent_holders": { "values": ["ΠΠΠ ΠΠΎΠΌΠΏΠ°Π½ΠΈΡ"] },
"country": { "values": ["RU", "US"] },
"kind": { "values": ["A1", "U1"] }
}
)
# Π€ΠΈΠ»ΡΡΡΡ ΠΏΠΎ ID Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠΎΠ²
results = client.search(
q: "ΡΡΡΡΠΎΠΉΡΡΠ²ΠΎ",
filter: {
"ids": { "values": ["RU134694U1_20131120", "RU2358138C1_20090610"] }
}
)
ΠΠΈΠ°ΠΏΠ°Π·ΠΎΠ½Π½ΡΠ΅ ΡΠΈΠ»ΡΡΡΡ ΠΏΠΎ Π΄Π°ΡΠ°ΠΌ (ΡΡΠ΅Π±ΡΡΡ ΡΠΎΡΠΌΠ°Ρ {"range": {"operator": "YYYYMMDD"}}
)
# ΠΠ²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ΅ ΠΏΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ ΡΠΎΡΠΌΠ°ΡΠ° Π΄Π°Ρ
results = client.search(
q: "ΠΈΠ½Π½ΠΎΠ²Π°ΡΠΈΡ",
filter: {
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
"application.filing_date": { "range": { "gte": "2019-06-15" } }
}
)
# ΠΡΡΠΌΠΎΠΉ ΡΠΎΡΠΌΠ°Ρ API (YYYYMMDD)
results = client.search(
q: "ΡΠ΅Ρ
Π½ΠΎΠ»ΠΎΠ³ΠΈΡ",
filter: {
"date_published": { "range": { "gte": "20200101", "lt": "20240101" } }
}
)
# ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠ² Date (Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΠΊΠΎΠ½Π²Π΅ΡΡΠΈΡΡΡΡΡΡ)
results = client.search(
q: "ΠΏΠ°ΡΠ΅Π½Ρ",
filter: {
"application.filing_date": {
"range": {
"gte": Date.new(2020, 1, 1),
"lte": Date.new(2023, 12, 31)
}
}
}
)
ΠΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅ΠΌΡΠ΅ ΠΎΠΏΠ΅ΡΠ°ΡΠΎΡΡ Π΄Π°Ρ: gt
, gte
, lt
, lte
ΠΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ ΡΠΎΡΠΌΠ°ΡΠ° Π΄Π°Ρ:
-
"2020-01-01"
β"20200101"
-
Date.new(2020, 1, 1)
β"20200101"
-
"20200101"
β"20200101"
(Π±Π΅Π· ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ)
Π‘Π»ΠΎΠΆΠ½ΡΠ΅ ΡΠΎΡΡΠ°Π²Π½ΡΠ΅ ΡΠΈΠ»ΡΡΡΡ
# ΠΠΎΠΌΠΏΠ»Π΅ΠΊΡΠ½ΡΠΉ ΠΏΡΠΈΠΌΠ΅Ρ ΡΠΈΠ»ΡΡΡΠ°
results = client.search(
q: "ΠΌΠ°ΡΠΈΠ½Π½ΠΎΠ΅ ΠΎΠ±ΡΡΠ΅Π½ΠΈΠ΅",
filter: {
# Π‘ΠΏΠΈΡΠΎΡΠ½ΡΠ΅ ΡΠΈΠ»ΡΡΡΡ
"classification.ipc_group": { "values": ["G06N", "G06F"] },
"country": { "values": ["RU", "US", "CN"] },
"kind": { "values": ["A1", "A2"] },
"authors": { "values": ["ΠΠ²Π°Π½ΠΎΠ² Π.Π."] },
# ΠΠΈΠ°ΠΏΠ°Π·ΠΎΠ½Π½ΡΠ΅ ΡΠΈΠ»ΡΡΡΡ ΠΏΠΎ Π΄Π°ΡΠ°ΠΌ
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
"application.filing_date": { "range": { "gte": "2019-01-01" } }
},
limit: 50
)
ΠΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅ΠΌΡΠ΅ ΠΏΠΎΠ»Ρ ΡΠΈΠ»ΡΡΡΠΎΠ²:
Π‘ΠΏΠΈΡΠΎΡΠ½ΡΠ΅ ΡΠΈΠ»ΡΡΡΡ (ΡΡΠ΅Π±ΡΡΡ ΡΠΎΡΠΌΠ°Ρ {"values": [...]}
)::
-
authors
- ΠΠ²ΡΠΎΡΡ ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² -
patent_holders
- ΠΠ°ΡΠ΅Π½ΡΠΎΠΎΠ±Π»Π°Π΄Π°ΡΠ΅Π»ΠΈ/ΠΏΡΠ°Π²ΠΎΠΏΡΠ΅Π΅ΠΌΠ½ΠΈΠΊΠΈ -
country
- ΠΠΎΠ΄Ρ ΡΡΡΠ°Π½ -
kind
- Π’ΠΈΠΏΡ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠΎΠ² -
ids
- ΠΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΠ΅ ID Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠΎΠ² -
classification.ipc*
- ΠΠΎΠ΄Ρ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ IPC -
classification.cpc*
- ΠΠΎΠ΄Ρ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ CPC
Π€ΠΈΠ»ΡΡΡΡ ΠΏΠΎ Π΄Π°ΡΠ°ΠΌ (ΡΡΠ΅Π±ΡΡΡ ΡΠΎΡΠΌΠ°Ρ {"range": {"operator": "YYYYMMDD"}}
)::
-
date_published
- ΠΠ°ΡΠ° ΠΏΡΠ±Π»ΠΈΠΊΠ°ΡΠΈΠΈ -
application.filing_date
- ΠΠ°ΡΠ° ΠΏΠΎΠ΄Π°ΡΠΈ Π·Π°ΡΠ²ΠΊΠΈ
ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΡΠΈΠ»ΡΡΡΠΎΠ²:
- β ΠΠ²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠ°Ρ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΡ Π½Π°Π·Π²Π°Π½ΠΈΠΉ ΠΏΠΎΠ»Π΅ΠΉ
- β ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΡΡΡΡΠΊΡΡΡΡ (ΡΠΏΠΈΡΠΎΡΠ½ΡΠΉ vs Π΄ΠΈΠ°ΠΏΠ°Π·ΠΎΠ½Π½ΡΠΉ ΡΠΎΡΠΌΠ°Ρ)
- β ΠΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΡ ΡΠΎΡΠΌΠ°ΡΠ° Π΄Π°Ρ
- β ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΎΡΠΎΠ² Π΄Π»Ρ Π΄ΠΈΠ°ΠΏΠ°Π·ΠΎΠ½ΠΎΠ²
- β ΠΠΎΠ»Π΅Π·Π½ΡΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ°Ρ Π΄Π»Ρ Π½Π΅Π²Π΅ΡΠ½ΡΡ ΡΠΈΠ»ΡΡΡΠΎΠ²
# ΠΡΠΈ ΠΏΡΠΈΠΌΠ΅ΡΡ Π²ΡΠ·ΠΎΠ²ΡΡ ValidationError Ρ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΠΌΠΈ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡΠΌΠΈ:
client.search(
q: "ΡΠ΅ΡΡ",
filter: { "invalid_field": { "values": ["ΡΠ΅ΡΡ"] } }
)
# ΠΡΠΈΠ±ΠΊΠ°: "Invalid filter field: invalid_field"
client.search(
q: "ΡΠ΅ΡΡ",
filter: { "authors": ["ΠΏΡΡΠΌΠΎΠΉ", "ΠΌΠ°ΡΡΠΈΠ²"] } # ΠΡΡΡΡΡΡΠ²ΡΠ΅Ρ ΠΎΠ±Π΅ΡΡΠΊΠ° {"values": [...]}
)
# ΠΡΠΈΠ±ΠΊΠ°: "Filter 'authors' requires format: {\"values\": [...]}"
client.search(
q: "ΡΠ΅ΡΡ",
filter: { "date_published": { "range": { "invalid_op": "20200101" } } }
)
# ΠΡΠΈΠ±ΠΊΠ°: "Invalid range operator: invalid_op. Supported: gt, gte, lt, lte"
ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠΎΠ² ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ²
# ΠΠΎ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡΡ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°
patent = client.patent("RU134694U1_20131120")
# ΠΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡΠ°
patent_doc = client.patent_by_components(
"RU", # country_code
"134694", # number
"U1", # doc_type
Date.new(2013, 11, 20) # date (String ΠΈΠ»ΠΈ ΠΎΠ±ΡΠ΅ΠΊΡ Date)
)
# ΠΠΎΡΡΡΠΏ ΠΊ Π΄Π°Π½Π½ΡΠΌ ΠΏΠ°ΡΠ΅Π½ΡΠ°
title = patent_doc.dig('biblio', 'ru', 'title')
abstract = patent_doc.dig('abstract', 'ru')
inventors = patent_doc.dig('biblio', 'ru', 'inventor')
ΠΠ°ΡΡΠΈΠ½Π³ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΠΎΠ³ΠΎ ΠΏΠ°ΡΠ΅Π½ΡΠ°
ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΡΠΈΡΡΠΎΠ³ΠΎ ΡΠ΅ΠΊΡΡΠ° ΠΈΠ»ΠΈ ΡΡΡΡΠΊΡΡΡΠΈΡΠΎΠ²Π°Π½Π½ΠΎΠ³ΠΎ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΠΎΠ³ΠΎ:
# ΠΠ°ΡΡΠΈΠ½Π³ Π°Π½Π½ΠΎΡΠ°ΡΠΈΠΈ
abstract_text = client.parse_abstract(patent_doc)
abstract_html = client.parse_abstract(patent_doc, format: :html)
abstract_ru = client.parse_abstract(patent_doc, language: "ru")
# ΠΠ°ΡΡΠΈΠ½Π³ ΠΎΠΏΠΈΡΠ°Π½ΠΈΡ
description_text = client.parse_description(patent_doc)
description_html = client.parse_description(patent_doc, format: :html)
# ΠΠ°ΡΡΠΈΠ½Π³ ΠΎΠΏΠΈΡΠ°Π½ΠΈΡ Ρ ΡΠ°Π·Π±ΠΈΠ²ΠΊΠΎΠΉ Π½Π° ΡΠ΅ΠΊΡΠΈΠΈ
sections = client.parse_description(patent_doc, format: :sections)
sections.each do |section|
puts "Π‘Π΅ΠΊΡΠΈΡ #{section[:number]}: #{section[:content]}"
end
ΠΠΎΠΈΡΠΊ ΠΏΠΎΡ ΠΎΠΆΠΈΡ ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ²
# ΠΠΎΠΈΡΠΊ ΠΏΠΎΡ
ΠΎΠΆΠΈΡ
ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² ΠΏΠΎ ID
similar = client.similar_patents_by_id("RU134694U1_20131120", count: 50)
# ΠΠΎΠΈΡΠΊ ΠΏΠΎΡ
ΠΎΠΆΠΈΡ
ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² ΠΏΠΎ ΠΎΠΏΠΈΡΠ°Π½ΠΈΡ ΡΠ΅ΠΊΡΡΠ°
similar = client.similar_patents_by_text(
"Π Π°ΠΊΠ΅ΡΠ½ΡΠΉ Π΄Π²ΠΈΠ³Π°ΡΠ΅Π»Ρ Ρ ΡΠ»ΡΡΡΠ΅Π½Π½ΠΎΠΉ ΡΡΠ³ΠΎΠΉ ...", # ΠΌΠΈΠ½ΠΈΠΌΡΠΌ 50 ΡΠ»ΠΎΠ² Π² Π·Π°ΠΏΡΠΎΡΠ΅
count: 25
)
# ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΏΠΎΡ
ΠΎΠΆΠΈΡ
ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ²
similar["data"]&.each do |patent|
puts "ΠΠΎΡ
ΠΎΠΆΠΈΠΉ: #{patent['id']} (ΠΎΡΠ΅Π½ΠΊΠ°: #{patent['similarity']} (#{patent['similarity_norm']}))"
end
ΠΠΎΠΈΡΠΊ ΠΏΠΎ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΎΡΠ°ΠΌ
ΠΠΎΠΈΡΠΊ Π² ΡΠΈΡΡΠ΅ΠΌΠ°Ρ ΠΏΠ°ΡΠ΅Π½ΡΠ½ΠΎΠΉ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ (IPC ΠΈ CPC) ΠΈ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΡΠΎΠ±Π½ΠΎΠΉ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ ΠΎ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΎΠ½Π½ΡΡ ΠΊΠΎΠ΄Π°Ρ :
# ΠΠΎΠΈΡΠΊ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΎΠ½Π½ΡΡ
ΠΊΠΎΠ΄ΠΎΠ², ΡΠ²ΡΠ·Π°Π½Π½ΡΡ
Ρ ΡΠ°ΠΊΠ΅ΡΠ°ΠΌΠΈ Π² IPC
ipc_results = client.classification_search("ipc", query: "ΡΠ°ΠΊΠ΅ΡΠ°", lang: "ru")
puts "ΠΠ°ΠΉΠ΄Π΅Π½ΠΎ #{ipc_results.size} ΠΊΠΎΠ΄ΠΎΠ² IPC"
ipc_results&.each do |result|
puts "#{result['Code']}: #{result['Description']}"
end
# ΠΠΎΠΈΡΠΊ ΠΊΠΎΠ΄ΠΎΠ², ΡΠ²ΡΠ·Π°Π½Π½ΡΡ
Ρ ΡΠ°ΠΊΠ΅ΡΠ°ΠΌΠΈ Π² CPC Π½Π° Π°Π½Π³Π»ΠΈΠΉΡΠΊΠΎΠΌ
cpc_results = client.classification_search("cpc", query: "rocket", lang: "en")
# ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΡΠΎΠ±Π½ΠΎΠΉ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ ΠΎ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΎΠΌ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΎΠ½Π½ΠΎΠΌ ΠΊΠΎΠ΄Π΅
code, info = client.classification_code("ipc", code: "F02K9/00", lang: "ru")&.first
puts "ΠΠΎΠ΄: #{code}"
puts "ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅: #{info&.first['Description']}"
puts "ΠΠ΅ΡΠ°ΡΡ
ΠΈΡ: #{info&.map{|level| level['Code']}&.join(' β ')}"
# ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ ΠΎ ΠΊΠΎΠ΄Π΅ CPC Π½Π° Π°Π½Π³Π»ΠΈΠΉΡΠΊΠΎΠΌ
cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")
ΠΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅ΠΌΡΠ΅ ΡΠΈΡΡΠ΅ΠΌΡ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ:
-
"ipc"
- ΠΠ΅ΠΆΠ΄ΡΠ½Π°ΡΠΎΠ΄Π½Π°Ρ ΠΏΠ°ΡΠ΅Π½ΡΠ½Π°Ρ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ (ΠΠΠ) -
"cpc"
- Π‘ΠΎΠ²ΠΌΠ΅ΡΡΠ½Π°Ρ ΠΏΠ°ΡΠ΅Π½ΡΠ½Π°Ρ ΠΊΠ»Π°ΡΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ (Π‘ΠΠ)
ΠΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅ΠΌΡΠ΅ ΡΠ·ΡΠΊΠΈ:
-
"ru"
- Π ΡΡΡΠΊΠΈΠΉ -
"en"
- ΠΠ½Π³Π»ΠΈΠΉΡΠΊΠΈΠΉ
Π‘ΠΏΠΈΡΠΎΠΊ Π΄ΠΎΡΡΡΠΏΠ½ΡΡ Π΄Π°ΡΠ°ΡΠ΅ΡΠΎΠ²
datasets = client.datasets_tree
datasets.each do |category|
puts "ΠΠ°ΡΠ΅Π³ΠΎΡΠΈΡ: #{category['name_ru']}"
category.children.each do |dataset|
puts " #{dataset['id']}: #{dataset['name_ru']}"
end
end
ΠΠ΅Π΄ΠΈΠ°ΡΠ°ΠΉΠ»Ρ ΠΈ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΡ
# β
Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΠ΅ΡΡΡ: Π‘ΠΊΠ°ΡΠΈΠ²Π°Π½ΠΈΠ΅ PDF ΠΏΠ°ΡΠ΅Π½ΡΠ° Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ Π³Π΅Π½Π΅ΡΠΈΡΡΠ΅ΠΌΡΠΌ ΠΈΠΌΠ΅Π½Π΅ΠΌ ΡΠ°ΠΉΠ»Π°
# ΠΠ²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ ΠΎΡΡΠΎΡΠΌΠ°ΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ Π½ΠΎΠΌΠ΅Ρ ΠΏΡΠ±Π»ΠΈΠΊΠ°ΡΠΈΠΈ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, "0000134694.pdf")
pdf_data = client.patent_media(
"National", # collection_id
"RU", # country_code
"U1", # doc_type
"2013/11/20", # pub_date
"134694" # pub_number (ΠΈΠΌΡ ΡΠ°ΠΉΠ»Π° Π³Π΅Π½Π΅ΡΠΈΡΡΠ΅ΡΡΡ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ)
)
client.save_binary_file(pdf_data, "patent.pdf")
# β
ΠΠ»ΡΡΠ΅ΡΠ½Π°ΡΠΈΠ²Π°: Π‘ΠΊΠ°ΡΠΈΠ²Π°Π½ΠΈΠ΅ Ρ ΡΠ²Π½ΡΠΌ ΡΠΊΠ°Π·Π°Π½ΠΈΠ΅ΠΌ ΠΈΠΌΠ΅Π½ΠΈ ΡΠ°ΠΉΠ»Π°
pdf_data = client.patent_media(
"National", # collection_id
"RU", # country_code
"U1", # doc_type
"2013/11/20", # pub_date
"134694", # pub_number
"document.pdf" # ΡΠ²Π½ΠΎΠ΅ ΠΈΠΌΡ ΡΠ°ΠΉΠ»Π°
)
client.save_binary_file(pdf_data, "patent_explicit.pdf")
# β
Π£ΠΏΡΠΎΡΠ΅Π½Π½ΡΠΉ ΠΌΠ΅ΡΠΎΠ΄ Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ ID ΠΏΠ°ΡΠ΅Π½ΡΠ° (Π°Π²ΡΠΎΠ³Π΅Π½Π΅ΡΠΈΡΡΠ΅ΠΌΠΎΠ΅ ΠΈΠΌΡ)
pdf_data = client.patent_media_by_id(
"RU134694U1_20131120",
"National" # ΠΈΠΌΡ ΡΠ°ΠΉΠ»Π° Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ Π³Π΅Π½Π΅ΡΠΈΡΡΠ΅ΡΡΡ ΠΊΠ°ΠΊ "0000134694.pdf"
)
client.save_binary_file(pdf_data, "patent_by_id.pdf")
# β
ΠΠ»ΠΈ Ρ ΡΠ²Π½ΡΠΌ ΠΈΠΌΠ΅Π½Π΅ΠΌ ΡΠ°ΠΉΠ»Π° Π΄Π»Ρ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΡ
ΡΠ°ΠΉΠ»ΠΎΠ²
image_data = client.patent_media_by_id(
"RU134694U1_20131120",
"National",
"image.png" # ΡΠ²Π½ΠΎΠ΅ ΠΈΠΌΡ ΡΠ°ΠΉΠ»Π° Π΄Π»Ρ ΡΠ°ΠΉΠ»ΠΎΠ² Π½Π΅-PDF
)
client.save_binary_file(image_data, "patent_image.png")
# β
ΠΠ°ΡΠΈΠ°Π½ΡΡ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΠ³ΠΎ ΡΠΎΡ
ΡΠ°Π½Π΅Π½ΠΈΡ ΡΠ°ΠΉΠ»ΠΎΠ²:
File.binwrite("patent.pdf", pdf_data) # Π ΡΡΠ½Π°Ρ Π±ΠΈΠ½Π°ΡΠ½Π°Ρ Π·Π°ΠΏΠΈΡΡ
# β ΠΠ·Π±Π΅Π³Π°ΠΉΡΠ΅: File.write ΠΌΠΎΠΆΠ΅Ρ Π²ΡΠ·Π²Π°ΡΡ ΠΎΡΠΈΠ±ΠΊΠΈ ΠΊΠΎΠ΄ΠΈΡΠΎΠ²ΠΊΠΈ Ρ Π±ΠΈΠ½Π°ΡΠ½ΡΠΌΠΈ Π΄Π°Π½Π½ΡΠΌΠΈ
# File.write("patent.pdf", pdf_data) # ΠΡΠΎ ΠΌΠΎΠΆΠ΅Ρ Π½Π΅ ΡΡΠ°Π±ΠΎΡΠ°ΡΡ!
Π Π°ΡΡΠΈΡΠ΅Π½Π½ΡΠ΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ
ΠΠ°ΠΊΠ΅ΡΠ½ΡΠ΅ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ
ΠΡΡΠ΅ΠΊΡΠΈΠ²Π½Π°Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²Π° ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² Ρ ΠΏΠ°ΡΠ°Π»Π»Π΅Π»ΡΠ½ΡΠΌΠΈ Π·Π°ΠΏΡΠΎΡΠ°ΠΌΠΈ:
document_ids = ["RU134694U1_20131120", "RU2358138C1_20090610", "RU2756123C1_20210927"]
# ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² ΠΏΠ°ΠΊΠ΅ΡΠ°ΠΌΠΈ
client.batch_patents(document_ids, batch_size: 5) do |patent_doc|
if patent_doc[:error]
puts "ΠΡΠΈΠ±ΠΊΠ° Π΄Π»Ρ #{patent_doc[:document_id]}: #{patent_doc[:error]}"
else
puts "ΠΠΎΠ»ΡΡΠ΅Π½ ΠΏΠ°ΡΠ΅Π½Ρ: #{patent_doc['id']}"
# ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ° ΠΏΠ°ΡΠ΅Π½ΡΠ°
end
end
# ΠΠ»ΠΈ ΡΠ±ΠΎΡ Π²ΡΠ΅Ρ
ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠ²
patents = []
client.batch_patents(document_ids) { |doc| patents << doc }
ΠΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅
ΠΠ²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ΅ ΠΈΠ½ΡΠ΅Π»Π»Π΅ΠΊΡΡΠ°Π»ΡΠ½ΠΎΠ΅ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΡΠ»ΡΡΡΠ°Π΅Ρ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΡ:
# ΠΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ΅ ΠΈ ΠΏΡΠΎΠ·ΡΠ°ΡΠ½ΠΎΠ΅
patent1 = client.patent("RU134694U1_20131120") # API Π²ΡΠ·ΠΎΠ²
patent2 = client.patent("RU134694U1_20131120") # ΠΠ΅ΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ ΡΠ΅Π·ΡΠ»ΡΡΠ°Ρ
# ΠΡΠΎΠ²Π΅ΡΠΊΠ° ΡΡΠ°ΡΠΈΡΡΠΈΠΊΠΈ ΠΊΠ΅ΡΠ°
stats = client.statistics
puts "ΠΡΠΎΡΠ΅Π½Ρ ΠΏΠΎΠΏΠ°Π΄Π°Π½ΠΈΠΉ Π² ΠΊΠ΅Ρ: #{stats[:cache_stats][:hit_rate_percent]}%"
puts "ΠΡΠ΅Π³ΠΎ Π·Π°ΠΏΡΠΎΡΠΎΠ²: #{stats[:requests_made]}"
puts "Π‘ΡΠ΅Π΄Π½Π΅Π΅ Π²ΡΠ΅ΠΌΡ ΠΎΡΠ²Π΅ΡΠ°: #{stats[:average_request_time]}Ρ"
# ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΎΠ±ΡΠ΅Π³ΠΎ ΠΊΠ΅ΡΠ° ΠΌΠ΅ΠΆΠ΄Ρ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°ΠΌΠΈ
shared_cache = Rospatent.shared_cache
client1 = Rospatent.client(cache: shared_cache)
client2 = Rospatent.client(cache: shared_cache)
# Π ΡΡΠ½ΠΎΠ΅ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠ΅ΡΠ΅ΠΌ
shared_cache.clear # ΠΡΠΈΡΡΠΈΡΡ Π²ΡΠ΅ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅
expired_count = shared_cache.cleanup_expired # Π£Π΄Π°Π»ΠΈΡΡ ΠΈΡΡΠ΅ΠΊΡΠΈΠ΅ Π·Π°ΠΏΠΈΡΠΈ
cache_stats = shared_cache.statistics # ΠΠΎΠ»ΡΡΠΈΡΡ Π΄Π΅ΡΠ°Π»ΡΠ½ΡΡ ΡΡΠ°ΡΠΈΡΡΠΈΠΊΡ ΠΊΠ΅ΡΠ°
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΡ
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° Π΄Π΅ΡΠ°Π»ΡΠ½ΠΎΠ³ΠΎ Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΡ Π΄Π»Ρ ΠΌΠΎΠ½ΠΈΡΠΎΡΠΈΠ½Π³Π° ΠΈ ΠΎΡΠ»Π°Π΄ΠΊΠΈ:
# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΠΎΠ±ΡΡΠ²Π΅Π½Π½ΠΎΠ³ΠΎ Π»ΠΎΠ³Π³Π΅ΡΠ°
logger = Rospatent::Logger.new(
output: Rails.logger, # ΠΠ»ΠΈ Π»ΡΠ±ΠΎΠΉ ΠΎΠ±ΡΠ΅ΠΊΡ IO
level: :info,
formatter: :json # :json ΠΈΠ»ΠΈ :text
)
client = Rospatent.client(logger: logger)
# ΠΠΎΠ³ΠΈ Π²ΠΊΠ»ΡΡΠ°ΡΡ:
# - API Π·Π°ΠΏΡΠΎΡΡ/ΠΎΡΠ²Π΅ΡΡ Ρ Π²ΡΠ΅ΠΌΠ΅Π½Π½ΡΠΌΠΈ ΠΌΠ΅ΡΠΊΠ°ΠΌΠΈ
# - ΠΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΠΊΠ΅ΡΠ° (ΠΏΠΎΠΏΠ°Π΄Π°Π½ΠΈΡ/ΠΏΡΠΎΠΌΠ°Ρ
ΠΈ)
# - ΠΠ΅ΡΠ°Π»ΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ Ρ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡΠΎΠΌ
# - ΠΠ΅ΡΡΠΈΠΊΠΈ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ
# ΠΠΎΡΡΡΠΏ ΠΊ ΠΎΠ±ΡΠ΅ΠΌΡ Π»ΠΎΠ³Π³Π΅ΡΡ
shared_logger = Rospatent.shared_logger(level: :debug)
ΠΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ:
- ΠΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ
Rails.logger
, ΡΠΎΡΠΌΠ°ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΊΠΎΠ½ΡΡΠΎΠ»ΠΈΡΡΠ΅ΡΡΡ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠ΅ΠΉ Rails, ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡformatter
ΠΈΠ³Π½ΠΎΡΠΈΡΡΠ΅ΡΡΡ - ΠΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ IO ΠΎΠ±ΡΠ΅ΠΊΡΠ°, ΡΠΎΡΠΌΠ°Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅ΡΡΡ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠΌ
formatter
ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΎΡΠΈΠ±ΠΎΠΊ
ΠΠΎΠΌΠΏΠ»Π΅ΠΊΡΠ½Π°Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΎΡΠΈΠ±ΠΎΠΊ Ρ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΠΌΠΈ ΡΠΈΠΏΠ°ΠΌΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ ΠΈ ΡΠ»ΡΡΡΠ΅Π½Π½ΡΠΌ ΠΈΠ·Π²Π»Π΅ΡΠ΅Π½ΠΈΠ΅ΠΌ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠΉ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ°Ρ :
begin
patent = client.patent("INVALID_ID")
rescue Rospatent::Errors::ValidationError => e
puts "ΠΠ΅Π²Π΅ΡΠ½ΡΠΉ Π²Π²ΠΎΠ΄: #{e.message}"
puts "ΠΡΠΈΠ±ΠΊΠΈ ΠΏΠΎΠ»Π΅ΠΉ: #{e.errors}" if e.errors.any?
rescue Rospatent::Errors::NotFoundError => e
puts "ΠΠ°ΡΠ΅Π½Ρ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½: #{e.message}"
rescue Rospatent::Errors::RateLimitError => e
puts "ΠΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠ΅ ΡΠΊΠΎΡΠΎΡΡΠΈ. ΠΠΎΠ²ΡΠΎΡΠΈΡΡ ΡΠ΅ΡΠ΅Π·: #{e.retry_after} ΡΠ΅ΠΊΡΠ½Π΄"
rescue Rospatent::Errors::AuthenticationError => e
puts "ΠΡΠΈΠ±ΠΊΠ° Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ: #{e.message}"
rescue Rospatent::Errors::ApiError => e
puts "ΠΡΠΈΠ±ΠΊΠ° API (#{e.status_code}): #{e.message}"
puts "ID Π·Π°ΠΏΡΠΎΡΠ°: #{e.request_id}" if e.request_id
retry if e.retryable?
rescue Rospatent::Errors::ConnectionError => e
puts "ΠΡΠΈΠ±ΠΊΠ° ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΡ: #{e.message}"
puts "ΠΡΡ
ΠΎΠ΄Π½Π°Ρ ΠΎΡΠΈΠ±ΠΊΠ°: #{e.original_error}"
end
# Π£Π»ΡΡΡΠ΅Π½Π½ΠΎΠ΅ ΠΈΠ·Π²Π»Π΅ΡΠ΅Π½ΠΈΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠΉ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ°Ρ
# ΠΠ»ΠΈΠ΅Π½Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΠΈΠ·Π²Π»Π΅ΠΊΠ°Π΅Ρ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ°Ρ
ΠΈΠ· ΡΠ°Π·Π»ΠΈΡΠ½ΡΡ
ΡΠΎΡΠΌΠ°ΡΠΎΠ² ΠΎΡΠ²Π΅ΡΠΎΠ² API:
# - {"result": "Π‘ΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ΅"} (ΡΠΎΡΠΌΠ°Ρ API Π ΠΎΡΠΏΠ°ΡΠ΅Π½ΡΠ°)
# - {"error": "Π‘ΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ΅"} (ΡΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ ΡΠΎΡΠΌΠ°Ρ)
# - {"message": "Π‘ΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ΅"} (Π°Π»ΡΡΠ΅ΡΠ½Π°ΡΠΈΠ²Π½ΡΠΉ ΡΠΎΡΠΌΠ°Ρ)
# - {"details": "ΠΠ΅ΡΠ°Π»ΠΈ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠΈ"} (ΠΎΡΠΈΠ±ΠΊΠΈ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠΈ)
ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ Π²Ρ ΠΎΠ΄Π½ΡΡ Π΄Π°Π½Π½ΡΡ
ΠΡΠ΅ Π²Ρ ΠΎΠ΄Π½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ Π²Π°Π»ΠΈΠ΄ΠΈΡΡΡΡΡΡ Ρ ΠΏΠΎΠ»Π΅Π·Π½ΡΠΌΠΈ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡΠΌΠΈ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ°Ρ :
# ΠΡΠΈ ΠΏΡΠΈΠΌΠ΅ΡΡ Π²ΡΠ·ΠΎΠ²ΡΡ ValidationError Ρ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΠΌΠΈ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡΠΌΠΈ:
client.search(limit: 0) # "Limit must be at least 1"
client.patent("") # "Document_id cannot be empty"
client.similar_patents_by_text("", count: -1) # ΠΠ½ΠΎΠΆΠ΅ΡΡΠ²Π΅Π½Π½ΡΠ΅ ΠΎΡΠΈΠ±ΠΊΠΈ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠΈ
# ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ Π²ΠΊΠ»ΡΡΠ°Π΅Ρ:
# - Π’ΠΈΠΏΡ ΠΈ ΡΠΎΡΠΌΠ°ΡΡ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠ²
# - ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΡΠΎΡΠΌΠ°ΡΠ° ID ΠΏΠ°ΡΠ΅Π½ΡΠ°
# - ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΡΠΎΡΠΌΠ°ΡΠ° Π΄Π°ΡΡ
# - ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ Π·Π½Π°ΡΠ΅Π½ΠΈΠΉ ΠΏΠ΅ΡΠ΅ΡΠΈΡΠ»Π΅Π½ΠΈΠΉ
# - ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΡΡ
ΠΏΠΎΠ»Π΅ΠΉ
ΠΠΎΠ½ΠΈΡΠΎΡΠΈΠ½Π³ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ
ΠΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ ΠΈ ΡΡΠ°ΡΠΈΡΡΠΈΠΊΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ:
# Π‘ΡΠ°ΡΠΈΡΡΠΈΠΊΠ° ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΎΠ³ΠΎ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°
stats = client.statistics
puts "ΠΡΠΏΠΎΠ»Π½Π΅Π½ΠΎ Π·Π°ΠΏΡΠΎΡΠΎΠ²: #{stats[:requests_made]}"
puts "ΠΠ±ΡΠ°Ρ ΠΏΡΠΎΠ΄ΠΎΠ»ΠΆΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΡ: #{stats[:total_duration_seconds]}Ρ"
puts "Π‘ΡΠ΅Π΄Π½Π΅Π΅ Π²ΡΠ΅ΠΌΡ Π·Π°ΠΏΡΠΎΡΠ°: #{stats[:average_request_time]}Ρ"
puts "ΠΡΠΎΡΠ΅Π½Ρ ΠΏΠΎΠΏΠ°Π΄Π°Π½ΠΈΠΉ Π² ΠΊΠ΅Ρ: #{stats[:cache_stats][:hit_rate_percent]}%"
# ΠΠ»ΠΎΠ±Π°Π»ΡΠ½Π°Ρ ΡΡΠ°ΡΠΈΡΡΠΈΠΊΠ°
global_stats = Rospatent.statistics
puts "ΠΠΊΡΡΠΆΠ΅Π½ΠΈΠ΅: #{global_stats[:configuration][:environment]}"
puts "ΠΠ΅Ρ Π²ΠΊΠ»ΡΡΠ΅Π½: #{global_stats[:configuration][:cache_enabled]}"
puts "URL API: #{global_stats[:configuration][:api_url]}"
ΠΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΡ Ρ Rails
ΠΠ΅Π½Π΅ΡΠ°ΡΠΎΡ
$ rails generate rospatent:install
ΠΡΠΎ ΡΠΎΠ·Π΄Π°Π΅Ρ config/initializers/rospatent.rb
:
Rospatent.configure do |config|
# ΠΡΠΈΠΎΡΠΈΡΠ΅Ρ ΡΠΎΠΊΠ΅Π½Π°: Rails credentials > ROSPATENT_TOKEN > ROSPATENT_API_TOKEN
config.token = Rails.application.credentials.rospatent_token ||
ENV["ROSPATENT_TOKEN"] ||
ENV["ROSPATENT_API_TOKEN"]
# ΠΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ ΡΡΠΈΡΡΠ²Π°Π΅Ρ ROSPATENT_ENV
config.environment = ENV.fetch("ROSPATENT_ENV", Rails.env)
# ΠΠ ΠΠ’ΠΠ§ΠΠ: ΠΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ ΠΈΠΌΠ΅ΡΡ ΠΏΡΠΈΠΎΡΠΈΡΠ΅Ρ Π½Π°Π΄ Π½Π°ΡΡΡΠΎΠΉΠΊΠ°ΠΌΠΈ Rails
# ΠΡΠΎ ΠΏΡΠ΅Π΄ΠΎΡΠ²ΡΠ°ΡΠ°Π΅Ρ ΠΏΠΎΡΠ²Π»Π΅Π½ΠΈΠ΅ DEBUG Π»ΠΎΠ³ΠΎΠ² Π² ΠΏΡΠΎΠ΄Π°ΠΊΡΠ½Π΅ ΠΏΡΠΈ ROSPATENT_LOG_LEVEL=debug
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
ENV["ROSPATENT_LOG_LEVEL"].to_sym
else
Rails.env.production? ? :warn : :debug
end
config.cache_enabled = Rails.env.production?
end
ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ Ρ Π»ΠΎΠ³Π³Π΅ΡΠΎΠΌ Rails
# Π config/initializers/rospatent.rb
Rospatent.configure do |config|
config.token = Rails.application.credentials.rospatent_token
end
# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΊΠ»ΠΈΠ΅Π½ΡΠ° Ρ Π»ΠΎΠ³Π³Π΅ΡΠΎΠΌ Rails
logger = Rospatent::Logger.new(
output: Rails.logger,
level: Rails.env.production? ? :warn : :debug,
formatter: :text
)
# ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ Π² ΠΊΠΎΠ½ΡΡΠΎΠ»Π»Π΅ΡΠ°Ρ
/ΡΠ΅ΡΠ²ΠΈΡΠ°Ρ
class PatentService
def initialize
@client = Rospatent.client(logger: logger)
end
def search_patents(query)
@client.search(q: query, limit: 20)
rescue Rospatent::Errors::ApiError => e
Rails.logger.error "ΠΠΎΠΈΡΠΊ ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² Π½Π΅ ΡΠ΄Π°Π»ΡΡ: #{e.message}"
raise
end
end
Π’Π΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅
ΠΠ°ΠΏΡΡΠΊ ΡΠ΅ΡΡΠΎΠ²
# ΠΠ°ΠΏΡΡΠΊ Π²ΡΠ΅Ρ
ΡΠ΅ΡΡΠΎΠ²
$ bundle exec rake test
# ΠΠ°ΠΏΡΡΠΊ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΎΠ³ΠΎ ΡΠ΅ΡΡΠΎΠ²ΠΎΠ³ΠΎ ΡΠ°ΠΉΠ»Π°
$ bundle exec ruby -Itest test/unit/client_test.rb
# ΠΠ°ΠΏΡΡΠΊ ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΠΎΠ½Π½ΡΡ
ΡΠ΅ΡΡΠΎΠ² (ΡΡΠ΅Π±ΡΠ΅ΡΡΡ API ΡΠΎΠΊΠ΅Π½)
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=Π²Π°Ρ_ΡΠΎΠΊΠ΅Π½ bundle exec rake test_integration
# ΠΠ°ΠΏΡΡΠΊ Ρ ΠΏΠΎΠΊΡΡΡΠΈΠ΅ΠΌ
$ bundle exec rake coverage
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΡΠ΅ΡΡΠΎΠ²
ΠΠ»Ρ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΡΠ±ΡΠ°ΡΡΠ²Π°ΠΉΡΠ΅ ΠΈ Π½Π°ΡΡΡΠ°ΠΈΠ²Π°ΠΉΡΠ΅ Π² ΠΌΠ΅ΡΠΎΠ΄Π΅ setup ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΡΠ΅ΡΡΠ°:
# test/test_helper.rb - ΠΠ°Π·ΠΎΠ²Π°Ρ Π½Π°ΡΡΡΠΎΠΉΠΊΠ° Π΄Π»Ρ ΠΌΠΎΠ΄ΡΠ»ΡΠ½ΡΡ
ΡΠ΅ΡΡΠΎΠ²
module Minitest
class Test
def setup
Rospatent.reset # Π§ΠΈΡΡΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΌΠ΅ΠΆΠ΄Ρ ΡΠ΅ΡΡΠ°ΠΌΠΈ
Rospatent.configure do |config|
config.token = ENV.fetch("ROSPATENT_TEST_TOKEN", "test_token")
config.environment = "development"
config.cache_enabled = false # ΠΡΠΊΠ»ΡΡΠΈΡΡ ΠΊΠ΅Ρ Π΄Π»Ρ ΠΏΡΠ΅Π΄ΡΠΊΠ°Π·ΡΠ΅ΠΌΡΡ
ΡΠ΅ΡΡΠΎΠ²
config.log_level = :error # Π£ΠΌΠ΅Π½ΡΡΠΈΡΡ ΡΡΠΌ ΡΠ΅ΡΡΠΎΠ²
end
end
end
end
# ΠΠ»Ρ ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΠΎΠ½Π½ΡΡ
ΡΠ΅ΡΡΠΎΠ² - ΡΡΠ°Π±ΠΈΠ»ΡΠ½Π°Ρ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ, ΡΠ±ΡΠΎΡ Π½Π΅ Π½ΡΠΆΠ΅Π½
class IntegrationTest < Minitest::Test
def setup
skip unless ENV["ROSPATENT_INTEGRATION_TESTS"]
@token = ENV.fetch("ROSPATENT_TEST_TOKEN", nil)
skip "ROSPATENT_TEST_TOKEN not set" unless @token
# Π‘Π±ΡΠΎΡ Π½Π΅ Π½ΡΠΆΠ΅Π½ - ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΠΎΠ½Π½ΡΠ΅ ΡΠ΅ΡΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡΡ ΡΠΎΠ³Π»Π°ΡΠΎΠ²Π°Π½Π½ΡΡ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ
Rospatent.configure do |config|
config.token = @token
config.environment = "development"
config.cache_enabled = true
config.log_level = :debug
end
end
end
ΠΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΠ΅ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ (Minitest)
# test/test_helper.rb
module Minitest
class Test
def assert_valid_patent_id(patent_id, message = nil)
message ||= "ΠΠΆΠΈΠ΄Π°Π΅ΡΡΡ #{patent_id} ΠΊΠ°ΠΊ Π΄Π΅ΠΉΡΡΠ²ΠΈΡΠ΅Π»ΡΠ½ΡΠΉ ID ΠΏΠ°ΡΠ΅Π½ΡΠ° (ΡΠΎΡΠΌΠ°Ρ: XX12345Y1_YYYYMMDD)"
assert patent_id.match?(/^[A-Z]{2}[A-Z0-9]+[A-Z]\d*_\d{8}$/), message
end
end
end
# ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ Π² ΡΠ΅ΡΡΠ°Ρ
def test_patent_id_validation
assert_valid_patent_id("RU134694U1_20131120")
assert_valid_patent_id("RU134694A_20131120")
end
ΠΠ·Π²Π΅ΡΡΠ½ΡΠ΅ ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΡ API
ΠΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ° ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ Faraday Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ HTTP-ΠΊΠ»ΠΈΠ΅Π½ΡΠ° Ρ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠΎΠΉ ΡΠ΅Π΄ΠΈΡΠ΅ΠΊΡΠΎΠ² Π΄Π»Ρ Π²ΡΠ΅Ρ endpoints:
-
ΠΡΠ΅ endpoints (
/search
,/docs/{id}
,/similar_search
,/datasets/tree
, ΠΈ Ρ.Π΄.) - β Π Π°Π±ΠΎΡΠ°ΡΡ ΠΈΠ΄Π΅Π°Π»ΡΠ½ΠΎ Ρ Faraday -
ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΠ΅Π΄ΠΈΡΠ΅ΠΊΡΠΎΠ²: ΠΠ°ΡΡΡΠΎΠ΅Π½Π° Ρ middleware
faraday-follow_redirects
Π΄Π»Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠΉ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΡΠ΅ΡΠ²Π΅ΡΠ½ΡΡ ΡΠ΅Π΄ΠΈΡΠ΅ΠΊΡΠΎΠ²
β οΈ ΠΠ΅Π·Π½Π°ΡΠΈΡΠ΅Π»ΡΠ½ΡΠ΅ ΡΠ΅ΡΠ²Π΅ΡΠ½ΡΠ΅ ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΡ:
-
ΠΠΎΠΈΡΠΊ ΠΏΠΎΡ
ΠΎΠΆΠΈΡ
ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ² ΠΏΠΎ ΡΠ΅ΠΊΡΡΡ: ΠΠ½ΠΎΠ³Π΄Π° Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ
503 Service Unavailable
(ΠΏΡΠΎΠ±Π»Π΅ΠΌΠ° ΡΠ΅ΡΠ²Π΅ΡΠ°, Π½Π΅ ΠΊΠ»ΠΈΠ΅Π½ΡΡΠΊΠΎΠΉ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ)
β οΈ ΠΠ΅ΡΠΎΡΠ½ΠΎΡΡΠΈ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ:
-
ΠΠΎΠΈΡΠΊ ΠΏΠΎΡ
ΠΎΠΆΠΈΡ
ΠΏΠ°ΡΠ΅Π½ΡΠΎΠ²: ΠΠ°ΡΡΠΈΠ² ΡΠΎΠ²ΠΏΠ°Π΄Π΅Π½ΠΈΠΉ Π² Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ Π½Π°Π·Π²Π°Π½
hits
, ΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΠ°Ρ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρdata
-
ΠΠ΅ΡΠ΅ΡΠ΅Π½Ρ Π΄Π°ΡΠ°ΡΠ΅ΡΠΎΠ²: ΠΠ»ΡΡ
name
Π² ΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΠΎΠΉ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ ΠΏΡΠΈΠ·Π½Π°ΠΊ Π»ΠΎΠΊΠ°Π»ΠΈΠ·Π°ΡΠΈΠΈ βname_ru
,name_en
ΠΡΡ ΠΎΡΠ½ΠΎΠ²Π½Π°Ρ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎΡΡΡ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½Π° ΠΈ Π³ΠΎΡΠΎΠ²Π° Π΄Π»Ρ ΠΏΡΠΎΠ΄Π°ΠΊΡΠ΅Π½Π°.
Π‘ΠΏΡΠ°Π²ΠΎΡΠ½ΠΈΠΊ ΠΎΡΠΈΠ±ΠΎΠΊ
ΠΠ΅ΡΠ°ΡΡ ΠΈΡ ΠΎΡΠΈΠ±ΠΎΠΊ
Rospatent::Errors::Error (Π±Π°Π·ΠΎΠ²Π°Ρ)
βββ MissingTokenError
βββ ApiError
β βββ AuthenticationError (401)
β βββ NotFoundError (404)
β βββ RateLimitError (429)
β βββ ServiceUnavailableError (503)
βββ ConnectionError
β βββ TimeoutError
βββ InvalidRequestError
βββ ValidationError
Π Π°ΡΠΏΡΠΎΡΡΡΠ°Π½Π΅Π½Π½ΡΠ΅ ΡΡΠ΅Π½Π°ΡΠΈΠΈ ΠΎΡΠΈΠ±ΠΎΠΊ
# ΠΡΡΡΡΡΡΠ²ΡΡΡΠΈΠΉ ΠΈΠ»ΠΈ Π½Π΅Π΄Π΅ΠΉΡΡΠ²ΠΈΡΠ΅Π»ΡΠ½ΡΠΉ ΡΠΎΠΊΠ΅Π½
Rospatent::Errors::MissingTokenError
Rospatent::Errors::AuthenticationError
# ΠΠ΅Π΄Π΅ΠΉΡΡΠ²ΠΈΡΠ΅Π»ΡΠ½ΡΠ΅ Π²Ρ
ΠΎΠ΄Π½ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ
Rospatent::Errors::ValidationError
# Π Π΅ΡΡΡΡ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½
Rospatent::Errors::NotFoundError
# ΠΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠ΅ ΡΠΊΠΎΡΠΎΡΡΠΈ
Rospatent::Errors::RateLimitError # ΠΡΠΎΠ²Π΅ΡΡΡΠ΅ retry_after
# ΠΡΠΎΠ±Π»Π΅ΠΌΡ Ρ ΡΠ΅ΡΡΡ
Rospatent::Errors::ConnectionError
Rospatent::Errors::TimeoutError
# ΠΡΠΎΠ±Π»Π΅ΠΌΡ ΡΠ΅ΡΠ²Π΅ΡΠ°
Rospatent::Errors::ServiceUnavailableError
Rake Π·Π°Π΄Π°ΡΠΈ
ΠΠΎΠ»Π΅Π·Π½ΡΠ΅ Π·Π°Π΄Π°ΡΠΈ Π΄Π»Ρ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΈ ΠΎΠ±ΡΠ»ΡΠΆΠΈΠ²Π°Π½ΠΈΡ:
# ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ
$ bundle exec rake validate
# Π£ΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠ΅ΡΠ΅ΠΌ
$ bundle exec rake cache:stats
$ bundle exec rake cache:clear
# ΠΠ΅Π½Π΅ΡΠ°ΡΠΈΡ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ
$ bundle exec rake doc
# ΠΠ°ΠΏΡΡΠΊ ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΠΎΠ½Π½ΡΡ
ΡΠ΅ΡΡΠΎΠ²
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN='<Π²Π°Ρ_jwt_ΡΠΎΠΊΠ΅Π½>' bundle exec rake test_integration
# ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΡΡΠ΅Π΄Ρ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ
$ bundle exec rake setup
# ΠΡΠΎΠ²Π΅ΡΠΊΠΈ ΠΏΠ΅ΡΠ΅Π΄ ΡΠ΅Π»ΠΈΠ·ΠΎΠΌ
$ bundle exec rake release_check
Π‘ΠΎΠ²Π΅ΡΡ ΠΏΠΎ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ
- ΠΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅: ΠΠΊΠ»ΡΡΠΈΡΠ΅ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π΄Π»Ρ ΠΏΠΎΠ²ΡΠΎΡΡΡΡΠΈΡ ΡΡ Π·Π°ΠΏΡΠΎΡΠΎΠ²
-
ΠΠ°ΠΊΠ΅ΡΠ½ΡΠ΅ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ: ΠΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅
batch_patents
Π΄Π»Ρ ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠΎΠ² - ΠΠΎΠ΄Ρ ΠΎΠ΄ΡΡΠΈΠ΅ Π»ΠΈΠΌΠΈΡΡ: ΠΠ΅ Π·Π°ΠΏΡΠ°ΡΠΈΠ²Π°ΠΉΡΠ΅ Π±ΠΎΠ»ΡΡΠ΅ Π΄Π°Π½Π½ΡΡ , ΡΠ΅ΠΌ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠΎ
- ΠΠ΅ΡΠ΅ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΠΉ: ΠΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ ΠΎΠ΄ΠΈΠ½ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡ ΠΊΠ»ΠΈΠ΅Π½ΡΠ° ΠΊΠΎΠ³Π΄Π° Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ
- ΠΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ: ΠΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ ΠΏΡΠΎΠ΄Π°ΠΊΡΠ½ Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ Π² ΠΏΡΠΎΠ΄Π°ΠΊΡΠ½Π΅
# Π₯ΠΎΡΠΎΡΠΎ: ΠΠ΅ΡΠ΅ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡΠ° ΠΊΠ»ΠΈΠ΅Π½ΡΠ°
client = Rospatent.client
patents = patent_ids.map { |id| client.patent(id) }
# ΠΡΡΡΠ΅: ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΊΠ΅ΡΠ½ΡΡ
ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΉ
patents = []
client.batch_patents(patent_ids) { |doc| patents << doc }
# ΠΡΠ»ΠΈΡΠ½ΠΎ: ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ Ρ ΠΎΠ±ΡΠΈΠΌ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡΠΎΠΌ
shared_client = Rospatent.client(cache: Rospatent.shared_cache)
Π£ΡΡΡΠ°Π½Π΅Π½ΠΈΠ΅ Π½Π΅ΠΏΠΎΠ»Π°Π΄ΠΎΠΊ
Π§Π°ΡΡΡΠ΅ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ
ΠΡΠΈΠ±ΠΊΠΈ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ:
# ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π²Π°Π»ΠΈΠ΄Π½ΠΎΡΡΠΈ ΡΠΎΠΊΠ΅Π½Π°
errors = Rospatent.validate_configuration
puts errors if errors.any?
Π’Π°ΠΉΠΌΠ°ΡΡΡ ΡΠ΅ΡΠΈ:
# Π£Π²Π΅Π»ΠΈΡΠ΅Π½ΠΈΠ΅ ΡΠ°ΠΉΠΌΠ°ΡΡΠ° Π΄Π»Ρ ΠΌΠ΅Π΄Π»Π΅Π½Π½ΡΡ
ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΠΉ
Rospatent.configure do |config|
config.timeout = 120
config.retry_count = 5
end
ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΌΡΡΠΈ:
# ΠΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠ΅ ΡΠ°Π·ΠΌΠ΅ΡΠ° ΠΊΠ΅ΡΠ° Π΄Π»Ρ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΠΉ Ρ ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½Π½ΠΎΠΉ ΠΏΠ°ΠΌΡΡΡΡ
Rospatent.configure do |config|
config.cache_max_size = 100
config.cache_ttl = 300
end
ΠΡΠ»Π°Π΄ΠΊΠ° API Π²ΡΠ·ΠΎΠ²ΠΎΠ²:
# ΠΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΡΠΎΠ±Π½ΠΎΠ³ΠΎ Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΡ
Rospatent.configure do |config|
config.log_level = :debug
config.log_requests = true
config.log_responses = true
end
Π Π°Π·ΡΠ°Π±ΠΎΡΠΊΠ°
ΠΠΎΡΠ»Π΅ ΠΊΠ»ΠΎΠ½ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΡΠ΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΡ Π²ΡΠΏΠΎΠ»Π½ΠΈΡΠ΅ bin/setup
Π΄Π»Ρ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ. ΠΠ°ΡΠ΅ΠΌ Π·Π°ΠΏΡΡΡΠΈΡΠ΅ rake test
Π΄Π»Ρ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ ΡΠ΅ΡΡΠΎΠ².
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ
$ git clone https://hub.mos.ru/ad/rospatent.git
$ cd rospatent
$ bundle install
$ bundle exec rake setup
ΠΠ°ΠΏΡΡΠΊ ΡΠ΅ΡΡΠΎΠ²
# ΠΠΎΠ΄ΡΠ»ΡΠ½ΡΠ΅ ΡΠ΅ΡΡΡ
$ bundle exec rake test
# ΠΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΠΎΠ½Π½ΡΠ΅ ΡΠ΅ΡΡΡ (ΡΡΠ΅Π±ΡΠ΅ΡΡΡ API ΡΠΎΠΊΠ΅Π½)
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=Π²Π°Ρ_ΡΠΎΠΊΠ΅Π½ bundle exec rake test_integration
# Π‘ΡΠΈΠ»Ρ ΠΊΠΎΠ΄Π°
$ bundle exec rubocop
# ΠΡΠ΅ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ
$ bundle exec rake ci
ΠΠ½ΡΠ΅ΡΠ°ΠΊΡΠΈΠ²Π½Π°Ρ ΠΊΠΎΠ½ΡΠΎΠ»Ρ
$ bin/console
Π‘ΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΠ΅
ΠΡΡΠ΅ΡΡ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ°Ρ ΠΈ pull request ΠΏΡΠΈΠ²Π΅ΡΡΡΠ²ΡΡΡΡΡ Π½Π° MosHub ΠΏΠΎ Π°Π΄ΡΠ΅ΡΡ https://hub.mos.ru/ad/rospatent.
Π ΡΠΊΠΎΠ²ΠΎΠ΄ΡΡΠ²ΠΎ ΠΏΠΎ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠ΅
- ΠΠΈΡΠΈΡΠ΅ ΡΠ΅ΡΡΡ: Π£Π±Π΅Π΄ΠΈΡΠ΅ΡΡ, ΡΡΠΎ Π²ΡΠ΅ Π½ΠΎΠ²ΡΠ΅ ΡΡΠ½ΠΊΡΠΈΠΈ ΠΈΠΌΠ΅ΡΡ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠΈΠ΅ ΡΠ΅ΡΡΡ
-
Π‘Π»Π΅Π΄ΡΠΉΡΠ΅ ΡΡΠΈΠ»Ρ: ΠΡΠΏΠΎΠ»Π½ΠΈΡΠ΅
rubocop
ΠΈ ΠΈΡΠΏΡΠ°Π²ΡΡΠ΅ Π»ΡΠ±ΡΠ΅ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ ΡΡΠΈΠ»Ρ - ΠΠΎΠΊΡΠΌΠ΅Π½ΡΠΈΡΡΠΉΡΠ΅ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ: ΠΠ±Π½ΠΎΠ²ΠΈΡΠ΅ README ΠΈ CHANGELOG
-
ΠΠ°Π»ΠΈΠ΄ΠΈΡΡΠΉΡΠ΅ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ: ΠΠ°ΠΏΡΡΡΠΈΡΠ΅
rake validate
ΠΏΠ΅ΡΠ΅Π΄ ΠΎΡΠΏΡΠ°Π²ΠΊΠΎΠΉ
ΠΡΠΎΡΠ΅ΡΡ ΡΠ΅Π»ΠΈΠ·Π°
# ΠΡΠΎΠ²Π΅ΡΠΊΠΈ ΠΏΠ΅ΡΠ΅Π΄ ΡΠ΅Π»ΠΈΠ·ΠΎΠΌ
$ bundle exec rake release_check
# ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ Π²Π΅ΡΡΠΈΠΈ ΠΈ ΡΠ΅Π»ΠΈΠ·
$ bundle exec rake release
Changelog
See CHANGELOG.md for detailed version history.
License
The gem is available as open source under the terms of the MIT License.
API Reference
For detailed API documentation, see the generated documentation or run:
$ bundle exec rake doc
$ open doc/index.html
Key Classes:
-
Rospatent::Client
- Main API client -
Rospatent::Configuration
- Configuration management -
Rospatent::Cache
- Caching system -
Rospatent::Logger
- Structured logging -
Rospatent::SearchResult
- Search result wrapper -
Rospatent::PatentParser
- Patent content parsing
Classification Features:
- Classification system search (IPC/CPC)
- Detailed classification code information
- Multi-language support (Russian/English)
- Automatic caching of classification data
Patent Features:
- Patent search by text
- Patent details retrieval
- Patent classification retrieval
- Patent content parsing
- Patent media retrieval
- Patent similarity search by text
- Patent similarity search by ID
Supported Ruby Versions: Ruby 3.3.0+