First steps Dashboard.
parent
a03ce3d647
commit
1574edea88
|
@ -64,3 +64,5 @@ AWS_BUCKET=
|
||||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
VITE_APP_NAME="${APP_NAME}"
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
FASTAPI_URI=http://localhost:8080
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
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;
|
||||||
|
$get = Http::get($request);
|
||||||
|
|
||||||
|
if($get->successful()){
|
||||||
|
return $get->json();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function propertiesPerRegion()
|
||||||
|
{
|
||||||
|
return self::get('/region/properties');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function propertyExtractions(int $id)
|
||||||
|
{
|
||||||
|
return self::get("/properties/extractions/{$id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,18 @@
|
||||||
|
<!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>
|
||||||
|
<header>
|
||||||
|
<span>Dashboard</span>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
@yield('main')
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,45 +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>
|
|
||||||
<header>
|
|
||||||
<span>Dashboard</span>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<article class="header">
|
|
||||||
<header>
|
|
||||||
<h2>
|
|
||||||
Headline
|
|
||||||
</h2>
|
|
||||||
</header>
|
|
||||||
<p>Lorem Ipsum...</p>
|
|
||||||
</article>
|
|
||||||
<article class="header">
|
|
||||||
<header>
|
|
||||||
<h2>
|
|
||||||
Anzahl Properties p. Extractions
|
|
||||||
</h2>
|
|
||||||
</header>
|
|
||||||
<div id="extractions"></div>
|
|
||||||
</article>
|
|
||||||
<article class="header">
|
|
||||||
<header>
|
|
||||||
<h2>
|
|
||||||
Anzahl Properties p. Extractions
|
|
||||||
</h2>
|
|
||||||
</header>
|
|
||||||
<div id="capacity"></div>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<div id="leaflet"></div>
|
|
||||||
</article>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
@extends('base')
|
||||||
|
@section('main')
|
||||||
|
<article class="header">
|
||||||
|
<header>
|
||||||
|
<h2>
|
||||||
|
Properties pro Region
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<div id="chart-props-per-region"></div>
|
||||||
|
</article>
|
||||||
|
<article class="header">
|
||||||
|
<header>
|
||||||
|
<h2>
|
||||||
|
Anzahl Properties p. Extractions
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<div id="extractions"></div>
|
||||||
|
</article>
|
||||||
|
<article class="header">
|
||||||
|
<header>
|
||||||
|
<h2>
|
||||||
|
Anzahl Properties p. Extractions
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<div id="capacity"></div>
|
||||||
|
</article>
|
||||||
|
<article>
|
||||||
|
<div id="leaflet"></div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
const chartPropsPerRegion = document.getElementById('chart-props-per-region');
|
||||||
|
const cPropsPerRegion = echarts.init(chartPropsPerRegion);
|
||||||
|
const cPropsPerRegionOptions = {
|
||||||
|
grid: {
|
||||||
|
top: 20,
|
||||||
|
left: 30,
|
||||||
|
right: 0,
|
||||||
|
bottom: 20
|
||||||
|
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: {!! $propsPerRegion[0] !!}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: {!! $propsPerRegion[1] !!},
|
||||||
|
type: 'bar'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
cPropsPerRegion.setOption(cPropsPerRegionOptions);
|
||||||
|
|
||||||
|
const chartExtractions = document.getElementById('extractions');
|
||||||
|
const cExtractions = echarts.init(chartExtractions);
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
regions: ["Davos", "Engadin", "Heidiland", "St. Moritz"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const cExtractionsOptions = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: filters.regions
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '0',
|
||||||
|
right: 10,
|
||||||
|
bottom: '0',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: ['2024-04-14','2024-04-15','2024-04-16','2024-04-19','2024-04-22','2024-04-25','2024-04-28','2024-05-01','2024-05-04','2024-05-07','2024-05-10','2024-05-13','2024-05-16','2024-05-19','2024-05-22','2024-05-25','2024-05-28','2024-05-31','2024-06-01','2024-06-04','2024-06-07','2024-06-10','2024-06-13','2024-06-16','2024-06-19','2024-06-22','2024-06-25','2024-06-28','2024-07-01','2024-07-04','2024-07-07','2024-07-10','2024-07-13','2024-07-16','2024-07-19','2024-07-22','2024-07-25','2024-07-28','2024-07-31','2024-08-01','2024-08-04','2024-08-07','2024-08-10','2024-08-13','2024-08-16','2024-08-19','2024-08-22']
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Alle',
|
||||||
|
type: 'line',
|
||||||
|
stack: 'Total',
|
||||||
|
data: [596, 239, 835, 673, 863, 1803, 904, 915, 958, 966, 1001, 1031, 1044, 1055, 1158, 1162, 1181, 1203, 1207, 1214, 1254, 1258, 1264, 1288, 1296, 1305, 1318, 1323, 1330, 1333, 1342, 1350, 1436, 1454, 1461, 1469, 1492, 1504, 1506, 1510, 1512, 1518, 1534, 1535, 1541, 1544, 1500]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Heidiland',
|
||||||
|
type: 'line',
|
||||||
|
stack: 'Heidiland',
|
||||||
|
data: [133,64,197,151,197,417,210,213,215,220,226,239,247,251,251,252,262,275,276,277,281,283,284,286,287,287,287,287,287,287,287,289,290,292,293,294,294,294,295,295,295,296,312,313,313,313,301]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Davos',
|
||||||
|
type: 'line',
|
||||||
|
stack: 'Davos',
|
||||||
|
data: [133,56,189,152,196,409,206,209,209,209,221,223,223,224,226,227,230,234,236,238,250,252,252,259,261,263,267,270,272,272,274,274,277,278,279,281,286,289,289,289,289,290,290,290,293,296,285]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Engadin',
|
||||||
|
type: 'line',
|
||||||
|
stack: 'Engadin',
|
||||||
|
data: [185,73,258,212,278,569,284,289,326,326,340,346,350,355,413,413,413,413,414,415,438,438,442,455,460,463,470,472,477,479,484,486,544,554,558,561,578,585,586,590,592,595,595,595,597,597,583]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'St. Moritz',
|
||||||
|
type: 'line',
|
||||||
|
stack: 'St. Moritz',
|
||||||
|
data: [145,46,191,158,192,408,204,204,208,211,214,223,224,225,268,270,276,281,281,284,285,285,286,288,288,292,294,294,294,295,297,301,325,330,331,333,334,336,336,336,336,337,337,337,338,338,331]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
cExtractions.setOption(cExtractionsOptions);
|
||||||
|
</script>
|
||||||
|
@endsection
|
|
@ -0,0 +1,98 @@
|
||||||
|
@extends('base')
|
||||||
|
@section('main')
|
||||||
|
<article class="header">
|
||||||
|
<header>
|
||||||
|
<h2>
|
||||||
|
Properties pro Region
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<div id="chart-props-per-region"></div>
|
||||||
|
</article>
|
||||||
|
<article class="header">
|
||||||
|
<header>
|
||||||
|
<h2 id="belegung-title">
|
||||||
|
Belegung am {{ json_decode($extractiondates)[0] }}
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<div id="chart-calendar"></div>
|
||||||
|
</article>
|
||||||
|
<script type="module">
|
||||||
|
const chartCalendar = document.getElementById('chart-calendar');
|
||||||
|
const cCalendar = echarts.init(chartCalendar);
|
||||||
|
const h2Belegung = document.getElementById('belegung-title');
|
||||||
|
|
||||||
|
const cCalendarOptions = {
|
||||||
|
timeline: {
|
||||||
|
data: {!! $extractiondates !!},
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
visualMap: {
|
||||||
|
categories: [0,1,2],
|
||||||
|
inRange: {
|
||||||
|
color: ['red', 'purple', 'green']
|
||||||
|
},
|
||||||
|
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: 50,
|
||||||
|
right: 0,
|
||||||
|
left: 50,
|
||||||
|
bottom: "55%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
orient: 'horizontal',
|
||||||
|
range: '2025',
|
||||||
|
right: 100,
|
||||||
|
left: 50,
|
||||||
|
bottom: 60,
|
||||||
|
top: '55%'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
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) !!}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
@endforeach
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
cCalendar.setOption(cCalendarOptions);
|
||||||
|
cCalendar.on('timelinechanged', (e) => {
|
||||||
|
|
||||||
|
h2Belegung.innerText = "Belegung am "+cCalendarOptions.timeline.data[e.currentIndex];
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
@endsection
|
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,41 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use App\Api;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
return view('main');
|
|
||||||
|
$propsPerRegion = Api::propertiesPerRegion();
|
||||||
|
$propsPerRegionName = [];
|
||||||
|
$propsPerRegionCounts = [];
|
||||||
|
|
||||||
|
foreach ($propsPerRegion as $el) {
|
||||||
|
$propsPerRegionName[] = $el['name'];
|
||||||
|
$propsPerRegionCounts[] = $el['count_properties'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('overview', ["propsPerRegion" => [json_encode($propsPerRegionName), json_encode($propsPerRegionCounts)]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::get('/prop/{id}', function (int $id) {
|
||||||
|
|
||||||
|
$extractions = Api::propertyExtractions($id);
|
||||||
|
$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', ["extractiondates" => json_encode($dates), "calendar" => $data]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -2200,9 +2200,7 @@ packages:
|
||||||
name: consultancy-2
|
name: consultancy-2
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
path: .
|
path: .
|
||||||
sha256: c5e1a7be44a8bc92a9119ccc4b7e7e7b3db765694b975b5cd30b288638254471
|
sha256: 878bb6af1502cc9ac71feab6f184f593077f134626d6f8c552e9bcafb178f6b4
|
||||||
requires_dist:
|
|
||||||
- polars
|
|
||||||
requires_python: '>=3.11'
|
requires_python: '>=3.11'
|
||||||
editable: true
|
editable: true
|
||||||
- kind: conda
|
- kind: conda
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
authors = [{name = "Giò Diani", email = "mail@gionathandiani.name"}]
|
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"}]
|
||||||
dependencies = ["polars"]
|
description = "Datenauferbeitung"
|
||||||
description = "Add a short description here"
|
|
||||||
name = "consultancy_2"
|
name = "consultancy_2"
|
||||||
requires-python = ">= 3.11"
|
requires-python = ">= 3.11"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import data
|
||||||
|
import polars as pl
|
||||||
|
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(), "values" : d.properties_growth().pl()['properties_count'].to_list()}
|
||||||
|
return options
|
||||||
|
|
||||||
|
@app.get("/properties/extractions/{id}")
|
||||||
|
def property_extractions(id: int):
|
||||||
|
return d.extractions_for(property_id = id).pl().to_dicts()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
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")
|
|
|
@ -69,6 +69,8 @@ class Database:
|
||||||
GROUP BY
|
GROUP BY
|
||||||
properties.seed_id,
|
properties.seed_id,
|
||||||
regions.name
|
regions.name
|
||||||
|
ORDER BY
|
||||||
|
count_properties ASC
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def propIds_with_region(self):
|
def propIds_with_region(self):
|
||||||
|
@ -208,7 +210,7 @@ class Database:
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def extractions(self):
|
def extractions(self):
|
||||||
return self.connection.sql(f"""
|
return self.connection.sql("""
|
||||||
SELECT
|
SELECT
|
||||||
JSON_EXTRACT(body, '$.content.days') as calendar,
|
JSON_EXTRACT(body, '$.content.days') as calendar,
|
||||||
property_id,
|
property_id,
|
||||||
|
@ -226,7 +228,6 @@ class Database:
|
||||||
return self.connection.sql(f"""
|
return self.connection.sql(f"""
|
||||||
SELECT
|
SELECT
|
||||||
JSON_EXTRACT(body, '$.content.days') as calendar,
|
JSON_EXTRACT(body, '$.content.days') as calendar,
|
||||||
property_id,
|
|
||||||
created_at
|
created_at
|
||||||
FROM
|
FROM
|
||||||
consultancy_d.extractions
|
consultancy_d.extractions
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,8 +1,9 @@
|
||||||
|
import data
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
|
||||||
import data
|
|
||||||
|
|
||||||
inst = data.load()
|
inst = data.load()
|
||||||
|
|
||||||
|
"""
|
||||||
test = inst.extractions_for(1).pl()
|
test = inst.extractions_for(1).pl()
|
||||||
|
|
||||||
out = test.with_columns(
|
out = test.with_columns(
|
||||||
|
@ -11,6 +12,6 @@ out = test.with_columns(
|
||||||
out = out.drop(['calendar', 'property_id'])
|
out = out.drop(['calendar', 'property_id'])
|
||||||
|
|
||||||
print(out.to_dict(as_series=True))
|
print(out.to_dict(as_series=True))
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(inst.price_developement_per_property().pl())
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue