feat(calibration): complete step 1 data inspection with data quality v1
This commit is contained in:
266
src/web/ui/v2/static/js/pages/calibration_data.js
Normal file
266
src/web/ui/v2/static/js/pages/calibration_data.js
Normal 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);
|
||||
});
|
||||
0
src/web/ui/v2/static/js/pages/dashboard.js
Normal file
0
src/web/ui/v2/static/js/pages/dashboard.js
Normal file
0
src/web/ui/v2/static/js/pages/positions.js
Normal file
0
src/web/ui/v2/static/js/pages/positions.js
Normal file
0
src/web/ui/v2/static/js/pages/trades.js
Normal file
0
src/web/ui/v2/static/js/pages/trades.js
Normal file
Reference in New Issue
Block a user