Compare commits

..

1 Commits

Author SHA1 Message Date
Giò Diani 479feba6c2 refactoring to monorepo 2024-11-28 16:38:18 +01:00
92 changed files with 1537 additions and 63568 deletions

2
.gitignore vendored
View File

@ -23,7 +23,6 @@
*.ipr
.idea/
# eclipse project file
.settings/
.classpath
@ -66,4 +65,3 @@ env3.*/
# duckdb
*.duckdb
/src/mauro/dok/

View File

@ -1,6 +0,0 @@
# Consultancy 2
## Projektstruktur
- 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.

View File

@ -1,16 +0,0 @@
# Install
## Prerequisites
- In order to run this project please install all required software according to the laravel documentation: https://laravel.com/docs/11.x#installing-php
## Configuration & installation
- Make a copy of the .env.example to .env
- Run the following commands:
```bash
composer install && php artisan key:generate && npm i
```
# Run server
```bash
composer run dev
```

View File

@ -1,93 +0,0 @@
<?php
namespace App;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
class Api
{
public function __construct()
{
}
public static function get(string $path, string $query = ''): ?array
{
$endpoint = env('FASTAPI_URI');
$request = $endpoint.$path;
if (Cache::has($request)) {
return Cache::get($request);
}
$get = Http::timeout(800)->get($request);
if($get->successful()){
$result = $get->json();
Cache::put($request, $result);
return $result;
}
return null;
}
public static function propertiesPerRegion()
{
return self::get('/region/properties');
}
public static function propertiesGrowth()
{
return self::get('/properties/growth');
}
public static function propertiesGeo()
{
return self::get('/properties/geo');
}
public static function propertyExtractions(int $id)
{
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
{
return self::get("/property/{$id}/base");
}
public static function regionPropertyCapacities(int $id): mixed
{
return self::get("/region/{$id}/properties/capacities");
}
public static function propertyCapacitiesMonthly(int $id, string $date): mixed
{
return self::get("/property/{$id}/capacities/monthly/{$date}");
}
public static function propertyCapacitiesDaily(int $id, string $date): mixed
{
return self::get("/property/{$id}/capacities/weekdays/{$date}");
}
public static function propertyNeighbours(int $id): mixed
{
return self::get("/property/{$id}/neighbours");
}
public static function regionCapacities(int $id): mixed
{
return self::get("/region/{$id}/capacities");
}
}

View File

@ -1,188 +0,0 @@
/* 1. Use a more-intuitive box-sizing model */
*, *::before, *::after {
box-sizing: border-box;
}
/* 2. Remove default margin */
* {
margin: 0;
font-family: sans-serif;
}
body {
/* 3. Add accessible line-height */
line-height: 1.5;
/* 4. Improve text rendering */
-webkit-font-smoothing: antialiased;
padding: 0 1em;
height: 100vh;
background-image: radial-gradient(73% 147%, #EADFDF 59%, #ECE2DF 100%), radial-gradient(91% 146%, rgba(255,255,255,0.50) 47%, rgba(0,0,0,0.50) 100%);
background-blend-mode: screen;
}
/* 5. Improve media defaults */
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
/* 6. Inherit fonts for form controls */
input, button, textarea, select {
font: inherit;
}
/* 7. Avoid text overflows */
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
/* 8. Improve line wrapping */
p {
text-wrap: pretty;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}
dt{
font-weight: 600;
}
dd + dt{
margin-top: .2em;
}
span + button{
margin-left: .5em;
}
button[popovertarget]{
background: no-repeat center / .3em #5470c6 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 192 512'%3E%3C!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--%3E%3Cpath fill='%23fff' d='M48 80a48 48 0 1 1 96 0A48 48 0 1 1 48 80zM0 224c0-17.7 14.3-32 32-32l64 0c17.7 0 32 14.3 32 32l0 224 32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 512c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0 0-192-32 0c-17.7 0-32-14.3-32-32z'/%3E%3C/svg%3E%0A");
cursor: pointer;
display: inline-block;
width: 1.5em;
height: 1.5em;
border-radius: 50%;
border: 1px solid #fff;
}
button[popovertarget]::before{
color: #fff;
font-weight: 700;
}
button[popovertarget]>span{
position: absolute;
left: -999em;
top: -999em;
}
[popover] {
border: none;
border-radius: 1em;
background: #fff;
padding: 1.5em;
border-radius: var(--small-border);
box-shadow: .0625em .0625em .625em rgba(0, 0, 0, 0.1);
max-width: 40em;
top: 4em;
margin: 0 auto;
}
[popover]::backdrop{
background-color: rgba(0,0,0,.5);
}
/*
9. Create a root stacking context
*/
#root, #__next {
isolation: isolate;
}
nav>ul{
list-style: none;
}
body>header{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3em;
background: #ccc;
z-index: 99;
display: flex;
align-items: center;
padding: 0 1em;
}
main{
width: 100%;
height: 100vh;
padding: 4em 0 1em;
display: grid;
gap: .5em;
}
body.overview main{
grid-template-columns: repeat(8, minmax(1%, 50%));
grid-template-rows: repeat(4, 1fr);
grid-template-areas:
"chart3 chart3 chart3 chart1 chart1 chart1 chart4 chart4"
"chart3 chart3 chart3 chart1 chart1 chart1 chart4 chart4"
"chart3 chart3 chart3 chart2 chart2 chart2 chart4 chart4"
"chart3 chart3 chart3 chart2 chart2 chart2 chart4 chart4"
}
body.property main{
grid-template-columns: repeat(4, minmax(10%, 50%));
grid-template-rows: repeat(3, 1fr) 4em;
grid-template-areas:
"chart2 chart2 chart5 chart5"
"chart1 chart1 chart3 chart4"
"chart1 chart1 chart3 chart4"
"timeline timeline timeline timeline";
}
article{
background: #f9f9f9;
border: .0625em solid #ccc;
box-shadow: 0 5px 10px rgba(154,160,185,.05), 0 15px 40px rgba(166,173,201,.2);
border-radius: .2em;
display: grid;
}
article.header{
grid-template-columns: 100%;
grid-template-rows: minmax(1%, 10%) 1fr;
padding: .5em 1em 1em .5em;
}
article>header{
display: grid;
grid-template-columns: 1fr 1em;
grid-template-rows: 1fr;
}
article>header>h2{
font-size: .8em;
font-weight: 600;
}
@media(max-width: 960px){
body{
height: auto;
}
main{
height: auto;
grid-template-columns: 100%;
grid-template-rows: repeat(4, minmax(20em, 25em));
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,4 +0,0 @@
import * as echarts from 'echarts';
import 'leaflet'
window.echarts = echarts;

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
@vite(['resources/css/app.css', 'resources/js/app.js', 'node_modules/leaflet/dist/leaflet.css'])
</head>
<body class="@yield('body-class')">
<header>
@yield('header')
</header>
<main>
@yield('main')
</main>
</body>
</html>

View File

@ -1,261 +0,0 @@
@extends('base')
@section('body-class', 'overview')
@section('main')
<article class="header" style="grid-area: chart1;">
<header>
<h2>
Anzahl jemals gefundene Kurzzeitmietobjekte pro Region
</h2>
<button popovertarget="pop1">
<span>Erklärungen zum Diagramm</span>
</button>
<div popover id="pop1">
<p>Das Diagram zeigt...</p>
</div>
<div>
</header>
<div id="chart-props-per-region"></div>
</article>
<article class="header" style="grid-area: chart2;">
<header>
<h2>
Entwicklung der Anzahl jemals gefunden Kurzzeitmietobjekte
</h2>
</header>
<div id="extractions"></div>
</article>
<article style="grid-area: chart4;">
<div id="leaflet"></div>
</article>
<article class="header" style="grid-area: chart3;">
<header>
<h2>
Gesamtauslastung
</h2>
</header>
<div id="chart-heatmap"></div>
</article>
<script type="module">
const sharedOptions = {
basic: {
color: ['#f1eef6','#bdc9e1','#74a9cf','#2b8cbe','#045a8d'],
grid: {
top: 20,
left: 60,
right: 0,
bottom: 50
},
name: (opt) => {
return {
name: opt.name,
nameLocation: opt.location,
nameGap: 24,
nameTextStyle: {
fontWeight: 'bold',
},
}
}
}
}
const extractionDates = {!! json_encode($regionPropertiesCapacities['scrapeDates']) !!};
const chartHeatmap = document.getElementById('chart-heatmap');
const cHeatmap = echarts.init(chartHeatmap);
const cHeatmapOptions = {
tooltip: {
position: 'top'
},
grid: {
top: 30,
right: 0,
bottom: 0,
left: 0
},
dataZoom: [{
type: 'inside'
}
],
xAxis: {
show: false,
name: 'Kurzzeitmietobjekt',
type: 'category',
data: extractionDates,
splitArea: {
show: false
},
axisLabel: {
show: true,
}
},
yAxis: {
show: false,
type: 'category',
data: {!! json_encode($regionPropertiesCapacities['property_ids']) !!},
splitArea: {
show: true
}
},
visualMap: {
type: 'piecewise',
min: 0,
max: 100,
calculable: true,
orient: 'horizontal',
left: 'center',
top: 0,
formatter: (v1, v2) => {
return `${v1}${v2}%`;
},
inRange: {
color: sharedOptions.basic.color,
},
},
series: [
{
name: 'Auslastung',
type: 'heatmap',
blurSize: 0,
data: {!! json_encode($regionPropertiesCapacities['values']) !!},
label: {
show: false
},
tooltip: {
formatter: (data) => {
let v = data.value
return `Kurzzeitmietobjekte-ID: ${data.name}<br />Datum Scraping: ${extractionDates[v[1]]}<br/>Auslastung: ${v[2]} %`
},
},
emphasis: {
itemStyle: {
borderColor: '#000',
borderWidth: 2
}
}
}
]
}
cHeatmap.setOption(cHeatmapOptions);
const chartPropsPerRegion = document.getElementById('chart-props-per-region');
const cPropsPerRegion = echarts.init(chartPropsPerRegion);
const cPropsPerRegionOptions = {
grid: sharedOptions.basic.grid,
xAxis: {
name: 'Region',
nameLocation: 'center',
nameGap: 24,
nameTextStyle: {
fontWeight: 'bold',
},
type: 'category',
data: {!! $propsPerRegion[0] !!}
},
yAxis: {
type: 'value',
name: 'Anzahl Kurzzeitmietobjekte',
nameLocation: 'middle',
nameGap: 38,
nameTextStyle: {
fontWeight: 'bold',
},
},
series: [
{
data: {!! $propsPerRegion[1] !!},
type: 'bar'
}
]
};
cPropsPerRegion.setOption(cPropsPerRegionOptions);
const chartExtractions = document.getElementById('extractions');
const cExtractions = echarts.init(chartExtractions);
const filters = {
regions: ["Alle", "Davos", "Engadin", "Heidiland", "St. Moritz"]
}
const cExtractionsOptions = {
tooltip: {
trigger: 'axis'
},
legend: {
data: filters.regions
},
color: sharedOptions.basic.color,
grid: sharedOptions.basic.grid,
xAxis: {
name: 'Zeitpunkt Scraping',
nameLocation: 'center',
nameGap: 24,
nameTextStyle: {
fontWeight: 'bold',
},
type: 'category',
boundaryGap: false,
data: extractionDates
},
yAxis: {
name: 'Anzahl Kurzzeitmietobjekte',
nameLocation: 'center',
nameGap: 38,
nameTextStyle: {
fontWeight: 'bold',
},
type: 'value'
},
series: [
{
name: 'Alle',
type: 'line',
stack: 'Total',
data: {!! json_encode($growth['total_all']) !!}
},
{
name: 'Heidiland',
type: 'line',
stack: 'Heidiland',
data: {!! json_encode($growth['total_heidiland']) !!}
},
{
name: 'Davos',
type: 'line',
stack: 'Davos',
data: {!! json_encode($growth['total_davos']) !!}
},
{
name: 'Engadin',
type: 'line',
stack: 'Engadin',
data: {!! json_encode($growth['total_engadin']) !!}
},
{
name: 'St. Moritz',
type: 'line',
stack: 'St. Moritz',
data: {!! json_encode($growth['total_stmoritz']) !!}
},
]
};
cExtractions.setOption(cExtractionsOptions);
const map = L.map('leaflet').setView([46.862962, 9.535296], 9);
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);
const properties = {!! json_encode($geo) !!}
properties.forEach( prop => {
let coords = prop.coordinates.split(',');
L.marker(coords).addTo(map).bindPopup('<a href="/prop/'+prop.id+'">'+prop.coordinates+'</a>');
})
</script>
@endsection

View File

@ -1,354 +0,0 @@
@extends('base')
@section('body-class', 'property')
@section('header')
<span>Property {{ $base['property_platform_id'] }}</span><button popovertarget="prop-details"></button>
<div popover id="prop-details">
<dl>
<dt>Region</dt>
<dd>{{ $base['region_name'] }}</dd>
<dt>Zum ersten mal gefunden</dt>
<dd>{{ $base['first_found'] }}</dd>
<dt>Zum letzten mal gefunden</dt>
<dd>{{ $base['last_found'] }}</dd>
</dl>
<h2>Kurzzeitmietobjekte in der Nähe</h2>
<ul>
@foreach($neighbours as $n)
<li><a href="/prop/{{ $n['id'] }}">{{ $n['lat'] }}, {{$n['lon']}}</a></li>
@endforeach
</ul>
</div>
@endsection
@section('main')
<article style="grid-area: timeline;">
<div id="timeline"></div>
</article>
<article class="header" style="grid-area: chart1;">
<header>
<h2 id="belegung-title">
Belegung am {{ json_decode($extractiondates)[0] }}
</h2>
</header>
<div id="chart-calendar"></div>
</article>
<article class="header" style="grid-area: chart3;">
<header>
<h2>
Auslastung nach Monat am 2024-04-15T07:06:22
</h2>
</header>
<div id="chart-capacity-monthly">
</div>
</article>
<article class="header" style="grid-area: chart2;">
<header>
<h2>
Entwicklung der Verfügbarkeit
</h2>
<button popovertarget="chart-capacity-popover"></button>
<div id="chart-capacity-popover" popover>
<h2>Erkläung zum Diagramm</h2>
<p>Das Liniendiagramm zeigt, wie sich die insgesamte Verfügbarkeit des Kurzzeitmietobjekts entwickelt hat.</p>
</div>
</header>
<div id="chart-capacity"></div>
</article>
<article class="header" style="grid-area: chart4;">
<header>
<h2>
Auslastung Tage für Monat
</h2>
</header>
<div id="chart-capacity-daily">
</article>
<script type="module">
const chartTimeline = document.getElementById('timeline');
const cTimeline = echarts.init(chartTimeline);
const cTimelineOptions = {
grid: {
show: false,
},
timeline: {
data: {!! $extractiondates !!},
playInterval: 2000,
axisType: 'time',
left: 8,
right: 8,
bottom: 0,
label: {
show: false
}
}
};
cTimeline.setOption(cTimelineOptions);
const chartCapacityMonthly = document.getElementById('chart-capacity-monthly');
const cCapacityMonthly = echarts.init(chartCapacityMonthly);
const cCapacityMonthlyOptions = {
timeline: {
show: false,
data: {!! $extractiondates !!},
axisType: 'time',
},
grid: {
top: 0,
bottom: 25,
left: 70,
right: 10
},
xAxis: {
type: 'value',
max: 100
},
yAxis: {
type: 'category',
},
options: [
@foreach ($capacitiesMonthly as $cM)
{
yAxis: {
data: {!! json_encode($cM['months']) !!}
},
series: [{
type: 'bar',
data: {!! json_encode($cM['capacities']) !!}
}]
},
@endforeach
]
};
cCapacityMonthly.setOption(cCapacityMonthlyOptions);
const chartCapacityDaily = document.getElementById('chart-capacity-daily');
const cCapacityDaily = echarts.init(chartCapacityDaily);
const cCapacityDailyOptions = {
timeline: {
show: false,
data: {!! $extractiondates !!},
axisType: 'time',
},
grid: {
top: 0,
bottom: 25,
left: 70,
right: 10
},
xAxis: {
type: 'value',
max: 100
},
yAxis: {
type: 'category',
},
options: [
@foreach ($capacitiesDaily as $cD)
{
yAxis: {
data: {!! json_encode($cD['weekdays']) !!}
},
series: [{
type: 'bar',
data: {!! json_encode($cD['capacities']) !!}
}]
},
@endforeach
]
};
cCapacityDaily.setOption(cCapacityDailyOptions);
const chartCapacity = document.getElementById('chart-capacity');
const cCapacity = echarts.init(chartCapacity);
const cCapacityOptions = {
tooltip: {
trigger: 'axis',
formatter: 'Datum Scraping: {b}<br />Verfügbarkeit: {c} %'
},
grid: {
top: 20,
left: 25,
right: 10,
bottom: 20,
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: {!! json_encode($capacities['dates']) !!},
name: 'Zeitpunkt Scraping',
nameLocation: 'center',
nameGap: 24,
nameTextStyle: {
fontWeight: 'bold',
}
},
yAxis: {
type: 'value',
min: 0,
max: 100,
name: 'Auslastung in Prozent',
nameLocation: 'center',
nameGap: 38,
nameTextStyle: {
fontWeight: 'bold',
}
},
series: [{
name: 'Auslastung',
type: 'line',
symbolSize: 7,
data: {!! json_encode($capacities['capacities']) !!}
},
{
name: 'Auslastung Region',
type: 'line',
symbolSize: 7,
data: {!! json_encode($capacities['capacities']) !!}
}]
};
cCapacity.setOption(cCapacityOptions);
const chartCalendar = document.getElementById('chart-calendar');
const cCalendar = echarts.init(chartCalendar);
const h2Belegung = document.getElementById('belegung-title');
const cCalendarOptions = {
timeline: {
show: false,
data: {!! $extractiondates !!},
axisType: 'time',
},
visualMap: {
categories: [0,1,2],
inRange: {
color: ['#d95f02', '#7570b3', '#1b9e77']
},
formatter: (cat) => {
switch (cat) {
case 0:
return 'Ausgebucht';
case 1:
return 'Verfügbar (kein Anreisetag)';
case 2:
return 'Verfügbar';
}
},
type: 'piecewise',
orient: 'horizontal',
left: 'center',
top: 0
},
calendar:[
{
orient: 'horizontal',
range: '2024',
top: '15%',
right: 10,
bottom: '65%',
left: 50,
},
{
orient: 'horizontal',
range: '2025',
top: '47%',
right: 10,
bottom: '33%',
left: 50,
},
{
orient: 'horizontal',
range: '2026',
top: '79%',
right: 10,
bottom: '1%',
left: 50,
}
],
options: [
@foreach ($calendar as $c)
{
series: [{
type: 'heatmap',
coordinateSystem: 'calendar',
calendarIndex: 0,
data: {!! json_encode($c) !!}
},
{
type: 'heatmap',
coordinateSystem: 'calendar',
calendarIndex: 1,
data: {!! json_encode($c) !!}
},
{
type: 'heatmap',
coordinateSystem: 'calendar',
calendarIndex: 2,
data: {!! json_encode($c) !!}
}]
},
@endforeach
]
};
cCalendar.setOption(cCalendarOptions);
cTimeline.on('timelinechanged', (e) => {
h2Belegung.innerText = "Belegung am "+cCalendarOptions.timeline.data[e.currentIndex];
// Set markpoint on linechart
let x = cCapacityOptions.xAxis.data[e.currentIndex];
let y = cCapacityOptions.series[0].data[e.currentIndex];
cCapacityMonthly.dispatchAction({
type: 'timelineChange',
currentIndex: e.currentIndex
});
cCapacityDaily.dispatchAction({
type: 'timelineChange',
currentIndex: e.currentIndex
});
cCalendar.dispatchAction({
type: 'timelineChange',
currentIndex: e.currentIndex
});
cCapacity.setOption({
series: {
markPoint: {
data: [{
coord: [x, y]
}]
}
}
});
})
cCapacity.on('click', 'series', (e) => {
// Switch to correct calendar in the timeline
cTimeline.dispatchAction({
type: 'timelineChange',
currentIndex: e.dataIndex
});
});
</script>
@endsection

View File

@ -1,72 +0,0 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Api;
Route::get('/', function () {
$regionPropertyCapacities = Api::regionPropertyCapacities(-1);
$propertiesGrowth = Api::propertiesGrowth();
$propsPerRegion = Api::propertiesPerRegion();
$propsPerRegionName = [];
$propsPerRegionCounts = [];
foreach ($propsPerRegion as $el) {
$propsPerRegionName[] = $el['name'];
$propsPerRegionCounts[] = $el['count_properties'];
}
$propertiesGeo = Api::propertiesGeo();
return view('overview', ["regionPropertiesCapacities" => $regionPropertyCapacities, "geo" => $propertiesGeo, "growth" => $propertiesGrowth, "propsPerRegion" => [json_encode($propsPerRegionName), json_encode($propsPerRegionCounts)]]);
});
Route::get('/prop/{id}', function (int $id) {
$propertyBase = Api::propertyBase($id);
$extractions = Api::propertyExtractions($id);
$propertyCapacities = Api::propertyCapacities($id);
$propertyNeighbours = Api::propertyNeighbours($id);
//$regionCapacities = Api::regionCapacities(-1);
$regionCapacities = [];
$propertyCapacitiesMonthly = [];
$propertyCapacitiesDaily = [];
foreach ($extractions as $extraction) {
$propertyCapacitiesMonthly[] = Api::propertyCapacitiesMonthly($id, $extraction['created_at']);
$propertyCapacitiesDaily[] = Api::propertyCapacitiesDaily($id, $extraction['created_at']);
}
$data = [];
$dates = [];
foreach ($extractions as $ext) {
$series = [];
$dates[] = $ext['created_at'];
$extCalendar = json_decode($ext['calendar'], 1);
foreach ($extCalendar as $date => $status) {
$series[] = [$date, $status];
}
$data[] = $series;
}
return view('property', ['base' => $propertyBase[0], "extractiondates" => json_encode($dates), "calendar" => $data, 'capacities' => $propertyCapacities, 'capacitiesMonthly' => $propertyCapacitiesMonthly, 'capacitiesDaily' => $propertyCapacitiesDaily, 'regionCapacities' => $regionCapacities, 'neighbours' => $propertyNeighbours]);
});
Route::get('/region/{id}', function (int $id) {
$regionCapacities = Api::regionCapacities($id);
dump($regionCapacities);
return view('region', ['capacities' => $regionCapacities]);
});

View File

@ -1,31 +0,0 @@
<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">
<diagram name="Seite-1" id="5abS_fUiar5VuBZXZINZ">
<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">
<root>
<mxCell id="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">
<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">
<mxGeometry x="40" y="240" width="240" height="120" as="geometry" />
</mxCell>
</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">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@ -1,4 +0,0 @@
# How to run
```bash
fastapi dev api/main.py --port 8080
```

1569
etl/pixi.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
[project]
authors = [{name = "Giò Diani", email = "mail@gionathandiani.name"}, {name = "Mauro Stoffel", email = "mauro.stoffel@stud.fhgr.ch"}, {name = "Colin Bolli", email = "colin.bolli@stud.fhgr.ch"}, {name = "Charles Winkler", email = "charles.winkler@stud.fhgr.ch"}]
description = "Datenauferbeitung"
authors = [{name = "Giò Diani", email = "mail@gionathandiani.name"}]
dependencies = []
description = "Add a short description here"
name = "consultancy_2"
requires-python = ">= 3.11"
version = "0.1.0"
@ -24,6 +25,5 @@ pandas = ">=2.2.3,<3"
plotly = ">=5.24.1,<6"
duckdb = ">=1.1.2,<2"
python-dotenv = ">=1.0.1,<2"
fastapi = ">=0.115.4,<0.116"
polars = ">=0.20.26,<2"
pyarrow = ">=18.0.0,<19"

View File

@ -1,82 +0,0 @@
import data
import polars as pl
from data import etl_property_capacities as etl_pc
from data import etl_property_capacities_monthly as etl_pcm
from data import etl_property_capacities_weekdays as etl_pcw
from data import etl_property_neighbours as etl_pn
from data import etl_region_capacities as etl_rc
from data import etl_region_properties_capacities as etl_rpc
from data import etl_region_capacities_comparison as etl_rcc
from fastapi import FastAPI, Response
d = data.load()
app = FastAPI()
@app.get("/")
def read_root():
return {"Hi there!"}
@app.get("/items/{item_id}")
def read_item(item_id: int):
ext = d.extractions_for(item_id).pl()
out = ext.with_columns(pl.col("calendar").str.extract_all(r"([0-9]{4}-[0-9]{2}-[0-9]{2})|[0-2]").alias("calendar_data"))
out = out.drop(['calendar', 'property_id'])
return Response(content=out.write_json(), media_type="application/json")
@app.get("/region/properties")
def properties_region():
return d.properties_per_region().pl().to_dicts()
@app.get("/properties/growth")
def properties_growth():
options = {"dates" : d.properties_growth().pl()['date'].to_list(), "total_all" : d.properties_growth().pl()['total_all'].to_list(), "total_heidiland" : d.properties_growth().pl()['total_heidiland'].to_list(), "total_engadin" : d.properties_growth().pl()['total_engadin'].to_list(), "total_davos" : d.properties_growth().pl()['total_davos'].to_list(), "total_stmoritz" : d.properties_growth().pl()['total_stmoritz'].to_list()}
return options
@app.get("/properties/geo")
def properties_geo():
return d.properties_geo().pl().to_dicts()
@app.get("/property/{id}/neighbours")
def property_neighbours(id: int):
capacities = etl_pn.property_neighbours(id)
return capacities
@app.get("/property/{id}/extractions")
def property_extractions(id: int):
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}/capacities/monthly/{scrapeDate}")
def property_capacities_data(id: int, scrapeDate: str):
capacities = etl_pcm.property_capacities_monthly(id, scrapeDate)
return capacities
@app.get("/property/{id}/capacities/weekdays/{scrapeDate}")
def property_capacities_data(id: int, scrapeDate: str):
capacities = etl_pcw.property_capacities_weekdays(id, scrapeDate)
return capacities
@app.get("/property/{id}/base")
def property_base_data(id: int):
return d.property_base_data(id).pl().to_dicts()
@app.get("/region/{id}/properties/capacities")
def region_property_capacities_data(id: int):
capacities = etl_rpc.region_properties_capacities(id)
return capacities
@app.get("/region/{id}/capacities")
def region_capacities_data(id: int):
capacities = etl_rc.region_capacities(id)
return capacities
@app.get("/region/capacities/comparison/{id_1}/{id_2}")
def region_capacities_data(id_1: int, id_2: int):
capacities = etl_rcc.region_capacities_comparison(id_1, id_2)
return capacities

22
etl/src/dashboard/main.py Normal file
View File

@ -0,0 +1,22 @@
from typing import Union
import polars as pl
from fastapi import FastAPI, Response
import data
d = data.load()
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int):
ext = d.extractions_for(item_id).pl()
out = ext.with_columns(pl.col("calendar").str.extract_all(r"([0-9]{4}-[0-9]{2}-[0-9]{2})|[0-2]").alias("calendar_data"))
out = out.drop(['calendar', 'property_id'])
return Response(content=out.write_json(), media_type="application/json")

