feat(calibration): complete step 1 data inspection with data quality v1

This commit is contained in:
DaM
2026-02-08 22:29:09 +01:00
parent f85c522f22
commit 4d769af8bf
89 changed files with 5014 additions and 203 deletions

View File

@@ -0,0 +1,266 @@
// src/web/ui/v2/static/js/pages/calibration_data.js
console.log(
"[calibration_data] script loaded ✅",
new Date().toISOString()
);
let currentDownloadJobId = null;
let downloadPollTimer = null;
// =================================================
// INSPECT DATA (DB)
// =================================================
async function inspectCalibrationData() {
console.log("[calibration_data] inspectCalibrationData() START ✅");
const symbol = document.getElementById("symbol")?.value;
const timeframe = document.getElementById("timeframe")?.value;
const start_date = document.getElementById("start_date")?.value || null;
const end_date = document.getElementById("end_date")?.value || null;
const resultEl = document.getElementById("inspect-output");
const payload = { symbol, timeframe };
if (start_date) payload.start_date = start_date;
if (end_date) payload.end_date = end_date;
console.log("[calibration_data] inspect payload:", payload);
if (resultEl) {
resultEl.textContent = "⏳ Inspeccionando datos en DB...";
}
try {
const res = await fetch("/api/v2/calibration/data/inspect", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await res.json();
console.log("[calibration_data] inspect response:", data);
if (resultEl) {
resultEl.textContent = JSON.stringify(data, null, 2);
}
renderDataSummary(data);
} catch (err) {
console.error("[calibration_data] inspect FAILED", err);
if (resultEl) {
resultEl.textContent = "❌ Error inspeccionando datos";
}
}
}
// =================================================
// START DOWNLOAD JOB
// =================================================
async function startDownloadJob() {
console.log("[calibration_data] startDownloadJob()");
const symbol = document.getElementById("symbol")?.value;
const timeframe = document.getElementById("timeframe")?.value;
const start_date = document.getElementById("start_date")?.value || null;
const end_date = document.getElementById("end_date")?.value || null;
const payload = { symbol, timeframe };
if (start_date) payload.start_date = start_date;
if (end_date) payload.end_date = end_date;
console.log("[calibration_data] download payload:", payload);
try {
const res = await fetch("/api/v2/calibration/data/download/job", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await res.json();
console.log("[calibration_data] job created:", data);
currentDownloadJobId = data.job_id;
showDownloadProgress();
pollDownloadStatus();
} catch (err) {
console.error("[calibration_data] startDownloadJob FAILED", err);
alert("Error iniciando la descarga");
}
}
// =================================================
// POLLING STATUS
// =================================================
async function pollDownloadStatus() {
if (!currentDownloadJobId) return;
try {
const res = await fetch(
`/api/v2/calibration/data/download/job/${currentDownloadJobId}`
);
const data = await res.json();
console.log("[calibration_data] job status:", data);
renderDownloadProgress(data);
if (
data.status === "done" ||
data.status === "failed" ||
data.status === "cancelled"
) {
stopPolling();
}
} catch (err) {
console.error("[calibration_data] polling FAILED", err);
stopPolling();
}
downloadPollTimer = setTimeout(pollDownloadStatus, 1500);
}
function stopPolling() {
if (downloadPollTimer) {
clearTimeout(downloadPollTimer);
downloadPollTimer = null;
}
}
// =================================================
// CANCEL JOB
// =================================================
async function cancelDownloadJob() {
if (!currentDownloadJobId) return;
try {
await fetch(
`/api/v2/calibration/data/download/job/${currentDownloadJobId}/cancel`,
{ method: "POST" }
);
} catch (err) {
console.error("[calibration_data] cancel FAILED", err);
}
}
// =================================================
// RENDER UI
// =================================================
function showDownloadProgress() {
document.getElementById("download-progress-card")?.classList.remove("d-none");
document.getElementById("download-progress-bar").style.width = "0%";
document.getElementById("download-progress-text").textContent =
"Iniciando descarga…";
}
function renderDownloadProgress(job) {
const bar = document.getElementById("download-progress-bar");
const text = document.getElementById("download-progress-text");
if (!bar || !text) return;
bar.style.width = `${job.progress || 0}%`;
bar.textContent = `${job.progress || 0}%`;
text.textContent = job.message || job.status;
}
// =================================================
// DATA SUMMARY (YA EXISTENTE)
// =================================================
function renderDataSummary(data) {
const card = document.getElementById("data-summary-card");
if (!card) return;
card.classList.remove("d-none");
document.getElementById("first-ts").textContent =
data.first_available ?? "";
document.getElementById("last-ts").textContent =
data.last_available ?? "";
document.getElementById("candles-count").textContent =
data.candles_count ?? 0;
const badge = document.getElementById("data-status-badge");
const warning = document.getElementById("data-warning");
const ok = document.getElementById("data-ok");
const logEl = document.getElementById("data-log");
badge.className = "badge me-2";
warning?.classList.add("d-none");
ok?.classList.add("d-none");
if (!data.valid) {
badge.classList.add("bg-warning");
badge.textContent = "SIN DATOS";
warning?.classList.remove("d-none");
if (logEl) {
logEl.textContent =
`❌ No hay datos para ${data.symbol} @ ${data.timeframe}`;
}
return;
}
badge.classList.add("bg-success");
badge.textContent = "DATOS DISPONIBLES";
ok?.classList.remove("d-none");
if (logEl) {
logEl.textContent =
`✅ Datos disponibles para ${data.symbol} @ ${data.timeframe}\n` +
`Rango: ${data.first_available}${data.last_available}`;
}
// -----------------------------
// DATA QUALITY
// -----------------------------
const dqCard = document.getElementById("data-quality-card");
if (dqCard && data.data_quality) {
dqCard.classList.remove("d-none");
const dq = data.data_quality;
document.getElementById("dq-status").textContent = dq.status.toUpperCase();
document.getElementById("dq-message").textContent = dq.message;
document.getElementById("dq-continuity").textContent =
dq.checks.continuity;
document.getElementById("dq-gaps").textContent =
`${dq.checks.gaps.count} (max ${dq.checks.gaps.max_gap ?? ""})`;
document.getElementById("dq-coverage").textContent =
`${(dq.checks.coverage.ratio * 100).toFixed(2)}%`;
document.getElementById("dq-volume").textContent =
dq.checks.volume;
}
}
// =================================================
// INIT
// =================================================
document.addEventListener("DOMContentLoaded", () => {
console.log("[calibration_data] DOMContentLoaded ✅");
document
.getElementById("inspect-data-btn")
?.addEventListener("click", inspectCalibrationData);
document
.getElementById("download-data-btn")
?.addEventListener("click", startDownloadJob);
document
.getElementById("cancel-download-btn")
?.addEventListener("click", cancelDownloadJob);
});

View File