libs.domain.rule
libs/domain/rule.py
1""" 2libs/domain/rule.py 3""" 4 5import logging 6import sys 7from configparser import ConfigParser 8from dataclasses import dataclass, field 9from typing import TYPE_CHECKING, Any, Literal, Mapping 10 11from table2ascii import Alignment, PresetStyle, table2ascii 12 13from libs.domain.command import CommandParser 14from libs.utils import dbutil 15from libs.utils.timekit import ExtendedDatetime as ExtDt 16 17if TYPE_CHECKING: 18 from pathlib import Path 19 20 21@dataclass 22class RuleData: 23 """ルールデータ""" 24 25 # ルール 26 rule_version: str = "" 27 """ルール識別子""" 28 mode: Literal[3, 4] = 4 29 """ 集計モード切替(四人打ち/三人打ち)""" 30 origin_point: int = 250 31 """配給原点""" 32 return_point: int = 300 33 """返し点""" 34 rank_point: list[int] = field(default_factory=list) 35 """順位点""" 36 ignore_flying: bool = False 37 """トビカウント 38 - *True*: なし 39 - *False*: あり 40 """ 41 draw_split: bool = False 42 """同点時の順位点 43 - *True*: 山分けにする 44 - *False*: 席順で決める 45 """ 46 undefined_word: int = 1 47 """未定義ワードタイプ""" 48 keywords: list[str] = field(default_factory=list) 49 """成績記録キーワード""" 50 remarks: list[str] = field(default_factory=list) 51 """メモ記録用ワード""" 52 dropitems: list[str] = field(default_factory=list) 53 """非表示にする項目""" 54 55 # ステータス 56 first_time: ExtDt = field(default=ExtDt("1900-01-01 00:00:00")) 57 """記録開始日時""" 58 last_time: ExtDt = field(default=ExtDt("1900-01-01 00:00:00")) 59 """最終記録日時""" 60 count: int = 0 61 """記録回数""" 62 63 def update(self, rule_data: Mapping[str, Any]) -> None: 64 """ 65 ルール更新 66 67 Args: 68 rule_data (Mapping): 更新データ 69 70 """ 71 if "rule_version" in rule_data: 72 self.rule_version = str(rule_data["rule_version"]) 73 if "origin_point" in rule_data: 74 self.origin_point = int(rule_data["origin_point"]) 75 if "return_point" in rule_data: 76 self.return_point = int(rule_data["return_point"]) 77 78 if rank_point := rule_data.get("rank_point"): 79 if isinstance(rank_point, str): 80 rank_point = rank_point.split(",") 81 self.rank_point = list(map(int, map(float, rank_point[: self.mode]))) 82 elif isinstance(rank_point, list): 83 self.rank_point = list(map(int, map(float, rank_point[: self.mode]))) 84 85 if undefined_word := rule_data.get("undefined_word"): 86 self.undefined_word = int(undefined_word) 87 else: 88 self.undefined_word = 1 89 90 for item in ["ignore_flying", "draw_split"]: 91 if flag := rule_data.get(item): 92 if isinstance(flag, bool): 93 setattr(self, item, flag) 94 else: 95 setattr(self, item, str(flag).lower() in {"1", "true", "yes", "on"}) 96 97 for item in ["keywords", "remarks", "dropitems"]: 98 if item_list := rule_data.get(item): 99 if isinstance(item_list, str): 100 setattr(self, item, [x.strip() for x in item_list.split(",")]) 101 elif isinstance(item_list, list): 102 setattr(self, item, item_list) 103 104 105class RuleSet: 106 """ルールセット""" 107 108 def __init__(self) -> None: 109 self.config: ConfigParser = ConfigParser() 110 """ルール設定ファイル""" 111 self.data: dict[str, RuleData] = {} 112 """ルール情報格納辞書""" 113 self.keyword_mapping: dict[str, str] = {} 114 """登録キーワードとルール識別子のマッピング""" 115 self.remarks_words: list[str] = [] 116 """メモ記録用ワードリスト""" 117 118 def data_set(self, section_name: str, rule_data: Mapping[str, Any]) -> None: 119 """ 120 ルール登録 121 122 Args: 123 section_name (str): セクション名 124 rule_data (Mapping): 更新データ情報 125 126 """ 127 rule = RuleData() 128 129 # 初期値セット 130 match int(rule_data.get("mode", 4)): 131 case 3: 132 rule.mode = 3 133 rule.origin_point = 350 134 rule.return_point = 400 135 rule.rank_point = [30, 0, -30] 136 case 4: 137 rule.mode = 4 138 rule.origin_point = 250 139 rule.return_point = 300 140 rule.rank_point = [30, 10, -10, -30] 141 case _: 142 logging.warning("Do not register: %s (invalid mode: %s)", section_name, rule_data.get("mode")) 143 return 144 145 # 設定値取り込み 146 rule.update(rule_data) 147 if not rule.rule_version: 148 rule.rule_version = section_name 149 self.data.update({rule.rule_version: rule}) 150 151 def read_config(self, config: "Path") -> None: 152 """ 153 設定ファイル読み込み 154 155 Args: 156 config (Path): ルール設定ファイル 157 158 """ 159 self.config.read(config, encoding="utf-8") 160 for section_name in map(str, self.config.sections()): 161 if section_name.startswith("regulations_") or section_name.endswith("_regulations"): 162 continue 163 if section_name.startswith("regulations_team_") or section_name.endswith("_regulations_team"): 164 continue 165 166 self.data_set(section_name, dict(self.config[section_name])) 167 168 def status_update(self, params: dict[str, Any]) -> None: 169 """ 170 ステータス更新 171 172 Args: 173 params (dict[str, Any]): プレースホルダ 174 175 """ 176 # ステータスリセット 177 for rule_version in self.rule_list: 178 self.data[rule_version].count = 0 179 self.data[rule_version].first_time.set("1900-01-01 00:00:00") 180 self.data[rule_version].last_time.set("1900-01-01 00:00:00") 181 182 status = dbutil.execute( 183 """ 184 select 185 rule_version, 186 min(ts) as first_time, 187 max(ts) as last_time, 188 count() as count 189 from 190 result 191 --[separate] where source = :source 192 group by 193 rule_version 194 ; 195 """, 196 params, 197 ) 198 199 # ステータス更新 200 for status_data in status: 201 if (rule_version := str(status_data.get("rule_version", ""))) and self.data.get(rule_version): 202 if "count" in status_data: 203 self.data[rule_version].count = int(status_data["count"]) 204 if "first_time" in status_data: 205 self.data[rule_version].first_time = ExtDt(float(status_data["first_time"])) 206 if "last_time" in status_data: 207 self.data[rule_version].last_time = ExtDt(float(status_data["last_time"])) 208 209 def remarks_words_update(self, suffix: list[str]) -> None: 210 """ 211 メモ記録ワードリストを更新する 212 213 Args: 214 suffix (list[str]): メモに追加するサフィックス 215 216 """ 217 ret: list[str] = [] 218 219 for rule in self.rule_list: 220 if rule_data := self.data.get(rule): 221 if rule_data.remarks: 222 ret.extend(rule_data.remarks) 223 else: 224 ret.extend( 225 [f"{pre}{suf}" for pre in self.keywords(rule) for suf in suffix], 226 ) 227 228 self.remarks_words = list(set(ret)) 229 230 def to_dict(self, rule_version: str) -> dict[str, Any]: 231 """ 232 指定ルール識別子の情報を辞書で返す 233 234 Args: 235 rule_version (str): ルール識別子 236 237 Returns: 238 dict[str, Any]: ルール情報 239 240 """ 241 if rule := self.data.get(rule_version): 242 return { 243 "rule_version": rule.rule_version, 244 "mode": rule.mode, 245 "origin_point": rule.origin_point, 246 "return_point": rule.return_point, 247 "rank_point": rule.rank_point, 248 "ignore_flying": rule.ignore_flying, 249 "draw_split": rule.draw_split, 250 "undefined_word": rule.undefined_word, 251 } 252 253 return {} 254 255 def keywords(self, rule_version: str) -> set[str]: 256 """ 257 成績記録キーワードの取得 258 259 Args: 260 rule_version (str): ルール識別子 261 262 Returns: 263 set[str]: 成績記録キーワード 264 """ 265 if items := self.data.get(rule_version): 266 return set(items.keywords) 267 else: 268 return set([]) 269 270 def dropitems(self, rule_version: str) -> set[str]: 271 """ 272 非表示項目の取得 273 274 Args: 275 rule_version (str): ルール識別子 276 277 Returns: 278 set[str]: 非表示項目 279 """ 280 if items := self.data.get(rule_version): 281 return set(items.dropitems) 282 else: 283 return set([]) 284 285 def get_version(self, mode: int, mapping: bool = True) -> list[str]: 286 """ 287 指定した条件のルール識別子をリストで返す 288 289 Args: 290 mode (int): 集計モード 291 mapping (bool, optional): Defaults to True. 292 - *True*: キーワードマッピングに登録されているルールのみ 293 - *False*: ルールとして定義されているものすべて 294 295 Returns: 296 list[str]: ルール識別子 297 298 """ 299 ret: list[str] = [] 300 301 for keyword, rule in self.data.items(): 302 if rule.mode == mode: 303 if mapping: 304 if keyword in self.keyword_mapping.values(): 305 ret.append(rule.rule_version) 306 else: 307 ret.append(rule.rule_version) 308 309 return ret 310 311 def get_mode(self, rule_version: str) -> int: 312 """ 313 指定ルール識別子の集計モードを返す 314 315 Args: 316 rule_version (str): ルール識別子 317 318 Returns: 319 int: 集計モード 320 321 """ 322 return int(self.to_dict(rule_version).get("mode", 0)) 323 324 def get_ignore_flying(self, rule_version: str) -> bool: 325 """ 326 指定ルール識別子のトビカウントフラグを返す 327 328 Args: 329 rule_version (str): ルール識別子 330 331 Returns: 332 bool: トビカウントフラグ 333 334 """ 335 return bool(self.to_dict(rule_version).get("ignore_flying", False)) 336 337 def get_undefined_word(self, rule_version: str) -> int: 338 """ 339 指定ルール識別子の未定義ワードタイプを返す 340 341 Args: 342 rule_version (str): ルール識別子 343 344 Returns: 345 int: 未定義ワードタイプ 346 347 """ 348 return int(self.to_dict(rule_version).get("undefined_word", 1)) 349 350 def print(self, rule_version: str) -> str: 351 """ 352 指定ルール識別子の内容を出力する 353 354 Args: 355 rule_version (str): ルール識別子 356 357 Returns: 358 str: 内容 359 360 """ 361 ret: str = "" 362 body_data: list[list[str]] = [] 363 364 if rule := self.data.get(rule_version): 365 body_data.append(["ルール識別子", rule.rule_version]) 366 367 # 集計モード 368 match rule.mode: 369 case 3: 370 body_data.append(["集計モード", "三人打ち"]) 371 case 4: 372 body_data.append(["集計モード", "四人打ち"]) 373 case _: 374 body_data.append(["集計モード", "未定義"]) 375 376 body_data.extend( 377 [ 378 ["素点", f"{rule.origin_point * 100}点持ち / {rule.return_point * 100}点返し"], 379 ["順位点", " / ".join([f"{pt}pt".replace("-", "▲") for pt in rule.rank_point])], 380 ["同点時", "順位点山分け" if rule.draw_split else "席順"], 381 ] 382 ) 383 384 # マッピング情報 385 if keyword := [word for word, mapping_rule in self.keyword_mapping.items() if mapping_rule == rule_version]: 386 body_data.append(["成績登録ワード", "、".join(keyword)]) 387 else: 388 body_data.append(["成績登録ワード", "---"]) 389 390 # 記録時間 391 body_data.append(["記録数", f"{rule.count} ゲーム"]) 392 if rule.count: 393 body_data.extend( 394 [ 395 ["記録開始日時", rule.first_time.format(ExtDt.FMT.YMDHMS)], 396 ["最終記録日時", rule.last_time.format(ExtDt.FMT.YMDHMS)], 397 ] 398 ) 399 400 ret = table2ascii( 401 body=body_data, 402 alignments=[Alignment.LEFT, Alignment.LEFT], 403 style=PresetStyle.plain, 404 ) 405 406 return ret 407 408 def info(self) -> None: 409 """定義ルールをログに出力する""" 410 for rule in self.data.values(): 411 logging.info( 412 "%s: mode=%s, origin_point=%s, return_point=%s, rank_point=%s, draw_split=%s, ignore_flying=%s, undefined_word=%s", 413 rule.rule_version, 414 rule.mode, 415 rule.origin_point, 416 rule.return_point, 417 rule.rank_point, 418 rule.draw_split, 419 rule.ignore_flying, 420 rule.undefined_word, 421 ) 422 if self.keyword_mapping: 423 logging.info("keyword_mapping: %s", self.keyword_mapping) 424 else: 425 logging.warning("keyword_mapping: empty") 426 if self.remarks_words: 427 logging.info("remarks_words: %s", self.remarks_words) 428 else: 429 logging.warning("remarks_words: empty") 430 431 def check(self, chk_commands: set[str], chk_members: set[str], default_rule: str) -> None: 432 """ 433 キーワード重複チェック 434 435 Args: 436 chk_commands (set[str]): チェック対象コマンド名 437 chk_members (set[str]): チェック対象メンバー名/チーム名 438 default_rule (str): デフォルトルールバージョン 439 440 Raises: 441 RuntimeError: 重複あり 442 443 """ 444 chk_word: str | RuleData 445 446 try: 447 # ルール識別子チェック 448 for chk_word in self.data.values(): 449 if CommandParser().is_valid_command(chk_word.rule_version): 450 raise RuntimeError(f"ルール識別子にオプションに使用される単語が使用されています。({chk_word.rule_version})") 451 if chk_word.rule_version in ExtDt.valid_keywords(): 452 raise RuntimeError(f"ルール識別子に検索範囲指定に使用される単語が使用されています。({chk_word.rule_version})") 453 if chk_word.rule_version in chk_commands: 454 raise RuntimeError(f"ルール識別子と定義済みコマンドに重複があります。({chk_word.rule_version})") 455 if chk_word.rule_version in chk_members: 456 raise RuntimeError(f"ルール識別子と登録メンバー(チーム)に重複があります。({chk_word.rule_version})") 457 # 成績登録ワードチェック 458 for chk_word in self.keyword_mapping.keys(): 459 if CommandParser().is_valid_command(chk_word): 460 raise RuntimeError(f"成績登録ワードにオプションに使用される単語が使用されています。({chk_word})") 461 if chk_word in ExtDt.valid_keywords(): 462 raise RuntimeError(f"成績登録ワードに検索範囲指定に使用される単語が使用されています。({chk_word})") 463 if chk_word in chk_commands: 464 raise RuntimeError(f"成績登録ワードと定義済みコマンドに重複があります。({chk_word})") 465 if chk_word in chk_members: 466 raise RuntimeError(f"成績登録ワードと登録メンバー(チーム)に重複があります。({chk_word})") 467 # デフォルトルールバージョンチェック 468 if default_rule not in self.rule_list: 469 raise RuntimeError(f"デフォルトルールバージョンに指定されているルールセットが見つかりません。({default_rule})") 470 except RuntimeError as err: 471 logging.critical("%s", err) 472 sys.exit(1) 473 474 def register_to_database(self) -> None: 475 """ルールセット情報をDBに登録する""" 476 dbutil.execute("delete from rule;") 477 for rule in self.rule_list: 478 params = self.to_dict(rule) 479 params.update(rank_point=" ".join(map(str, params["rank_point"]))) 480 dbutil.execute( 481 """ 482 insert into 483 rule ( 484 rule_version, mode, origin_point, return_point, rank_point, ignore_flying, draw_split, undefined_word 485 ) values ( 486 :rule_version, :mode, :origin_point, :return_point, :rank_point, :ignore_flying, :draw_split, :undefined_word 487 ); 488 """, 489 params, 490 ) 491 492 @property 493 def rule_list(self) -> list[str]: 494 """ 495 定義済みルール識別子の列挙 496 497 Returns: 498 list[str]: ルール識別子 499 500 """ 501 return [x.rule_version for x in self.data.values()]
@dataclass
class
RuleData:
22@dataclass 23class RuleData: 24 """ルールデータ""" 25 26 # ルール 27 rule_version: str = "" 28 """ルール識別子""" 29 mode: Literal[3, 4] = 4 30 """ 集計モード切替(四人打ち/三人打ち)""" 31 origin_point: int = 250 32 """配給原点""" 33 return_point: int = 300 34 """返し点""" 35 rank_point: list[int] = field(default_factory=list) 36 """順位点""" 37 ignore_flying: bool = False 38 """トビカウント 39 - *True*: なし 40 - *False*: あり 41 """ 42 draw_split: bool = False 43 """同点時の順位点 44 - *True*: 山分けにする 45 - *False*: 席順で決める 46 """ 47 undefined_word: int = 1 48 """未定義ワードタイプ""" 49 keywords: list[str] = field(default_factory=list) 50 """成績記録キーワード""" 51 remarks: list[str] = field(default_factory=list) 52 """メモ記録用ワード""" 53 dropitems: list[str] = field(default_factory=list) 54 """非表示にする項目""" 55 56 # ステータス 57 first_time: ExtDt = field(default=ExtDt("1900-01-01 00:00:00")) 58 """記録開始日時""" 59 last_time: ExtDt = field(default=ExtDt("1900-01-01 00:00:00")) 60 """最終記録日時""" 61 count: int = 0 62 """記録回数""" 63 64 def update(self, rule_data: Mapping[str, Any]) -> None: 65 """ 66 ルール更新 67 68 Args: 69 rule_data (Mapping): 更新データ 70 71 """ 72 if "rule_version" in rule_data: 73 self.rule_version = str(rule_data["rule_version"]) 74 if "origin_point" in rule_data: 75 self.origin_point = int(rule_data["origin_point"]) 76 if "return_point" in rule_data: 77 self.return_point = int(rule_data["return_point"]) 78 79 if rank_point := rule_data.get("rank_point"): 80 if isinstance(rank_point, str): 81 rank_point = rank_point.split(",") 82 self.rank_point = list(map(int, map(float, rank_point[: self.mode]))) 83 elif isinstance(rank_point, list): 84 self.rank_point = list(map(int, map(float, rank_point[: self.mode]))) 85 86 if undefined_word := rule_data.get("undefined_word"): 87 self.undefined_word = int(undefined_word) 88 else: 89 self.undefined_word = 1 90 91 for item in ["ignore_flying", "draw_split"]: 92 if flag := rule_data.get(item): 93 if isinstance(flag, bool): 94 setattr(self, item, flag) 95 else: 96 setattr(self, item, str(flag).lower() in {"1", "true", "yes", "on"}) 97 98 for item in ["keywords", "remarks", "dropitems"]: 99 if item_list := rule_data.get(item): 100 if isinstance(item_list, str): 101 setattr(self, item, [x.strip() for x in item_list.split(",")]) 102 elif isinstance(item_list, list): 103 setattr(self, item, item_list)
ルールデータ
RuleData( rule_version: str = '', mode: Literal[3, 4] = 4, origin_point: int = 250, return_point: int = 300, rank_point: list[int] = <factory>, ignore_flying: bool = False, draw_split: bool = False, undefined_word: int = 1, keywords: list[str] = <factory>, remarks: list[str] = <factory>, dropitems: list[str] = <factory>, first_time: libs.utils.timekit.ExtendedDatetime = 1900-01-01 00:00:00.000000, last_time: libs.utils.timekit.ExtendedDatetime = 1900-01-01 00:00:00.000000, count: int = 0)
def
update(self, rule_data: Mapping[str, Any]) -> None:
64 def update(self, rule_data: Mapping[str, Any]) -> None: 65 """ 66 ルール更新 67 68 Args: 69 rule_data (Mapping): 更新データ 70 71 """ 72 if "rule_version" in rule_data: 73 self.rule_version = str(rule_data["rule_version"]) 74 if "origin_point" in rule_data: 75 self.origin_point = int(rule_data["origin_point"]) 76 if "return_point" in rule_data: 77 self.return_point = int(rule_data["return_point"]) 78 79 if rank_point := rule_data.get("rank_point"): 80 if isinstance(rank_point, str): 81 rank_point = rank_point.split(",") 82 self.rank_point = list(map(int, map(float, rank_point[: self.mode]))) 83 elif isinstance(rank_point, list): 84 self.rank_point = list(map(int, map(float, rank_point[: self.mode]))) 85 86 if undefined_word := rule_data.get("undefined_word"): 87 self.undefined_word = int(undefined_word) 88 else: 89 self.undefined_word = 1 90 91 for item in ["ignore_flying", "draw_split"]: 92 if flag := rule_data.get(item): 93 if isinstance(flag, bool): 94 setattr(self, item, flag) 95 else: 96 setattr(self, item, str(flag).lower() in {"1", "true", "yes", "on"}) 97 98 for item in ["keywords", "remarks", "dropitems"]: 99 if item_list := rule_data.get(item): 100 if isinstance(item_list, str): 101 setattr(self, item, [x.strip() for x in item_list.split(",")]) 102 elif isinstance(item_list, list): 103 setattr(self, item, item_list)
ルール更新
Arguments:
- rule_data (Mapping): 更新データ
class
RuleSet:
106class RuleSet: 107 """ルールセット""" 108 109 def __init__(self) -> None: 110 self.config: ConfigParser = ConfigParser() 111 """ルール設定ファイル""" 112 self.data: dict[str, RuleData] = {} 113 """ルール情報格納辞書""" 114 self.keyword_mapping: dict[str, str] = {} 115 """登録キーワードとルール識別子のマッピング""" 116 self.remarks_words: list[str] = [] 117 """メモ記録用ワードリスト""" 118 119 def data_set(self, section_name: str, rule_data: Mapping[str, Any]) -> None: 120 """ 121 ルール登録 122 123 Args: 124 section_name (str): セクション名 125 rule_data (Mapping): 更新データ情報 126 127 """ 128 rule = RuleData() 129 130 # 初期値セット 131 match int(rule_data.get("mode", 4)): 132 case 3: 133 rule.mode = 3 134 rule.origin_point = 350 135 rule.return_point = 400 136 rule.rank_point = [30, 0, -30] 137 case 4: 138 rule.mode = 4 139 rule.origin_point = 250 140 rule.return_point = 300 141 rule.rank_point = [30, 10, -10, -30] 142 case _: 143 logging.warning("Do not register: %s (invalid mode: %s)", section_name, rule_data.get("mode")) 144 return 145 146 # 設定値取り込み 147 rule.update(rule_data) 148 if not rule.rule_version: 149 rule.rule_version = section_name 150 self.data.update({rule.rule_version: rule}) 151 152 def read_config(self, config: "Path") -> None: 153 """ 154 設定ファイル読み込み 155 156 Args: 157 config (Path): ルール設定ファイル 158 159 """ 160 self.config.read(config, encoding="utf-8") 161 for section_name in map(str, self.config.sections()): 162 if section_name.startswith("regulations_") or section_name.endswith("_regulations"): 163 continue 164 if section_name.startswith("regulations_team_") or section_name.endswith("_regulations_team"): 165 continue 166 167 self.data_set(section_name, dict(self.config[section_name])) 168 169 def status_update(self, params: dict[str, Any]) -> None: 170 """ 171 ステータス更新 172 173 Args: 174 params (dict[str, Any]): プレースホルダ 175 176 """ 177 # ステータスリセット 178 for rule_version in self.rule_list: 179 self.data[rule_version].count = 0 180 self.data[rule_version].first_time.set("1900-01-01 00:00:00") 181 self.data[rule_version].last_time.set("1900-01-01 00:00:00") 182 183 status = dbutil.execute( 184 """ 185 select 186 rule_version, 187 min(ts) as first_time, 188 max(ts) as last_time, 189 count() as count 190 from 191 result 192 --[separate] where source = :source 193 group by 194 rule_version 195 ; 196 """, 197 params, 198 ) 199 200 # ステータス更新 201 for status_data in status: 202 if (rule_version := str(status_data.get("rule_version", ""))) and self.data.get(rule_version): 203 if "count" in status_data: 204 self.data[rule_version].count = int(status_data["count"]) 205 if "first_time" in status_data: 206 self.data[rule_version].first_time = ExtDt(float(status_data["first_time"])) 207 if "last_time" in status_data: 208 self.data[rule_version].last_time = ExtDt(float(status_data["last_time"])) 209 210 def remarks_words_update(self, suffix: list[str]) -> None: 211 """ 212 メモ記録ワードリストを更新する 213 214 Args: 215 suffix (list[str]): メモに追加するサフィックス 216 217 """ 218 ret: list[str] = [] 219 220 for rule in self.rule_list: 221 if rule_data := self.data.get(rule): 222 if rule_data.remarks: 223 ret.extend(rule_data.remarks) 224 else: 225 ret.extend( 226 [f"{pre}{suf}" for pre in self.keywords(rule) for suf in suffix], 227 ) 228 229 self.remarks_words = list(set(ret)) 230 231 def to_dict(self, rule_version: str) -> dict[str, Any]: 232 """ 233 指定ルール識別子の情報を辞書で返す 234 235 Args: 236 rule_version (str): ルール識別子 237 238 Returns: 239 dict[str, Any]: ルール情報 240 241 """ 242 if rule := self.data.get(rule_version): 243 return { 244 "rule_version": rule.rule_version, 245 "mode": rule.mode, 246 "origin_point": rule.origin_point, 247 "return_point": rule.return_point, 248 "rank_point": rule.rank_point, 249 "ignore_flying": rule.ignore_flying, 250 "draw_split": rule.draw_split, 251 "undefined_word": rule.undefined_word, 252 } 253 254 return {} 255 256 def keywords(self, rule_version: str) -> set[str]: 257 """ 258 成績記録キーワードの取得 259 260 Args: 261 rule_version (str): ルール識別子 262 263 Returns: 264 set[str]: 成績記録キーワード 265 """ 266 if items := self.data.get(rule_version): 267 return set(items.keywords) 268 else: 269 return set([]) 270 271 def dropitems(self, rule_version: str) -> set[str]: 272 """ 273 非表示項目の取得 274 275 Args: 276 rule_version (str): ルール識別子 277 278 Returns: 279 set[str]: 非表示項目 280 """ 281 if items := self.data.get(rule_version): 282 return set(items.dropitems) 283 else: 284 return set([]) 285 286 def get_version(self, mode: int, mapping: bool = True) -> list[str]: 287 """ 288 指定した条件のルール識別子をリストで返す 289 290 Args: 291 mode (int): 集計モード 292 mapping (bool, optional): Defaults to True. 293 - *True*: キーワードマッピングに登録されているルールのみ 294 - *False*: ルールとして定義されているものすべて 295 296 Returns: 297 list[str]: ルール識別子 298 299 """ 300 ret: list[str] = [] 301 302 for keyword, rule in self.data.items(): 303 if rule.mode == mode: 304 if mapping: 305 if keyword in self.keyword_mapping.values(): 306 ret.append(rule.rule_version) 307 else: 308 ret.append(rule.rule_version) 309 310 return ret 311 312 def get_mode(self, rule_version: str) -> int: 313 """ 314 指定ルール識別子の集計モードを返す 315 316 Args: 317 rule_version (str): ルール識別子 318 319 Returns: 320 int: 集計モード 321 322 """ 323 return int(self.to_dict(rule_version).get("mode", 0)) 324 325 def get_ignore_flying(self, rule_version: str) -> bool: 326 """ 327 指定ルール識別子のトビカウントフラグを返す 328 329 Args: 330 rule_version (str): ルール識別子 331 332 Returns: 333 bool: トビカウントフラグ 334 335 """ 336 return bool(self.to_dict(rule_version).get("ignore_flying", False)) 337 338 def get_undefined_word(self, rule_version: str) -> int: 339 """ 340 指定ルール識別子の未定義ワードタイプを返す 341 342 Args: 343 rule_version (str): ルール識別子 344 345 Returns: 346 int: 未定義ワードタイプ 347 348 """ 349 return int(self.to_dict(rule_version).get("undefined_word", 1)) 350 351 def print(self, rule_version: str) -> str: 352 """ 353 指定ルール識別子の内容を出力する 354 355 Args: 356 rule_version (str): ルール識別子 357 358 Returns: 359 str: 内容 360 361 """ 362 ret: str = "" 363 body_data: list[list[str]] = [] 364 365 if rule := self.data.get(rule_version): 366 body_data.append(["ルール識別子", rule.rule_version]) 367 368 # 集計モード 369 match rule.mode: 370 case 3: 371 body_data.append(["集計モード", "三人打ち"]) 372 case 4: 373 body_data.append(["集計モード", "四人打ち"]) 374 case _: 375 body_data.append(["集計モード", "未定義"]) 376 377 body_data.extend( 378 [ 379 ["素点", f"{rule.origin_point * 100}点持ち / {rule.return_point * 100}点返し"], 380 ["順位点", " / ".join([f"{pt}pt".replace("-", "▲") for pt in rule.rank_point])], 381 ["同点時", "順位点山分け" if rule.draw_split else "席順"], 382 ] 383 ) 384 385 # マッピング情報 386 if keyword := [word for word, mapping_rule in self.keyword_mapping.items() if mapping_rule == rule_version]: 387 body_data.append(["成績登録ワード", "、".join(keyword)]) 388 else: 389 body_data.append(["成績登録ワード", "---"]) 390 391 # 記録時間 392 body_data.append(["記録数", f"{rule.count} ゲーム"]) 393 if rule.count: 394 body_data.extend( 395 [ 396 ["記録開始日時", rule.first_time.format(ExtDt.FMT.YMDHMS)], 397 ["最終記録日時", rule.last_time.format(ExtDt.FMT.YMDHMS)], 398 ] 399 ) 400 401 ret = table2ascii( 402 body=body_data, 403 alignments=[Alignment.LEFT, Alignment.LEFT], 404 style=PresetStyle.plain, 405 ) 406 407 return ret 408 409 def info(self) -> None: 410 """定義ルールをログに出力する""" 411 for rule in self.data.values(): 412 logging.info( 413 "%s: mode=%s, origin_point=%s, return_point=%s, rank_point=%s, draw_split=%s, ignore_flying=%s, undefined_word=%s", 414 rule.rule_version, 415 rule.mode, 416 rule.origin_point, 417 rule.return_point, 418 rule.rank_point, 419 rule.draw_split, 420 rule.ignore_flying, 421 rule.undefined_word, 422 ) 423 if self.keyword_mapping: 424 logging.info("keyword_mapping: %s", self.keyword_mapping) 425 else: 426 logging.warning("keyword_mapping: empty") 427 if self.remarks_words: 428 logging.info("remarks_words: %s", self.remarks_words) 429 else: 430 logging.warning("remarks_words: empty") 431 432 def check(self, chk_commands: set[str], chk_members: set[str], default_rule: str) -> None: 433 """ 434 キーワード重複チェック 435 436 Args: 437 chk_commands (set[str]): チェック対象コマンド名 438 chk_members (set[str]): チェック対象メンバー名/チーム名 439 default_rule (str): デフォルトルールバージョン 440 441 Raises: 442 RuntimeError: 重複あり 443 444 """ 445 chk_word: str | RuleData 446 447 try: 448 # ルール識別子チェック 449 for chk_word in self.data.values(): 450 if CommandParser().is_valid_command(chk_word.rule_version): 451 raise RuntimeError(f"ルール識別子にオプションに使用される単語が使用されています。({chk_word.rule_version})") 452 if chk_word.rule_version in ExtDt.valid_keywords(): 453 raise RuntimeError(f"ルール識別子に検索範囲指定に使用される単語が使用されています。({chk_word.rule_version})") 454 if chk_word.rule_version in chk_commands: 455 raise RuntimeError(f"ルール識別子と定義済みコマンドに重複があります。({chk_word.rule_version})") 456 if chk_word.rule_version in chk_members: 457 raise RuntimeError(f"ルール識別子と登録メンバー(チーム)に重複があります。({chk_word.rule_version})") 458 # 成績登録ワードチェック 459 for chk_word in self.keyword_mapping.keys(): 460 if CommandParser().is_valid_command(chk_word): 461 raise RuntimeError(f"成績登録ワードにオプションに使用される単語が使用されています。({chk_word})") 462 if chk_word in ExtDt.valid_keywords(): 463 raise RuntimeError(f"成績登録ワードに検索範囲指定に使用される単語が使用されています。({chk_word})") 464 if chk_word in chk_commands: 465 raise RuntimeError(f"成績登録ワードと定義済みコマンドに重複があります。({chk_word})") 466 if chk_word in chk_members: 467 raise RuntimeError(f"成績登録ワードと登録メンバー(チーム)に重複があります。({chk_word})") 468 # デフォルトルールバージョンチェック 469 if default_rule not in self.rule_list: 470 raise RuntimeError(f"デフォルトルールバージョンに指定されているルールセットが見つかりません。({default_rule})") 471 except RuntimeError as err: 472 logging.critical("%s", err) 473 sys.exit(1) 474 475 def register_to_database(self) -> None: 476 """ルールセット情報をDBに登録する""" 477 dbutil.execute("delete from rule;") 478 for rule in self.rule_list: 479 params = self.to_dict(rule) 480 params.update(rank_point=" ".join(map(str, params["rank_point"]))) 481 dbutil.execute( 482 """ 483 insert into 484 rule ( 485 rule_version, mode, origin_point, return_point, rank_point, ignore_flying, draw_split, undefined_word 486 ) values ( 487 :rule_version, :mode, :origin_point, :return_point, :rank_point, :ignore_flying, :draw_split, :undefined_word 488 ); 489 """, 490 params, 491 ) 492 493 @property 494 def rule_list(self) -> list[str]: 495 """ 496 定義済みルール識別子の列挙 497 498 Returns: 499 list[str]: ルール識別子 500 501 """ 502 return [x.rule_version for x in self.data.values()]
ルールセット
def
data_set(self, section_name: str, rule_data: Mapping[str, Any]) -> None:
119 def data_set(self, section_name: str, rule_data: Mapping[str, Any]) -> None: 120 """ 121 ルール登録 122 123 Args: 124 section_name (str): セクション名 125 rule_data (Mapping): 更新データ情報 126 127 """ 128 rule = RuleData() 129 130 # 初期値セット 131 match int(rule_data.get("mode", 4)): 132 case 3: 133 rule.mode = 3 134 rule.origin_point = 350 135 rule.return_point = 400 136 rule.rank_point = [30, 0, -30] 137 case 4: 138 rule.mode = 4 139 rule.origin_point = 250 140 rule.return_point = 300 141 rule.rank_point = [30, 10, -10, -30] 142 case _: 143 logging.warning("Do not register: %s (invalid mode: %s)", section_name, rule_data.get("mode")) 144 return 145 146 # 設定値取り込み 147 rule.update(rule_data) 148 if not rule.rule_version: 149 rule.rule_version = section_name 150 self.data.update({rule.rule_version: rule})
ルール登録
Arguments:
- section_name (str): セクション名
- rule_data (Mapping): 更新データ情報
def
read_config(self, config: pathlib.Path) -> None:
152 def read_config(self, config: "Path") -> None: 153 """ 154 設定ファイル読み込み 155 156 Args: 157 config (Path): ルール設定ファイル 158 159 """ 160 self.config.read(config, encoding="utf-8") 161 for section_name in map(str, self.config.sections()): 162 if section_name.startswith("regulations_") or section_name.endswith("_regulations"): 163 continue 164 if section_name.startswith("regulations_team_") or section_name.endswith("_regulations_team"): 165 continue 166 167 self.data_set(section_name, dict(self.config[section_name]))
設定ファイル読み込み
Arguments:
- config (Path): ルール設定ファイル
def
status_update(self, params: dict[str, typing.Any]) -> None:
169 def status_update(self, params: dict[str, Any]) -> None: 170 """ 171 ステータス更新 172 173 Args: 174 params (dict[str, Any]): プレースホルダ 175 176 """ 177 # ステータスリセット 178 for rule_version in self.rule_list: 179 self.data[rule_version].count = 0 180 self.data[rule_version].first_time.set("1900-01-01 00:00:00") 181 self.data[rule_version].last_time.set("1900-01-01 00:00:00") 182 183 status = dbutil.execute( 184 """ 185 select 186 rule_version, 187 min(ts) as first_time, 188 max(ts) as last_time, 189 count() as count 190 from 191 result 192 --[separate] where source = :source 193 group by 194 rule_version 195 ; 196 """, 197 params, 198 ) 199 200 # ステータス更新 201 for status_data in status: 202 if (rule_version := str(status_data.get("rule_version", ""))) and self.data.get(rule_version): 203 if "count" in status_data: 204 self.data[rule_version].count = int(status_data["count"]) 205 if "first_time" in status_data: 206 self.data[rule_version].first_time = ExtDt(float(status_data["first_time"])) 207 if "last_time" in status_data: 208 self.data[rule_version].last_time = ExtDt(float(status_data["last_time"]))
ステータス更新
Arguments:
- params (dict[str, Any]): プレースホルダ
def
remarks_words_update(self, suffix: list[str]) -> None:
210 def remarks_words_update(self, suffix: list[str]) -> None: 211 """ 212 メモ記録ワードリストを更新する 213 214 Args: 215 suffix (list[str]): メモに追加するサフィックス 216 217 """ 218 ret: list[str] = [] 219 220 for rule in self.rule_list: 221 if rule_data := self.data.get(rule): 222 if rule_data.remarks: 223 ret.extend(rule_data.remarks) 224 else: 225 ret.extend( 226 [f"{pre}{suf}" for pre in self.keywords(rule) for suf in suffix], 227 ) 228 229 self.remarks_words = list(set(ret))
メモ記録ワードリストを更新する
Arguments:
- suffix (list[str]): メモに追加するサフィックス
def
to_dict(self, rule_version: str) -> dict[str, typing.Any]:
231 def to_dict(self, rule_version: str) -> dict[str, Any]: 232 """ 233 指定ルール識別子の情報を辞書で返す 234 235 Args: 236 rule_version (str): ルール識別子 237 238 Returns: 239 dict[str, Any]: ルール情報 240 241 """ 242 if rule := self.data.get(rule_version): 243 return { 244 "rule_version": rule.rule_version, 245 "mode": rule.mode, 246 "origin_point": rule.origin_point, 247 "return_point": rule.return_point, 248 "rank_point": rule.rank_point, 249 "ignore_flying": rule.ignore_flying, 250 "draw_split": rule.draw_split, 251 "undefined_word": rule.undefined_word, 252 } 253 254 return {}
指定ルール識別子の情報を辞書で返す
Arguments:
- rule_version (str): ルール識別子
Returns:
dict[str, Any]: ルール情報
def
keywords(self, rule_version: str) -> set[str]:
256 def keywords(self, rule_version: str) -> set[str]: 257 """ 258 成績記録キーワードの取得 259 260 Args: 261 rule_version (str): ルール識別子 262 263 Returns: 264 set[str]: 成績記録キーワード 265 """ 266 if items := self.data.get(rule_version): 267 return set(items.keywords) 268 else: 269 return set([])
成績記録キーワードの取得
Arguments:
- rule_version (str): ルール識別子
Returns:
set[str]: 成績記録キーワード
def
dropitems(self, rule_version: str) -> set[str]:
271 def dropitems(self, rule_version: str) -> set[str]: 272 """ 273 非表示項目の取得 274 275 Args: 276 rule_version (str): ルール識別子 277 278 Returns: 279 set[str]: 非表示項目 280 """ 281 if items := self.data.get(rule_version): 282 return set(items.dropitems) 283 else: 284 return set([])
非表示項目の取得
Arguments:
- rule_version (str): ルール識別子
Returns:
set[str]: 非表示項目
def
get_version(self, mode: int, mapping: bool = True) -> list[str]:
286 def get_version(self, mode: int, mapping: bool = True) -> list[str]: 287 """ 288 指定した条件のルール識別子をリストで返す 289 290 Args: 291 mode (int): 集計モード 292 mapping (bool, optional): Defaults to True. 293 - *True*: キーワードマッピングに登録されているルールのみ 294 - *False*: ルールとして定義されているものすべて 295 296 Returns: 297 list[str]: ルール識別子 298 299 """ 300 ret: list[str] = [] 301 302 for keyword, rule in self.data.items(): 303 if rule.mode == mode: 304 if mapping: 305 if keyword in self.keyword_mapping.values(): 306 ret.append(rule.rule_version) 307 else: 308 ret.append(rule.rule_version) 309 310 return ret
指定した条件のルール識別子をリストで返す
Arguments:
- mode (int): 集計モード
- mapping (bool, optional): Defaults to True.
- True: キーワードマッピングに登録されているルールのみ
- False: ルールとして定義されているものすべて
Returns:
list[str]: ルール識別子
def
get_mode(self, rule_version: str) -> int:
312 def get_mode(self, rule_version: str) -> int: 313 """ 314 指定ルール識別子の集計モードを返す 315 316 Args: 317 rule_version (str): ルール識別子 318 319 Returns: 320 int: 集計モード 321 322 """ 323 return int(self.to_dict(rule_version).get("mode", 0))
指定ルール識別子の集計モードを返す
Arguments:
- rule_version (str): ルール識別子
Returns:
int: 集計モード
def
get_ignore_flying(self, rule_version: str) -> bool:
325 def get_ignore_flying(self, rule_version: str) -> bool: 326 """ 327 指定ルール識別子のトビカウントフラグを返す 328 329 Args: 330 rule_version (str): ルール識別子 331 332 Returns: 333 bool: トビカウントフラグ 334 335 """ 336 return bool(self.to_dict(rule_version).get("ignore_flying", False))
指定ルール識別子のトビカウントフラグを返す
Arguments:
- rule_version (str): ルール識別子
Returns:
bool: トビカウントフラグ
def
get_undefined_word(self, rule_version: str) -> int:
338 def get_undefined_word(self, rule_version: str) -> int: 339 """ 340 指定ルール識別子の未定義ワードタイプを返す 341 342 Args: 343 rule_version (str): ルール識別子 344 345 Returns: 346 int: 未定義ワードタイプ 347 348 """ 349 return int(self.to_dict(rule_version).get("undefined_word", 1))
指定ルール識別子の未定義ワードタイプを返す
Arguments:
- rule_version (str): ルール識別子
Returns:
int: 未定義ワードタイプ
def
print(self, rule_version: str) -> str:
351 def print(self, rule_version: str) -> str: 352 """ 353 指定ルール識別子の内容を出力する 354 355 Args: 356 rule_version (str): ルール識別子 357 358 Returns: 359 str: 内容 360 361 """ 362 ret: str = "" 363 body_data: list[list[str]] = [] 364 365 if rule := self.data.get(rule_version): 366 body_data.append(["ルール識別子", rule.rule_version]) 367 368 # 集計モード 369 match rule.mode: 370 case 3: 371 body_data.append(["集計モード", "三人打ち"]) 372 case 4: 373 body_data.append(["集計モード", "四人打ち"]) 374 case _: 375 body_data.append(["集計モード", "未定義"]) 376 377 body_data.extend( 378 [ 379 ["素点", f"{rule.origin_point * 100}点持ち / {rule.return_point * 100}点返し"], 380 ["順位点", " / ".join([f"{pt}pt".replace("-", "▲") for pt in rule.rank_point])], 381 ["同点時", "順位点山分け" if rule.draw_split else "席順"], 382 ] 383 ) 384 385 # マッピング情報 386 if keyword := [word for word, mapping_rule in self.keyword_mapping.items() if mapping_rule == rule_version]: 387 body_data.append(["成績登録ワード", "、".join(keyword)]) 388 else: 389 body_data.append(["成績登録ワード", "---"]) 390 391 # 記録時間 392 body_data.append(["記録数", f"{rule.count} ゲーム"]) 393 if rule.count: 394 body_data.extend( 395 [ 396 ["記録開始日時", rule.first_time.format(ExtDt.FMT.YMDHMS)], 397 ["最終記録日時", rule.last_time.format(ExtDt.FMT.YMDHMS)], 398 ] 399 ) 400 401 ret = table2ascii( 402 body=body_data, 403 alignments=[Alignment.LEFT, Alignment.LEFT], 404 style=PresetStyle.plain, 405 ) 406 407 return ret
指定ルール識別子の内容を出力する
Arguments:
- rule_version (str): ルール識別子
Returns:
str: 内容
def
info(self) -> None:
409 def info(self) -> None: 410 """定義ルールをログに出力する""" 411 for rule in self.data.values(): 412 logging.info( 413 "%s: mode=%s, origin_point=%s, return_point=%s, rank_point=%s, draw_split=%s, ignore_flying=%s, undefined_word=%s", 414 rule.rule_version, 415 rule.mode, 416 rule.origin_point, 417 rule.return_point, 418 rule.rank_point, 419 rule.draw_split, 420 rule.ignore_flying, 421 rule.undefined_word, 422 ) 423 if self.keyword_mapping: 424 logging.info("keyword_mapping: %s", self.keyword_mapping) 425 else: 426 logging.warning("keyword_mapping: empty") 427 if self.remarks_words: 428 logging.info("remarks_words: %s", self.remarks_words) 429 else: 430 logging.warning("remarks_words: empty")
定義ルールをログに出力する
def
check( self, chk_commands: set[str], chk_members: set[str], default_rule: str) -> None:
432 def check(self, chk_commands: set[str], chk_members: set[str], default_rule: str) -> None: 433 """ 434 キーワード重複チェック 435 436 Args: 437 chk_commands (set[str]): チェック対象コマンド名 438 chk_members (set[str]): チェック対象メンバー名/チーム名 439 default_rule (str): デフォルトルールバージョン 440 441 Raises: 442 RuntimeError: 重複あり 443 444 """ 445 chk_word: str | RuleData 446 447 try: 448 # ルール識別子チェック 449 for chk_word in self.data.values(): 450 if CommandParser().is_valid_command(chk_word.rule_version): 451 raise RuntimeError(f"ルール識別子にオプションに使用される単語が使用されています。({chk_word.rule_version})") 452 if chk_word.rule_version in ExtDt.valid_keywords(): 453 raise RuntimeError(f"ルール識別子に検索範囲指定に使用される単語が使用されています。({chk_word.rule_version})") 454 if chk_word.rule_version in chk_commands: 455 raise RuntimeError(f"ルール識別子と定義済みコマンドに重複があります。({chk_word.rule_version})") 456 if chk_word.rule_version in chk_members: 457 raise RuntimeError(f"ルール識別子と登録メンバー(チーム)に重複があります。({chk_word.rule_version})") 458 # 成績登録ワードチェック 459 for chk_word in self.keyword_mapping.keys(): 460 if CommandParser().is_valid_command(chk_word): 461 raise RuntimeError(f"成績登録ワードにオプションに使用される単語が使用されています。({chk_word})") 462 if chk_word in ExtDt.valid_keywords(): 463 raise RuntimeError(f"成績登録ワードに検索範囲指定に使用される単語が使用されています。({chk_word})") 464 if chk_word in chk_commands: 465 raise RuntimeError(f"成績登録ワードと定義済みコマンドに重複があります。({chk_word})") 466 if chk_word in chk_members: 467 raise RuntimeError(f"成績登録ワードと登録メンバー(チーム)に重複があります。({chk_word})") 468 # デフォルトルールバージョンチェック 469 if default_rule not in self.rule_list: 470 raise RuntimeError(f"デフォルトルールバージョンに指定されているルールセットが見つかりません。({default_rule})") 471 except RuntimeError as err: 472 logging.critical("%s", err) 473 sys.exit(1)
キーワード重複チェック
Arguments:
- chk_commands (set[str]): チェック対象コマンド名
- chk_members (set[str]): チェック対象メンバー名/チーム名
- default_rule (str): デフォルトルールバージョン
Raises:
- RuntimeError: 重複あり
def
register_to_database(self) -> None:
475 def register_to_database(self) -> None: 476 """ルールセット情報をDBに登録する""" 477 dbutil.execute("delete from rule;") 478 for rule in self.rule_list: 479 params = self.to_dict(rule) 480 params.update(rank_point=" ".join(map(str, params["rank_point"]))) 481 dbutil.execute( 482 """ 483 insert into 484 rule ( 485 rule_version, mode, origin_point, return_point, rank_point, ignore_flying, draw_split, undefined_word 486 ) values ( 487 :rule_version, :mode, :origin_point, :return_point, :rank_point, :ignore_flying, :draw_split, :undefined_word 488 ); 489 """, 490 params, 491 )
ルールセット情報をDBに登録する