From e3077252a8e1add97b09781500d48d6a5a15d0c7 Mon Sep 17 00:00:00 2001 From: Maksim Date: Tue, 2 Sep 2025 11:28:07 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B4=D0=B5=D0=B2=20=D0=B4=D0=BE=D0=BA=D0=B5=D1=80=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BE=D1=83=D0=B7,=20=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=B5=D1=82=20=D1=81=D0=B2=D0=BE=D0=B4=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=A1=D0=90=20(=D0=BD=D0=BE=20=D1=82=D0=B0=D0=BC=20?= =?UTF-8?q?=D0=BF=D1=83=D1=81=D1=82=D1=8B=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docker-compose.dev.yml | 13 ++- .../__pycache__/pconfig.cpython-313.pyc | Bin 8206 -> 8206 bytes .../monitoring_fuel.cpython-313.pyc | Bin 11198 -> 11198 bytes .../__pycache__/svodka_ca.cpython-313.pyc | Bin 16493 -> 17230 bytes .../__pycache__/svodka_pm.cpython-313.pyc | Bin 13658 -> 13658 bytes python_parser/adapters/parsers/svodka_ca.py | 92 +++++++++++++++--- .../__pycache__/__init__.cpython-313.pyc | Bin 615 -> 615 bytes .../monitoring_fuel.cpython-313.pyc | Bin 1690 -> 1690 bytes .../core/__pycache__/ports.cpython-313.pyc | Bin 5953 -> 5953 bytes python_parser/core/services.py | 43 +++++--- 11 files changed, 124 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 94cf779..0e31b10 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ python_parser/app/schemas/test_schemas/test_adapters/__pycache__/ python_parser/app/schemas/test_schemas/test_app/__pycache__/ nin_python_parser +*.pyc *.py[cod] *$py.class diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 4327337..0bb12fa 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -14,7 +14,7 @@ services: restart: unless-stopped fastapi: - build: ./python_parser + image: python:3.11-slim container_name: svodka_fastapi_dev ports: - "8000:8000" @@ -24,9 +24,20 @@ services: - MINIO_SECRET_KEY=minioadmin - MINIO_SECURE=false - MINIO_BUCKET=svodka-data + volumes: + # Монтируем исходный код для автоматической перезагрузки + - ./python_parser:/app + # Монтируем requirements.txt для установки зависимостей + - ./python_parser/requirements.txt:/app/requirements.txt + working_dir: /app depends_on: - minio restart: unless-stopped + command: > + bash -c " + pip install --no-cache-dir -r requirements.txt && + uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + " streamlit: image: python:3.11-slim diff --git a/python_parser/adapters/__pycache__/pconfig.cpython-313.pyc b/python_parser/adapters/__pycache__/pconfig.cpython-313.pyc index 34fa65daaf59b3d45658af0d60abbe9c1e495e8d..04c2debfca9f66d3b422f35a36a05254739069a5 100644 GIT binary patch delta 22 ccmeBk=yTxv%*)Hg00b^Gwq?waSjeXU07#_uU&yBb08SW)t}nhDNe1{W~SO{JAL<$ola9n$=uq$vwHc#c`WFpqCTzYp`>)Pw)&veGDK`9>W7ZZ1M1mkkrVen>hOheCpQ}^kJ zq0PS_AWa(Uxq4dX5EnI4odF{nPPC_T^UobVbNEhfdCFBWnw88eNqOgAlP}9RyOZ9wac@V`+i}<1nJn;(cBX74 zcWhOurnU?1NxOT@bK71&#UbA+JZVJM!U;3VbADqgq-}h;1rF4_`3^?tWfVZP_NfAT zim#@lnHi=R5b_~eAD|aYYR$}3%cniHjJ^KF@}g3C4VWZpI!~1#}rT(yVaA;h1@#&0l(;ofs`5J zv2mN!+?YrdDX8p=hW3aoElDxkK-k@o%8Rj>B!^UeM3PlqETm?%vpvfR1XvduaAy#f zgou;_9IzpsEO3Z+EGg1#m4dts;Afal2Z1%}GL|1379W_Zu%~<%h zqxFR0lzGC0vhz;2oNoEaUU*_XJg;U7_Jj_NXD{t-BVv9JwlMY4W43 zWb{zVUNUa4Oxi0`uF6!o@1Cb-Z1Dr!FdWFBF^E2+*v%SN1l?#hXp9lanEWy z*LSw>g8R0$dQ_JxEPBb9vN=XG9`GpNdan9xHT5P!#w-(Vpt0Pwcnkd~cQr>KmULF$ zO}vrb%473tn9sG$<%kzXZI-PSkGw-5e58m$%^e6w;u4WUK`kx{sidD;{CEkSvJiH_ zz^pP{MNcwzCa%ufXX=2RD!)q-LlRMWNgfQ#5?R3(4{_W36pyRhja80FgVF)g4u^CB z*uNirZgIYl?{X8J`@1%5B_ThU&<4es>IKdia+tROi?uLvIeePUer8RHf7!_o`*DBWIG%pCMgSpZMcZ_Bn= z)6W3)K7eL&khZ#f_;ng{&BMp(c~>d^HSxZy7u!To>77w_>(ZBANoy)v@!wRg@blW; z8xof)))|YTV3KCQVg?0Di6D%m@77B5yz{r^uzx#g1C6B&Yc;QTYtGifLfD@jk(Gc> zz$yUb3SpHUku?BTauHbv*Z|N%*3Cw48%=uM5y&JG1ndB44}hE^I|2OwEiMv_1^|Nq zC>&%LAPm^e;4XMFL=7N!~w&AeSrOR*w?Ak;lwAt zn?@7(%biC=dbF{z;8iwJjdDmNL_A0!!&u&Z)p!g?X;;&Dy;fNsdk!m)Og$deJ~8PK z!PDL5^rf`|@;Mz?;K?SXU<*nJ`I5f2pwp^apoD1~gUrBgB`T`|Eo>6-P{O)U*5OL} zT=RP8CfEVKQcbI*gUbks64ek^!ZHccKbz~YpSl(|yHpc=mBFBD+o;vTrBAPi%1FF3 zg0u9<;?pKwxu;tJa&$5{R=@)>`e7vz>^z8 delta 1632 zcmZ9MYitx%6oBXM?#%A?3HzWeecIApW{J`^EzfPcEu}o%mRGm7v^oymX}hpHyPVk; zY65MdkR}=>9!bOoAs$=}7r1p;q>nl~mN@4Q~}oz3Bz(6YIEHejqCUv<2K@5yd<8Q*qv zOD_q6F2!)K({GCj7%B2q48_~*v|SK}27{b^4T4Z8)Cn=1?eIrqVjHuB!Wu!gLopwl zVk#Zi3{%Rghmx8hiy-n`K7!$w+{op?bE`rXR{(_z_h z@-6sG+{9kT_0qaFf7!hIS6{_^&%K)ZPgCbomwK-5_;Sa!fx9)`^Sj>N^TaOrSKlx7 zE|xalDQ&!%xLexvv#SYjNR3#z*6rveV+(RUx}Eh7?lHp!*VdP9W@$zRLsN|Dbo_{_ zjHyt~6?r^9-T@C-fC>(SE&{>bSh<#FwH$N8k|cP`7CnJGp1{SKyPnOtNI{{Ct;49N zpS9zHC+ajs>n8A#=N8+ApL?S&Q&KY-O^@R__$W#YKncd&5z90=j8Z#C<}Pkc?R z6@T&px7XoskOKTfvj!8M+T3aXv}-m*b7@jj;~JQfrcWev4Z?i!E%A*}BeZ5fW~K<* zgmxS@^HtbMuo1R!n2xb@QjxtdOp^lOW)$uCn3go9@kBDItFV_;_mR{IS|+KEX{Jp( zYTDIg(i9WA80Ur$;9O1Bu05pLPnso4!cg)qDUdz}qa=BaBqFH#gl3Ayq?*wn#+Ufn z|KBhL1J(GT<|gaI^R)qXKKFg?QF|GA{>O9Pcs^#yjykYosDYo4 z-~_(Y5Mtltt~NYiER1IxFV@b`wt_)5Ii+m}NQ3Dfn91aaGJ{qe2*lVgcsVfaUyc~| zldTU43YG`gvkSO07-T0g6Rc;qa~K?9WkV#T>#>w&TRxZ$6y*r}4SVG#w-wVmu7UU4 zSLM#i8=Tcmq~AxdK*4$YMFgP!i|JY9Q{`62CR%(xrKHTbr3oU5keQC zo3M@0L)cE(L9p_^n~&r&=%WS76j(WXA>adoutItX;kOPl3%d-m2=q9zeXTwl7odNE@0K@p%mQ(BihPE!Y^E%CHDFdurS%vQI;a^WLs42~K z^1p}vpnMXoiY&>8Q%82b!VTe&*DSD}ucGrE*1rH>V=}y3gc`1cKZfhszTBg*ZsYHD zq;sznrJbXAz0=1A@rTY&jLo5qpI2X@9w{jW%Lzm>;Te2?j{Jn_NmCpa{=&RkCKh($yR)5}PkMo(jBMs&a^B z9~(;o?=JON-L?HUN3V}QrIn>3bau}bTh}A%Q>n}hT*HsL4|6lmyUW=_EZ^3+a!O>C OXAb@&P!Hw&sQ&=9te<-T diff --git a/python_parser/adapters/parsers/__pycache__/svodka_pm.cpython-313.pyc b/python_parser/adapters/parsers/__pycache__/svodka_pm.cpython-313.pyc index 1fecb749a7dc66e495129a49bc9695348ec8a99c..66f3854c898febc98cfa2b00d6522ec7ca7c5baa 100644 GIT binary patch delta 20 acmcbWbt{YeGcPX}0}!~+-nNn3-xL5$^#+3g delta 20 acmcbWbt{YeGcPX}0}#BrwRI!6zbODx-v?v> diff --git a/python_parser/adapters/parsers/svodka_ca.py b/python_parser/adapters/parsers/svodka_ca.py index 4c3be9b..8ef0c53 100644 --- a/python_parser/adapters/parsers/svodka_ca.py +++ b/python_parser/adapters/parsers/svodka_ca.py @@ -17,7 +17,7 @@ class SvodkaCAParser(ParserPort): # Используем схемы Pydantic как единый источник правды register_getter_from_schema( parser_instance=self, - getter_name="get_data", + getter_name="get_ca_data", method=self._get_data_wrapper, schema_class=SvodkaCARequest, description="Получение данных по режимам и таблицам" @@ -31,16 +31,74 @@ class SvodkaCAParser(ParserPort): modes = validated_params["modes"] tables = validated_params["tables"] - # TODO: Переделать под новую архитектуру - data_dict = {} + # Проверяем, есть ли данные в data_dict (из парсинга) или в df (из загрузки) + if hasattr(self, 'data_dict') and self.data_dict is not None: + # Данные из парсинга + data_source = self.data_dict + elif hasattr(self, 'df') and self.df is not None and not self.df.empty: + # Данные из загрузки - преобразуем DataFrame обратно в словарь + data_source = self._df_to_data_dict() + else: + return {} + + # Фильтруем данные по запрошенным режимам и таблицам + result_data = {} for mode in modes: - data_dict[mode] = self.get_data(self.df, mode, tables) - return self.data_dict_to_json(data_dict) + if mode in data_source: + result_data[mode] = {} + for table_name, table_data in data_source[mode].items(): + if table_name in tables: + result_data[mode][table_name] = table_data + + return result_data + + def _df_to_data_dict(self): + """Преобразование DataFrame обратно в словарь данных""" + if not hasattr(self, 'df') or self.df is None or self.df.empty: + return {} + + data_dict = {} + + # Группируем данные по режимам и таблицам + for _, row in self.df.iterrows(): + mode = row.get('mode') + table = row.get('table') + data = row.get('data') + + if mode and table and data is not None: + if mode not in data_dict: + data_dict[mode] = {} + data_dict[mode][table] = data + + return data_dict def parse(self, file_path: str, params: dict) -> pd.DataFrame: """Парсинг файла и возврат DataFrame""" - # Сохраняем DataFrame для использования в геттерах - self.df = self.parse_svodka_ca(file_path, params) + # Парсим данные и сохраняем словарь для использования в геттерах + self.data_dict = self.parse_svodka_ca(file_path, params) + + # Преобразуем словарь в DataFrame для совместимости с services.py + # Создаем простой DataFrame с информацией о загруженных данных + if self.data_dict: + # Создаем DataFrame с информацией о режимах и таблицах + data_rows = [] + for mode, tables in self.data_dict.items(): + for table_name, table_data in tables.items(): + if table_data: + data_rows.append({ + 'mode': mode, + 'table': table_name, + 'rows_count': len(table_data), + 'data': table_data + }) + + if data_rows: + df = pd.DataFrame(data_rows) + self.df = df + return df + + # Если данных нет, возвращаем пустой DataFrame + self.df = pd.DataFrame() return self.df def parse_svodka_ca(self, file_path: str, params: dict) -> dict: @@ -147,12 +205,22 @@ class SvodkaCAParser(ParserPort): # Сортируем по индексу (id) и по столбцу 'table' combined_df = combined_df.sort_values(by=['id', 'table'], axis=0) - # Устанавливаем id как индекс - # combined_df.set_index('id', inplace=True) - - return combined_df + # Преобразуем DataFrame в словарь по режимам и таблицам + # Для сводки СА у нас есть только один режим - 'fact' (по умолчанию) + # Но нужно определить режим из данных или параметров + mode = params.get('mode', 'fact') # По умолчанию 'fact' + + data_dict = {mode: {}} + + # Группируем данные по таблицам + for table_name, group_df in combined_df.groupby('table'): + # Удаляем колонку 'table' из результата + table_data = group_df.drop('table', axis=1) + data_dict[mode][table_name] = table_data.to_dict('records') + + return data_dict else: - return None + return {} def extract_all_tables(self, file_path, sheet_name=0): """Извлечение всех таблиц из Excel файла""" diff --git a/python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc b/python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc index 1aec30edcf94720f2f76ab9e5a817bd6e53fb8a6..ba4174ea6daf5dfd258ac27e73ffa56d87f0bae2 100644 GIT binary patch delta 22 ccmaFP@|=bDGcPX}0}!~(*p?x_kvED708DBICIA2c delta 22 ccmaFP@|=bDGcPX}0}zy4U6aANkvED708e%XdjJ3c diff --git a/python_parser/app/schemas/__pycache__/monitoring_fuel.cpython-313.pyc b/python_parser/app/schemas/__pycache__/monitoring_fuel.cpython-313.pyc index cd2c9092a66e970c99f878544856253897801de3..665d31487c15a77b0c3a0b05a984377a2e7f4eeb 100644 GIT binary patch delta 22 ccmbQmJBye1GcPX}0}!~(*p?Bvk++u(07AS54*&oF delta 22 ccmbQmJBye1GcPX}0}$M~v?fD;BX2Jo07&!(wEzGB diff --git a/python_parser/core/__pycache__/ports.cpython-313.pyc b/python_parser/core/__pycache__/ports.cpython-313.pyc index 6bf9520b22aff7db6c5e669ad6f6df92ecf92e7a..07bb3675611242055bac838c54bdd635951bf74e 100644 GIT binary patch delta 20 acmX@8cTkV}GcPX}0}!~+-nNn3OdJ3}e+8ld delta 20 acmX@8cTkV}GcPX}0}vQo+`5t5OdJ3~9R 0 else 'fact' else: - return DataResult( - success=False, - message="Парсер не имеет доступных геттеров" - ) + default_mode = 'fact' + + # Устанавливаем режим в параметры, если он не указан + if 'mode' not in get_params: + get_params['mode'] = default_mode + + # Определяем имя геттера + if request.report_type == 'svodka_ca': + # Для svodka_ca используем геттер get_ca_data + getter_name = 'get_ca_data' + else: + # Для других парсеров определяем из параметра mode + getter_name = get_params.pop("mode", None) + if not getter_name: + # Если режим не указан, берем первый доступный + available_getters = list(parser.getters.keys()) + if available_getters: + getter_name = available_getters[0] + print(f"⚠️ Режим не указан, используем первый доступный: {getter_name}") + else: + return DataResult( + success=False, + message="Парсер не имеет доступных геттеров" + ) # Получаем значение через указанный геттер try: