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

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

2

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

4"""

5 from typing import List, Optional, Dict, Any

6 from datetime import date

7 import logging

8

9 from .neo4j_client import Neo4jClient

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

11 from ..models.person import Person, Gender

12 from ..models.relationship import BloodType

13 from ..utils.exceptions import DatabaseException

14

15

16 class 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

228 class 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

469 class 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)}")