Coverage for src/inheritance_calculator_core/database/repositories.py: 0%

194 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-17 05:31 +0900

1"""リポジトリパターンの実装 

2 

3Neo4jデータベースへのデータアクセスを抽象化するリポジトリクラス。 

4""" 

5from typing import List, Optional, Dict, Any 

6from datetime import date 

7import logging 

8 

9from .neo4j_client import Neo4jClient 

10from .queries import PersonQueries, RelationshipQueries, InheritanceQueries, build_person_params 

11from ..models.person import Person, Gender 

12from ..models.relationship import BloodType 

13from ..utils.exceptions import DatabaseException 

14 

15 

16class PersonRepository: 

17 """ 

18 人物(Person)のリポジトリ 

19 

20 Neo4jとPydanticモデル間の変換を行う。 

21 """ 

22 

23 def __init__(self, client: Neo4jClient) -> None: 

24 """ 

25 初期化 

26 

27 Args: 

28 client: Neo4jクライアント 

29 """ 

30 self.client = client 

31 self.logger = logging.getLogger(__name__) 

32 

33 def create(self, person: Person) -> Person: 

34 """ 

35 人物を作成 

36 

37 Args: 

38 person: 作成する人物 

39 

40 Returns: 

41 作成された人物 

42 

43 Raises: 

44 DatabaseException: 作成に失敗した場合 

45 """ 

46 params = build_person_params( 

47 name=person.name, 

48 is_alive=person.is_alive, 

49 is_decedent=person.is_decedent, 

50 birth_date=person.birth_date.isoformat() if person.birth_date else None, 

51 death_date=person.death_date.isoformat() if person.death_date else None, 

52 gender=person.gender.value if person.gender else None, 

53 address=person.address, 

54 phone=person.phone, 

55 email=person.email 

56 ) 

57 

58 try: 

59 result = self.client.execute(PersonQueries.CREATE, params) 

60 self.logger.info(f"Created person: {person.name}") 

61 return person 

62 

63 except Exception as e: 

64 self.logger.error(f"Failed to create person {person.name}: {e}") 

65 raise DatabaseException(f"人物作成エラー: {str(e)}") 

66 

67 def find_by_name(self, name: str) -> Optional[Person]: 

68 """ 

69 名前で人物を検索 

70 

71 Args: 

72 name: 氏名 

73 

74 Returns: 

75 見つかった人物、存在しない場合はNone 

76 """ 

77 try: 

78 result = self.client.execute(PersonQueries.FIND_BY_NAME, {"name": name}) 

79 

80 if not result: 

81 return None 

82 

83 return self._to_person(result[0]["p"]) 

84 

85 except Exception as e: 

86 self.logger.error(f"Failed to find person {name}: {e}") 

87 raise DatabaseException(f"人物検索エラー: {str(e)}") 

88 

89 def find_decedent(self) -> Optional[Person]: 

90 """ 

91 被相続人を検索 

92 

93 Returns: 

94 被相続人、存在しない場合はNone 

95 """ 

96 try: 

97 result = self.client.execute(PersonQueries.FIND_DECEDENT) 

98 

99 if not result: 

100 return None 

101 

102 return self._to_person(result[0]["p"]) 

103 

104 except Exception as e: 

105 self.logger.error(f"Failed to find decedent: {e}") 

106 raise DatabaseException(f"被相続人検索エラー: {str(e)}") 

107 

108 def find_all(self) -> List[Person]: 

109 """ 

110 全ての人物を取得 

111 

112 Returns: 

113 人物のリスト 

114 """ 

115 try: 

116 result = self.client.execute(PersonQueries.FIND_ALL) 

117 return [self._to_person(record["p"]) for record in result] 

118 

119 except Exception as e: 

120 self.logger.error(f"Failed to find all persons: {e}") 

121 raise DatabaseException(f"人物一覧取得エラー: {str(e)}") 

122 

123 def update(self, person: Person) -> Person: 

124 """ 

125 人物を更新 

126 

127 Args: 

128 person: 更新する人物 

129 

130 Returns: 

131 更新された人物 

132 

133 Raises: 

134 DatabaseException: 更新に失敗した場合 

135 """ 

136 params = { 

137 "name": person.name, 

138 "is_alive": person.is_alive, 

139 "death_date": person.death_date.isoformat() if person.death_date else None, 

140 "gender": person.gender.value if person.gender else None, 

141 "address": person.address, 

142 "phone": person.phone, 

143 "email": person.email 

144 } 

145 

146 try: 

147 result = self.client.execute(PersonQueries.UPDATE, params) 

148 

149 if not result: 

