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,189 @@
1
+ # Tasks: RBS 型注釈の導入
2
+
3
+ **Input**: Design documents from `/specs/002-rbs-type-annotations/`
4
+ **Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/
5
+
6
+ **Organization**: User story ごとにタスクをグループ化し、各ストーリーを独立して実装・検証できるようにする。
7
+
8
+ ## Format: `[ID] [P?] [Story?] Description`
9
+
10
+ - **[P]**: 並行実行可能(別ファイル・依存なし)
11
+ - **[Story]**: ユーザーストーリー(US1, US2, US3)
12
+ - 説明には対象ファイルパスを明記する
13
+
14
+ ## Path Conventions
15
+
16
+ - リポジトリルート: `lib/`, `sig/`, `Steepfile`, `jp_address_complement.gemspec`
17
+ - 型定義: `sig/`, `sig/manual/`
18
+ - Rake: `lib/tasks/jp_address_complement.rake` または同等
19
+
20
+ ---
21
+
22
+ ## Phase 1: Setup(共有インフラ)
23
+
24
+ **Purpose**: RBS/Steep 用の開発依存と設定の追加
25
+
26
+ - [x] T001 Add development dependencies (rbs-inline, steep, rbs-rails, gem_rbs_collection) to jp_address_complement.gemspec per research.md
27
+ - [x] T002 Create Steepfile at project root with target :lib, signature "sig", check "lib" per research.md and plan.md
28
+ - [x] T003 Create sig/ and sig/manual/ directories (sig/manual/ for hand-written RBS not overwritten by rbs-inline)
29
+ - [x] T004 [P] Add rbs_collection.yaml or configure gem_rbs_collection at repo root when required(必要条件は research.md セクション 4「rbs_collection の設定」を参照)
30
+
31
+ ---
32
+
33
+ ## Phase 2: Foundational(全ストーリーの前提)
34
+
35
+ **Purpose**: Rake タスクと手動 RBS を整え、どのユーザーストーリーもここが完了するまで開始できない
36
+
37
+ **⚠️ CRITICAL**: この Phase が完了するまでユーザーストーリーの実装に着手しない
38
+
39
+ - [x] T005 Create sig/manual/address_record.rbs with all fields (postal_code, pref_code, pref, city, town, kana_pref, kana_city, kana_town, has_alias, is_partial, is_large_office) per contracts/rbs-public-api.md and data-model.md
40
+ - [x] T006 Add Rake task rbs:generate (bundle exec rbs-inline --output sig/ lib/) in lib/tasks/jp_address_complement.rake
41
+ - [x] T007 Add Rake task steep (bundle exec steep check) in lib/tasks/jp_address_complement.rake
42
+ - [x] T008 [P] Adjust .rubocop.yml if rbs-inline comments violate existing rules; document reason in comment (FR-010, no rubocop:disable)
43
+
44
+ **Checkpoint**: Foundation ready — T006・T007 完了後、`bundle exec rake rbs:generate` と `bundle exec rake steep` を一度手動実行し動作を確認する(CC-003: Rake タスクの検証は手動で代替)。以降、US1/US2/US3 の実装を開始可能。
45
+
46
+ ---
47
+
48
+ ## Phase 3: User Story 1 — Gem 利用者が型情報を参照して安全に呼び出せる (P1) 🎯 MVP
49
+
50
+ **Goal**: 公開 API のシグネチャが sig/ に存在し、steep check がエラーゼロで終了する。AddressRecord の型が明示され、誤った型の引数で Steep がエラーを報告する。
51
+
52
+ **Independent Test**: `bundle exec rake steep` がエラーゼロで終了する。sig/ に公開メソッドと AddressRecord の型が定義されている。誤った型(例: Integer)を渡すコードで Steep が型エラーを報告する。
53
+
54
+ ### Implementation for User Story 1
55
+
56
+ - [x] T009 [US1] Add rbs-inline annotations to lib/jp_address_complement.rb (configure, configuration, repository, search_by_postal_code, search_by_postal_code_prefix, valid_combination?, default_repository) per contracts/rbs-public-api.md
57
+ - [x] T010 [US1] Add rbs-inline annotations to lib/jp_address_complement/configuration.rb
58
+ - [x] T011 [US1] Add rbs-inline annotations to lib/jp_address_complement/normalizer.rb
59
+ - [x] T012 [US1] Add rbs-inline annotations to lib/jp_address_complement/searcher.rb
60
+ - [x] T013 [US1] Add rbs-inline annotations to lib/jp_address_complement/repositories/postal_code_repository.rb (find_by_code, find_by_prefix)
61
+ - [x] T014 [US1] Add rbs-inline annotations to lib/jp_address_complement/repositories/active_record_postal_code_repository.rb
62
+ - [x] T015 [US1] Run bundle exec rake rbs:generate and fix Steep errors (add sig/manual reference if needed, use # steep:ignore with reason only for false positives) until bundle exec rake steep exits 0
63
+ - [x] T016 [US1] Verify bundle exec rake steep exits 0, bundle exec rspec passes, bundle exec rubocop passes, and SimpleCov remains ≥90%
64
+
65
+ **Checkpoint**: User Story 1 完了 — 利用者向けに型情報が参照可能で、steep check が通る状態
66
+
67
+ ---
68
+
69
+ ## Phase 4: User Story 2 — Gem 開発者が rbs-inline コメントで型を維持できる (P1)
70
+
71
+ **Goal**: lib/ 配下のすべての .rb に rbs-inline アノテーションを付与し、rbs-inline で sig/ を生成できる。シグネチャ変更でアノテーションを更新しないと steep がエラーを出す。未注釈メソッドがあれば Steep が警告する。
72
+
73
+ **Independent Test**: lib/ の全 .rb に rbs-inline が付与されている。bundle exec rake rbs:generate で sig/ が更新される。bundle exec rake steep がエラーゼロで完了する。
74
+
75
+ ### Implementation for User Story 2
76
+
77
+ - [x] T017 [P] [US2] Add rbs-inline annotations to lib/jp_address_complement/address_record.rb (Data.define は manual を併用する想定で必要範囲のみでも可)
78
+ - [x] T018 [P] [US2] Add rbs-inline annotations to lib/jp_address_complement/version.rb and lib/jp_address_complement/railtie.rb
79
+ - [x] T019 [P] [US2] Add rbs-inline annotations to lib/jp_address_complement/models/postal_code.rb
80
+ - [x] T020 [P] [US2] Add rbs-inline annotations to lib/jp_address_complement/validators/address_validator.rb
81
+ - [x] T021 [P] [US2] Add rbs-inline annotations to lib/jp_address_complement/importers/csv_importer.rb
82
+ - [x] T022 [P] [US2] Add rbs-inline annotations to lib/generators/jp_address_complement/install_generator.rb
83
+ - [x] T023 [US2] Run bundle exec rake rbs:generate and ensure bundle exec rake steep exits 0; fix any new type errors
84
+ - [x] T024 [US2] Resolve any Steep warnings for unannotated methods so that FR-004 (型エラーゼロ) and SC-002 are met
85
+
86
+ **Checkpoint**: User Story 2 完了 — 開発者が rbs-inline で型を一括維持できる状態
87
+
88
+ ---
89
+
90
+ ## Phase 5: User Story 3 — CI で型チェックが自動実行される (P2)
91
+
92
+ **Goal**: PR 時に steep check が自動実行され、型エラーがあるとマージできないようにする。
93
+
94
+ **Independent Test**: CI 設定に steep check が含まれており、bundle exec rake steep が成功する。型エラーを意図的に入れた変更で CI が失敗する。
95
+
96
+ ### Implementation for User Story 3
97
+
98
+ - [x] T025 [US3] Add steep check to CI (e.g. .github/workflows/*.yml or project CI config) so that bundle exec rake steep is run and non-zero exit fails the job
99
+ - [x] T026 [US3] Document rake steep and rake rbs:generate in README or docs so contributors can run type check locally (align with specs/002-rbs-type-annotations/quickstart.md)
100
+
101
+ **Checkpoint**: User Story 3 完了 — CI で型安全性が継続的に保護される
102
+
103
+ ---
104
+
105
+ ## Phase 6: Polish & Cross-Cutting Concerns
106
+
107
+ **Purpose**: 複数ストーリーにまたがる仕上げ
108
+
109
+ - [x] T027 [P] Validate developer workflow per specs/002-rbs-type-annotations/quickstart.md (rbs:generate → steep の流れが動作すること)
110
+ - [x] T028 Update CHANGELOG for RBS/type annotations support if this is a user-facing change
111
+
112
+ ---
113
+
114
+ ## Dependencies & Execution Order
115
+
116
+ ### Phase Dependencies
117
+
118
+ - **Phase 1 (Setup)**: 依存なし — 即時開始可能
119
+ - **Phase 2 (Foundational)**: Phase 1 完了後に実施 — 全ユーザーストーリーをブロック
120
+ - **Phase 3 (US1)**: Phase 2 完了後に実施 — MVP
121
+ - **Phase 4 (US2)**: Phase 2 完了後に実施可能(US1 完了後が望ましい:公開 API が先に型付きになる)
122
+ - **Phase 5 (US3)**: Phase 3 または Phase 4 完了後(steep がローカルで通っていることが前提)
123
+ - **Phase 6 (Polish)**: 必要なストーリー完了後に実施
124
+
125
+ ### User Story Dependencies
126
+
127
+ - **US1 (P1)**: Phase 2 完了後から開始。他ストーリーに依存しない。
128
+ - **US2 (P1)**: Phase 2 完了後から開始。US1 完了後に進めると sig/ の状態が安定する。
129
+ - **US3 (P2)**: Phase 3 または 4 の完了後。CI に steep を追加するだけなので、US1 完了後でも実施可能。
130
+
131
+ ### Within Each User Story
132
+
133
+ - 公開 API(モジュールメソッド・Repository)の型を先に揃え、その後に内部実装の型を揃える。
134
+ - 各 Phase の最後で bundle exec rake steep / rspec / rubocop で検証する。
135
+
136
+ ### Parallel Opportunities
137
+
138
+ - Phase 1: T004 [P] は他と並行可能
139
+ - Phase 2: T008 [P] は他と並行可能
140
+ - Phase 4: T017〜T022 は [P] のため、別々のファイルを並行してアノテーション追加可能
141
+ - Phase 6: T027 [P], T028 は並行可能
142
+
143
+ ---
144
+
145
+ ## Parallel Example: User Story 2
146
+
147
+ ```text
148
+ # Phase 4 で複数ファイルに同時にアノテーションを追加する例
149
+ T017: lib/jp_address_complement/address_record.rb
150
+ T018: lib/jp_address_complement/version.rb, lib/jp_address_complement/railtie.rb
151
+ T019: lib/jp_address_complement/models/postal_code.rb
152
+ T020: lib/jp_address_complement/validators/address_validator.rb
153
+ T021: lib/jp_address_complement/importers/csv_importer.rb
154
+ T022: lib/generators/jp_address_complement/install_generator.rb
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Implementation Strategy
160
+
161
+ ### MVP First (User Story 1 Only)
162
+
163
+ 1. Phase 1: Setup を完了
164
+ 2. Phase 2: Foundational を完了(Rake タスク・sig/manual/address_record.rbs)
165
+ 3. Phase 3: User Story 1 を完了(公開 API の型・steep エラーゼロ)
166
+ 4. **STOP and VALIDATE**: bundle exec rake steep が 0、rspec 全件パス、rubocop パスを確認
167
+ 5. 必要ならこの時点でマージ・リリース可能(型情報提供の最小単位)
168
+
169
+ ### Incremental Delivery
170
+
171
+ 1. Setup + Foundational → 基盤完了
172
+ 2. US1 完了 → 利用者に型情報が提供され、steep が通る(MVP)
173
+ 3. US2 完了 → 開発者が全ファイルで rbs-inline を維持可能
174
+ 4. US3 完了 → CI で型チェックが必須に
175
+ 5. Polish で quickstart 検証・CHANGELOG 更新
176
+
177
+ ### Suggested MVP Scope
178
+
179
+ - **MVP**: Phase 1 + Phase 2 + Phase 3(User Story 1 まで)
180
+ - これで「Gem 利用者が型情報を参照して安全に呼び出せる」が満たされ、SC-001, SC-002, SC-003 の主要部分を達成できる。
181
+
182
+ ---
183
+
184
+ ## Notes
185
+
186
+ - 各タスクはチェックボックス・Task ID・必要時は [P]/[USn]・対象パスを明記している。
187
+ - テストタスクは spec で明示的に要求されていないため、独立検証は「bundle exec rake steep / rspec / rubocop」の実行で行う。
188
+ - コミットはタスク単位または論理的なまとまりで行うとよい。
189
+ - どのチェックポイントでも、その時点でストーリーを独立して検証可能。
@@ -0,0 +1,34 @@
1
+ # Specification Quality Checklist: CSVインポート時の消えたレコード削除
2
+
3
+ **Purpose**: Validate specification completeness and quality before proceeding to planning
4
+ **Created**: 2025-02-23
5
+ **Feature**: [spec.md](../spec.md)
6
+
7
+ ## Content Quality
8
+
9
+ - [x] No implementation details (languages, frameworks, APIs)
10
+ - [x] Focused on user value and business needs
11
+ - [x] Written for non-technical stakeholders
12
+ - [x] All mandatory sections completed
13
+
14
+ ## Requirement Completeness
15
+
16
+ - [x] No [NEEDS CLARIFICATION] markers remain
17
+ - [x] Requirements are testable and unambiguous
18
+ - [x] Success criteria are measurable
19
+ - [x] Success criteria are technology-agnostic (no implementation details)
20
+ - [x] All acceptance scenarios are defined
21
+ - [x] Edge cases are identified
22
+ - [x] Scope is clearly bounded
23
+ - [x] Dependencies and assumptions identified
24
+
25
+ ## Feature Readiness
26
+
27
+ - [x] All functional requirements have clear acceptance criteria
28
+ - [x] User scenarios cover primary flows
29
+ - [x] Feature meets measurable outcomes defined in Success Criteria
30
+ - [x] No implementation details leak into specification
31
+
32
+ ## Notes
33
+
34
+ - 本チェックリストは初回検証で全項目をパス済み。次のフェーズ(`/speckit.clarify` または `/speckit.plan`)に進む準備ができています。
@@ -0,0 +1,41 @@
1
+ # Contract: CSV インポート(CsvImporter と Rake タスク)
2
+
3
+ **Feature**: 003-csv-remove-obsolete
4
+ **Contract Type**: インポート API と Rake タスクの入出力
5
+
6
+ ---
7
+
8
+ ## 1. `JpAddressComplement::Importers::CsvImporter`
9
+
10
+ ### 1.1 コンストラクタ
11
+
12
+ - **シグネチャ**: `new(csv_path)`
13
+ - **引数**: `csv_path` — CSV ファイルのパス(String)
14
+ - **挙動**: 従来どおり。ファイル存在チェックは `import` 内で実施。
15
+
16
+ ### 1.2 `#import`
17
+
18
+ - **シグネチャ**: `import → インポート結果(件数情報を持つオブジェクトまたは Hash)`
19
+ - **戻り値(本機能で拡張)**: 次のいずれかを満たすこと。
20
+ - **必須**: 追加(新規)・更新・削除の件数を利用者コードから参照できること。
21
+ - 例: `{ inserted: Integer, updated: Integer, deleted: Integer }` または `Data.define(:inserted, :updated, :deleted)` のインスタンス。
22
+ - 簡易案: `{ upserted: Integer, deleted: Integer }`(upsert 総数と削除数のみ)。
23
+ - **例外**:
24
+ - ファイルが存在しない: `JpAddressComplement::ImportError`(既存どおり)。
25
+ - 空CSV(有効行が 0 件): インポートを実行せず、`JpAddressComplement::ImportError`(または同等)を発生させる。既存データは変更しない。
26
+ - **処理順**: (1) 空CSVでないか確認(有効行が 1 件もない場合はエラー)、(2) CSV 走査でキー集合を蓄積しつつバッチ upsert、(3) upsert が全て成功したら、キー集合に含まれない行を削除、(4) 件数を返す。
27
+ - **冪等性**: 同じCSVを複数回流しても、最終状態が同じになること(既存と同様)。
28
+
29
+ ### 1.3 一意キー
30
+
31
+ - upsert および削除対象の判定に使うキー: `(postal_code, pref_code, city, town)`(既存の `unique_by` と同一)。
32
+
33
+ ---
34
+
35
+ ## 2. Rake タスク `jp_address_complement:import`
36
+
37
+ - **起動**: `rake jp_address_complement:import CSV=/path/to/KEN_ALL.CSV`(既存どおり)。
38
+ - **環境変数**: `CSV` にファイルパスを指定。未指定の場合は既存どおりエラー。
39
+ - **出力(本機能で拡張)**: インポート完了時に、追加・更新・削除の件数を標準出力に表示する。
40
+ - 例: `インポート完了: 追加 1000 件, 更新 500 件, 削除 200 件` または `インポート完了: upsert 1500 件, 削除 200 件`。
41
+ - **エラー時**: 空CSVやファイル不存在の場合は、既存と同様にエラーメッセージを出力し、既存データは変更しない。
@@ -0,0 +1,47 @@
1
+ # Data Model: 003 CSVインポート時の消えたレコード削除
2
+
3
+ **Branch**: `003-csv-remove-obsolete`
4
+ **Date**: 2025-02-23
5
+
6
+ ---
7
+
8
+ ## 既存エンティティ(変更なし)
9
+
10
+ - **住所レコード(PostalCode / jp_address_complement_postal_codes)**: 001 の data-model に準拠。一意キーは `(postal_code, pref_code, city, town)`。
11
+ - **AddressRecord**: 検索結果の値オブジェクト。本機能では変更なし。
12
+
13
+ ---
14
+
15
+ ## 本機能で扱うデータ
16
+
17
+ ### 1. インポート中に保持する「今回CSVに出現したキー集合」
18
+
19
+ - **役割**: 削除フェーズで「この集合に含まれない行」をストアから削除するための判定に使う。
20
+ - **表現**: 一意キー4要素の組み合わせの集合。実装では `Set<[postal_code, pref_code, city, town]>` または同等(例: `Set<String>` でキーを文字列化したもの)。
21
+ - **ライフサイクル**: 1回の `import` 呼び出しの開始時に空で初期化し、CSV の有効行を処理するたびに追加。upsert 完了後に削除クエリの条件に使用し、メソッド終了後は破棄。
22
+ - **永続化**: しない(メモリのみ)。
23
+
24
+ ### 2. インポート結果(件数報告用)
25
+
26
+ - **役割**: FR-006 に基づき、追加・更新・削除の件数を利用者に報告する。
27
+ - **表現**: 次のいずれかで十分。
28
+ - **案A**: `{ inserted: Integer, updated: Integer, deleted: Integer }` のようにハッシュまたは Data オブジェクト。
29
+ - **案B**: `{ upserted: Integer, deleted: Integer }` のように「upsert した総数」と「削除した数」の2つ(DB が insert/update を区別して返さない場合はこちらが簡易)。
30
+ - **取得方法**: `CsvImporter#import` の戻り値。Rake タスクはこの戻り値を標準出力に整形して表示する。
31
+ - **永続化**: しない。ログに残すかは実装オプション。
32
+
33
+ ---
34
+
35
+ ## 削除対象の定義(FR-002 準拠)
36
+
37
+ - テーブル `jp_address_complement_postal_codes` の行のうち、その行の `(postal_code, pref_code, city, town)` が、**今回のCSVを走査して得た「出現したキー集合」に含まれない**ものが削除対象。
38
+ - 単一ストア(このテーブルのみ)を対象とし、他テーブル・他ソースは考慮しない。
39
+
40
+ ---
41
+
42
+ ## 状態遷移(インポート1回分)
43
+
44
+ 1. **開始**: キー集合を空で初期化。有効行数カウント 0。
45
+ 2. **CSV 走査**: 有効行ごとにキーを集合に追加し、バッチ upsert。有効行が 0 件の時点で検出したら空CSVとしてエラー終了(削除・upsert とも行わない)。
46
+ 3. **Upsert 完了後**: キー集合に含まれない行をテーブルから削除。削除件数を集計。
47
+ 4. **終了**: 追加(および/または更新)件数と削除件数を返す。
@@ -0,0 +1,73 @@
1
+ # Implementation Plan: CSVインポート時の消えたレコード削除
2
+
3
+ **Branch**: `003-csv-remove-obsolete` | **Date**: 2025-02-23 | **Spec**: [spec.md](./spec.md)
4
+ **Input**: Feature specification from `/specs/003-csv-remove-obsolete/spec.md`
5
+
6
+ ## Summary
7
+
8
+ KEN_ALL.CSV のフルインポート時に、今回のCSVに存在しない住所レコードをストアから削除する。削除は upsert が全て成功した後にのみ実行し、空CSVは拒否する。インポート完了時に追加・更新・削除件数を報告する。既存の `CsvImporter` を拡張し、一意キー `(postal_code, pref_code, city, town)` で「今回CSVに出現したキー集合」を保持したうえで、削除フェーズでそれに含まれない行を削除する。
9
+
10
+ ## Technical Context
11
+
12
+ **Language/Version**: Ruby 3.x(3.2 以上推奨)
13
+ **Primary Dependencies**: Rails 7.x、ActiveRecord、標準庫 CSV
14
+ **Storage**: 単一テーブル `jp_address_complement_postal_codes`(既存)
15
+ **Testing**: RSpec、SimpleCov 90% 以上
16
+ **Target Platform**: Rails アプリケーション(Rake タスク経由)
17
+ **Project Type**: Ruby gem(Rails Engine)
18
+ **Performance Goals**: 既存インポート時間の約2倍以内を目安(upsert + 削除の二相)
19
+ **Constraints**: Rubocop 100% パス、TDD 必須、既存一意キー `(postal_code, pref_code, city, town)` を変更しない
20
+ **Scale/Scope**: KEN_ALL 相当の約12万行〜14万行の CSV を想定
21
+
22
+ ## Constitution Check
23
+
24
+ *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
25
+
26
+ - [x] **I. Gem-First**: 変更は `JpAddressComplement::Importers::CsvImporter` および Rake タスクに閉じ、gem 責務内。
27
+ - [x] **II. TDD**: テストを先に書き、Red-Green-Refactor で実装する。カバレッジ 90% 以上維持。
28
+ - [x] **III. Rubocop**: 100% パス、disable コメント禁止。
29
+ - [x] **IV. データ整合性**: 冪等性維持。削除は「今回のCSVに出現しなかったキー」に限定し、upsert 成功後に実施。Constitution の「削除検出は現在の全郵便番号セットに含まれない行を削除」に準拠。
30
+ - [x] **V. 機能要件**: 郵便番号検索・前方一致・不一致検出は変更しない。インポートの振る舞い拡張のみ。
31
+ - [x] **VI. シンプルさ**: 単一ストア・二相(upsert → delete)で YAGNI を守る。
32
+
33
+ ## Project Structure
34
+
35
+ ### Documentation (this feature)
36
+
37
+ ```text
38
+ specs/003-csv-remove-obsolete/
39
+ ├── plan.md # This file
40
+ ├── research.md # Phase 0
41
+ ├── data-model.md # Phase 1
42
+ ├── quickstart.md # Phase 1
43
+ ├── contracts/ # Phase 1 (csv-import contract)
44
+ ├── checklists/
45
+ │ └── requirements.md
46
+ └── tasks.md # (/speckit.tasks - not created by plan)
47
+ ```
48
+
49
+ ### Source Code (repository root)
50
+
51
+ ```text
52
+ lib/jp_address_complement/
53
+ ├── importers/
54
+ │ └── csv_importer.rb # 拡張: 二相インポート・件数集計・空CSV拒否
55
+ ├── models/
56
+ │ └── postal_code.rb # 既存(変更なし)
57
+ └── ...
58
+
59
+ lib/tasks/
60
+ └── jp_address_complement.rake # 拡張: 件数報告出力(Rails 慣習)
61
+
62
+ spec/
63
+ ├── importers/
64
+ │ └── csv_importer_spec.rb # 拡張: 削除・空CSV・件数・失敗時
65
+ └── tasks/
66
+ └── import_task_spec.rb # 拡張: 出力メッセージ
67
+ ```
68
+
69
+ **Structure Decision**: 既存の gem レイアウトをそのまま使用。新規テーブルは追加せず、`CsvImporter` と Rake タスクの拡張のみで完結する。
70
+
71
+ ## Complexity Tracking
72
+
73
+ > 本機能では Constitution 違反はなく、追記なし。
@@ -0,0 +1,40 @@
1
+ # クイックスタート(003 で追加するインポートの振る舞い)
2
+
3
+ **Feature**: 003-csv-remove-obsolete
4
+ **前提**: 001 のクイックスタート(インストール・マイグレーション・初回インポート)が完了していること。
5
+
6
+ ---
7
+
8
+ ## インポートで「消えたレコード」が削除される
9
+
10
+ KEN_ALL.CSV の新版でフルインポートを実行すると、**今回のCSVに含まれない住所レコードは自動で削除**されます。
11
+
12
+ **手元に CSV がある場合(従来どおり):**
13
+
14
+ ```bash
15
+ bundle exec rake jp_address_complement:import CSV=/path/to/KEN_ALL.CSV
16
+ ```
17
+
18
+ **公式URLから zip を取得してそのままインポートする場合:**
19
+
20
+ ```bash
21
+ bundle exec rake jp_address_complement:import DOWNLOAD=1
22
+ ```
23
+
24
+ - `DOWNLOAD=1` を指定すると、[郵便番号データ(大字・小字)](https://www.post.japanpost.jp/zipcode/dl/oogaki/zip/ken_all.zip) をダウンロード・展開してからインポートします。
25
+ - 別の URL を使う場合は `KEN_ALL_ZIP_URL=<URL>` も指定できます。
26
+
27
+ - 以前のインポートで入っていたが、新しいCSVには存在しない行は、この1回のインポート完了後にテーブルから削除されます。
28
+ - 同じCSVを何度実行しても結果は同じです(冪等)。
29
+
30
+ ---
31
+
32
+ ## 空のCSVは拒否される
33
+
34
+ 有効行が 0 件のCSV(空ファイルやヘッダのみ)を指定した場合、インポートは実行されずエラーになります。既存の住所データは変更されません。
35
+
36
+ ---
37
+
38
+ ## インポート完了時に件数が表示される
39
+
40
+ 完了時には、追加・更新・削除の件数が標準出力に表示されます(例: 「追加 1000 件, 更新 500 件, 削除 200 件」)。運用時の確認やログに利用できます。
@@ -0,0 +1,71 @@
1
+ # Research: CSVインポート時の消えたレコード削除
2
+
3
+ **Branch**: `003-csv-remove-obsolete`
4
+ **Date**: 2025-02-23
5
+
6
+ ---
7
+
8
+ ## 1. 削除の実行タイミング(二相インポート)
9
+
10
+ **Decision**: upsert を全て成功させた後にのみ削除フェーズを実行する。
11
+
12
+ **Rationale**:
13
+ - 仕様 Clarification で「削除は upsert 完了後に実施」を採用済み。
14
+ - upsert 途中で失敗した場合、削除を一切行わないため、既存データが中途半端に消えない。
15
+ - 再実行で同じCSVを流せば整合する。単一トランザクションで全体を囲むより実装が単純で、既存のバッチ upsert の戻り値(成功/失敗)をそのまま利用できる。
16
+
17
+ **Alternatives considered**:
18
+ - 単一トランザクションで upsert + delete: メモリ上に「今回のCSVの全キー」を保持する必要があり、トランザクション時間が長くなる。本 gem のスコープでは二相で十分。
19
+ - 削除を先に実行: 削除後に upsert が失敗すると「消えたまま」の不整合が残る。仕様で拒否済み。
20
+
21
+ ---
22
+
23
+ ## 2. 「消えたレコード」の検出方法
24
+
25
+ **Decision**: 今回のCSVを1回走査する間に「出現した一意キー」の集合をメモリ(Set)で保持し、upsert 完了後に「その集合に含まれない」テーブル行を削除する。
26
+
27
+ **Rationale**:
28
+ - 既存の一意キー `(postal_code, pref_code, city, town)` は Constitution および既存コードで固定。同じキーで upsert しているため、「今回CSVに1行も出てこなかったキー」=削除対象。
29
+ - KEN_ALL は約12万〜14万行。キー4要素の Set はメモリ負荷が許容範囲。
30
+ - 二回走査(1回目でキー収集、2回目で upsert)にするとファイル I/O が倍になるため、1回の `CSV.foreach` 内で「キーを Set に追加」と「バッチ upsert」を並行して行う。
31
+
32
+ **Alternatives considered**:
33
+ - 削除フラグ列(`deleted_at`): スキーマ変更が必要で、本機能スコープは「単一テーブル・既存スキーマのまま」のため不採用。将来の拡張では選択肢になり得る。
34
+ - 一時テーブルに載せ替え: Constitution の「全更新」フォールバックで言及されているが、今回は「差分削除」のみで足りるため過剰。
35
+
36
+ ---
37
+
38
+ ## 3. 空CSVの検出と拒否
39
+
40
+ **Decision**: 有効行が1行もない場合(ヘッダのみや空ファイル)は、upsert を1件も行わず、例外またはエラー終了とする。削除は実行しない。
41
+
42
+ **Rationale**:
43
+ - 仕様で「空CSVは拒否」を採用。誤操作による全削除を防ぐ。
44
+ - 実装: 1行目を読む前にファイルが空か、または `CSV.foreach` で1件も `parse_row` が有効なレコードを返さなかった時点で「有効行0件」と判定し、`ImportError`(または既存の `JpAddressComplement::ImportError`)を発生させる。
45
+
46
+ **Alternatives considered**:
47
+ - 空CSVを許可して全削除: 仕様で拒否済み(誤操作リスク)。
48
+ - 明示フラグでのみ全削除: 仕様で「空CSVは拒否」を選択済みのため、今回は不要。
49
+
50
+ ---
51
+
52
+ ## 4. 追加・更新・削除件数の報告
53
+
54
+ **Decision**: インポート処理の戻り値で「追加(新規)・更新・削除」の件数を持ち、Rake タスクから標準出力で報告する。
55
+
56
+ **Rationale**:
57
+ - 仕様 FR-006 で「利用者が確認できる形で報告」を要求。Rake タスクは標準出力が自然。
58
+ - upsert_all は「insert されたか update されたか」を DB によっては返せるが、汎用的にするため「今回CSVの行数」と「削除した行数」を明示的に数える方が確実。追加/更新の内訳は、実装コストと価値のバランスで「upsert した行数(新規+更新の合計)」と「削除した行数」の2種類でも可。必要なら「挿入件数」「更新件数」を DB の戻り値で分離する(PostgreSQL の upsert 戻り値等)。
59
+
60
+ **Alternatives considered**:
61
+ - 報告しない: 仕様で「件数を報告する」を採用済みのため不採用。
62
+ - ログのみ: 運用では標準出力の方が Rake 実行結果として見やすいため、標準出力を主とし、必要に応じてログも出す。
63
+
64
+ ---
65
+
66
+ ## 5. 一意キーの扱い(既存準拠)
67
+
68
+ **Decision**: 一意キーは既存の `unique_by: %i[postal_code pref_code city town]` を変更しない。削除対象の判定も同じ4要素の組み合わせで行う。
69
+
70
+ **Rationale**:
71
+ - 001 の data-model および既存 CsvImporter で確定済み。本機能は「削除」を足すだけで、キー定義の変更は不要。
@@ -0,0 +1,85 @@
1
+ # Feature Specification: CSVインポート時の消えたレコード削除
2
+
3
+ **Feature Branch**: `003-csv-remove-obsolete`
4
+ **Created**: 2025-02-23
5
+ **Status**: Draft
6
+ **Input**: User description: "現在のCSVインポートでは、新しいCSVから「消えたレコード」を削除する機能がないため、消えた住所情報が残り続ける問題があります。CSVインポート時になくなった住所情報を削除するようにします"
7
+
8
+ ## Clarifications
9
+
10
+ ### Session 2025-02-23
11
+
12
+ - Q: インポート途中で失敗した場合、削除と追加・更新の整合性をどう保つか(削除の実行タイミング・トランザクション方針)? → A: 削除は upsert 完了後に実施。upsert が全て成功した場合のみ削除フェーズを実行する。途中失敗時は削除は行わず、既存データはそのまま。再実行で同じCSVを流して整合を取る。
13
+ - Q: 空のCSV(0件)でインポートした場合の扱い(全削除するか・拒否するか)? → A: 空CSVは拒否。0件のCSVの場合はインポートを実行せずエラーとし、既存データは変更しない。
14
+ - Q: 削除対象のスコープ(単一ストアか・複数ソース混在を想定するか)? → A: 単一ストアに限定。本機能ではインポート対象は KEN_ALL 用の単一ストア(1テーブル)のみ。他ソースの混在は考慮しない。
15
+ - Q: インポート完了時に追加・更新・削除件数を利用者に報告するか? → A: 件数を報告する。インポート完了時に、追加(新規)・更新・削除の件数を利用者が確認できる形で報告する。
16
+
17
+ ## User Scenarios & Testing *(mandatory)*
18
+
19
+ ### User Story 1 - フルインポートで新CSVにない住所レコードを削除する (Priority: P1)
20
+
21
+ 管理者が KEN_ALL.CSV の新版でフルインポートを実行したとき、今回のCSVに含まれない住所レコードがストアから削除され、CSVの内容とストアの内容が一致する。
22
+
23
+ **Why this priority**: 消えたレコードが残り続ける現状の問題を解消する本機能の根幹である。
24
+
25
+ **Independent Test**: 既存データがある状態で、一部レコードを除いたCSVでインポートを実行し、除いたレコードがストアから消えていることを確認する。
26
+
27
+ **Acceptance Scenarios**:
28
+
29
+ 1. **Given** ストアに住所レコード A, B, C が存在する, **When** レコード B を含まない新しいCSVでインポートを実行する, **Then** インポート完了後、ストアに A と C のみが残り、B は削除されている
30
+ 2. **Given** ストアが空である, **When** 任意のCSVでインポートを実行する, **Then** 削除対象はなく、CSVの内容のみが登録される
31
+ 3. **Given** ストアに住所レコードが存在する, **When** 同じ内容のCSVで再度インポートを実行する, **Then** 既存レコードは維持され、新CSVにないレコードがなければ削除は発生しない(冪等)
32
+
33
+ ---
34
+
35
+ ### User Story 2 - 削除と追加・更新が同一インポートで正しく反映される (Priority: P2)
36
+
37
+ 管理者が、前回よりレコードが増減・変更されたCSVでインポートしたとき、追加・更新・削除が一括で正しく反映される。
38
+
39
+ **Why this priority**: 実運用では「消えたものの削除」と「新規・更新」を同時に扱うため、同一インポート内で一貫して動作することが必要である。
40
+
41
+ **Independent Test**: 既存データに対し、一部削除・一部追加・一部変更したCSVでインポートし、最終状態がCSVと一致することを確認する。
42
+
43
+ **Acceptance Scenarios**:
44
+
45
+ 1. **Given** ストアにレコード A, B が存在する, **When** A を削除し C を追加したCSVでインポートする, **Then** 完了後は B と C が存在し、A は存在しない
46
+ 2. **Given** ストアにレコードが存在する, **When** 同じ一意キーで内容が変更されたCSVでインポートする, **Then** 該当レコードは更新され、かつ新CSVにない他レコードは削除される
47
+
48
+ ---
49
+
50
+ ### Edge Cases
51
+
52
+ - 新CSVが空(0件)の場合:インポートを拒否し、エラーとして終了する。既存の住所レコードは変更しない(誤操作による全削除を防ぐ)。
53
+ - インポート途中で失敗した場合:削除は upsert が全て成功した後にのみ実行する。upsert の途中で失敗した場合は削除フェーズは行わず、既存データは変更しない。利用者は同じCSVを再実行することで整合性を取る。
54
+ - 一意キー(郵便番号・都道府県コード・市区町村・町域など)で「消えた」とみなす基準をどうするか:今回のCSV形式(KEN_ALL.CSV)では、同一キーで upsert しているため、「今回のCSVに出現しなかったキーの組み合わせ」を削除対象とする。
55
+
56
+ ## Requirements *(mandatory)*
57
+
58
+ ### Functional Requirements
59
+
60
+ - **FR-001**: システムは、CSVインポート実行時に、今回読み込んだCSVに存在しない住所レコードをストアから削除しなければならない。
61
+ - **FR-002**: 「存在しない」の判定は、既存の一意キー(郵便番号・都道府県コード・市区町村・町域等、既存仕様に準拠)で今回のCSVに一度も出現しなかったレコードとする。
62
+ - **FR-003**: 削除は、インポート処理の一環として実行され、同一のインポート操作で「追加・更新」と「削除」の結果が利用者からは一貫して見えなければならない。
63
+ - **FR-004**: 空のCSV(有効行が0件)の場合はインポートを実行せず、エラーとし、既存データは変更してはならない。
64
+ - **FR-005**: 削除フェーズは、今回のCSVに対する upsert が全て成功した後にのみ実行しなければならない。upsert の途中で失敗した場合は削除を実行せず、既存データは変更しない。利用者が同じCSVを再実行すれば整合した状態になる設計とすること。
65
+ - **FR-006**: インポート完了時に、追加(新規)・更新・削除の件数を利用者が確認できる形で報告しなければならない。
66
+
67
+ ### Key Entities
68
+
69
+ - **住所レコード(郵便番号住所)**: 郵便番号・都道府県・市区町村・町域等の属性を持ち、一意キーで識別される。CSVの1行と1対1対応する。
70
+ - **インポート実行**: 1回のCSV読み込みと、それに基づく「追加・更新」および「新CSVにないレコードの削除」の一連の処理。
71
+
72
+ ## Assumptions
73
+
74
+ - 既存のCSVインポートは KEN_ALL.CSV 形式を前提としており、本機能も同じ形式・同じ一意キーを前提とする。
75
+ - 「今回のCSV」は単一ファイルのフル内容を指し、差分だけを渡す部分インポートは対象外とする。
76
+ - 本機能のスコープは、KEN_ALL.CSV が投入される単一のストア(1テーブル)に限定する。他ソースの混在は考慮しない。
77
+ - FR-006 の件数報告は、「upsert した件数(新規+更新の合計)」と「削除した件数」の2種を利用者が確認できる形で報告すれば足りる(DB が insert/update を区別して返さない場合はこの2種とする)。
78
+
79
+ ## Success Criteria *(mandatory)*
80
+
81
+ ### Measurable Outcomes
82
+
83
+ - **SC-001**: 管理者が新版CSVでフルインポートを実行した後、ストアに存在する住所レコードの集合が、そのCSVの内容と一致する(新CSVにないレコードが残らない)。
84
+ - **SC-002**: 同じCSVで複数回インポートを実行しても結果は同じであり、不要な削除・二重削除が発生しない(冪等)。
85
+ - **SC-003**: インポート処理の所要時間やリソースは、既存の追加・更新に加えて削除が入っても、許容可能な範囲に収まる(例: 既存インポート時間の2倍以内など、運用で許容できる目安を満たす)。