Coverage for src/inheritance_calculator_core/models/inheritance.py: 95%
96 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-17 05:31 +0900
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-17 05:31 +0900
1"""相続計算結果モデル"""
2from typing import List, Optional, Any
3from fractions import Fraction
4from enum import Enum
5from uuid import UUID
7from pydantic import BaseModel, Field, field_validator, ConfigDict
9from .person import Person
12class HeritageRank(str, Enum):
13 """相続順位"""
14 SPOUSE = "spouse" # 配偶者(常に相続人)
15 FIRST = "first" # 第1順位(子・直系卑属)
16 SECOND = "second" # 第2順位(直系尊属)
17 THIRD = "third" # 第3順位(兄弟姉妹)
20class SubstitutionType(str, Enum):
21 """代襲相続タイプ"""
22 NONE = "none" # 代襲なし
23 CHILD = "child" # 子の代襲(孫、曾孫...)
24 SIBLING = "sibling" # 兄弟姉妹の代襲(甥・姪のみ)
27class Heir(BaseModel):
28 """
29 相続人
31 相続人の資格と相続割合を表現。
32 """
34 model_config = ConfigDict(
35 validate_assignment=True,
36 extra='forbid',
37 )
39 person: Person = Field(..., description="相続人")
40 rank: HeritageRank = Field(..., description="相続順位")
41 share: Fraction = Field(..., description="相続割合(法定相続分)")
42 share_percentage: float = Field(..., description="相続割合(百分率)")
43 is_substitution: bool = Field(default=False, description="代襲相続人か")
44 substitution_type: SubstitutionType = Field(
45 default=SubstitutionType.NONE, description="代襲相続タイプ"
46 )
47 substituted_person: Optional[Person] = Field(
48 default=None, description="被代襲者(代襲相続の場合)"
49 )
50 generation: int = Field(default=1, description="代襲世代数(1=直接相続人)")
51 is_retransfer: bool = Field(default=False, description="再転相続人か")
52 retransfer_from: Optional[Person] = Field(
53 default=None, description="再転相続元の相続人(遺産分割前に死亡した相続人)"
54 )
55 original_share: Optional[Fraction] = Field(
56 default=None, description="再転相続前の元の相続分"
57 )
59 @field_validator('share_percentage', mode='before')
60 @classmethod
61 def calculate_percentage(cls, v: Any, info: Any) -> float:
62 """相続割合の百分率を計算"""
63 # shareから計算(vが0.0の場合も含む)
64 values = info.data
65 share = values.get('share')
66 if share is not None:
67 return float(share) * 100
69 # すでに計算されている場合はそのまま返す
70 if isinstance(v, float):
71 return v
73 return 0.0
75 @field_validator('substitution_type')
76 @classmethod
77 def validate_substitution_type(cls, v: SubstitutionType, info: Any) -> SubstitutionType:
78 """代襲相続タイプの検証"""
79 values = info.data
80 is_substitution = values.get('is_substitution', False)
82 # 代襲相続でない場合、タイプはNONEでなければならない
83 if not is_substitution and v != SubstitutionType.NONE:
84 raise ValueError("Non-substitution heir must have NONE type")
86 # 代襲相続の場合、タイプはNONE以外でなければならない
87 if is_substitution and v == SubstitutionType.NONE:
88 raise ValueError("Substitution heir must have valid substitution type")
90 return v
92 def __str__(self) -> str:
93 """文字列表現"""
94 substitution_mark = ""
95 if self.is_substitution:
96 substitution_mark = f"(代襲: {self.substituted_person.name if self.substituted_person else '不明'})"
98 share_str = f"{self.share} ({self.share_percentage:.2f}%)"
99 return f"{self.person.name} - {self.rank.value} - {share_str}{substitution_mark}"
102class InheritanceResult(BaseModel):
103 """
104 相続計算結果
106 相続人の確定と相続割合の計算結果を表現。
107 """
109 model_config = ConfigDict(
110 validate_assignment=True,
111 extra='forbid',
112 )
114 decedent: Person = Field(..., description="被相続人")
115 heirs: List[Heir] = Field(default_factory=list, description="相続人リスト")
116 has_spouse: bool = Field(default=False, description="配偶者がいるか")
117 has_children: bool = Field(default=False, description="子がいるか")
118 has_parents: bool = Field(default=False, description="直系尊属がいるか")
119 has_siblings: bool = Field(default=False, description="兄弟姉妹がいるか")
120 calculation_basis: List[str] = Field(
121 default_factory=list, description="計算根拠(適用した民法条文等)"
122 )
124 @field_validator('heirs')
125 @classmethod
126 def validate_total_share(cls, v: List[Heir]) -> List[Heir]:
127 """相続割合の合計が1(100%)であることを検証"""
128 if not v:
129 return v
131 total = sum(heir.share for heir in v)
132 if total != Fraction(1, 1):
133 raise ValueError(
134 f"Total share must be 1 (100%), but got {total} ({float(total) * 100}%)"
135 )
137 return v
139 def add_heir(
140 self,
141 person: Person,
142 rank: HeritageRank,
143 share: Fraction,
144 is_substitution: bool = False,
145 substitution_type: SubstitutionType = SubstitutionType.NONE,
146 substituted_person: Optional[Person] = None,
147 generation: int = 1
148 ) -> None:
149 """
150 相続人を追加
152 Args:
153 person: 相続人
154 rank: 相続順位
155 share: 相続割合
156 is_substitution: 代襲相続人か
157 substitution_type: 代襲相続タイプ
158 substituted_person: 被代襲者
159 generation: 代襲世代数
160 """
161 heir = Heir(
162 person=person,
163 rank=rank,
164 share=share,
165 share_percentage=float(share) * 100,
166 is_substitution=is_substitution,
167 substitution_type=substitution_type,
168 substituted_person=substituted_person,
169 generation=generation
170 )
171 self.heirs.append(heir)
173 def add_calculation_basis(self, basis: str) -> None:
174 """
175 計算根拠を追加
177 Args:
178 basis: 計算根拠(民法条文等)
179 """
180 self.calculation_basis.append(basis)
182 def get_heirs_by_rank(self, rank: HeritageRank) -> List[Heir]:
183 """
184 指定された順位の相続人を取得
186 Args:
187 rank: 相続順位
189 Returns:
190 相続人のリスト
191 """
192 return [heir for heir in self.heirs if heir.rank == rank]
194 def get_substitution_heirs(self) -> List[Heir]:
195 """
196 代襲相続人のリストを取得
198 Returns:
199 代襲相続人のリスト
200 """
201 return [heir for heir in self.heirs if heir.is_substitution]
203 @property
204 def total_heirs(self) -> int:
205 """相続人の総数"""
206 return len(self.heirs)
208 def __str__(self) -> str:
209 """文字列表現"""
210 result = [f"被相続人: {self.decedent.name}"]
211 result.append(f"相続人総数: {self.total_heirs}名")
212 result.append("---")
214 for heir in self.heirs:
215 result.append(str(heir))
217 if self.calculation_basis:
218 result.append("---")
219 result.append("計算根拠:")
220 for basis in self.calculation_basis:
221 result.append(f" - {basis}")
223 return "\n".join(result)