150 raise DatabaseException(f"人物が見つかりません: {person.name}") 

151 

152 self.logger.info(f"Updated person: {person.name}") 

153 return person 

154 

155 except Exception as e: 

156 self.logger.error(f"Failed to update person {person.name}: {e}") 

157 raise DatabaseException(f"人物更新エラー: {str(e)}") 

158 

159 def delete(self, name: str) -> None: 

160 """ 

161 人物を削除 

162 

163 Args: 

164 name: 削除する人物の氏名 

165 

166 Raises: 

167 DatabaseException: 削除に失敗した場合 

168 """ 

169 try: 

170 self.client.execute(PersonQueries.DELETE, {"name": name}) 

171 self.logger.info(f"Deleted person: {name}") 

172 

173 except Exception as e: 

174 self.logger.error(f"Failed to delete person {name}: {e}") 

175 raise DatabaseException(f"人物削除エラー: {str(e)}") 

176 

177 def delete_all(self) -> None: 

178 """ 

179 全ての人物を削除(テスト用) 

180 

181 Warning: 

182 本番環境では使用しないこと 

183 """ 

184 try: 

185 self.client.execute(PersonQueries.DELETE_ALL) 

186 self.logger.warning("Deleted all persons") 

187 

188 except Exception as e: 

189 self.logger.error(f"Failed to delete all persons: {e}") 

190 raise DatabaseException(f"全人物削除エラー: {str(e)}") 

191 

192 def _to_person(self, node: Dict[str, Any]) -> Person: 

193 """ 

194 Neo4jノードをPersonモデルに変換 

195 

196 Args: 

197 node: Neo4jノード 

198 

199 Returns: 

200 Personモデル 

201 """ 

202 # Neo4jのdateオブジェクトをPythonのdateに変換 

203 birth_date = None 

204 if node.get("birth_date"): 

205 bd = node["birth_date"] 

206 birth_date = date(bd.year, bd.month, bd.day) if hasattr(bd, "year") else None 

207 

208 death_date = None 

209 if node.get("death_date"): 

210 dd = node["death_date"] 

211 death_date = date(dd.year, dd.month, dd.day) if hasattr(dd, "year") else None 

212 

213 gender = Gender(node["gender"]) if node.get("gender") else Gender.UNKNOWN 

214 

215 return Person( 

216 name=node["name"], 

217 is_alive=node["is_alive"], 

218 is_decedent=node["is_decedent"], 

219 birth_date=birth_date, 

220 death_date=death_date, 

221 gender=gender, 

222 address=node.get("address"), 

223 phone=node.get("phone"), 

224 email=node.get("email") 

225 ) 

226 

227 

228class RelationshipRepository: 

229 """ 

230 リレーションシップのリポジトリ 

231 

232 親子関係、配偶者関係、兄弟姉妹関係などを管理する。 

233 """ 

234 

235 def __init__(self, client: Neo4jClient) -> None: 

236 """ 

237 初期化 

238 

239 Args: 

240 client: Neo4jクライアント 

241 """ 

242 self.client = client 

243 self.person_repo = PersonRepository(client) 

244 self.logger = logging.getLogger(__name__) 

245 

246 def create_child_of( 

247 self, 

248 child_name: str, 

249 parent_name: str, 

250 adoption: bool = False, 

251 is_biological: bool = True 

252 ) -> None: 

253 """ 

254 親子関係を作成 

255 

256 Args: 

257 child_name: 子の氏名 

258 parent_name: 親の氏名 

259 adoption: 養子縁組フラグ 

260 is_biological: 実子フラグ 

261 """ 

262 params = { 

263 "child_name": child_name, 

264 "parent_name": parent_name, 

265 "adoption": adoption, 

266 "is_biological": is_biological 

267 } 

268 

269 try: 

270 self.client.execute(RelationshipQueries.CREATE_CHILD_OF, params) 

271 self.logger.info(f"Created CHILD_OF: {child_name} -> {parent_name}") 

272 

273 except Exception as e: 

274 self.logger.error(f"Failed to create CHILD_OF relationship: {e}") 

275 raise DatabaseException(f"親子関係作成エラー: {str(e)}") 

276 

277 def create_spouse_of( 

278 self, 

279 person1_name: str, 

280 person2_name: str, 

281 marriage_date: Optional[date] = None, 

282 divorce_date: Optional[date] = None, 

283 is_current: bool = True 

284 ) -> None: 

285 """ 

286 配偶者関係を作成 

287 

288 Args: 

289 person1_name: 配偶者1の氏名 

290 person2_name: 配偶者2の氏名 

291 marriage_date: 婚姻日 

292 divorce_date: 離婚日 

293 is_current: 現在の配偶者フラグ 

294 """ 