View File

@ -28,6 +28,8 @@ class Database:
if(spatial_installed and not spatial_installed[0]):
self.connection.sql("INSTALL spatial")
def db_overview(self):
return self.connection.sql("DESCRIBE;").show()
@ -44,93 +46,13 @@ class Database:
def properties_growth(self):
return self.connection.sql("""
WITH PropertiesALL AS (
SELECT
strftime(created_at, '%Y-%m-%d') AS date,
COUNT(*) as properties_count,
SUM(properties_count) OVER (ORDER BY date) AS total
FROM
consultancy_d.properties p
GROUP BY
date
ORDER BY
date
),
PropertiesR1 AS (
SELECT
strftime(created_at, '%Y-%m-%d') AS date,
COUNT(*) as properties_count,
SUM(properties_count) OVER (ORDER BY date) AS total
FROM
consultancy_d.properties p
WHERE
p.seed_id = 1
GROUP BY
date
ORDER BY
date
),
PropertiesR2 AS (
SELECT
strftime(created_at, '%Y-%m-%d') AS date,
COUNT(*) as properties_count,
SUM(properties_count) OVER (ORDER BY date) AS total
FROM
consultancy_d.properties p
WHERE
p.seed_id = 2
GROUP BY
date
ORDER BY
date
),
PropertiesR3 AS (
SELECT
strftime(created_at, '%Y-%m-%d') AS date,
COUNT(*) as properties_count,
SUM(properties_count) OVER (ORDER BY date) AS total
FROM
consultancy_d.properties p
WHERE
p.seed_id = 3
GROUP BY
date
ORDER BY
date
),
PropertiesR4 AS (
SELECT
strftime(created_at, '%Y-%m-%d') AS date,
COUNT(*) as properties_count,
SUM(properties_count) OVER (ORDER BY date) AS total
FROM
consultancy_d.properties p
WHERE
p.seed_id = 4
GROUP BY
date
ORDER BY
date
)
SELECT
p.date,
p.total AS total_all,
pR1.total as total_heidiland,
pR2.total AS total_davos,
pR3.total AS total_engadin,
pR4.total AS total_stmoritz
strftime(created_at, '%Y-%m-%d') AS date,
COUNT(*) as properties_count
FROM
PropertiesAll p
LEFT JOIN
PropertiesR1 pR1 ON p.date = pR1.date
LEFT JOIN
PropertiesR2 pR2 ON p.date = pR2.date
LEFT JOIN
PropertiesR3 pR3 ON p.date = pR3.date
LEFT JOIN
PropertiesR4 pR4 ON p.date = pR4.date
ORDER BY
p.date
consultancy_d.properties
GROUP BY
date;
""")
def properties_per_region(self):
@ -147,20 +69,6 @@ class Database:
GROUP BY
properties.seed_id,
regions.name
ORDER BY
count_properties ASC
""")
def propIds_with_region(self):
return self.connection.sql("""
SELECT
properties.id, seed_id, regions.name
FROM
consultancy_d.properties
LEFT JOIN
consultancy_d.seeds ON seeds.id = properties.seed_id
LEFT JOIN
consultancy_d.regions ON regions.id = seeds.region_id
""")
def properties_unreachable(self):
@ -288,7 +196,7 @@ class Database:
""")
def extractions(self):
return self.connection.sql("""
return self.connection.sql(f"""
SELECT
JSON_EXTRACT(body, '$.content.days') as calendar,
property_id,
@ -301,54 +209,19 @@ class Database:
property_id
""")
def extractions_with_region(self):
return self.connection.sql("""
SELECT
JSON_EXTRACT(body, '$.content.days') as calendar,
extractions.property_id,
extractions.created_at,
properties.seed_id,
regions.name
FROM
consultancy_d.extractions
LEFT JOIN
consultancy_d.properties ON properties.id = extractions.property_id
LEFT JOIN
consultancy_d.seeds ON seeds.id = properties.seed_id
LEFT JOIN
consultancy_d.regions ON regions.id = seeds.region_id
""")
def extractions_for(self, property_id):
return self.connection.sql(f"""
SELECT
JSON_EXTRACT(body, '$.content.days') as calendar,
property_id,
created_at
FROM
consultancy_d.extractions
WHERE
type == 'calendar' AND
property_id = {property_id} AND
calendar NOT NULL
property_id = {property_id}
ORDER BY
created_at
""")
def extractions_propId_scrapeDate(self, property_id: int, scrape_date: str):
return self.connection.sql(f"""
SELECT
JSON_EXTRACT(body, '$.content.days') as calendar,
created_at
FROM
consultancy_d.extractions
WHERE
type == 'calendar' AND
property_id = {property_id} AND
calendar NOT NULL AND
created_at >= '{scrape_date}'
ORDER BY
created_at
LIMIT 1
property_id
""")
# Anzahl der extrahierten properties pro Exktraktionsvorgang
@ -394,83 +267,3 @@ class Database:
ORDER BY property_id
""")
def property_base_data(self, id):
return self.connection.sql(f"""
SELECT
p.property_platform_id,
p.created_at as first_found,
p.last_found,
r.name as region_name
FROM
consultancy_d.properties p
INNER JOIN consultancy_d.seeds s ON s.id = p.seed_id
INNER JOIN consultancy_d.regions r ON s.region_id = r.id
WHERE
p.id = {id}
""")
def properties_geo(self):
return self.connection.sql("""
SELECT
p.id,
p.check_data as coordinates
FROM
consultancy_d.properties p
""")
def properties_geo_seeds(self):
return self.connection.sql("""
SELECT
p.id,
p.seed_id,
p.check_data as coordinates
FROM
consultancy_d.properties p
""")
def capacity_of_region(self, region_id):
return self.connection.sql(f"""
SELECT
JSON_EXTRACT(body, '$.content.days') as calendarBody,
strftime(extractions.created_at, '%Y-%m-%d') AS ScrapeDate,
extractions.property_id,
FROM
consultancy_d.extractions
LEFT JOIN
consultancy_d.properties ON properties.id = extractions.property_id
WHERE
type == 'calendar' AND
properties.seed_id = {region_id}
""")
def capacity_global(self):
return self.connection.sql(f"""
SELECT
JSON_EXTRACT(body, '$.content.days') as calendarBody,
strftime(extractions.created_at, '%Y-%m-%d') AS ScrapeDate,
extractions.property_id,
FROM
consultancy_d.extractions
LEFT JOIN
consultancy_d.properties ON properties.id = extractions.property_id
WHERE
type == 'calendar'
""")
def capacity_comparison_of_region(self, region_id_1, region_id_2):
return self.connection.sql(f"""
SELECT
JSON_EXTRACT(body, '$.content.days') as calendarBody,
strftime(extractions.created_at, '%Y-%m-%d') AS ScrapeDate,
extractions.property_id,
properties.seed_id
FROM
consultancy_d.extractions
LEFT JOIN
consultancy_d.properties ON properties.id = extractions.property_id
WHERE
type == 'calendar' AND
(properties.seed_id = {region_id_1} OR
properties.seed_id = {region_id_2})
""")

