integrations.slack.events.comparison
integrations/slack/events/comparison.py
1""" 2integrations/slack/events/comparison.py 3""" 4 5import logging 6from typing import TYPE_CHECKING, cast 7 8import libs.global_value as g 9from cls.score import GameResult 10from cls.timekit import ExtendedDatetime as ExtDt 11from libs.data import modify 12from libs.datamodels import ComparisonResults 13from libs.functions import search 14from libs.types import StyleOptions 15from libs.utils import formatter 16 17if TYPE_CHECKING: 18 from integrations.protocols import MessageParserProtocol 19 from integrations.slack.adapter import ServiceAdapter 20 from libs.types import RemarkDict 21 22 23def main(m: "MessageParserProtocol") -> None: 24 """突合処理 25 26 Args: 27 m (MessageParserProtocol): メッセージデータ 28 """ 29 30 g.adapter = cast("ServiceAdapter", g.adapter) 31 results = ComparisonResults(search_after=-g.adapter.conf.search_after) 32 33 check_omission(results) 34 check_remarks(results) 35 check_total_score(results) 36 37 m.set_data("データ突合", results.output("headline"), StyleOptions(key_title=True)) 38 if results.pending: 39 m.set_data("保留", results.output("pending"), StyleOptions(key_title=False)) 40 m.set_data("不一致", results.output("mismatch"), StyleOptions(key_title=False)) 41 m.set_data("取りこぼし", results.output("missing"), StyleOptions(key_title=False)) 42 m.set_data("削除漏れ", results.output("delete"), StyleOptions(key_title=False)) 43 m.set_data("メモ更新", results.output("remark_mod"), StyleOptions(key_title=False)) 44 m.set_data("メモ削除", results.output("remark_del"), StyleOptions(key_title=False)) 45 if results.invalid_score: 46 m.set_data("供託残り", results.output("invalid_score"), StyleOptions(key_title=False)) 47 48 m.post.thread = True 49 m.post.ts = m.data.event_ts 50 m.status.action = "nothing" 51 m.status.message = results 52 53 54def check_omission(results: ComparisonResults): 55 """スコア突合 56 57 Args: 58 results (ComparisonResults): 結果格納データクラス 59 """ 60 61 g.adapter = cast("ServiceAdapter", g.adapter) 62 slack_score: list[GameResult] = [] 63 keep_channel_id: list = [] 64 65 for work_m in set(g.adapter.functions.pickup_score()): 66 if (score := GameResult(**work_m.get_score(g.cfg.setting.keyword), **g.cfg.mahjong.to_dict())): 67 for k, v in score.to_dict().items(): # 名前の正規化 68 if str(k).endswith("_name"): 69 score.set(**{k: formatter.name_replace(str(v), not_replace=True)}) 70 # 保留チェック 71 if check_pending(work_m): 72 results.pending.append(score) 73 else: 74 slack_score.append(score) 75 results.score_list.update({work_m.data.event_ts: work_m}) 76 keep_channel_id.append(work_m.data.channel_id) 77 78 db_score = search.for_db_score(float(results.after.format("ts"))) 79 80 # SLACK -> DATABASE 81 ts_list = [x.ts for x in db_score] 82 for score in slack_score: 83 work_m = results.score_list[score.ts] 84 if score.ts in ts_list: 85 target = db_score[ts_list.index(score.ts)] 86 if score != target: # 不一致(更新) 87 results.mismatch.append({"before": target, "after": score}) 88 logging.info("mismatch: %s (%s)", score.ts, ExtDt(float(score.ts)).format("ymdhms")) 89 logging.debug(" * slack: %s", score.to_text("detail")) 90 logging.debug(" * db: %s", target.to_text("detail")) 91 modify.db_update(score, work_m) 92 g.adapter.functions.post_processing(work_m) 93 else: # 取りこぼし(追加) 94 results.missing.append(score) 95 logging.info("missing: %s (%s)", score.ts, ExtDt(float(score.ts)).format("ymdhms")) 96 logging.debug(score.to_text("logging")) 97 modify.db_insert(score, work_m) 98 g.adapter.functions.post_processing(work_m) 99 100 # DATABASE -> SLACK 101 ts_list = [x.ts for x in slack_score] 102 work_m = g.adapter.parser() 103 work_m.status.command_type = "comparison" 104 for score in db_score: 105 if score.ts not in ts_list: # 削除漏れ 106 work_m.data.event_ts = score.ts 107 if score.source: 108 work_m.data.channel_id = score.source.replace("slack_", "") 109 if work_m.data.channel_id in set(keep_channel_id): 110 results.delete.append(score) 111 logging.info("delete (Only database): %s (%s)", score.ts, ExtDt(float(score.ts)).format("ymdhms")) 112 modify.db_delete(work_m) 113 g.adapter.functions.post_processing(work_m) 114 115 116def check_remarks(results: ComparisonResults): 117 """メモ突合 118 119 Args: 120 results (ComparisonResults): 結果格納データクラス 121 """ 122 123 g.adapter = cast("ServiceAdapter", g.adapter) 124 slack_remarks: list["RemarkDict"] = [] 125 score_list: dict[str, GameResult] = {} 126 127 for loop_m in results.score_list.values(): 128 if (score := GameResult(**loop_m.get_score(g.cfg.setting.keyword), **g.cfg.mahjong.to_dict())): 129 for k, v in score.to_dict().items(): # 名前の正規化 130 if str(k).endswith("_name"): 131 score.set(**{k: formatter.name_replace(str(v), not_replace=True)}) 132 score_list.update({loop_m.data.event_ts: score}) 133 134 for loop_m in g.adapter.functions.pickup_remarks(): 135 for name, matter in zip(loop_m.argument[0::2], loop_m.argument[1::2]): 136 # 対象外のメモはスキップ 137 if not float(loop_m.data.thread_ts): 138 continue # リプライになっていない 139 if loop_m.data.thread_ts not in score_list: 140 continue # ゲーム結果に紐付かない 141 pname = formatter.name_replace(str(name), not_replace=True) 142 if pname not in score_list[loop_m.data.thread_ts].to_list("name"): 143 continue # ゲーム結果に名前がない 144 if loop_m.data.thread_ts in [x.ts for x in results.pending]: 145 continue # 紐付くゲーム結果が保留中 146 slack_remarks.append({ 147 "thread_ts": loop_m.data.thread_ts, 148 "event_ts": loop_m.data.event_ts, 149 "name": pname, 150 "matter": matter, 151 "source": loop_m.status.source, 152 }) 153 154 db_remarks = search.for_db_remarks(float(results.after.format("ts"))) 155 156 # SLACK -> DATABASE 157 work_m = g.adapter.parser() 158 159 for remark in slack_remarks: 160 if remark in db_remarks: 161 continue # 変化なし 162 if remark["thread_ts"] in [x.ts for x in results.pending]: 163 continue # 紐付くゲーム結果が保留中 164 results.remark_mod.append(remark) 165 166 if results.remark_mod: 167 for remark in results.remark_mod: 168 work_m.data.event_ts = remark["event_ts"] 169 work_m.status.command_type = "comparison" 170 work_m.data.channel_id = remark["source"].replace("slack_", "") 171 work_m.status.command_type = "comparison" # リセットがかかるので再セット 172 work_m.data.channel_id = remark["source"].replace("slack_", "") 173 modify.remarks_delete(work_m) 174 modify.remarks_append(work_m, results.remark_mod) 175 176 # DATABASE -> SLACK 177 for remark in db_remarks: 178 if remark not in slack_remarks: # slackに記録なし 179 if remark["source"] in {x.source for x in score_list.values()}: 180 results.remark_del.append(remark) 181 modify.remarks_delete_compar(remark, work_m) 182 183 184def check_total_score(results: ComparisonResults): 185 """素点合計の再チェック 186 187 Args: 188 results (ComparisonResults): 結果格納データクラス 189 """ 190 191 for loop_m in results.score_list.values(): 192 if (score := GameResult(**loop_m.get_score(g.cfg.setting.keyword), **g.cfg.mahjong.to_dict())): 193 for k, v in score.to_dict().items(): # 名前の正規化 194 if str(k).endswith("_name"): 195 score.set(**{k: formatter.name_replace(str(v), not_replace=True)}) 196 if score.deposit: 197 results.invalid_score.append(score) 198 199 200def check_pending(m: "MessageParserProtocol") -> bool: 201 """保留チェック 202 203 Args: 204 m (MessageParserProtocol): メッセージデータ 205 206 Returns: 207 bool: 真偽 208 - *True*: 保留中 209 - *False*: チェック開始 210 """ 211 212 g.adapter = cast("ServiceAdapter", g.adapter) 213 214 now_ts = float(ExtDt().format("ts")) 215 216 if m.data.edited_ts == "undetermined": 217 check_ts = float(m.data.event_ts) + g.adapter.conf.search_wait 218 else: 219 check_ts = float(m.data.edited_ts) + g.adapter.conf.search_wait 220 221 if check_ts > now_ts: 222 return True 223 return False
24def main(m: "MessageParserProtocol") -> None: 25 """突合処理 26 27 Args: 28 m (MessageParserProtocol): メッセージデータ 29 """ 30 31 g.adapter = cast("ServiceAdapter", g.adapter) 32 results = ComparisonResults(search_after=-g.adapter.conf.search_after) 33 34 check_omission(results) 35 check_remarks(results) 36 check_total_score(results) 37 38 m.set_data("データ突合", results.output("headline"), StyleOptions(key_title=True)) 39 if results.pending: 40 m.set_data("保留", results.output("pending"), StyleOptions(key_title=False)) 41 m.set_data("不一致", results.output("mismatch"), StyleOptions(key_title=False)) 42 m.set_data("取りこぼし", results.output("missing"), StyleOptions(key_title=False)) 43 m.set_data("削除漏れ", results.output("delete"), StyleOptions(key_title=False)) 44 m.set_data("メモ更新", results.output("remark_mod"), StyleOptions(key_title=False)) 45 m.set_data("メモ削除", results.output("remark_del"), StyleOptions(key_title=False)) 46 if results.invalid_score: 47 m.set_data("供託残り", results.output("invalid_score"), StyleOptions(key_title=False)) 48 49 m.post.thread = True 50 m.post.ts = m.data.event_ts 51 m.status.action = "nothing" 52 m.status.message = results
突合処理
Arguments:
- m (MessageParserProtocol): メッセージデータ
55def check_omission(results: ComparisonResults): 56 """スコア突合 57 58 Args: 59 results (ComparisonResults): 結果格納データクラス 60 """ 61 62 g.adapter = cast("ServiceAdapter", g.adapter) 63 slack_score: list[GameResult] = [] 64 keep_channel_id: list = [] 65 66 for work_m in set(g.adapter.functions.pickup_score()): 67 if (score := GameResult(**work_m.get_score(g.cfg.setting.keyword), **g.cfg.mahjong.to_dict())): 68 for k, v in score.to_dict().items(): # 名前の正規化 69 if str(k).endswith("_name"): 70 score.set(**{k: formatter.name_replace(str(v), not_replace=True)}) 71 # 保留チェック 72 if check_pending(work_m): 73 results.pending.append(score) 74 else: 75 slack_score.append(score) 76 results.score_list.update({work_m.data.event_ts: work_m}) 77 keep_channel_id.append(work_m.data.channel_id) 78 79 db_score = search.for_db_score(float(results.after.format("ts"))) 80 81 # SLACK -> DATABASE 82 ts_list = [x.ts for x in db_score] 83 for score in slack_score: 84 work_m = results.score_list[score.ts] 85 if score.ts in ts_list: 86 target = db_score[ts_list.index(score.ts)] 87 if score != target: # 不一致(更新) 88 results.mismatch.append({"before": target, "after": score}) 89 logging.info("mismatch: %s (%s)", score.ts, ExtDt(float(score.ts)).format("ymdhms")) 90 logging.debug(" * slack: %s", score.to_text("detail")) 91 logging.debug(" * db: %s", target.to_text("detail")) 92 modify.db_update(score, work_m) 93 g.adapter.functions.post_processing(work_m) 94 else: # 取りこぼし(追加) 95 results.missing.append(score) 96 logging.info("missing: %s (%s)", score.ts, ExtDt(float(score.ts)).format("ymdhms")) 97 logging.debug(score.to_text("logging")) 98 modify.db_insert(score, work_m) 99 g.adapter.functions.post_processing(work_m) 100 101 # DATABASE -> SLACK 102 ts_list = [x.ts for x in slack_score] 103 work_m = g.adapter.parser() 104 work_m.status.command_type = "comparison" 105 for score in db_score: 106 if score.ts not in ts_list: # 削除漏れ 107 work_m.data.event_ts = score.ts 108 if score.source: 109 work_m.data.channel_id = score.source.replace("slack_", "") 110 if work_m.data.channel_id in set(keep_channel_id): 111 results.delete.append(score) 112 logging.info("delete (Only database): %s (%s)", score.ts, ExtDt(float(score.ts)).format("ymdhms")) 113 modify.db_delete(work_m) 114 g.adapter.functions.post_processing(work_m)
スコア突合
Arguments:
- results (ComparisonResults): 結果格納データクラス
117def check_remarks(results: ComparisonResults): 118 """メモ突合 119 120 Args: 121 results (ComparisonResults): 結果格納データクラス 122 """ 123 124 g.adapter = cast("ServiceAdapter", g.adapter) 125 slack_remarks: list["RemarkDict"] = [] 126 score_list: dict[str, GameResult] = {} 127 128 for loop_m in results.score_list.values(): 129 if (score := GameResult(**loop_m.get_score(g.cfg.setting.keyword), **g.cfg.mahjong.to_dict())): 130 for k, v in score.to_dict().items(): # 名前の正規化 131 if str(k).endswith("_name"): 132 score.set(**{k: formatter.name_replace(str(v), not_replace=True)}) 133 score_list.update({loop_m.data.event_ts: score}) 134 135 for loop_m in g.adapter.functions.pickup_remarks(): 136 for name, matter in zip(loop_m.argument[0::2], loop_m.argument[1::2]): 137 # 対象外のメモはスキップ 138 if not float(loop_m.data.thread_ts): 139 continue # リプライになっていない 140 if loop_m.data.thread_ts not in score_list: 141 continue # ゲーム結果に紐付かない 142 pname = formatter.name_replace(str(name), not_replace=True) 143 if pname not in score_list[loop_m.data.thread_ts].to_list("name"): 144 continue # ゲーム結果に名前がない 145 if loop_m.data.thread_ts in [x.ts for x in results.pending]: 146 continue # 紐付くゲーム結果が保留中 147 slack_remarks.append({ 148 "thread_ts": loop_m.data.thread_ts, 149 "event_ts": loop_m.data.event_ts, 150 "name": pname, 151 "matter": matter, 152 "source": loop_m.status.source, 153 }) 154 155 db_remarks = search.for_db_remarks(float(results.after.format("ts"))) 156 157 # SLACK -> DATABASE 158 work_m = g.adapter.parser() 159 160 for remark in slack_remarks: 161 if remark in db_remarks: 162 continue # 変化なし 163 if remark["thread_ts"] in [x.ts for x in results.pending]: 164 continue # 紐付くゲーム結果が保留中 165 results.remark_mod.append(remark) 166 167 if results.remark_mod: 168 for remark in results.remark_mod: 169 work_m.data.event_ts = remark["event_ts"] 170 work_m.status.command_type = "comparison" 171 work_m.data.channel_id = remark["source"].replace("slack_", "") 172 work_m.status.command_type = "comparison" # リセットがかかるので再セット 173 work_m.data.channel_id = remark["source"].replace("slack_", "") 174 modify.remarks_delete(work_m) 175 modify.remarks_append(work_m, results.remark_mod) 176 177 # DATABASE -> SLACK 178 for remark in db_remarks: 179 if remark not in slack_remarks: # slackに記録なし 180 if remark["source"] in {x.source for x in score_list.values()}: 181 results.remark_del.append(remark) 182 modify.remarks_delete_compar(remark, work_m)
メモ突合
Arguments:
- results (ComparisonResults): 結果格納データクラス
185def check_total_score(results: ComparisonResults): 186 """素点合計の再チェック 187 188 Args: 189 results (ComparisonResults): 結果格納データクラス 190 """ 191 192 for loop_m in results.score_list.values(): 193 if (score := GameResult(**loop_m.get_score(g.cfg.setting.keyword), **g.cfg.mahjong.to_dict())): 194 for k, v in score.to_dict().items(): # 名前の正規化 195 if str(k).endswith("_name"): 196 score.set(**{k: formatter.name_replace(str(v), not_replace=True)}) 197 if score.deposit: 198 results.invalid_score.append(score)
素点合計の再チェック
Arguments:
- results (ComparisonResults): 結果格納データクラス
201def check_pending(m: "MessageParserProtocol") -> bool: 202 """保留チェック 203 204 Args: 205 m (MessageParserProtocol): メッセージデータ 206 207 Returns: 208 bool: 真偽 209 - *True*: 保留中 210 - *False*: チェック開始 211 """ 212 213 g.adapter = cast("ServiceAdapter", g.adapter) 214 215 now_ts = float(ExtDt().format("ts")) 216 217 if m.data.edited_ts == "undetermined": 218 check_ts = float(m.data.event_ts) + g.adapter.conf.search_wait 219 else: 220 check_ts = float(m.data.edited_ts) + g.adapter.conf.search_wait 221 222 if check_ts > now_ts: 223 return True 224 return False
保留チェック
Arguments:
- m (MessageParserProtocol): メッセージデータ
Returns:
bool: 真偽
- True: 保留中
- False: チェック開始