From c50c97bc82c2c024cfd340b3090246d20a870c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9=20=D0=A7=D0=B5=D1=80=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE?= Date: Thu, 6 Nov 2025 20:49:24 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82=20=D0=B2?= =?UTF-8?q?=D0=B5=D1=87=D0=B5=D1=80=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/get_data.cpython-312.pyc | Bin 15952 -> 35507 bytes __pycache__/smb_excel.cpython-312.pyc | Bin 0 -> 4960 bytes get_data.py | 467 +++++++++++++++++++++++++- smb_excel.py | 154 +++++++++ 4 files changed, 609 insertions(+), 12 deletions(-) create mode 100644 __pycache__/smb_excel.cpython-312.pyc create mode 100644 smb_excel.py diff --git a/__pycache__/get_data.cpython-312.pyc b/__pycache__/get_data.cpython-312.pyc index a015ccd935089c9d03bee94bb8ee1eda513ae675..6d4df545e5241ade3224bdc6a161668e00aa63d7 100644 GIT binary patch literal 35507 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 G|9=4iI!-wN delta 3872 zcmZ`*eQXrh5#P7F_px`kzAxM7vkg8QY{8e z+D^+#J3H?;^XARGH#2Xm-xBXurr%dpp8${hoIm%<7D4y~2aU&5XCB>_1mU`HLeK<_ z7(#)h2ti&n#DbKP3UW#=xKb{TNrpS+0hwfYQ(pd6QVRe2Qa<>~hQAO<1-P!um{to- zsU|*f8^J;_6|5Txr9zXJpZt_ zeV&fahFn{5S#V~rM_r7xW2GbSS|Z;7@+```6OJ7G1s}SX_^{Ej(&K2YT4H6BBVX;v zdzZ*JH{|S=)au&ejD9P%hPUe5w1Knzf@Cj#`=cit%i8)j#R7rS5FTBu-uA8$TPv2k> zk3ICqTa?$tt1YrHJBWwfP`bK2S{O0Swcwr?fq|z280KO{$`7KZ z>W7(iz5H5sD-b0E>|Afu@5kL(v?v8C0roJ^!ag91m|xGrYP4wsw+Mu9H+v({MmFKT zbw#!2g_g#OS}R(aQY!)58+#RYL~Uj-i%MXssnVpiT|}2uMkZT)@y6j@2a2)44> zP*if$ZgxGiPIYCdUd+%{;2>T7&9Ff(vO>5&lCZo~KR2$MWz&*I^fL9Z?}ZN%h5bFe zZh!_s+wx>f#(1G14l%T$ICLkv$#V>q`TZfY%JLm!ki2Kj=)}mE-Kq=40aXr22XDuZc>c zU)nCe*CGR@&$8dN9w0p|9BW_uEX<;(aVn-*B0;|nA1jzXGoCZF^a!k+1~;e%s9ybQ zY>sSh1D4{RTAp&QpqIwWmP9iXNgr3D8&SLo36>4rjARRo#`_OG32f3uofRz!Eq@tM zOV%>wjEO~>z{hqXlpfa0*-_hxQZcL35-8-W--!QHj4fL6aV=d-XUeq5UhYh6Zvr*G z+kAo*NE?|loJra!WzSLtg?QMOk~2%gC0c-aAq2zh&z(D;D58kyrVQUjU&6K*VqmGtf6Np#oLx^&v!8XpOUBtu&smaWf9r{##IvP)kl_BnrkB!rvs9$$U{a*V5mXUL zo7qua8#nZ2N9n>dH0kT0wg9hr7|36Rf6oYiQaah0RcCw{`z3fEy~93SHAbe{Q>%X> zk-Umkxy@UVq01?p`YImaeZ1rODsIkuf%nV#kC4Z13x<`FY;b?}8{+^=>{#w!aB1qzxX6?L!th9SJY}F!Zgo z<0>W(eG7!^*_|yPlWXj?t=pa{pz>)XScOS>Ni5tfHVgU%vtFl_d>^EntNv}LNf>Tm zzgf1a!@-c_ zm}2V{#|xH=v!7!3q5~|k>%=r3j^#4Pj9fV>Sqg7Hco>!+rt4JKO*E?_lx`r))6-Lj z>94@lAuw(J2#DR6LW!HL_d@+MhaY&uulC*Z#$O+}=k5L7;-Gig=nI3I;)=Rwj@Cle zySoZRO6>1pzr1u@P42Kf!*IuD#&T3*>o3Q8uz%WRz`KbXnz5EK=~jgKCAOVSUye=V zEoQm3Qduvar(P6dm0F(h^SNw^7A>VbO7)DED~{Mr-;XK*B&$)+mn{{``h~KQJ401O zdEM_tQ81m(6pJOu|6HkPrqlM7#rIa$a?p7&^QY+noW*MbFBlrZsctAz^vAwk5#EiY zf`oTTUV?aC(itRp$fcG^^9xK5Q2et=Z zRVd+Ivxoj3reJQE`{56wCJ{pZAar~z?7JuIV`Ep~e+l0Au3TC35AQxg+G|Hdi5w+2 zH`N6E{xE8Pe;R$n$2A}Oh5jD2@YxhL~gwWpKl2gmz8j=#O0) z$IO~>(sZz!6o>vy+yUCmv=2}-(?FAA2nn6!g-%~qH8U&=ou(~);BAThz=VhPoL$Lr zT&K`Gy7zv&_uPB#-Fwb=j{Y17_z0Ar{z=OJBTC3W@u5)?bufQ~5^|czM5YB&qyv;9 zW(rJ^9bk*x09W)3c#8Z0@4ROV-l8xdIG8Jl13r-D3jP5B+aO@l1%8mqio z;o+TUka3;_#W&RHev75;V2j)iI-5ti^qS9bAWbdruFEm)pM zO8_jkr!;DDyYsm!2nPE1?dmO;N=goe`f^JbhMRQCyae58qL2Y9lL2OwrWq>?^LCd< zN`-P(zE4%lYG0bQgiNNCEh?Fe<;!G>WqG)OSjuFc9?llrk=MvPu6ezscl*KKvaB53 z`@lm7wc?W*lg#AL*fb& ztjsl58uG6&&MjS`pQJs*Rgf}%ZMSi9+R@rEdyD z$yKi~K5B--6T+v0Z%Ue!jCkj(J-^%Y+MbyQ=Y(zJJs)`mQwmJ&ncQ=#=frm>=v6_S z5+}t|>!;Z{q3uIqT}@bbrgu*081FIt()a_8=~#YMDVD2>W1*I)De5zdnt{y=!+_B^ zEuoUtw2`tZTd`aruav5pVm5auU#idH<#I8bFIilrtX6Xz>=g@I9NxHNc`AU@ODs-j zbXMndqI+a2)7P1qy7i2CMWTC$aL%%#Q=M;EipuETepq~ztRvNiKC+&uEG3V_+Yxev zeVmNYG~4$`npM}sQkEZ1lPOgT#mqCCtw3JO$axifJ5$Y7Km=XN^5$HBx5WFv=NxiK z3+fseQd8L6hRs@R)?tIos^5Ud;&7sdYozMMy-+DrRYx=RRn?6!z6z>#5Snqa&_tw! zDTJM0t0~0HP~2>4F`JVMAwlBDciU0U6EOXusU4F$ro}T`Kk#>$u~id$ZI(!BTOd;S ziv?=uP6wIY&=RT!6mMNR^HfSz+eEEj4o0KovE)K0>{6tGSTB16ie+lPza zTesfT1tf$P=|q&YsCFYxVRI`pcR$Mp5Yf=D>z3XiFR5YY832|YmfGSG<+G4m|z=WW7zc$d+9+M%pkC#UFO9)c~> zX(nIFI0HG@lT+{*ixhPoJmV&cVjH$N#>#X|6qi^n`T@`vSUK3du!O!unO!Mp>gf%a)6a38AphHTki=lI-ezY+^%nQ^XmUNDP`Wu-e zCAxnI^M_?c<{NuK30+j9K+EC~2CZeKp%4AKAL$tKU$->qxPOt3myIUJq-wk&Eqisw z;Q;5G0?G*HvWJt0wUo2$6@qs@ID};S>r9o!=3cvom4%~bUCFDNabrd zSTpS4vwuTTy8x+}8w_K-F@A{|2&6oaxgd8zhunhvhdB+f%d^b(RNC((436(v+)x=J zvPY{F@>NSHpl?fF$MpW zFe#jhob;Qa=;sn}lFhJzxEXFb?R(MpbIFGI3sIOSO{*aa2jU-vn`{>PFBc+CU}im$ zPlD}c@Q$;|v(jvjanF8Z=K9Kj13Hh#u!nTJIBVXDqO!)G1-+$@( z;*(h0O$x5tUJ$yj4Zvrc>e;cYn_LctccqxiDRI|E&*cpi+itNp%3i)Z-Wy=A1U!JR zMA_arxe{l4x3gCg;oeT}N}3|>6nnRMuWVru-$fB`;{d0*<%t3?tD;n$R8j2#-Bv_Z zvaCR}uHdjJok1>!GUmUL9}IEsyQQt}AEb~s zhgs!2Gw%WiIsa`=258Heb9jUw~=tW*|f%twwkfkX6q(10)a7Mwsm8I7>F#y zd|`h4frVxw1*djS?woF^`TJfSGW>nUj(q?PuFr{r=~Wh!FS{PkS4S)P(o-saOcnps zsXR8AbXyVd!>*hD?7G=7ICDwPYU*y3#Ju$eIanCcRweGbYqFT zv(@bVDrA%@o=^>9~2r@W&9dPf8Fbq$DI4TxL~ zh+GYbTn&goLu@{VX#bMM<6Sg<6}Inz9u^J9Y%d~#z&jCekYxLG?#!0yL%(RZ6OMF}@WS&4 Nj~-lT#Ye|r{sSS&4HN(X literal 0 HcmV?d00001 diff --git a/get_data.py b/get_data.py index 0b94c1b..4cc8e63 100644 --- a/get_data.py +++ b/get_data.py @@ -1,5 +1,6 @@ -from fastapi import FastAPI +from fastapi import FastAPI, HTTPException, Query from fastapi.responses import Response, JSONResponse, HTMLResponse +from fastapi.encoders import jsonable_encoder import pandas as pd import requests, io, os from requests.auth import HTTPBasicAuth @@ -9,11 +10,14 @@ import uvicorn from threading import Thread, Event, Lock import time from contextlib import asynccontextmanager +from smb_excel import read_excel_from_smb, fetch_smb_file +import numpy as np # --- Глобальные переменные --- selected_game_id: int | None = None current_tournament_id: int | None = None # будем обновлять при загрузке расписания +current_season: str | None = None # будем обновлять при загрузке расписания latest_game_data: dict | None = None # сюда кладём последние данные по матчу latest_game_error: str | None = None _latest_lock = Lock() @@ -26,6 +30,13 @@ api_user = os.getenv("API_USER") api_pass = os.getenv("API_PASS") league = os.getenv("LEAGUE") POLL_SEC = int(os.getenv("GAME_POLL_SECONDS")) +SERVER_NAME = os.getenv("SERVER_NAME") +SERVER_IP = os.getenv("SERVER_IP") +SHARE = os.getenv("SHARE") +PATH_IN_SHARE = os.getenv("PATH_IN_SHARE") +USER = os.getenv("USER") +PASSWORD = os.getenv("PASSWORD") +DOMAIN = os.getenv("DOMAIN") @@ -51,14 +62,16 @@ def load_today_schedule(): return pd.DataFrame() tournament_id = int(filtered.iloc[0]["id"]) - global current_tournament_id + season = str(filtered.iloc[0]["season"]) + global current_tournament_id, current_season current_tournament_id = tournament_id + current_season = season url_schedule = f"http://stat2tv.khl.ru/{tournament_id}/schedule-{tournament_id}.xml" r = requests.get(url_schedule, auth=HTTPBasicAuth(api_user, api_pass), verify=False) schedule_df = pd.read_xml(io.StringIO(r.text)) # Нужные колонки (скорректируй под реальные имена из XML) - needed_columns = ["id", "date", "time", "homeName_en", "visitorName_en", "arena"] + needed_columns = ["id", "date", "time", "homeName_en", "visitorName_en", "arena_en", "arena_city_en", "homeCity_en", "visitorCity_en"] exist = [c for c in needed_columns if c in schedule_df.columns] schedule_df = schedule_df[exist].copy() @@ -103,6 +116,7 @@ def _build_game_url(tournament_id: int, game_id: int) -> str: # Если у тебя другой шаблон — просто поменяй строку ниже. return f"http://stat2tv.khl.ru/{tournament_id}/json_en/{game_id}.json" + def _fetch_game_once(tournament_id: int, game_id: int) -> dict: """Один запрос к API матча -> чистый JSON из API.""" url = _build_game_url(tournament_id, game_id) @@ -176,6 +190,7 @@ app = FastAPI( @app.get("/games") async def games(): + df = load_today_schedule() if df.empty: return JSONResponse({"message": "Сегодня матчей нет"}) @@ -184,6 +199,8 @@ async def games(): return Response(content=json_schedule, media_type="application/json") + + @app.get("/select") async def select(): df = load_today_schedule() @@ -199,7 +216,8 @@ async def select(): home = row.get("homeName_en", "") away = row.get("visitorName_en", "") when = row.get("datetime_str", "") - arena = row.get("arena", "") + arena = row.get("arena_en", "") + city = row.get("arena_city_en", "") rows_html.append( f""" @@ -208,6 +226,7 @@ async def select(): {home} {away} {arena} + {city} """ @@ -238,7 +257,7 @@ async def select(): - + @@ -319,6 +338,13 @@ async def game_url(): "game_id": selected_game_id, "tournament_id": current_tournament_id }) + + +# @app.get("/info") +# async def info(): +# if selected_game_id: +# df = load_today_schedule() + @app.get("/data") async def game_data(): @@ -333,12 +359,6 @@ async def game_data(): @app.get("/referee") async def referee(): json_data = latest_game_data["data"] - referees_id = [ - json_data["game"]["mref1_id"], - json_data["game"]["mref2_id"], - json_data["game"]["lref1_id"], - json_data["game"]["lref2_id"], - ] data_referees = [ { "number": json_data["game"]["mref1_num"], @@ -361,9 +381,432 @@ async def referee(): -# def team(who:str): +async def team(who:str): + """"who: A - домашняя команда, B - гостевая""" + with _latest_lock: + lgd = latest_game_data + # print(lgd) + if not lgd or "data" not in lgd: + return [{"details":"Нет данных по матчу!"}] + + players1_temp = lgd["data"]["players"][who] + players1 = [] + players1_f = [] + players1_d = [] + players1_g = [] + goaltenders1 = [] + for player in players1_temp: + players1_temp[player]["mask"] = "#FF0000" + # for s in seasonA_json: + # if int(players1_temp[player]["id"]) == int(s["id"]): + # for key, value in s.items(): + # # Создаем новый ключ с префиксом + # new_key = f"season_{key}" + # players1_temp[player][new_key] = value + if players1_temp[player]["ps"] not in ["в", "g"]: + data1 = players1_temp[player] + if "." in data1["name"]: + lastname1, *names1 = data1["name"].split() + names_new = " ".join(names1) + elif len(data1["name"].split()) == 3: + *lastname1, names1 = data1["name"].split() + names_new = names1 + lastname1 = " ".join(lastname1) + else: + lastname1, *names1 = data1["name"].split() + names_new = " ".join(names1) + if players1_temp[player]["ps"] == "н": + position = "нападающий" + elif players1_temp[player]["ps"] == "з": + position = "защитник" + elif players1_temp[player]["ps"] == "f": + position = "forward" + elif players1_temp[player]["ps"] == "d": + position = "defenseman" + + data_with_number = { + "number": player, + "NameSurnameGFX": names_new + " " + lastname1, + "NameGFX": names_new, + "SurnameGFX": lastname1, + "PositionGFX": position, + **data1, + } + if players1_temp[player]["ps"] == "н": + players1_f.append(data_with_number) + elif players1_temp[player]["ps"] == "з": + players1_d.append(data_with_number) + elif players1_temp[player]["ps"] == "f": + players1_f.append(data_with_number) + elif players1_temp[player]["ps"] == "d": + players1_d.append(data_with_number) + else: + data2 = players1_temp[player] + position = "" + if players1_temp[player]["ps"] == "в": + position = "вратарь" + elif players1_temp[player]["ps"] == "g": + position = "Goaltender" + lastname1, *names1 = data2["name"].split() + names_new = " ".join(names1) + data_with_number2 = { + "number": player, + "NameSurnameGFX": names_new + " " + lastname1, + "NameGFX": names_new, + "SurnameGFX": lastname1, + "PositionGFX": position, + **data2, + } + players1_g.append(data_with_number2) + goaltenders1.append(data_with_number2) + + def make_empty(example_list): + if not example_list: + return {} + return {key: "" for key in example_list[0].keys()} + + empty_d = make_empty(players1_d) + empty_f = make_empty(players1_f) + empty_g = make_empty(players1_g) + + # добивка пустыми слотами + while len(players1_d) < 9: + players1_d.append(empty_d.copy()) + while len(players1_f) < 13: + players1_f.append(empty_f.copy()) + while len(players1_g) < 3: + players1_g.append(empty_g.copy()) + + players1 = players1_d + players1_f + players1_g + # print(len(players1)) + return players1 +@app.get("/team1") +async def team1(): + return await team("A") + +@app.get("/team2") +async def team2(): + return await team("B") + +# 👉 метка для первой строки (period row) +def _period_label(period: str | int) -> str: + s = str(period).strip().upper() + + # овертаймы: OT, OT1, OT2... или числовые >= 4 + if s == "OT" or (s.startswith("OT") and s[2:].isdigit()): + return "AFTER OVERTIME" + if s.isdigit(): + n = int(s) + if n >= 4: + return "AFTER OVERTIME" + # 1–3 — с порядковым суффиксом + if n % 100 in (11, 12, 13): + suffix = "TH" + else: + suffix = {1: "ST", 2: "ND", 3: "RD"}.get(n % 10, "TH") + return f"AFTER {n}{suffix} PERIOD" + + # например, Total + if s == "TOTAL": + return "FINAL" + return "" + +# 👉 сортировка периодов: 1,2,3, затем OT/OT2/... +def _period_sort_key(k: str) -> tuple[int, int]: + s = str(k).strip().upper() + + # 1–3 — обычные периоды + if s.isdigit(): + n = int(s) + if 1 <= n <= 3: + return (n, 0) + # 4-й и далее — трактуем как овертаймы (4 -> OT1, 5 -> OT2 ...) + return (100, n - 3) + + # явные овертаймы: OT, OT1, OT2... + if s == "OT": + return (100, 1) + if s.startswith("OT") and s[2:].isdigit(): + return (100, int(s[2:])) + + # неизвестные — в конец + return (200, 0) + + +def _sorted_period_keys(teams_periods: dict) -> list[str]: + a = teams_periods.get("A", {}) + b = teams_periods.get("B", {}) + keys = [k for k in a.keys() if k in b] + return sorted(keys, key=_period_sort_key) + + +def _current_period_key(payload: dict) -> str | None: + keys = _sorted_period_keys(payload.get("teams_periods", {})) + return keys[-1] if keys else None + + +# >>> 1) Замените вашу async-функцию get_team_stat на обычную sync: +def format_team_stat(team1: dict, team2: dict, period: str | None = None) -> list[dict]: + """Форматирует статы двух команд в список записей для GFX (общие ключи + подписи).""" + stat_list = [ + ("coach_id", "coach id", "ID тренеров"), + ("coach_fullname", "coach fullname", "Тренеры"), + ("name", "name", "Команды"), + ("outs", "Outs", ""), + ("pim", "Penalty Minutes", "Минуты штрафа"), + ("shots", "Shots on goal", "Броски в створ"), + ("goals", "Goals", "Голы"), + ("fo", "Face-offs", "Вбрасывания всего"), + ("fow", "Face-offs won", "Вбрасывания"), + ("fow_pct", "Face-offs won, %", "Выигранные вбрасывания, %"), + ("hits", "Hits", "Силовые приемы"), + ("bls", "Blocked shots", "Блокированные броски"), + ("tka", "Takeaways", "Отборы"), + ("toa", "Time on attack", "Время в атаке"), + ("tie", "Even Strength Time on Ice", "Время в равных составах"), + ("tipp", "Powerplay Time On Ice", "Время при большинстве"), + ("tish", "Shorthanded Time On Ice", "Время при меньшинстве"), + ("tien", "Emptry Net Time On Ice", "Время с пустыми воротами"), + ("gva", "Giveaways", "Потери шайбы"), + ("p_intc", "Pass interceptions", "Перехваты передачи"), + ] + + teams = [{k: str(v) for k, v in t.items()} for t in [team1, team2]] + keys = list(teams[0].keys()) + + formatted = [] + + if period is not None: + formatted.append({ + "name0": "period", + "name1": str(period), + "name2": "", + "StatParameterGFX": _period_label(period) or "Period" + }) + + for key in keys: + row = {"name0": key, "name1": teams[0].get(key, ""), "name2": teams[1].get(key, "")} + # подписи + for code, eng, _ru in stat_list: + if key == code: + row["StatParameterGFX"] = eng + break + formatted.append(row) + + # постобработка отдельных полей + for r in formatted: + if r["name0"] == "fow_pct": + r["name1"] = str(round(float(r["name1"]))) if r["name1"] else r["name1"] + r["name2"] = str(round(float(r["name2"]))) if r["name2"] else r["name2"] + if r["name0"] == "coach_fullname": + # "Фамилия Имя" -> "Имя Фамилия" если есть пробел + def flip(s: str) -> str: + parts = s.split() + return f"{parts[1]} {parts[0]}" if len(parts) >= 2 else s + r["name1"] = flip(r["name1"]) + r["name2"] = flip(r["name2"]) + + return formatted + + +# >>> 2) Вспомогалки для обхода периодов +def _iter_period_pairs(teams_periods: dict): + """ + Итерируем по периодам, отдаём (period_key, A_stats, B_stats). + Ключи сортируем по числу, если это цифры. + """ + a = teams_periods.get("A", {}) + b = teams_periods.get("B", {}) + def _key(k): + try: + return int(k) + except (TypeError, ValueError): + return k + for k in sorted(a.keys(), key=_key): + if k in b: + yield k, a[k], b[k] + + +def _build_all_stats(payload: dict) -> dict: + """ + Собирает общий блок ('total') и список по периодам ('periods') из json матча. + """ + total_a = payload["teams"]["A"] + total_b = payload["teams"]["B"] + result = { + "total": format_team_stat(total_a, total_b), + "periods": [] + } + for period_key, a_stat, b_stat in _iter_period_pairs(payload["teams_periods"]): + result["periods"].append({ + "period": period_key, + "stats": format_team_stat(a_stat, b_stat) + }) + return result + +def xl(): + df = read_excel_from_smb( + server_name=SERVER_NAME, + server_ip=SERVER_IP, + share_name=SHARE, + file_path_in_share=PATH_IN_SHARE, + username=USER, + password=PASSWORD, + domain=DOMAIN, + client_machine_name="KHL_SOFT", + sheet_name="TEAMS", # или None, или список листов + ) + df = df.replace([float("inf"), float("-inf")], pd.NA) + columns_to_keep = [ + "Team", "Logo", "Short", "HexPodl", # ← укажи свои + ] + df = df.loc[:, [c for c in columns_to_keep if c in df.columns]] + json_text = df.to_json(orient="records", force_ascii=False) # NaN/NA -> null + return Response(content=json_text, media_type="application/json; charset=utf-8") + +@app.get("/teams/stats") +async def teams_stats( + scope: str = Query("all", pattern="^(all|total|period)$"), + n: str | None = Query(None, description="Номер периода (строка или число) при scope=period"), +): + """ + Все-в-одном: GET /teams/stats?scope=all + вернёт { total: [...], periods: [{period: "1", stats:[...]}, ...] } + Только общий: GET /teams/stats?scope=total + Конкретный период: GET /teams/stats?scope=period&n=2 + """ + # читаем атомарно снапшот + with _latest_lock: + lgd = latest_game_data + + if not lgd or "data" not in lgd or not isinstance(lgd["data"], dict): + raise HTTPException(status_code=400, detail="Нет данных по матчу.") + + payload = lgd["data"] + + if scope == "total": + data = format_team_stat(payload["teams"]["A"], payload["teams"]["B"], period="Total") + return JSONResponse({"scope": "total", "data": data}) + + if scope == "period": + # 👉 если n не задан/или просит актуальный — берём последний период + wants_current = (n is None) or (str(n).strip().lower() in {"current", "last"}) + if wants_current: + key = _current_period_key(payload) + if key is None: + raise HTTPException(status_code=404, detail="Периоды не найдены.") + n = key # используем актуальный ключ периода + + # ключи в исходном json строковые + period_key = str(n) + a = payload["teams_periods"]["A"].get(period_key) + b = payload["teams_periods"]["B"].get(period_key) + if a is None or b is None: + raise HTTPException(status_code=404, detail=f"Период {period_key} не найден.") + return JSONResponse({ + "scope": "period", + "period": period_key, + "is_current": period_key == _current_period_key(payload), + "data": format_team_stat(a, b, period=period_key) + }) + + # scope == "all" + cur = _current_period_key(payload) + return JSONResponse({ + "scope": "all", + "current_period": cur, + "data": _build_all_stats(payload) + }) + + +def _norm_name(s: str | None) -> str: + """Нормализует название команды для сравнения.""" + if not s: + return "" + return str(s).strip().casefold() + +@app.get("/info") +async def info(): + # 1) Проверяем, выбран ли матч + global current_season + if not selected_game_id: + return JSONResponse({"message": "Матч не выбран", "selected_id": None}) + + # 2) Берём расписание и ищем строку по выбранному ID + df = load_today_schedule() + if df.empty: + return JSONResponse({"message": "Сегодня матчей нет", "selected_id": selected_game_id}) + + # безопасно приводим id к int и ищем + try: + row = df.loc[df["id"].astype(int) == int(selected_game_id)].iloc[0] + except Exception: + return JSONResponse({"message": "Выбранный матч не найден в расписании на сегодня", + "selected_id": selected_game_id}, status_code=404) + + home_name = str(row.get("homeName_en", "")).strip() + away_name = str(row.get("visitorName_en", "")).strip() + + # 3) Подтягиваем справочник команд из Excel (лист TEAMS) + teams_df = read_excel_from_smb( + server_name=SERVER_NAME, + server_ip=SERVER_IP, + share_name=SHARE, + file_path_in_share=PATH_IN_SHARE, + username=USER, + password=PASSWORD, + domain=DOMAIN, + client_machine_name="KHL_SOFT", + sheet_name="TEAMS", + ) + + # Оставляем только полезные поля (подгони под свой файл) + keep = [ "Team", "Logo", "Short", "HexPodl", "HexBase", "HexText" ] + keep = [c for c in keep if c in teams_df.columns] + teams_df = teams_df.loc[:, keep].copy() + + # 4) Нормализованные ключи для джоина по имени + teams_df["__key"] = teams_df["Team"].apply(_norm_name) + + def _pick_team_info(name: str) -> dict: + key = _norm_name(name) + hit = teams_df.loc[teams_df["__key"] == key] + if hit.empty: + # не нашли точное совпадение — вернём только название + return {"Team": name} + rec = hit.iloc[0].to_dict() + rec.pop("__key", None) + # заменим NaN/inf на None, чтобы JSON не падал + for k, v in list(rec.items()): + if pd.isna(v) or v in (np.inf, -np.inf): + rec[k] = None + return rec + + home_info = _pick_team_info(home_name) + away_info = _pick_team_info(away_name) + date_obj = datetime.strptime(row.get("datetime_str", ""), "%d.%m.%Y %H:%M") + try: + full_format = date_obj.strftime("%B %-d, %Y") + except ValueError: + full_format = date_obj.strftime("%B %#d, %Y") + + + payload = [{ + "selected_id": int(selected_game_id), + "tournament_id": int(current_tournament_id) if current_tournament_id else None, + "datetime": str(full_format), + "arena": str(row.get("arena_en", "")), + "arena_city": str(row.get("arena_city_en", "")), + "home": home_info, + "home_city": str(row.get("homeCity_en", "")), + "away": away_info, + "away_city": str(row.get("visitorCity_en", "")), + "season": current_season, + }] + + return JSONResponse(content=payload) if __name__ == "__main__": uvicorn.run( diff --git a/smb_excel.py b/smb_excel.py new file mode 100644 index 0000000..c28478c --- /dev/null +++ b/smb_excel.py @@ -0,0 +1,154 @@ +# smb_excel.py (обновлённый фрагмент) +from io import BytesIO +from typing import Optional, Union, Any, Dict +from smb.SMBConnection import SMBConnection +import pandas as pd +import re + +class SMBDownloadError(Exception): + pass + +def _normalize_path(p: str) -> str: + """Убирает случайные r"..."/"...", лишние кавычки и ставит прямые слэши.""" + if p is None: + return p + # уберём обрамляющие кавычки/префикс r"..." + m = re.fullmatch(r"""r?["'](.*)["']""", p.strip()) + if m: + p = m.group(1) + # заменим \ на / + p = p.replace("\\", "/").lstrip("/") # путь внутри шары не должен начинаться с / + return p + +def _try_connect( + remote_name: str, + server_ip: str, + username: str, + password: str, + *, + client_machine_name: str, + domain: str, + port: int +) -> Optional[SMBConnection]: + conn = SMBConnection( + username, + password, + client_machine_name, + remote_name, # ВАЖНО: remote_name — имя сервера (или IP как fallback) + domain=domain, + use_ntlm_v2=True, + is_direct_tcp=True + ) + try: + if conn.connect(server_ip, port): + return conn + except Exception: + pass + return None + +def _connect_smb( + server_name: str, + server_ip: str, + username: str, + password: str, + *, + client_machine_name: str = "client", + domain: str = "", + port: int = 445, +) -> SMBConnection: + """ + Пробуем два варианта remote_name: (1) реальное имя сервера, (2) IP. + """ + for remote_name in (server_name, server_ip): + if not remote_name: + continue + conn = _try_connect( + remote_name=remote_name, + server_ip=server_ip, + username=username, + password=password, + client_machine_name=client_machine_name, + domain=domain, + port=port, + ) + if conn: + return conn + raise SMBDownloadError( + f"Не удалось подключиться к SMB {server_ip}:{port}. " + f"Проверь server_name (реальное имя хоста), домен/логин и доступность порта 445." + ) + +def fetch_smb_file( + *, + server_name: str, + server_ip: str, + share_name: str, + file_path_in_share: str, + username: str, + password: str, + client_machine_name: str = "client", + domain: str = "", + port: int = 445, +) -> BytesIO: + """ + Возвращает содержимое файла из SMB как BytesIO. + """ + file_path_in_share = _normalize_path(file_path_in_share) + conn: Optional[SMBConnection] = None + try: + conn = _connect_smb( + server_name=server_name, + server_ip=server_ip, + username=username, + password=password, + client_machine_name=client_machine_name, + domain=domain, + port=port, + ) + # Быстрая проверка существования каталога/файла + parent = "/".join(file_path_in_share.split("/")[:-1]) or "" + # listPath кидает исключение, если каталога/шары нет — получим понятную ошибку + if parent: + conn.listPath(share_name, parent) + + buf = BytesIO() + conn.retrieveFile(share_name, file_path_in_share, buf) + buf.seek(0) + return buf + except Exception as e: + raise SMBDownloadError( + f"Ошибка скачивания {share_name}/{file_path_in_share}: {e}" + ) from e + finally: + if conn: + try: + conn.close() + except Exception: + pass + +def read_excel_from_smb( + *, + server_name: str, + server_ip: str, + share_name: str, + file_path_in_share: str, + username: str, + password: str, + client_machine_name: str = "client", + domain: str = "", + port: int = 445, + sheet_name: Optional[Union[str, int, list]] = None, + **read_excel_kwargs: Dict[str, Any], +) -> pd.DataFrame: + file_obj = fetch_smb_file( + server_name=server_name, + server_ip=server_ip, + share_name=share_name, + file_path_in_share=file_path_in_share, + username=username, + password=password, + client_machine_name=client_machine_name, + domain=domain, + port=port, + ) + return pd.read_excel(file_obj, sheet_name=sheet_name, **read_excel_kwargs)
IDДата/времяХозяеваГостиАренаIDДата/времяХозяеваГостиАренаГород