View File

@ -1,39 +0,0 @@
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))
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

View File

@ -1,27 +0,0 @@
from io import StringIO
import polars as pl
import data
d = data.load()
def property_capacities_monthly(id: int, scrapeDate: str):
extractions = d.extractions_propId_scrapeDate(id, scrapeDate).pl()
df_calendar = pl.DataFrame()
for row in extractions.rows(named=True):
scrapeDate = row['created_at']
df_calendar = pl.read_json(StringIO(row['calendar']))
columnTitles = df_calendar.columns
df_calendar = df_calendar.transpose()
df_calendar = df_calendar.with_columns(pl.Series(name="dates", values=columnTitles))
df_calendar = df_calendar.with_columns((pl.col("dates").str.to_date()))
df_calendar = df_calendar.with_columns((pl.col("dates").dt.strftime("%b") + " " + (pl.col("dates").dt.strftime("%Y"))).alias('date_short'))
df_calendar = df_calendar.with_columns((pl.col("dates").dt.strftime("%Y") + " " + (pl.col("dates").dt.strftime("%m"))).alias('dates'))
df_calendar = df_calendar.group_by(['dates', 'date_short']).agg(pl.col("column_0").sum())
df_calendar = df_calendar.sort('dates')
df_calendar = df_calendar.drop('dates')
result = {"scraping-date": scrapeDate, "months": df_calendar['date_short'].to_list(), 'capacities': df_calendar['column_0'].to_list()}
return result

