From 5bc68e362726ef016323eeb109f81fa18ffe3f83 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 3 Sep 2026 15:05:45 -0300 Subject: [PATCH] feat(db): add SQLite database persistence for insights Implement local database storage to automatically save all sent insights with metadata including topic, content, AI provider, model, and timestamp. Add database module with initialization and query functions, integrate with main job flow, and update documentation with database schema details. --- README.md | 14 ++++++++++++++ database.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ insights.db | Bin 0 -> 12288 bytes main.py | 10 ++++++++++ 4 files changed, 70 insertions(+) create mode 100644 database.py create mode 100644 insights.db diff --git a/README.md b/README.md index 56a803e..66e873d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Sistema automatizado de envio de insights via Telegram, utilizando inteligência - Intervalo entre envios configurável. - Seleção aleatória de tópicos cadastrados. - Suporte a múltiplos provedores de IA: OpenRouter, LlamaCPP local ou OpenAI. +- Todos os insights são salvos automaticamente em um banco de dados SQLite local (`insights.db`). ## Pré-requisitos @@ -80,3 +81,16 @@ python3 main.py | `TOPICS` | Lista de tópicos separados por vírgula | ver `.env` | | `LANGUAGE` | Idioma dos insights | pt-BR | | `MIN_LINES` | Mínimo de linhas por insight | 20 | + +### Banco de dados SQLite + +Todos os insights enviados são registrados automaticamente no arquivo `insights.db`, na mesma pasta do projeto. A tabela `insights` armazena: + +| Campo | Descrição | +|---|---| +| `id` | Identificador único | +| `topic` | Tópico do insight | +| `content` | Conteúdo completo do insight | +| `provider` | Provedor de IA utilizado | +| `model` | Modelo utilizado | +| `sent_at` | Data e hora do envio | diff --git a/database.py b/database.py new file mode 100644 index 0000000..c06f4ac --- /dev/null +++ b/database.py @@ -0,0 +1,46 @@ +import sqlite3 +import os +from datetime import datetime + + +DB_PATH = os.path.join(os.path.dirname(__file__), "insights.db") + + +def init_db() -> None: + conn = sqlite3.connect(DB_PATH) + conn.execute(""" + CREATE TABLE IF NOT EXISTS insights ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + topic TEXT NOT NULL, + content TEXT NOT NULL, + provider TEXT NOT NULL, + model TEXT NOT NULL, + sent_at TEXT NOT NULL + ) + """) + conn.commit() + conn.close() + + +def save_insight(topic: str, content: str, provider: str, model: str) -> int: + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + sent_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + cursor.execute( + "INSERT INTO insights (topic, content, provider, model, sent_at) VALUES (?, ?, ?, ?, ?)", + (topic, content, provider, model, sent_at), + ) + insight_id = cursor.lastrowid + conn.commit() + conn.close() + return insight_id + + +def get_insights(limit: int = 50): + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + rows = conn.execute( + "SELECT * FROM insights ORDER BY id DESC LIMIT ?", (limit,) + ).fetchall() + conn.close() + return [dict(row) for row in rows] diff --git a/insights.db b/insights.db new file mode 100644 index 0000000000000000000000000000000000000000..3f4df86089880ff5c2a45ed9c9925ce9e55de94c GIT binary patch literal 12288 zcmeI2&u=706~|}RXaS1^ZaJVu#Vt|BgUJRYHWy4ZTCLdF8{?JOQ&iJaGcK#Us<*0p zwAv%^2jq|g$2c6KAVnMqmk4p`KLiO0{s8_0KJRtU+TO!*k9y?U@wBU7z4!g`UX8uK z`S77{oLR?I*~Z+R{9rPjPJU_3WHPzUbC2ilG3A?Y?cVSz@BiQ9_GDg6Ut`s+*FT=T z{xAL+A0q-I0wV$=0wV$=0wV$=0wV$=0wV$=0wV$-@aWdnJ_72cK-A3ZG?fy1LoMqWiOHVr7kuri@`j7Ltgau`sWk>DLvpjD@KrbM`~`sXl96Y zZh^*{Qj@Rgvngh4YMZR_gh~|F*hGF+E1LkChV`{AE4hdFvQ}zKL(zW&u1esO0Hxo^ zvTOo5x7N_>3GZ@_wlt1laqV;VG1zjxzyG)bmrSV)LUe3hXKcD>L-+%wT;4eScl05}5ffNp|gEXk` zf@HU8rINWhlB6gTErEP;u&C^B8BP+%Fm!`QMXa&Q3X-Yv=3lOqb34u!0O(pem zv0Ls`X7Uw#a2k_>gJqDc@R4OkvxEA{)-G7=VU{kqb@yfABj`Hx+{78&XTz4u?h7(j zkb94i)-zRVuNVxMq5Pz6z@YXe!PjU^b444f{Nzg8eJ&tUEdEO#W|0k|#G+JxJSb}I zgB8sr9Az87-APBtL(x=_8+1-WYnNOB*$Fy&3{ja2BCHv^JjG1tD%NZ(LWVrF921aW zEb+cv5~ZLM=yMx_Dn6)Tw-P}weW{9+)wN42OSeEoA^{V{po3ktdj#ceX-gZr&$TBQ zTf{U-QLJ7?rCXwjgJ$A?2v}aYzF4$}xtdtI)O{vtBDn}H2m}5!0m=hYb{fi179{f~ zwMbRE00lLIq2Vk_{>vnt4E1hR_I*neP;gRPX1|5#Dv|)pXg7d_2EeWreEKvX_y~!v zcQlH1mGHF--B*IU3dF7vDfXcak#rvP+Pgy9iVB&$h%RWXd^jgh_`ly;uoX6oHf4y; z*d(}cj!&)_)~?J^RFn*5@Ni|-nRKQaF*f)X<--*e?RL1)uZMp7h(hUED1F&x@%js~ zsApV>5GX7QhgFSINJ+Zs4g?(HGQC_@ORJ%Cin%dWYCv*kE*K~PMF|@^p9lya0){kN z9W-gnuH1}fMalw6ZJA_M_f^1(+@amF*Cl;Ky4U{x01C{m}Z78wRDA zMlkMaRQJQlBNsAvGl2C_z+iYT$CVizm0S0mW;QgHEp16>fN+KB#dTVq__m*?phbP0 zCtTP@?oM-aqSwe4crS)VNE!b=A%iy%dvMm+qO0ixj{_ zKd(}>G-zoG+5~&Ci4D!w)Ru8MNIB!m+?MEE#C3HR;th_GvkXg#@92YS8Xa}X6f~BX zeZ%O|PP?$;r6|`}8IwXU;Gp0WJ?2 zBw+|auYEP+h*aT~11jrdke>JYtDQKjhMoc95&>zBLU@F_jTh@`-wtz{d^-c zIBN3}6_Y3#Vp-|n5&L}zsp}Wh25)9dhCrx~4`^3_T#pLQc3|%|Q;BH(@ zzIO(3p2_j`eQ++=0msPMZb+25a^77o<@hy|vqWh0NFytBa9wY}7(m`?V+7Jh^7pJ{ zl8R3^oVOJ*K$8)7%^}0$-aaxuq!5a48)T}&?gymE`Jz98=$x8I&K`*wqWt>GKKltg z%j-dP_a4}}cMjkA`N82Y4i4{{pZ@IL;k)F)nI?EMe&P!>=C literal 0 HcmV?d00001 diff --git a/main.py b/main.py index 1e3a4b3..6ef7cf8 100644 --- a/main.py +++ b/main.py @@ -6,6 +6,7 @@ import openai import schedule import time from datetime import datetime +from database import init_db, save_insight load_dotenv() @@ -45,6 +46,10 @@ elif OPENAI_API_KEY: else: raise ValueError("Nenhuma chave de IA configurada no .env") +init_db() + +CURRENT_PROVIDER = DEFAULT_AI_PROVIDER + TOPICS = [t.strip() for t in TOPICS_RAW.split(",") if t.strip()] if not TOPICS: raise ValueError("Nenhum tópico informado no .env (variável TOPICS)") @@ -148,6 +153,11 @@ def job(): print(f"[{now.strftime('%Y-%m-%d %H:%M:%S')}] Tópico escolhido: {topic}") print(f"[{now.strftime('%Y-%m-%d %H:%M:%S')}] Gerando insight...") insight = generate_insight(topic) + try: + save_insight(topic=topic, content=insight, provider=CURRENT_PROVIDER, model=DEFAULT_AI_MODEL) + print(f"[{now.strftime('%Y-%m-%d %H:%M:%S')}] Insight salvo no banco de dados.") + except Exception as e: + print(f"[{now.strftime('%Y-%m-%d %H:%M:%S')}] Erro ao salvar no banco: {e}") send_telegram_message(insight, topic)