libs.commands.report.stats_list
libs/commands/report/results_list.py
1""" 2libs/commands/report/results_list.py 3""" 4 5from typing import TYPE_CHECKING 6 7import matplotlib.pyplot as plt 8 9import libs.global_value as g 10from libs.domain.datamodels import GameInfo 11from libs.functions import message 12from libs.functions.compose import text_item 13from libs.types import StyleOptions 14from libs.utils import formatter, graphutil, textutil 15 16if TYPE_CHECKING: 17 from pathlib import Path # noqa: F401 18 19 import pandas as pd 20 21 from integrations.protocols import MessageParserProtocol 22 from libs.types import MessageType 23 24 25def main(m: "MessageParserProtocol") -> None: 26 """ 27 成績一覧表を生成する 28 29 Args: 30 m (MessageParserProtocol): メッセージデータ 31 32 """ 33 # 検索動作を合わせる 34 g.params.guest_skip = g.params.guest_skip2 35 36 # --- データ取得 37 game_info = GameInfo() 38 df = g.params.read_data("REPORT_RESULTS_LIST").reset_index(drop=True) 39 df.index = df.index + 1 40 if df.empty: 41 m.set_headline(message.random_reply(m, "no_hits"), StyleOptions(title="成績一覧")) 42 m.status.result = False 43 return 44 45 if g.params.anonymous: 46 mapping_dict = formatter.anonymous_mapping(df["name"].unique().tolist()) 47 df["name"] = df["name"].replace(mapping_dict) 48 49 # 見出し設定 50 if g.params.individual: 51 title = "個人成績一覧" 52 df = df.rename(columns={"name": "player"}) 53 else: # チーム集計 54 title = "チーム成績一覧" 55 df = df.rename(columns={"name": "team"}) 56 57 if g.params.mode == 3: 58 df.drop(columns=["4th_count", "rank4_rate", "4th_mix"], inplace=True) 59 60 # 非表示項目 61 if g.params.ignore_flying or g.cfg.rule.dropitems(g.params.rule_version) & g.cfg.dropitems.flying: 62 df = df.drop(columns=["flying_mix", "flying_count", "flying_rate"]) 63 if g.cfg.rule.dropitems(g.params.rule_version) & g.cfg.dropitems.yakuman: 64 df = df.drop(columns=["yakuman_mix", "yakuman_count", "yakuman_rate"]) 65 66 file_path: "MessageType" 67 match g.params.format.lower(): 68 case "text" | "txt": 69 file_path = text_generation(df) 70 case "csv": 71 file_path = csv_generation(df) 72 case _: 73 file_path = graph_generation(game_info, df, title) 74 75 m.set_headline(message.header(game_info, m), StyleOptions(title=title)) 76 match g.adapter.interface_type: 77 case "slack" | "discord": 78 m.set_message(file_path, StyleOptions(title=title, use_comment=True, header_hidden=True)) 79 case "web": 80 m.set_message(df_generation(df), StyleOptions()) 81 case _: 82 df = df.filter( 83 items=["player", "team", "game", "total_mix", "avg_mix", "1st_mix", "2nd_mix", "2nd_mix", "3rd_mix", "4th_mix", "flying_mix", "yakuman_mix"] 84 ) 85 m.set_message(df, StyleOptions()) 86 87 88def graph_generation(game_info: GameInfo, df: "pd.DataFrame", title: str) -> "MessageType": 89 """ 90 グラフ生成処理 91 92 Args: 93 game_info (GameInfo): ゲーム情報 94 df (pd.DataFrame): 描写データ 95 title (str): グラフタイトル 96 97 Returns: 98 MessageType: 生成ファイルパス 99 100 """ 101 if g.adapter.conf.plotting_backend == "plotly": 102 return None 103 104 df = formatter.df_rename( 105 df.filter( 106 items=[ 107 "player", 108 "team", 109 "game", 110 "total_mix", 111 "avg_mix", 112 "rank_avg", 113 "1st_mix", 114 "2nd_mix", 115 "3rd_mix", 116 "4th_mix", 117 "rank_dist", 118 "flying_mix", 119 "yakuman_mix", 120 ] 121 ), 122 StyleOptions(), 123 ) 124 125 # フォント/色彩設定 126 graphutil.setup() 127 report_file_path = textutil.save_file_path("report.png") 128 plt.rcParams["font.size"] = 6 129 130 match (plt.rcParams["text.color"], plt.rcParams["figure.facecolor"]): 131 case text_color, bg_color if text_color == "black" and bg_color == "white": 132 line_color1 = "#dddddd" 133 line_color2 = "#ffffff" 134 case text_color, bg_color if text_color == "white" and bg_color == "black": 135 line_color1 = "#111111" 136 line_color2 = "#000000" 137 case _: 138 line_color1 = plt.rcParams["figure.facecolor"] 139 line_color2 = plt.rcParams["figure.facecolor"] 140 141 column_color = ["#000080"] * len(df.columns) 142 cell_color = [] 143 for x in range(len(df)): 144 if int(x % 2): 145 cell_color.append([line_color1] * len(df.columns)) 146 else: 147 cell_color.append([line_color2] * len(df.columns)) 148 149 fig = plt.figure(figsize=(8, (len(df) * 0.2) + 0.8), dpi=200, tight_layout=True) 150 ax_dummy = fig.add_subplot(111) 151 ax_dummy.axis("off") 152 153 plt.title(title, fontsize=12) 154 tb = plt.table( 155 cellText=df.values, 156 colLabels=df.columns, 157 colColours=column_color, 158 cellColours=cell_color, 159 loc="center", 160 ) 161 162 tb.auto_set_font_size(False) 163 tb.auto_set_column_width(range(len(df))) 164 for i in range(len(df.columns)): 165 tb[0, i].set_text_props(color="#FFFFFF", weight="bold") 166 for j in range(len(df) + 1): 167 tb[j, i].set_text_props(ha="center") 168 169 # 追加テキスト 170 remark_text = "".join(text_item.remarks(True)) + text_item.search_word(True) 171 add_text = "[検索範囲:{}] [総ゲーム数:{}] {}".format( 172 text_item.search_range(time_pattern="time"), 173 game_info.count, 174 f"[{remark_text}]" if remark_text else "", 175 ) 176 177 fig.text( 178 0.01, 179 0.01, # 表示位置(左下0,0 右下0,1) 180 add_text, 181 transform=fig.transFigure, 182 fontsize=6, 183 ) 184 185 fig.savefig(report_file_path) 186 return report_file_path 187 188 189def text_generation(df: "pd.DataFrame") -> "MessageType": 190 """ 191 テキストテーブル生成 192 193 Args: 194 df (pd.DataFrame): 描写データ 195 196 Returns: 197 MessageType: 生成ファイルパス 198 199 """ 200 report_file_path = g.cfg.setting.work_dir / (f"{g.params.filename}.txt" if g.params.filename else "report.txt") 201 202 df = df.filter( 203 items=[ 204 "player", 205 "team", 206 "game", 207 "point_sum", 208 "point_avg", 209 "1st_count", 210 "rank1_rate", 211 "2nd_count", 212 "rank2_rate", 213 "3rd_count", 214 "rank3_rate", 215 "4th_count", 216 "rank4_rate", 217 "rank_avg", 218 "flying_count", 219 "flying_rate", 220 "yakuman_count", 221 "yakuman_rate", 222 ] 223 ) 224 fmt = formatter.floatfmt_adjust(df, index=True) 225 df = formatter.df_rename(df, StyleOptions()) 226 df.to_markdown(report_file_path, tablefmt="outline", floatfmt=fmt) 227 228 return report_file_path 229 230 231def csv_generation(df: "pd.DataFrame") -> "MessageType": 232 """ 233 CSV生成 234 235 Args: 236 df (pd.DataFrame): 描写データ 237 238 Returns: 239 MessageType: 生成ファイルパス 240 241 """ 242 report_file_path = g.cfg.setting.work_dir / (f"{g.params.filename}.csv" if g.params.filename else "report.csv") 243 244 df = df.filter( 245 items=[ 246 "player", 247 "team", 248 "game", 249 "point_sum", 250 "point_avg", 251 "1st_count", 252 "rank1_rate", 253 "2nd_count", 254 "rank2_rate", 255 "3rd_count", 256 "rank3_rate", 257 "4th_count", 258 "rank4_rate", 259 "rank_avg", 260 "flying_count", 261 "flying_rate", 262 "yakuman_count", 263 "yakuman_rate", 264 ] 265 ) 266 267 for x in df.columns: 268 match x: 269 case "point_sum" | "point_avg": 270 df[x] = df[x].round(1) 271 case "rank1_rate" | "rank2_rate" | "rank3_rate" | "rank4_rate" | "flying_rate" | "yakuman_rate": 272 df[x] = df[x].round(2) 273 case "rank_avg": 274 df[x] = df[x].astype(float).round(2) 275 276 df.to_csv(report_file_path) 277 278 return report_file_path 279 280 281def df_generation(df: "pd.DataFrame") -> "MessageType": 282 """ 283 テキストテーブル生成 284 285 Args: 286 df (pd.DataFrame): 描写データ 287 288 Returns: 289 MessageType: 整形データ 290 291 """ 292 df = df.filter( 293 items=[ 294 "player", 295 "team", 296 "game", 297 "point_sum", 298 "point_avg", 299 "1st_count", 300 "rank1_rate", 301 "2nd_count", 302 "rank2_rate", 303 "3rd_count", 304 "rank3_rate", 305 "4th_count", 306 "rank4_rate", 307 "rank_avg", 308 "flying_count", 309 "flying_rate", 310 "yakuman_count", 311 "yakuman_rate", 312 ] 313 ) 314 315 return formatter.df_rename(df, StyleOptions())
26def main(m: "MessageParserProtocol") -> None: 27 """ 28 成績一覧表を生成する 29 30 Args: 31 m (MessageParserProtocol): メッセージデータ 32 33 """ 34 # 検索動作を合わせる 35 g.params.guest_skip = g.params.guest_skip2 36 37 # --- データ取得 38 game_info = GameInfo() 39 df = g.params.read_data("REPORT_RESULTS_LIST").reset_index(drop=True) 40 df.index = df.index + 1 41 if df.empty: 42 m.set_headline(message.random_reply(m, "no_hits"), StyleOptions(title="成績一覧")) 43 m.status.result = False 44 return 45 46 if g.params.anonymous: 47 mapping_dict = formatter.anonymous_mapping(df["name"].unique().tolist()) 48 df["name"] = df["name"].replace(mapping_dict) 49 50 # 見出し設定 51 if g.params.individual: 52 title = "個人成績一覧" 53 df = df.rename(columns={"name": "player"}) 54 else: # チーム集計 55 title = "チーム成績一覧" 56 df = df.rename(columns={"name": "team"}) 57 58 if g.params.mode == 3: 59 df.drop(columns=["4th_count", "rank4_rate", "4th_mix"], inplace=True) 60 61 # 非表示項目 62 if g.params.ignore_flying or g.cfg.rule.dropitems(g.params.rule_version) & g.cfg.dropitems.flying: 63 df = df.drop(columns=["flying_mix", "flying_count", "flying_rate"]) 64 if g.cfg.rule.dropitems(g.params.rule_version) & g.cfg.dropitems.yakuman: 65 df = df.drop(columns=["yakuman_mix", "yakuman_count", "yakuman_rate"]) 66 67 file_path: "MessageType" 68 match g.params.format.lower(): 69 case "text" | "txt": 70 file_path = text_generation(df) 71 case "csv": 72 file_path = csv_generation(df) 73 case _: 74 file_path = graph_generation(game_info, df, title) 75 76 m.set_headline(message.header(game_info, m), StyleOptions(title=title)) 77 match g.adapter.interface_type: 78 case "slack" | "discord": 79 m.set_message(file_path, StyleOptions(title=title, use_comment=True, header_hidden=True)) 80 case "web": 81 m.set_message(df_generation(df), StyleOptions()) 82 case _: 83 df = df.filter( 84 items=["player", "team", "game", "total_mix", "avg_mix", "1st_mix", "2nd_mix", "2nd_mix", "3rd_mix", "4th_mix", "flying_mix", "yakuman_mix"] 85 ) 86 m.set_message(df, StyleOptions())
成績一覧表を生成する
Arguments:
- m (MessageParserProtocol): メッセージデータ
def
graph_generation( game_info: libs.domain.datamodels.GameInfo, df: pandas.DataFrame, title: str) -> None | str | pathlib.Path | pandas.DataFrame:
89def graph_generation(game_info: GameInfo, df: "pd.DataFrame", title: str) -> "MessageType": 90 """ 91 グラフ生成処理 92 93 Args: 94 game_info (GameInfo): ゲーム情報 95 df (pd.DataFrame): 描写データ 96 title (str): グラフタイトル 97 98 Returns: 99 MessageType: 生成ファイルパス 100 101 """ 102 if g.adapter.conf.plotting_backend == "plotly": 103 return None 104 105 df = formatter.df_rename( 106 df.filter( 107 items=[ 108 "player", 109 "team", 110 "game", 111 "total_mix", 112 "avg_mix", 113 "rank_avg", 114 "1st_mix", 115 "2nd_mix", 116 "3rd_mix", 117 "4th_mix", 118 "rank_dist", 119 "flying_mix", 120 "yakuman_mix", 121 ] 122 ), 123 StyleOptions(), 124 ) 125 126 # フォント/色彩設定 127 graphutil.setup() 128 report_file_path = textutil.save_file_path("report.png") 129 plt.rcParams["font.size"] = 6 130 131 match (plt.rcParams["text.color"], plt.rcParams["figure.facecolor"]): 132 case text_color, bg_color if text_color == "black" and bg_color == "white": 133 line_color1 = "#dddddd" 134 line_color2 = "#ffffff" 135 case text_color, bg_color if text_color == "white" and bg_color == "black": 136 line_color1 = "#111111" 137 line_color2 = "#000000" 138 case _: 139 line_color1 = plt.rcParams["figure.facecolor"] 140 line_color2 = plt.rcParams["figure.facecolor"] 141 142 column_color = ["#000080"] * len(df.columns) 143 cell_color = [] 144 for x in range(len(df)): 145 if int(x % 2): 146 cell_color.append([line_color1] * len(df.columns)) 147 else: 148 cell_color.append([line_color2] * len(df.columns)) 149 150 fig = plt.figure(figsize=(8, (len(df) * 0.2) + 0.8), dpi=200, tight_layout=True) 151 ax_dummy = fig.add_subplot(111) 152 ax_dummy.axis("off") 153 154 plt.title(title, fontsize=12) 155 tb = plt.table( 156 cellText=df.values, 157 colLabels=df.columns, 158 colColours=column_color, 159 cellColours=cell_color, 160 loc="center", 161 ) 162 163 tb.auto_set_font_size(False) 164 tb.auto_set_column_width(range(len(df))) 165 for i in range(len(df.columns)): 166 tb[0, i].set_text_props(color="#FFFFFF", weight="bold") 167 for j in range(len(df) + 1): 168 tb[j, i].set_text_props(ha="center") 169 170 # 追加テキスト 171 remark_text = "".join(text_item.remarks(True)) + text_item.search_word(True) 172 add_text = "[検索範囲:{}] [総ゲーム数:{}] {}".format( 173 text_item.search_range(time_pattern="time"), 174 game_info.count, 175 f"[{remark_text}]" if remark_text else "", 176 ) 177 178 fig.text( 179 0.01, 180 0.01, # 表示位置(左下0,0 右下0,1) 181 add_text, 182 transform=fig.transFigure, 183 fontsize=6, 184 ) 185 186 fig.savefig(report_file_path) 187 return report_file_path
グラフ生成処理
Arguments:
- game_info (GameInfo): ゲーム情報
- df (pd.DataFrame): 描写データ
- title (str): グラフタイトル
Returns:
MessageType: 生成ファイルパス
def
text_generation(df: pandas.DataFrame) -> None | str | pathlib.Path | pandas.DataFrame:
190def text_generation(df: "pd.DataFrame") -> "MessageType": 191 """ 192 テキストテーブル生成 193 194 Args: 195 df (pd.DataFrame): 描写データ 196 197 Returns: 198 MessageType: 生成ファイルパス 199 200 """ 201 report_file_path = g.cfg.setting.work_dir / (f"{g.params.filename}.txt" if g.params.filename else "report.txt") 202 203 df = df.filter( 204 items=[ 205 "player", 206 "team", 207 "game", 208 "point_sum", 209 "point_avg", 210 "1st_count", 211 "rank1_rate", 212 "2nd_count", 213 "rank2_rate", 214 "3rd_count", 215 "rank3_rate", 216 "4th_count", 217 "rank4_rate", 218 "rank_avg", 219 "flying_count", 220 "flying_rate", 221 "yakuman_count", 222 "yakuman_rate", 223 ] 224 ) 225 fmt = formatter.floatfmt_adjust(df, index=True) 226 df = formatter.df_rename(df, StyleOptions()) 227 df.to_markdown(report_file_path, tablefmt="outline", floatfmt=fmt) 228 229 return report_file_path
テキストテーブル生成
Arguments:
- df (pd.DataFrame): 描写データ
Returns:
MessageType: 生成ファイルパス
def
csv_generation(df: pandas.DataFrame) -> None | str | pathlib.Path | pandas.DataFrame:
232def csv_generation(df: "pd.DataFrame") -> "MessageType": 233 """ 234 CSV生成 235 236 Args: 237 df (pd.DataFrame): 描写データ 238 239 Returns: 240 MessageType: 生成ファイルパス 241 242 """ 243 report_file_path = g.cfg.setting.work_dir / (f"{g.params.filename}.csv" if g.params.filename else "report.csv") 244 245 df = df.filter( 246 items=[ 247 "player", 248 "team", 249 "game", 250 "point_sum", 251 "point_avg", 252 "1st_count", 253 "rank1_rate", 254 "2nd_count", 255 "rank2_rate", 256 "3rd_count", 257 "rank3_rate", 258 "4th_count", 259 "rank4_rate", 260 "rank_avg", 261 "flying_count", 262 "flying_rate", 263 "yakuman_count", 264 "yakuman_rate", 265 ] 266 ) 267 268 for x in df.columns: 269 match x: 270 case "point_sum" | "point_avg": 271 df[x] = df[x].round(1) 272 case "rank1_rate" | "rank2_rate" | "rank3_rate" | "rank4_rate" | "flying_rate" | "yakuman_rate": 273 df[x] = df[x].round(2) 274 case "rank_avg": 275 df[x] = df[x].astype(float).round(2) 276 277 df.to_csv(report_file_path) 278 279 return report_file_path
CSV生成
Arguments:
- df (pd.DataFrame): 描写データ
Returns:
MessageType: 生成ファイルパス
def
df_generation(df: pandas.DataFrame) -> None | str | pathlib.Path | pandas.DataFrame:
282def df_generation(df: "pd.DataFrame") -> "MessageType": 283 """ 284 テキストテーブル生成 285 286 Args: 287 df (pd.DataFrame): 描写データ 288 289 Returns: 290 MessageType: 整形データ 291 292 """ 293 df = df.filter( 294 items=[ 295 "player", 296 "team", 297 "game", 298 "point_sum", 299 "point_avg", 300 "1st_count", 301 "rank1_rate", 302 "2nd_count", 303 "rank2_rate", 304 "3rd_count", 305 "rank3_rate", 306 "4th_count", 307 "rank4_rate", 308 "rank_avg", 309 "flying_count", 310 "flying_rate", 311 "yakuman_count", 312 "yakuman_rate", 313 ] 314 ) 315 316 return formatter.df_rename(df, StyleOptions())
テキストテーブル生成
Arguments:
- df (pd.DataFrame): 描写データ
Returns:
MessageType: 整形データ