View File

@ -1,33 +0,0 @@
from io import StringIO
import polars as pl
import data
d = data.load()
def property_capacities_weekdays(id: int, scrapeDate: str):
extractions = d.extractions_propId_scrapeDate(id, scrapeDate).pl()
weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
df_calendar = pl.DataFrame()
numWeeks = 0
for row in extractions.rows(named=True):
scrapeDate = row['created_at']
df_calendar = pl.read_json(StringIO(row['calendar']))
columnTitles = df_calendar.columns
df_calendar = df_calendar.transpose()
df_calendar = df_calendar.with_columns(pl.Series(name="dates", values=columnTitles))
df_calendar = df_calendar.with_columns((pl.col("dates").str.to_date()))
numWeeks = round((df_calendar.get_column("dates").max() - df_calendar.get_column("dates").min()).days / 7, 0)
df_calendar = df_calendar.with_columns(pl.col("dates").dt.weekday().alias("weekday_num"))
df_calendar = df_calendar.with_columns(pl.col("dates").dt.strftime("%A").alias("weekday"))
df_calendar = df_calendar.drop("dates")
df_calendar = df_calendar.group_by(["weekday", "weekday_num"]).agg(pl.col("column_0").sum())
df_calendar = df_calendar.with_columns((pl.col("column_0") / numWeeks * 100).alias("column_0"))
df_calendar = df_calendar.sort('weekday_num')
df_calendar = df_calendar.drop('weekday_num')
result = {"scraping-date": scrapeDate, "weekdays": df_calendar['weekday'].to_list(), 'capacities': df_calendar['column_0'].to_list()}
return result

View File

@ -1,66 +0,0 @@
import polars as pl
from math import radians, cos, sin, asin, sqrt, degrees, atan2
import data
d = data.load()
def calcHaversinDistance(latMain, lonMain, lat, lon):
R = 6371
# convert decimal degrees to radians
latMain, lonMain, lat, lon = map(radians, [latMain, lonMain, lat, lon])
# haversine formula
dlon = lonMain - lon
dlat = latMain - lat
a = sin(dlat / 2) ** 2 + cos(lat) * cos(latMain) * sin(dlon / 2) ** 2
c = 2 * asin(sqrt(a)) # 2 * atan2(sqrt(a), sqrt(1-a))
d = R * c
return d
def property_neighbours(id: int):
extractions = d.properties_geo_seeds().pl()
# Get lat, long and region from main property
latMain, lonMain = extractions.filter(pl.col('id') == str(id))['coordinates'][0].split(',')
latMain, lonMain = map(float, [latMain, lonMain])
region = extractions.filter(pl.col('id') == str(id))['seed_id'][0]
# Prefilter the dataframe to only the correct region
extractions = extractions.filter(pl.col('seed_id') == str(region))
extractions = extractions.drop('seed_id')
# Remove main property from DF
extractions = extractions.filter(pl.col('id') != str(id))
# Split coordinate into lat and lon
#extractions = extractions.with_columns((pl.col('coordinates').str.split(','))[0].alias("coordinates")).unnest("fields")
extractions = extractions.with_columns(pl.col("coordinates").str.split_exact(",", 1).struct.rename_fields(["lat", "lon"]).alias("lat/lon")).unnest("lat/lon")
extractions = extractions.drop('coordinates')
extractions = extractions.with_columns(pl.col("lat").cast(pl.Float32))
extractions = extractions.with_columns(pl.col("lon").cast(pl.Float32))
# Calculate distances
distances = []
for row in extractions.rows(named=True):
lat = row['lat']
lon = row['lon']
dist = calcHaversinDistance(latMain, lonMain, lat, lon)
distances.append(dist)
# Add distance to DF
extractions = extractions.with_columns(pl.Series(name="distances", values=distances))
# Sort for distance and give only first 10
extractions = extractions.sort("distances").head(10)
extractions = extractions.drop('distances')
#result = {"ids": extractions['id'].to_list(), "lat": extractions['lat'].to_list(), "lon": extractions['lon'].to_list()}
result = extractions.to_dicts()
return result

View File

@ -1,53 +0,0 @@
from io import StringIO
from datetime import date
import polars as pl
import data
d = data.load()
def region_capacities(id: int):
# Get Data
if id == -1:
extractions = d.capacity_global().pl()
else:
extractions = d.capacity_of_region(id).pl()
# turn PropertyIDs to ints for sorting
extractions = extractions.cast({"property_id": int})
extractions.drop('property_id')
df_dates = pl.DataFrame()
# Get Data from JSON
gridData = []
dayCounts = []
for row in extractions.rows(named=True):
# Return 0 for sum if calendar is null
if row['calendarBody']:
calDF = pl.read_json(StringIO(row['calendarBody']))
sum_hor = calDF.sum_horizontal()[0]
else:
sum_hor = 0
gridData.append([row['ScrapeDate'], sum_hor, calDF.width])
# Create Aggregates of values
df = pl.DataFrame(gridData)
df_count = df.group_by("column_0").agg(pl.col("column_1").count())
df_sum = df.group_by("column_0").agg(pl.col("column_1").sum())
df_numDays = df.group_by("column_0").agg(pl.col("column_2").max())
# Join and rename DF's
df = df_sum.join(df_count, on= 'column_0').join(df_numDays, on= 'column_0')
df = df.rename({"column_0": "ScrapeDate", "column_1": "Sum", "column_1_right": "num_properties", "column_2": "max_value", })
# Calculate normed capacities for each scrapeDate
df = df.with_columns((pl.col("Sum") / pl.col("num_properties") / (pl.col("max_value")*2) * 100).alias("capacity"))
# Sort the date column
df = df.cast({"ScrapeDate": date})
df = df.sort('ScrapeDate')
result = {"capacities": df['capacity'].to_list(), "dates": df['ScrapeDate'].to_list()}
return result