295 params = { 

296 "person1_name": person1_name, 

297 "person2_name": person2_name, 

298 "marriage_date": marriage_date.isoformat() if marriage_date else None, 

299 "divorce_date": divorce_date.isoformat() if divorce_date else None, 

300 "is_current": is_current 

301 } 

302 

303 try: 

304 self.client.execute(RelationshipQueries.CREATE_SPOUSE_OF, params) 

305 self.logger.info(f"Created SPOUSE_OF: {person1_name} <-> {person2_name}") 

306 

307 except Exception as e: 

308 self.logger.error(f"Failed to create SPOUSE_OF relationship: {e}") 

309 raise DatabaseException(f"配偶者関係作成エラー: {str(e)}") 

310 

311 def create_sibling_of( 

312 self, 

313 person1_name: str, 

314 person2_name: str, 

315 blood_type: BloodType = BloodType.FULL, 

316 shared_parent: str = "both" 

317 ) -> None: 

318 """ 

319 兄弟姉妹関係を作成 

320 

321 Args: 

322 person1_name: 兄弟姉妹1の氏名 

323 person2_name: 兄弟姉妹2の氏名 

324 blood_type: 血縁タイプ 

325 shared_parent: 共通の親 

326 """ 

327 params = { 

328 "person1_name": person1_name, 

329 "person2_name": person2_name, 

330 "blood_type": blood_type.value, 

331 "shared_parent": shared_parent 

332 } 

333 

334 try: 

335 self.client.execute(RelationshipQueries.CREATE_SIBLING_OF, params) 

336 self.logger.info(f"Created SIBLING_OF: {person1_name} <-> {person2_name}") 

337 

338 except Exception as e: 

339 self.logger.error(f"Failed to create SIBLING_OF relationship: {e}") 

340 raise DatabaseException(f"兄弟姉妹関係作成エラー: {str(e)}") 

341 

342 def create_renounced( 

343 self, 

344 person_name: str, 

345 decedent_name: str, 

346 renounce_date: date, 

347 reason: Optional[str] = None 

348 ) -> None: 

349 """ 

350 相続放棄関係を作成 

351 

352 Args: 

353 person_name: 放棄者の氏名 

354 decedent_name: 被相続人の氏名 

355 renounce_date: 放棄日 

356 reason: 理由 

357 """ 

358 params = { 

359 "person_name": person_name, 

360 "decedent_name": decedent_name, 

361 "renounce_date": renounce_date.isoformat(), 

362 "reason": reason 

363 } 

364 

365 try: 

366 self.client.execute(RelationshipQueries.CREATE_RENOUNCED, params) 

367 self.logger.info(f"Created RENOUNCED: {person_name} -> {decedent_name}") 

368 

369 except Exception as e: 

370 self.logger.error(f"Failed to create RENOUNCED relationship: {e}") 

371 raise DatabaseException(f"相続放棄関係作成エラー: {str(e)}") 

372 

373 def find_children(self, parent_name: str) -> List[Person]: 

374 """ 

375 親の子を検索 

376 

377 Args: 

378 parent_name: 親の氏名 

379 

380 Returns: 

381 子のリスト 

382 """ 

383 try: 

384 result = self.client.execute( 

385 RelationshipQueries.FIND_CHILDREN, 

386 {"parent_name": parent_name} 

387 ) 

388 return [self.person_repo._to_person(record["child"]) for record in result] 

389 

390 except Exception as e: 

391 self.logger.error(f"Failed to find children of {parent_name}: {e}") 

392 raise DatabaseException(f"子の検索エラー: {str(e)}") 

393 

394 def find_parents(self, child_name: str) -> List[Person]: 

395 """ 

396 子の親を検索 

397 

398 Args: 

399 child_name: 子の氏名 

400 

401 Returns: 

402 親のリスト 

403 """ 

404 try: 

405 result = self.client.execute( 

406 RelationshipQueries.FIND_PARENTS, 

407 {"child_name": child_name} 

408 ) 

409 return [self.person_repo._to_person(record["parent"]) for record in result] 

410 

411 except Exception as e: 

412 self.logger.error(f"Failed to find parents of {child_name}: {e}") 

413 raise DatabaseException(f"親の検索エラー: {str(e)}") 

414 

415 def find_spouse(self, person_name: str) -> Optional[Person]: 

416 """ 

417 配偶者を検索 

418 

419 Args: 

420 person_name: 氏名 

421 

422 Returns: 

423 配偶者、存在しない場合はNone 

424 """ 

