jp_address_complement 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.
Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/.agent/rules/specify-rules.md +29 -0
  3. data/.agent/workflows/speckit.analyze.md +184 -0
  4. data/.agent/workflows/speckit.checklist.md +294 -0
  5. data/.agent/workflows/speckit.clarify.md +181 -0
  6. data/.agent/workflows/speckit.constitution.md +84 -0
  7. data/.agent/workflows/speckit.implement.md +135 -0
  8. data/.agent/workflows/speckit.plan.md +90 -0
  9. data/.agent/workflows/speckit.specify.md +258 -0
  10. data/.agent/workflows/speckit.tasks.md +137 -0
  11. data/.agent/workflows/speckit.taskstoissues.md +30 -0
  12. data/.claude/commands/speckit.analyze.md +184 -0
  13. data/.claude/commands/speckit.checklist.md +294 -0
  14. data/.claude/commands/speckit.clarify.md +181 -0
  15. data/.claude/commands/speckit.constitution.md +84 -0
  16. data/.claude/commands/speckit.implement.md +135 -0
  17. data/.claude/commands/speckit.plan.md +90 -0
  18. data/.claude/commands/speckit.specify.md +258 -0
  19. data/.claude/commands/speckit.tasks.md +137 -0
  20. data/.claude/commands/speckit.taskstoissues.md +30 -0
  21. data/.cursor/commands/speckit.analyze.md +184 -0
  22. data/.cursor/commands/speckit.checklist.md +294 -0
  23. data/.cursor/commands/speckit.clarify.md +181 -0
  24. data/.cursor/commands/speckit.constitution.md +84 -0
  25. data/.cursor/commands/speckit.implement.md +135 -0
  26. data/.cursor/commands/speckit.plan.md +90 -0
  27. data/.cursor/commands/speckit.specify.md +258 -0
  28. data/.cursor/commands/speckit.tasks.md +137 -0
  29. data/.cursor/commands/speckit.taskstoissues.md +30 -0
  30. data/.cursor/rules/specify-rules.mdc +32 -0
  31. data/.rubocop.yml +118 -0
  32. data/.specify/memory/constitution.md +130 -0
  33. data/.specify/scripts/bash/check-prerequisites.sh +166 -0
  34. data/.specify/scripts/bash/common.sh +156 -0
  35. data/.specify/scripts/bash/create-new-feature.sh +297 -0
  36. data/.specify/scripts/bash/setup-plan.sh +61 -0
  37. data/.specify/scripts/bash/update-agent-context.sh +810 -0
  38. data/.specify/templates/agent-file-template.md +28 -0
  39. data/.specify/templates/checklist-template.md +40 -0
  40. data/.specify/templates/constitution-template.md +50 -0
  41. data/.specify/templates/plan-template.md +104 -0
  42. data/.specify/templates/spec-template.md +115 -0
  43. data/.specify/templates/tasks-template.md +251 -0
  44. data/CHANGELOG.md +26 -0
  45. data/LICENSE +9 -0
  46. data/README.md +274 -0
  47. data/Rakefile +19 -0
  48. data/Steepfile +9 -0
  49. data/examples/rails/jp_address_complement_demo/.gitignore +15 -0
  50. data/examples/rails/jp_address_complement_demo/.ruby-version +1 -0
  51. data/examples/rails/jp_address_complement_demo/Gemfile +22 -0
  52. data/examples/rails/jp_address_complement_demo/Gemfile.lock +252 -0
  53. data/examples/rails/jp_address_complement_demo/README.md +57 -0
  54. data/examples/rails/jp_address_complement_demo/Rakefile +6 -0
  55. data/examples/rails/jp_address_complement_demo/app/assets/images/.keep +0 -0
  56. data/examples/rails/jp_address_complement_demo/app/assets/stylesheets/application.css +1 -0
  57. data/examples/rails/jp_address_complement_demo/app/controllers/addresses_controller.rb +59 -0
  58. data/examples/rails/jp_address_complement_demo/app/controllers/application_controller.rb +4 -0
  59. data/examples/rails/jp_address_complement_demo/app/controllers/concerns/.keep +0 -0
  60. data/examples/rails/jp_address_complement_demo/app/helpers/application_helper.rb +2 -0
  61. data/examples/rails/jp_address_complement_demo/app/models/application_record.rb +3 -0
  62. data/examples/rails/jp_address_complement_demo/app/models/concerns/.keep +0 -0
  63. data/examples/rails/jp_address_complement_demo/app/views/addresses/index.html.erb +22 -0
  64. data/examples/rails/jp_address_complement_demo/app/views/addresses/prefecture.html.erb +20 -0
  65. data/examples/rails/jp_address_complement_demo/app/views/addresses/prefix.html.erb +25 -0
  66. data/examples/rails/jp_address_complement_demo/app/views/addresses/reverse.html.erb +30 -0
  67. data/examples/rails/jp_address_complement_demo/app/views/addresses/validate.html.erb +23 -0
  68. data/examples/rails/jp_address_complement_demo/app/views/layouts/application.html.erb +40 -0
  69. data/examples/rails/jp_address_complement_demo/app/views/pwa/manifest.json.erb +22 -0
  70. data/examples/rails/jp_address_complement_demo/app/views/pwa/service-worker.js +26 -0
  71. data/examples/rails/jp_address_complement_demo/bin/ci +6 -0
  72. data/examples/rails/jp_address_complement_demo/bin/dev +2 -0
  73. data/examples/rails/jp_address_complement_demo/bin/rails +4 -0
  74. data/examples/rails/jp_address_complement_demo/bin/rake +4 -0
  75. data/examples/rails/jp_address_complement_demo/bin/setup +35 -0
  76. data/examples/rails/jp_address_complement_demo/config/application.rb +42 -0
  77. data/examples/rails/jp_address_complement_demo/config/boot.rb +3 -0
  78. data/examples/rails/jp_address_complement_demo/config/ci.rb +15 -0
  79. data/examples/rails/jp_address_complement_demo/config/credentials.yml.enc +1 -0
  80. data/examples/rails/jp_address_complement_demo/config/database.yml +31 -0
  81. data/examples/rails/jp_address_complement_demo/config/environment.rb +5 -0
  82. data/examples/rails/jp_address_complement_demo/config/environments/development.rb +54 -0
  83. data/examples/rails/jp_address_complement_demo/config/environments/production.rb +67 -0
  84. data/examples/rails/jp_address_complement_demo/config/environments/test.rb +42 -0
  85. data/examples/rails/jp_address_complement_demo/config/initializers/content_security_policy.rb +29 -0
  86. data/examples/rails/jp_address_complement_demo/config/initializers/filter_parameter_logging.rb +8 -0
  87. data/examples/rails/jp_address_complement_demo/config/initializers/inflections.rb +16 -0
  88. data/examples/rails/jp_address_complement_demo/config/locales/en.yml +31 -0
  89. data/examples/rails/jp_address_complement_demo/config/puma.rb +39 -0
  90. data/examples/rails/jp_address_complement_demo/config/routes.rb +19 -0
  91. data/examples/rails/jp_address_complement_demo/config.ru +6 -0
  92. data/examples/rails/jp_address_complement_demo/db/migrate/20260228083709_create_jp_address_complement_postal_codes.rb +44 -0
  93. data/examples/rails/jp_address_complement_demo/db/schema.rb +33 -0
  94. data/examples/rails/jp_address_complement_demo/db/seeds.rb +24 -0
  95. data/examples/rails/jp_address_complement_demo/lib/tasks/.keep +0 -0
  96. data/examples/rails/jp_address_complement_demo/log/.keep +0 -0
  97. data/examples/rails/jp_address_complement_demo/public/400.html +135 -0
  98. data/examples/rails/jp_address_complement_demo/public/404.html +135 -0
  99. data/examples/rails/jp_address_complement_demo/public/406-unsupported-browser.html +135 -0
  100. data/examples/rails/jp_address_complement_demo/public/422.html +135 -0
  101. data/examples/rails/jp_address_complement_demo/public/500.html +135 -0
  102. data/examples/rails/jp_address_complement_demo/public/icon.png +0 -0
  103. data/examples/rails/jp_address_complement_demo/public/icon.svg +3 -0
  104. data/examples/rails/jp_address_complement_demo/public/robots.txt +1 -0
  105. data/examples/rails/jp_address_complement_demo/script/.keep +0 -0
  106. data/examples/rails/jp_address_complement_demo/storage/.keep +0 -0
  107. data/examples/rails/jp_address_complement_demo/vendor/.keep +0 -0
  108. data/lib/generators/jp_address_complement/install_generator.rb +34 -0
  109. data/lib/generators/jp_address_complement/templates/create_jp_address_complement_postal_codes.rb.erb +45 -0
  110. data/lib/jp_address_complement/address_record.rb +36 -0
  111. data/lib/jp_address_complement/configuration.rb +21 -0
  112. data/lib/jp_address_complement/importers/csv_importer.rb +148 -0
  113. data/lib/jp_address_complement/ken_all_downloader.rb +122 -0
  114. data/lib/jp_address_complement/models/postal_code.rb +21 -0
  115. data/lib/jp_address_complement/normalizer.rb +77 -0
  116. data/lib/jp_address_complement/prefecture.rb +105 -0
  117. data/lib/jp_address_complement/railtie.rb +27 -0
  118. data/lib/jp_address_complement/repositories/active_record_postal_code_repository.rb +78 -0
  119. data/lib/jp_address_complement/repositories/csv_postal_code_repository.rb +200 -0
  120. data/lib/jp_address_complement/repositories/postal_code_repository.rb +36 -0
  121. data/lib/jp_address_complement/searcher.rb +85 -0
  122. data/lib/jp_address_complement/validators/address_validator.rb +41 -0
  123. data/lib/jp_address_complement/version.rb +6 -0
  124. data/lib/jp_address_complement.rb +129 -0
  125. data/lib/tasks/jp_address_complement.rake +32 -0
  126. data/rbs_collection.lock.yaml +380 -0
  127. data/rbs_collection.yaml +19 -0
  128. data/sig/generated/generators/jp_address_complement/install_generator.rbs +18 -0
  129. data/sig/generated/jp_address_complement/configuration.rbs +18 -0
  130. data/sig/generated/jp_address_complement/importers/csv_importer.rbs +78 -0
  131. data/sig/generated/jp_address_complement/ken_all_downloader.rbs +49 -0
  132. data/sig/generated/jp_address_complement/normalizer.rbs +37 -0
  133. data/sig/generated/jp_address_complement/prefecture.rbs +27 -0
  134. data/sig/generated/jp_address_complement/railtie.rbs +8 -0
  135. data/sig/generated/jp_address_complement/repositories/active_record_postal_code_repository.rbs +38 -0
  136. data/sig/generated/jp_address_complement/repositories/csv_postal_code_repository.rbs +100 -0
  137. data/sig/generated/jp_address_complement/repositories/postal_code_repository.rbs +29 -0
  138. data/sig/generated/jp_address_complement/searcher.rbs +43 -0
  139. data/sig/generated/jp_address_complement/validators/address_validator.rbs +24 -0
  140. data/sig/generated/jp_address_complement/version.rbs +5 -0
  141. data/sig/generated/jp_address_complement.rbs +84 -0
  142. data/sig/manual/address_record.rbs +40 -0
  143. data/sig/manual/gem_rubyzip.rbs +17 -0
  144. data/sig/manual/postal_code.rbs +9 -0
  145. data/sig/manual/stdlib_csv_invalid_encoding_error.rbs +5 -0
  146. data/sig/manual/stdlib_net_http.rbs +33 -0
  147. data/sig/manual/stdlib_openuri.rbs +9 -0
  148. data/sig/manual/stdlib_tmpdir.rbs +4 -0
  149. data/specs/001-jp-address-complement-gem/checklists/requirements.md +36 -0
  150. data/specs/001-jp-address-complement-gem/contracts/public-api.md +209 -0
  151. data/specs/001-jp-address-complement-gem/data-model.md +207 -0
  152. data/specs/001-jp-address-complement-gem/plan.md +124 -0
  153. data/specs/001-jp-address-complement-gem/quickstart.md +151 -0
  154. data/specs/001-jp-address-complement-gem/research.md +139 -0
  155. data/specs/001-jp-address-complement-gem/spec.md +153 -0
  156. data/specs/001-jp-address-complement-gem/tasks.md +279 -0
  157. data/specs/002-rbs-type-annotations/checklists/requirements.md +37 -0
  158. data/specs/002-rbs-type-annotations/contracts/rbs-public-api.md +116 -0
  159. data/specs/002-rbs-type-annotations/data-model.md +119 -0
  160. data/specs/002-rbs-type-annotations/plan.md +116 -0
  161. data/specs/002-rbs-type-annotations/quickstart.md +105 -0
  162. data/specs/002-rbs-type-annotations/research.md +173 -0
  163. data/specs/002-rbs-type-annotations/spec.md +125 -0
  164. data/specs/002-rbs-type-annotations/tasks.md +189 -0
  165. data/specs/003-csv-remove-obsolete/checklists/requirements.md +34 -0
  166. data/specs/003-csv-remove-obsolete/contracts/csv-import.md +41 -0
  167. data/specs/003-csv-remove-obsolete/data-model.md +47 -0
  168. data/specs/003-csv-remove-obsolete/plan.md +73 -0
  169. data/specs/003-csv-remove-obsolete/quickstart.md +40 -0
  170. data/specs/003-csv-remove-obsolete/research.md +71 -0
  171. data/specs/003-csv-remove-obsolete/spec.md +85 -0
  172. data/specs/003-csv-remove-obsolete/tasks.md +167 -0
  173. data/specs/004-prefecture-code-reverse-lookup/checklists/requirements.md +34 -0
  174. data/specs/004-prefecture-code-reverse-lookup/contracts/public-api-prefecture-and-reverse.md +122 -0
  175. data/specs/004-prefecture-code-reverse-lookup/data-model.md +81 -0
  176. data/specs/004-prefecture-code-reverse-lookup/plan.md +92 -0
  177. data/specs/004-prefecture-code-reverse-lookup/quickstart.md +91 -0
  178. data/specs/004-prefecture-code-reverse-lookup/research.md +62 -0
  179. data/specs/004-prefecture-code-reverse-lookup/spec.md +120 -0
  180. data/specs/004-prefecture-code-reverse-lookup/tasks.md +190 -0
  181. metadata +451 -0