View File

@ -1,68 +0,0 @@
import data
import polars as pl
from io import StringIO
import numpy as np
d = data.load()
def region_capacities_comparison(id_1: int, id_2: int):
fulldf = d.capacity_comparison_of_region(id_1, id_2).pl()
# turn PropertyIDs and seedIDs to ints for sorting and filtering
fulldf = fulldf.cast({"property_id": int})
fulldf = fulldf.cast({"seed_id": int})
df_region1 = fulldf.filter(pl.col("seed_id") == id_1)
df_region2 = fulldf.filter(pl.col("seed_id") == id_2)
df_list = [df_region1, df_region2]
outDictList = []
for df in df_list:
# Get uniques for dates and propIDs and sort them
listOfDates = df.get_column("ScrapeDate").unique().sort()
listOfPropertyIDs = df.get_column("property_id").unique().sort()
# Create DFs from lists to merge later
datesDF = pl.DataFrame(listOfDates).with_row_index("date_index")
propIdDF = pl.DataFrame(listOfPropertyIDs).with_row_index("prop_index")
# Merge Dataframe to generate indices
df = df.join(datesDF, on='ScrapeDate')
df = df.join(propIdDF, on='property_id')
# Drop now useless columns ScrapeDate and property_id
df = df[['ScrapeDate', 'calendarBody', 'date_index', 'prop_index']]
# Calculate grid values
gridData = []
for row in df.rows(named=True):
# Return 0 for sum if calendar is null
if row['calendarBody']:
calDF = pl.read_json(StringIO(row['calendarBody']))
sum_hor = calDF.sum_horizontal()[0]
else:
sum_hor = 0
# With Index
# gridData.append([row['prop_index'], row['date_index'], sum_hor])
# With ScrapeDate
gridData.append([row['ScrapeDate'], row['date_index'], sum_hor])
gridData = np.array(gridData)
# get all values to calculate Max
allValues = gridData[:, 2].astype(int)
maxValue = np.max(allValues)
gridData[:, 2] = (allValues*100)/maxValue
# Return back to list
gridData = gridData.tolist()
# Cast listOfDates to datetime
listOfDates = listOfDates.cast(pl.Date).to_list()
listOfPropertyIDs = listOfPropertyIDs.to_list()
# Create JSON
tempDict = {'scrapeDates': listOfDates, 'property_ids': listOfPropertyIDs, 'values': gridData}
outDictList.append(tempDict)
outDict = {'region1': outDictList[0], 'region2': outDictList[1],}
return outDict
out = region_capacities_comparison(1,2)
print(out)

View File

@ -1,57 +0,0 @@
from io import StringIO
import polars as pl
import data
d = data.load()
def region_properties_capacities(id: int):
# Get Data
if id == -1:
df = d.capacity_global().pl()
else:
df = d.capacity_of_region(id).pl()
# turn PropertyIDs to ints for sorting
df = df.cast({"property_id": int})
# Get uniques for dates and propIDs and sort them
listOfDates = df.get_column("ScrapeDate").unique().sort()
listOfPropertyIDs = df.get_column("property_id").unique().sort()
# Create DFs from lists to merge later
datesDF = pl.DataFrame(listOfDates).with_row_index("date_index")
propIdDF = pl.DataFrame(listOfPropertyIDs).with_row_index("prop_index")
# Merge Dataframe to generate indices
df = df.join(datesDF, on='ScrapeDate')
df = df.join(propIdDF, on='property_id')
# Calculate grid values
gridData = pl.DataFrame(schema=[("scrape_date", pl.String), ("property_id", pl.String), ("sum_hor", pl.Int64)])
for row in df.rows(named=True):
# Return 0 for sum if calendar is null
if row['calendarBody']:
calDF = pl.read_json(StringIO(row['calendarBody']))
sum_hor = calDF.sum_horizontal()[0]
else:
sum_hor = 0
gridData = gridData.vstack(pl.DataFrame({"scrape_date" : row['ScrapeDate'], "property_id": str(row['property_id']), "sum_hor": sum_hor}))
# get the overall maximum sum
maxValue = gridData['sum_hor'].max()
values = []
for row in gridData.rows(named=True):
capacity = (row['sum_hor']*100)/maxValue
values.append((row['scrape_date'], row['property_id'], capacity))
# Cast listOfDates to datetime
listOfDates = listOfDates.cast(pl.Date).to_list()
listOfPropertyIDs = listOfPropertyIDs.cast(pl.String).to_list()
# Create JSON
outDict = {'scrapeDates': listOfDates, 'property_ids': listOfPropertyIDs, 'values': values}
return outDict

215
etl/src/gio/index.html Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,22 @@
import polars as pl
import data
inst = data.load()
test = inst.extractions_for(1).pl()
out = test.with_columns(
pl.col("calendar").str.extract_all(r"([0-9]{4}-[0-9]{2}-[0-9]{2})|[0-2]").alias("extracted_nrs"),
)
out.drop(['calendar', 'property_id'])
ll = out.get_column("extracted_nrs").explode().gather_every(2)
llo = out.get_column("extracted_nrs").explode().gather_every(2, offset=1)
lli = ll.list.concat(llo)
print(ll)
print(lli)

View File

