dashboard einzelansicht trend auslastung.

main
Giò Diani 2024-12-19 19:22:09 +01:00
parent 4b7067fb63
commit 47a5035787
7 changed files with 148 additions and 11 deletions

View File

@ -3,3 +3,4 @@
## Projektstruktur ## Projektstruktur
- etl: Enthält den Programmcode, welcher die Daten aufbereitet und via REST-API zur Verfügung stellt. - etl: Enthält den Programmcode, welcher die Daten aufbereitet und via REST-API zur Verfügung stellt.
- dashboard: Webapplikation zur Exploration und Visualisierung der Daten. - dashboard: Webapplikation zur Exploration und Visualisierung der Daten.

View File

@ -45,6 +45,11 @@ class Api
return self::get("/property/{$id}/extractions"); return self::get("/property/{$id}/extractions");
} }
public static function propertyCapacities(int $id)
{
return self::get("/property/{$id}/capacities");
}
public static function propertyBase(int $id): mixed public static function propertyBase(int $id): mixed
{ {
return self::get("/property/{$id}/base"); return self::get("/property/{$id}/base");

View File

@ -21,7 +21,7 @@
@endforeach @endforeach
</dl> </dl>
</article> </article>
<article class="header"> <article class="header" style="grid-row-start: 1; grid-row-end: 3;">
<header> <header>
<h2 id="belegung-title"> <h2 id="belegung-title">
Belegung am {{ json_decode($extractiondates)[0] }} Belegung am {{ json_decode($extractiondates)[0] }}
@ -29,7 +29,52 @@
</header> </header>
<div id="chart-calendar"></div> <div id="chart-calendar"></div>
</article> </article>
<article class="header">
<header>
<h2>
Entwicklung der Verfügbarkeit
</h2>
</header>
<div id="chart-capacity"></div>
</article>
<script type="module"> <script type="module">
const chartCapacity = document.getElementById('chart-capacity');
const cCapacity = echarts.init(chartCapacity);
const cCapacityOptions = {
tooltip: {
trigger: 'axis'
},
grid: {
left: '0',
right: 10,
bottom: '0',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: {!! json_encode($capacities['dates']) !!}
},
yAxis: {
type: 'value',
min: 0,
max: 100
},
series: [
{
name: 'Alle',
type: 'line',
stack: 'Total',
symbolSize: 7,
data: {!! json_encode($capacities['capacities']) !!}
}
]
};
cCapacity.setOption(cCapacityOptions);
const chartCalendar = document.getElementById('chart-calendar'); const chartCalendar = document.getElementById('chart-calendar');
const cCalendar = echarts.init(chartCalendar); const cCalendar = echarts.init(chartCalendar);
const h2Belegung = document.getElementById('belegung-title'); const h2Belegung = document.getElementById('belegung-title');
@ -70,18 +115,26 @@ const cCalendarOptions = {
orient: 'horizontal', orient: 'horizontal',
range: '2024', range: '2024',
top: 50, top: 50,
right: 0, right: 10,
left: 50, left: 50,
bottom: "55%" bottom: "66%"
}, },
{ {
orient: 'horizontal', orient: 'horizontal',
range: '2025', range: '2025',
right: 100, right: 10,
left: 50, left: 50,
bottom: 60, top: '43%',
top: '55%' bottom: '43%'
}, },
{
orient: 'horizontal',
range: '2026',
right: 10,
left: 50,
top: '66%',
bottom: '10%'
}
], ],
options: [ options: [
@foreach ($calendar as $c) @foreach ($calendar as $c)
@ -97,6 +150,12 @@ const cCalendarOptions = {
coordinateSystem: 'calendar', coordinateSystem: 'calendar',
calendarIndex: 1, calendarIndex: 1,
data: {!! json_encode($c) !!} data: {!! json_encode($c) !!}
},
{
type: 'heatmap',
coordinateSystem: 'calendar',
calendarIndex: 2,
data: {!! json_encode($c) !!}
}] }]
}, },
@endforeach @endforeach
@ -105,9 +164,35 @@ const cCalendarOptions = {
cCalendar.setOption(cCalendarOptions); cCalendar.setOption(cCalendarOptions);
cCalendar.on('timelinechanged', (e) => { cCalendar.on('timelinechanged', (e) => {
h2Belegung.innerText = "Belegung am "+cCalendarOptions.timeline.data[e.currentIndex]; h2Belegung.innerText = "Belegung am "+cCalendarOptions.timeline.data[e.currentIndex];
/*
series = cCalendarOptions.series[0].data
series[e.currentIndex]
series: [
{
name: 'Alle',
type: 'line',
stack: 'Total',
symbolSize: 7,
data: {!! json_encode($capacities['capacities']) !!}
}
]
cCapacity.setOption({
}) })
*/
})
cCapacity.on('click', 'series', (e) => {
cCalendar.dispatchAction({
type: 'timelineChange',
currentIndex: e.dataIndex
});
});
</script> </script>
@endsection @endsection

View File

@ -17,7 +17,6 @@ Route::get('/', function () {
} }
$propertiesGeo = Api::propertiesGeo(); $propertiesGeo = Api::propertiesGeo();
//dump($propertiesGeo);
return view('overview', ["geo" => $propertiesGeo, "growth" => $propertiesGrowth, "propsPerRegion" => [json_encode($propsPerRegionName), json_encode($propsPerRegionCounts)]]); return view('overview', ["geo" => $propertiesGeo, "growth" => $propertiesGrowth, "propsPerRegion" => [json_encode($propsPerRegionName), json_encode($propsPerRegionCounts)]]);
}); });
@ -26,6 +25,7 @@ Route::get('/prop/{id}', function (int $id) {
$propertyBase = Api::propertyBase($id); $propertyBase = Api::propertyBase($id);
$extractions = Api::propertyExtractions($id); $extractions = Api::propertyExtractions($id);
$propertyCapacities = Api::propertyCapacities($id);
$data = []; $data = [];
$dates = []; $dates = [];
@ -46,5 +46,5 @@ Route::get('/prop/{id}', function (int $id) {
} }
return view('property', ['base' => $propertyBase[0], "extractiondates" => json_encode($dates), "calendar" => $data]); return view('property', ['base' => $propertyBase[0], "extractiondates" => json_encode($dates), "calendar" => $data, 'capacities' => $propertyCapacities]);
}); });