425 try: 

426 result = self.client.execute( 

427 RelationshipQueries.FIND_SPOUSE, 

428 {"person_name": person_name} 

429 ) 

430 

431 if not result: 

432 return None 

433 

434 return self.person_repo._to_person(result[0]["spouse"]) 

435 

436 except Exception as e: 

437 self.logger.error(f"Failed to find spouse of {person_name}: {e}") 

438 raise DatabaseException(f"配偶者の検索エラー: {str(e)}") 

439 

440 def find_siblings(self, person_name: str) -> List[tuple[Person, BloodType]]: 

441 """ 

442 兄弟姉妹を検索 

443 

444 Args: 

445 person_name: 氏名 

446 

447 Returns: 

448 (兄弟姉妹, 血縁タイプ)のリスト 

449 """ 

450 try: 

451 result = self.client.execute( 

452 RelationshipQueries.FIND_SIBLINGS, 

453 {"person_name": person_name} 

454 ) 

455 

456 siblings = [] 

457 for record in result: 

458 sibling = self.person_repo._to_person(record["sibling"]) 

459 blood_type = BloodType(record["r"]["blood_type"]) if "r" in record else BloodType.FULL 

460 siblings.append((sibling, blood_type)) 

461 

462 return siblings 

463 

464 except Exception as e: 

465 self.logger.error(f"Failed to find siblings of {person_name}: {e}") 

466 raise DatabaseException(f"兄弟姉妹の検索エラー: {str(e)}") 

467 

468 

469class InheritanceRepository: 

470 """ 

471 相続計算用のリポジトリ 

472 

473 相続人の取得など、相続計算に特化したクエリを実行する。 

474 """ 

475 

476 def __init__(self, client: Neo4jClient) -> None: 

477 """ 

478 初期化 

479 

480 Args: 

481 client: Neo4jクライアント 

482 """ 

483 self.client = client 

484 self.person_repo = PersonRepository(client) 

485 self.logger = logging.getLogger(__name__) 

486 

487 def get_spouse(self) -> Optional[Person]: 

488 """ 

489 配偶者を取得 

490 

491 Returns: 

492 配偶者、存在しない場合はNone 

493 """ 

494 try: 

495 result = self.client.execute(InheritanceQueries.GET_SPOUSE) 

496 

497 if not result: 

498 return None 

499 

500 return self.person_repo._to_person(result[0]["spouse"]) 

501 

502 except Exception as e: 

503 self.logger.error(f"Failed to get spouse: {e}") 

504 raise DatabaseException(f"配偶者取得エラー: {str(e)}") 

505 

506 def get_first_rank_heirs(self) -> List[Person]: 

507 """ 

508 第1順位相続人(子)を取得 

509 

510 Returns: 

511 子のリスト 

512 """ 

513 try: 

514 result = self.client.execute(InheritanceQueries.GET_FIRST_RANK_HEIRS) 

515 return [self.person_repo._to_person(record["child"]) for record in result] 

516 

517 except Exception as e: 

518 self.logger.error(f"Failed to get first rank heirs: {e}") 

519 raise DatabaseException(f"第1順位相続人取得エラー: {str(e)}") 

520 

521 def get_second_rank_heirs(self) -> List[Person]: 

522 """ 

523 第2順位相続人(直系尊属)を取得 

524 

525 Returns: 

526 直系尊属のリスト 

527 """ 

528 try: 

529 result = self.client.execute(InheritanceQueries.GET_SECOND_RANK_HEIRS) 

530 return [self.person_repo._to_person(record["ancestor"]) for record in result] 

531 

532 except Exception as e: 

533 self.logger.error(f"Failed to get second rank heirs: {e}") 

534 raise DatabaseException(f"第2順位相続人取得エラー: {str(e)}") 

535 

536 def get_third_rank_heirs(self) -> List[tuple[Person, BloodType]]: 

537 """ 

538 第3順位相続人(兄弟姉妹)を取得 

539 

540 Returns: 

541 (兄弟姉妹, 血縁タイプ)のリスト 

542 """ 

543 try: 

544 result = self.client.execute(InheritanceQueries.GET_THIRD_RANK_HEIRS) 

545 

546 heirs = [] 

547 for record in result: 

548 sibling = self.person_repo._to_person(record["sibling"]) 

549 blood_type = BloodType(record["blood_type"]) 

550 heirs.append((sibling, blood_type)) 

551 

552 return heirs 

553 

554 except Exception as e: 

555 self.logger.error(f"Failed to get third rank heirs: {e}") 

556 raise DatabaseException(f"第3順位相続人取得エラー: {str(e)}")