diff --git a/flows.json b/flows.json
index 10c9720..178e7f8 100644
--- a/flows.json
+++ b/flows.json
@@ -59,7 +59,7 @@
{
"id": "3afa9de4406d30d8",
"type": "ui-base",
- "name": "My Dashboard",
+ "name": "Mapa de demanda",
"path": "/dashboard",
"appIcon": "",
"includeClientData": true,
@@ -68,13 +68,13 @@
"ui-control"
],
"showPathInSidebar": false,
- "headerContent": "page",
- "navigationStyle": "default",
- "titleBarStyle": "default",
+ "headerContent": "dashboard",
+ "navigationStyle": "temporary",
+ "titleBarStyle": "fixed",
"showReconnectNotification": true,
"notificationDisplayTime": 1,
"showDisconnectNotification": true,
- "allowInstall": false
+ "allowInstall": true
},
{
"id": "c81bf3ad6297e603",
@@ -98,9 +98,9 @@
{
"id": "b332967d63ddbdfe",
"type": "ui-page",
- "name": "Page 1",
+ "name": "Estimaciones",
"ui": "3afa9de4406d30d8",
- "path": "/page1",
+ "path": "/estimaciones",
"icon": "home",
"layout": "flex",
"theme": "c81bf3ad6297e603",
@@ -128,16 +128,30 @@
],
"order": 1,
"className": "",
- "visible": "true",
- "disabled": "false"
+ "visible": true,
+ "disabled": false
},
{
"id": "f8afeee042444067",
"type": "ui-group",
- "name": "Visualizador",
+ "name": "Mapa",
"page": "b332967d63ddbdfe",
- "width": "8",
- "height": "8",
+ "width": 12,
+ "height": "9",
+ "order": 2,
+ "showTitle": true,
+ "className": "",
+ "visible": "true",
+ "disabled": "false",
+ "groupType": "default"
+ },
+ {
+ "id": "bb66042c3e7c9cb3",
+ "type": "ui-group",
+ "name": "Configuración",
+ "page": "b332967d63ddbdfe",
+ "width": 3,
+ "height": "7",
"order": 1,
"showTitle": true,
"className": "",
@@ -789,18 +803,189 @@
"y": 340,
"wires": []
},
+ {
+ "id": "b2c5c01b68565205",
+ "type": "function",
+ "z": "f511dd2230a153e7",
+ "name": "Con colores por riesgo y demanda",
+ "func": "// NODE-RED FUNCTION: Visualizar H3 con predicciones y grúas\nconst h3 = h3Js; // Asegúrate de que h3Js está cargado globalmente\n\n// Validar librerías\nif (typeof h3 === 'undefined') {\n node.error(\"H3 library not available\");\n return null;\n}\n\nconst payload = msg.payload;\n\n// Extraer datos del payload de tu API\nconst week = payload.week || 0;\nconst dow = payload.dow || 0;\nconst hour = payload.hour || 0;\nconst recommendedH3 = payload.recommended_h3 || [];\nconst statistics = payload.statistics || [];\nconst coverage = payload.coverage || {};\nconst predictionData = payload.prediction_data || []; // Si tienes datos de predicción por celda\n\n// Si no hay datos de predicción, podemos generarlos a partir de statistics\nlet allH3Data = [];\nif (predictionData.length > 0) {\n // Si la API ya devuelve datos por celda\n allH3Data = predictionData;\n} else if (statistics.length > 0) {\n // Crear datos a partir de statistics (solo celdas con grúas)\n allH3Data = statistics.map(stat => ({\n h3: stat.h3,\n expected_demand: stat.expected_demand,\n nulo_probability: stat.nulo_probability,\n risk: stat.risk,\n coverage_radius: stat.coverage_radius,\n is_grua: true\n }));\n}\n\nconst features = [];\n\n// --- COLORES SEGÚN RIESGO ---\nfunction getRiskColor(risk, maxRisk) {\n if (maxRisk <= 0) return \"#00ff00\";\n\n const pct = (risk / maxRisk) * 100;\n\n if (pct <= 25) return \"#00ff00\"; // verde (bajo riesgo)\n if (pct <= 50) return \"#ffff00\"; // amarillo\n if (pct <= 75) return \"#ffa500\"; // naranja\n return \"#ff0000\"; // rojo (alto riesgo)\n}\n\n// --- COLORES SEGÚN DEMANDA ---\nfunction getDemandColor(demand) {\n if (demand < 2) return \"#00ff00\"; // verde (baja demanda)\n if (demand < 5) return \"#ffff00\"; // amarillo (media demanda)\n return \"#ff0000\"; // rojo (alta demanda)\n}\n\n// --- COLORES PARA GRÚAS ---\nfunction getGruaColor(index) {\n const colors = [\"#0000ff\", \"#ff00ff\", \"#00ffff\", \"#800080\"];\n return colors[index % colors.length];\n}\n\n// Calcular máximos para escalado de colores\nconst maxRisk = allH3Data.length > 0 ? Math.max(...allH3Data.map(i => parseFloat(i.risk || 0))) : 1;\nconst maxDemand = allH3Data.length > 0 ? Math.max(...allH3Data.map(i => parseFloat(i.expected_demand || 0))) : 1;\n\n// 1. Primero, pintar todas las celdas H3 con predicciones\nallH3Data.forEach((item, idx) => {\n const h3Index = item.h3;\n const risk = parseFloat(item.risk || 0);\n const demand = parseFloat(item.expected_demand || 0);\n const nuloProb = parseFloat(item.nulo_probability || 0);\n const isGrua = item.is_grua || false;\n const coverageRadius = item.coverage_radius || 0;\n\n try {\n const hexBoundary = h3.cellToBoundary(h3Index, true);\n const center = h3.cellToLatLng(h3Index);\n\n // Determinar color basado en riesgo\n const fillColor = getRiskColor(risk, maxRisk);\n\n // Opacidad diferente para grúas\n const fillOpacity = isGrua ? 0.8 : 0.4;\n\n // Ancho de borde diferente para grúas\n const strokeWidth = isGrua ? 3 : 1;\n const strokeColor = isGrua ? \"#000000\" : \"#333333\";\n\n // Texto para tooltip\n let tooltip = `H3: ${h3Index}`;\n tooltip += `\\\\nDemanda: ${demand.toFixed(2)}`;\n tooltip += `\\\\nProb. Nulo: ${(nuloProb * 100).toFixed(1)}%`;\n tooltip += `\\\\nRiesgo: ${risk.toFixed(3)}`;\n if (isGrua) {\n tooltip += `\\\\n🚨 GRÚA (Radio: ${coverageRadius})`;\n }\n\n const feature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"h3\": h3Index,\n \"risk\": risk,\n \"demand\": demand,\n \"nulo_probability\": nuloProb,\n \"coverage_radius\": coverageRadius,\n \"is_grua\": isGrua,\n \"name\": tooltip,\n \"lat\": center[0],\n \"lon\": center[1],\n\n // ESTILO\n \"fill\": fillColor,\n \"fill-opacity\": fillOpacity,\n \"stroke\": strokeColor,\n \"stroke-width\": strokeWidth,\n \"stroke-opacity\": 0.9,\n\n // Metadata adicional\n \"marker-type\": isGrua ? \"grua\" : \"prediction\"\n },\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [hexBoundary]\n }\n };\n\n features.push(feature);\n\n } catch (error) {\n node.warn(`Error procesando H3 index ${h3Index}: ${error}`);\n }\n});\n\n// 2. Añadir áreas de cobertura de las grúas (círculos)\nstatistics.forEach((grua, gruaIndex) => {\n try {\n const h3Index = grua.h3;\n const radius = grua.coverage_radius || 12;\n const cellsCovered = grua.cells_covered || 0;\n\n // Obtener celdas en el radio de cobertura\n const coveredCells = h3.gridDisk(h3Index, radius);\n\n // Crear polígono para el área de cobertura (opcional, puede ser pesado)\n // En su lugar, podemos crear un círculo aproximado\n\n const center = h3.cellToLatLng(h3Index);\n\n // Crear feature para el área de cobertura\n const coverageFeature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"name\": `Área cobertura Grúa ${gruaIndex + 1}`,\n \"grua_h3\": h3Index,\n \"radius\": radius,\n \"cells_covered\": cellsCovered,\n \"risk_covered\": grua.risk_covered || 0,\n \"lat\": center[0],\n \"lon\": center[1],\n\n // ESTILO (semi-transparente)\n \"fill\": getGruaColor(gruaIndex),\n \"fill-opacity\": 0.2,\n \"stroke\": getGruaColor(gruaIndex),\n \"stroke-width\": 2,\n \"stroke-opacity\": 0.5,\n \"marker-type\": \"coverage_area\"\n },\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [center[1], center[0]]\n }\n };\n\n features.push(coverageFeature);\n\n // Añadir marcador para la grúa\n const gruaMarker = {\n \"type\": \"Feature\",\n \"properties\": {\n \"name\": `🚨 Grúa ${gruaIndex + 1}`,\n \"description\": `Demanda: ${grua.expected_demand?.toFixed(2) || 0}\\\\nRadio: ${radius}\\\\nCeldas cubiertas: ${cellsCovered}`,\n \"grua_index\": gruaIndex + 1,\n \"lat\": center[0],\n \"lon\": center[1],\n\n // ESTILO para marcador\n \"marker-color\": getGruaColor(gruaIndex),\n \"marker-size\": \"large\",\n \"marker-symbol\": \"warehouse\",\n \"marker-type\": \"grua_marker\"\n },\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [center[1], center[0]]\n }\n };\n\n features.push(gruaMarker);\n\n } catch (error) {\n node.warn(`Error procesando área de cobertura para grúa ${grua.h3}: ${error}`);\n }\n});\n\n// 3. Añadir información general como feature\nconst infoFeature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"name\": \"Información del Análisis\",\n \"description\": `Semana ${week}, Día ${dow}, Hora ${hour}\\\\n` +\n `Cobertura: ${coverage.coverage_percentage?.toFixed(2) || 0}%\\\\n` +\n `Riesgo cubierto: ${coverage.risk_coverage_percentage?.toFixed(2) || 0}%\\\\n` +\n `Grúas recomendadas: ${recommendedH3.length}`,\n \"marker-type\": \"info\",\n \"marker-color\": \"#333333\",\n \"marker-symbol\": \"info\"\n },\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [-3.7038, 40.4168] // Madrid como referencia\n }\n};\n\nfeatures.push(infoFeature);\n\n// FeatureCollection final\nmsg.payload = {\n \"type\": \"FeatureCollection\",\n \"features\": features,\n \"metadata\": {\n \"week\": week,\n \"dow\": dow,\n \"hour\": hour,\n \"total_gruas\": recommendedH3.length,\n \"coverage_percentage\": coverage.coverage_percentage || 0,\n \"risk_coverage_percentage\": coverage.risk_coverage_percentage || 0,\n \"total_cells\": coverage.total_cells || 0,\n \"cells_covered\": coverage.cells_covered || 0\n },\n \"Layer\": \"H3 Predictions & Gruas\"\n};\n\nreturn msg;",
+ "outputs": 1,
+ "timeout": 0,
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [
+ {
+ "var": "h3Js",
+ "module": "h3-js"
+ }
+ ],
+ "x": 340,
+ "y": 960,
+ "wires": [
+ [
+ "ac4ac0ee965cef89",
+ "572948d3a25d485f"
+ ]
+ ]
+ },
+ {
+ "id": "ac4ac0ee965cef89",
+ "type": "worldmap",
+ "z": "f511dd2230a153e7",
+ "name": "",
+ "lat": "",
+ "lon": "",
+ "zoom": "",
+ "layer": "",
+ "cluster": "",
+ "maxage": "",
+ "usermenu": "show",
+ "layers": "show",
+ "panit": "false",
+ "panlock": "false",
+ "zoomlock": "false",
+ "hiderightclick": "false",
+ "coords": "false",
+ "showgrid": "false",
+ "showruler": "false",
+ "allowFileDrop": "false",
+ "path": "/worldmap",
+ "overlist": "DR,CO,RA,DN",
+ "maplist": "OSMG,OSMC,EsriC,EsriS,UKOS",
+ "mapname": "",
+ "mapurl": "",
+ "mapopt": "",
+ "mapwms": false,
+ "x": 640,
+ "y": 960,
+ "wires": []
+ },
+ {
+ "id": "32e2494f462cc6f4",
+ "type": "link in",
+ "z": "f511dd2230a153e7",
+ "name": "link in 1",
+ "links": [
+ "b63c955f07e230f1",
+ "c1552a91a92da199"
+ ],
+ "x": 125,
+ "y": 1080,
+ "wires": [
+ [
+ "572948d3a25d485f",
+ "4bbf565e7701cb10"
+ ]
+ ]
+ },
+ {
+ "id": "2fc1745ca3803fcb",
+ "type": "function",
+ "z": "f511dd2230a153e7",
+ "name": "Solo ver las gruas",
+ "func": "// VERSIÓN MÍNIMA - Solo muestra las grúas\nconst h3 = h3Js;\nconst payload = msg.payload;\nconst recommendedH3 = payload.recommended_h3 || [];\n\nconst features = recommendedH3.map((h3Index, index) => {\n try {\n const center = h3.cellToLatLng(h3Index);\n return {\n \"type\": \"Feature\",\n \"properties\": {\n \"name\": `Grúa ${index + 1}: ${h3Index}`,\n \"h3\": h3Index,\n },\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [center[1], center[0]]\n }\n };\n } catch (e) {\n return null;\n }\n}).filter(f => f !== null);\n\nmsg.payload = {\n \"type\": \"FeatureCollection\",\n \"features\": features,\n \"icon\": \"truck\",\n \"layer\": \"H3 Gruas\"\n};\n\nreturn msg;",
+ "outputs": 1,
+ "timeout": 0,
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [
+ {
+ "var": "h3Js",
+ "module": "h3-js"
+ }
+ ],
+ "x": 290,
+ "y": 920,
+ "wires": [
+ [
+ "ac4ac0ee965cef89",
+ "572948d3a25d485f"
+ ]
+ ]
+ },
+ {
+ "id": "572948d3a25d485f",
+ "type": "debug",
+ "z": "f511dd2230a153e7",
+ "name": "debug 17",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "true",
+ "targetType": "full",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 640,
+ "y": 1080,
+ "wires": []
+ },
+ {
+ "id": "19a26466059a3db1",
+ "type": "function",
+ "z": "f511dd2230a153e7",
+ "name": "Con colores con probabilidad",
+ "func": "// NODE-RED FUNCTION: Visualizar H3 con predicciones y grúas\nconst h3 = h3Js; // Asegúrate de que h3Js está cargado globalmente\n\n// Validar librerías\nif (typeof h3 === 'undefined') {\n node.error(\"H3 library not available\");\n return null;\n}\n\nconst payload = msg.payload;\n\n// Extraer datos del payload de tu API\nconst week = payload.week || 0;\nconst dow = payload.dow || 0;\nconst hour = payload.hour || 0;\nconst recommendedH3 = payload.recommended_h3 || [];\nconst statistics = payload.statistics || [];\nconst coverage = payload.coverage || {};\nconst predictionData = payload.prediction_data || []; // Si tienes datos de predicción por celda\n\n// Si no hay datos de predicción, podemos generarlos a partir de statistics\nlet allH3Data = [];\nif (predictionData.length > 0) {\n // Si la API ya devuelve datos por celda\n allH3Data = predictionData;\n} else if (statistics.length > 0) {\n // Crear datos a partir de statistics (solo celdas con grúas)\n allH3Data = statistics.map(stat => ({\n h3: stat.h3,\n expected_demand: stat.expected_demand,\n nulo_probability: stat.nulo_probability,\n risk: stat.risk,\n coverage_radius: stat.coverage_radius,\n is_grua: true\n }));\n}\n\nconst features = [];\n\n// --- COLORES SEGÚN PROBABILIDAD DE NULO ---\nfunction getNuloColor(nuloProbability) {\n // Convertir a porcentaje (si viene como decimal)\n const pct = nuloProbability <= 1 ? nuloProbability * 100 : nuloProbability;\n\n if (pct < 25) return \"#00ff00\"; // verde (baja probabilidad de nulo)\n if (pct < 50) return \"#ffff00\"; // amarillo\n if (pct < 75) return \"#ffa500\"; // naranja\n return \"#ff0000\"; // rojo (alta probabilidad de nulo)\n}\n\n// --- COLORES PARA GRÚAS ---\nfunction getGruaColor(index) {\n const colors = [\"#0000ff\", \"#ff00ff\", \"#00ffff\", \"#800080\"];\n return colors[index % colors.length];\n}\n\n// 1. Primero, pintar todas las celdas H3 con predicciones\nallH3Data.forEach((item, idx) => {\n const h3Index = item.h3;\n const risk = parseFloat(item.risk || 0);\n const demand = parseFloat(item.expected_demand || 0);\n const nuloProb = parseFloat(item.nulo_probability || 0);\n const isGrua = item.is_grua || false;\n const coverageRadius = item.coverage_radius || 0;\n\n try {\n const hexBoundary = h3.cellToBoundary(h3Index, true);\n const center = h3.cellToLatLng(h3Index);\n\n // Determinar color basado en probabilidad de nulo\n const fillColor = getNuloColor(nuloProb);\n\n // Opacidad diferente para grúas\n const fillOpacity = isGrua ? 0.8 : 0.4;\n\n // Ancho de borde diferente para grúas\n const strokeWidth = isGrua ? 3 : 1;\n const strokeColor = isGrua ? \"#000000\" : \"#333333\";\n\n // Texto para tooltip\n let tooltip = `H3: ${h3Index}`;\n tooltip += `\\\\nDemanda: ${demand.toFixed(2)}`;\n tooltip += `\\\\nProb. Nulo: ${(nuloProb * 100).toFixed(1)}%`;\n tooltip += `\\\\nRiesgo: ${risk.toFixed(3)}`;\n if (isGrua) {\n tooltip += `\\\\n🚨 GRÚA (Radio: ${coverageRadius})`;\n }\n\n const feature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"h3\": h3Index,\n \"risk\": risk,\n \"demand\": demand,\n \"nulo_probability\": nuloProb,\n \"coverage_radius\": coverageRadius,\n \"is_grua\": isGrua,\n \"name\": tooltip,\n \"lat\": center[0],\n \"lon\": center[1],\n\n // ESTILO\n \"fill\": fillColor,\n \"fill-opacity\": fillOpacity,\n \"stroke\": strokeColor,\n \"stroke-width\": strokeWidth,\n \"stroke-opacity\": 0.9,\n\n // Metadata adicional\n \"marker-type\": isGrua ? \"grua\" : \"prediction\",\n // Añadir nivel de color para referencia\n \"nulo_level\": nuloProb <= 0.25 ? \"bajo\" :\n nuloProb <= 0.5 ? \"medio\" :\n nuloProb <= 0.75 ? \"alto\" : \"muy_alto\"\n },\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [hexBoundary]\n }\n };\n\n features.push(feature);\n\n } catch (error) {\n node.warn(`Error procesando H3 index ${h3Index}: ${error}`);\n }\n});\n\n// 2. Añadir áreas de cobertura de las grúas (círculos)\nstatistics.forEach((grua, gruaIndex) => {\n try {\n const h3Index = grua.h3;\n const radius = grua.coverage_radius || 12;\n const cellsCovered = grua.cells_covered || 0;\n\n // Obtener celdas en el radio de cobertura\n const coveredCells = h3.gridDisk(h3Index, radius);\n\n // Crear polígono para el área de cobertura (opcional, puede ser pesado)\n // En su lugar, podemos crear un círculo aproximado\n\n const center = h3.cellToLatLng(h3Index);\n\n // Crear feature para el área de cobertura\n const coverageFeature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"name\": `Área cobertura Grúa ${gruaIndex + 1}`,\n \"grua_h3\": h3Index,\n \"radius\": radius,\n \"cells_covered\": cellsCovered,\n \"risk_covered\": grua.risk_covered || 0,\n \"lat\": center[0],\n \"lon\": center[1],\n\n // ESTILO (semi-transparente)\n \"fill\": getGruaColor(gruaIndex),\n \"fill-opacity\": 0.2,\n \"stroke\": getGruaColor(gruaIndex),\n \"stroke-width\": 2,\n \"stroke-opacity\": 0.5,\n \"marker-type\": \"coverage_area\"\n },\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [center[1], center[0]]\n }\n };\n\n features.push(coverageFeature);\n\n // Añadir marcador para la grúa\n const gruaMarker = {\n \"type\": \"Feature\",\n \"properties\": {\n \"name\": `🚨 Grúa ${gruaIndex + 1}`,\n \"description\": `Demanda: ${grua.expected_demand?.toFixed(2) || 0}\\\\nProb. Nulo: ${(grua.nulo_probability * 100).toFixed(1)}%\\\\nRadio: ${radius}\\\\nCeldas cubiertas: ${cellsCovered}`,\n \"grua_index\": gruaIndex + 1,\n \"lat\": center[0],\n \"lon\": center[1],\n\n // ESTILO para marcador\n \"marker-color\": getGruaColor(gruaIndex),\n \"marker-size\": \"large\",\n \"marker-symbol\": \"warehouse\",\n \"marker-type\": \"grua_marker\"\n },\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [center[1], center[0]]\n }\n };\n\n features.push(gruaMarker);\n\n } catch (error) {\n node.warn(`Error procesando área de cobertura para grúa ${grua.h3}: ${error}`);\n }\n});\n\n// 3. Añadir información general como feature\nconst infoFeature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"name\": \"Información del Análisis\",\n \"description\": `Semana ${week}, Día ${dow}, Hora ${hour}\\\\n` +\n `Cobertura: ${coverage.coverage_percentage?.toFixed(2) || 0}%\\\\n` +\n `Riesgo cubierto: ${coverage.risk_coverage_percentage?.toFixed(2) || 0}%\\\\n` +\n `Grúas recomendadas: ${recommendedH3.length}`,\n \"marker-type\": \"info\",\n \"marker-color\": \"#333333\",\n \"marker-symbol\": \"info\"\n },\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [-3.7038, 40.4168] // Madrid como referencia\n }\n};\n\nfeatures.push(infoFeature);\n\n// FeatureCollection final\nmsg.payload = {\n \"type\": \"FeatureCollection\",\n \"features\": features,\n \"metadata\": {\n \"week\": week,\n \"dow\": dow,\n \"hour\": hour,\n \"total_gruas\": recommendedH3.length,\n \"coverage_percentage\": coverage.coverage_percentage || 0,\n \"risk_coverage_percentage\": coverage.risk_coverage_percentage || 0,\n \"total_cells\": coverage.total_cells || 0,\n \"cells_covered\": coverage.cells_covered || 0,\n \"color_schema\": \"nulo_probability\" // Indicar qué esquema de color se usa\n },\n \"Layer\": \"H3 Predictions & Gruas\"\n};\n\nreturn msg;",
+ "outputs": 1,
+ "timeout": 0,
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [
+ {
+ "var": "h3Js",
+ "module": "h3-js"
+ }
+ ],
+ "x": 320,
+ "y": 1000,
+ "wires": [
+ [
+ "ac4ac0ee965cef89",
+ "572948d3a25d485f"
+ ]
+ ]
+ },
+ {
+ "id": "4bbf565e7701cb10",
+ "type": "function",
+ "z": "f511dd2230a153e7",
+ "name": "Con colores con probabilidad",
+ "func": "// NODE-RED FUNCTION: Visualizar H3 con predicciones y grúas\nconst h3 = h3Js; // Asegúrate de que h3Js está cargado globalmente\n\n// Validar librerías\nif (typeof h3 === 'undefined') {\n node.error(\"H3 library not available\");\n return null;\n}\n\nconst payload = msg.payload;\n\n// Extraer datos del payload de tu API\nconst week = payload.semana || 0;\nconst dow = payload.dia_semana || 0;\nconst hour = payload.hora || 0;\nconst recommendedH3 = payload.h3_recomendados || [];\nconst statistics = payload.estadisticas || [];\nconst coverage = payload.cobertura || {};\nconst predictionData = payload.datos_prediccion || []; // Si tienes datos de predicción por celda\n\n// Si no hay datos de predicción, podemos generarlos a partir de statistics\nlet allH3Data = [];\nif (predictionData.length > 0) {\n // Si la API ya devuelve datos por celda\n allH3Data = predictionData;\n} else if (statistics.length > 0) {\n // Crear datos a partir de statistics (solo celdas con grúas)\n allH3Data = statistics.map(stat => ({\n h3: stat.h3,\n expected_demand: stat.expected_demand,\n nulo_probability: stat.nulo_probability,\n risk: stat.risk,\n coverage_radius: stat.coverage_radius,\n is_grua: true\n }));\n}\n\nconst features = [];\n\n// --- FUNCIÓN PARA GRADIENTE DE COLOR SEGÚN PROBABILIDAD DE NULO ---\nfunction getNuloGradientColor(nuloProbability) {\n // Asegurar que el valor esté entre 0 y 1\n let pct = Math.max(0, Math.min(1, nuloProbability));\n\n // Gradiente de verde (0) a rojo (1)\n const red = Math.floor(255 * pct); // Aumenta con la probabilidad\n const green = Math.floor(255 * (1 - pct)); // Disminuye con la probabilidad\n const blue = 0; // Sin componente azul\n\n return `rgb(${red}, ${green}, ${blue})`;\n}\n\n// --- FUNCIÓN PARA CREAR CÍRCULO GEOJSON ---\nfunction createCircle(center, radiusInKm, points = 32) {\n const coords = [];\n const [lat, lng] = center;\n\n // Radio de la Tierra en kilómetros\n const earthRadius = 6371;\n\n // Convertir radio de kilómetros a radianes\n const radiusRad = radiusInKm / earthRadius;\n\n for (let i = 0; i < points; i++) {\n const angle = (i * 2 * Math.PI) / points;\n\n // Coordenadas del punto en el círculo\n const circleLat = Math.asin(\n Math.sin(lat * Math.PI / 180) * Math.cos(radiusRad) +\n Math.cos(lat * Math.PI / 180) * Math.sin(radiusRad) * Math.cos(angle)\n ) * 180 / Math.PI;\n\n const circleLng = lng + Math.atan2(\n Math.sin(angle) * Math.sin(radiusRad) * Math.cos(lat * Math.PI / 180),\n Math.cos(radiusRad) - Math.sin(lat * Math.PI / 180) * Math.sin(circleLat * Math.PI / 180)\n ) * 180 / Math.PI;\n\n coords.push([circleLng, circleLat]);\n }\n\n // Cerrar el polígono\n coords.push(coords[0]);\n\n return coords;\n}\n\n// --- COLORES PARA GRÚAS ---\nfunction getGruaColor(index) {\n const colors = [\"#0000ff\", \"#ff00ff\", \"#00ffff\", \"#800080\", \"#008000\", \"#800000\"];\n return colors[index % colors.length];\n}\n\n// 1. Primero, pintar todas las celdas H3 con predicciones\nallH3Data.forEach((item, idx) => {\n const h3Index = item.h3;\n const risk = parseFloat(item.risk || 0);\n const demand = parseFloat(item.expected_demand || 0);\n const nuloProb = parseFloat(item.nulo_probability || 0);\n const isGrua = item.is_grua || false;\n const coverageRadius = item.coverage_radius || 0;\n\n try {\n const hexBoundary = h3.cellToBoundary(h3Index, true);\n const center = h3.cellToLatLng(h3Index);\n\n // Determinar color con gradiente basado en probabilidad de nulo\n const fillColor = getNuloGradientColor(nuloProb);\n\n // Opacidad variable según probabilidad de nulo (más opaco para alta probabilidad)\n const fillOpacity = isGrua ? 0.8 : (0.3 + (nuloProb * 0.5));\n\n // Ancho de borde diferente para grúas\n const strokeWidth = isGrua ? 3 : 1;\n const strokeColor = isGrua ? \"#000000\" :\n nuloProb > 0.5 ? \"#222222\" : \"#555555\";\n\n // Texto para tooltip\n let tooltip = `H3: ${h3Index}`;\n tooltip += ` Demanda: ${demand.toFixed(2)}`;\n tooltip += ` Prob. Nulo: ${(nuloProb * 100).toFixed(1)}%`;\n tooltip += ` Riesgo: ${risk.toFixed(3)}`;\n if (isGrua) {\n tooltip += `\\\\n🚨 GRÚA (Radio: ${coverageRadius} celdas)`;\n }\n\n const feature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"h3\": h3Index,\n \"risk\": risk,\n \"demand\": demand,\n \"nulo_probability\": nuloProb,\n \"nulo_percentage\": (nuloProb * 100).toFixed(1),\n \"coverage_radius\": coverageRadius,\n \"is_grua\": isGrua,\n \"name\": tooltip,\n \"lat\": center[0],\n \"lon\": center[1],\n\n // ESTILO\n \"fill\": fillColor,\n \"fill-opacity\": fillOpacity,\n \"stroke\": strokeColor,\n \"stroke-width\": strokeWidth,\n \"stroke-opacity\": 0.9,\n\n // Metadata adicional para leyenda\n \"marker-type\": isGrua ? \"grua\" : \"prediction\",\n \"color_intensity\": nuloProb,\n \"color_category\": nuloProb < 0.25 ? \"bajo\" :\n nuloProb < 0.5 ? \"medio-bajo\" :\n nuloProb < 0.75 ? \"medio-alto\" : \"alto\"\n },\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [hexBoundary]\n }\n };\n\n features.push(feature);\n\n } catch (error) {\n node.warn(`Error procesando H3 index ${h3Index}: ${error}`);\n }\n});\n\n// 2. Añadir áreas de cobertura de las grúas como círculos\nstatistics.forEach((grua, gruaIndex) => {\n try {\n const h3Index = grua.h3;\n const radius = grua.coverage_radius || 12;\n const cellsCovered = grua.cells_covered || 0;\n const nuloProb = parseFloat(grua.nulo_probability || 0);\n\n const center = h3.cellToLatLng(h3Index);\n\n // Calcular radio aproximado en kilómetros\n // Nota: El radio en H3 es en número de celdas, no en km\n // Para convertir a km, necesitamos la resolución de las celdas\n // Asumimos resolución 9 (aprox 0.1 km² por celda)\n const cellAreaKm2 = 0.2; // Área aproximada de celda H3 res 9 en km²\n const cellDiameterKm = Math.sqrt(cellAreaKm2) * 2; // Diámetro aproximado en km\n const radiusKm = radius * cellDiameterKm;\n\n // Crear círculo para el área de cobertura\n const circleCoords = createCircle(center, radiusKm, 64);\n\n // Color para esta grúa\n const gruaColor = getGruaColor(gruaIndex);\n\n // Crear feature para el área de cobertura (círculo)\n const coverageFeature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"name\": `Área cobertura Grúa ${gruaIndex + 1}`,\n \"description\": `Radio: ${radius}
celdas (≈${radiusKm.toFixed(1)} km)
` +\n `Celdas cubiertas: ${cellsCovered}
` +\n `Prob. Nulo: ${(nuloProb * 100).toFixed(1)}%`,\n \"grua_h3\": h3Index,\n \"grua_index\": gruaIndex + 1,\n \"radius_cells\": radius,\n \"radius_km\": radiusKm.toFixed(1),\n \"cells_covered\": cellsCovered,\n \"risk_covered\": grua.risk_covered || 0,\n \"lat\": center[0],\n \"lon\": center[1],\n \"grua_color\": gruaColor,\n\n // ESTILO (semi-transparente)\n \"fill\": gruaColor,\n \"fill-opacity\": 0.15,\n \"stroke\": gruaColor,\n \"stroke-width\": 3,\n \"stroke-opacity\": 0.6,\n \"stroke-dasharray\": \"5,5\", // Línea punteada para mejor visibilidad\n \"marker-type\": \"coverage_area\"\n },\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [circleCoords]\n }\n };\n\n features.push(coverageFeature);\n\n // Añadir marcador para la grúa en el centro\n const gruaMarker = {\n \"type\": \"Feature\",\n \"properties\": {\n \"name\": `🚨 Grúa ${gruaIndex + 1}`,\n \"description\": `
Prob. Nulo: ${(nuloProb * 100).toFixed(1)}%
` +\n `Demanda: ${grua.expected_demand?.toFixed(2) || 0}
` +\n `Radio: ${radius} celdas (≈${radiusKm.toFixed(1)} km)
` +\n `Celdas cubiertas: ${cellsCovered}`,\n \"grua_index\": gruaIndex + 1,\n \"grua_color\": gruaColor,\n \"lat\": center[0],\n \"lon\": center[1],\n\n // ESTILO para marcador\n \"marker-color\": gruaColor,\n \"marker-size\": \"large\",\n \"marker-symbol\": \"truck\",\n \"marker-type\": \"grua_center\"\n },\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [center[1], center[0]]\n }\n };\n\n features.push(gruaMarker);\n\n } catch (error) {\n node.warn(`Error procesando área de cobertura para grúa ${grua.h3}: ${error}`);\n }\n});\n\n// 3. Añadir información general como feature\nconst infoFeature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"name\": \"Información del Análisis\",\n \"description\": `Semana: ${week} Día: ${dow} Hora: ${hour}
` +\n `Cobertura: ${coverage.coverage_percentage?.toFixed(2) || 0}%
` +\n `Riesgo cubierto: ${coverage.risk_coverage_percentage?.toFixed(2) || 0}%
` +\n `Grúas recomendadas: ${recommendedH3.length}
` +\n `🎨 Colores: Gradiente por probabilidad de nulo
` +\n `⭕ Círculos: Áreas de cobertura de grúas`,\n \"marker-type\": \"info\",\n \"marker-color\": \"#333333\",\n \"marker-symbol\": \"info\"\n },\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [-3.7038, 40.4168] // Madrid como referencia\n }\n};\n\nfeatures.push(infoFeature);\n\n// FeatureCollection final\nmsg.payload = {\n \"type\": \"FeatureCollection\",\n \"features\": features,\n \"metadata\": {\n \"week\": week,\n \"dow\": dow,\n \"hour\": hour,\n \"total_gruas\": recommendedH3.length,\n \"coverage_percentage\": coverage.coverage_percentage || 0,\n \"risk_coverage_percentage\": coverage.risk_coverage_percentage || 0,\n \"total_cells\": coverage.total_cells || 0,\n \"cells_covered\": coverage.cells_covered || 0,\n \"color_schema\": \"nulo_probability_gradient\",\n \"gradient_min\": \"#00ff00\", // Verde\n \"gradient_max\": \"#ff0000\", // Rojo\n \"color_explanation\": \"Verde (0% nulo) → Rojo (100% nulo)\"\n },\n \"layer\": \"H3\"\n};\n\nreturn msg;",
+ "outputs": 1,
+ "timeout": 0,
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [
+ {
+ "var": "h3Js",
+ "module": "h3-js"
+ }
+ ],
+ "x": 320,
+ "y": 1040,
+ "wires": [
+ [
+ "572948d3a25d485f",
+ "ac4ac0ee965cef89"
+ ]
+ ]
+ },
{
"id": "c7c7d9aedc600352",
"type": "postgresql",
"z": "230815bb0628a63e",
"name": "Generamos la vista básica de H3 con 8",
- "query": "-- 1.1 Vista básica con h3 (nivel 8)\nCREATE OR REPLACE VIEW vw_servicios_h3 AS\nSELECT\n id,\n id_servicio,\n fecha_entrada,\n fecha_servicio,\n codigo_grua,\n tipo_servicio,\n geom,\n h3_lat_lng_to_cell(point(ST_Y(geom), ST_X(geom)), 8) AS h3,\n EXTRACT(HOUR FROM fecha_servicio AT TIME ZONE 'UTC')::int AS hour,\n EXTRACT(DOW FROM fecha_servicio AT TIME ZONE 'UTC')::int AS dow, -- 0 = Sunday\n CASE WHEN tipo_servicio = 'Requerimiento nulo' THEN 1 ELSE 0 END AS is_nulo\nFROM servicios_geo;",
+ "query": "-- 1.1 Vista básica con h3 (nivel 8)\nCREATE OR REPLACE VIEW vw_servicios_h3 AS\nSELECT\n id,\n id_servicio,\n fecha_entrada,\n fecha_servicio,\n codigo_grua,\n tipo_servicio,\n geom,\n h3_lat_lng_to_cell(point(ST_Y(geom), ST_X(geom)), 8) AS h3,\n EXTRACT(HOUR FROM fecha_servicio AT TIME ZONE 'UTC')::int AS hour,\n EXTRACT(DOW FROM fecha_servicio AT TIME ZONE 'UTC')::int AS dow, -- 0 = Domingo\n EXTRACT(WEEK FROM fecha_servicio AT TIME ZONE 'UTC')::int AS week,\n CASE WHEN tipo_servicio = 'Requerimiento nulo' THEN 1 ELSE 0 END AS is_nulo\nFROM servicios_geo;",
"postgreSQLConfig": "748b5921246ec468",
"split": false,
"rowsPerMsg": 1,
"outputs": 1,
"x": 360,
- "y": 60,
+ "y": 120,
"wires": [
[
"eab6c7a9df1e6c8c"
@@ -829,7 +1014,7 @@
"payload": "",
"payloadType": "date",
"x": 110,
- "y": 60,
+ "y": 120,
"wires": [
[
"c7c7d9aedc600352"
@@ -850,21 +1035,21 @@
"statusVal": "",
"statusType": "auto",
"x": 940,
- "y": 60,
+ "y": 120,
"wires": []
},
{
"id": "a1c78ddc8ef1f1fd",
"type": "postgresql",
"z": "230815bb0628a63e",
- "name": "Generamos el nivel de demanda por hora y día de la semana",
+ "name": "Generamos el nivel de demanda por hora y día de la semana modelo SQL",
"query": "-- Conteo por (dia de la semana y hora)\nCREATE OR REPLACE VIEW vw_total_by_hour_dow AS\nSELECT\n dow,\n hour,\n COUNT(*) AS total_hour_dow\nFROM vw_servicios_h3\nGROUP BY dow, hour\nORDER BY dow, hour;\n\n--- Calcular valores para día de la semana y hora, asignamos un demand_level\nCREATE OR REPLACE VIEW vw_hour_levels_dow AS\nWITH counts AS (\n SELECT dow, hour, total_hour_dow\n FROM vw_total_by_hour_dow\n),\npercentiles_per_dow AS (\n SELECT\n dow,\n percentile_cont(0.3333) WITHIN GROUP (ORDER BY total_hour_dow) AS q1,\n percentile_cont(0.6666) WITHIN GROUP (ORDER BY total_hour_dow) AS q2\n FROM counts\n GROUP BY dow\n)\nSELECT\n c.dow,\n c.hour,\n c.total_hour_dow,\n CASE\n WHEN c.total_hour_dow <= p.q1 THEN 'baja'\n WHEN c.total_hour_dow <= p.q2 THEN 'media'\n ELSE 'alta'\n END AS demand_level\nFROM counts c\nJOIN percentiles_per_dow p ON p.dow = c.dow\nORDER BY c.dow, c.hour;",
"postgreSQLConfig": "748b5921246ec468",
"split": false,
"rowsPerMsg": 1,
"outputs": 1,
- "x": 420,
- "y": 120,
+ "x": 460,
+ "y": 180,
"wires": [
[
"e9272483eeab8c19"
@@ -885,7 +1070,7 @@
"statusVal": "",
"statusType": "auto",
"x": 940,
- "y": 120,
+ "y": 180,
"wires": []
},
{
@@ -910,7 +1095,7 @@
"payload": "",
"payloadType": "date",
"x": 110,
- "y": 120,
+ "y": 180,
"wires": [
[
"a1c78ddc8ef1f1fd"
@@ -928,7 +1113,7 @@
"rowsPerMsg": 1,
"outputs": 1,
"x": 470,
- "y": 180,
+ "y": 240,
"wires": [
[
"ae7888f46928631e"
@@ -940,13 +1125,13 @@
"type": "postgresql",
"z": "230815bb0628a63e",
"name": "Generamos la vista de demanda_h3_hour",
- "query": "--- Generamos la vista de la demanda h3 por hora\nCREATE OR REPLACE VIEW demanda_h3_hour AS\nSELECT\n s.h3,\n s.hour,\n s.dow,\n hl.demand_level,\n COUNT(*)::int AS total_events,\n SUM(s.is_nulo)::int AS total_nulos,\n CASE WHEN COUNT(*)=0 THEN 0.0 ELSE SUM(s.is_nulo)::double precision / COUNT(*) END AS nulo_rate\nFROM vw_servicios_labeled s\nLEFT JOIN vw_hour_levels_dow hl\n ON hl.dow = s.dow\n AND hl.hour = s.hour\nGROUP BY s.h3, s.hour, s.dow, hl.demand_level;",
+ "query": "--- Generamos la vista de la demanda h3 por hora modelo sql\nCREATE OR REPLACE VIEW demanda_h3_hour AS\nSELECT\n s.h3,\n s.hour,\n s.dow,\n hl.demand_level,\n COUNT(*)::int AS total_events,\n SUM(s.is_nulo)::int AS total_nulos,\n CASE WHEN COUNT(*)=0 THEN 0.0 ELSE SUM(s.is_nulo)::double precision / COUNT(*) END AS nulo_rate\nFROM vw_servicios_labeled s\nLEFT JOIN vw_hour_levels_dow hl\n ON hl.dow = s.dow\n AND hl.hour = s.hour\nGROUP BY s.h3, s.hour, s.dow, hl.demand_level;",
"postgreSQLConfig": "748b5921246ec468",
"split": false,
"rowsPerMsg": 1,
"outputs": 1,
"x": 360,
- "y": 240,
+ "y": 300,
"wires": [
[
"b7999a625ea2a4f7"
@@ -964,7 +1149,7 @@
"rowsPerMsg": 1,
"outputs": 1,
"x": 280,
- "y": 300,
+ "y": 360,
"wires": [
[
"f11aaa2122049f7e"
@@ -993,7 +1178,7 @@
"payload": "",
"payloadType": "date",
"x": 110,
- "y": 180,
+ "y": 240,
"wires": [
[
"6b5cc73d3ff597e3"
@@ -1022,7 +1207,7 @@
"payload": "",
"payloadType": "date",
"x": 110,
- "y": 240,
+ "y": 300,
"wires": [
[
"b2f0b35e19e565c8"
@@ -1051,7 +1236,7 @@
"payload": "",
"payloadType": "date",
"x": 110,
- "y": 300,
+ "y": 360,
"wires": [
[
"94425a5d1997a6de"
@@ -1072,7 +1257,7 @@
"statusVal": "",
"statusType": "auto",
"x": 940,
- "y": 180,
+ "y": 240,
"wires": []
},
{
@@ -1089,7 +1274,7 @@
"statusVal": "",
"statusType": "auto",
"x": 940,
- "y": 240,
+ "y": 300,
"wires": []
},
{
@@ -1106,7 +1291,7 @@
"statusVal": "",
"statusType": "auto",
"x": 940,
- "y": 300,
+ "y": 360,
"wires": []
},
{
@@ -1130,8 +1315,8 @@
"topic": "",
"payload": "",
"payloadType": "date",
- "x": 200,
- "y": 380,
+ "x": 110,
+ "y": 480,
"wires": [
[
"239bb9ceb6c8f1a2"
@@ -1154,8 +1339,8 @@
"authType": "",
"senderr": false,
"headers": [],
- "x": 360,
- "y": 380,
+ "x": 270,
+ "y": 480,
"wires": [
[
"3bbe9a72ae47d0d3"
@@ -1174,8 +1359,565 @@
"complete": "false",
"statusVal": "",
"statusType": "auto",
- "x": 520,
- "y": 380,
+ "x": 440,
+ "y": 480,
+ "wires": []
+ },
+ {
+ "id": "df2b3c5fccde910c",
+ "type": "postgresql",
+ "z": "230815bb0628a63e",
+ "name": "Generamos la vista de demanda_h3_hour_ml",
+ "query": "--- Generamos la vista de la demanda h3 por hora modelo ml\nCREATE OR REPLACE VIEW demanda_h3_hour_ml AS\nSELECT\n h3,\n week,\n dow,\n hour,\n COUNT(*)::int AS total_events,\n SUM(is_nulo)::int AS total_nulos,\n CASE\n WHEN COUNT(*) = 0 THEN 0\n ELSE SUM(is_nulo)::float / COUNT(*)\n END AS nulo_rate\nFROM vw_servicios_h3\nGROUP BY h3, week, dow, hour;",
+ "postgreSQLConfig": "748b5921246ec468",
+ "split": false,
+ "rowsPerMsg": 1,
+ "outputs": 1,
+ "x": 380,
+ "y": 420,
+ "wires": [
+ [
+ "4b916996a32da5f8"
+ ]
+ ]
+ },
+ {
+ "id": "010bdecaa9c98232",
+ "type": "inject",
+ "z": "230815bb0628a63e",
+ "name": "",
+ "props": [
+ {
+ "p": "payload"
+ },
+ {
+ "p": "topic",
+ "vt": "str"
+ }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": 0.1,
+ "topic": "",
+ "payload": "",
+ "payloadType": "date",
+ "x": 110,
+ "y": 420,
+ "wires": [
+ [
+ "df2b3c5fccde910c"
+ ]
+ ]
+ },
+ {
+ "id": "4b916996a32da5f8",
+ "type": "debug",
+ "z": "230815bb0628a63e",
+ "name": "debug 10",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "true",
+ "targetType": "full",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 940,
+ "y": 420,
+ "wires": []
+ },
+ {
+ "id": "29d73c2064e1eba6",
+ "type": "inject",
+ "z": "230815bb0628a63e",
+ "name": "",
+ "props": [
+ {
+ "p": "payload"
+ },
+ {
+ "p": "topic",
+ "vt": "str"
+ }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": 0.1,
+ "topic": "",
+ "payload": "",
+ "payloadType": "date",
+ "x": 110,
+ "y": 60,
+ "wires": [
+ [
+ "f56d3d761eefad37"
+ ]
+ ]
+ },
+ {
+ "id": "f56d3d761eefad37",
+ "type": "postgresql",
+ "z": "230815bb0628a63e",
+ "name": "Limpieza de la vistas",
+ "query": "DROP VIEW IF EXISTS vw_servicios_h3 CASCADE;",
+ "postgreSQLConfig": "748b5921246ec468",
+ "split": false,
+ "rowsPerMsg": 1,
+ "outputs": 1,
+ "x": 300,
+ "y": 60,
+ "wires": [
+ [
+ "716a760504fb3ce1"
+ ]
+ ]
+ },
+ {
+ "id": "716a760504fb3ce1",
+ "type": "debug",
+ "z": "230815bb0628a63e",
+ "name": "debug 11",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "true",
+ "targetType": "full",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 940,
+ "y": 60,
+ "wires": []
+ },
+ {
+ "id": "3b3a9ea3a19ff7e5",
+ "type": "http request",
+ "z": "230815bb0628a63e",
+ "name": "TRAIN",
+ "method": "POST",
+ "ret": "txt",
+ "paytoqs": "ignore",
+ "url": "http://python:5000/train",
+ "tls": "",
+ "persist": false,
+ "proxy": "",
+ "insecureHTTPParser": false,
+ "authType": "",
+ "senderr": false,
+ "headers": [],
+ "x": 250,
+ "y": 540,
+ "wires": [
+ [
+ "5290cdca42f99d2c"
+ ]
+ ]
+ },
+ {
+ "id": "4faac7a3217d6419",
+ "type": "inject",
+ "z": "230815bb0628a63e",
+ "name": "",
+ "props": [
+ {
+ "p": "payload"
+ },
+ {
+ "p": "topic",
+ "vt": "str"
+ }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": 0.1,
+ "topic": "",
+ "payload": "",
+ "payloadType": "date",
+ "x": 110,
+ "y": 540,
+ "wires": [
+ [
+ "3b3a9ea3a19ff7e5"
+ ]
+ ]
+ },
+ {
+ "id": "5290cdca42f99d2c",
+ "type": "debug",
+ "z": "230815bb0628a63e",
+ "name": "debug 12",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "false",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 440,
+ "y": 540,
+ "wires": []
+ },
+ {
+ "id": "ce865e88acfa3de3",
+ "type": "http request",
+ "z": "230815bb0628a63e",
+ "name": "MODELS",
+ "method": "GET",
+ "ret": "obj",
+ "paytoqs": "ignore",
+ "url": "http://python:5000/models",
+ "tls": "",
+ "persist": false,
+ "proxy": "",
+ "insecureHTTPParser": false,
+ "authType": "",
+ "senderr": false,
+ "headers": [],
+ "x": 260,
+ "y": 600,
+ "wires": [
+ [
+ "43f0ea10327964bc"
+ ]
+ ]
+ },
+ {
+ "id": "41583ff4f043ae6d",
+ "type": "inject",
+ "z": "230815bb0628a63e",
+ "name": "",
+ "props": [
+ {
+ "p": "payload"
+ },
+ {
+ "p": "topic",
+ "vt": "str"
+ }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": 0.1,
+ "topic": "",
+ "payload": "",
+ "payloadType": "date",
+ "x": 110,
+ "y": 600,
+ "wires": [
+ [
+ "ce865e88acfa3de3"
+ ]
+ ]
+ },
+ {
+ "id": "43f0ea10327964bc",
+ "type": "debug",
+ "z": "230815bb0628a63e",
+ "name": "debug 13",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "false",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 440,
+ "y": 600,
+ "wires": []
+ },
+ {
+ "id": "740a140db7cd93f9",
+ "type": "inject",
+ "z": "230815bb0628a63e",
+ "name": "",
+ "props": [
+ {
+ "p": "payload"
+ },
+ {
+ "p": "topic",
+ "vt": "str"
+ }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": 0.1,
+ "topic": "",
+ "payload": "",
+ "payloadType": "date",
+ "x": 110,
+ "y": 660,
+ "wires": [
+ [
+ "7d0ae91a06114c05"
+ ]
+ ]
+ },
+ {
+ "id": "7d0ae91a06114c05",
+ "type": "http request",
+ "z": "230815bb0628a63e",
+ "name": "RECOMMEND",
+ "method": "GET",
+ "ret": "obj",
+ "paytoqs": "ignore",
+ "url": "http://10.10.11.211:5000/recommend?week=42&dow=3&hour=14&k=2",
+ "tls": "",
+ "persist": false,
+ "proxy": "",
+ "insecureHTTPParser": false,
+ "authType": "",
+ "senderr": false,
+ "headers": [],
+ "x": 280,
+ "y": 660,
+ "wires": [
+ [
+ "f27c12eab34c0d48"
+ ]
+ ]
+ },
+ {
+ "id": "f27c12eab34c0d48",
+ "type": "debug",
+ "z": "230815bb0628a63e",
+ "name": "debug 14",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "false",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 620,
+ "y": 660,
+ "wires": []
+ },
+ {
+ "id": "c630ce9f80c77046",
+ "type": "http request",
+ "z": "230815bb0628a63e",
+ "name": "RECOMMEND - OPTIMIZED GRUAS",
+ "method": "GET",
+ "ret": "obj",
+ "paytoqs": "ignore",
+ "url": "http://python:5000/recommend?week=42&dow=3&hour=14&optimize=true&min_gruas=1&max_gruas=5&include_cell_data=true",
+ "tls": "",
+ "persist": false,
+ "proxy": "",
+ "insecureHTTPParser": false,
+ "authType": "",
+ "senderr": false,
+ "headers": [],
+ "x": 350,
+ "y": 700,
+ "wires": [
+ [
+ "1d6ef76f3145f9d3",
+ "b63c955f07e230f1"
+ ]
+ ]
+ },
+ {
+ "id": "1d6ef76f3145f9d3",
+ "type": "debug",
+ "z": "230815bb0628a63e",
+ "name": "debug 15",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "false",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 620,
+ "y": 700,
+ "wires": []
+ },
+ {
+ "id": "e50b886e6d4a366e",
+ "type": "inject",
+ "z": "230815bb0628a63e",
+ "name": "",
+ "props": [
+ {
+ "p": "payload"
+ },
+ {
+ "p": "topic",
+ "vt": "str"
+ }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": 0.1,
+ "topic": "",
+ "payload": "",
+ "payloadType": "date",
+ "x": 110,
+ "y": 700,
+ "wires": [
+ [
+ "c630ce9f80c77046"
+ ]
+ ]
+ },
+ {
+ "id": "b96227cc7c8c3bcd",
+ "type": "inject",
+ "z": "230815bb0628a63e",
+ "name": "",
+ "props": [
+ {
+ "p": "payload"
+ },
+ {
+ "p": "topic",
+ "vt": "str"
+ }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": 0.1,
+ "topic": "",
+ "payload": "",
+ "payloadType": "date",
+ "x": 110,
+ "y": 740,
+ "wires": [
+ [
+ "dea2827d47fce819"
+ ]
+ ]
+ },
+ {
+ "id": "dea2827d47fce819",
+ "type": "http request",
+ "z": "230815bb0628a63e",
+ "name": "RECOMMEND - OPTIMIZED COVERAGED",
+ "method": "GET",
+ "ret": "obj",
+ "paytoqs": "ignore",
+ "url": "http://python:5000/recommend?week=42&dow=3&hour=14&optimize=true&target_coverage=95&include_cell_data=true",
+ "tls": "",
+ "persist": false,
+ "proxy": "",
+ "insecureHTTPParser": false,
+ "authType": "",
+ "senderr": false,
+ "headers": [],
+ "x": 370,
+ "y": 740,
+ "wires": [
+ [
+ "289ab66bd5b46e06",
+ "b63c955f07e230f1"
+ ]
+ ]
+ },
+ {
+ "id": "289ab66bd5b46e06",
+ "type": "debug",
+ "z": "230815bb0628a63e",
+ "name": "debug 16",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "false",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 620,
+ "y": 740,
+ "wires": []
+ },
+ {
+ "id": "b63c955f07e230f1",
+ "type": "link out",
+ "z": "230815bb0628a63e",
+ "name": "link out 1",
+ "mode": "link",
+ "links": [
+ "32e2494f462cc6f4"
+ ],
+ "x": 265,
+ "y": 900,
+ "wires": []
+ },
+ {
+ "id": "36934d545031c7fd",
+ "type": "inject",
+ "z": "230815bb0628a63e",
+ "name": "",
+ "props": [
+ {
+ "p": "payload"
+ },
+ {
+ "p": "topic",
+ "vt": "str"
+ }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": 0.1,
+ "topic": "",
+ "payload": "",
+ "payloadType": "date",
+ "x": 110,
+ "y": 960,
+ "wires": [
+ [
+ "148399ac5cb60fa4"
+ ]
+ ]
+ },
+ {
+ "id": "148399ac5cb60fa4",
+ "type": "http request",
+ "z": "230815bb0628a63e",
+ "name": "PREDICT",
+ "method": "GET",
+ "ret": "obj",
+ "paytoqs": "ignore",
+ "url": "http://10.10.11.211:5000/predict?model_type=demand",
+ "tls": "",
+ "persist": false,
+ "proxy": "",
+ "insecureHTTPParser": false,
+ "authType": "",
+ "senderr": false,
+ "headers": [],
+ "x": 260,
+ "y": 960,
+ "wires": [
+ [
+ "7b7e2d456a6ff158"
+ ]
+ ]
+ },
+ {
+ "id": "7b7e2d456a6ff158",
+ "type": "debug",
+ "z": "230815bb0628a63e",
+ "name": "debug 18",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "false",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 400,
+ "y": 960,
"wires": []
},
{
@@ -1187,8 +1929,8 @@
"ui": "",
"name": "Mapa Embebido",
"order": 1,
- "width": 0,
- "height": 0,
+ "width": 12,
+ "height": "9",
"head": "",
"format": "",
"storeOutMessages": true,
@@ -1197,9 +1939,902 @@
"templateScope": "local",
"className": "",
"x": 120,
- "y": 260,
+ "y": 80,
"wires": [
[]
]
+ },
+ {
+ "id": "c2bb3385f15f27e1",
+ "type": "ui-text-input",
+ "z": "56fb69eb622c3f3b",
+ "group": "bb66042c3e7c9cb3",
+ "name": "Calendario",
+ "label": "Fecha Estimación:",
+ "order": 1,
+ "width": 0,
+ "height": 0,
+ "topic": "topic",
+ "topicType": "msg",
+ "mode": "datetime-local",
+ "tooltip": "Añada la fecha en la que quiera la estimación ",
+ "delay": 300,
+ "passthru": false,
+ "sendOnDelay": false,
+ "sendOnBlur": true,
+ "sendOnEnter": true,
+ "className": "",
+ "clearable": true,
+ "sendOnClear": false,
+ "icon": "",
+ "iconPosition": "left",
+ "iconInnerPosition": "inside",
+ "x": 330,
+ "y": 40,
+ "wires": [
+ [
+ "a37879075935bb78"
+ ]
+ ]
+ },
+ {
+ "id": "8236702597bcb3d2",
+ "type": "ui-slider",
+ "z": "56fb69eb622c3f3b",
+ "group": "bb66042c3e7c9cb3",
+ "name": "Cobertura",
+ "label": "Cobertura",
+ "tooltip": "",
+ "order": 3,
+ "width": 0,
+ "height": 0,
+ "passthru": false,
+ "outs": "end",
+ "topic": "topic",
+ "topicType": "msg",
+ "thumbLabel": "true",
+ "showTicks": "false",
+ "min": "50",
+ "max": "95",
+ "step": 1,
+ "className": "",
+ "iconPrepend": "",
+ "iconAppend": "",
+ "color": "green",
+ "colorTrack": "red",
+ "colorThumb": "",
+ "showTextField": true,
+ "x": 320,
+ "y": 140,
+ "wires": [
+ [
+ "387fe5c1fac19afe"
+ ]
+ ]
+ },
+ {
+ "id": "a67e9a88c70fb435",
+ "type": "ui-number-input",
+ "z": "56fb69eb622c3f3b",
+ "group": "bb66042c3e7c9cb3",
+ "name": "Gruas",
+ "label": "Número de Grúas disponibles",
+ "order": 4,
+ "width": 0,
+ "height": 0,
+ "topic": "topic",
+ "topicType": "msg",
+ "min": "1",
+ "max": 10,
+ "step": 1,
+ "tooltip": "Número de gruas disponibles para las estimaciones",
+ "passthru": false,
+ "sendOnBlur": true,
+ "sendOnEnter": true,
+ "className": "",
+ "clearable": true,
+ "icon": "",
+ "iconPosition": "left",
+ "iconInnerPosition": "inside",
+ "spinner": "default",
+ "x": 310,
+ "y": 200,
+ "wires": [
+ [
+ "d62279b67e0184f1"
+ ]
+ ]
+ },
+ {
+ "id": "e2fe0aeabbab5ac6",
+ "type": "ui-button-group",
+ "z": "56fb69eb622c3f3b",
+ "name": "Opciones",
+ "group": "bb66042c3e7c9cb3",
+ "order": 2,
+ "width": "",
+ "height": "",
+ "label": "Parámetros para las estimaciones:",
+ "className": "",
+ "rounded": false,
+ "useThemeColors": true,
+ "passthru": false,
+ "options": [
+ {
+ "label": "Optimo",
+ "icon": "map-search",
+ "value": "optimo",
+ "valueType": "str",
+ "color": "#009933"
+ },
+ {
+ "label": "Grúas ",
+ "icon": "truck-flatbed",
+ "value": "gruas",
+ "valueType": "str",
+ "color": "#999999"
+ }
+ ],
+ "topic": "topic",
+ "topicType": "msg",
+ "x": 320,
+ "y": 80,
+ "wires": [
+ [
+ "9e77a7b3c8e11460"
+ ]
+ ]
+ },
+ {
+ "id": "a8f18f06a75851a1",
+ "type": "ui-button",
+ "z": "56fb69eb622c3f3b",
+ "group": "bb66042c3e7c9cb3",
+ "name": "Limpieza",
+ "label": "Borrar los datos del mapa",
+ "order": 7,
+ "width": 0,
+ "height": 0,
+ "emulateClick": true,
+ "tooltip": "",
+ "color": "",
+ "bgcolor": "",
+ "className": "",
+ "icon": "",
+ "iconPosition": "left",
+ "payload": "{\"command\":{\"clearlayer\":[\"H3\",\"layer2\"]}}",
+ "payloadType": "json",
+ "topic": "topic",
+ "topicType": "msg",
+ "buttonColor": "",
+ "textColor": "",
+ "iconColor": "",
+ "enableClick": true,
+ "enablePointerdown": false,
+ "pointerdownPayload": "",
+ "pointerdownPayloadType": "str",
+ "enablePointerup": false,
+ "pointerupPayload": "",
+ "pointerupPayloadType": "str",
+ "x": 320,
+ "y": 360,
+ "wires": [
+ [
+ "26adce90744a441d"
+ ]
+ ]
+ },
+ {
+ "id": "f78b5cc7dc506422",
+ "type": "ui-button",
+ "z": "56fb69eb622c3f3b",
+ "group": "bb66042c3e7c9cb3",
+ "name": "Mostrar",
+ "label": "Calcular los datos",
+ "order": 6,
+ "width": 0,
+ "height": 0,
+ "emulateClick": false,
+ "tooltip": "",
+ "color": "",
+ "bgcolor": "",
+ "className": "",
+ "icon": "",
+ "iconPosition": "left",
+ "payload": "",
+ "payloadType": "str",
+ "topic": "topic",
+ "topicType": "msg",
+ "buttonColor": "",
+ "textColor": "",
+ "iconColor": "",
+ "enableClick": true,
+ "enablePointerdown": false,
+ "pointerdownPayload": "",
+ "pointerdownPayloadType": "str",
+ "enablePointerup": false,
+ "pointerupPayload": "",
+ "pointerupPayloadType": "str",
+ "x": 120,
+ "y": 520,
+ "wires": [
+ [
+ "fa837f38ed1dbc1e"
+ ]
+ ]
+ },
+ {
+ "id": "fe084cbbae83b4fa",
+ "type": "worldmap",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "lat": "",
+ "lon": "",
+ "zoom": "",
+ "layer": "",
+ "cluster": "",
+ "maxage": "",
+ "usermenu": "show",
+ "layers": "show",
+ "panit": "false",
+ "panlock": "false",
+ "zoomlock": "false",
+ "hiderightclick": "false",
+ "coords": "false",
+ "showgrid": "false",
+ "showruler": "false",
+ "allowFileDrop": "false",
+ "path": "/worldmap",
+ "overlist": "DR,CO,RA,DN",
+ "maplist": "OSMG,OSMC,EsriC,EsriS,UKOS",
+ "mapname": "",
+ "mapurl": "",
+ "mapopt": "",
+ "mapwms": false,
+ "x": 140,
+ "y": 120,
+ "wires": []
+ },
+ {
+ "id": "f42fd41cad44b2d1",
+ "type": "link in",
+ "z": "56fb69eb622c3f3b",
+ "name": "IN - WorldMAP",
+ "links": [
+ "26adce90744a441d"
+ ],
+ "x": 45,
+ "y": 120,
+ "wires": [
+ [
+ "fe084cbbae83b4fa"
+ ]
+ ]
+ },
+ {
+ "id": "26adce90744a441d",
+ "type": "link out",
+ "z": "56fb69eb622c3f3b",
+ "name": "OUT - Limpiar mapa",
+ "mode": "link",
+ "links": [
+ "f42fd41cad44b2d1"
+ ],
+ "x": 695,
+ "y": 360,
+ "wires": []
+ },
+ {
+ "id": "a796d79ddb167eb1",
+ "type": "ui-dropdown",
+ "z": "56fb69eb622c3f3b",
+ "group": "bb66042c3e7c9cb3",
+ "name": "Tráfico",
+ "label": "Densidad del tráfico (opcional)",
+ "tooltip": "",
+ "order": 5,
+ "width": 0,
+ "height": 0,
+ "passthru": false,
+ "multiple": false,
+ "chips": false,
+ "clearable": true,
+ "options": [
+ {
+ "label": "Alto",
+ "value": 3,
+ "type": "num"
+ },
+ {
+ "label": "Normal",
+ "value": 2,
+ "type": "num"
+ },
+ {
+ "label": "Bajo",
+ "value": 1,
+ "type": "num"
+ }
+ ],
+ "payload": "",
+ "topic": "topic",
+ "topicType": "msg",
+ "className": "",
+ "typeIsComboBox": true,
+ "msgTrigger": "onChange",
+ "x": 310,
+ "y": 280,
+ "wires": [
+ [
+ "5eedba05f8e677de"
+ ]
+ ]
+ },
+ {
+ "id": "ebf80c2abc415017",
+ "type": "ui-text",
+ "z": "56fb69eb622c3f3b",
+ "group": "bb66042c3e7c9cb3",
+ "order": 8,
+ "width": 0,
+ "height": null,
+ "name": "Resultados",
+ "label": "",
+ "format": "{{msg.payload}}",
+ "layout": "row-left",
+ "style": true,
+ "font": "Helvetica, sans-serif",
+ "fontSize": 16,
+ "color": "#717171",
+ "wrapText": false,
+ "className": "",
+ "value": "payload",
+ "valueType": "msg",
+ "x": 590,
+ "y": 420,
+ "wires": []
+ },
+ {
+ "id": "a37879075935bb78",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "calendario",
+ "pt": "flow",
+ "to": "payload",
+ "tot": "msg"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 530,
+ "y": 40,
+ "wires": [
+ []
+ ]
+ },
+ {
+ "id": "9e77a7b3c8e11460",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "opcion",
+ "pt": "flow",
+ "to": "payload",
+ "tot": "msg"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 540,
+ "y": 80,
+ "wires": [
+ []
+ ]
+ },
+ {
+ "id": "5387e2228122ee16",
+ "type": "inject",
+ "z": "56fb69eb622c3f3b",
+ "name": "Incializar Valores",
+ "props": [
+ {
+ "p": "payload"
+ },
+ {
+ "p": "topic",
+ "vt": "str"
+ }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": true,
+ "onceDelay": "1",
+ "topic": "",
+ "payload": "iso",
+ "payloadType": "date",
+ "x": 130,
+ "y": 40,
+ "wires": [
+ [
+ "c2bb3385f15f27e1",
+ "e2fe0aeabbab5ac6",
+ "8236702597bcb3d2",
+ "a67e9a88c70fb435",
+ "a796d79ddb167eb1"
+ ]
+ ]
+ },
+ {
+ "id": "387fe5c1fac19afe",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "cobertura",
+ "pt": "flow",
+ "to": "payload",
+ "tot": "msg"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 530,
+ "y": 140,
+ "wires": [
+ []
+ ]
+ },
+ {
+ "id": "d62279b67e0184f1",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "gruas",
+ "pt": "flow",
+ "to": "payload",
+ "tot": "msg"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 540,
+ "y": 200,
+ "wires": [
+ []
+ ]
+ },
+ {
+ "id": "5eedba05f8e677de",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "trafico",
+ "pt": "flow",
+ "to": "payload",
+ "tot": "msg"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 740,
+ "y": 280,
+ "wires": [
+ []
+ ]
+ },
+ {
+ "id": "8afe796111cb8c8d",
+ "type": "switch",
+ "z": "56fb69eb622c3f3b",
+ "name": "opcion",
+ "property": "opcion",
+ "propertyType": "flow",
+ "rules": [
+ {
+ "t": "eq",
+ "v": "gruas",
+ "vt": "str"
+ },
+ {
+ "t": "eq",
+ "v": "optimo",
+ "vt": "str"
+ },
+ {
+ "t": "else"
+ }
+ ],
+ "checkall": "true",
+ "repair": false,
+ "outputs": 3,
+ "x": 90,
+ "y": 780,
+ "wires": [
+ [
+ "16926b851536025d"
+ ],
+ [
+ "9c6f7af87438a4bd"
+ ],
+ [
+ "ec90eeee993b765a"
+ ]
+ ]
+ },
+ {
+ "id": "447113b576ecfcba",
+ "type": "ui-notification",
+ "z": "56fb69eb622c3f3b",
+ "ui": "3afa9de4406d30d8",
+ "position": "top right",
+ "colorDefault": true,
+ "color": "#000000",
+ "displayTime": "10",
+ "showCountdown": true,
+ "outputs": 1,
+ "allowDismiss": true,
+ "dismissText": "Cerrar",
+ "allowConfirm": true,
+ "confirmText": "Aceptar",
+ "raw": false,
+ "className": "",
+ "name": "",
+ "x": 630,
+ "y": 880,
+ "wires": [
+ []
+ ]
+ },
+ {
+ "id": "fa837f38ed1dbc1e",
+ "type": "function",
+ "z": "56fb69eb622c3f3b",
+ "name": "Calculamos la fecha",
+ "func": "let fecha = new Date(flow.get(\"calendario\"));\n\nif (isNaN(fecha.getTime())) {\n node.error(\"Fecha inválida\", msg);\n return null;\n}\n\nconst firstDayOfYear = new Date(fecha.getFullYear(), 0, 1);\n// @ts-ignore\nconst pastDaysOfYear = (fecha - firstDayOfYear) / 86400000;\nconst week = Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);\n\nconst dow = fecha.getDay();\n\nconst hour = fecha.getHours();\n\nmsg.payload = { week, dow, hour };\nreturn msg;",
+ "outputs": 1,
+ "timeout": 0,
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [],
+ "x": 300,
+ "y": 520,
+ "wires": [
+ [
+ "8afe796111cb8c8d"
+ ]
+ ]
+ },
+ {
+ "id": "ec90eeee993b765a",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "payload",
+ "pt": "msg",
+ "to": "Es necesario indicar que calculo se necesita si por cobertura o por número de gruas.",
+ "tot": "str"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 340,
+ "y": 880,
+ "wires": [
+ [
+ "447113b576ecfcba"
+ ]
+ ]
+ },
+ {
+ "id": "9c6f7af87438a4bd",
+ "type": "switch",
+ "z": "56fb69eb622c3f3b",
+ "name": "cobertura",
+ "property": "cobertura",
+ "propertyType": "flow",
+ "rules": [
+ {
+ "t": "istype",
+ "v": "number",
+ "vt": "number"
+ },
+ {
+ "t": "else"
+ }
+ ],
+ "checkall": "true",
+ "repair": false,
+ "outputs": 2,
+ "x": 260,
+ "y": 780,
+ "wires": [
+ [
+ "91d413e80d8139af"
+ ],
+ [
+ "9af49346793df573"
+ ]
+ ]
+ },
+ {
+ "id": "9af49346793df573",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "payload",
+ "pt": "msg",
+ "to": "Es necesario indicar la cobertura requerida",
+ "tot": "str"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 460,
+ "y": 820,
+ "wires": [
+ [
+ "447113b576ecfcba"
+ ]
+ ]
+ },
+ {
+ "id": "11b08389769a3a9f",
+ "type": "http request",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "method": "GET",
+ "ret": "obj",
+ "paytoqs": "ignore",
+ "url": "",
+ "tls": "",
+ "persist": false,
+ "proxy": "",
+ "insecureHTTPParser": false,
+ "authType": "",
+ "senderr": false,
+ "headers": [],
+ "x": 870,
+ "y": 760,
+ "wires": [
+ [
+ "c1552a91a92da199"
+ ]
+ ]
+ },
+ {
+ "id": "91d413e80d8139af",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "url",
+ "pt": "msg",
+ "to": "\"http://python:5000/recommend?week=\" & payload.week &\t\"&dow=\" & payload.dow &\t\"&hour=\" & payload.hour &\t\"&traffic=\" & $flowContext(\"trafico\") &\t\"&optimize=true&include_cell_data=true&min_gruas=1&max_gruas=\" & $flowContext(\"gruas\") &\t\"&target_coverage=\" & $flowContext(\"cobertura\")",
+ "tot": "jsonata"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 690,
+ "y": 760,
+ "wires": [
+ [
+ "11b08389769a3a9f"
+ ]
+ ]
+ },
+ {
+ "id": "c1552a91a92da199",
+ "type": "link out",
+ "z": "56fb69eb622c3f3b",
+ "name": "link out 2",
+ "mode": "link",
+ "links": [
+ "32e2494f462cc6f4",
+ "68707cddaa9512b9"
+ ],
+ "x": 1055,
+ "y": 620,
+ "wires": []
+ },
+ {
+ "id": "16926b851536025d",
+ "type": "switch",
+ "z": "56fb69eb622c3f3b",
+ "name": "gruas",
+ "property": "gruas",
+ "propertyType": "flow",
+ "rules": [
+ {
+ "t": "istype",
+ "v": "number",
+ "vt": "number"
+ },
+ {
+ "t": "else"
+ }
+ ],
+ "checkall": "true",
+ "repair": false,
+ "outputs": 2,
+ "x": 250,
+ "y": 680,
+ "wires": [
+ [
+ "dcd489d1ae63d998"
+ ],
+ [
+ "1cd6a1eb10de5864"
+ ]
+ ]
+ },
+ {
+ "id": "1cd6a1eb10de5864",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "payload",
+ "pt": "msg",
+ "to": "Es necesario indicar las gruas disponibles",
+ "tot": "str"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 460,
+ "y": 720,
+ "wires": [
+ [
+ "447113b576ecfcba"
+ ]
+ ]
+ },
+ {
+ "id": "dcd489d1ae63d998",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "url",
+ "pt": "msg",
+ "to": "\"http://python:5000/recommend?week=\" & payload.week &\t\"&dow=\" & payload.dow &\t\"&hour=\" & payload.hour &\t\"&traffic=\" & $flowContext(\"trafico\") &\t\"&optimize=false&include_cell_data=true&k=\" & $flowContext(\"gruas\")",
+ "tot": "jsonata"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 690,
+ "y": 660,
+ "wires": [
+ [
+ "2e7fc0d864b209e7"
+ ]
+ ]
+ },
+ {
+ "id": "2e7fc0d864b209e7",
+ "type": "http request",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "method": "GET",
+ "ret": "obj",
+ "paytoqs": "ignore",
+ "url": "",
+ "tls": "",
+ "persist": false,
+ "proxy": "",
+ "insecureHTTPParser": false,
+ "authType": "",
+ "senderr": false,
+ "headers": [],
+ "x": 870,
+ "y": 660,
+ "wires": [
+ [
+ "c1552a91a92da199"
+ ]
+ ]
+ },
+ {
+ "id": "4de317cc487a47c6",
+ "type": "change",
+ "z": "56fb69eb622c3f3b",
+ "name": "",
+ "rules": [
+ {
+ "t": "set",
+ "p": "payload",
+ "pt": "msg",
+ "to": "\"Resultados:
\" & \"Riesgo cubierto: \" & payload.cobertura.risk_coverage_percentage &\t \" %
\" & \"Cobertura total: \" & payload.cobertura.coverage_percentage &\t \" %\"",
+ "tot": "jsonata"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 360,
+ "y": 420,
+ "wires": [
+ [
+ "ebf80c2abc415017"
+ ]
+ ]
+ },
+ {
+ "id": "68707cddaa9512b9",
+ "type": "link in",
+ "z": "56fb69eb622c3f3b",
+ "name": "link in 2",
+ "links": [
+ "c1552a91a92da199"
+ ],
+ "x": 215,
+ "y": 420,
+ "wires": [
+ [
+ "4de317cc487a47c6"
+ ]
+ ]
}
]
\ No newline at end of file
diff --git a/flows_cred.json b/flows_cred.json
index 2a5fc8b..6305138 100644
--- a/flows_cred.json
+++ b/flows_cred.json
@@ -1,3 +1,10 @@
{
- "239bb9ceb6c8f1a2": {}
+ "239bb9ceb6c8f1a2": {},
+ "3b3a9ea3a19ff7e5": {},
+ "ce865e88acfa3de3": {},
+ "7d0ae91a06114c05": {},
+ "c630ce9f80c77046": {},
+ "dea2827d47fce819": {},
+ "11b08389769a3a9f": {},
+ "148399ac5cb60fa4": {}
}
\ No newline at end of file