From d6180e49733f69d006c12084d7e790b73440af20 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Tue, 17 Jun 2025 20:06:49 +0100 Subject: [PATCH] =?UTF-8?q?ChatBot=20-=20Intents=20Dinamicas=20a=20funcion?= =?UTF-8?q?ar/tradu=C3=A7=C3=B5es/estrutura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/__pycache__/main.cpython-313.pyc | Bin 38274 -> 31805 bytes ChatBot-Python/src/main.py | 202 +++++------------- ChatBot-Python/src/server.py | 12 +- 3 files changed, 60 insertions(+), 154 deletions(-) diff --git a/ChatBot-Python/src/__pycache__/main.cpython-313.pyc b/ChatBot-Python/src/__pycache__/main.cpython-313.pyc index db5ba9858ca8912441465184571951728038091b..ba8374828d63960ab40263c57191fbe971c7b8a1 100644 GIT binary patch delta 3098 zcma)8Yj6|S6~3#NWlOd!%kP)gl8r566yrw-GB^RpfQfA$Cx%cIlto$_8A+>NNw#ZD zWC%$VS^~~CwcGv(q|E@+A7w_JOs1V?((#}CgepJqHl1|ZnYPo;v`*-cOghu{+!c~7 zrnK1^e|ui{JLlee&dL{mqWR^QT<3$*(h`EtUw(1qO!Z5I{DCRCr}Uxl;Cp&PmfY_o zFF;W}ndWFIT!dm(DuY+Rq)HYr*ArR}m!M45tzbdD0?H9>@G4kUYbCq}6{=K)95z*| zMsKAm)u2?RO0_6et5O}*AVWR84z)?u(9lhw4(fdzG(aO_6Eq{*fk$is2ci>N5!+x3 zVmovox}XzrD|8`l12^Im(2ckqdJub|53wJfMBD*SA?^f^kHb6zunV=_Fo^gxB%i_8 z9@vXG1kWN4!#>3QZ~$=x4kA7WqljZLj(7+TBOZaHhyc$cPQWq5iM;=C0O;&tX@+PrN?$K<6gHp@5jw)UK@iFT8IjanC>9o}|`u{{Y- zV$mHBSH<(ITshlNB5&*T)?hZ5w-aV|YUoyVKeOJgWu#Y=i{Z)GtH{?5b6^YG@n>i&OS`?;}N zHOo0QOItPzyN98L+{w@`Zv!%Q!TFus<7!2oA(iLlM|pgkE2=`CPDuVwo(S?-F>a$b zwyF2E=-rm<0UrB#>;mf|S1NtSR@Sx?TPpwPP}1Vxg9U`Q32+mnjDV(B8{tmo<0iQ5 z_T+}X)}(_>4P9-97fe(m$A|4C{=Y@boc@gy)DKvw|Fnln$>6G7dwP(&CLce2-L%LI*(9>Y-^#ruxBCb6KSb+7dfMM^Fl5)t%fa!+CA7MX zk4ec*Kc{~O18$~|OkdG+SLM&78(d1h5cTTcMe}O<_tB66&4*^+N3&sWTK^uJKT9X( zo+;vP$nPiHd9zY9Ekwj1m6YO1aYm#}Ot23~7-!_BBx@G^Nji(V+G|Q#VfQLpY*G3OWH{3#Dg1%)v)-&_QTsiXJQhm)XZrtUjXW5o448jc9|9Er`K^}VJfSa{HWlcy~ zYf;uKlywPZ7fjhrQFiW>oicq-{@ELY0-L*8kt(K#!xRwWf})+mZ%qF1=$}~lpBelV zLD8{YsB?Ln(38xpj-_!|SPCRU;voGe+Bp1+&Eb!N{O`*?y_xI%qA`8y+kG1O?JEt& zx>9S&;>dcl{K1u$!=D>SP4}IWO!Yv@bl=*zY+tfpugzFHQ--{Dt0!Z1rwqR{S?^Z0 zWlU}ND{7ZJmpWG_G8N9$P@%RapIWLPUo%%?^tDqNle5rWsIA#{eBl3Bd(SqSdUnlh zy*9EESUGmLqV*GVTbAK&l{;f{%gx`daperIjLB8VQmC!Tf0XS_SSee1-&VKmS@Nt% z8JinZ*tUP9yJs7g|1MV_$B6cfsr`|~w$@wDdunW9If}XR^Ab|oa=kud>q;5djHRjR Wm5xu0&M$%-FGX4X3#xUgo delta 8623 zcmeHMdr(_fdcRjfAP{eX03q-N^RNJmhXn?Lu>qThjlkxB?U-wmYb1e;9{5Vaj_|JB zB;Angwu^H&YrM|JIN8>a)D2mh)O}6a)gMXQneLUPSfF-x?CefDJKJ{cxZ8=RGo60t zN>@Ozx86>7|5}VboqNvr{C(f~&bjz+z93(CSE75El9EKg&ubqZ9@nx2@e}+Y{-j0+ z4_{Ie#3}NWtIS6K6;^1zksp!YCEJg%rXl4JE4yHXXfzIS;8m>M%ltB7pXu)9{OXHBRs3UH3Z5YOs18*FtwFUw>(DNsdQ=az0qq9b zh?+)7u+xm5fu|O<2WTtW3v?gyw!v3B>HxYQ9RS*ix`1}09-s%&A)vj;0JIPF13iq6 z03ARG=pY&bdK4W4dK^6q^aL6nArFvC1>6u2s*i# zsE=lXEhwf7c0?bTJXkC&CeZ16GBQOGbiu%0?gvep?tZzjG`hZoj3^1Ez&{f*qcJp& zEa(i9qX}e1HeCu!6%uq2D6@+aY%yIt(8XPEQhSl(88EPba}lb;;YEZq(q}&>C#kUmqgS>5nW_IN6Q8d6I@$SmdqLR zW{ZYe&`_!*juK8uHGGsrcLG5;2;`E1slyUOB0(q!3Rdg{s}*2zqp5+Rh#wm%tre|r z@uUS?=pws!5-9{*NmoXcNAQUgT|10nGD3icN`iQTiAr=1y9Mr&r^uMY8fI4 zh}4tlJk+|J&L0>S&|k-SkVMa8VG#j{O&g1i6n$rPLW~~K$y5SO(>kCRcF9B@sFX_d z6|5_khiiahbZ%f67Q`R!Pnyzn;I8D9@Ip&6gn@#6aZsQ3`rF{WrJ9aD8}jqVYY7Q>=fpEp+3+G&XrArWYEHptn)f!?yNk(A0o;T}&L&8+hneQG-zd z8gR9~B5HtkC^dYaAo?3NdvsA$wEoSuZe;0vg42*Sx|ME0uQo}d<+(L$Qp0~k4Siiq zLb6apR&Y(MjBkib)MNAWC49ZH9Enl&lZXTWvmnwx0AK!4@TEmWfk}&^@J+e}+Sja5 zRdJ#$>|2F^(WNcb7H9Zs#L0C~@QCXxN|3!Ky8OG^`!#A$W_a=cP<#8IPBT3U8X|8)mz&6Fr<|kPpdVe?(vLDRXMHqL zQQq%t=}rlT{{o%SyLBtNDs)xcDpSBrF?u6r=DT$9`At!US5Scc1O;ER zer=0&xuG98Gq5SKld|nXA7$xSn&7mN8&2cvzEcvpIGf?xCydDGNVvTTV+G-iK4@<& z|4^1%N)nvEE^9eX_j9S@JfN`zALp9{cQ0GHoKkl$j-29t(a@Vc4b$ueOyKpBV2d#E zaPQaVa^t)8vai77kCw0RemRlCr!EUgBUlQPb3nB6aKfmYWleS$dx2AQYB*zCrQ#y2 z+=q{oj~Khz_FkOqC77sQW;(x<&UL=U6NGDmQa%Ua`R%x9fNb`3>&)@mAUEz-C{* zdb~V7J|fF~e5z0Lb(q!4pJ2OgjCeEA+Dc1z9VBH5>vu!F!GmRTL=OsrNC zmgs82apxSY(Fv~`1Vyn>=tMZNWz1wZou6b|+0Wp4FRVEWVem8JBEhFxZ#PUV7{BES z<&^t#$^$vtJ84>&dgR1DzOda}N@``{co%cpWM#tfqYkUvj=ve(q{A6bG&x4m=LMI;$T%tZjZUzQpxX`aaYT%{Ew)G*EzU`Y-FI`! zWN}hnr|)eC=#o@XtPTXWz_<%0AGQN&CAR}OQ6RG-uUwOvLy|!$BJj%_{wsm-zL`$I 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 WuQS&s{k^8z+#vslvTAdS{C@y1TF-L; diff --git a/ChatBot-Python/src/main.py b/ChatBot-Python/src/main.py index c4463d2..ccf619f 100644 --- a/ChatBot-Python/src/main.py +++ b/ChatBot-Python/src/main.py @@ -173,6 +173,20 @@ def chat_with_gpt(prompt, attempts=3): except Exception as e: return f"Erro genérico com a API OpenAI: {type(e).__name__} - {e}" +def extract_intent_and_params(user_input): + prompt = f""" + Analisa a seguinte pergunta de utilizador e devolve um JSON com: + - "intent": a intenção principal (ex: comparar_total, comparar_kwh, listar_faturas, total_kwh_tipo_edificio, top_consumidores, etc) + - "params": dicionário com parâmetros relevantes (ex: mes, ano, tipo_edificio, cunit_id, etc) + Pergunta: "{user_input}" + Exemplo de resposta: {{"intent": "comparar_total", "params": {{"mes": 1, "ano": 2025}}}} + """ + response = chat_with_gpt(prompt) + try: + return json.loads(response) + except Exception: + return {"intent": "unknown", "params": {}} + def get_price_comparison(): conn = connect_db() if not conn: @@ -555,7 +569,6 @@ if __name__ == "__main__": "janeiro": 1, "fevereiro": 2, "março": 3, - "março": 3, "abril": 4, "maio": 5, "junho": 6, @@ -572,173 +585,60 @@ if __name__ == "__main__": if user_input.lower() in ["quit", "exit", "bye"]: break - if any(word in user_input.lower() for word in ["tabela", "coluna", "campos", "estrutura", "schema"]): - schema_info = get_schema_with_examples() - if isinstance(schema_info, str): - print(f"Chatbot: {schema_info}") - else: - if re.search(r"tabelas|todas as tabelas", user_input.lower()): - nomes = [t['table'] for t in schema_info] - print(f"Chatbot: As tabelas disponíveis são: {', '.join(nomes)}") - else: - for t in schema_info: - if t['table'].lower() in user_input.lower(): - colunas = ", ".join([ - next((k for k, v in column_mapping.items() if v == c['name']), c['name']) - for c in t['columns'] - ]) - print(f"Chatbot: A tabela '{t['table']}' tem as colunas: {colunas}.") - if t['example']: - exemplo_traduzido = { - next((k for k, v in column_mapping.items() if v == k or v == k or v == col), col): val - for col, val in t['example'].items() - } - exemplo_traduzido = { - next((k for k, v in column_mapping.items() if v == col), col): val - for col, val in t['example'].items() - } - print(f"Exemplo de linha: {exemplo_traduzido}") - break - else: - print("Chatbot: Não encontrei essa tabela. Pergunte por outra ou peça 'tabelas' para ver todas.") - continue - - cunit_id, date_billling_begin, date_billing_end, total_requested = parse_user_input(user_input) - - if total_requested and cunit_id: - data = get_total_by_cunit(cunit_id) - print(f"Chatbot: Aqui estão os totais encontrados:\n{data}") - continue - - if cunit_id or date_billling_begin or date_billing_end: - data = get_filtered_data(cunit_id, date_billling_begin, date_billing_end) - print(f"Chatbot: Aqui estão os dados encontrados:\n{data}") - continue - - if "preços faturados" in user_input.lower(): - data = get_price_comparison() - print(f"Chatbot: Aqui está a comparação dos preços:\n{data}") - continue - - if re.search(r"mês atual.*igual período.*ano anterior", user_input.lower()): - data = compare_current_vs_previous_year() - print(f"Chatbot: {data}") - continue - - if re.search(r"mês.*igual período.*ano anterior", user_input.lower()): - match = re.search( - r"(?:mês\s+de\s+([a-zç]+|\d{1,2}))(?:\s+do\s+ano\s+(\d{4}))?", - user_input.lower() - ) - - if match: - mes_input = match.group(1).strip().lower() - ano = int(match.group(2)) if match.group(2) else datetime.now().year - - if mes_input.isdigit(): - mes = int(mes_input) - else: - mes = month_map.get(mes_input) - - if not mes: - print("Chatbot: Mês não reconhecido. Tenta novamente.") - continue - else: - mes = datetime.now().month - ano = datetime.now().year + # NOVO: Extração dinâmica de intenção e parâmetros + intent_data = extract_intent_and_params(user_input) + intent = intent_data.get("intent") + params = intent_data.get("params", {}) + if intent == "comparar_total": + mes = params.get("mes") + ano = params.get("ano") data = compare_current_vs_previous_year(month=mes, year=ano) print(f"Chatbot: {data}") continue - - if "homólogo" in user_input.lower(): - match = re.search(r"homólogo.*?(\d{4})", user_input.lower()) - ano = int(match.group(1)) if match else None - data = get_top_consumers(current=False, year=ano) - if ano: - print(f"Chatbot: Aqui estão as instalações com maior consumo no período homólogo de {ano}:\n{data}") - else: - print(f"Chatbot: Aqui estão as instalações com maior consumo no período homólogo atual:\n{data}") - continue - - if re.search(r"total de kwh.*mês.*ano anterior", user_input.lower()): - match = re.search( - r"(?:mês\s+de\s+([a-zç]+|\d{1,2}))(?:\s+do\s+ano\s+(\d{4}))?", - user_input.lower() - ) - - if match: - mes_input = match.group(1).strip().lower() - ano = int(match.group(2)) if match.group(2) else datetime.now().year - - if mes_input.isdigit(): - mes = int(mes_input) - else: - mes = month_map.get(mes_input) - - if not mes: - print("Chatbot: Mês não reconhecido. Tenta novamente.") - continue - else: - mes = datetime.now().month - ano = datetime.now().year + if intent == "comparar_kwh": + mes = params.get("mes") + ano = params.get("ano") data = compare_kwh_current_vs_previous_year(month=mes, year=ano) print(f"Chatbot: {data}") continue - - if re.search(r"quantas faturas.*mês", user_input.lower()): - match = re.search( - r"(?:mês\s+de\s+([a-zç]+|\d{1,2}))(?:\s+do\s+ano\s+(\d{4}))?", - user_input.lower() - ) - if match: - mes_input = match.group(1).strip().lower() - ano = int(match.group(2)) if match.group(2) else datetime.now().year - - if mes_input.isdigit(): - mes = int(mes_input) - else: - mes = month_map.get(mes_input) - - if not mes: - print("Chatbot: Mês não reconhecido. Tenta novamente.") - continue - else: - mes = datetime.now().month - ano = datetime.now().year - - data = get_invoices_by_month_year(month=mes, year=ano) - print(f"Chatbot: {data}") - continue - - if re.search(r"faturas.*instalações.*inativas", user_input.lower()): + if intent == "listar_faturas_inativas": data = get_invoices_from_inactive_units() print(f"Chatbot: {data}") continue - if re.search(r"total de kwh.*tipo de edifícios", user_input.lower()): - match = re.search(r"tipo de edifícios\s+([a-zçãõáéíóúâêîôûäëïöü\s]+)", user_input.lower()) - building_type = match.group(1).strip() if match else None - - if building_type: - data = get_total_kwh_by_building_type(building_type=building_type) - print(f"Chatbot: Aqui está o total de kWh para o tipo de edifício '{building_type}':\n{data}") - else: - data = get_total_kwh_by_building_type() - print(f"Chatbot: Aqui está o total de kWh por tipo de edifício:\n{data}") + if intent == "listar_faturas_mes": + mes = params.get("mes") + ano = params.get("ano") + data = get_invoices_by_month_year(month=mes, year=ano) + print(f"Chatbot: {data}") continue - if user_input.lower() in ["cunitbills", "cunits", "cunittypes"]: - data = get_data(table_name=user_input) - print(f"\nDados da tabela {user_input}:\n{data}") + if intent == "total_kwh_tipo_edificio": + tipo = params.get("tipo_edificio") + data = get_total_kwh_by_building_type(building_type=tipo) + print(f"Chatbot: {data}") continue - if "dados" in user_input.lower(): - data = get_data() - print(f"\nDados do SQL Server:\n{data}") + if intent == "top_consumidores": + ano = params.get("ano") + data = get_top_consumers(year=ano) + print(f"Chatbot: {data}") continue + # ...adiciona mais intents conforme necessário... + + # fallback: resposta do LLM se não reconhecer a intenção response = chat_with_gpt(user_input) - print("Chatbot: ", response) \ No newline at end of file + print("Chatbot:", response) + + # --- CÓDIGO ANTIGO (comentado, não removido) --- + # if "No mês de Janeiro estamos a pagar mais ou menos que em igual período do ano anterior?" in user_input: + # print("Chatbot: Estamos a pagar menos em 01/2025 (219711.01) do que no mesmo período do ano passado (250508.93).") + # continue + # if "total do CUnitId" in user_input: + # # lógica antiga... + # continue + # ...restante código hardcoded... \ No newline at end of file diff --git a/ChatBot-Python/src/server.py b/ChatBot-Python/src/server.py index d036fda..7491b2c 100644 --- a/ChatBot-Python/src/server.py +++ b/ChatBot-Python/src/server.py @@ -211,6 +211,15 @@ def chat(): user_input = data.get("message", "") lower_input = user_input.lower() + if "cunits" in lower_input or "dados cunit" in lower_input or "dados unidades" in lower_input: + return make_reply(translate_any(get_data(table_name="CUnits"), column_mapping_inv)) + + if "cunittypes" in lower_input or "cunitstypes" in lower_input or "dados cunittypes" in lower_input or "dados tipos" in lower_input: + return make_reply(translate_any(get_data(table_name="CUnitTypes"), column_mapping_inv)) + + if "cunitbills" in lower_input or "dados" in lower_input: + return make_reply(translate_any(get_data(table_name="CUnitBills"), column_mapping_inv)) + if "tabela" in lower_input or "coluna" in lower_input: return make_reply(answer_schema_question(lower_input)) @@ -286,9 +295,6 @@ def chat(): pref = f"para o tipo de edifício '{building}'" if building else "por tipo de edifício" return make_reply(f"Aqui está o total de kWh {pref}:\n" + dicts_to_markdown(translate_any(dados, column_mapping_inv))) - if "dados" in lower_input: - return make_reply(translate_any(get_data(), column_mapping_inv)) - return make_reply(chat_with_gpt(user_input)) if __name__ == "__main__":