@@ -0,0 +1,120 @@
1
+ # Feature Specification: 都道府県コード変換・住所逆引き(住所→郵便番号)
2
+
3
+ **Feature Branch**: `004-prefecture-code-reverse-lookup`
4
+ **Created**: 2026-02-23
5
+ **Status**: Draft
6
+ **Input**: User description: "都道府県のコード変換、逆引き(住所→郵便番号)の機能を提供します"
7
+
8
+ ## User Scenarios & Testing *(mandatory)*
9
+
10
+ ### User Story 1 - 都道府県コードから都道府県名を取得する (Priority: P1)
11
+
12
+ 利用者が都道府県コード(2桁の数値またはゼロパディング文字列)を渡すと、対応する都道府県名(例: 「東京都」)を取得できる。外部システムや既存DBが都道府県をコードで保持している場合に、表示用の名称へ変換するために使用する。
13
+
14
+ **Why this priority**: コード→名称の変換は、表示・帳票・API応答で頻出する基本ニーズであり、この機能単体で即時価値がある。
15
+
16
+ **Independent Test**: 都道府県コード「13」を渡して「東京都」が返ることを確認することで、独立してテスト可能。
17
+
18
+ **Acceptance Scenarios**:
19
+
20
+ 1. **Given** 有効な都道府県コード(例: 13 または "13")を与えた時、**When** コードから名称を取得すると、**Then** 対応する都道府県名(「東京都」)が返る。
21
+ 2. **Given** ゼロパディングされた文字列(例: "01")を与えた時、**When** コードから名称を取得すると、**Then** 対応する都道府県名(「北海道」)が返る。
22
+ 3. **Given** 存在しないコード(例: 99)または範囲外を与えた時、**When** コードから名称を取得すると、**Then** nil が返る(例外は発生しない)。
23
+ 4. **Given** nil や空文字を与えた時、**When** コードから名称を取得すると、**Then** エラーを発生させず、nil が返る。
24
+
25
+ ---
26
+
27
+ ### User Story 2 - 都道府県名から都道府県コードを取得する (Priority: P2)
28
+
29
+ 利用者が都道府県名(例: 「東京都」「北海道」)を渡すと、対応する都道府県コードを2桁の文字列(例: "13", "01")で取得できる。フォームで都道府県名を選択した後に、DB保存用のコードへ変換するために使用する。
30
+
31
+ **Why this priority**: 名称→コードは、既存の「郵便番号→住所」フローと組み合わせてデータ正規化に使われるため、P1の次に重要。
32
+
33
+ **Independent Test**: 都道府県名「東京都」を渡して2桁の文字列「13」が返ることを確認することで、独立してテスト可能。
34
+
35
+ **Acceptance Scenarios**:
36
+
37
+ 1. **Given** 正式な都道府県名(例: 「東京都」「北海道」)を与えた時、**When** 名称からコードを取得すると、**Then** 対応する都道府県コードが2桁の文字列(例: "13", "01")で返る。
38
+ 2. **Given** 省略表記(例: 「東京」「北海」)や正式名称以外の文字列を与えた時、**When** 名称からコードを取得すると、**Then** nil が返る(正式名称のみ受け付けるため)。
39
+ 3. **Given** 存在しない都道府県名や曖昧な文字列を与えた時、**When** 名称からコードを取得すると、**Then** nil が返る(例外は発生しない)。
40
+ 4. **Given** nil や空文字を与えた時、**When** 名称からコードを取得すると、**Then** エラーを発生させず、nil が返る。
41
+
42
+ ---
43
+
44
+ ### User Story 3 - 住所(分離フィールド)から郵便番号(候補)を取得する(逆引き) (Priority: P3)
45
+
46
+ 利用者が都道府県名・市区町村名・町域名を**分離した形**(連結した1文字列ではなく、それぞれ別フィールド)で渡すと、その住所に対応する郵便番号の候補一覧を取得できる。最低限、都道府県と市区町村が揃っている場合にのみ検索を行う。町域は任意で、指定されていれば結果を絞り込む。住所は分かっているが郵便番号が不明なデータの補完や、バリデーション・データ整形に使用する。
47
+
48
+ **Why this priority**: 既存の「郵便番号→住所」の逆方向の検索であり、データ入力の双方向補完や既存データの正規化に役立つ。
49
+
50
+ **Independent Test**: 都道府県「東京都」、市区町村「千代田区」、町域「千代田」を分離して渡し、対応する郵便番号(例: 1000001)が候補に含まれることを確認することで、独立してテスト可能。
51
+
52
+ **Acceptance Scenarios**:
53
+
54
+ 1. **Given** 都道府県・市区町村・町域を分離したフィールドで渡し(例: 都道府県「東京都」、市区町村「千代田区」、町域「千代田」)、**When** 住所から郵便番号を検索すると、**Then** その住所に対応する郵便番号の候補(1件以上)が返る。
55
+ 2. **Given** 都道府県と市区町村のみを渡し、町域は省略した時、**When** 住所から郵便番号を検索すると、**Then** その都道府県・市区町村に属する郵便番号の候補一覧が返る。
56
+ 3. **Given** 都道府県のみ、または市区町村が欠けているなど、都道府県+市区町村が揃っていない時、**When** 住所から郵便番号を検索すると、**Then** 空の候補リストが返る(検索は行わない)。
57
+ 4. **Given** 複数の町域が同一の都道府県・市区町村・町域の組み合わせで存在する場合、**When** 住所から郵便番号を検索すると、**Then** 該当する複数の郵便番号が候補として返る。
58
+ 5. **Given** 存在しないまたはデータにない住所の組み合わせを与えた時、**When** 住所から郵便番号を検索すると、**Then** 空の候補リストが返る。
59
+ 6. **Given** 必須フィールド(都道府県・市区町村)が nil や空の時、**When** 住所から郵便番号を検索すると、**Then** エラーを発生させず、空の結果が返る。
60
+
61
+ ---
62
+
63
+ ### Edge Cases
64
+
65
+ - 都道府県コードの体系が複数ある場合(JIS X 0401 等)、どの体系を採用するかは仕様で明示し、一貫して同じ体系でコード⇔名称を扱う。
66
+ - 都道府県名は正式名称(「東京都」「北海道」等)のみを受け付ける。省略表記(「東京」「北海」等)は許容しない。
67
+ - 逆引きで、住所が複数郵便番号にまたがる場合(例: 大口事業所と一般町域)は、該当する郵便番号をすべて候補として返す。
68
+ - 逆引きの入力は、連結した住所文字列ではなく、都道府県名・市区町村名・町域名を**分離したフィールド**で受け取る。都道府県+市区町村が揃っている場合にのみ検索を実行し、それ未満(都道府県のみ等)の場合は空の候補リストを返す。町域は任意で、指定時は結果を絞り込む。照合は都道府県・市区町村・町域の各フィールドと住所データの対応する項目を**完全一致**で行う。入力は住所データと同じ表記を前提とする。
69
+ - 不正入力(nil、空文字、型の不一致)に対しては例外を投げず、空の結果で応答する。コード→名称・名称→コードで該当なしのときは nil を返す。逆引きで該当なしや入力不十分のときは空の候補リスト(0件のリスト)を返す。
70
+
71
+ ## Requirements *(mandatory)*
72
+
73
+ ### Functional Requirements
74
+
75
+ - **FR-001**: システムは都道府県コード(2桁、01–47 の範囲を想定)を受け取り、対応する都道府県名を返さなければならない。
76
+ - **FR-002**: 都道府県コードは数値またはゼロパディングされた文字列(例: "01", "13")のいずれでも受け付け、同一の都道府県名を返さなければならない。
77
+ - **FR-003**: 存在しない都道府県コードや範囲外の値を渡された場合、例外を発生させず、nil を返さなければならない。
78
+ - **FR-004**: システムは都道府県名を**正式名称のみ**受け取り、対応する都道府県コードを返さなければならない。戻り値は2桁の文字列(例: "01", "13")とし、ゼロパディングを維持する。省略表記(「東京」「北海」等)は受け付けない。
79
+ - **FR-005**: 都道府県名の照合は正式名称(「東京都」「北海道」等)と完全一致で行い、省略表記は許容しない。
80
+ - **FR-006**: 正式名称に一致しない都道府県名や曖昧な文字列を渡された場合、例外を発生させず、nil を返さなければならない。
81
+ - **FR-007**: システムは逆引きの入力として、都道府県名・市区町村名・町域名を**分離したフィールド**(連結文字列ではない)で受け取り、その住所に対応する郵便番号の候補一覧を返さなければならない。
82
+ - **FR-008**: 逆引きは都道府県+市区町村が揃っている場合にのみ検索を実行する。都道府県・市区町村・町域の各フィールドは住所データの対応する項目と**完全一致**で照合しなければならない。町域が指定されていない場合は都道府県+市区町村のみで照合する。入力は住所データと同じ表記を前提とする。
83
+ - **FR-009**: 一つの住所(都道府県・市区町村・町域の組み合わせ)に複数の郵便番号が対応する場合、該当するすべての郵便番号を候補として返さなければならない。
84
+ - **FR-010**: 逆引きで都道府県または市区町村が欠けている場合、該当する住所が存在しない場合、または必須フィールドが空の場合は、検索を行わず空の候補リストを返さなければならない。
85
+ - **FR-011**: コード→名称・名称→コードでは、該当なし・不正入力時は例外を発生させず nil を返さなければならない。逆引きでは、該当なし・入力不十分時は空の候補リストを返し、不正入力時は例外を発生させず空の候補リストを返さなければならない。
86
+ - **FR-012**: 都道府県コードの体系(例: JIS X 0401)は仕様で明示し、コード変換はその体系に従って一貫して動作しなければならない。
87
+
88
+ ### Key Entities
89
+
90
+ - **都道府県 (Prefecture)**: 都道府県コードと都道府県名の対応を表す。47都道府県分のコード・名称ペアが存在する。既存の住所データ(郵便番号データ)と整合したコード体系を用いる。名称→コードの戻り値は2桁の文字列(ゼロパディング維持)とする。
91
+ - **逆引き入力 (Reverse lookup input)**: 都道府県名・市区町村名・町域名を**分離したフィールド**で受け取る構造。連結した1文字列ではなく、住所レコードの都道府県名・市区町村名・町域名に相当する属性を個別に指定する。都道府県+市区町村が必須で、町域は任意。各フィールドは住所データと完全一致で照合されるため、データと同じ表記で渡すことを前提とする。
92
+ - **郵便番号候補 (Postal code candidates)**: 逆引きの結果として返される、住所に対応する郵便番号のリスト。1件または複数件となりうる。
93
+
94
+ ## Success Criteria *(mandatory)*
95
+
96
+ ### Measurable Outcomes
97
+
98
+ - **SC-001**: 都道府県コードから名称への変換が 5 ミリ秒以内に完了する(単一呼び出し)。コード⇔名称は DB に依存しないため、常に満たすことを想定する。
99
+ - **SC-002**: 都道府県名からコードへの変換が 5 ミリ秒以内に完了する(単一呼び出し)。同上。
100
+ - **SC-003**: 住所(都道府県・市区町村・町域を分離した入力)からの郵便番号逆引きが、既存の住所データとインデックスが利用可能な状態で 50 ミリ秒以内に完了する(99パーセンタイル)。
101
+ - **SC-004**: 47都道府県すべてについて、コード⇔名称の変換が正しく行える。
102
+ - **SC-005**: 日本郵便の公式データに含まれる代表的な住所について、分離フィールドで逆引きした際に正しい郵便番号候補が返る(サンプル検証で確認可能)。
103
+
104
+ ## Assumptions
105
+
106
+ - 都道府県コードは JIS X 0401(2桁、01–47)を採用する。既存の住所テーブルに都道府県コードが含まれている場合は、そのコード体系に合わせる。都道府県名→コードの戻り値は2桁の文字列("01", "13" 等)とし、ゼロパディングを維持する。
107
+ - 逆引きに用いる住所データは、既存機能(郵便番号→住所)と同じデータソース(日本郵便の郵便番号データに基づくDB)を利用する。新たに別データソースを導入しない。
108
+ - 都道府県名は正式名称(「東京都」「北海道」等)のみを受け付ける。省略表記(「東京」「北海」等)は許容しない。
109
+ - 逆引きの入力は都道府県・市区町村・町域を分離したフィールドで渡し、各フィールドは UTF-8 の文字列を前提とする。照合は完全一致のため、住所データ(郵便番号データ)と同じ表記で渡すことを前提とする。読みがなのみの検索は本機能のスコープ外とする。
110
+ - 該当なし・不正入力時は例外を発生させず、スカラー検索は nil、リスト検索は空配列で返す。Constitution 原則 V の「戻り値の型の一貫性」は、この nil / 空配列の使い分けで満たすものとする。
111
+
112
+ ## Clarifications
113
+
114
+ ### Session 2026-02-23
115
+
116
+ - Q: 逆引きで住所が短すぎる場合(都道府県のみ等)の扱い(件数上限 vs 最低一致条件)はどうするか? → A: 都道府県+市区町村を必須とする(B)。さらに、入力は連結した1文字列ではなく、都道府県・市区町村・町域を分離した構造(住所レコードの都道府県名・市区町村名・町域名に相当するフィールド)で受け取るインターフェースとする。
117
+ - Q: 都道府県名の省略表記(「東京」等)の許容範囲を仕様でどう決めるか? → A: 正式名称のみ受け付ける。省略表記は許容しない(C)。
118
+ - Q: 都道府県コード→名称・名称→コードで該当なしのときの戻り値の形はどうするか? → A: 該当なしのときは nil を返す(名称・コードどちらも)(A)。
119
+ - Q: 逆引きで都道府県・市区町村・町域の各フィールドを住所データとどう照合するか? → A: 各フィールドとも**完全一致**で照合する。入力はデータと同じ表記を前提とする(A)。
120
+ - Q: 都道府県名→コードの戻り値の型(数値か文字列か)はどうするか? → A: 2桁の文字列で返す(例: "13", "01")。ゼロパディングを維持する(B)。
@@ -0,0 +1,190 @@
1
+ # Tasks: 都道府県コード変換・住所逆引き(住所→郵便番号)
2
+
3
+ **Input**: Design documents from `/specs/004-prefecture-code-reverse-lookup/`
4
+ **Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/
5
+
6
+ **Tests**: Constitution に従い TDD を採用。各ユーザーストーリーでテストを先に書き、失敗を確認してから実装する。
7
+
8
+ **Organization**: ユーザーストーリーごとにタスクをグループ化し、各ストーリーを独立して実装・テストできるようにする。
9
+
10
+ ## Format: `[ID] [P?] [Story?] Description`
11
+
12
+ - **[P]**: 並列実行可能(別ファイル、未完了タスクへの依存なし)
13
+ - **[Story]**: ユーザーストーリー(US1, US2, US3)
14
+ - 説明には対象ファイルパスを含める
15
+
16
+ ## Path Conventions
17
+
18
+ - ライブラリ: `lib/jp_address_complement/`, `spec/` をリポジトリルートに使用(plan.md に準拠)
19
+
20
+ ---
21
+
22
+ ## Phase 1: Setup(ベースライン確認)
23
+
24
+ **Purpose**: 既存 gem のテスト・Lint が通ることを確認し、004 実装の前提を満たす
25
+
26
+ - [x] T001 [P] 既存の RSpec がすべてパスすることを確認する(`bundle exec rspec`)
27
+ - [ ] T002 [P] 既存の Rubocop がパスすることを確認する(`bundle exec rubocop`)
28
+ - [x] T003 既存の SimpleCov カバレッジを確認し、90% 以上であることを記録する
29
+
30
+ **Checkpoint**: 現状の green ベースラインが取れていること
31
+
32
+ ---
33
+
34
+ ## Phase 2: Foundational(全ストーリー共通の前提)
35
+
36
+ **Purpose**: 本機能では新規 DB や共通インフラは追加しない。Phase 1 完了後、各ユーザーストーリーに進める。
37
+
38
+ **Checkpoint**: Phase 1 完了後、User Story 1 から順に実装開始可能
39
+
40
+ ---
41
+
42
+ ## Phase 3: User Story 1 - 都道府県コードから都道府県名を取得する (Priority: P1) 🎯 MVP
43
+
44
+ **Goal**: 都道府県コード(2桁・数値または文字列)を渡すと都道府県名を返す。該当なし・不正入力時は nil。
45
+
46
+ **Independent Test**: `JpAddressComplement.prefecture_name_from_code("13")` が `"東京都"` を返すことを確認する。
47
+
48
+ ### Tests for User Story 1(TDD: 先にテストを書き、失敗を確認する)
49
+
50
+ - [x] T004 [P] [US1] `prefecture_name_from_code` のスペックを追加する(有効コード・ゼロパッド・存在しないコード・nil・空文字。47都道府県すべてのコード⇔名称が正しく動作することを 1 件以上の代表例で検証し、必要に応じて全件ループで検証する) in `spec/jp_address_complement/prefecture_spec.rb`
51
+
52
+ ### Implementation for User Story 1
53
+
54
+ - [x] T005 [US1] JIS X 0401 の 47 都道府県マッピング定数と `name_from_code` を実装する in `lib/jp_address_complement/prefecture.rb`
55
+ - [x] T006 [US1] モジュールメソッド `JpAddressComplement.prefecture_name_from_code` を追加する in `lib/jp_address_complement.rb`
56
+
57
+ **Checkpoint**: User Story 1 が単体で動作し、`prefecture_name_from_code("13")` で "東京都" が返る
58
+
59
+ ---
60
+
61
+ ## Phase 4: User Story 2 - 都道府県名から都道府県コードを取得する (Priority: P2)
62
+
63
+ **Goal**: 都道府県の正式名称を渡すと 2 桁のコード文字列を返す。該当なし・省略表記・不正入力時は nil。
64
+
65
+ **Independent Test**: `JpAddressComplement.prefecture_code_from_name("東京都")` が `"13"` を返すことを確認する。
66
+
67
+ ### Tests for User Story 2(TDD: 先にテストを書き、失敗を確認する)
68
+
69
+ - [x] T007 [P] [US2] `prefecture_code_from_name` のスペックを追加する(正式名称・省略表記で nil・存在しない・nil・空文字。47都道府県すべて名称→コードが正しく動作することを代表例または全件で検証する) in `spec/jp_address_complement/prefecture_spec.rb`
70
+
71
+ ### Implementation for User Story 2
72
+
73
+ - [x] T008 [US2] `code_from_name` と名称→コード用マッピングを追加する in `lib/jp_address_complement/prefecture.rb`
74
+ - [x] T009 [US2] モジュールメソッド `JpAddressComplement.prefecture_code_from_name` を追加する in `lib/jp_address_complement.rb`
75
+
76
+ **Checkpoint**: User Story 2 が単体で動作し、`prefecture_code_from_name("東京都")` で "13" が返る
77
+
78
+ ---
79
+
80
+ ## Phase 5: User Story 3 - 住所(分離フィールド)から郵便番号候補を取得する(逆引き) (Priority: P3)
81
+
82
+ **Goal**: pref・city・town(任意)を分離引数で渡すと、完全一致で検索し郵便番号の配列を返す。入力不十分・該当なし時は []。
83
+
84
+ **Independent Test**: `JpAddressComplement.search_postal_codes_by_address(pref: "東京都", city: "千代田区", town: "千代田")` で対応する郵便番号が含まれることを確認する。
85
+
86
+ ### Tests for User Story 3(TDD: 先にテストを書き、失敗を確認する)
87
+
88
+ - [x] T010 [P] [US3] `find_postal_codes_by_address` のスペックを追加する(pref+city+town / pref+city のみ / 入力不十分で [] / 該当なしで []) in `spec/repositories/active_record_postal_code_repository_spec.rb`
89
+ - [x] T011 [P] [US3] `search_postal_codes_by_address` のスペックを追加する(FakeRepository に `find_postal_codes_by_address` をスタブまたは実装して使用。正常・空・nil/空文字で [] のケースを含める) in `spec/searcher_spec.rb`
90
+
91
+ ### Implementation for User Story 3
92
+
93
+ - [x] T012 [US3] `find_postal_codes_by_address(pref:, city:, town: nil)` をインターフェースに追加する in `lib/jp_address_complement/repositories/postal_code_repository.rb`
94
+ - [x] T013 [US3] `find_postal_codes_by_address` を完全一致クエリで実装する(重複除く) in `lib/jp_address_complement/repositories/active_record_postal_code_repository.rb`
95
+ - [x] T014 [US3] `search_postal_codes_by_address(pref:, city:, town: nil)` を追加し Repository を呼ぶ in `lib/jp_address_complement/searcher.rb`
96
+ - [x] T015 [US3] モジュールメソッド `JpAddressComplement.search_postal_codes_by_address` を追加する in `lib/jp_address_complement.rb`
97
+
98
+ **Checkpoint**: User Story 3 が単体で動作し、逆引きで郵便番号配列が返る
99
+
100
+ ---
101
+
102
+ ## Phase 6: Polish & Cross-Cutting Concerns
103
+
104
+ **Purpose**: 複数ストーリーにまたがる仕上げ
105
+
106
+ - [x] T016 [P] 新規 API 3 件(prefecture_name_from_code, prefecture_code_from_name, search_postal_codes_by_address)を CHANGELOG に追記する
107
+ - [ ] T017 全ファイルで `bundle exec rubocop` を実行し、警告・エラーを解消する
108
+ - [x] T018 全テストを実行し、SimpleCov でカバレッジ 90% 以上を満たすことを確認する(`bundle exec rspec`)
109
+ - [ ] T019 [P] quickstart.md の手順で 3 API の動作を手動確認する(任意)
110
+
111
+ ---
112
+
113
+ ## Dependencies & Execution Order
114
+
115
+ ### Phase Dependencies
116
+
117
+ - **Phase 1 (Setup)**: 依存なし。即時開始可能。
118
+ - **Phase 2 (Foundational)**: 本機能ではタスクなし。Phase 1 完了後に Phase 3 へ。
119
+ - **Phase 3 (US1)**: Phase 1 完了後開始。他ストーリーに非依存。
120
+ - **Phase 4 (US2)**: Phase 3 完了後推奨(同一 Prefecture モジュールを拡張)。独立テスト可能。
121
+ - **Phase 5 (US3)**: Phase 1 完了後開始可能(Repository/Searcher は既存)。Phase 3/4 と並行も可。
122
+ - **Phase 6 (Polish)**: 実装したいストーリーがすべて完了してから実施。
123
+
124
+ ### User Story Dependencies
125
+
126
+ - **US1 (P1)**: Phase 1 完了後から開始可能。他ストーリーに非依存。
127
+ - **US2 (P2)**: Prefecture モジュールを拡張するため、US1 完了後の方が自然。独立テスト可能。
128
+ - **US3 (P3)**: 既存 Repository/Searcher の拡張のため、Phase 1 完了後から開始可能。US1/US2 と並行可能。
129
+
130
+ ### Within Each User Story
131
+
132
+ - テストを先に追加し、失敗することを確認してから実装する(TDD)。
133
+ - 実装は contracts/data-model に従う。
134
+
135
+ ### Parallel Opportunities
136
+
137
+ - T001 と T002 は並列可能。
138
+ - Phase 3 の T004 と、Phase 4 の T007、Phase 5 の T010・T011 はそれぞれ別ファイルのため、ストーリー単位で並列化可能。
139
+ - Phase 6 の T016 と T019 は他と並列可能。
140
+
141
+ ---
142
+
143
+ ## Parallel Example: User Story 1
144
+
145
+ ```bash
146
+ # Phase 3: まずテストを追加して RED を確認
147
+ Task T004: spec/jp_address_complement/prefecture_spec.rb に prefecture_name_from_code のスペックを追加
148
+
149
+ # 続けて実装で GREEN
150
+ Task T005: lib/jp_address_complement/prefecture.rb に定数と name_from_code を実装
151
+ Task T006: lib/jp_address_complement.rb に prefecture_name_from_code を追加
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Implementation Strategy
157
+
158
+ ### MVP First(User Story 1 のみ)
159
+
160
+ 1. Phase 1 を完了する。
161
+ 2. Phase 3(US1)のテストを追加し、失敗を確認する。
162
+ 3. Phase 3(US1)の実装を完了する。
163
+ 4. **STOP and VALIDATE**: `prefecture_name_from_code("13")` が "東京都" を返すことを確認する。
164
+ 5. 必要ならデモ・コミットする。
165
+
166
+ ### Incremental Delivery
167
+
168
+ 1. Phase 1 → ベースライン確認。
169
+ 2. Phase 3(US1)→ コード→名称を検証してリリース候補。
170
+ 3. Phase 4(US2)→ 名称→コードを追加して検証。
171
+ 4. Phase 5(US3)→ 逆引きを追加して検証。
172
+ 5. Phase 6 → CHANGELOG・Rubocop・カバレッジで仕上げ。
173
+
174
+ ### Parallel Team Strategy
175
+
176
+ - Phase 1 を共有で完了したあと、
177
+ - 担当 A: Phase 3(US1)→ Phase 4(US2)(Prefecture を順に拡張)
178
+ - 担当 B: Phase 5(US3)(Repository + Searcher + モジュールメソッド)
179
+ - 各ストーリーは独立してテスト可能。
180
+
181
+ ---
182
+
183
+ ## Notes
184
+
185
+ - [P] タスクは別ファイルで依存がなければ並列実行可能。
186
+ - [USn] は spec.md のユーザーストーリーとの対応を示す。
187
+ - 各ストーリーは単体で完了・テストできるようにする。
188
+ - 実装前にテストの失敗を確認する(TDD)。
189
+ - タスクまたは論理的なまとまりごとにコミットする。
190
+ - チェックポイントでストーリー単位の動作確認を行う。