Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 74 additions & 48 deletions src/nopywer/frontend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
}
}


function cableStyle(feature) {
const plugs = feature.properties && feature.properties.plugs_and_sockets_a
? feature.properties.plugs_and_sockets_a
Expand All @@ -109,59 +110,82 @@
}
return { color: "#f59e0b", weight: 4 };
}
function getCableWeight(plugs) {
if (plugs >= 63) return 8;
if (plugs >= 32) return 6;
return 4;
}
function getCableColor(plugs) {
if (plugs >= 63) return "#dc2626";
if (plugs >= 32) return "#ea580c";
return "#f59e0b";
}

function phaseColor(phase) {
const colors = { 0: "#6f2018dc", 1: "#000000", 2: "#9d9d9d" };
return colors[phase] ?? "#e81212";
}

function renderOptimizedCables(cablesGeojson) {
optimizedCablesLayer.clearLayers();
console.log(cablesGeojson.features[0].properties);

optimizedCablesLayer.clearLayers();

const filter = function (feature) {
return feature.geometry && feature.geometry.type === "LineString";
};

// Capa base: tipo de cable (gruesa, semitransparente)
const baseLayer = L.geoJSON(cablesGeojson, {
filter,
style: function (feature) {
const style = cableStyle(feature);
return {
color: style.color,
weight: style.weight * 2,
opacity: 0.5,
};
},
});

const cableLayer = L.geoJSON(cablesGeojson, {
filter: function (feature) {
return feature.geometry && feature.geometry.type === "LineString";
},
style: function (feature) {
const style = cableStyle(feature);
return {
color: style.color,
weight: style.weight,
opacity: 0.95,
};
},
onEachFeature: function (feature, layer) {
const props = feature.properties || {};
const details =
"<strong>" +
(props.from || "?") +
" → " +
(props.to || "?") +
"</strong><br />" +
"Length: " +
String(props.length_m ?? "?") +
" m<br />" +
"Cable: " +
String(props.cable_type || "?") +
"<br />" +
"Current: " +
String(props.current_a ?? "?") +
" A<br />" +
"Load: " +
String(props.cum_power_kw ?? "?") +
" kW";

layer.bindPopup(details);
layer.bindTooltip(details, {
sticky: true,
direction: "top",
className: "cable-tooltip",
});
},
});
// Capa superior: fase (fina, sólida) + popups
const phaseLayer = L.geoJSON(cablesGeojson, {
filter,
style: function (feature) {
const plugs = feature.properties?.plugs_and_sockets_a ?? 16;
const phase = feature.properties?.phase ?? null;
const baseWeight = cableStyle(feature).weight;
return {
color: phaseColor(phase),
weight: baseWeight,
opacity: 1,
};
},
onEachFeature: function (feature, layer) {
const props = feature.properties || {};
const phase = props.phase ?? null;
const plugs = props.plugs_and_sockets_a ?? 16;

const details =
"<strong>" + (props.from || "?") + " → " + (props.to || "?") + "</strong><br />" +
"Phase: <span style='color:" + phaseColor(phase) + "'><strong>L" + ((phase ?? "?") + 1) + "</strong></span><br />" +
"Cable: <span style='color:" + cableStyle(feature).color + "'><strong>" + plugs + "A</strong></span><br />" +
"Length: " + String(props.length_m ?? "?") + " m<br />" +
"Current: " + String(props.current_a ?? "?") + " A<br />" +
"Load: " + String(props.cum_power_kw ?? "?") + " kW";

layer.bindPopup(details);
},
});

cableLayer.addTo(optimizedCablesLayer);
if (cableLayer.getLayers().length > 0) {
map.fitBounds(cableLayer.getBounds().pad(0.08));
}
bringNodesToFront();
}
baseLayer.addTo(optimizedCablesLayer);
phaseLayer.addTo(optimizedCablesLayer);

if (phaseLayer.getLayers().length > 0) {
map.fitBounds(phaseLayer.getBounds().pad(0.08));
}
bringNodesToFront();
}
function buildNodesGeojson() {
if (!nodesGeojson || !Array.isArray(nodesGeojson.features)) {
return { type: "FeatureCollection", features: [] };
Expand Down Expand Up @@ -254,3 +278,5 @@
map.setView([41.7008, -0.1379], 17);
});
})();

console.log(cablesGeojson.features[0].properties);
Binary file added src/nopywer/frontend/favicon.ico
Binary file not shown.
7 changes: 5 additions & 2 deletions src/nopywer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class PowerNode:
lat: float
power_watts: float = 0.0
is_generator: bool = False
phase: int | str | list | None = None
phase: int = 0

parent: str | None = None
children: dict[str, str] = field(default_factory=dict)
Expand Down Expand Up @@ -52,6 +52,7 @@ def to_geojson(self) -> dict:
"voltage": self.voltage,
"vdrop_percent": self.vdrop_percent,
"distro": self.distro,
"phase": self.phase,
},
}

Expand All @@ -62,7 +63,7 @@ class Cable:
length_m: float
area_mm2: float = 2.5
plugs_and_sockets_a: float = 16.0
phase: int | str | list | None = None
phase: int = 0

from_node: str = ""
to_node: str = ""
Expand Down Expand Up @@ -113,6 +114,7 @@ def to_geojson(self) -> dict:
"current_a": round(max_current, 1),
"cum_power_kw": round(cum_power_w / 1000, 2),
"vdrop_volts": self.vdrop_volts,
"phase": self.phase,
},
}

Expand All @@ -124,6 +126,7 @@ class Cable16A(Cable):
tier_cost: ClassVar[float] = 1.0
num_phases: ClassVar[int] = 1
max_current_a: ClassVar[int] = 16
area_mm2: float = 2.5


@dataclass
Expand Down
57 changes: 57 additions & 0 deletions telemetry data/telemetry visualizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from pathlib import Path

import pandas as pd
import plotly.express as px


def load_telemetry(file_path):
phases = {}
current_phase = None
rows = []

with open(file_path, encoding="utf-8-sig") as f:
for line in f:
line = line.strip()

if line.startswith("Phase"):
if current_phase and rows:
df_phase = pd.DataFrame(rows, columns=["Time", "Wh"])
phases[current_phase] = df_phase
rows = []
current_phase = line

elif line and not line.startswith("Time"):
parts = line.split(" , ")
if len(parts) == 2:
rows.append(parts)

if current_phase and rows:
df_phase = pd.DataFrame(rows, columns=["Time", "Wh"])
phases[current_phase] = df_phase
return phases


def clean_phase_data(df):
df["Time"] = pd.to_datetime(df["Time"], dayfirst=True)
df["Wh"] = pd.to_numeric(df["Wh"])
return df


DATA_DIR = Path("./telemetry data")
files = list(DATA_DIR.glob("*.csv"))
all_data = []

for file in files:
phases = load_telemetry(file)

for phase_name, df in phases.items():
df = clean_phase_data(df)
df["phase"] = phase_name
df["source"] = file.name.split("_")[0]
all_data.append(df)

df = pd.concat(all_data)

fig = px.line(df, x="Time", y="Wh", color="phase", facet_row="source", title="Power telemetry")

fig.show()
Loading