mmaurostoffel 2025-01-13 17:12:15 +01:00
commit 4487932f1b
9 changed files with 306 additions and 65 deletions

View File

@ -23,7 +23,7 @@ class Api
return Cache::get($request); return Cache::get($request);
} }
$get = Http::timeout(800)->get($request); $get = Http::timeout(1600)->get($request);
if($get->successful()){ if($get->successful()){
$result = $get->json(); $result = $get->json();
@ -94,6 +94,11 @@ class Api
return self::get("/region/{$id}/capacities"); return self::get("/region/{$id}/capacities");
} }
public static function regionMovingAverage(int $id, string $date): mixed
{
return self::get("/region/{$id}/movingAverage/{$date}");
}
} }

View File

@ -178,10 +178,10 @@ body.property main{
grid-template-columns: repeat(4, minmax(10%, 50%)); grid-template-columns: repeat(4, minmax(10%, 50%));
grid-template-rows: repeat(3, 1fr) 4em; grid-template-rows: repeat(3, 1fr) 4em;
grid-template-areas: grid-template-areas:
"chart2 chart2 chart5 chart5" "chart2 chart2 chart1 chart1"
"chart1 chart1 chart3 chart4" "chart5 chart5 chart1 chart1"
"chart1 chart1 chart3 chart4" "chart5 chart5 chart3 chart4"
"timeline timeline timeline timeline"; "chart5 chart5 timeline timeline";
} }
body.region main{ body.region main{
@ -190,8 +190,8 @@ body.region main{
grid-template-areas: grid-template-areas:
"chart1 chart1 chart2 chart2" "chart1 chart1 chart2 chart2"
"chart1 chart1 chart3 chart4" "chart1 chart1 chart3 chart4"
"chart1 chart1 chart3 chart4" "chart6 chart6 chart3 chart4"
"chart1 chart1 timeline timeline"; "chart6 chart6 timeline timeline";
} }
article{ article{
@ -204,10 +204,18 @@ article{
article.header{ article.header{
grid-template-columns: 100%; grid-template-columns: 100%;
grid-template-rows: minmax(1%, 10%) 1fr; grid-template-rows: minmax(1%, 2.5em) 1fr;
padding: .5em 1em 1em .5em; padding: .5em 1em 1em .5em;
} }
article.map{
padding: 0;
}
article.map>header{
padding: .5em 1em 1em .5em;
}
article>header{ article>header{
display: grid; display: grid;
grid-template-columns: 1fr 1em; grid-template-columns: 1fr 1em;
@ -231,3 +239,33 @@ article>header>h2{
grid-template-rows: repeat(4, minmax(20em, 25em)); grid-template-rows: repeat(4, minmax(20em, 25em));
} }
} }
.leaflet-marker-icon span{
background: blue;
width: 2rem;
height: 2rem;
display: block;
left: -1rem;
top: -1rem;
position: relative;
border-radius: 50% 50% 0;
transform: rotate(45deg);
border: 2px solid #fff
}
/*['#9ecae1','#6baed6','#4292c6','#2171b5','#084594'*/
.leaflet-marker-icon.region1 span{
background: #9ecae1;
}
.leaflet-marker-icon.region2 span{
background: #6baed6;
}
.leaflet-marker-icon.region3 span{
background: #4292c6;
}
.leaflet-marker-icon.region4 span{
background: #2171b5;
}

View File

@ -49,7 +49,7 @@
const sharedOptions = { const sharedOptions = {
basic: { basic: {
color: ['#f1eef6','#bdc9e1','#74a9cf','#2b8cbe','#045a8d'], color: ['#9ecae1','#6baed6','#4292c6','#2171b5','#08519c','#08306b'],
grid: { grid: {
top: 20, top: 20,
left: 60, left: 60,
@ -156,6 +156,7 @@ const chartPropsPerRegion = document.getElementById('chart-props-per-region');
const cPropsPerRegion = echarts.init(chartPropsPerRegion); const cPropsPerRegion = echarts.init(chartPropsPerRegion);
const cPropsPerRegionOptions = { const cPropsPerRegionOptions = {
grid: sharedOptions.basic.grid, grid: sharedOptions.basic.grid,
color: sharedOptions.basic.color,
xAxis: { xAxis: {
name: 'Region', name: 'Region',
nameLocation: 'center', nameLocation: 'center',
@ -178,8 +179,13 @@ const cPropsPerRegionOptions = {
series: [ series: [
{ {
data: {!! $propsPerRegion[1] !!}, data: {!! $propsPerRegion[1] !!},
type: 'bar' type: 'bar',
} itemStyle: {
color: (e) => {
return sharedOptions.basic.color[e.dataIndex];
}
}
},
] ]
}; };
@ -193,13 +199,13 @@ const filters = {
} }
const cExtractionsOptions = { const cExtractionsOptions = {
color: sharedOptions.basic.color,
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis'
}, },
legend: { legend: {
data: filters.regions data: filters.regions
}, },
color: sharedOptions.basic.color,
grid: sharedOptions.basic.grid, grid: sharedOptions.basic.grid,
xAxis: { xAxis: {
name: 'Zeitpunkt Scraping', name: 'Zeitpunkt Scraping',
@ -226,30 +232,26 @@ const cExtractionsOptions = {
name: 'Alle', name: 'Alle',
type: 'line', type: 'line',
stack: 'Total', stack: 'Total',
data: {!! json_encode($growth['total_all']) !!} data: {!! json_encode($growth['total_all']) !!},
},
{
name: 'Heidiland',
type: 'line',
stack: 'Heidiland',
data: {!! json_encode($growth['total_heidiland']) !!}
}, },
{ {
name: 'Davos', name: 'Davos',
type: 'line', type: 'line',
stack: 'Davos',
data: {!! json_encode($growth['total_davos']) !!} data: {!! json_encode($growth['total_davos']) !!}
}, },
{ {
name: 'Engadin', name: 'Engadin',
type: 'line', type: 'line',
stack: 'Engadin',
data: {!! json_encode($growth['total_engadin']) !!} data: {!! json_encode($growth['total_engadin']) !!}
}, },
{
name: 'Heidiland',
type: 'line',
data: {!! json_encode($growth['total_heidiland']) !!}
},
{ {
name: 'St. Moritz', name: 'St. Moritz',
type: 'line', type: 'line',
stack: 'St. Moritz',
data: {!! json_encode($growth['total_stmoritz']) !!} data: {!! json_encode($growth['total_stmoritz']) !!}
}, },
] ]
@ -257,19 +259,38 @@ const cExtractionsOptions = {
cExtractions.setOption(cExtractionsOptions); cExtractions.setOption(cExtractionsOptions);
const map = L.map('leaflet').setView([46.862962, 9.535296], 9); const map = L.map('leaflet');
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19, maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map); }).addTo(map);
const properties = {!! json_encode($geo) !!} function icon(id){
properties.forEach( prop => { return L.divIcon({
let coords = prop.coordinates.split(','); className: "region"+id,
L.marker(coords).addTo(map).bindPopup('<a href="/prop/'+prop.id+'">'+prop.coordinates+'</a>'); html: '<span></span>'
})
}
const markers = L.featureGroup([
@foreach($geo as $g)
L.marker([{{ $g['latlng'] }}], {icon: icon({{ $g['region_id'] }})}).bindPopup('<a href="/property/{{ $g['property_id'] }}">{{ $g['latlng'] }}</a>'),
@endforeach
]).addTo(map);
map.fitBounds(markers.getBounds(), {padding: [20,20]})
cHeatmap.on('click', 'series', (e) => {
window.open(`/property/${e.value[1]}?date=${e.value[0]}`, '_self');
}) })
cPropsPerRegion.on('click', 'series', (e) => {
console.log(e.dataIndex);
//window.open(`/property/${e.value[1]}?date=${e.value[0]}`, '_self');
})
</script> </script>
@endsection @endsection

View File

@ -32,6 +32,14 @@
</header> </header>
<div id="chart-calendar"></div> <div id="chart-calendar"></div>
</article> </article>
<article class="header map" style="grid-area: chart5;">
<header>
<h2 id="belegung-title">
Kurzzeitmietobjekte in der Nähe
</h2>
</header>
<div id="chart-map"></div>
</article>
<article class="header" style="grid-area: chart3;"> <article class="header" style="grid-area: chart3;">
<header> <header>
<h2> <h2>
@ -80,7 +88,7 @@ const cTimelineOptions = {
label: { label: {
show: false show: false
} }
} },
}; };
cTimeline.setOption(cTimelineOptions); cTimeline.setOption(cTimelineOptions);
@ -168,6 +176,7 @@ const chartCapacity = document.getElementById('chart-capacity');
const cCapacity = echarts.init(chartCapacity); const cCapacity = echarts.init(chartCapacity);
const cCapacityOptions = { const cCapacityOptions = {
color: ['#9ecae1','#6baed6','#4292c6','#2171b5','#084594'],
legend: { legend: {
data: ['Auslastung Property', 'Auslastung {{ $base['region_name'] }}', 'Auslastung alle Regionen'] data: ['Auslastung Property', 'Auslastung {{ $base['region_name'] }}', 'Auslastung alle Regionen']
}, },
@ -176,7 +185,7 @@ const cCapacityOptions = {
valueFormatter: (value) => value.toFixed(2)+'%' valueFormatter: (value) => value.toFixed(2)+'%'
}, },
grid: { grid: {
top: 20, top: 40,
left: 25, left: 25,
right: 10, right: 10,
bottom: 20, bottom: 20,
@ -348,6 +357,29 @@ cTimeline.on('timelinechanged', (e) => {
}) })
/* Map w/ neighbours*/
const map = L.map('chart-map');
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
function icon(id = 0){
return L.divIcon({
className: "region"+id,
html: '<span></span>'
})
}
const markers = L.featureGroup([
@foreach($neighbours as $n)
L.marker([{{ $n['lat'] }}, {{ $n['lon'] }}], {icon: icon()}).bindPopup('<a href="/property/{{ $n['id'] }}">{{ $n['lat'] }}, {{ $n['lon'] }}</a>'),
@endforeach
]).addTo(map);
map.fitBounds(markers.getBounds(), {padding: [20,20]})
cCapacity.on('click', 'series', (e) => { cCapacity.on('click', 'series', (e) => {
// Switch to correct calendar in the timeline // Switch to correct calendar in the timeline
@ -356,7 +388,6 @@ cCapacity.on('click', 'series', (e) => {
currentIndex: e.dataIndex currentIndex: e.dataIndex
}); });
}); });

View File

@ -17,9 +17,15 @@
<article style="grid-area: timeline;"> <article style="grid-area: timeline;">
<div id="timeline"></div> <div id="timeline"></div>
</article> </article>
<article class="header" style="grid-area: chart6;">
<header>
<h2 id="prediction-title">Auslastung Vorhersage</h2>
</header>
<div id="chart-prediction"></div>
</article>
<article class="header" style="grid-area: chart1;"> <article class="header" style="grid-area: chart1;">
<header> <header>
<h2 id="belegung-title"></h2> <h2 id="belegung-title">Gesamtauslastung</h2>
</header> </header>
<div id="chart-heatmap"></div> <div id="chart-heatmap"></div>
</article> </article>
@ -58,7 +64,7 @@
const sharedOptions = { const sharedOptions = {
basic: { basic: {
color: ['#f1eef6','#bdc9e1','#74a9cf','#2b8cbe','#045a8d'], color: ['#9ecae1','#6baed6','#4292c6','#2171b5','#084594'],
grid: { grid: {
top: 20, top: 20,
left: 60, left: 60,
@ -134,22 +140,90 @@ const cCapacityOptions = {
cCapacity.setOption(cCapacityOptions); cCapacity.setOption(cCapacityOptions);
const chartPrediction = document.getElementById('chart-prediction');
const cPrediction = echarts.init(chartPrediction);
const cPredictionOptions = {
legend: {
data: ['Moving Average', 'Earlier', 'Later']
},
tooltip: {
trigger: 'axis',
valueFormatter: (value) => value.toFixed(2)+'%'
},
grid: {
top: 20,
left: 25,
right: 10,
bottom: 20,
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: {!! json_encode($prediction['dates']) !!},
name: 'Zeitpunkt Scraping',
nameLocation: 'center',
nameGap: 24,
nameTextStyle: {
fontWeight: 'bold',
}
},
yAxis: {
type: 'value',
min: 0,
max: 1,
name: 'Auslastung in Prozent',
nameLocation: 'center',
nameGap: 38,
nameTextStyle: {
fontWeight: 'bold',
}
},
series: [{
name: 'Moving Average',
type: 'line',
symbolSize: 7,
data: {!! json_encode($prediction['movAvg']) !!}
},
{
name: 'Earlier',
type: 'line',
symbolSize: 7,
data: {!! json_encode($prediction['cap_earlierTimeframe']) !!}
},
{
name: 'Later',
type: 'line',
symbolSize: 7,
data: {!! json_encode($prediction['cap_laterTimeframe']) !!}
}]
};
cPrediction.setOption(cPredictionOptions);
const chartHeatmap = document.getElementById('chart-heatmap'); const chartHeatmap = document.getElementById('chart-heatmap');
const cHeatmap = echarts.init(chartHeatmap); const cHeatmap = echarts.init(chartHeatmap);
const cHeatmapOptions = { const cHeatmapOptions = {
animation: false,
tooltip: { tooltip: {
position: 'top' position: 'top'
}, },
grid: { grid: {
top: 30, top: 30,
right: 0, right: 45,
bottom: 0, bottom: 50,
left: 0 left: 5
}, },
dataZoom: [{ dataZoom: [{
type: 'inside' type: 'slider'
} },
], {
type: 'slider',
show: true,
yAxisIndex: 0,
}],
xAxis: { xAxis: {
show: false, show: false,
name: 'Kurzzeitmietobjekt', name: 'Kurzzeitmietobjekt',
@ -196,8 +270,7 @@ const cHeatmapOptions = {
}, },
tooltip: { tooltip: {
formatter: (data) => { formatter: (data) => {
let v = data.value return `Kurzzeitmietobjekte-ID: ${data.data[1]}<br />Datum Scraping: ${data.data[0]}<br/>Auslastung: ${data.data[2].toFixed(2)}%`
return `Kurzzeitmietobjekte-ID: ${data.name}<br />Datum Scraping: ${extractionDates[v[1]]}<br/>Auslastung: ${v[2]} %`
}, },
}, },
emphasis: { emphasis: {

View File

@ -12,18 +12,20 @@ Route::get('/', function () {
$propsPerRegion = Api::propertiesPerRegion(); $propsPerRegion = Api::propertiesPerRegion();
$propsPerRegionName = []; $propsPerRegionName = [];
$propsPerRegionCounts = []; $propsPerRegionCounts = [];
$propsPerRegionId = [];
foreach ($propsPerRegion as $el) { foreach ($propsPerRegion as $el) {
$propsPerRegionName[] = $el['name']; $propsPerRegionName[] = $el['name'];
$propsPerRegionId[] = $el['id'];
$propsPerRegionCounts[] = $el['count_properties']; $propsPerRegionCounts[] = $el['count_properties'];
} }
$propertiesGeo = Api::propertiesGeo(); $propertiesGeo = Api::propertiesGeo();
return view('overview', ["regions" => $regionBase, "regionPropertiesCapacities" => $regionPropertyCapacities, "geo" => $propertiesGeo, "growth" => $propertiesGrowth, "propsPerRegion" => [json_encode($propsPerRegionName), json_encode($propsPerRegionCounts)]]); return view('overview', ["regions" => $regionBase, "regionPropertiesCapacities" => $regionPropertyCapacities, "geo" => $propertiesGeo, "growth" => $propertiesGrowth, "propsPerRegion" => [json_encode($propsPerRegionId), json_encode($propsPerRegionName), json_encode($propsPerRegionCounts)]]);
}); });
Route::get('/prop/{id}', function (int $id) { Route::get('/property/{id}', function (int $id) {
$propertyBase = Api::propertyBase($id); $propertyBase = Api::propertyBase($id);
$calendars = Api::propertyExtractions($id); $calendars = Api::propertyExtractions($id);
@ -90,13 +92,14 @@ Route::get('/region/{id}', function (int $id) {
$regionBaseAll = Api::regionBase(-1); $regionBaseAll = Api::regionBase(-1);
$regionBaseAll[] = ['region_name' => 'Alle Regionen', 'region_id' => -1]; $regionBaseAll[] = ['region_name' => 'Alle Regionen', 'region_id' => -1];
$regionBaseRegion = $id >= 0 ? Api::regionBase($id) : [['region_name' => 'Alle Regionen']]; $regionBaseRegion = $id >= 0 ? Api::regionBase($id) : [['region_name' => 'Alle Regionen']];
$regionMovingAverage = Api::regionMovingAverage($id, '2024-04-25');
$regionPropertiesCapacities = Api::regionPropertiesCapacities($id); $regionPropertiesCapacities = Api::regionPropertiesCapacities($id);
$regionCapacitiesRegion = Api::regionCapacities($id); $regionCapacitiesRegion = Api::regionCapacities($id);
$regionCapacitiesAll = Api::regionCapacities(-1); $regionCapacitiesAll = Api::regionCapacities(-1);
$regionCapacities = [$regionCapacitiesAll, $regionCapacitiesRegion]; $regionCapacities = [$regionCapacitiesAll, $regionCapacitiesRegion];
return view('region', ['regions' => $regionBaseAll, 'region' => $regionBaseRegion, 'region_id' => $id, 'regionCapacities' => $regionCapacities, 'regionPropertiesCapacities' => $regionPropertiesCapacities]); return view('region', ['regions' => $regionBaseAll, 'region' => $regionBaseRegion, 'region_id' => $id, 'regionCapacities' => $regionCapacities, 'regionPropertiesCapacities' => $regionPropertiesCapacities, 'prediction' => $regionMovingAverage]);
}); });

View File

@ -1,30 +1,92 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0" version="25.0.3"> <mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0" version="26.0.5">
<diagram name="Seite-1" id="5abS_fUiar5VuBZXZINZ"> <diagram name="Seite-1" id="chpUGVRRn7alPJZ1I-il">
<mxGraphModel dx="1195" dy="1534" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0"> <mxGraphModel dx="819" dy="1052" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root> <root>
<mxCell id="0" /> <mxCell id="0" />
<mxCell id="1" parent="0" /> <mxCell id="1" parent="0" />
<object placeholders="1" c4Name="REST-API" c4Type="Python (FastAPI)" c4Description="REST Schnittstelle" label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;%c4Name%&lt;/b&gt;&lt;/font&gt;&lt;div&gt;[%c4Type%]&lt;/div&gt;&lt;br&gt;&lt;div&gt;&lt;font style=&quot;font-size: 11px&quot;&gt;&lt;font color=&quot;#cccccc&quot;&gt;%c4Description%&lt;/font&gt;&lt;/div&gt;" id="DRD_0cKAZXVdgcTgqyKr-1"> <object placeholders="1" c4Name="RDBMS" c4Type="Container" c4Technology="DuckDB" c4Description="Aggregierte Daten." label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;%c4Name%&lt;/b&gt;&lt;/font&gt;&lt;div&gt;[%c4Type%:&amp;nbsp;%c4Technology%]&lt;/div&gt;&lt;br&gt;&lt;div&gt;&lt;font style=&quot;font-size: 11px&quot;&gt;&lt;font color=&quot;#E6E6E6&quot;&gt;%c4Description%&lt;/font&gt;&lt;/div&gt;" id="_wAeSdXpbb6KPP4DEc36-2">
<mxCell style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#1061B0;fontColor=#ffffff;align=center;arcSize=10;strokeColor=#0D5091;metaEdit=1;resizable=0;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];" vertex="1" parent="1">
<mxGeometry x="360" y="40" width="240" height="120" as="geometry" />
</mxCell>
</object>
<mxCell id="DRD_0cKAZXVdgcTgqyKr-5" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="DRD_0cKAZXVdgcTgqyKr-2" target="DRD_0cKAZXVdgcTgqyKr-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<object placeholders="1" c4Name="Data" c4Type="Python (Polars)" c4Description="Eigenes Python Package. Enthält Programmcode für das ETL" label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;%c4Name%&lt;/b&gt;&lt;/font&gt;&lt;div&gt;[%c4Type%]&lt;/div&gt;&lt;br&gt;&lt;div&gt;&lt;font style=&quot;font-size: 11px&quot;&gt;&lt;font color=&quot;#cccccc&quot;&gt;%c4Description%&lt;/font&gt;&lt;/div&gt;" id="DRD_0cKAZXVdgcTgqyKr-2">
<mxCell style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#1061B0;fontColor=#ffffff;align=center;arcSize=10;strokeColor=#0D5091;metaEdit=1;resizable=0;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];" vertex="1" parent="1">
<mxGeometry x="40" y="40" width="240" height="120" as="geometry" />
</mxCell>
</object>
<object placeholders="1" c4Name="Datenbank" c4Type="Container" c4Technology="DuckDB" c4Description="Datenbank, welches die aggregierten Daten enthält." label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;%c4Name%&lt;/b&gt;&lt;/font&gt;&lt;div&gt;[%c4Type%:&amp;nbsp;%c4Technology%]&lt;/div&gt;&lt;br&gt;&lt;div&gt;&lt;font style=&quot;font-size: 11px&quot;&gt;&lt;font color=&quot;#E6E6E6&quot;&gt;%c4Description%&lt;/font&gt;&lt;/div&gt;" id="DRD_0cKAZXVdgcTgqyKr-3">
<mxCell style="shape=cylinder3;size=15;whiteSpace=wrap;html=1;boundedLbl=1;rounded=0;labelBackgroundColor=none;fillColor=#23A2D9;fontSize=12;fontColor=#ffffff;align=center;strokeColor=#0E7DAD;metaEdit=1;points=[[0.5,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.5,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];resizable=0;" vertex="1" parent="1"> <mxCell style="shape=cylinder3;size=15;whiteSpace=wrap;html=1;boundedLbl=1;rounded=0;labelBackgroundColor=none;fillColor=#23A2D9;fontSize=12;fontColor=#ffffff;align=center;strokeColor=#0E7DAD;metaEdit=1;points=[[0.5,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.5,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];resizable=0;" vertex="1" parent="1">
<mxGeometry x="40" y="240" width="240" height="120" as="geometry" /> <mxGeometry x="50" y="60" width="240" height="120" as="geometry" />
</mxCell> </mxCell>
</object> </object>
<mxCell id="DRD_0cKAZXVdgcTgqyKr-4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="DRD_0cKAZXVdgcTgqyKr-2" target="DRD_0cKAZXVdgcTgqyKr-3"> <object placeholders="1" c4Name="ETL" c4Type="SQL, Python (Polars)" c4Description="Bereitet Daten mittels algorithmischer&lt;br&gt; Verfahren auf." label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;%c4Name%&lt;/b&gt;&lt;/font&gt;&lt;div&gt;[%c4Type%]&lt;/div&gt;&lt;br&gt;&lt;div&gt;&lt;font style=&quot;font-size: 11px&quot;&gt;&lt;font color=&quot;#cccccc&quot;&gt;%c4Description%&lt;/font&gt;&lt;/div&gt;" id="_wAeSdXpbb6KPP4DEc36-3">
<mxCell style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#1061B0;fontColor=#ffffff;align=center;arcSize=10;strokeColor=#0D5091;metaEdit=1;resizable=0;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];" vertex="1" parent="1">
<mxGeometry x="480" y="60" width="240" height="120" as="geometry" />
</mxCell>
</object>
<mxCell id="_wAeSdXpbb6KPP4DEc36-4" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;dashed=1;dashPattern=8 8;" edge="1" parent="1" source="_wAeSdXpbb6KPP4DEc36-3" target="_wAeSdXpbb6KPP4DEc36-2">
<mxGeometry relative="1" as="geometry" /> <mxGeometry relative="1" as="geometry" />
</mxCell> </mxCell>
<mxCell id="_wAeSdXpbb6KPP4DEc36-5" value="Liest Datenbank" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="_wAeSdXpbb6KPP4DEc36-4">
<mxGeometry x="0.0412" y="1" relative="1" as="geometry">
<mxPoint x="-1" y="-1" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="_wAeSdXpbb6KPP4DEc36-15" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;dashed=1;dashPattern=8 8;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="_wAeSdXpbb6KPP4DEc36-6" target="_wAeSdXpbb6KPP4DEc36-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_wAeSdXpbb6KPP4DEc36-21" value="&lt;div&gt;Führt Abfragen aus&lt;/div&gt;&lt;div&gt;[JSON/HTTPS]&lt;br&gt;&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="_wAeSdXpbb6KPP4DEc36-15">
<mxGeometry x="-0.0541" y="-1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<object placeholders="1" c4Name="Webapplikation" c4Type="PHP (Laravel)" c4Description="Verarbeitet Anfragen von Benutzer:innen" label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;%c4Name%&lt;/b&gt;&lt;/font&gt;&lt;div&gt;[%c4Type%]&lt;/div&gt;&lt;br&gt;&lt;div&gt;&lt;font style=&quot;font-size: 11px&quot;&gt;&lt;font color=&quot;#cccccc&quot;&gt;%c4Description%&lt;/font&gt;&lt;/div&gt;" id="_wAeSdXpbb6KPP4DEc36-6">
<mxCell style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#1061B0;fontColor=#ffffff;align=center;arcSize=10;strokeColor=#0D5091;metaEdit=1;resizable=0;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];" vertex="1" parent="1">
<mxGeometry x="50" y="230" width="240" height="120" as="geometry" />
</mxCell>
</object>
<object placeholders="1" c4Name="Dashboard" c4Type="Container" c4Technology="Apache Echarts" c4Description="Stellt Benutzer:innen Auswertungs-&lt;br&gt;möglichkeiten zur Verfügbarkeit von Kurzzeitmietobjekten." label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;%c4Name%&lt;/b&gt;&lt;/font&gt;&lt;div&gt;[%c4Type%:&amp;nbsp;%c4Technology%]&lt;/div&gt;&lt;br&gt;&lt;div&gt;&lt;font style=&quot;font-size: 11px&quot;&gt;&lt;font color=&quot;#E6E6E6&quot;&gt;%c4Description%&lt;/font&gt;&lt;/div&gt;" id="_wAeSdXpbb6KPP4DEc36-8">
<mxCell style="shape=mxgraph.c4.webBrowserContainer2;whiteSpace=wrap;html=1;boundedLbl=1;rounded=0;labelBackgroundColor=none;strokeColor=#118ACD;fillColor=#23A2D9;strokeColor=#118ACD;strokeColor2=#0E7DAD;fontSize=12;fontColor=#ffffff;align=center;metaEdit=1;points=[[0.5,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.5,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];resizable=0;" vertex="1" parent="1">
<mxGeometry x="480" y="370" width="240" height="160" as="geometry" />
</mxCell>
</object>
<mxCell id="_wAeSdXpbb6KPP4DEc36-10" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;dashed=1;dashPattern=8 8;" edge="1" parent="1" source="_wAeSdXpbb6KPP4DEc36-9" target="_wAeSdXpbb6KPP4DEc36-6">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_wAeSdXpbb6KPP4DEc36-16" value="&lt;div&gt;Besucht Webapplikation&lt;/div&gt;&lt;div&gt;[HTTPS]&lt;br&gt;&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="_wAeSdXpbb6KPP4DEc36-10">
<mxGeometry x="0.1247" y="-2" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="_wAeSdXpbb6KPP4DEc36-11" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;dashed=1;dashPattern=8 8;" edge="1" parent="1" source="_wAeSdXpbb6KPP4DEc36-9" target="_wAeSdXpbb6KPP4DEc36-8">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_wAeSdXpbb6KPP4DEc36-17" value="Betrachtet Auswertungen" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="_wAeSdXpbb6KPP4DEc36-11">
<mxGeometry x="0.2151" y="-1" relative="1" as="geometry">
<mxPoint x="2" as="offset" />
</mxGeometry>
</mxCell>
<object placeholders="1" c4Name="Benutzer:in" c4Type="Person" c4Description="Person welche Auswertungen zur Verfügbarkeit von Kurzzeitmietobjekten in Ferienregionen durchführt." label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;%c4Name%&lt;/b&gt;&lt;/font&gt;&lt;div&gt;[%c4Type%]&lt;/div&gt;&lt;br&gt;&lt;div&gt;&lt;font style=&quot;font-size: 11px&quot;&gt;&lt;font color=&quot;#cccccc&quot;&gt;%c4Description%&lt;/font&gt;&lt;/div&gt;" id="_wAeSdXpbb6KPP4DEc36-9">
<mxCell style="html=1;fontSize=11;dashed=0;whiteSpace=wrap;fillColor=#083F75;strokeColor=#06315C;fontColor=#ffffff;shape=mxgraph.c4.person2;align=center;metaEdit=1;points=[[0.5,0,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0]];resizable=0;" vertex="1" parent="1">
<mxGeometry x="314" y="600" width="200" height="180" as="geometry" />
</mxCell>
</object>
<mxCell id="_wAeSdXpbb6KPP4DEc36-14" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;dashed=1;dashPattern=8 8;" edge="1" parent="1" source="_wAeSdXpbb6KPP4DEc36-13" target="_wAeSdXpbb6KPP4DEc36-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_wAeSdXpbb6KPP4DEc36-20" value="Ruft ETL Verfahren auf" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="_wAeSdXpbb6KPP4DEc36-14">
<mxGeometry x="-0.0667" y="-1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<object placeholders="1" c4Name="FastAPI" c4Type="Python (FastAPI)" c4Description="Stellt aufbereitete Daten via &lt;br&gt;JSON/HTTPS API zur Verfügung." label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;%c4Name%&lt;/b&gt;&lt;/font&gt;&lt;div&gt;[%c4Type%]&lt;/div&gt;&lt;br&gt;&lt;div&gt;&lt;font style=&quot;font-size: 11px&quot;&gt;&lt;font color=&quot;#cccccc&quot;&gt;%c4Description%&lt;/font&gt;&lt;/div&gt;" id="_wAeSdXpbb6KPP4DEc36-13">
<mxCell style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#1061B0;fontColor=#ffffff;align=center;arcSize=10;strokeColor=#0D5091;metaEdit=1;resizable=0;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];" vertex="1" parent="1">
<mxGeometry x="480" y="230" width="240" height="120" as="geometry" />
</mxCell>
</object>
<mxCell id="_wAeSdXpbb6KPP4DEc36-18" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;dashed=1;dashPattern=8 8;" edge="1" parent="1" source="_wAeSdXpbb6KPP4DEc36-6" target="_wAeSdXpbb6KPP4DEc36-8">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_wAeSdXpbb6KPP4DEc36-19" value="&lt;div&gt;Liefert Inhalte zum Webbrowser&amp;nbsp;&lt;/div&gt;&lt;div&gt;von Benutzer:innen&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="_wAeSdXpbb6KPP4DEc36-18">
<mxGeometry x="-0.0888" y="-2" relative="1" as="geometry">
<mxPoint x="5" y="7" as="offset" />
</mxGeometry>
</mxCell>
<object placeholders="1" c4Name="Visual Analytics Tool" c4Type="SystemScopeBoundary" c4Application="Software System" label="&lt;font style=&quot;font-size: 16px&quot;&gt;&lt;b&gt;&lt;div style=&quot;text-align: left&quot;&gt;%c4Name%&lt;/div&gt;&lt;/b&gt;&lt;/font&gt;&lt;div style=&quot;text-align: left&quot;&gt;[%c4Application%]&lt;/div&gt;" id="_wAeSdXpbb6KPP4DEc36-23">
<mxCell style="rounded=1;fontSize=11;whiteSpace=wrap;html=1;dashed=1;arcSize=20;fillColor=none;strokeColor=#666666;fontColor=#333333;labelBackgroundColor=none;align=left;verticalAlign=bottom;labelBorderColor=none;spacingTop=0;spacing=10;dashPattern=8 4;metaEdit=1;rotatable=0;perimeter=rectanglePerimeter;noLabel=0;labelPadding=0;allowArrows=0;connectable=0;expand=0;recursiveResize=0;editable=1;pointerEvents=0;absoluteArcSize=1;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];" vertex="1" parent="1">
<mxGeometry x="30" y="40" width="710" height="540" as="geometry" />
</mxCell>
</object>
</root> </root>
</mxGraphModel> </mxGraphModel>
</diagram> </diagram>

View File

@ -9,6 +9,7 @@ from data import etl_region_capacities_weekdays as etl_rcw
from data import etl_region_movAverage as etl_rmA from data import etl_region_movAverage as etl_rmA
from data import etl_region_properties_capacities as etl_rpc from data import etl_region_properties_capacities as etl_rpc
from data import etl_region_capacities_comparison as etl_rcc from data import etl_region_capacities_comparison as etl_rcc
from data import etl_region_movAverage as etl_rmA
from data import etl_region_properties_capacities as etl_rpc from data import etl_region_properties_capacities as etl_rpc
from fastapi import FastAPI, Response from fastapi import FastAPI, Response

View File

@ -137,6 +137,7 @@ class Database:
return self.connection.sql(""" return self.connection.sql("""
SELECT SELECT
regions.name, regions.name,
regions.id,
COUNT(*) AS count_properties COUNT(*) AS count_properties
FROM FROM
consultancy_d.properties consultancy_d.properties
@ -146,7 +147,8 @@ class Database:
consultancy_d.regions ON regions.id = seeds.region_id consultancy_d.regions ON regions.id = seeds.region_id
GROUP BY GROUP BY
properties.seed_id, properties.seed_id,
regions.name regions.name,
regions.id
ORDER BY ORDER BY
count_properties ASC count_properties ASC
""") """)
@ -427,10 +429,15 @@ class Database:
def properties_geo(self): def properties_geo(self):
return self.connection.sql(""" return self.connection.sql("""
SELECT SELECT
p.id, p.id as property_id,
p.check_data as coordinates p.check_data as latlng,
r.id as region_id
FROM FROM
consultancy_d.properties p consultancy_d.properties p
LEFT JOIN
consultancy_d.seeds s ON s.id = p.seed_id
LEFT JOIN
consultancy_d.regions r ON r.id = s.region_id
""") """)
def properties_geo_seeds(self): def properties_geo_seeds(self):