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:
KM-R 2023-09-19 23:45:10 +02:00 committed by GitHub
parent 80f077ee7a
commit 487b2f42d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 900 additions and 732 deletions

View File

@ -47,7 +47,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: chartboost/ruff-action@v1 - uses: chartboost/ruff-action@v1
with: with:
version: 0.0.289 version: 0.0.290
python-requirements: python-requirements:
name: Check Python Requirements name: Check Python Requirements

3
.gitignore vendored
View File

@ -3,6 +3,9 @@
*secrets.json *secrets.json
*secrets_prod.json *secrets_prod.json
# Settings
.vscode/
# Snyk # Snyk
.dccache .dccache

View File

@ -26,7 +26,7 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.0.289 rev: v0.0.290
hooks: hooks:
- id: ruff - id: ruff
args: [--fix, --exit-non-zero-on-fix] args: [--fix, --exit-non-zero-on-fix]

View File

@ -1,3 +0,0 @@
{
"files.eol": "\n"
}

465
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ requires = ["poetry-core"]
target-version = ["py311"] target-version = ["py311"]
[tool.coverage.report] [tool.coverage.report]
exclude_also = ["if __name__ == .__main__.:"] exclude_also = ["if __name__ == .__main__.:", "if not isinstance(engine, Engine):"]
[tool.coverage.run] [tool.coverage.run]
branch = true branch = true
@ -81,7 +81,7 @@ mypy = "^1.5.1"
pandas-stubs = "^2.0.3.230814" pandas-stubs = "^2.0.3.230814"
pip-audit = "^2.6.1" pip-audit = "^2.6.1"
pip-licenses = "^4.3.2" pip-licenses = "^4.3.2"
ruff = "^0.0.289" ruff = "^0.0.290"
types-cachetools = "^5.3.0.6" types-cachetools = "^5.3.0.6"
types-pyOpenSSL = "*" types-pyOpenSSL = "*"
types-requests = "^2.31.0.2" types-requests = "^2.31.0.2"

View File

@ -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;
}

View 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;
}

View 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;
}

View File

@ -1,390 +1,78 @@
"""Dash.""" """Dash."""
import dash_bootstrap_components as dbc import dash_bootstrap_components as dbc
import pandas as pd from dash import Dash, Input, Output, callback, html
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 aki_prj23_transparenzregister.config.config_providers import JsonFileConfigProvider 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__": if __name__ == "__main__":
session = connector.get_session(JsonFileConfigProvider("./secrets.json")) session = connector.get_session(JsonFileConfigProvider("./secrets.json"))
query_finance = session.query( company_df = get_company_data(session)
entities.AnnualFinanceStatement, entities.Company.name, entities.Company.id finance_df = get_finance_data(session)
).join(entities.Company) options = company_df["company_name"].to_dict()
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)
app = Dash( app = Dash(
__name__, external_stylesheets=[dbc.icons.BOOTSTRAP] __name__, external_stylesheets=[dbc.icons.BOOTSTRAP]
) # use dbc for icons ) # use dbc for icons
app.title = "Company Finance Data"
kennzahlen_layout = html.Div(
[
dcc.Graph(
figure=financials_figure(
finance_df, str(company), "annual_finance_statement_ebit"
)
)
]
)
app.layout = html.Div( app.layout = html.Div(
[ className="page_content",
# title header of page children=[
html.Div( create_header(options),
style={ html.Div(id="id-company-header"),
"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"),
],
),
]
) )
@callback( @callback(
Output("select_company", "options"), Input("select_company", "search_value") Output("select_company", "options"), Input("select_company", "search_value")
) )
def update_options(search_value: str) -> list: 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: if not search_value:
raise PreventUpdate return [{"label": o, "value": key} for key, o in options.items()]
return [o for o in options if search_value in o["label"]] 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) app.run_server(debug=True)

View 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

View File

@ -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 @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( 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: ) -> int:
"""Identifies the id of and court. """Identifies the id of and court.

View File

@ -3,6 +3,7 @@ import datetime
import os import os
from collections.abc import Generator from collections.abc import Generator
from inspect import getmembers, isfunction from inspect import getmembers, isfunction
from typing import Any
import pytest import pytest
from sqlalchemy.engine import Engine from sqlalchemy.engine import Engine
@ -49,7 +50,60 @@ def empty_db() -> Generator[Session, None, None]:
@pytest.fixture() @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.""" """Fills a db with some test data."""
empty_db.add_all( empty_db.add_all(
[ [
@ -112,5 +166,13 @@ def full_db(empty_db: Session) -> Session:
] ]
) )
empty_db.commit() 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()) # print(pd.read_sql_table("company", empty_db.bind).to_string())
return empty_db return empty_db

View 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)

View File

@ -168,7 +168,7 @@ def test_get_person_id_value_check(
data_transfer.get_person_id( data_transfer.get_person_id(
firstname, firstname,
surname, surname,
date.fromisoformat(date_str) if date_str else None, # type: ignore date.fromisoformat(date_str) if date_str else None,
full_db, full_db,
) )
@ -941,7 +941,11 @@ def test_add_annual_report_to_unknown_company(
@pytest.mark.parametrize("year", [2023, 2025, 2020]) @pytest.mark.parametrize("year", [2023, 2025, 2020])
@pytest.mark.parametrize("short_term_debt", [2023.2, 2025.5, 2020.5, float("NaN")]) @pytest.mark.parametrize("short_term_debt", [2023.2, 2025.5, 2020.5, float("NaN")])
def test_add_annual_report( 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: ) -> None:
"""Tests the addition of annual financial records.""" """Tests the addition of annual financial records."""
data_transfer.add_annual_report( data_transfer.add_annual_report(
@ -961,34 +965,38 @@ def test_add_annual_report(
df_prior = pd.read_sql_table( df_prior = pd.read_sql_table(
entities.AnnualFinanceStatement.__tablename__, full_db.bind # type: ignore 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.testing.assert_frame_equal(
pd.DataFrame( expected_results,
[
{
"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"),
}
]
),
df_prior, df_prior,
) )