11from datetime import datetime
22from pathlib import Path
3- from typing import Any , Literal
3+ from typing import Literal
44
55from pydantic import BaseModel , Field
66
@@ -22,6 +22,7 @@ class LevelRecordInfo(BaseModel):
2222 rank : LevelItem | str = ""
2323 """Level"""
2424 Rating : Literal [
25+ "NEW" ,
2526 "phi" ,
2627 "FC" ,
2728 "V" ,
@@ -45,139 +46,55 @@ class LevelRecordInfo(BaseModel):
4546 """推分建议"""
4647 num : int = Field (default = 0 , exclude = True )
4748 """是 Best 几"""
48- date : datetime = Field (default = datetime .now () , exclude = True )
49+ date : datetime = Field (default_factory = datetime .now , exclude = True )
4950 """更新时间(iso)"""
51+ _raw_rank : str | int = Field (default = "" , exclude = True )
5052
51- # 私有字段
52- lazy_song_id : str = Field (default = "" , exclude = True )
53- lazy_index : int = Field (default = 0 , exclude = True )
54- lazy_raw_data : dict [str , Any ] | None = Field (default = None , exclude = True )
55- is_lazy : bool = Field (default = False , exclude = True )
56- initialized : bool = Field (default = True , exclude = True ) # 默认为已初始化
57-
58- @classmethod
59- def lazy_init (cls , data : dict , id : str , index : int ) -> "LevelRecordInfo" :
53+ def build (self ,) -> None : # type: ignore[override]
6054 """
61- 惰性初始化方法,只设置基本字段
55+ 构建所需的完整数据
6256 """
63- # 检查数据是否包含完整字段,如果包含则直接初始化
64- if cls ._is_data_complete (data ):
65- # 数据已经完整,直接创建实例而不调用 init
66- instance = cls (** data )
67- # 标记为已初始化
68- instance .initialized = True
69- instance .is_lazy = False
70- return instance
71- # 数据不完整,使用惰性加载
72- instance = cls (
73- fc = data .get ("fc" , False ),
74- score = data .get ("score" , 0 ),
75- acc = data .get ("acc" , 0.0 ),
76- )
77- instance .lazy_raw_data = data
78- instance .lazy_song_id = id
79- instance .lazy_index = index
80- instance .is_lazy = True
81- instance .initialized = False
82- return instance
8357
84- @classmethod
85- def _is_data_complete (cls , data : dict ) -> bool :
86- """
87- 检查传入数据是否包含完整字段
88- """
89- # 定义完整字段列表
90- complete_fields = [
91- "song" ,
92- "rank" ,
93- "difficulty" ,
94- "rks" ,
95- "Rating" ,
96- "illustration" ,
97- ]
58+ # rank 可能传的是 int / str,这里统一一下
59+ if isinstance (self ._raw_rank , int ):
60+ rank_key : LevelItem | str = getInfo .Level [self ._raw_rank ]
61+ self .rank = rank_key
62+ else :
63+ rank_key = self .rank
9864
99- # 检查是否包含完整字段
100- return all ( field in data for field in complete_fields )
65+ # rating
66+ self . Rating = Rating ( self . score , self . fc )
10167
102- def _ensure_initialized (self ):
103- """
104- 确保完全初始化
105- """
106- if not self .initialized and self .is_lazy and self .lazy_raw_data is not None :
107- # 完整初始化
108- full_instance = LevelRecordInfo .init (
109- self .lazy_raw_data , self .lazy_song_id , self .lazy_index
110- )
111- # 复制所有字段
112- for field_name in LevelRecordInfo .model_fields :
113- setattr (self , field_name , getattr (full_instance , field_name ))
114- self .initialized = True
115- self .is_lazy = False
68+ if song := getInfo .idgetsong (self .id ):
69+ info = getInfo .info (song )
70+ else :
71+ info = None
72+ if not info :
73+ self .song = self .id
74+ self .difficulty = 0.0
75+ self .rks = 0.0
76+ return
11677
117- # 重写 __getattribute__ 实现惰性加载
118- def __getattribute__ (self , name : str ):
119- # 避免无限递归,对特殊属性直接返回
120- if name in {
121- "initialized" ,
122- "is_lazy" ,
123- "lazy_raw_data" ,
124- "lazy_song_id" ,
125- "lazy_index" ,
126- }:
127- return object .__getattribute__ (self , name )
78+ self .song = info .song
12879
129- # 检查是否是模型字段且需要初始化
130- if name in {
131- "song" ,
132- "rank" ,
133- "difficulty" ,
134- "rks" ,
135- "Rating" ,
136- "illustration" ,
137- } and not object .__getattribute__ (self , "initialized" ):
138- object .__getattribute__ (self , "_ensure_initialized" )()
139- return object .__getattribute__ (self , name )
80+ # illustration
81+ ill = getInfo .getill (self .song )
82+ self .illustration = ill
14083
141- @classmethod
142- def init (cls , data : dict , id : str , rank : int | str ) -> "LevelRecordInfo" :
143- """
144- :param data: 原始数据
145- :param id: 曲目id
146- :param rank: 难度
147- """
148- data_ = {"fc" : data ["fc" ], "score" : data ["score" ], "acc" : data ["acc" ], "id" : id }
149- song = getInfo .idgetsong (id )
150- info = getInfo .info (song ) if song else None
151- data_ ["rank" ] = (
152- getInfo .Level [rank ] if isinstance (rank , int ) else rank
153- ) # EZ HD IN AT LEGACY
154- data_ ["Rating" ] = Rating (data_ ["score" ], data_ ["fc" ]) # V S A
155- if not info :
156- data_ ["song" ] = id
157- data_ ["difficulty" ] = 0
158- data_ ["rks" ] = 0
159- return cls (** data_ )
160- data_ ["song" ] = info .song # 曲名
161- data_ ["illustration" ] = getInfo .getill (data_ ["song" ]) # 曲绘链接
162- difficulty = (
163- info .chart [data_ ["rank" ]].difficulty
164- if data_ ["rank" ] in info .chart
165- else None
166- )
167- if info .chart and difficulty :
168- assert isinstance (difficulty , float )
169- data_ ["difficulty" ] = difficulty # 难度
170- data_ ["rks" ] = fCompute .rks (data_ ["acc" ], data_ ["difficulty" ]) # 等效rks
84+ chart = info .chart
85+ if chart and rank_key in chart and chart [rank_key ].difficulty :
86+ diff_val = chart [rank_key ].difficulty
87+ assert isinstance (diff_val , float )
88+ self .difficulty = diff_val
89+ self .rks = fCompute .rks (self .acc , self .difficulty )
17190 else :
172- data_ ["difficulty" ] = 0
173- data_ ["rks" ] = 0
174- return cls (** data_ )
91+ self .difficulty = 0.0
92+ self .rks = 0.0
17593
176- @classmethod
177- def to_tuple (cls ) -> tuple [float , int , datetime , bool ]:
94+ def to_tuple (self ) -> tuple [float , int , datetime , bool ]:
17895 return (
179- cls .acc ,
180- cls .score ,
181- cls .date ,
182- cls .fc ,
96+ self .acc ,
97+ self .score ,
98+ self .date ,
99+ self .fc ,
183100 )
0 commit comments