mirror of
https://github.com/fhswf/aki_prj23_transparenzregister.git
synced 2025-04-22 11:42:55 +02:00
update data based on selected company (#122)
Added UI elements to select a company and update shown data depending on chosen company --------- Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
This commit is contained in:
parent
80f077ee7a
commit
487b2f42d1
2
.github/workflows/lint-actions.yaml
vendored
2
.github/workflows/lint-actions.yaml
vendored
@ -47,7 +47,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: chartboost/ruff-action@v1
|
||||
with:
|
||||
version: 0.0.289
|
||||
version: 0.0.290
|
||||
|
||||
python-requirements:
|
||||
name: Check Python Requirements
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,6 +3,9 @@
|
||||
*secrets.json
|
||||
*secrets_prod.json
|
||||
|
||||
# Settings
|
||||
.vscode/
|
||||
|
||||
# Snyk
|
||||
.dccache
|
||||
|
||||
|
@ -26,7 +26,7 @@ repos:
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.289
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,3 +0,0 @@
|
||||
{
|
||||
"files.eol": "\n"
|
||||
}
|
465
poetry.lock
generated
465
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@ requires = ["poetry-core"]
|
||||
target-version = ["py311"]
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_also = ["if __name__ == .__main__.:"]
|
||||
exclude_also = ["if __name__ == .__main__.:", "if not isinstance(engine, Engine):"]
|
||||
|
||||
[tool.coverage.run]
|
||||
branch = true
|
||||
@ -81,7 +81,7 @@ mypy = "^1.5.1"
|
||||
pandas-stubs = "^2.0.3.230814"
|
||||
pip-audit = "^2.6.1"
|
||||
pip-licenses = "^4.3.2"
|
||||
ruff = "^0.0.289"
|
||||
ruff = "^0.0.290"
|
||||
types-cachetools = "^5.3.0.6"
|
||||
types-pyOpenSSL = "*"
|
||||
types-requests = "^2.31.0.2"
|
||||
|
@ -0,0 +1,69 @@
|
||||
.company-header {
|
||||
float: left;
|
||||
background-color: var(--paynes-gray);
|
||||
border: 1px solid;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.company-header .company-header-title {
|
||||
color: white;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stats-wrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
margin-top: 20px;
|
||||
margin-right: 2%;
|
||||
}
|
||||
|
||||
.stats-wrapper .widget-large {
|
||||
background-color: var(--ash-gray);
|
||||
border: 1px solid;
|
||||
border-radius: 10px;
|
||||
width: 45%;
|
||||
min-width: 600px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: 2%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stats-wrapper .widget-large .widget-title {
|
||||
color: var(--raisin-black);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.stats-wrapper .widget-small {
|
||||
background-color: var(--ash-gray);
|
||||
border: 1px solid;
|
||||
border-radius: 10px;
|
||||
width: 15%;
|
||||
min-width: 200px;
|
||||
height: 150px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 2%;
|
||||
}
|
||||
|
||||
.stats-wrapper .widget-small .widget-title {
|
||||
color: var(--raisin-black);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.stats-wrapper .widget-small .widget-content {
|
||||
color: var(--raisin-black);
|
||||
text-align: center;
|
||||
}
|
58
src/aki_prj23_transparenzregister/ui/assets/header.css
Normal file
58
src/aki_prj23_transparenzregister/ui/assets/header.css
Normal file
@ -0,0 +1,58 @@
|
||||
:root {
|
||||
--light: #edefef;
|
||||
--lavender-blush: #f3e8ee;
|
||||
--ash-gray: #bacdb0;
|
||||
--cambridge-blue: #729b79;
|
||||
--paynes-gray: #475b63;
|
||||
--raisin-black: #2e2c2f;
|
||||
}
|
||||
|
||||
.header-wrapper {
|
||||
float:left;
|
||||
background-color: var(--raisin-black);
|
||||
border: 1px solid;
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
min-height: 77px;
|
||||
}
|
||||
|
||||
.header-wrapper .header-title {
|
||||
float: left;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
padding-left: 15px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-wrapper .header-title .bi-house-door-fill {
|
||||
color: white;
|
||||
font-size: x-large;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.header-wrapper .header-title .header-title-text {
|
||||
color: white;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-wrapper .header-search {
|
||||
float: right;
|
||||
width: 400px;
|
||||
margin: 0;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 20px;
|
||||
padding-top: 20px;
|
||||
vertical-align: middle;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.header-wrapper .header-search .header-search-dropdown {
|
||||
overflow: visible;
|
||||
}
|
23
src/aki_prj23_transparenzregister/ui/assets/tabs.css
Normal file
23
src/aki_prj23_transparenzregister/ui/assets/tabs.css
Normal file
@ -0,0 +1,23 @@
|
||||
.tabs {
|
||||
float: left;
|
||||
margin-top: 20px;
|
||||
border: 1px solid;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tabs .tab-style {
|
||||
border-bottom: 1px solid #d6d6d6 !important;
|
||||
padding: 8px !important;
|
||||
background-color: white !important;
|
||||
color: var(--paynes-gray) !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.tabs .selected-tab-style {
|
||||
border-bottom: 1px solid #d6d6d6 !important;
|
||||
border-top: 1px solid #d6d6d6 !important;
|
||||
padding: 8px !important;
|
||||
color: white !important;
|
||||
background-color: var(--paynes-gray) !important;
|
||||
font-weight: bold !important;
|
||||
}
|
@ -1,390 +1,78 @@
|
||||
"""Dash."""
|
||||
|
||||
import dash_bootstrap_components as dbc
|
||||
import pandas as pd
|
||||
import plotly.graph_objs as go
|
||||
from dash import Dash, Input, Output, callback, dash_table, dcc, html
|
||||
from dash.exceptions import PreventUpdate
|
||||
from sqlalchemy.engine import Engine
|
||||
from dash import Dash, Input, Output, callback, html
|
||||
|
||||
from aki_prj23_transparenzregister.config.config_providers import JsonFileConfigProvider
|
||||
from aki_prj23_transparenzregister.utils.sql import connector, entities
|
||||
from aki_prj23_transparenzregister.ui.ui_elements import (
|
||||
create_company_header,
|
||||
create_company_stats,
|
||||
create_header,
|
||||
create_tabs,
|
||||
get_company_data,
|
||||
get_finance_data,
|
||||
)
|
||||
from aki_prj23_transparenzregister.utils.sql import connector
|
||||
|
||||
if __name__ == "__main__":
|
||||
session = connector.get_session(JsonFileConfigProvider("./secrets.json"))
|
||||
query_finance = session.query(
|
||||
entities.AnnualFinanceStatement, entities.Company.name, entities.Company.id
|
||||
).join(entities.Company)
|
||||
|
||||
query_company = session.query(entities.Company, entities.DistrictCourt.name).join(
|
||||
entities.DistrictCourt
|
||||
)
|
||||
engine = session.bind
|
||||
if not isinstance(engine, Engine):
|
||||
raise TypeError
|
||||
|
||||
finance_df: pd.DataFrame = pd.read_sql(str(query_finance), engine)
|
||||
company_df: pd.DataFrame = pd.read_sql(str(query_company), engine)
|
||||
|
||||
select_company_df = company_df[["company_id", "company_name"]]
|
||||
select_company_dropdown = select_company_df.to_dict("records")
|
||||
options = [
|
||||
{"label": i["company_name"], "value": i["company_id"]}
|
||||
for i in select_company_dropdown
|
||||
]
|
||||
|
||||
colors = {
|
||||
"light": "#edefef",
|
||||
"lavender-blush": "#f3e8ee",
|
||||
"ash-gray": "#bacdb0",
|
||||
"cambridge-blue": "#729b79",
|
||||
"paynes-gray": "#475b63",
|
||||
"raisin-black": "#2e2c2f",
|
||||
}
|
||||
|
||||
def financials_figure(
|
||||
finance_df: pd.DataFrame, company: str, metric: str
|
||||
) -> go.Figure:
|
||||
"""Creates plotly line chart for a specific company and a metric."""
|
||||
finance_df = finance_df.loc[finance_df["company_name"] == company]
|
||||
# create figure
|
||||
fig_line = go.Figure()
|
||||
# add trace for company 1
|
||||
fig_line.add_trace(
|
||||
go.Scatter(
|
||||
x=finance_df["annual_finance_statement_date"],
|
||||
y=finance_df[metric],
|
||||
name=company,
|
||||
line_color=colors["raisin-black"],
|
||||
marker_color=colors["raisin-black"],
|
||||
)
|
||||
)
|
||||
# set title and labels
|
||||
fig_line.update_layout(
|
||||
title=metric,
|
||||
xaxis_title="Jahr",
|
||||
yaxis_title="in Mio.€",
|
||||
plot_bgcolor=colors["light"],
|
||||
)
|
||||
return fig_line
|
||||
|
||||
tab_style = {
|
||||
"borderBottom": "1px solid #d6d6d6",
|
||||
"padding": "6px",
|
||||
"backgroundColor": "white",
|
||||
"color": colors["paynes-gray"],
|
||||
"fontWeight": "bold",
|
||||
}
|
||||
|
||||
tab_selected_style = {
|
||||
"borderTop": "1px solid #d6d6d6",
|
||||
"borderBottom": "1px solid #d6d6d6",
|
||||
"padding": "6px",
|
||||
"backgroundColor": colors["paynes-gray"],
|
||||
"color": "white",
|
||||
"fontWeight": "bold",
|
||||
}
|
||||
|
||||
# TBD: get data from database instead of mock data
|
||||
company = 1 # selected company id
|
||||
selected_company = company_df.loc[company_df["company_id"] == company]
|
||||
|
||||
turnover = 123456
|
||||
stock = "1,23"
|
||||
company_data = {
|
||||
"col1": ["Unternehmen", "Straße", "Stadt"],
|
||||
"col2": [
|
||||
selected_company["company_name"][0],
|
||||
selected_company["company_street"][0],
|
||||
str(
|
||||
selected_company["company_zip_code"][0]
|
||||
+ " "
|
||||
+ selected_company["company_city"][0]
|
||||
),
|
||||
],
|
||||
"col3": ["Branche", "Amtsgericht", "Gründungsjahr"],
|
||||
"col4": [
|
||||
selected_company["company_sector"][0],
|
||||
selected_company["district_court_name"][0],
|
||||
"xxx",
|
||||
],
|
||||
}
|
||||
df_company_data = pd.DataFrame(data=company_data)
|
||||
company_df = get_company_data(session)
|
||||
finance_df = get_finance_data(session)
|
||||
options = company_df["company_name"].to_dict()
|
||||
|
||||
app = Dash(
|
||||
__name__, external_stylesheets=[dbc.icons.BOOTSTRAP]
|
||||
) # use dbc for icons
|
||||
|
||||
kennzahlen_layout = html.Div(
|
||||
[
|
||||
dcc.Graph(
|
||||
figure=financials_figure(
|
||||
finance_df, str(company), "annual_finance_statement_ebit"
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
app.title = "Company Finance Data"
|
||||
|
||||
app.layout = html.Div(
|
||||
[
|
||||
# title header of page
|
||||
html.Div(
|
||||
style={
|
||||
"backgroundColor": colors["raisin-black"],
|
||||
"border": "1px solid",
|
||||
},
|
||||
children=[
|
||||
html.I(
|
||||
className="bi bi-house-door-fill",
|
||||
style={
|
||||
"fontSize": 24,
|
||||
"paddingLeft": "10px",
|
||||
"color": "white",
|
||||
"display": "inline-block",
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
),
|
||||
html.H1(
|
||||
children="Transparenzregister für Kapitalgesellschaften",
|
||||
style={
|
||||
"color": "white",
|
||||
"textAlign": "left",
|
||||
"margin": "0",
|
||||
"paddingLeft": "10px",
|
||||
"paddingBottom": "20px",
|
||||
"paddingTop": "20px",
|
||||
"display": "inline-block",
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
),
|
||||
html.Div(
|
||||
dcc.Dropdown(
|
||||
id="select_company",
|
||||
placeholder="Suche nach Unternehmen oder Person",
|
||||
),
|
||||
style={
|
||||
"float": "right",
|
||||
"width": "30%",
|
||||
"margin": "0",
|
||||
"paddingRight": "10px",
|
||||
"paddingBottom": "20px",
|
||||
"paddingTop": "20px",
|
||||
"display": "inline-block",
|
||||
"verticalAlign": "middle",
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
# header company name
|
||||
html.Div(
|
||||
style={"backgroundColor": colors["paynes-gray"], "border": "1px solid"},
|
||||
children=[
|
||||
html.H1(
|
||||
children=selected_company["company_name"][0],
|
||||
style={
|
||||
"color": "white",
|
||||
"fontSize": 30,
|
||||
"textAlign": "left",
|
||||
"margin": "0",
|
||||
"paddingLeft": "20px",
|
||||
"paddingBottom": "20px",
|
||||
"paddingTop": "20px",
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
html.Div(style={"height": "20px"}),
|
||||
html.Div(style={"width": "2%", "display": "inline-block"}),
|
||||
# table basic company information
|
||||
html.Div(
|
||||
style={
|
||||
"backgroundColor": colors["ash-gray"],
|
||||
"border": "1px solid",
|
||||
"border-radius": 10,
|
||||
"width": "45%",
|
||||
"height": "150px",
|
||||
"display": "inline-block",
|
||||
"vertical-align": "top",
|
||||
},
|
||||
children=[
|
||||
html.H5(
|
||||
children="Stammdaten",
|
||||
style={
|
||||
"color": colors["raisin-black"],
|
||||
"fontSize": 16,
|
||||
"textAlign": "center",
|
||||
"margin": "0",
|
||||
"paddingBottom": "10px",
|
||||
"paddingTop": "10px",
|
||||
},
|
||||
),
|
||||
dash_table.DataTable(
|
||||
df_company_data.to_dict("records"),
|
||||
[{"name": i, "id": i} for i in df_company_data.columns],
|
||||
style_table={
|
||||
"width": "80%",
|
||||
"overflowX": "auto",
|
||||
"marginLeft": "auto",
|
||||
"marginRight": "auto",
|
||||
"paddingBottom": "20px",
|
||||
"color": colors["raisin-black"],
|
||||
},
|
||||
# hide header of table
|
||||
css=[
|
||||
{
|
||||
"selector": "tr:first-child",
|
||||
"rule": "display: none",
|
||||
},
|
||||
],
|
||||
style_cell={"textAlign": "center"},
|
||||
style_cell_conditional=[
|
||||
{"if": {"column_id": c}, "fontWeight": "bold"}
|
||||
for c in ["col1", "col3"]
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.Div(style={"width": "2%", "display": "inline-block"}),
|
||||
html.Div(
|
||||
style={
|
||||
"backgroundColor": colors["ash-gray"],
|
||||
"border": "1px solid",
|
||||
"border-radius": 10,
|
||||
"width": "15%",
|
||||
"height": "150px",
|
||||
"display": "inline-block",
|
||||
"vertical-align": "top",
|
||||
},
|
||||
children=[
|
||||
html.H5(
|
||||
children="Stimmung",
|
||||
style={
|
||||
"color": colors["raisin-black"],
|
||||
"fontSize": 16,
|
||||
"textAlign": "center",
|
||||
"margin": "0",
|
||||
"paddingBottom": "10px",
|
||||
"paddingTop": "10px",
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
html.Div(style={"width": "2%", "display": "inline-block"}),
|
||||
html.Div(
|
||||
style={
|
||||
"backgroundColor": colors["ash-gray"],
|
||||
"border": "1px solid",
|
||||
"border-radius": 10,
|
||||
"width": "15%",
|
||||
"height": "150px",
|
||||
"display": "inline-block",
|
||||
"vertical-align": "top",
|
||||
},
|
||||
children=[
|
||||
html.H5(
|
||||
children="Aktienkurs",
|
||||
style={
|
||||
"color": colors["raisin-black"],
|
||||
"fontSize": 16,
|
||||
"textAlign": "center",
|
||||
"margin": "0",
|
||||
"paddingBottom": "10px",
|
||||
"paddingTop": "10px",
|
||||
},
|
||||
),
|
||||
html.H1(
|
||||
children=stock,
|
||||
style={
|
||||
"color": colors["raisin-black"],
|
||||
"textAlign": "center",
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
html.Div(style={"width": "2%", "display": "inline-block"}),
|
||||
html.Div(
|
||||
style={
|
||||
"backgroundColor": colors["ash-gray"],
|
||||
"border": "1px solid",
|
||||
"border-radius": 10,
|
||||
"width": "15%",
|
||||
"height": "150px",
|
||||
"display": "inline-block",
|
||||
"vertical-align": "top",
|
||||
},
|
||||
children=[
|
||||
html.H5(
|
||||
children="Umsatz",
|
||||
style={
|
||||
"color": colors["raisin-black"],
|
||||
"fontSize": 16,
|
||||
"textAlign": "center",
|
||||
"margin": "0",
|
||||
"paddingBottom": "10px",
|
||||
"paddingTop": "10px",
|
||||
},
|
||||
),
|
||||
html.H1(
|
||||
children=turnover,
|
||||
style={
|
||||
"color": colors["raisin-black"],
|
||||
"textAlign": "center",
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
html.Div(style={"width": "2%", "display": "inline-block"}),
|
||||
# ]),
|
||||
html.Div(
|
||||
style={
|
||||
"marginTop": "20px",
|
||||
"border": "1px solid",
|
||||
},
|
||||
children=[
|
||||
dcc.Tabs(
|
||||
id="tabs",
|
||||
value="tab-1",
|
||||
children=[
|
||||
dcc.Tab(
|
||||
label="Kennzahlen",
|
||||
value="tab-1",
|
||||
style=tab_style,
|
||||
selected_style=tab_selected_style,
|
||||
children=[kennzahlen_layout],
|
||||
),
|
||||
dcc.Tab(
|
||||
label="Beteiligte Personen",
|
||||
value="tab-2",
|
||||
style=tab_style,
|
||||
selected_style=tab_selected_style,
|
||||
),
|
||||
dcc.Tab(
|
||||
label="Stimmung",
|
||||
value="tab-3",
|
||||
style=tab_style,
|
||||
selected_style=tab_selected_style,
|
||||
),
|
||||
dcc.Tab(
|
||||
label="Verflechtungen",
|
||||
value="tab-4",
|
||||
style=tab_style,
|
||||
selected_style=tab_selected_style,
|
||||
),
|
||||
],
|
||||
),
|
||||
html.Div(id="tabs-example-content-1"),
|
||||
],
|
||||
),
|
||||
]
|
||||
className="page_content",
|
||||
children=[
|
||||
create_header(options),
|
||||
html.Div(id="id-company-header"),
|
||||
],
|
||||
)
|
||||
|
||||
@callback(
|
||||
Output("select_company", "options"), Input("select_company", "search_value")
|
||||
)
|
||||
def update_options(search_value: str) -> list:
|
||||
"""Update page based on selected company."""
|
||||
"""Update dropdown options based on user input.
|
||||
|
||||
Args:
|
||||
search_value: The input string in the dropdown field entered by the user.
|
||||
|
||||
Returns:
|
||||
The available companies matching the input.
|
||||
"""
|
||||
if not search_value:
|
||||
raise PreventUpdate
|
||||
return [o for o in options if search_value in o["label"]]
|
||||
return [{"label": o, "value": key} for key, o in options.items()]
|
||||
return [
|
||||
{"label": o, "value": key}
|
||||
for key, o in options.items()
|
||||
if search_value.upper() in o.upper()
|
||||
]
|
||||
|
||||
@callback(Output("id-company-header", "children"), Input("select_company", "value"))
|
||||
def update_output(value_chosen: int) -> html:
|
||||
"""Update page based on chosen company.
|
||||
|
||||
Args:
|
||||
value_chosen: Id of the selected company.
|
||||
|
||||
Returns:
|
||||
The html divs of the company page.
|
||||
"""
|
||||
label = options.get(value_chosen)
|
||||
if not label:
|
||||
return ""
|
||||
selected_company = str(label)
|
||||
selected_company_stats = company_df.loc[value_chosen]
|
||||
selected_finance_df = finance_df.loc[finance_df["company_id"] == value_chosen]
|
||||
return (
|
||||
create_company_header(selected_company),
|
||||
create_company_stats(selected_company_stats),
|
||||
create_tabs(selected_finance_df),
|
||||
)
|
||||
|
||||
app.run_server(debug=True)
|
||||
|
331
src/aki_prj23_transparenzregister/ui/ui_elements.py
Normal file
331
src/aki_prj23_transparenzregister/ui/ui_elements.py
Normal file
@ -0,0 +1,331 @@
|
||||
"""Dash elements."""
|
||||
|
||||
import pandas as pd
|
||||
import plotly.graph_objs as go
|
||||
from dash import dash_table, dcc, html
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from aki_prj23_transparenzregister.utils.sql import entities
|
||||
|
||||
COLORS = {
|
||||
"light": "#edefef",
|
||||
"lavender-blush": "#f3e8ee",
|
||||
"ash-gray": "#bacdb0",
|
||||
"cambridge-blue": "#729b79",
|
||||
"paynes-gray": "#475b63",
|
||||
"raisin-black": "#2e2c2f",
|
||||
}
|
||||
|
||||
|
||||
def get_company_data(session: Session) -> pd.DataFrame:
|
||||
"""Creates a session to the database and get's all available company data.
|
||||
|
||||
Args:
|
||||
session: A session connecting to the database.
|
||||
|
||||
Returns:
|
||||
A dataframe containing all available company data including the corresponding district court.
|
||||
"""
|
||||
query_company = session.query(entities.Company, entities.DistrictCourt.name).join(
|
||||
entities.DistrictCourt
|
||||
)
|
||||
engine = session.bind
|
||||
if not isinstance(engine, Engine):
|
||||
raise TypeError
|
||||
|
||||
return pd.read_sql(str(query_company), engine, index_col="company_id")
|
||||
|
||||
|
||||
def get_finance_data(session: Session) -> pd.DataFrame:
|
||||
"""Creates a session to the database and get's all available company data.
|
||||
|
||||
Args:
|
||||
session: A session connecting to the database.
|
||||
|
||||
Returns:
|
||||
A dataframe containing all financial data of all companies.
|
||||
"""
|
||||
query_finance = session.query(
|
||||
entities.AnnualFinanceStatement, entities.Company.name, entities.Company.id
|
||||
).join(entities.Company)
|
||||
|
||||
engine = session.bind
|
||||
if not isinstance(engine, Engine):
|
||||
raise TypeError
|
||||
|
||||
return pd.read_sql(str(query_finance), engine)
|
||||
|
||||
|
||||
def create_header(options: dict) -> html:
|
||||
"""Creates header for dashboard.
|
||||
|
||||
Args:
|
||||
options: A dictionary with company names and ids for the dropdown.
|
||||
|
||||
Returns:
|
||||
The html div to create the page's header including the name of the page and the search for companies.
|
||||
"""
|
||||
return html.Div(
|
||||
className="header-wrapper",
|
||||
children=[
|
||||
html.Div(
|
||||
className="header-title",
|
||||
children=[
|
||||
html.I(
|
||||
className="bi-house-door-fill",
|
||||
),
|
||||
html.H1(
|
||||
className="header-title-text",
|
||||
children="Transparenzregister für Kapitalgesellschaften",
|
||||
),
|
||||
],
|
||||
),
|
||||
html.Div(
|
||||
className="header-search",
|
||||
children=[
|
||||
html.Div(
|
||||
className="header-search-dropdown",
|
||||
children=[
|
||||
dcc.Dropdown(
|
||||
id="select_company",
|
||||
options=[
|
||||
{"label": o, "value": key}
|
||||
for key, o in options.items()
|
||||
],
|
||||
placeholder="Suche nach Unternehmen oder Person",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def create_company_header(selected_company_name: str) -> html:
|
||||
"""Create company header based on selected company.
|
||||
|
||||
Args:
|
||||
selected_company_name: The company name that has been chosen in the dropdown.
|
||||
|
||||
Returns:
|
||||
The html div to create the company header.
|
||||
"""
|
||||
return html.Div(
|
||||
className="company-header",
|
||||
children=[
|
||||
html.H1(
|
||||
className="company-header-title",
|
||||
id="id-company-header-title",
|
||||
children=selected_company_name,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def create_company_stats(selected_company_data: pd.Series) -> html:
|
||||
"""Create company stats.
|
||||
|
||||
Args:
|
||||
selected_company_data: A series containing all company information of the selected company.
|
||||
|
||||
Returns:
|
||||
The html div to create the company stats table and the three small widgets.
|
||||
"""
|
||||
company_data = {
|
||||
"col1": ["Unternehmen", "Straße", "Stadt"],
|
||||
"col2": [
|
||||
selected_company_data["company_name"],
|
||||
selected_company_data["company_street"],
|
||||
str(
|
||||
selected_company_data["company_zip_code"]
|
||||
+ " "
|
||||
+ selected_company_data["company_city"]
|
||||
),
|
||||
],
|
||||
"col3": ["Branche", "Amtsgericht", "Gründungsjahr"],
|
||||
"col4": [
|
||||
selected_company_data["company_sector"],
|
||||
selected_company_data["district_court_name"],
|
||||
"xxx",
|
||||
],
|
||||
}
|
||||
df_company_data = pd.DataFrame(data=company_data)
|
||||
return html.Div(
|
||||
className="stats-wrapper",
|
||||
children=[
|
||||
html.Div(
|
||||
className="widget-large",
|
||||
children=[
|
||||
html.H3(
|
||||
className="widget-title",
|
||||
children="Stammdaten",
|
||||
),
|
||||
dash_table.DataTable(
|
||||
df_company_data.to_dict("records"),
|
||||
[{"name": i, "id": i} for i in df_company_data.columns],
|
||||
style_table={
|
||||
"width": "90%",
|
||||
"marginLeft": "auto",
|
||||
"marginRight": "auto",
|
||||
"paddingBottom": "20px",
|
||||
"color": COLORS["raisin-black"],
|
||||
},
|
||||
# hide header of table
|
||||
css=[
|
||||
{
|
||||
"selector": "tr:first-child",
|
||||
"rule": "display: none",
|
||||
},
|
||||
],
|
||||
style_cell={"textAlign": "center"},
|
||||
style_cell_conditional=[
|
||||
{"if": {"column_id": c}, "fontWeight": "bold"}
|
||||
for c in ["col1", "col3"]
|
||||
],
|
||||
style_data={
|
||||
"whiteSpace": "normal",
|
||||
"height": "auto",
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
html.Div(
|
||||
className="widget-small",
|
||||
children=[
|
||||
html.H3(
|
||||
className="widget-title",
|
||||
children="Stimmung",
|
||||
),
|
||||
],
|
||||
),
|
||||
html.Div(
|
||||
className="widget-small",
|
||||
children=[
|
||||
html.H3(
|
||||
className="widget-title",
|
||||
children="Aktienkurs",
|
||||
),
|
||||
html.H1(
|
||||
className="widget-content",
|
||||
children="123",
|
||||
),
|
||||
],
|
||||
),
|
||||
html.Div(
|
||||
className="widget-small",
|
||||
children=[
|
||||
html.H3(
|
||||
className="widget-title",
|
||||
children="Umsatz",
|
||||
),
|
||||
html.H1(
|
||||
className="widget-content",
|
||||
children="1234",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def create_tabs(selected_finance_df: pd.DataFrame) -> html:
|
||||
"""Create tabs for more company information.
|
||||
|
||||
Args:
|
||||
selected_company_id: Id of the chosen company in the dropdown.
|
||||
selected_finance_df: A dataframe containing all available finance information of the companies.
|
||||
|
||||
Returns:
|
||||
The html div to create the tabs of the company page.
|
||||
"""
|
||||
return html.Div(
|
||||
className="tabs",
|
||||
children=[
|
||||
dcc.Tabs(
|
||||
id="tabs",
|
||||
value="tab-1",
|
||||
children=[
|
||||
dcc.Tab(
|
||||
label="Kennzahlen",
|
||||
value="tab-1",
|
||||
className="tab-style",
|
||||
selected_className="selected-tab-style",
|
||||
children=[kennzahlen_layout(selected_finance_df)],
|
||||
),
|
||||
dcc.Tab(
|
||||
label="Beteiligte Personen",
|
||||
value="tab-2",
|
||||
className="tab-style",
|
||||
selected_className="selected-tab-style",
|
||||
),
|
||||
dcc.Tab(
|
||||
label="Stimmung",
|
||||
value="tab-3",
|
||||
className="tab-style",
|
||||
selected_className="selected-tab-style",
|
||||
),
|
||||
dcc.Tab(
|
||||
label="Verflechtungen",
|
||||
value="tab-4",
|
||||
className="tab-style",
|
||||
selected_className="selected-tab-style",
|
||||
),
|
||||
],
|
||||
),
|
||||
html.Div(id="tabs-example-content-1"),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def kennzahlen_layout(selected_finance_df: pd.DataFrame) -> html:
|
||||
"""Create metrics tab.
|
||||
|
||||
Args:
|
||||
selected_company_id: Id of the chosen company in the dropdown.
|
||||
selected_finance_df: A dataframe containing all available finance information of the companies.
|
||||
|
||||
Returns:
|
||||
The html div to create the metrics tab of the company page.
|
||||
"""
|
||||
return html.Div(
|
||||
[
|
||||
dcc.Graph(
|
||||
figure=financials_figure(
|
||||
selected_finance_df, "annual_finance_statement_ebit"
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def financials_figure(selected_finance_df: pd.DataFrame, metric: str) -> go.Figure:
|
||||
"""Creates plotly line chart for a specific company and a metric.
|
||||
|
||||
Args:
|
||||
selected_finance_df: A dataframe containing all finance information of the selected company.
|
||||
metric: The metric that should be visualized.
|
||||
|
||||
Returns:
|
||||
A plotly figure showing the available metric data of the company.
|
||||
"""
|
||||
# create figure
|
||||
fig_line = go.Figure()
|
||||
# add trace for company 1
|
||||
fig_line.add_trace(
|
||||
go.Scatter(
|
||||
x=selected_finance_df["annual_finance_statement_date"],
|
||||
y=selected_finance_df[metric],
|
||||
line_color=COLORS["raisin-black"],
|
||||
marker_color=COLORS["raisin-black"],
|
||||
)
|
||||
)
|
||||
# set title and labels
|
||||
fig_line.update_layout(
|
||||
title=metric,
|
||||
xaxis_title="Jahr",
|
||||
yaxis_title="in Mio.€",
|
||||
plot_bgcolor=COLORS["light"],
|
||||
)
|
||||
return fig_line
|
@ -124,7 +124,7 @@ def get_district_court_id(name: str, city: str | None, db: Session) -> int:
|
||||
|
||||
@cached(cache=LRUCache(maxsize=2000), key=lambda name, surname, date_of_birth, db: hash((name, surname, date_of_birth))) # type: ignore
|
||||
def get_person_id(
|
||||
name: str, surname: str, date_of_birth: date | str, db: Session
|
||||
name: str, surname: str, date_of_birth: date | str | None, db: Session
|
||||
) -> int:
|
||||
"""Identifies the id of and court.
|
||||
|
||||
|
@ -3,6 +3,7 @@ import datetime
|
||||
import os
|
||||
from collections.abc import Generator
|
||||
from inspect import getmembers, isfunction
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.engine import Engine
|
||||
@ -49,7 +50,60 @@ def empty_db() -> Generator[Session, None, None]:
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def full_db(empty_db: Session) -> Session:
|
||||
def finance_statements() -> list[dict[str, Any]]:
|
||||
"""Creates a list of finance statements."""
|
||||
return [
|
||||
{
|
||||
"id": 1,
|
||||
"company_id": 1,
|
||||
"date": datetime.date.fromisoformat("2023-01-01"),
|
||||
"total_volume": 1000.0,
|
||||
"ebit": 1000.0,
|
||||
"ebitda": 1000.0,
|
||||
"ebit_margin": 1000.0,
|
||||
"total_balance": 1000.0,
|
||||
"equity": 1000.0,
|
||||
"debt": 1000.0,
|
||||
"return_on_equity": 1000.0,
|
||||
"capital_turnover_rate": 1000.0,
|
||||
"current_liabilities": 1000.0,
|
||||
"dividends": float("NaN"),
|
||||
"net_income": float("NaN"),
|
||||
"assets": 1000.0,
|
||||
"long_term_debt": 1000.0,
|
||||
"short_term_debt": 1000.0,
|
||||
"revenue": 1000.0,
|
||||
"cash_flow": 1000.0,
|
||||
"current_assets": 1000.0,
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"company_id": 1,
|
||||
"date": datetime.date.fromisoformat("2022-01-01"),
|
||||
"total_volume": 1100.0,
|
||||
"ebit": 1100.0,
|
||||
"ebitda": 1100.0,
|
||||
"ebit_margin": 1100.0,
|
||||
"total_balance": 1100.0,
|
||||
"equity": 1100.0,
|
||||
"debt": 1100.0,
|
||||
"return_on_equity": 1100.0,
|
||||
"capital_turnover_rate": 1100.0,
|
||||
"current_liabilities": 1100.0,
|
||||
"dividends": float("NaN"),
|
||||
"net_income": float("NaN"),
|
||||
"assets": 1100.0,
|
||||
"long_term_debt": 1100.0,
|
||||
"short_term_debt": 1100.0,
|
||||
"revenue": 1100.0,
|
||||
"cash_flow": 1100.0,
|
||||
"current_assets": 1100.0,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def full_db(empty_db: Session, finance_statements: list[dict[str, Any]]) -> Session:
|
||||
"""Fills a db with some test data."""
|
||||
empty_db.add_all(
|
||||
[
|
||||
@ -112,5 +166,13 @@ def full_db(empty_db: Session) -> Session:
|
||||
]
|
||||
)
|
||||
empty_db.commit()
|
||||
|
||||
empty_db.add_all(
|
||||
[
|
||||
entities.AnnualFinanceStatement(**finance_statement)
|
||||
for finance_statement in finance_statements
|
||||
]
|
||||
)
|
||||
empty_db.commit()
|
||||
# print(pd.read_sql_table("company", empty_db.bind).to_string())
|
||||
return empty_db
|
||||
|
118
tests/ui/ui_elements_test.py
Normal file
118
tests/ui/ui_elements_test.py
Normal file
@ -0,0 +1,118 @@
|
||||
"""Tests for ui elements."""
|
||||
|
||||
import pandas as pd
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from aki_prj23_transparenzregister.ui import ui_elements
|
||||
|
||||
|
||||
def test_import() -> None:
|
||||
"""Checks if an import co ui_elements can be made."""
|
||||
assert ui_elements is not None
|
||||
|
||||
|
||||
def test_get_company_data(full_db: Session) -> None:
|
||||
"""Checks if data from the company and district court tables can be accessed."""
|
||||
company_df = ui_elements.get_company_data(full_db)
|
||||
|
||||
test_data = pd.DataFrame(
|
||||
{
|
||||
"company_id": {0: 1, 1: 2, 2: 3},
|
||||
"company_hr": {0: "HRB 123", 1: "HRB 123", 2: "HRB 12"},
|
||||
"company_court_id": {0: 2, 1: 1, 2: 2},
|
||||
"company_name": {
|
||||
0: "Some Company GmbH",
|
||||
1: "Other Company GmbH",
|
||||
2: "Third Company GmbH",
|
||||
},
|
||||
"company_street": {0: "Sesamstr.", 1: "Sesamstr.", 2: None},
|
||||
"company_zip_code": {0: "12345", 1: "12345", 2: None},
|
||||
"company_city": {0: "TV City", 1: "TV City", 2: None},
|
||||
"company_last_update": {
|
||||
0: "2023-01-01",
|
||||
1: "2023-01-01",
|
||||
2: "2023-01-01",
|
||||
},
|
||||
"company_sector": {0: None, 1: None, 2: None},
|
||||
"district_court_name": {
|
||||
0: "Amtsgericht Dortmund",
|
||||
1: "Amtsgericht Bochum",
|
||||
2: "Amtsgericht Dortmund",
|
||||
},
|
||||
}
|
||||
)
|
||||
test_data = test_data.set_index("company_id")
|
||||
pd.testing.assert_frame_equal(company_df, test_data)
|
||||
|
||||
|
||||
def test_get_finance_data(full_db: Session) -> None:
|
||||
"""Checks if data from the company and finance tables can be accessed."""
|
||||
finance_df = ui_elements.get_finance_data(full_db)
|
||||
test_data = pd.DataFrame(
|
||||
{
|
||||
"annual_finance_statement_id": {0: 1, 1: 2},
|
||||
"annual_finance_statement_company_id": {0: 1, 1: 1},
|
||||
"annual_finance_statement_date": {0: "2023-01-01", 1: "2022-01-01"},
|
||||
"annual_finance_statement_total_volume": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_ebit": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_ebitda": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_ebit_margin": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_total_balance": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_equity": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_debt": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_return_on_equity": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_capital_turnover_rate": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_current_liabilities": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_dividends": {0: None, 1: None},
|
||||
"annual_finance_statement_net_income": {0: None, 1: None},
|
||||
"annual_finance_statement_assets": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_long_term_debt": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_short_term_debt": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_revenue": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_cash_flow": {0: 1000.0, 1: 1100.0},
|
||||
"annual_finance_statement_current_assets": {0: 1000.0, 1: 1100.0},
|
||||
"company_name": {0: "Some Company GmbH", 1: "Some Company GmbH"},
|
||||
"company_id": {0: 1, 1: 1},
|
||||
}
|
||||
)
|
||||
pd.testing.assert_frame_equal(finance_df, test_data)
|
||||
|
||||
|
||||
def test_create_header() -> None:
|
||||
"""Checks if the header can be created."""
|
||||
options = {1: "a", 2: "b"}
|
||||
ui_elements.create_header(options)
|
||||
|
||||
|
||||
def test_create_company_header() -> None:
|
||||
"""Checks if the company header can be created."""
|
||||
selected_company = "Test GmbH"
|
||||
ui_elements.create_company_header(selected_company)
|
||||
|
||||
|
||||
def test_create_company_stats(full_db: Session) -> None:
|
||||
"""Checks if the company widgets can be created."""
|
||||
company_df = ui_elements.get_company_data(full_db)
|
||||
value_chosen = 1
|
||||
selected_company_stats = company_df.loc[value_chosen]
|
||||
ui_elements.create_company_stats(selected_company_stats)
|
||||
|
||||
|
||||
def test_create_tabs(full_db: Session) -> None:
|
||||
"""Checks if the tabs of the company page can be created."""
|
||||
selected_company_id = 1
|
||||
finance_df = ui_elements.get_finance_data(full_db)
|
||||
selected_finance_df = finance_df.loc[
|
||||
finance_df["company_id"] == selected_company_id
|
||||
]
|
||||
ui_elements.create_tabs(selected_finance_df)
|
||||
|
||||
|
||||
def test_kennzahlen_layout(full_db: Session) -> None:
|
||||
"""Checks if the financial metric layout of the company page can be created."""
|
||||
selected_company_id = 1
|
||||
finance_df = ui_elements.get_finance_data(full_db)
|
||||
selected_finance_df = finance_df.loc[
|
||||
finance_df["company_id"] == selected_company_id
|
||||
]
|
||||
ui_elements.kennzahlen_layout(selected_finance_df)
|
@ -168,7 +168,7 @@ def test_get_person_id_value_check(
|
||||
data_transfer.get_person_id(
|
||||
firstname,
|
||||
surname,
|
||||
date.fromisoformat(date_str) if date_str else None, # type: ignore
|
||||
date.fromisoformat(date_str) if date_str else None,
|
||||
full_db,
|
||||
)
|
||||
|
||||
@ -941,7 +941,11 @@ def test_add_annual_report_to_unknown_company(
|
||||
@pytest.mark.parametrize("year", [2023, 2025, 2020])
|
||||
@pytest.mark.parametrize("short_term_debt", [2023.2, 2025.5, 2020.5, float("NaN")])
|
||||
def test_add_annual_report(
|
||||
short_term_debt: float, company_id: int, year: int, full_db: Session
|
||||
short_term_debt: float,
|
||||
company_id: int,
|
||||
year: int,
|
||||
finance_statements: list[dict[str, Any]],
|
||||
full_db: Session,
|
||||
) -> None:
|
||||
"""Tests the addition of annual financial records."""
|
||||
data_transfer.add_annual_report(
|
||||
@ -961,34 +965,38 @@ def test_add_annual_report(
|
||||
df_prior = pd.read_sql_table(
|
||||
entities.AnnualFinanceStatement.__tablename__, full_db.bind # type: ignore
|
||||
)
|
||||
expected_results = pd.DataFrame(
|
||||
finance_statements
|
||||
+ [
|
||||
{
|
||||
"id": 3,
|
||||
"company_id": company_id,
|
||||
"date": pd.to_datetime(date(year, 1, 1)),
|
||||
"total_volume": float("NaN"),
|
||||
"ebit": 123.0,
|
||||
"ebitda": 235.0,
|
||||
"ebit_margin": float("NaN"),
|
||||
"total_balance": float("NaN"),
|
||||
"equity": float("NaN"),
|
||||
"debt": float("NaN"),
|
||||
"return_on_equity": float("NaN"),
|
||||
"capital_turnover_rate": float("NaN"),
|
||||
"current_liabilities": float("NaN"),
|
||||
"dividends": float("NaN"),
|
||||
"net_income": float("NaN"),
|
||||
"assets": float("NaN"),
|
||||
"long_term_debt": float("NaN"),
|
||||
"short_term_debt": short_term_debt,
|
||||
"revenue": float("NaN"),
|
||||
"cash_flow": float("NaN"),
|
||||
"current_assets": float("NaN"),
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
expected_results["date"] = pd.to_datetime(expected_results["date"])
|
||||
pd.testing.assert_frame_equal(
|
||||
pd.DataFrame(
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"company_id": company_id,
|
||||
"date": pd.to_datetime(date(year, 1, 1)),
|
||||
"total_volume": float("NaN"),
|
||||
"ebit": 123.0,
|
||||
"ebitda": 235.0,
|
||||
"ebit_margin": float("NaN"),
|
||||
"total_balance": float("NaN"),
|
||||
"equity": float("NaN"),
|
||||
"debt": float("NaN"),
|
||||
"return_on_equity": float("NaN"),
|
||||
"capital_turnover_rate": float("NaN"),
|
||||
"current_liabilities": float("NaN"),
|
||||
"dividends": float("NaN"),
|
||||
"net_income": float("NaN"),
|
||||
"assets": float("NaN"),
|
||||
"long_term_debt": float("NaN"),
|
||||
"short_term_debt": short_term_debt,
|
||||
"revenue": float("NaN"),
|
||||
"cash_flow": float("NaN"),
|
||||
"current_assets": float("NaN"),
|
||||
}
|
||||
]
|
||||
),
|
||||
expected_results,
|
||||
df_prior,
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user