@ -1,121 +0,0 @@
from etl.src import data
import json
import polars as pl
from datetime import datetime
import matplotlib.pyplot as plt
import numpy as np
'''
# Get Data from DB
inst = data.load()
df = inst.extractions_with_region().pl()
print(df)
counter = 0
data = []
for row in df.iter_rows():
property_id = row[1]
created_at = row[2].date()
dict = {'property_id': property_id, 'created_at': created_at, 'name': row[3]}
jsonStr = row[0]
if jsonStr:
calendarDict = json.loads(jsonStr)
for key in calendarDict:
dict[key] = calendarDict[key]
data.append(dict)
dfNew = pl.from_dicts(data)
dfNew.write_csv('results/data_quality.csv')
print(dfNew)
'''
dfNew = pl.read_csv('results/data_quality.csv')
dfNew = dfNew.with_columns(pl.col("created_at").map_elements(lambda x: datetime.strptime(x, "%Y-%m-%d").date()))
# Create Row Means
dfTemp = dfNew
# Temporary Remove leading columns but save for later
prop = dfTemp.get_column('property_id')
dfTemp = dfTemp.drop('property_id')
crea = dfTemp.get_column('created_at')
dfTemp = dfTemp.drop('created_at')
name = dfTemp.get_column('name')
dfTemp = dfTemp.drop('name')
dfTemp = dfTemp.with_columns(sum=pl.sum_horizontal(dfTemp.columns))
sumCol = dfTemp.get_column('sum')
# Create new DF with only property_id, created_at ,Location name and sum
df = pl.DataFrame([prop, crea, name, sumCol])
df = df.sort('created_at')
# Create Full Copy
# 0 = Alles
# 1 = Heidiland
# 2 = Davos
# 3 = Engadin
# 4 = St. Moritz
filterList = ['Alle Regionen', 'Heidiland', 'Davos', 'Engadin', 'St. Moritz']
filter = 4
if filter != 0:
df = df.filter(pl.col("name") == filter)
# Remove Location name
df = df.drop('name')
# Get unique property_ids
propsIDs = df.unique(subset=["property_id"])
propsIDs = propsIDs.get_column("property_id").to_list()
propsIDs.sort()
# create Matrix
matrix = []
for id in propsIDs:
dict = {}
temp = df.filter(pl.col("property_id") == id)
for row in temp.iter_rows():
dict[row[1].strftime('%Y-%m-%d')] = row[2]
matrix.append(dict)
matrix = pl.DataFrame(matrix)
dates = matrix.columns
matrix = matrix.to_numpy()
# normalized
matrix = matrix/1111
yRange = range(len(dates))
xRange = range(len(propsIDs))
matrix = matrix.T
plt.imshow(matrix)
plt.yticks(yRange[::5], dates[::5])
plt.xticks(xRange[::10], propsIDs[::10])
plt.title(filterList[filter])
plt.xlabel("Property ID")
plt.ylabel("Scrape Date")
plt.colorbar()
plt.tight_layout()
# Create DiffMatrix
diffMatrix = np.zeros((len(matrix)-1, len(matrix[0])))
for y in range(len(matrix[0])):
for x in range(len(matrix)-1):
diffMatrix[x][y] = abs(matrix[x][y] - matrix[x+1][y])
plt.figure()
plt.imshow(diffMatrix, cmap="Reds")
plt.yticks(yRange[::5], dates[::5])
plt.xticks(xRange[::10], propsIDs[::10])
plt.title(filterList[filter])
plt.xlabel("Property ID")
plt.ylabel("Scrape Date")
plt.colorbar()
plt.tight_layout()
plt.show()

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,115 +1,34 @@
from etl.src import data
from etl.src.data import etl_pipelines as ep
import data
from data import etl_pipelines as ep
import polars as pl
from datetime import datetime, timedelta
import pandas as pd
'''
# Get Data from DB
#Create Data
inst = data.load()
df = inst.extractions().pl()
df = ep.expansion_Pipeline(df)
df = ep.liveDates_Pipeline(df)
df.write_csv('dok/flatDates.csv')
df.write_csv('dok/liveDates.csv')
print(df)
'''
'''
#Load Data from DF
dfLive = pl.read_csv('dok/liveDates.csv')
dfFlat = pl.read_csv('dok/flatDates.csv')
#Load Data
df = pl.read_csv('dok/liveDates.csv')
# Step 1 Get all occupied dates in live data
dfLive = dfLive.filter(pl.col("calendar_value") == 0)
dfLive = dfLive.with_columns(pl.col("created_at").str.to_date("%Y-%m-%d"))
dfLive = dfLive.with_columns(pl.col("calendar_date").str.to_date("%Y-%m-%d"))
#print(dfLive)
propIds = df.get_column('property_id').unique()
dfFlat = dfFlat.with_columns(pl.col("created_at").str.to_date("%Y-%m-%d"))
dfFlat = dfFlat.with_columns(pl.col("calendar_date").str.to_date("%Y-%m-%d"))
propIds = dfLive.get_column('property_id').unique()
createdAt = dfLive.get_column('created_at').unique()
#print(createdAt)
fullPreorderMatrix = []
createdAt = df.get_column('created_at').unique()
for propId in propIds:
curPreorderList = []
print("Property ID = " + str(propId))
tempPropFlatDf = dfFlat.filter(pl.col("property_id") == propId)
tempPropLiveDf = dfLive.filter(pl.col("property_id") == propId)
allLiveOccupiedDates = tempPropLiveDf.filter(pl.col("calendar_value") == 0).get_column('created_at')
#print("allLiveOccupiedDates = ",allLiveOccupiedDates)
for date in allLiveOccupiedDates:
calLiveDate = tempPropLiveDf.filter(pl.col("created_at") == date).get_column('calendar_date')[0]
#print("Occupied Date = " + str(date), "with Calendar Date =", str(calLiveDate))
numOfScrapedPreordered = 0
foundLastDate = False
for createDate in createdAt:
if date > createDate:
#print("Finding Flat Date with CreateDate =",createDate, "and Calendar Date =", calLiveDate)
tempFlatDf = tempPropFlatDf.filter(pl.col("created_at") == createDate)
tempFlatDf = tempFlatDf.filter(pl.col("calendar_date") == calLiveDate)
#print("tempLiveDf = ", tempFlatDf)
calVal = tempFlatDf.get_column('calendar_value')
if len(calVal) > 0:
if calVal[0] == 0:
# Still Occupied
if not foundLastDate:
numOfScrapedPreordered += 1
else:
# Found last Date where not occupied
foundLastDate = True
#print("number of Scrapes already occupied =", numOfScrapedPreordered)
#break
#else:
#print("Skipped: Live Date = ",date, "Flat Date =",createDate)
#print(propId, date, numOfScrapedPreordered)
curPreorderList.append(numOfScrapedPreordered)
if len(curPreorderList) > 0:
mean = sum(curPreorderList) / len(curPreorderList)
else: mean = 0
#fullPreorderMatrix.append([propId, mean, curPreorderList])
fullPreorderMatrix.append([propId, mean])
print(fullPreorderMatrix)
fullPreoDF = pl.DataFrame(fullPreorderMatrix,orient="row")
fullPreoDF.write_csv('dok/fullPreoDF.csv')
print(fullPreoDF)
'''
# Filter Props to locations and calculate Means per location
inst = data.load()
propDf = inst.propIds_with_region().pl()
print(propDf)
propDf = propDf.select(
pl.col("id").cast(pl.Int64),
pl.col("seed_id").cast(pl.Int64),
)
preoDF = pl.read_csv('dok/fullPreoDF.csv')
preoDF = preoDF.rename({"column_0": "id", "column_1": "meanPreorderScrapeNum"})
for createdAt in createdAt:
temp = df.filter(pl.col("created_at") == createdAt)
temp = temp.filter(pl.col("property_id") == propId)
if temp.shape[0] > 0:
print(temp.get_column('calendar_value')[0])
else:
print(0)
merge = preoDF.join(propDf, how='inner', on='id')
print(merge)
print("Global meanPreorderTime = ",round(merge.get_column("meanPreorderScrapeNum").mean()*3,2))
# 1 = Heidiland
heidi = merge.filter(pl.col("seed_id") == 1)
print("Heidiland meanPreorderTime = ", round(heidi.get_column("meanPreorderScrapeNum").mean()*3,2))
# 2 = Davos
Davos = merge.filter(pl.col("seed_id") == 2)
print("Davos meanPreorderTime = ", (Davos.get_column("meanPreorderScrapeNum").mean()*3,2))
# 3 = Engadin
Engadin = merge.filter(pl.col("seed_id") == 3)
print("Engadin meanPreorderTime = ", round(Engadin.get_column("meanPreorderScrapeNum").mean()*3,2))
# 4 = St. Moritz
Moritz = merge.filter(pl.col("seed_id") == 4)
print("St. Moritz meanPreorderTime = ", round(Moritz.get_column("meanPreorderScrapeNum").mean()*3,2))
#Hier weiter

View File

@ -21,14 +21,14 @@ LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
# DB_CONNECTION=sqlite
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=file
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
@ -38,7 +38,7 @@ BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=file
CACHE_STORE=database
CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
@ -49,11 +49,11 @@ REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
@ -64,5 +64,3 @@ AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
FASTAPI_URI=http://localhost:8080

66
frontend/README.md Normal file
View File

