staddress 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b5619831aa074839a1fdb4b2c4db74dd302d3c22effb66dec18416fb676064b6
4
+ data.tar.gz: c9f03cf74fbaef729e88fddfb9fc1c65f00da9af004c7865b2863ca3175a7793
5
+ SHA512:
6
+ metadata.gz: 45d4ac6515b8b6f1da89d9d6dcfbfa8d3b862a228e4031dea9100566bdef54cdbc600d8fe8ca299405608ea8fc115e7c91ca8e15bd64070c1f6d9aa00929e2b6
7
+ data.tar.gz: 5c4fe314c6eb3342ea07766c8ccab57b853f4480aeb8f1750b1b77d14a4272928d73435956b9c8ee99e6fe84dec870ee11b88c60b139b0e43e3c7cac57d8aacd
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # staddress (Ruby Gem)
2
+
3
+ Staddress AI API 公式 Ruby SDK。
4
+
5
+ ## ステータス
6
+
7
+ **Phase 4 — v0.1 実装済み**(`parse_address` / `parse_batch` / `get_usage`)
8
+
9
+ - HTTP: 標準ライブラリ `net/http`(**ランタイム依存なし**)
10
+ - Ruby 3.1+
11
+
12
+ ## インストール
13
+
14
+ ```bash
15
+ gem install staddress
16
+ ```
17
+
18
+ ```ruby
19
+ # Gemfile
20
+ gem "staddress"
21
+ ```
22
+
23
+ ## 使い方
24
+
25
+ ```ruby
26
+ require "staddress"
27
+
28
+ client = Staddress::Client.new(
29
+ api_key: "sk_xxx", # 省略時は環境変数 STADDRESS_API_KEY
30
+ base_url: "https://api.staddress.com", # 省略時は既定値(STADDRESS_BASE_URL も可)
31
+ timeout: 30 # 任意(秒)
32
+ )
33
+
34
+ # 単件解析
35
+ result = client.parse_address(input: "六本木ヒルズ 森タワー 52F", postal_code: "106-6100")
36
+ puts result.normalized
37
+ puts result.components.pref
38
+
39
+ # 一括解析(Standard プラン以上、最大 100 件)
40
+ results = client.parse_batch([
41
+ { id: "1", address: "東京都渋谷区道玄坂1-2-3" },
42
+ { id: "2", address: "大阪府大阪市北区梅田1-1-1" }
43
+ ])
44
+ results.each { |item| puts "#{item.id}: #{item.result&.normalized}" }
45
+
46
+ # 利用状況
47
+ usage = client.get_usage
48
+ puts "#{usage.plan} / #{usage.account_name}"
49
+ ```
50
+
51
+ ## エラーハンドリング
52
+
53
+ API エラー・ネットワークエラーは `Staddress::Error` として送出されます。
54
+
55
+ ```ruby
56
+ begin
57
+ client.parse_address(input: "...")
58
+ rescue Staddress::Error => err
59
+ err.code # 例: "unauthorized", "quota_exceeded", "unresolved"
60
+ err.http_status # HTTP ステータス(ネットワークエラー時は 0)
61
+ err.request_id # サポート問い合わせ用(あれば)
62
+ err.retry_after # 再試行可能日時(あれば)
63
+ end
64
+ ```
65
+
66
+ ## API
67
+
68
+ | メソッド | HTTP | 説明 |
69
+ | --- | --- | --- |
70
+ | `parse_address(input:, postal_code: nil)` | `POST /api/v1/addresses/parse` | 単件解析 → `Models::ParseResult` |
71
+ | `parse_batch(items)` | `POST /api/v1/addresses/parse/batch` | 一括解析 → `Array<Models::BatchItemResult>` |
72
+ | `get_usage` | `GET /api/v1/usage` | 利用状況 → `Models::UsageResponse` |
73
+
74
+ レスポンスモデルは snake_case アクセサ(`match_level` 等)でアクセスでき、未知フィールドは `#raw`(元の Hash)から参照できます。
75
+
76
+ ## 開発
77
+
78
+ ```bash
79
+ cd packages/ruby
80
+ bundle install
81
+ bundle exec rspec # 単体テスト(webmock でモック、実 API 不要)
82
+ gem build staddress.gemspec
83
+ ```
84
+
85
+ 詳細: [docs/plan-tools.md §3.6](../../docs/plan-tools.md)
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ require_relative "errors"
8
+ require_relative "models"
9
+
10
+ module Staddress
11
+ DEFAULT_BASE_URL = "https://api.staddress.com"
12
+ DEFAULT_TIMEOUT = 30
13
+ MAX_BATCH_ITEMS = 100
14
+
15
+ # Staddress AI API クライアント(同期)。
16
+ #
17
+ # 例:
18
+ # client = Staddress::Client.new(api_key: "sk_xxx")
19
+ # result = client.parse_address(input: "六本木ヒルズ 森タワー 52F")
20
+ # puts result.normalized
21
+ class Client
22
+ # @param api_key [String, nil] 省略時は環境変数 STADDRESS_API_KEY
23
+ # @param base_url [String, nil] 省略時は STADDRESS_BASE_URL または既定値
24
+ # @param timeout [Numeric] 接続・読み取りタイムアウト(秒)
25
+ def initialize(api_key: nil, base_url: nil, timeout: DEFAULT_TIMEOUT)
26
+ @api_key = api_key || ENV.fetch("STADDRESS_API_KEY", nil)
27
+ if @api_key.nil? || @api_key.empty?
28
+ raise Error.new(
29
+ "API キーが設定されていません。api_key 引数または環境変数 STADDRESS_API_KEY を指定してください。",
30
+ code: "unauthorized",
31
+ http_status: 0
32
+ )
33
+ end
34
+
35
+ base = base_url || ENV.fetch("STADDRESS_BASE_URL", nil) || DEFAULT_BASE_URL
36
+ @base_url = base.sub(%r{/+\z}, "")
37
+ @timeout = timeout
38
+ end
39
+
40
+ # 単件住所解析 (POST /api/v1/addresses/parse)。
41
+ # @return [Staddress::Models::ParseResult]
42
+ def parse_address(input:, postal_code: nil)
43
+ if input.nil? || input.empty?
44
+ raise Error.new("input は必須です。", code: "invalid_request", http_status: 0)
45
+ end
46
+
47
+ body = { "input" => input }
48
+ body["postalCode"] = postal_code if postal_code && !postal_code.empty?
49
+
50
+ data = request("POST", "/api/v1/addresses/parse", body)
51
+ Models::ParseResult.from(data["result"]) || Models::ParseResult.from({})
52
+ end
53
+
54
+ # 一括住所解析 (POST /api/v1/addresses/parse/batch)。Standard プラン以上、最大 100 件。
55
+ # @param items [Array<Hash>] 例: [{ id: "1", address: "東京都..." }]
56
+ # @return [Array<Staddress::Models::BatchItemResult>]
57
+ def parse_batch(items)
58
+ list = items.to_a
59
+ if list.empty?
60
+ raise Error.new("items には1件以上を指定してください。", code: "invalid_request", http_status: 0)
61
+ end
62
+ if list.size > MAX_BATCH_ITEMS
63
+ raise Error.new(
64
+ "items は最大 #{MAX_BATCH_ITEMS} 件です(現在: #{list.size} 件)。",
65
+ code: "batch_size_exceeded",
66
+ http_status: 0
67
+ )
68
+ end
69
+
70
+ data = request("POST", "/api/v1/addresses/parse/batch", { "items" => list })
71
+ (data["results"] || []).map { |item| Models::BatchItemResult.from(item) }
72
+ end
73
+
74
+ # 利用状況取得 (GET /api/v1/usage)。
75
+ # @return [Staddress::Models::UsageResponse]
76
+ def get_usage
77
+ data = request("GET", "/api/v1/usage", nil)
78
+ Models::UsageResponse.from(data)
79
+ end
80
+
81
+ private
82
+
83
+ def request(method, path, body)
84
+ uri = URI.parse("#{@base_url}#{path}")
85
+ req = build_request(method, uri, body)
86
+ req["X-Api-Key"] = @api_key
87
+ req["Content-Type"] = "application/json"
88
+
89
+ response = perform(uri, req)
90
+ interpret(response.code.to_i, response.body)
91
+ end
92
+
93
+ def build_request(method, uri, body)
94
+ klass = { "GET" => Net::HTTP::Get, "POST" => Net::HTTP::Post }.fetch(method)
95
+ req = klass.new(uri)
96
+ req.body = JSON.generate(body) unless body.nil?
97
+ req
98
+ end
99
+
100
+ def perform(uri, req)
101
+ http = Net::HTTP.new(uri.host, uri.port)
102
+ http.use_ssl = uri.scheme == "https"
103
+ http.open_timeout = @timeout
104
+ http.read_timeout = @timeout
105
+ http.request(req)
106
+ rescue Net::OpenTimeout, Net::ReadTimeout
107
+ raise Error.new("リクエストがタイムアウトしました。", code: "timeout", http_status: 0)
108
+ rescue SocketError, SystemCallError, IOError
109
+ raise Error.new("ネットワークエラーが発生しました。", code: "network_error", http_status: 0)
110
+ end
111
+
112
+ # HTTP レスポンスを解釈し、成功なら Hash を返す。エラーなら Error を送出する。
113
+ def interpret(status, text)
114
+ data = parse_json(text, status)
115
+
116
+ if status >= 400
117
+ err = data.is_a?(Hash) ? data["error"] : nil
118
+ if err.is_a?(Hash) && err["code"] && err["message"]
119
+ raise Error.from_body(err, status)
120
+ end
121
+
122
+ raise Error.new("API エラー (HTTP #{status})", code: "internal_error", http_status: status)
123
+ end
124
+
125
+ data.is_a?(Hash) ? data : {}
126
+ end
127
+
128
+ def parse_json(text, status)
129
+ return nil if text.nil? || text.empty?
130
+
131
+ JSON.parse(text)
132
+ rescue JSON::ParserError
133
+ raise Error.new("レスポンスの JSON 解析に失敗しました。", code: "internal_error", http_status: status)
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Staddress
4
+ # Staddress API 呼び出しで発生したエラーを表す統一例外。
5
+ #
6
+ # - HTTP 4xx / 5xx で API がエラーボディを返した場合
7
+ # - ネットワークエラー・タイムアウト(code: "network_error" / "timeout")
8
+ # - 入力バリデーションエラー(code: "invalid_request")
9
+ #
10
+ # 属性:
11
+ # code API エラーコード("unauthorized", "quota_exceeded" 等)
12
+ # http_status HTTP ステータスコード(ネットワークエラー時は 0)
13
+ # request_id サポート問い合わせ用のリクエスト ID(あれば)
14
+ # retry_after 再試行可能日時(ISO 8601、あれば)
15
+ class Error < StandardError
16
+ attr_reader :code, :http_status, :request_id, :retry_after
17
+
18
+ def initialize(message, code: "internal_error", http_status: 0, request_id: nil, retry_after: nil)
19
+ super(message)
20
+ @code = code
21
+ @http_status = http_status
22
+ @request_id = request_id
23
+ @retry_after = retry_after
24
+ end
25
+
26
+ # API のエラーボディ(Hash)から Error を生成する。
27
+ def self.from_body(body, http_status)
28
+ new(
29
+ body["message"] || "API error (#{body["code"]})",
30
+ code: body["code"],
31
+ http_status: http_status,
32
+ request_id: body["requestId"],
33
+ retry_after: body["retryAfter"]
34
+ )
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Staddress
4
+ # Staddress AI API のデータモデル。
5
+ #
6
+ # OpenAPI 定義(openapi/staddress-api.yaml)に準拠する。
7
+ # API は camelCase、Ruby 側は snake_case アクセサでアクセスできるようマッピングする。
8
+ # 未知のフィールドは +raw+(元の Hash)から参照できる(前方互換)。
9
+ module Models
10
+ class Base
11
+ # 元の(パース済み)レスポンス Hash。未定義フィールドはここから参照できる。
12
+ attr_reader :raw
13
+
14
+ class << self
15
+ def fields
16
+ @fields ||= {}
17
+ end
18
+
19
+ # フィールド定義。
20
+ # name Ruby 側の snake_case 名(アクセサになる)
21
+ # key: JSON 側のキー(省略時は name を camelCase 化)
22
+ # model: ネストしたモデルクラス
23
+ # list: true なら配列としてマッピング
24
+ def field(name, key: nil, model: nil, list: false)
25
+ fields[name] = { key: key || camelize(name), model: model, list: list }
26
+ attr_reader name
27
+ end
28
+
29
+ # Hash からインスタンスを生成する。nil の場合は nil を返す。
30
+ def from(data)
31
+ return nil if data.nil?
32
+
33
+ obj = allocate
34
+ obj.send(:populate, data, fields)
35
+ obj
36
+ end
37
+
38
+ def camelize(name)
39
+ head, *rest = name.to_s.split("_")
40
+ (rest.empty? ? head : head + rest.map(&:capitalize).join)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def populate(data, fields)
47
+ @raw = data
48
+ fields.each do |name, meta|
49
+ value = data[meta[:key]]
50
+ value = data[meta[:key].to_sym] if value.nil?
51
+ instance_variable_set("@#{name}", cast(value, meta))
52
+ end
53
+ end
54
+
55
+ def cast(value, meta)
56
+ return nil if value.nil?
57
+ return value unless meta[:model]
58
+ return value.map { |item| meta[:model].from(item) } if meta[:list]
59
+
60
+ meta[:model].from(value)
61
+ end
62
+ end
63
+
64
+ # 住所の構成要素。
65
+ class AddressComponents < Base
66
+ field :pref
67
+ field :pref_code
68
+ field :city
69
+ field :city_code
70
+ field :oaza_cho
71
+ field :chome_koaza
72
+ field :chome_koaza_normalized
73
+ field :chome_number
74
+ field :street_number_block
75
+ field :building_name
76
+ field :room_number
77
+ field :room_number_unit
78
+ field :lon
79
+ field :lat
80
+ field :lg_code
81
+ field :machiaza_id
82
+ end
83
+
84
+ # 解析の信頼度。
85
+ class Confidence < Base
86
+ field :score
87
+ field :match_level
88
+ field :query
89
+ end
90
+
91
+ # 住所解析結果。
92
+ class ParseResult < Base
93
+ field :normalized
94
+ field :standard
95
+ field :components, model: AddressComponents
96
+ field :confidence, model: Confidence
97
+ end
98
+
99
+ # API エラー本体(エラーレスポンスの error フィールド)。
100
+ class ErrorBody < Base
101
+ field :code
102
+ field :message
103
+ field :request_id
104
+ field :retry_after
105
+ end
106
+
107
+ # 一括解析の結果アイテム(result か error のいずれかを持つ)。
108
+ class BatchItemResult < Base
109
+ field :id
110
+ field :result, model: ParseResult
111
+ field :error, model: ErrorBody
112
+ end
113
+
114
+ class UsagePeriod < Base
115
+ field :start
116
+ field :end
117
+ end
118
+
119
+ class UsageCredit < Base
120
+ field :valid_until
121
+ field :total_amount
122
+ field :used_amount
123
+ field :remaining
124
+ end
125
+
126
+ class UsageDetail < Base
127
+ field :period, model: UsagePeriod
128
+ field :count
129
+ field :monthly_limit
130
+ field :contract_period_remaining
131
+ field :credits, model: UsageCredit, list: true
132
+ end
133
+
134
+ # 利用状況レスポンス。
135
+ class UsageResponse < Base
136
+ field :account_name
137
+ field :plan
138
+ field :usage, model: UsageDetail
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Staddress
4
+ VERSION = "0.1.0"
5
+ end
data/lib/staddress.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "staddress/version"
4
+ require_relative "staddress/errors"
5
+ require_relative "staddress/models"
6
+ require_relative "staddress/client"
7
+
8
+ # Staddress AI API 公式 Ruby SDK。
9
+ module Staddress
10
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: staddress
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - StaddressAI
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: webmock
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.23'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.23'
41
+ description: Ruby SDK for the Staddress AI address parsing API (parse_address / parse_batch
42
+ / get_usage). Zero runtime dependencies.
43
+ email:
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.md
49
+ - lib/staddress.rb
50
+ - lib/staddress/client.rb
51
+ - lib/staddress/errors.rb
52
+ - lib/staddress/models.rb
53
+ - lib/staddress/version.rb
54
+ homepage: https://staddress.com/api
55
+ licenses:
56
+ - MIT
57
+ metadata:
58
+ homepage_uri: https://staddress.com/api
59
+ source_code_uri: https://github.com/StaddressAI/staddress-tools
60
+ bug_tracker_uri: https://github.com/StaddressAI/staddress-tools/issues
61
+ rubygems_mfa_required: 'true'
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '3.1'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.5.22
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Official Ruby client for Staddress AI address parsing API
81
+ test_files: []