epo-ops 0.2.6 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +6 -0
- data/README.md +78 -38
- data/epo-ops.gemspec +2 -2
- data/lib/epo_ops.rb +46 -0
- data/lib/epo_ops/client.rb +46 -0
- data/lib/epo_ops/error.rb +87 -0
- data/lib/epo_ops/factories.rb +9 -0
- data/lib/epo_ops/factories/name_and_address_factory.rb +54 -0
- data/lib/epo_ops/factories/patent_application_factory.rb +116 -0
- data/lib/epo_ops/factories/register_search_result_factory.rb +42 -0
- data/lib/epo_ops/ipc_class_hierarchy.rb +146 -0
- data/lib/epo_ops/ipc_class_hierarchy_loader.rb +60 -0
- data/lib/epo_ops/ipc_class_util.rb +71 -0
- data/lib/epo_ops/limits.rb +20 -0
- data/lib/epo_ops/logger.rb +15 -0
- data/lib/epo_ops/name_and_address.rb +58 -0
- data/lib/epo_ops/patent_application.rb +159 -0
- data/lib/epo_ops/rate_limit.rb +47 -0
- data/lib/epo_ops/register.rb +100 -0
- data/lib/epo_ops/register_search_result.rb +40 -0
- data/lib/epo_ops/search_query_builder.rb +65 -0
- data/lib/epo_ops/token_store.rb +33 -0
- data/lib/epo_ops/token_store/redis.rb +45 -0
- data/lib/epo_ops/util.rb +52 -0
- data/lib/epo_ops/version.rb +3 -0
- metadata +26 -20
- data/lib/epo/ops.rb +0 -43
- data/lib/epo/ops/address.rb +0 -60
- data/lib/epo/ops/bibliographic_document.rb +0 -196
- data/lib/epo/ops/client.rb +0 -27
- data/lib/epo/ops/error.rb +0 -89
- data/lib/epo/ops/ipc_class_hierarchy.rb +0 -148
- data/lib/epo/ops/ipc_class_hierarchy_loader.rb +0 -62
- data/lib/epo/ops/ipc_class_util.rb +0 -73
- data/lib/epo/ops/limits.rb +0 -22
- data/lib/epo/ops/logger.rb +0 -11
- data/lib/epo/ops/rate_limit.rb +0 -49
- data/lib/epo/ops/register.rb +0 -152
- data/lib/epo/ops/search_query_builder.rb +0 -65
- data/lib/epo/ops/token_store.rb +0 -35
- data/lib/epo/ops/token_store/redis.rb +0 -47
- data/lib/epo/ops/util.rb +0 -32
- data/lib/epo/ops/version.rb +0 -6
@@ -0,0 +1,42 @@
|
|
1
|
+
module EpoOps
|
2
|
+
module Factories
|
3
|
+
# Parses the register search result from EPO Ops into an RegisterSearchResult object
|
4
|
+
class RegisterSearchResultFactory
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# @param raw_data [Hash] raw search result as retrieved from Epo Ops
|
8
|
+
# @return [EpoOps::RegisterSearchResult] RegisterSearchResult filled with parsed data
|
9
|
+
def build(raw_data)
|
10
|
+
factory = new(raw_data)
|
11
|
+
|
12
|
+
EpoOps::RegisterSearchResult.new(
|
13
|
+
factory.patents,
|
14
|
+
factory.count,
|
15
|
+
factory.raw_data
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :raw_data
|
21
|
+
|
22
|
+
def initialize(raw_data)
|
23
|
+
@raw_data = raw_data
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [integer] The number of applications matching the query
|
27
|
+
# @see EpoOps::RegisterSearchResult#count
|
28
|
+
def count
|
29
|
+
EpoOps::Util.dig(@raw_data, 'world_patent_data', 'register_search', 'total_result_count').to_i
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Array] the patents returned by the search. Patentapplication data is not complete
|
33
|
+
# @see EpoOps::RegisterSearchResult#patents
|
34
|
+
def patents
|
35
|
+
EpoOps::Util.flat_dig(
|
36
|
+
@raw_data,
|
37
|
+
%w(world_patent_data register_search register_documents register_document)
|
38
|
+
).map {|patent_data| EpoOps::Factories::PatentApplicationFactory.build(patent_data)}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module EpoOps
|
2
|
+
# The hierarchy is a flat Hash, that helps finding all known ipc subclasses
|
3
|
+
# of a given class. It was parsed from the WIPO. It does not support all
|
4
|
+
# levels, as it would (currently unnecessarily) blow up this hash. It only
|
5
|
+
# finds the first two sub class levels, e.g. A45F.
|
6
|
+
class IpcClassHierarchy
|
7
|
+
Hierarchy = { 'A' => %w(A01 A21 A22 A23 A24 A41 A42 A43 A44 A45 A46 A47 A61 A62 A63 A99),
|
8
|
+
'A01' => %w(A01B A01C A01D A01F A01G A01H A01J A01K A01L A01M A01N A01P),
|
9
|
+
'A21' => %w(A21B A21C A21D),
|
10
|
+
'A22' => %w(A22B A22C),
|
11
|
+
'A23' => %w(A23B A23C A23D A23F A23G A23J A23K A23L A23N A23P),
|
12
|
+
'A24' => %w(A24B A24C A24D A24F),
|
13
|
+
'A41' => %w(A41B A41C A41D A41F A41G A41H),
|
14
|
+
'A42' => %w(A42B A42C),
|
15
|
+
'A43' => %w(A43B A43C A43D),
|
16
|
+
'A44' => %w(A44B A44C),
|
17
|
+
'A45' => %w(A45B A45C A45D A45F),
|
18
|
+
'A46' => %w(A46B A46D),
|
19
|
+
'A47' => %w(A47B A47C A47D A47F A47G A47H A47J A47K A47L),
|
20
|
+
'A61' => %w(A61B A61C A61D A61F A61G A61H A61J A61K A61L A61M A61N A61P A61Q),
|
21
|
+
'A62' => %w(A62B A62C A62D),
|
22
|
+
'A63' => %w(A63B A63C A63D A63F A63G A63H A63J A63K),
|
23
|
+
'A99' => ['A99Z'],
|
24
|
+
'B' => %w(B01 B02 B03 B04 B05 B06 B07 B08 B09 B21 B22 B23 B24 B25 B26 B27 B28 B29 B30 B31 B32 B33 B41 B42 B43 B44 B60 B61 B62 B63 B64 B65 B66 B67 B68 B81 B82 B99),
|
25
|
+
'B01' => %w(B01B B01D B01F B01J B01L),
|
26
|
+
'B02' => %w(B02B B02C),
|
27
|
+
'B03' => %w(B03B B03C B03D),
|
28
|
+
'B04' => %w(B04B B04C),
|
29
|
+
'B05' => %w(B05B B05C B05D),
|
30
|
+
'B06' => ['B06B'],
|
31
|
+
'B07' => %w(B07B B07C),
|
32
|
+
'B08' => ['B08B'],
|
33
|
+
'B09' => %w(B09B B09C),
|
34
|
+
'B21' => %w(B21B B21C B21D B21F B21G B21H B21J B21K B21L),
|
35
|
+
'B22' => %w(B22C B22D B22F),
|
36
|
+
'B23' => %w(B23B B23C B23D B23F B23G B23H B23K B23P B23Q),
|
37
|
+
'B24' => %w(B24B B24C B24D),
|
38
|
+
'B25' => %w(B25B B25C B25D B25F B25G B25H B25J),
|
39
|
+
'B26' => %w(B26B B26D B26F),
|
40
|
+
'B27' => %w(B27B B27C B27D B27F B27G B27H B27J B27K B27L B27M B27N),
|
41
|
+
'B28' => %w(B28B B28C B28D),
|
42
|
+
'B29' => %w(B29B B29C B29D B29K B29L),
|
43
|
+
'B30' => ['B30B'],
|
44
|
+
'B31' => %w(B31B B31C B31D B31F),
|
45
|
+
'B32' => ['B32B'],
|
46
|
+
'B33' => ['B33Y'],
|
47
|
+
'B41' => %w(B41B B41C B41D B41F B41G B41J B41K B41L B41M B41N),
|
48
|
+
'B42' => %w(B42B B42C B42D B42F),
|
49
|
+
'B43' => %w(B43K B43L B43M),
|
50
|
+
'B44' => %w(B44B B44C B44D B44F),
|
51
|
+
'B60' => %w(B60B B60C B60D B60F B60G B60H B60J B60K B60L B60M B60N B60P B60Q B60R B60S B60T B60V B60W),
|
52
|
+
'B61' => %w(B61B B61C B61D B61F B61G B61H B61J B61K B61L),
|
53
|
+
'B62' => %w(B62B B62C B62D B62H B62J B62K B62L B62M),
|
54
|
+
'B63' => %w(B63B B63C B63G B63H B63J),
|
55
|
+
'B64' => %w(B64B B64C B64D B64F B64G),
|
56
|
+
'B65' => %w(B65B B65C B65D B65F B65G B65H),
|
57
|
+
'B66' => %w(B66B B66C B66D B66F),
|
58
|
+
'B67' => %w(B67B B67C B67D),
|
59
|
+
'B68' => %w(B68B B68C B68F B68G),
|
60
|
+
'B81' => %w(B81B B81C),
|
61
|
+
'B82' => %w(B82B B82Y),
|
62
|
+
'B99' => ['B99Z'],
|
63
|
+
'C' => %w(C01 C02 C03 C04 C05 C06 C07 C08 C09 C10 C11 C12 C13 C14 C21 C22 C23 C25 C30 C40 C99),
|
64
|
+
'C01' => %w(C01B C01C C01D C01F C01G),
|
65
|
+
'C02' => ['C02F'],
|
66
|
+
'C03' => %w(C03B C03C),
|
67
|
+
'C04' => ['C04B'],
|
68
|
+
'C05' => %w(C05B C05C C05D C05F C05G),
|
69
|
+
'C06' => %w(C06B C06C C06D C06F),
|
70
|
+
'C07' => %w(C07B C07C C07D C07F C07G C07H C07J C07K),
|
71
|
+
'C08' => %w(C08B C08C C08F C08G C08H C08J C08K C08L),
|
72
|
+
'C09' => %w(C09B C09C C09D C09F C09G C09H C09J C09K),
|
73
|
+
'C10' => %w(C10B C10C C10F C10G C10H C10J C10K C10L C10M C10N),
|
74
|
+
'C11' => %w(C11B C11C C11D),
|
75
|
+
'C12' => %w(C12C C12F C12G C12H C12J C12L C12M C12N C12P C12Q C12R),
|
76
|
+
'C13' => %w(C13B C13K),
|
77
|
+
'C14' => %w(C14B C14C),
|
78
|
+
'C21' => %w(C21B C21C C21D),
|
79
|
+
'C22' => %w(C22B C22C C22F),
|
80
|
+
'C23' => %w(C23C C23D C23F C23G),
|
81
|
+
'C25' => %w(C25B C25C C25D C25F),
|
82
|
+
'C30' => ['C30B'],
|
83
|
+
'C40' => ['C40B'],
|
84
|
+
'C99' => ['C99Z'],
|
85
|
+
'D' => %w(D01 D02 D03 D04 D05 D06 D07 D21 D99),
|
86
|
+
'D01' => %w(D01B D01C D01D D01F D01G D01H),
|
87
|
+
'D02' => %w(D02G D02H D02J),
|
88
|
+
'D03' => %w(D03C D03D D03J),
|
89
|
+
'D04' => %w(D04B D04C D04D D04G D04H),
|
90
|
+
'D05' => %w(D05B D05C),
|
91
|
+
'D06' => %w(D06B D06C D06F D06G D06H D06J D06L D06M D06N D06P D06Q),
|
92
|
+
'D07' => ['D07B'],
|
93
|
+
'D21' => %w(D21B D21C D21D D21F D21G D21H D21J),
|
94
|
+
'D99' => ['D99Z'],
|
95
|
+
'E' => %w(E01 E02 E03 E04 E05 E06 E21 E99),
|
96
|
+
'E01' => %w(E01B E01C E01D E01F E01H),
|
97
|
+
'E02' => %w(E02B E02C E02D E02F),
|
98
|
+
'E03' => %w(E03B E03C E03D E03F),
|
99
|
+
'E04' => %w(E04B E04C E04D E04F E04G E04H),
|
100
|
+
'E05' => %w(E05B E05C E05D E05F E05G),
|
101
|
+
'E06' => %w(E06B E06C),
|
102
|
+
'E21' => %w(E21B E21C E21D E21F),
|
103
|
+
'E99' => ['E99Z'],
|
104
|
+
'F' => %w(F01 F02 F03 F04 F15 F16 F17 F21 F22 F23 F24 F25 F26 F27 F28 F41 F42 F99),
|
105
|
+
'F01' => %w(F01B F01C F01D F01K F01L F01M F01N F01P),
|
106
|
+
'F02' => %w(F02B F02C F02D F02F F02G F02K F02M F02N F02P),
|
107
|
+
'F03' => %w(F03B F03C F03D F03G F03H),
|
108
|
+
'F04' => %w(F04B F04C F04D F04F),
|
109
|
+
'F15' => %w(F15B F15C F15D),
|
110
|
+
'F16' => %w(F16B F16C F16D F16F F16G F16H F16J F16K F16L F16M F16N F16P F16S F16T),
|
111
|
+
'F17' => %w(F17B F17C F17D),
|
112
|
+
'F21' => %w(F21H F21K F21L F21S F21V F21W F21Y),
|
113
|
+
'F22' => %w(F22B F22D F22G),
|
114
|
+
'F23' => %w(F23B F23C F23D F23G F23H F23J F23K F23L F23M F23N F23Q F23R),
|
115
|
+
'F24' => %w(F24B F24C F24D F24F F24H F24J),
|
116
|
+
'F25' => %w(F25B F25C F25D F25J),
|
117
|
+
'F26' => ['F26B'],
|
118
|
+
'F27' => %w(F27B F27D),
|
119
|
+
'F28' => %w(F28B F28C F28D F28F F28G),
|
120
|
+
'F41' => %w(F41A F41B F41C F41F F41G F41H F41J),
|
121
|
+
'F42' => %w(F42B F42C F42D),
|
122
|
+
'F99' => ['F99Z'],
|
123
|
+
'G' => %w(G01 G02 G03 G04 G05 G06 G07 G08 G09 G10 G11 G12 G21 G99),
|
124
|
+
'G01' => %w(G01B G01C G01D G01F G01G G01H G01J G01K G01L G01M G01N G01P G01Q G01R G01S G01T G01V G01W),
|
125
|
+
'G02' => %w(G02B G02C G02F),
|
126
|
+
'G03' => %w(G03B G03C G03D G03F G03G G03H),
|
127
|
+
'G04' => %w(G04B G04C G04D G04F G04G G04R),
|
128
|
+
'G05' => %w(G05B G05D G05F G05G),
|
129
|
+
'G06' => %w(G06C G06D G06E G06F G06G G06J G06K G06M G06N G06Q G06T),
|
130
|
+
'G07' => %w(G07B G07C G07D G07F G07G),
|
131
|
+
'G08' => %w(G08B G08C G08G),
|
132
|
+
'G09' => %w(G09B G09C G09D G09F G09G),
|
133
|
+
'G10' => %w(G10B G10C G10D G10F G10G G10H G10K G10L),
|
134
|
+
'G11' => %w(G11B G11C),
|
135
|
+
'G12' => ['G12B'],
|
136
|
+
'G21' => %w(G21B G21C G21D G21F G21G G21H G21J G21K),
|
137
|
+
'G99' => ['G99Z'],
|
138
|
+
'H' => %w(H01 H02 H03 H04 H05 H99),
|
139
|
+
'H01' => %w(H01B H01C H01F H01G H01H H01J H01K H01L H01M H01P H01Q H01R H01S H01T),
|
140
|
+
'H02' => %w(H02B H02G H02H H02J H02K H02M H02N H02P H02S),
|
141
|
+
'H03' => %w(H03B H03C H03D H03F H03G H03H H03J H03K H03L H03M),
|
142
|
+
'H04' => %w(H04B H04H H04J H04K H04L H04M H04N H04Q H04R H04S H04W),
|
143
|
+
'H05' => %w(H05B H05C H05F H05G H05H H05K),
|
144
|
+
'H99' => ['H99Z'] }
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'epo_ops/ipc_class_util'
|
3
|
+
|
4
|
+
module EpoOps
|
5
|
+
# Usually this should only used internally.
|
6
|
+
# Loads the Hierarchy from the WIPO.
|
7
|
+
# This is used to update IpcClassHierarchy manually.
|
8
|
+
# At the beginning of the year the WIPO publishes a new list of IPC classes.
|
9
|
+
# The IpcClassHierarchy should then be updated. Make sure that the url is
|
10
|
+
# correct!
|
11
|
+
class IpcClassHierarchyLoader
|
12
|
+
# loads data from the WIPO
|
13
|
+
# @return [Hash]
|
14
|
+
def self.load
|
15
|
+
load_url
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.load_url
|
21
|
+
url = 'http://www.wipo.int/ipc/itos4ipc/ITSupport_and_download_area/20160101/IPC_scheme_title_list/EN_ipc_section_#letter_title_list_20160101.txt'
|
22
|
+
|
23
|
+
# There is a file for every letter A-H
|
24
|
+
('A'..'H').inject({}) do |mem, letter|
|
25
|
+
# Fetch the file from the server
|
26
|
+
response = HTTParty.get(url.gsub('#letter', letter), http_proxyaddr: proxy[:addr], http_proxyport: proxy[:port])
|
27
|
+
file = response.body
|
28
|
+
mem.merge! process_file(file)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.process_file(file)
|
33
|
+
# Process every line (There is a line for every class entry, name and description are separated by a \t)
|
34
|
+
file.each_line.inject(Hash.new { |h, k| h[k] = [] }) do |mem, line|
|
35
|
+
next if line.to_s.strip.empty?
|
36
|
+
ipc_class_generic, description = line.split("\t")
|
37
|
+
|
38
|
+
# Some entries in the files have the same ipc class, the first line is
|
39
|
+
# just some kind of headline, the second is the description we want.
|
40
|
+
ipc_class = EpoOps::IpcClassUtil.parse_generic_format(ipc_class_generic)
|
41
|
+
if ipc_class.length == 3
|
42
|
+
mem[ipc_class[0]] << ipc_class
|
43
|
+
elsif ipc_class.length == 4
|
44
|
+
mem[ipc_class[0, 3]] << ipc_class
|
45
|
+
end
|
46
|
+
mem
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.proxy
|
51
|
+
# configure proxy
|
52
|
+
proxy_addr = nil
|
53
|
+
proxy_port = nil
|
54
|
+
unless ENV['http_proxy'].to_s.strip.empty?
|
55
|
+
proxy_addr, proxy_port = ENV['http_proxy'].gsub('http://', '').gsub('/', '').split(':')
|
56
|
+
end
|
57
|
+
{ addr: proxy_addr, port: proxy_port }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'epo_ops/ipc_class_hierarchy'
|
2
|
+
|
3
|
+
module EpoOps
|
4
|
+
# Utility functions to work on Strings representing ipc classes.
|
5
|
+
class IpcClassUtil
|
6
|
+
|
7
|
+
# @return [Array] \['A', 'B', …, 'H'\]
|
8
|
+
def self.main_classes
|
9
|
+
%w( A B C D E F G H )
|
10
|
+
end
|
11
|
+
|
12
|
+
# check if the given ipc_class is valid as OPS search parameter
|
13
|
+
# @param [String] ipc_class an ipc class
|
14
|
+
# @return [Boolean]
|
15
|
+
def self.valid_for_search?(ipc_class)
|
16
|
+
ipc_class.match(/\A[A-H](\d{2}([A-Z](\d{1,2}\/\d{2,3})?)?)?\z/)
|
17
|
+
end
|
18
|
+
|
19
|
+
# There is a generic format for ipc classes that does not have
|
20
|
+
# the / as delimiter and leaves space for additions. This parses
|
21
|
+
# it into the format the register search understands
|
22
|
+
# @param [String] generic ipc class in generic format
|
23
|
+
# @return [String] reformatted ipc class
|
24
|
+
# @example
|
25
|
+
# parse_generic_format('A01B0003140000') #=> 'A01B3/14'
|
26
|
+
def self.parse_generic_format(generic)
|
27
|
+
ipc_class = generic
|
28
|
+
if ipc_class.length > 4
|
29
|
+
match = ipc_class.match(/([A-Z]\d{2}[A-Z])(\d{4})(\d{6})$/)
|
30
|
+
ipc_class = match[1] + (match[2].to_i).to_s + '/' + process_number(match[3])
|
31
|
+
end
|
32
|
+
ipc_class
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [String] ipc_class an ipc_class
|
36
|
+
# @return [Array] List of all ipc classes one level more specific.
|
37
|
+
# @example
|
38
|
+
# children('A') #=> ['A01', 'A21', 'A22', 'A23', ...]
|
39
|
+
# children('A62') #=> ['A62B', 'A62C', 'A62D'],
|
40
|
+
# @raise [InvalidIpcClassError] if parameter is not a valid ipc class in
|
41
|
+
# the format EPO understands
|
42
|
+
# @raise [LevelNotSupportedError] for parameters with ipc class depth >= 3
|
43
|
+
# e.g. 'A62B' cannot be split further. It is currently not necessary to
|
44
|
+
# do so, it would only blow up the gem, and you do not want to query for
|
45
|
+
# all classes at the lowest level, as it takes too many requests.
|
46
|
+
def self.children(ipc_class)
|
47
|
+
return main_classes if ipc_class.nil?
|
48
|
+
valid = valid_for_search?(ipc_class)
|
49
|
+
fail InvalidIpcClassError, ipc_class unless valid
|
50
|
+
map = IpcClassHierarchy::Hierarchy
|
51
|
+
fail LevelNotSupportedError, ipc_class unless map.key? ipc_class
|
52
|
+
map[ipc_class]
|
53
|
+
end
|
54
|
+
|
55
|
+
# An ipc class in invalid format was given, or none at all.
|
56
|
+
class InvalidIpcClassError < StandardError; end
|
57
|
+
# It is currently not supported to split by the most specific class level.
|
58
|
+
# This would result in a large amount of requests.
|
59
|
+
class LevelNotSupportedError < StandardError; end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def self.process_number(number)
|
64
|
+
result = number.gsub(/0+$/, '')
|
65
|
+
result += '0' if result.length == 1
|
66
|
+
result = '00' if result.length == 0
|
67
|
+
|
68
|
+
result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module EpoOps
|
2
|
+
# The register search is limited by some parameters. With one
|
3
|
+
# query one may only request as many as
|
4
|
+
# {EpoOps::Limits::MAX_QUERY_INTERVAL} references at once.
|
5
|
+
# Considering this, you have to split your requests by this
|
6
|
+
# interval. Nevertheless, the maximum value you may use is
|
7
|
+
# {EpoOps::Limits::MAX_QUERY_RANGE}. If you want to retrieve more
|
8
|
+
# references you must split by other parameters.
|
9
|
+
# @see Register
|
10
|
+
class Limits
|
11
|
+
# @return [Integer] The range in which you can search is limited, say you
|
12
|
+
# cannot request all patents of a given class at once, you probably must
|
13
|
+
# split your requests by additional conditions.
|
14
|
+
MAX_QUERY_RANGE = 2000
|
15
|
+
|
16
|
+
# @return [Integer] The maximum number of elements you may search with one
|
17
|
+
# query. Ignoring this will result in errors.
|
18
|
+
MAX_QUERY_INTERVAL = 100
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module EpoOps
|
2
|
+
# Simple logger writing some notifications to standard output.
|
3
|
+
class Logger
|
4
|
+
# Just hands the parameter to puts.
|
5
|
+
def self.log(output)
|
6
|
+
puts output
|
7
|
+
end
|
8
|
+
|
9
|
+
# Debug logging only
|
10
|
+
def self.debug(output)
|
11
|
+
log(output) if ENV['DEBUG']
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module EpoOps
|
2
|
+
# Used to represent persons or companies (or both) in patents. Used for
|
3
|
+
# both, agents and applicants. Most of the time, when `name` is a person
|
4
|
+
# name, `address1` is a company name. Be aware that the addresses are in
|
5
|
+
# their respective local format.
|
6
|
+
#
|
7
|
+
# Current patents usually at least use the fields address1-3, so they should
|
8
|
+
# nearly always have values. Nevertheless, older ones often only use 1-2.
|
9
|
+
# Note also that EPOs schema documents fields like `street` or `city`, but
|
10
|
+
# by now they have not been used yet.
|
11
|
+
#
|
12
|
+
# @attr [String] name the name of an entity (one or more persons or
|
13
|
+
# companies)
|
14
|
+
# @attr [String] address1 first address line. May also be a company name
|
15
|
+
# @attr [String] address2 second address line
|
16
|
+
# @attr [String] address3 third address line, may be empty
|
17
|
+
# @attr [String] address4 fourth address line, may be empty
|
18
|
+
# @attr [String] address5 fifth address line, may be empty
|
19
|
+
# @attr [String] country_code two letter country code of the address
|
20
|
+
# @attr [String] cdsid some kind of id the EPO provides, not sure yet if
|
21
|
+
# @attr [Date] occurred_on the date an address occurred on, usually matching
|
22
|
+
# the entries change_gazette_num
|
23
|
+
# usable as reference.
|
24
|
+
class NameAndAddress
|
25
|
+
attr_reader :name, :address1,
|
26
|
+
:address2,
|
27
|
+
:address3,
|
28
|
+
:address4,
|
29
|
+
:address5,
|
30
|
+
:country_code,
|
31
|
+
:cdsid,
|
32
|
+
:occurred_on
|
33
|
+
|
34
|
+
def initialize(name, address1, address2, address3, address4,
|
35
|
+
address5, country_code, cdsid, occurred_on= nil)
|
36
|
+
@address1 = address1
|
37
|
+
@address2 = address2
|
38
|
+
@address3 = address3 || ''
|
39
|
+
@address4 = address4 || ''
|
40
|
+
@address5 = address5 || ''
|
41
|
+
@name = name
|
42
|
+
@country_code = country_code || ''
|
43
|
+
@occurred_on = occurred_on || ''
|
44
|
+
@cdsid = cdsid || ''
|
45
|
+
end
|
46
|
+
|
47
|
+
# Compare addresses by the name and address fields.
|
48
|
+
# @return [Boolean]
|
49
|
+
def equal_name_and_address?(other)
|
50
|
+
name == other.name &&
|
51
|
+
address1 == other.address1 &&
|
52
|
+
address2 == other.address2 &&
|
53
|
+
address3 == other.address3 &&
|
54
|
+
address4 == other.address4 &&
|
55
|
+
address5 == other.address5
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module EpoOps
|
2
|
+
# This class represents a Patent Application as returned by EPO OPS returns for bibliographic
|
3
|
+
# documents.
|
4
|
+
# Some elements are not yet fully parsed but hashes returned instead.
|
5
|
+
# Not all information available is parsed, but the full data can be accesses via {#raw_data}
|
6
|
+
class PatentApplication
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Finds an application document by application number. As an application may have several numbers assigned
|
11
|
+
# (for example EP and WO) we pick the first one returned - thus the returned document may have a different number
|
12
|
+
# @param application_number [String] identifies the application document at EPO
|
13
|
+
# @return [PatentApplication] the application document, nil if it can't be found
|
14
|
+
# @note API url: /3.1/rest-services/register/application/epodoc/#{application_number}/biblio
|
15
|
+
def find(application_number)
|
16
|
+
raw_data = EpoOps::Client.request(
|
17
|
+
:get,
|
18
|
+
"/3.1/rest-services/register/application/epodoc/#{application_number}/biblio"
|
19
|
+
).parsed
|
20
|
+
|
21
|
+
data = EpoOps::Util.flat_dig(
|
22
|
+
raw_data,
|
23
|
+
'world_patent_data', 'register_search', 'register_documents', 'register_document'
|
24
|
+
).first
|
25
|
+
|
26
|
+
return nil unless data
|
27
|
+
|
28
|
+
Factories::PatentApplicationFactory.build(data)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Searches for application documents using a CQl query
|
32
|
+
# @see EpoOps::SearchQueryBuilder
|
33
|
+
# @see EpoOps::RegisterSearchResult
|
34
|
+
# Returned documents are not fully populated with data,
|
35
|
+
# only publication references, application id and IPC classes are available
|
36
|
+
# to retrive all data use {#fetch}
|
37
|
+
#
|
38
|
+
# @param cql_query [String] a CQL query string
|
39
|
+
# @return [RegisterSearchResult]
|
40
|
+
# @note API url: /3.1/rest-services/register/search
|
41
|
+
def search(cql_query)
|
42
|
+
data = Client.request(
|
43
|
+
:get,
|
44
|
+
'/3.1/rest-services/register/search?' + cql_query
|
45
|
+
).parsed
|
46
|
+
|
47
|
+
EpoOps::Factories::RegisterSearchResultFactory.build(data)
|
48
|
+
rescue EpoOps::Error::NotFound
|
49
|
+
EpoOps::RegisterSearchResult::NullResult.new
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# A number by which a patent is uniquely identifiable and querieable.
|
54
|
+
# The first two letters are the country code of the processing patent
|
55
|
+
# office, for european patents this is EP.
|
56
|
+
# @return [String] application number.
|
57
|
+
attr_reader :application_nr
|
58
|
+
|
59
|
+
# @return [Hash] The raw application data as recived from EPO
|
60
|
+
attr_reader :raw_data
|
61
|
+
|
62
|
+
# @return [Array] a list of the IPC-Classifications, as strings.
|
63
|
+
# Format is set by EPO, should be similar to: E06B7/23
|
64
|
+
attr_reader :classifications
|
65
|
+
|
66
|
+
# Lists the Applicants of the Application
|
67
|
+
# Applicants are subject to change at EPO, often
|
68
|
+
# their names or addresses are updated, sometimes other
|
69
|
+
# people/companies appear or disappear.
|
70
|
+
# @return [Array] Array of {EpoOps::NameAndAddress}
|
71
|
+
attr_reader :applicants
|
72
|
+
|
73
|
+
# Lists the Agents of the Application
|
74
|
+
# Agents are subject to change at EPO, often
|
75
|
+
# their names or addresses are updated, sometimes other
|
76
|
+
# people/companies appear or disappear.
|
77
|
+
# @return [Array] Array of {EpoOps::NameAndAddress}
|
78
|
+
attr_reader :agents
|
79
|
+
|
80
|
+
# Lists the Inventors of the Application
|
81
|
+
# Agents are subject to change at EPO, often
|
82
|
+
# their names or addresses are updated, sometimes other
|
83
|
+
# people/companies appear or disappear.
|
84
|
+
# @return [Array] Array of {EpoOps::NameAndAddress}
|
85
|
+
attr_reader :inventors
|
86
|
+
|
87
|
+
# @return [String] the string representation of the current patent status as
|
88
|
+
# described by the EPO
|
89
|
+
attr_reader :status
|
90
|
+
|
91
|
+
# The priority claim describe the first documents that were filed at any
|
92
|
+
# patent office in the world regarding this patent.
|
93
|
+
# @return [Array] an Array of hashes which descibe the filed priorities with the fields:
|
94
|
+
# `country` `doc_number`, `date`, `kind`, and `sequence`
|
95
|
+
attr_reader :priority_claims
|
96
|
+
|
97
|
+
# @return [Array] List of hashes containing information about publications
|
98
|
+
# made, entries exist for multiple types of publications, e.g. A1, B1.
|
99
|
+
attr_reader :publication_references
|
100
|
+
|
101
|
+
attr_reader :effective_date
|
102
|
+
|
103
|
+
def initialize(application_nr, data={})
|
104
|
+
@application_nr = application_nr
|
105
|
+
data.each_pair do |key,value|
|
106
|
+
instance_variable_set("@#{key.to_s}",value)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns the Application title in the given languages
|
111
|
+
# @param lang [Integer] language identifier for the title
|
112
|
+
# @return [String] the english title of the patent
|
113
|
+
# @note Titles are usually available at least in english, french and german.
|
114
|
+
# Other languages are also possible.
|
115
|
+
def title(lang='en')
|
116
|
+
return nil unless @title.instance_of?(Hash)
|
117
|
+
@title[lang]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Many fields of the XML the EPO provides have a field
|
121
|
+
# `change_gazette_num`. It is a commercial date (year + week)
|
122
|
+
# that describes in which week the element has been
|
123
|
+
# changed. This method parses them and returns the most recent
|
124
|
+
# date found.
|
125
|
+
# @return [Date] the latest date found in the document.
|
126
|
+
def latest_update
|
127
|
+
gazette_nums = EpoOps::Util.parse_hash_flat(@raw_data, 'change_gazette_num')
|
128
|
+
nums = gazette_nums.map { |num| EpoOps::Util.parse_change_gazette_num(num) }.keep_if { |match| !match.nil? }
|
129
|
+
nums.max
|
130
|
+
end
|
131
|
+
|
132
|
+
# @return [String] The URL at which you can query the original document.
|
133
|
+
def url
|
134
|
+
@url ||= "https://ops.epo.org/3.1/rest-services/register/application/epodoc/#{application_nr}"
|
135
|
+
end
|
136
|
+
|
137
|
+
# Fetches the same document from the register populating all available fields
|
138
|
+
# @see {PatentApplication.find}
|
139
|
+
# @return [self]
|
140
|
+
def fetch
|
141
|
+
raise "Application Number must be set!" unless application_nr
|
142
|
+
|
143
|
+
new_data = self.class.find(application_nr)
|
144
|
+
|
145
|
+
@raw_data = new_data.raw_data
|
146
|
+
@title = new_data.instance_variable_get('@title')
|
147
|
+
@status = new_data.status
|
148
|
+
@agents = new_data.agents
|
149
|
+
@applicants = new_data.applicants
|
150
|
+
@inventors = new_data.inventors
|
151
|
+
@classifications = new_data.classifications
|
152
|
+
@priority_claims = new_data.priority_claims
|
153
|
+
@publication_references = new_data.publication_references
|
154
|
+
@effective_date = new_data.effective_date
|
155
|
+
|
156
|
+
self
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|