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
def main(m: integrations.protocols.MessageParserProtocol) -> None:
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): メッセージデータ
def check_omission(results: libs.datamodels.ComparisonResults):
 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): 結果格納データクラス
def check_remarks(results: libs.datamodels.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): 結果格納データクラス
def check_total_score(results: libs.datamodels.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): 結果格納データクラス
def check_pending(m: integrations.protocols.MessageParserProtocol) -> bool:
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: チェック開始