Project

rospatent

0.0
The project is in a healthy, maintained state
A comprehensive Ruby client for interacting with the Rospatent patent search API. Features include automatic caching, request validation, structured logging, error handling, and batch operations for efficient patent data retrieval.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

Runtime

 Project Readme

Rospatent

Gem Version

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

  1. Rails credentials: Rails.application.credentials.rospatent_token
  2. Primary environment variable: ROSPATENT_TOKEN
  3. 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

  1. Use Rails credentials for tokens:

    rails credentials:edit
    # Add: rospatent_token: your_jwt_token
  2. Set environment-specific variables:

    # config/environments/production.rb
    ENV["ROSPATENT_LOG_LEVEL"] ||= "warn"
    ENV["ROSPATENT_CACHE_ENABLED"] ||= "true"
  3. 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 name data
  • 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

  1. Use Caching: Enable caching for repeated requests
  2. Batch Operations: Use batch_patents for multiple documents
  3. Appropriate Limits: Don't request more data than needed
  4. Connection Reuse: Use the same client instance when possible
  5. 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

  1. Write Tests: Ensure all new features have corresponding tests
  2. Follow Style: Run rubocop and fix any style issues
  3. Document Changes: Update README and CHANGELOG
  4. 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 прилоТСниях.

ΠŸΡ€ΠΈΠΎΡ€ΠΈΡ‚Π΅Ρ‚ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ Ρ‚ΠΎΠΊΠ΅Π½Π°

  1. Rails credentials: Rails.application.credentials.rospatent_token
  2. Основная пСрСмСнная окруТСния: ROSPATENT_TOKEN
  3. Π£ΡΡ‚Π°Ρ€Π΅Π²ΡˆΠ°Ρ пСрСмСнная окруТСния: 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

  1. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Rails credentials для Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ²:

    rails credentials:edit
    # Π”ΠΎΠ±Π°Π²ΡŒΡ‚Π΅: rospatent_token: your_jwt_token
  2. УстановитС ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ для ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹Ρ… ΠΎΠΊΡ€ΡƒΠΆΠ΅Π½ΠΈΠΉ:

    # config/environments/production.rb
    ENV["ROSPATENT_LOG_LEVEL"] ||= "warn"
    ENV["ROSPATENT_CACHE_ENABLED"] ||= "true"
  3. Π˜Π·Π±Π΅Π³Π°ΠΉΡ‚Π΅ установки 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

Π‘ΠΎΠ²Π΅Ρ‚Ρ‹ ΠΏΠΎ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ

  1. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΊΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅: Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅ ΠΊΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ для ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΡΡŽΡ‰ΠΈΡ…ΡΡ запросов
  2. ΠŸΠ°ΠΊΠ΅Ρ‚Π½Ρ‹Π΅ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ: Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ batch_patents для мноТСства Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²
  3. ΠŸΠΎΠ΄Ρ…ΠΎΠ΄ΡΡ‰ΠΈΠ΅ Π»ΠΈΠΌΠΈΡ‚Ρ‹: НС Π·Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°ΠΉΡ‚Π΅ большС Π΄Π°Π½Π½Ρ‹Ρ…, Ρ‡Π΅ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ
  4. ΠŸΠ΅Ρ€Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ соСдинСний: Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΎΠ΄ΠΈΠ½ экзСмпляр ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° ΠΊΠΎΠ³Π΄Π° Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ
  5. ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ окруТСния: Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΏΡ€ΠΎΠ΄Π°ΠΊΡˆΠ½ настройки Π² ΠΏΡ€ΠΎΠ΄Π°ΠΊΡˆΠ½Π΅
# Π₯ΠΎΡ€ΠΎΡˆΠΎ: ΠŸΠ΅Ρ€Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ экзСмпляра ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°
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.

Руководство ΠΏΠΎ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠ΅

  1. ΠŸΠΈΡˆΠΈΡ‚Π΅ тСсты: Π£Π±Π΅Π΄ΠΈΡ‚Π΅ΡΡŒ, Ρ‡Ρ‚ΠΎ всС Π½ΠΎΠ²Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ ΠΈΠΌΠ΅ΡŽΡ‚ ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ тСсты
  2. Π‘Π»Π΅Π΄ΡƒΠΉΡ‚Π΅ ΡΡ‚ΠΈΠ»ΡŽ: Π’Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚Π΅ rubocop ΠΈ ΠΈΡΠΏΡ€Π°Π²ΡŒΡ‚Π΅ Π»ΡŽΠ±Ρ‹Π΅ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ стиля
  3. Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΈΡ€ΡƒΠΉΡ‚Π΅ измСнСния: ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚Π΅ README ΠΈ CHANGELOG
  4. Π’Π°Π»ΠΈΠ΄ΠΈΡ€ΡƒΠΉΡ‚Π΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ: ЗапуститС 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+