@ -0,0 +1,66 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
### Premium Partners
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[WebReinvent](https://webreinvent.com/)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Jump24](https://jump24.co.uk)**
- **[Redberry](https://redberry.international/laravel/)**
- **[Active Logic](https://activelogic.com)**
- **[byte5](https://byte5.de)**
- **[OP.GG](https://op.gg)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@ -15,7 +15,7 @@ class User extends Authenticatable
/**
* The attributes that are mass assignable.
*
* @var list<string>
* @var array<int, string>
*/
protected $fillable = [
'name',
@ -26,7 +26,7 @@ class User extends Authenticatable
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
* @var array<int, string>
*/
protected $hidden = [
'password',

View File

@ -3,10 +3,7 @@
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": [
"laravel",
"framework"
],
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
@ -71,4 +68,4 @@
},
"minimum-stability": "stable",
"prefer-stable": true
}
}

File diff suppressed because it is too large Load Diff

View File

@ -39,10 +39,10 @@ return [
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,

View File

@ -1,11 +1,10 @@
{
"name": "dashboard",
"name": "frontend",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@patternfly/patternfly": "^6.0.0",
"@picocss/pico": "^2.0.6",
"echarts": "^5.5.1",
"leaflet": "^1.9.4"
@ -443,9 +442,9 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -533,12 +532,6 @@
"node": ">= 8"
}
},
"node_modules/@patternfly/patternfly": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-6.1.0.tgz",
"integrity": "sha512-w+QazL8NHKkg5j01eotblsswKxQQSYB0CN3yBXQL9ScpHdp/fK8M6TqWbKZNRpf+NqhMxcH/om8eR0N/fDCJqw==",
"license": "MIT"
},
"node_modules/@picocss/pico": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@picocss/pico/-/pico-2.0.6.tgz",
@ -560,9 +553,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
"integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz",
"integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==",
"cpu": [
"arm"
],
@ -574,9 +567,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz",
"integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz",
"integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==",
"cpu": [
"arm64"
],
@ -588,9 +581,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz",
"integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz",
"integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==",
"cpu": [
"arm64"
],
@ -602,9 +595,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz",
"integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz",
"integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==",
"cpu": [
"x64"
],
@ -616,9 +609,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz",
"integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz",
"integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==",
"cpu": [
"arm64"
],
@ -630,9 +623,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz",
"integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz",
"integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==",
"cpu": [
"x64"
],
@ -644,9 +637,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz",
"integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz",
"integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==",
"cpu": [
"arm"
],
@ -658,9 +651,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz",
"integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz",
"integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==",
"cpu": [
"arm"
],
@ -672,9 +665,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz",
"integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz",
"integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==",
"cpu": [
"arm64"
],
@ -686,9 +679,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz",
"integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz",
"integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==",
"cpu": [
"arm64"
],
@ -699,24 +692,10 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz",
"integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz",
"integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz",
"integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==",
"cpu": [
"ppc64"
],
@ -728,9 +707,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz",
"integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz",
"integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==",
"cpu": [
"riscv64"
],
@ -742,9 +721,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz",
"integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz",
"integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==",
"cpu": [
"s390x"
],
@ -756,9 +735,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
"integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz",
"integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==",
"cpu": [
"x64"
],
@ -770,9 +749,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz",
"integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz",
"integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==",
"cpu": [
"x64"
],
@ -784,9 +763,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz",
"integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz",
"integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==",
"cpu": [
"arm64"
],
@ -798,9 +777,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz",
"integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz",
"integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==",
"cpu": [
"ia32"
],
@ -812,9 +791,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz",
"integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz",
"integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==",
"cpu": [
"x64"
],
@ -935,9 +914,9 @@
}
},
"node_modules/axios": {
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"version": "1.7.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
"integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -990,9 +969,9 @@
}
},
"node_modules/browserslist": {
"version": "4.24.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz",
"integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==",
"version": "4.24.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
"integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
"dev": true,
"funding": [
{
@ -1010,9 +989,9 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
"node-releases": "^2.0.19",
"caniuse-lite": "^1.0.30001669",
"electron-to-chromium": "^1.5.41",
"node-releases": "^2.0.18",
"update-browserslist-db": "^1.1.1"
},
"bin": {
@ -1033,9 +1012,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001689",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz",
"integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==",
"version": "1.0.30001684",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz",
"integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==",
"dev": true,
"funding": [
{
@ -1344,9 +1323,9 @@
"license": "0BSD"
},
"node_modules/electron-to-chromium": {
"version": "1.5.74",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz",
"integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==",
"version": "1.5.66",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.66.tgz",
"integrity": "sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ==",
"dev": true,
"license": "ISC"
},
@ -1632,9 +1611,9 @@
}
},
"node_modules/is-core-module": {
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz",
"integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==",
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1714,9 +1693,9 @@
}
},
"node_modules/jiti": {
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"version": "1.21.6",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
"dev": true,
"license": "MIT",
"bin": {
@ -1724,9 +1703,9 @@
}
},
"node_modules/laravel-vite-plugin": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.1.1.tgz",
"integrity": "sha512-HMZXpoSs1OR+7Lw1+g4Iy/s3HF3Ldl8KxxYT2Ot8pEB4XB/QRuZeWgDYJdu552UN03YRSRNK84CLC9NzYRtncA==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.0.6.tgz",
"integrity": "sha512-B34OqmZc/rV1KvSjst8SsUm/LKHsuDusw8jiZCIhlnTHXbXnK89JUM9pTJuk6E/Vc/1DT2gX7qNfhipak1WS8w==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1737,10 +1716,10 @@
"clean-orphaned-assets": "bin/clean.js"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
"node": "^18.0.0 || >=20.0.0"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0"
"vite": "^5.0.0"
}
},
"node_modules/leaflet": {
@ -1750,16 +1729,13 @@
"license": "BSD-2-Clause"
},
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
"node": ">=10"
}
},
"node_modules/lines-and-columns": {
@ -1888,9 +1864,9 @@
}
},
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
"dev": true,
"license": "MIT"
},
@ -2118,6 +2094,19 @@
}
}
},
"node_modules/postcss-load-config/node_modules/lilconfig": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/postcss-nested": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
@ -2227,13 +2216,13 @@
}
},
"node_modules/resolve": {
"version": "1.22.9",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz",
"integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==",
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.0",
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
@ -2256,9 +2245,9 @@
}
},
"node_modules/rollup": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz",
"integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz",
"integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2272,25 +2261,24 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.28.1",
"@rollup/rollup-android-arm64": "4.28.1",
"@rollup/rollup-darwin-arm64": "4.28.1",
"@rollup/rollup-darwin-x64": "4.28.1",
"@rollup/rollup-freebsd-arm64": "4.28.1",
"@rollup/rollup-freebsd-x64": "4.28.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.28.1",
"@rollup/rollup-linux-arm-musleabihf": "4.28.1",
"@rollup/rollup-linux-arm64-gnu": "4.28.1",
"@rollup/rollup-linux-arm64-musl": "4.28.1",
"@rollup/rollup-linux-loongarch64-gnu": "4.28.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.28.1",
"@rollup/rollup-linux-riscv64-gnu": "4.28.1",
"@rollup/rollup-linux-s390x-gnu": "4.28.1",
"@rollup/rollup-linux-x64-gnu": "4.28.1",
"@rollup/rollup-linux-x64-musl": "4.28.1",
"@rollup/rollup-win32-arm64-msvc": "4.28.1",
"@rollup/rollup-win32-ia32-msvc": "4.28.1",
"@rollup/rollup-win32-x64-msvc": "4.28.1",
"@rollup/rollup-android-arm-eabi": "4.27.4",
"@rollup/rollup-android-arm64": "4.27.4",
"@rollup/rollup-darwin-arm64": "4.27.4",
"@rollup/rollup-darwin-x64": "4.27.4",
"@rollup/rollup-freebsd-arm64": "4.27.4",
"@rollup/rollup-freebsd-x64": "4.27.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.27.4",
"@rollup/rollup-linux-arm-musleabihf": "4.27.4",
"@rollup/rollup-linux-arm64-gnu": "4.27.4",
"@rollup/rollup-linux-arm64-musl": "4.27.4",
"@rollup/rollup-linux-powerpc64le-gnu": "4.27.4",
"@rollup/rollup-linux-riscv64-gnu": "4.27.4",
"@rollup/rollup-linux-s390x-gnu": "4.27.4",
"@rollup/rollup-linux-x64-gnu": "4.27.4",
"@rollup/rollup-linux-x64-musl": "4.27.4",
"@rollup/rollup-win32-arm64-msvc": "4.27.4",
"@rollup/rollup-win32-ia32-msvc": "4.27.4",
"@rollup/rollup-win32-x64-msvc": "4.27.4",
"fsevents": "~2.3.2"
}
},
@ -2544,9 +2532,9 @@
}
},
"node_modules/tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz",
"integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2559,7 +2547,7 @@
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.21.6",
"lilconfig": "^3.1.3",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",

View File

@ -15,7 +15,6 @@
"vite": "^5.0"
},
"dependencies": {
"@patternfly/patternfly": "^6.0.0",
"@picocss/pico": "^2.0.6",
"echarts": "^5.5.1",
"leaflet": "^1.9.4"

View File

@ -0,0 +1,17 @@
article{
background: #fff;
}
#extractions{
height: 500px;
background: #fff;
}
#test{
height: 500px;
}
#capacity,
#leaflet{
height: 600px;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
@vite(['resources/css/app.css', 'resources/js/app.js', '/home/gio/Code/ConsultancyProject_2_ETL/frontend/node_modules/leaflet/dist/leaflet.css'])
</head>
<body>
<header>Dashboard</header>
<main class="container-fluid">
<div class="grid">
<div class="grid">
<article id="leaflet"></article>
</div>
</div>
<div class="grid">
<div>
<article id="capacity"></article>
</div>
</div>
<div class="grid">
<div>
<article id="extractions"></article>
</div>
<div>
<article id="test"></article>
</div>
</div>
</main>
</body>
</html>

File diff suppressed because one or more lines are too long

7
frontend/routes/web.php Normal file
View File

@ -0,0 +1,7 @@
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('main');
});