2
etl/pixi.lock generated
View File

@ -2200,7 +2200,7 @@ packages:
name: consultancy-2 name: consultancy-2
version: 0.1.0 version: 0.1.0
path: . path: .
sha256: 878bb6af1502cc9ac71feab6f184f593077f134626d6f8c552e9bcafb178f6b4 sha256: c09f63486f0dd4151008de68ef73d00f72663dc3cc47894ff750d517f898a23b
requires_python: '>=3.11' requires_python: '>=3.11'
editable: true editable: true
- kind: conda - kind: conda

View File

@ -1,5 +1,6 @@
import data import data
import polars as pl import polars as pl
from data import etl_property_capacities as etl_pc
from fastapi import FastAPI, Response from fastapi import FastAPI, Response
d = data.load() d = data.load()
@ -34,6 +35,11 @@ def properties_geo():
def property_extractions(id: int): def property_extractions(id: int):
return d.extractions_for(property_id = id).pl().to_dicts() return d.extractions_for(property_id = id).pl().to_dicts()
@app.get("/property/{id}/capacities")
def property_capacities_data(id: int):
capacities = etl_pc.property_capacities(id)
return capacities
@app.get("/property/{id}/base") @app.get("/property/{id}/base")
def property_base_data(id: int): def property_base_data(id: int):
return d.property_base_data(id).pl().to_dicts() return d.property_base_data(id).pl().to_dicts()

View File

@ -0,0 +1,40 @@
from io import StringIO
import polars as pl
import data
d = data.load()
def property_capacities(id: int):
extractions = d.extractions_for(id).pl()
df_dates = pl.DataFrame()
for row in extractions.rows(named=True):
df_calendar = pl.read_json(StringIO(row['calendar']))
#df_calendar.insert_column(0, pl.Series("created_at", [row['created_at']]))
df_dates = pl.concat([df_calendar, df_dates], how="diagonal")
# order = sorted(df_dates.columns)
# df_dates = df_dates.select(order)
sum_hor = df_dates.sum_horizontal()
#print(sum_hor)
# Get the available dates per extraction
count_days = []
for dates in df_dates.rows():
# Remove all None values
liste = [x for x in dates if x is not None]
count_days.append(len(liste))
print(sum_hor)
counts = pl.DataFrame({"count_days" : count_days, "sum" : sum_hor})
result = {"capacities": [], "dates": extractions['created_at'].cast(pl.Datetime).to_list() }
for row in counts.rows(named=True):
max_capacity = row['count_days'] * 2
max_capacity_perc = 100 / max_capacity
result['capacities'].append(round(max_capacity_perc * row['sum'], 2))
result['capacities'].reverse()
return result