feat: Update testing documentation and introduce comprehensive report for IPSExtractor campaign. Add sections for executive summary, objectives, environment details, and methodology. Enhance README with compilation instructions and data sources for metrics analysis.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
\chapter{Resumen ejecutivo}
|
||||
\label{ch:resumen}
|
||||
|
||||
\section{Veredicto global}
|
||||
La campa\~na de pruebas del \textbf{13 de julio de 2026} sobre el stack IPSExtractor desplegado en el host \texttt{ubuntu} (perfil \texttt{shared8g}) concluye:
|
||||
|
||||
\begin{center}
|
||||
\large
|
||||
Stack \textbf{operativo y estable} para orquestaci\'on y control remoto del bot.\\[0.3em]
|
||||
\normalsize
|
||||
Carga ligera del gateway y heartbeats: \pass\ bajo contenci\'on moderada.\\[0.2em]
|
||||
Pipeline completo a \textbf{100\,000 facturas/mes} en este host: \fail\ (ver cap.~\ref{ch:capacidad}).
|
||||
\end{center}
|
||||
|
||||
\section{KPIs de la campa\~na}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrrl@{}}
|
||||
\toprule
|
||||
\textbf{Indicador} & \textbf{Valor} & \textbf{Objetivo} & \textbf{Estado} \\
|
||||
\midrule
|
||||
ST-GW-01 p95 & 40{,}85\,ms & $<$500\,ms & \pass \\
|
||||
ST-GW-01 fail rate & 0\,\% & $<$1\,\% & \pass \\
|
||||
ST-BOT-01 p95 (reintento) & 85{,}55\,ms & $<$800\,ms & \pass \\
|
||||
Unitarias agent bot & 19/19 & 100\,\% & \pass \\
|
||||
Unitarias API bot store & 7/7 & 100\,\% & \pass \\
|
||||
Unitarias count-api (pipeline) & 3/3 & 100\,\% & \pass \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{KPIs ejecutivos de la campa\~na.}
|
||||
\end{table}
|
||||
|
||||
\section{Respuesta directa: 100\,000 facturas/mes}
|
||||
\begin{description}
|
||||
\item[Host actual (8\,GiB + MLflow/n8n/MinIO/\ldots)] \fail\ para el \emph{pipeline completo} (conteo + extracci\'on + bot + limpieza).
|
||||
\item[Infra dedicada dimensionada] \pass\ \emph{condicional}: workers LLM, OCR con RAM $\geq$16--32\,GiB, $N$ agents Selenium, cuota API, sin zoo Docker competidor.
|
||||
\end{description}
|
||||
Detalle cuantitativo en el cap\'itulo~\ref{ch:capacidad}.
|
||||
|
||||
\section{Riesgo principal}
|
||||
Contenci\'on de plataforma: swap $\approx$2{,}1\,GiB y \texttt{mlflow-server} $\approx$1{,}84\,GiB provocaron un outlier de 23\,s en heartbeats (corrida 1). Tras aliviar carga, p95 cay\'o a 86\,ms. Los fallos de latencia deben correlacionarse siempre con \texttt{free -h} / \texttt{docker stats}.
|
||||
@@ -0,0 +1,24 @@
|
||||
\chapter{Objeto y alcance}
|
||||
\label{ch:alcance}
|
||||
|
||||
\section{Objeto}
|
||||
Documentar de forma profesional los \textbf{resultados} de pruebas de IPSExtractor (humo, unitarias, carga) y evaluar la viabilidad de procesar \textbf{100\,000 facturas/mes}.
|
||||
|
||||
\section{Alcance incluido}
|
||||
\begin{itemize}
|
||||
\item Stack Docker IPS: gateway, extraction, workers, cleaning, count-api, bot-descarga, PostgreSQL, Redis.
|
||||
\item Perfil de carga \texttt{shared8g} en host de staging compartido.
|
||||
\item Unitarias del agent (\texttt{bots/bot\_descarga}) y del store API (\texttt{services/bot\_descarga}).
|
||||
\item Modelo anal\'itico de capacidad por etapa del pipeline.
|
||||
\end{itemize}
|
||||
|
||||
\section{Fuera de alcance de esta campa\~na}
|
||||
\begin{itemize}
|
||||
\item ST-COUNT-01 / OCR masivo y ST-EXT bajo LLM real a escala.
|
||||
\item E2E Imaging/Selenium con VPN.
|
||||
\item ST-BOT-03 (events) --- requiere \texttt{DOWNLOAD\_JOB\_ID} de un job real (\neje).
|
||||
\item Procesar 100\,000 PDFs reales.
|
||||
\end{itemize}
|
||||
|
||||
\section{Definici\'on de factura}
|
||||
Una \textbf{factura} = un PDF de cuenta m\'edica evaluado por etapa (conteo OCR, extracci\'on LLM, descarga bot, limpieza). El veredicto 100k/mes se expresa por etapa y para el pipeline completo.
|
||||
@@ -0,0 +1,64 @@
|
||||
\chapter{Entorno bajo prueba}
|
||||
\label{ch:entorno}
|
||||
|
||||
\section{Hardware y sistema}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lp{8.5cm}@{}}
|
||||
\toprule
|
||||
\textbf{Campo} & \textbf{Valor} \\
|
||||
\midrule
|
||||
Hostname & \texttt{ubuntu} \\
|
||||
SO & Ubuntu 24.04.4 LTS \\
|
||||
CPU & Intel Core i5-7200U @ 2{,}50\,GHz (2C/4T) \\
|
||||
RAM & 7{,}6\,GiB (l\'imite Docker $\approx$ 7{,}64\,GiB) \\
|
||||
Disco & HDD 500\,GB + USB 1\,TB \\
|
||||
Docker & 29.1.3 \\
|
||||
Gateway & \texttt{http://127.0.0.1:8189} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Host de ejecuci\'on.}
|
||||
\end{table}
|
||||
|
||||
\section{Baseline de memoria (2026-07-13)}
|
||||
\begin{itemize}
|
||||
\item Usados $\approx$4{,}4\,GiB; disponibles $\approx$3{,}3\,GiB; \textbf{swap 2{,}1\,GiB}.
|
||||
\item IPS idle $\approx$230--250\,MiB total.
|
||||
\item \texttt{mlflow-server} $\approx$1{,}84\,GiB; n8n $\approx$291\,MiB; MinIO $\approx$136\,MiB.
|
||||
\end{itemize}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.95\textwidth,
|
||||
height=6.5cm,
|
||||
ylabel={RAM (MiB)},
|
||||
symbolic x coords={api,extraction,w1,w2,cleaning,bot,count,db,redis,mlflow,n8n,minio},
|
||||
xtick=data,
|
||||
x tick label style={rotate=45, anchor=east, font=\scriptsize},
|
||||
nodes near coords,
|
||||
nodes near coords style={font=\tiny, rotate=90, anchor=west},
|
||||
bar width=8pt,
|
||||
ymin=0,
|
||||
enlarge x limits=0.08,
|
||||
grid=major,
|
||||
legend style={at={(0.98,0.98)},anchor=north east,font=\scriptsize}
|
||||
]
|
||||
\addplot[fill=ipsblue!70] coordinates {
|
||||
(api,43) (extraction,30) (w1,29) (w2,32) (cleaning,51)
|
||||
(bot,17) (count,13) (db,26) (redis,8) (mlflow,1884) (n8n,291) (minio,136)
|
||||
};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Huella de memoria por contenedor (idle). MLflow domina el host.}
|
||||
\label{fig:memoria}
|
||||
\end{figure}
|
||||
|
||||
\section{Salud del stack}
|
||||
\begin{itemize}
|
||||
\item Contenedores IPS: \texttt{Up} / healthy ($\sim$17\,h).
|
||||
\item \texttt{/health/live}: ok, $\approx$11\,ms.
|
||||
\item \texttt{/health}: ok (extraction, cleaning, counting, bot\_descarga), $\approx$194\,ms.
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,30 @@
|
||||
\chapter{Metodolog\'ia}
|
||||
\label{ch:metodologia}
|
||||
|
||||
\section{Tipos de prueba}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Unitarias} --- pytest (agent y API bot).
|
||||
\item \textbf{Humo / funcional} --- curl y checks HTTP del gateway.
|
||||
\item \textbf{Carga} --- k6 perfil \texttt{shared8g}.
|
||||
\item \textbf{Capacidad} --- modelo anal\'itico (no carga de 100k PDFs).
|
||||
\end{enumerate}
|
||||
|
||||
\section{Herramientas}
|
||||
k6 (Grafana), pytest, Docker stats, curl, pdflatex/pgfplots.
|
||||
|
||||
\section{SLOs perfil \texttt{shared8g}}
|
||||
\begin{itemize}
|
||||
\item Gateway \texttt{/health/live}: p95 $<$500\,ms; fail $<$1\,\%.
|
||||
\item Heartbeats bot: p95 $<$800\,ms; checks $>$95\,\%.
|
||||
\item No usar \texttt{/health} agregado como SLO de latencia.
|
||||
\end{itemize}
|
||||
|
||||
\section{Comandos representativos}
|
||||
\begin{lstlisting}[language=bash]
|
||||
k6 run -e PROFILE=shared8g -e BASE_URL=http://127.0.0.1:8189 \
|
||||
k6_gateway_health.js
|
||||
k6 run -e PROFILE=shared8g -e BOT_API_TOKEN=... \
|
||||
k6_bot_heartbeats.js
|
||||
python -m pytest bots/bot_descarga/tests -q
|
||||
cd services/bot_descarga && python -m pytest tests -q
|
||||
\end{lstlisting}
|
||||
@@ -0,0 +1,57 @@
|
||||
\chapter{Resultados --- pruebas unitarias}
|
||||
\label{ch:unitarias}
|
||||
|
||||
\section{Scorecard}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}llrl@{}}
|
||||
\toprule
|
||||
\textbf{Suite} & \textbf{Resultado} & \textbf{Passed} & \textbf{Duraci\'on} \\
|
||||
\midrule
|
||||
\texttt{bots/bot\_descarga/tests} & \pass & 19/19 & 3{,}21\,s \\
|
||||
\texttt{services/bot\_descarga/tests} & \pass & 7/7 & 1{,}45\,s \\
|
||||
\texttt{go test} count-api (\texttt{internal/pipeline}) & \pass$^{*}$ & 3+8 nuevos & ver Timing \\
|
||||
Vitest desktop & \neje & --- & No ejecutado en esta sesi\'on \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Resultados unitarios. $^{*}$Base 3 PASS confirmados en Ubuntu; ampliaci\'on features/clustering (+8) y Timing sobre 3 PDFs reales requieren \texttt{go test} en host con Tesseract.}
|
||||
\end{table}
|
||||
|
||||
\section{Cobertura agent}
|
||||
Incluye excel reader, PDF validator, file manager, cancel, browser fatal, MinIO keys, GUI settings (19 casos).
|
||||
|
||||
\section{Cobertura API store}
|
||||
Incluye helpers MinIO, register/heartbeat/offline, claim at\'omico, cancel, events, filtros por job padre (7 casos).
|
||||
|
||||
\section{Cobertura count-api}
|
||||
Ejecutado en Ubuntu (deps Tesseract/Leptonica):
|
||||
tests originales de features (3) \pass.
|
||||
Ampliaci\'on: \texttt{features\_extra\_test.go} + \texttt{clustering\_test.go} (+8 casos).
|
||||
Medici\'on de documentos: \texttt{processor\_timing\_test.go} sobre
|
||||
\texttt{docs/plan\_pruebas/data/*.pdf} (3 archivos); ver cap.~\ref{ch:count-latency}.
|
||||
\texttt{PDFResult} ahora incluye \texttt{render\_ms}/\texttt{ocr\_ms}/\texttt{cluster\_ms}/\texttt{infer\_ms}/\texttt{total\_ms}.
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
xbar,
|
||||
width=0.85\textwidth,
|
||||
height=4.2cm,
|
||||
xlabel={Casos},
|
||||
symbolic y coords={agent,api-store,count-api,desktop},
|
||||
ytick=data,
|
||||
xmin=0, xmax=22,
|
||||
nodes near coords,
|
||||
bar width=10pt,
|
||||
legend style={at={(0.98,0.35)},anchor=east,font=\small}
|
||||
]
|
||||
\addplot[fill=passgreen!70] coordinates {(19,agent) (7,api-store) (3,count-api) (0,desktop)};
|
||||
\addlegendentry{PASS}
|
||||
\addplot[fill=ipsgrey!40] coordinates {(0,agent) (0,api-store) (0,count-api) (1,desktop)};
|
||||
\addlegendentry{N/E}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Unitarias ejecutadas vs pendientes.}
|
||||
\label{fig:unitarias}
|
||||
\end{figure}
|
||||
@@ -0,0 +1,23 @@
|
||||
\chapter{Resultados --- humo y funcional}
|
||||
\label{ch:humo}
|
||||
|
||||
\section{Humo gateway}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}llrl@{}}
|
||||
\toprule
|
||||
\textbf{Check} & \textbf{Resultado} & \textbf{Latencia} & \textbf{Notas} \\
|
||||
\midrule
|
||||
\texttt{GET /health/live} & \pass & 11\,ms & Liveness \\
|
||||
\texttt{GET /health} & \pass & 194\,ms & 4 servicios ok \\
|
||||
Contenedores IPS healthy & \pass & --- & 17\,h uptime \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Humo HTTP.}
|
||||
\end{table}
|
||||
|
||||
\section{Funcional E2E-07 (worker offline $\rightarrow$ 409)}
|
||||
\neje\ en esta campa\~na (no se envi\'o \texttt{POST /bot-descarga/jobs} contra worker offline). Queda como follow-up de 2 minutos en staging.
|
||||
|
||||
\section{Script preparado}
|
||||
Se a\~nadi\'o \texttt{docs/plan\_pruebas/scripts/k6\_smoke\_suite.js} para repeticiones automatizadas de live/health/config.
|
||||
@@ -0,0 +1,110 @@
|
||||
\chapter{Resultados --- pruebas de carga}
|
||||
\label{ch:carga}
|
||||
|
||||
\section{Tabla consolidada}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrrrrrl@{}}
|
||||
\toprule
|
||||
\textbf{Caso} & \textbf{Reqs} & \textbf{Fail\%} & \textbf{avg} & \textbf{p95} & \textbf{max} & \textbf{Res.} \\
|
||||
\midrule
|
||||
ST-GW-01 & 6884 & 0 & 12{,}6\,ms & 40{,}9\,ms & 125\,ms & \pass \\
|
||||
ST-BOT-01 \#1 & 80 & 0 & 511\,ms & 2{,}28\,s & 23{,}1\,s & \parcial \\
|
||||
ST-BOT-01 \#2 & 80 & 0 & 57\,ms & 85{,}6\,ms & 98\,ms & \pass \\
|
||||
ST-BOT-03 & --- & --- & --- & --- & --- & \neje \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{M\'etricas de carga k6 (perfil shared8g).}
|
||||
\label{tab:carga}
|
||||
\end{table}
|
||||
|
||||
\section{ST-GW-01}
|
||||
\begin{itemize}
|
||||
\item Checks 13768/13768 (100\,\%); throughput $\approx$57\,req/s.
|
||||
\item Thresholds p95$<$500\,ms y fail$<$1\,\% en verde.
|
||||
\end{itemize}
|
||||
|
||||
\section{ST-BOT-01 --- comparativa}
|
||||
Corrida 1: funcional OK, latencia FAIL (contenci\'on host).\\
|
||||
Corrida 2: funcional OK, latencia PASS (host m\'as libre).
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.95\textwidth,
|
||||
height=7cm,
|
||||
ylabel={Latencia (ms)},
|
||||
symbolic x coords={GW-p95,BOT1-p95,BOT2-p95,GW-max,BOT1-max,BOT2-max},
|
||||
xtick=data,
|
||||
x tick label style={rotate=30, anchor=east, font=\small},
|
||||
nodes near coords,
|
||||
nodes near coords style={font=\scriptsize},
|
||||
bar width=12pt,
|
||||
ymin=0,
|
||||
enlarge x limits=0.12,
|
||||
grid=major,
|
||||
legend entries={medici\'on, umbral GW 500, umbral BOT 800},
|
||||
legend style={at={(0.02,0.98)},anchor=north west,font=\scriptsize}
|
||||
]
|
||||
\addplot[fill=ipsblue!75] coordinates {
|
||||
(GW-p95,40.85) (BOT1-p95,2280) (BOT2-p95,85.55)
|
||||
(GW-max,125.37) (BOT1-max,23120) (BOT2-max,98.31)
|
||||
};
|
||||
\addplot[red, sharp plot, update limits=false, forget plot] coordinates {(GW-p95,500) (BOT2-max,500)};
|
||||
\addplot[orange, sharp plot, update limits=false] coordinates {(BOT1-p95,800) (BOT2-p95,800)};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{p95 y max por caso. BOT1-max (23\,s) fuera de escala visual relativa --- ver tabla.}
|
||||
\label{fig:latencias}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=0.9\textwidth,
|
||||
height=6cm,
|
||||
ylabel={ms},
|
||||
symbolic x coords={avg,med,p95,max},
|
||||
xtick=data,
|
||||
legend style={at={(0.02,0.98)},anchor=north west},
|
||||
bar width=10pt,
|
||||
ymin=0, ymax=600,
|
||||
enlarge x limits=0.15,
|
||||
grid=major,
|
||||
title={ST-BOT-01: corrida 1 vs 2 (max de \#1 truncado en eje)}
|
||||
]
|
||||
\addplot[fill=failred!60] coordinates {(avg,511) (med,57) (p95,500) (max,600)};
|
||||
\addlegendentry{\#1 (p95 real 2280; max 23120)}
|
||||
\addplot[fill=passgreen!70] coordinates {(avg,57) (med,56) (p95,86) (max,98)};
|
||||
\addlegendentry{\#2 PASS}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Before/after heartbeats (eje limitado; valores exactos en tabla~\ref{tab:carga}).}
|
||||
\label{fig:bot-before-after}
|
||||
\end{figure}
|
||||
|
||||
\section{Throughput gateway}
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
width=0.7\textwidth,
|
||||
height=5cm,
|
||||
ybar,
|
||||
symbolic x coords={ST-GW-01},
|
||||
xtick=data,
|
||||
ylabel={req/s},
|
||||
nodes near coords,
|
||||
ymin=0,
|
||||
bar width=30pt
|
||||
]
|
||||
\addplot[fill=ipsblue] coordinates {(ST-GW-01,57.33)};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Throughput observado en health liveness (no equivale a PDFs/h).}
|
||||
\label{fig:throughput}
|
||||
\end{figure}
|
||||
@@ -0,0 +1,104 @@
|
||||
\chapter{Capacidad: 100\,000 facturas al mes}
|
||||
\label{ch:capacidad}
|
||||
|
||||
\section{Traducci\'on del objetivo}
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrr@{}}
|
||||
\toprule
|
||||
\textbf{Escenario operativo} & \textbf{Facturas/d\'ia} & \textbf{Facturas/hora} \\
|
||||
\midrule
|
||||
100\,000 / mes, 24$\times$7 & $\approx$3\,333 & $\approx$\textbf{139} \\
|
||||
100\,000 / mes, 8\,h $\times$ 22\,d & $\approx$4\,545 & $\approx$\textbf{570} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Throughput m\'inimo para cumplir 100k/mes.}
|
||||
\end{table}
|
||||
|
||||
\section{Modelo por etapa}
|
||||
Supuestos: host compartido medido el 2026-07-13; estimaciones \textbf{conservadoras} (no bench OCR/LLM masivo). Detalle en \texttt{scripts/estimate\_capacity.md}.
|
||||
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrrll@{}}
|
||||
\toprule
|
||||
\textbf{Etapa} & \textbf{Host act.} & \textbf{Dedicado} & \textbf{vs 139/h} & \textbf{vs 570/h} \\
|
||||
\midrule
|
||||
Gateway (orquestaci\'on) & $\gg$10\,000 & $\gg$20\,000 & OK & OK \\
|
||||
count-api OCR & $\sim$40 & $\sim$400 & RIESGO & NO \\
|
||||
extraction LLM & $\sim$100 & $\sim$800 & \'AMBAR & NO$^{*}$ \\
|
||||
bot Selenium (1 agent) & $\sim$60 & $\sim$500$^{\dagger}$ & RIESGO & NO \\
|
||||
cleaning & $\sim$800 & $\sim$3\,000 & OK & \'AMBAR \\
|
||||
\textbf{Pipeline completo} & $\sim$35 & $\sim$450 & \fail & \'AMBAR \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Facturas/hora estimadas. $^{*}$Sujeto a cuota LLM. $^{\dagger}$Con $N$ agents en paralelo.}
|
||||
\label{tab:capacidad}
|
||||
\end{table}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar,
|
||||
width=\textwidth,
|
||||
height=7.5cm,
|
||||
ylabel={Facturas / hora (estimadas)},
|
||||
symbolic x coords={Gateway,OCR,LLM,Bot,Cleaning,Pipeline},
|
||||
xtick=data,
|
||||
x tick label style={font=\small},
|
||||
legend style={at={(0.02,0.98)},anchor=north west,font=\small},
|
||||
bar width=8pt,
|
||||
ymin=0, ymax=1200,
|
||||
enlarge x limits=0.08,
|
||||
grid=major,
|
||||
title={Capacidad estimada vs objetivos 139/h y 570/h}
|
||||
]
|
||||
\addplot[fill=ipsblue!70] coordinates {
|
||||
(Gateway,1000) (OCR,40) (LLM,100) (Bot,60) (Cleaning,800) (Pipeline,35)
|
||||
};
|
||||
\addlegendentry{Host actual (Gateway truncado)}
|
||||
\addplot[fill=passgreen!55] coordinates {
|
||||
(Gateway,1000) (OCR,400) (LLM,800) (Bot,500) (Cleaning,1000) (Pipeline,450)
|
||||
};
|
||||
\addlegendentry{Dedicado (estim.)}
|
||||
\addplot[failred, ultra thick, sharp plot, update limits=false] coordinates {
|
||||
(Gateway,139) (Pipeline,139)
|
||||
};
|
||||
\addlegendentry{Objetivo 139/h}
|
||||
\addplot[warnorange, ultra thick, sharp plot, update limits=false] coordinates {
|
||||
(Gateway,570) (Pipeline,570)
|
||||
};
|
||||
\addlegendentry{Objetivo 570/h}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Brecha de capacidad. Gateway real $\gg$1000/h; se trunca para legibilidad.}
|
||||
\label{fig:capacidad}
|
||||
\end{figure}
|
||||
|
||||
\section{Interpretaci\'on}
|
||||
\begin{enumerate}
|
||||
\item El \textbf{gateway} no es el cuello de botella (evidencia ST-GW-01).
|
||||
\item En el \textbf{host actual}, OCR + Selenium + LLM compiten por CPU/RAM/swap con MLflow: el pipeline completo queda $\ll$139/h.
|
||||
\item Con infra \textbf{dedicada} (sin zoo, m\'as RAM, workers, $N$ PCs bot, cuota LLM), acercarse a 139/h (24$\times$7) es plausible; 570/h exige dimensionamiento agresivo y prueba de carga espec\'ifica.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Veredicto}
|
||||
\begin{center}
|
||||
\fbox{\parbox{0.92\textwidth}{\centering
|
||||
\large \textbf{Se pueden procesar +100\,000 facturas/mes?}\\[0.4em]
|
||||
\normalsize
|
||||
En el servidor actual compartido: \fail.\\[0.2em]
|
||||
En arquitectura dedicada y dimensionada: \pass\ condicional (cuota LLM, OCR, agents).\\[0.3em]
|
||||
\footnotesize Esta campa\~na no ejecut\'o 100k PDFs; el veredicto es modelo + evidencia de carga ligera.
|
||||
}}
|
||||
\end{center}
|
||||
|
||||
\section{Requisitos m\'inimos sugeridos para 100k/mes}
|
||||
\begin{itemize}
|
||||
\item Host(s) dedicados IPS: RAM $\geq$16--32\,GiB; SSD; sin MLflow/Airflow en el mismo nodo.
|
||||
\item count-api: pool OCR dimensionado; l\'imite de memoria realista.
|
||||
\item extraction: r\'eplicas worker + concurrencia acorde a RPM del LLM; presupuesto de coste.
|
||||
\item bot: varios agents PC en paralelo hacia Imaging/MinIO.
|
||||
\item Observabilidad: colas ARQ, p95 por etapa, alertas de swap.
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,41 @@
|
||||
\chapter{An\'alisis y hallazgos}
|
||||
\label{ch:analisis}
|
||||
|
||||
\section{Hallazgos}
|
||||
\begin{enumerate}
|
||||
\item \textbf{H1 --- Contenci\'on de plataforma:} ST-BOT-01 \#1 (p95 2{,}28\,s, max 23\,s) vs \#2 (p95 86\,ms) demuestra sensibilidad a swap/MLflow, no a un bug funcional del heartbeat (100\,\% HTTP 200 en ambas).
|
||||
\item \textbf{H2 --- Gateway saludable:} 0\,\% errores y p95 41\,ms bajo 25 VUs en liveness.
|
||||
\item \textbf{H3 --- Unitarias s\'olidas:} 29 casos verdes (19 agent + 7 API store + 3 count-api pipeline).
|
||||
\item \textbf{H4 --- Capacidad 100k:} imposible en host compartido actual; viable solo con escalado dedicado.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Scorecard global}
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
ybar stacked,
|
||||
width=0.9\textwidth,
|
||||
height=5.5cm,
|
||||
ylabel={Cantidad de casos},
|
||||
symbolic x coords={Unitaria,Humo,Carga,Funcional,Capacidad},
|
||||
xtick=data,
|
||||
legend style={at={(0.5,-0.22)},anchor=north,legend columns=3,font=\small},
|
||||
bar width=18pt,
|
||||
ymin=0, ymax=8,
|
||||
enlarge x limits=0.12
|
||||
]
|
||||
\addplot[fill=passgreen!70] coordinates {(Unitaria,3) (Humo,3) (Carga,2) (Funcional,0) (Capacidad,0)};
|
||||
\addlegendentry{PASS}
|
||||
\addplot[fill=warnorange!70] coordinates {(Unitaria,0) (Humo,0) (Carga,1) (Funcional,0) (Capacidad,0)};
|
||||
\addlegendentry{PARCIAL}
|
||||
\addplot[fill=ipsgrey!50] coordinates {(Unitaria,1) (Humo,0) (Carga,1) (Funcional,1) (Capacidad,1)};
|
||||
\addlegendentry{N/E}
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\caption{Distribuci\'on de resultados por tipo de prueba.}
|
||||
\label{fig:scorecard}
|
||||
\end{figure}
|
||||
|
||||
\section{Limitaciones}
|
||||
No se midieron tiempos reales OCR/LLM por PDF en esta campa\~na; las cifras de capacidad son estimaciones documentadas para decisi\'on de arquitectura, no un compromiso de SLA.
|
||||
@@ -0,0 +1,26 @@
|
||||
\chapter{Conclusiones y recomendaciones}
|
||||
\label{ch:conclusiones}
|
||||
|
||||
\section{Conclusiones}
|
||||
\begin{enumerate}
|
||||
\item IPSExtractor en staging responde correctamente a humo y carga ligera de control (gateway + heartbeats).
|
||||
\item La calidad de latencia depende cr\'iticamente de no saturar el host compartido.
|
||||
\item \textbf{100\,000 facturas/mes no son alcanzables} en el laptop Ubuntu actual con el zoo Docker observado.
|
||||
\item Con infraestructura dedicada y dimensionada, el objetivo es \textbf{condicional\-mente viable}; requiere prueba de carga espec\'ifica por etapa (OCR, LLM, bot).
|
||||
\end{enumerate}
|
||||
|
||||
\section{Recomendaciones}
|
||||
\begin{enumerate}
|
||||
\item Separar IPS de MLflow/Airflow/n8n en el nodo de producci\'on/staging de rendimiento.
|
||||
\item Ejecutar ST-COUNT-01 y bench LLM solo con $\geq$3\,GiB libres y sin swap activo.
|
||||
\item Completar E2E-07 y ST-BOT-03; archivar JSON de k6 en \texttt{informes/}.
|
||||
\item Dimensionar agents bot ($N$) antes de comprometer vol\'umenes mensuales de descarga.
|
||||
\item Incorporar CI: pytest agent + store en cada PR.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Pr\'oxima campa\~na sugerida}
|
||||
\begin{itemize}
|
||||
\item Ventana con MLflow detenido.
|
||||
\item Bench: 50--200 PDFs reales por etapa (OCR, LLM) midiendo p50/p95 y coste.
|
||||
\item Actualizar gr\'afica de capacidad con datos medidos (reemplazar estimaciones).
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,41 @@
|
||||
\chapter{Anexos}
|
||||
\label{ch:anexos}
|
||||
|
||||
\section{Datos fuente}
|
||||
Archivos: \texttt{informe/data/metricas.csv}, \texttt{capacidad.csv}, \texttt{memoria.csv}.\\
|
||||
Markdown de campa\~na: \texttt{docs/plan\_pruebas/informes/2026-07-13-shared8g.md}.
|
||||
|
||||
\section{Extracto ST-GW-01}
|
||||
\begin{lstlisting}
|
||||
checks 100% (13768)
|
||||
http_req_failed 0%
|
||||
p(95)=40.85ms avg=12.55ms reqs=6884 (~57/s)
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Extracto ST-BOT-01 \#2}
|
||||
\begin{lstlisting}
|
||||
checks 100% (75)
|
||||
p(95)=85.55ms max=98.31ms
|
||||
thresholds OK (p95<800)
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Unitarias}
|
||||
\begin{lstlisting}
|
||||
bots/bot_descarga/tests: 19 passed in 3.21s
|
||||
services/bot_descarga/tests: 7 passed in 1.45s
|
||||
count-api internal/pipeline: 3 passed (+8 features/clustering nuevos)
|
||||
Timing: go test -run Timing -v (3 PDFs en docs/plan_pruebas/data/)
|
||||
HTTP: bash docs/plan_pruebas/scripts/measure_count_latency.sh
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Scripts}
|
||||
\texttt{k6\_gateway\_health.js}, \texttt{k6\_bot\_heartbeats.js}, \texttt{k6\_bot\_events.js},
|
||||
\texttt{k6\_count\_batches.js}, \texttt{k6\_smoke\_suite.js}, \texttt{estimate\_capacity.md}.
|
||||
|
||||
\section{Reproducibilidad}
|
||||
\begin{enumerate}
|
||||
\item Registrar \texttt{hostnamectl}, \texttt{free -h}, \texttt{docker stats --no-stream}.
|
||||
\item Verificar \texttt{/health/live} y \texttt{/health}.
|
||||
\item Ejecutar k6 con \texttt{PROFILE=shared8g}.
|
||||
\item Adjuntar salida completa al directorio \texttt{informes/}.
|
||||
\end{enumerate}
|
||||
@@ -0,0 +1,49 @@
|
||||
\chapter{Resultados --- latencia count-api (PDFs reales)}
|
||||
\label{ch:count-latency}
|
||||
|
||||
\section{Fixtures}
|
||||
Tres PDFs en \texttt{docs/plan\_pruebas/data/}:
|
||||
\begin{itemize}
|
||||
\item \texttt{500000284335937.pdf}
|
||||
\item \texttt{500000284335940.pdf}
|
||||
\item \texttt{500000284335954.pdf}
|
||||
\end{itemize}
|
||||
|
||||
\section{Unitarias pipeline ampliadas}
|
||||
Adem\'as de los 3 tests originales de features:
|
||||
\begin{itemize}
|
||||
\item Header keyword, service code, empty rows, no table pattern.
|
||||
\item Clustering: dos bandas Y, misma banda, vac\'io, ruido \texttt{minSamples}.
|
||||
\end{itemize}
|
||||
Total esperado en \texttt{go test ./internal/pipeline/ -run 'TestExtract|TestBuild|TestFeature|TestCluster'}: \textbf{11} casos.
|
||||
|
||||
\section{Timings por etapa}
|
||||
El procesador expone \texttt{render\_ms}, \texttt{ocr\_ms}, \texttt{cluster\_ms}, \texttt{infer\_ms}, \texttt{total\_ms} en \texttt{PDFResult}.
|
||||
|
||||
\begin{lstlisting}[language=bash,caption={Medici\'on en Ubuntu}]
|
||||
cd services/services-count-facturas-medicas-serving
|
||||
go test ./internal/pipeline/ -run Timing -v
|
||||
|
||||
# HTTP v\'ia gateway:
|
||||
bash docs/plan_pruebas/scripts/measure_count_latency.sh
|
||||
# o PowerShell:
|
||||
# .\docs\plan_pruebas\scripts\measure_count_latency.ps1
|
||||
\end{lstlisting}
|
||||
|
||||
Los resultados de la corrida Timing/HTTP deben pegarse en
|
||||
\texttt{docs/plan\_pruebas/informes/count\_latency\_YYYYMMDD.csv} y actualizar esta tabla.
|
||||
|
||||
\begin{table}[ht]
|
||||
\centering
|
||||
\begin{tabular}{@{}lrrrrrl@{}}
|
||||
\toprule
|
||||
\textbf{PDF} & \textbf{render} & \textbf{ocr} & \textbf{cluster} & \textbf{infer} & \textbf{total} & \textbf{status} \\
|
||||
\midrule
|
||||
500000284335937 & --- & --- & --- & --- & --- & pendiente Ubuntu \\
|
||||
500000284335940 & --- & --- & --- & --- & --- & pendiente Ubuntu \\
|
||||
500000284335954 & --- & --- & --- & --- & --- & pendiente Ubuntu \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Latencia por etapa (ms). Completar tras \texttt{-run Timing} en el host con Tesseract.}
|
||||
\label{tab:count-timing}
|
||||
\end{table}
|
||||
Reference in New Issue
Block a user