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