primer commit
This commit is contained in:
commit
2be35d7e55
17 files changed
+2709
No files matched your search
@@ -0,0 +1,166 @@
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from collections import Counter
|
||||
|
||||
try:
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.ticker as mticker
|
||||
HAS_MPL = True
|
||||
except ImportError:
|
||||
HAS_MPL = False
|
||||
|
||||
DB_PATH = Path(__file__).parent / "fallas_descarga.db"
|
||||
REPORTES_DIR = Path(__file__).parent / "reportes"
|
||||
REPORTES_DIR.mkdir(exist_ok=True)
|
||||
|
||||
COLORS = {
|
||||
"Error HTTP 500": "#e74c3c",
|
||||
"falló: PDF": "#e67e22",
|
||||
"falló: XML": "#3498db",
|
||||
"falló: PDF, XML": "#9b59b6",
|
||||
}
|
||||
CMAP = list(COLORS.values())
|
||||
|
||||
|
||||
def _query(sql, params=()):
|
||||
with sqlite3.connect(str(DB_PATH)) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute(sql, params).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def reporte_texto():
|
||||
print("\n" + "=" * 60)
|
||||
print("📊 REPORTE DE FALLAS - SUNAT")
|
||||
print("=" * 60)
|
||||
|
||||
total = _query("SELECT COUNT(*) AS c FROM fallas")[0]["c"]
|
||||
print(f"\n📄 Total fallas registradas: {total}")
|
||||
|
||||
if total == 0:
|
||||
print("\n✅ No hay fallas registradas.")
|
||||
return
|
||||
|
||||
print("\n── Distribución por tipo de error ──")
|
||||
for r in _query("SELECT COUNT(*) AS c, error FROM fallas GROUP BY error ORDER BY c DESC"):
|
||||
pct = r["c"] / total * 100
|
||||
print(f" {r['error']:25s} {r['c']:>5d} ({pct:5.1f}%)")
|
||||
|
||||
print("\n── Top 10 RUC con más fallas ──")
|
||||
for i, r in enumerate(_query("SELECT COUNT(*) AS c, ruc FROM fallas GROUP BY ruc ORDER BY c DESC LIMIT 10"), 1):
|
||||
print(f" {i:2d}. {r['ruc']} ({r['c']} fallas)")
|
||||
|
||||
print("\n── Distribución por intentos ──")
|
||||
for r in _query("SELECT intentos, COUNT(*) AS c FROM fallas GROUP BY intentos ORDER BY intentos"):
|
||||
print(f" {r['intentos']:2d} intento(s) → {r['c']:>5d} documento(s)")
|
||||
|
||||
print("\n── Fallas por día ──")
|
||||
for r in _query("SELECT DATE(fecha_ultimo) AS dia, COUNT(*) AS c FROM fallas GROUP BY dia ORDER BY dia"):
|
||||
print(f" {r['dia']} → {r['c']} falla(s)")
|
||||
|
||||
|
||||
def _color_error(error):
|
||||
return COLORS.get(error, "#95a5a6")
|
||||
|
||||
|
||||
def grafico_barras_errores():
|
||||
data = _query("SELECT COUNT(*) AS c, error FROM fallas GROUP BY error ORDER BY c DESC")
|
||||
if not data:
|
||||
return None
|
||||
|
||||
labels = [r["error"] for r in data]
|
||||
values = [r["c"] for r in data]
|
||||
colors = [_color_error(l) for l in labels]
|
||||
|
||||
fig, ax = plt.subplots(figsize=(10, 5))
|
||||
bars = ax.barh(labels, values, color=colors, edgecolor="white", height=0.6)
|
||||
for b, v in zip(bars, values):
|
||||
ax.text(b.get_width() + max(values) * 0.01, b.get_y() + b.get_height() / 2,
|
||||
str(v), va="center", fontsize=11, fontweight="bold")
|
||||
ax.set_xlabel("Cantidad de fallas")
|
||||
ax.set_title("Distribución de fallas por tipo de error", fontsize=14, fontweight="bold")
|
||||
ax.margins(x=0.15)
|
||||
ax.spines[["top", "right"]].set_visible(False)
|
||||
fig.tight_layout()
|
||||
path = REPORTES_DIR / "barras_errores.png"
|
||||
fig.savefig(path, dpi=150)
|
||||
plt.close(fig)
|
||||
return path
|
||||
|
||||
|
||||
def grafico_barras_ruc():
|
||||
data = _query("SELECT COUNT(*) AS c, ruc FROM fallas GROUP BY ruc ORDER BY c DESC LIMIT 10")
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data.reverse()
|
||||
labels = [r["ruc"] for r in data]
|
||||
values = [r["c"] for r in data]
|
||||
|
||||
fig, ax = plt.subplots(figsize=(10, 5))
|
||||
bars = ax.barh(labels, values, color="#e67e22", edgecolor="white", height=0.6)
|
||||
for b, v in zip(bars, values):
|
||||
ax.text(b.get_width() + 0.3, b.get_y() + b.get_height() / 2,
|
||||
str(v), va="center", fontsize=10, fontweight="bold")
|
||||
ax.set_xlabel("Cantidad de fallas")
|
||||
ax.set_title("Top 10 RUC con más fallas", fontsize=14, fontweight="bold")
|
||||
ax.margins(x=0.15)
|
||||
ax.spines[["top", "right"]].set_visible(False)
|
||||
fig.tight_layout()
|
||||
path = REPORTES_DIR / "barras_ruc.png"
|
||||
fig.savefig(path, dpi=150)
|
||||
plt.close(fig)
|
||||
return path
|
||||
|
||||
|
||||
def grafico_torta():
|
||||
data = _query("SELECT COUNT(*) AS c, error FROM fallas GROUP BY error ORDER BY c DESC")
|
||||
if not data:
|
||||
return None
|
||||
|
||||
labels = [r["error"] for r in data]
|
||||
values = [r["c"] for r in data]
|
||||
colors = [_color_error(l) for l in labels]
|
||||
|
||||
fig, ax = plt.subplots(figsize=(7, 7))
|
||||
wedges, texts, autotexts = ax.pie(
|
||||
values, labels=labels, autopct="%1.1f%%",
|
||||
colors=colors, startangle=90, textprops={"fontsize": 11}
|
||||
)
|
||||
for at in autotexts:
|
||||
at.set_fontweight("bold")
|
||||
ax.set_title("Proporción de fallas por tipo", fontsize=14, fontweight="bold")
|
||||
fig.tight_layout()
|
||||
path = REPORTES_DIR / "torta_errores.png"
|
||||
fig.savefig(path, dpi=150)
|
||||
plt.close(fig)
|
||||
return path
|
||||
|
||||
|
||||
def main():
|
||||
reporte_texto()
|
||||
|
||||
if not HAS_MPL:
|
||||
print("\n⚠️ matplotlib no instalado. Ejecuta: pip3 install matplotlib")
|
||||
return
|
||||
|
||||
paths = []
|
||||
for name, fn in [("barras_errores", grafico_barras_errores),
|
||||
("barras_ruc", grafico_barras_ruc),
|
||||
("torta_errores", grafico_torta)]:
|
||||
p = fn()
|
||||
if p:
|
||||
paths.append(p)
|
||||
|
||||
if paths:
|
||||
print(f"\n📁 Gráficos guardados en: {REPORTES_DIR}")
|
||||
for p in paths:
|
||||
print(f" {p.name}")
|
||||
print(f"\n💡 Abre con: open {REPORTES_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user