diff --git a/poetry.lock b/poetry.lock index ed74015..35bf87b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. [[package]] name = "accelerate" @@ -2207,13 +2207,13 @@ files = [ [[package]] name = "ipykernel" -version = "6.26.0" +version = "6.27.0" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" files = [ - {file = "ipykernel-6.26.0-py3-none-any.whl", hash = "sha256:3ba3dc97424b87b31bb46586b5167b3161b32d7820b9201a9e698c71e271602c"}, - {file = "ipykernel-6.26.0.tar.gz", hash = "sha256:553856658eb8430bbe9653ea041a41bff63e9606fc4628873fc92a6cf3abd404"}, + {file = "ipykernel-6.27.0-py3-none-any.whl", hash = "sha256:4388caa3c2cba0a381e20d289545e88a8aef1fe57a884d4c018718ec8c23c121"}, + {file = "ipykernel-6.27.0.tar.gz", hash = "sha256:7f4986f606581be73bfb32dc7a1ac9fa0e804c9be50ddf1c7a119413e982693f"}, ] [package.dependencies] @@ -2551,13 +2551,13 @@ jupyter-server = ">=1.1.2" [[package]] name = "jupyter-server" -version = "2.10.1" +version = "2.11.0" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server-2.10.1-py3-none-any.whl", hash = "sha256:20519e355d951fc5e1b6ac5952854fe7620d0cfb56588fa4efe362a758977ed3"}, - {file = "jupyter_server-2.10.1.tar.gz", hash = "sha256:e6da2657a954a7879eed28cc08e0817b01ffd81d7eab8634660397b55f926472"}, + {file = "jupyter_server-2.11.0-py3-none-any.whl", hash = "sha256:c9bd6e6d71dc5a2a25df167dc323422997f14682b008bfecb5d7920a55020ea7"}, + {file = "jupyter_server-2.11.0.tar.gz", hash = "sha256:78c97ec8049f9062f0151725bc8a1364dfed716646a66819095e0e8a24793eba"}, ] [package.dependencies] @@ -3033,16 +3033,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -4058,13 +4048,13 @@ testing = ["aboutcode-toolkit (>=6.0.0)", "black", "pytest (>=6,!=7.0.0)", "pyte [[package]] name = "platformdirs" -version = "3.11.0" +version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, + {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, + {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, ] [package.extras] @@ -4209,13 +4199,13 @@ tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] [[package]] name = "prometheus-client" -version = "0.18.0" +version = "0.19.0" description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.8" files = [ - {file = "prometheus_client-0.18.0-py3-none-any.whl", hash = "sha256:8de3ae2755f890826f4b6479e5571d4f74ac17a81345fe69a6778fdb92579184"}, - {file = "prometheus_client-0.18.0.tar.gz", hash = "sha256:35f7a8c22139e2bb7ca5a698e92d38145bc8dc74c1c0bf56f25cca886a764e17"}, + {file = "prometheus_client-0.19.0-py3-none-any.whl", hash = "sha256:c88b1e6ecf6b41cd8fb5731c7ae919bf66df6ec6fafa555cd6c0e16ca169ae92"}, + {file = "prometheus_client-0.19.0.tar.gz", hash = "sha256:4585b0d1223148c27a225b10dbec5ae9bc4c81a99a3fa80774fa6209935324e1"}, ] [package.extras] @@ -4629,13 +4619,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pygments" -version = "2.17.1" +version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "pygments-2.17.1-py3-none-any.whl", hash = "sha256:1b37f1b1e1bff2af52ecaf28cc601e2ef7077000b227a0675da25aef85784bc4"}, - {file = "pygments-2.17.1.tar.gz", hash = "sha256:e45a0e74bf9c530f564ca81b8952343be986a29f6afe7f5ad95c5f06b7bdf5e8"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] @@ -4965,7 +4955,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -4973,15 +4962,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -4998,7 +4980,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -5006,7 +4987,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -5837,13 +5817,13 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "69.0.0" +version = "69.0.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.0-py3-none-any.whl", hash = "sha256:eb03b43f23910c5fd0909cb677ad017cd9531f493d27f8b3f5316ff1fb07390e"}, - {file = "setuptools-69.0.0.tar.gz", hash = "sha256:4c65d4f7891e5b046e9146913b87098144de2ca2128fbc10135b8556a6ddd946"}, + {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, + {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, ] [package.extras] @@ -7300,19 +7280,19 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.24.6" +version = "20.24.7" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, - {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, + {file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, + {file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<4" +platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] @@ -7334,13 +7314,13 @@ colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\" and python [[package]] name = "wcwidth" -version = "0.2.10" +version = "0.2.12" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.10-py2.py3-none-any.whl", hash = "sha256:aec5179002dd0f0d40c456026e74a729661c9d468e1ed64405e3a6c2176ca36f"}, - {file = "wcwidth-0.2.10.tar.gz", hash = "sha256:390c7454101092a6a5e43baad8f83de615463af459201709556b6e4b1c861f97"}, + {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"}, + {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"}, ] [[package]] diff --git a/src/aki_prj23_transparenzregister/ui/assets/tabs.css b/src/aki_prj23_transparenzregister/ui/assets/tabs.css index df01dd5..7b657b0 100644 --- a/src/aki_prj23_transparenzregister/ui/assets/tabs.css +++ b/src/aki_prj23_transparenzregister/ui/assets/tabs.css @@ -53,3 +53,49 @@ .dash-filter input { text-align: center !important; } + +.sentiment-wrapper { + float: left; + width: 100%; + background-color: white; + margin: 20px; +} + +.sentiment-wrapper .sentiment-title { + color: var(--raisin-black); + padding-right: 30px; + vertical-align:middle; + display: inline-block; +} + +.sentiment-wrapper .sentiment-line { + width: 60%; + display: inline-block; + vertical-align: top; + margin-right: 5%; +} + +.sentiment-wrapper .sentiment-gauge { + max-width: 30%; + display: inline-block; + vertical-align: top; + margin-top: 30px; + margin-left: 5%; + margin-right: 5%; + text-align: center; +} + +.sentiment-table { + margin: 15px; +} + +.sentiment-disclaimer { + margin: 20px; +} + +circle { + stroke-width: 25px; +} +.tick { + display: none; + } diff --git a/src/aki_prj23_transparenzregister/ui/company_elements.py b/src/aki_prj23_transparenzregister/ui/company_elements.py index ed837e5..09563de 100644 --- a/src/aki_prj23_transparenzregister/ui/company_elements.py +++ b/src/aki_prj23_transparenzregister/ui/company_elements.py @@ -9,7 +9,11 @@ from babel.numbers import format_currency from dash import dash_table, dcc, html from sqlalchemy.orm import Session -from aki_prj23_transparenzregister.ui import data_elements, finance_elements +from aki_prj23_transparenzregister.ui import ( + data_elements, + finance_elements, + sentiment_elements, +) from aki_prj23_transparenzregister.utils.networkx.network_2d import create_2d_graph from aki_prj23_transparenzregister.utils.networkx.network_base import initialize_network from aki_prj23_transparenzregister.utils.networkx.networkx_data import ( @@ -113,7 +117,7 @@ def create_company_stats( pd.DataFrame( data={ "revenue": [""], - "date": datetime.today(), + "date": datetime.today().strftime("%d.%m.%Y"), } ) if finance_data.empty @@ -124,6 +128,30 @@ def create_company_stats( # set data for third widget widget3_title = "Stimmung" + sentiment_data = data_elements.get_last_article_of_one_company( + selected_company_id, session + ) + if sentiment_data.empty: + sentiment_data = pd.DataFrame( + { + "timestamp": datetime.today().strftime("%d.%m.%Y"), + "overall_sentiment_label": "n.a.", + }, + index=[0], + ) + else: + sentiment_data["overall_sentiment_label"] = sentiment_data[ + "overall_sentiment_label" + ].apply(lambda _: _.value) + sentiment_data["overall_sentiment_label"] = sentiment_data[ + "overall_sentiment_label" + ].replace({-1: "negativ", 0: "neutral", 1: "positiv"}) + sentiment_data["timestamp"] = sentiment_data["timestamp"].dt.strftime( + "%d.%m.%Y" + ) + widget3_content = sentiment_data["overall_sentiment_label"][0] + widget3_info = f"Stand: {sentiment_data['timestamp'][0]}" + return html.Div( className="stats-wrapper", children=[ @@ -219,7 +247,11 @@ def create_company_stats( ), html.H1( className="widget-content", - children="1234", + children=[widget3_content], + ), + html.Div( + className="widget-info", + children=[widget3_info], ), ], ), @@ -269,6 +301,9 @@ def create_tabs(session: Session, selected_company_id: int) -> html: value="tab-3", className="tab-style", selected_className="selected-tab-style", + children=sentiment_elements.sentiment_layout( + selected_company_id, session + ), ), dcc.Tab( label="Verflechtungen", diff --git a/src/aki_prj23_transparenzregister/ui/data_elements.py b/src/aki_prj23_transparenzregister/ui/data_elements.py index bd3e2f9..b28cc13 100644 --- a/src/aki_prj23_transparenzregister/ui/data_elements.py +++ b/src/aki_prj23_transparenzregister/ui/data_elements.py @@ -38,6 +38,7 @@ def get_person_data(session: Session) -> pd.DataFrame: A dataframe containing all available company data including the corresponding district court. """ query_person = session.query(entities.Person) + engine = session.bind if not isinstance(engine, sa.engine.Engine): raise TypeError @@ -119,9 +120,9 @@ def get_options(session: Session | None) -> dict[int, str]: return {} persons = get_person_data(session).rename("p_{}".format) - persons["person_lastname_firstname"] = ( - persons["person_lastname"] + ", " + persons["person_firstname"] - ) + persons[ + "person_lastname_firstname" + ] = f"{persons['person_lastname']}, {persons['person_firstname']}" persons_options = persons["person_lastname_firstname"] companies = get_company_data(session).rename("c_{}".format) companies_options = companies["company_name"] @@ -164,3 +165,61 @@ def get_person_relations_of_one_company( .all() ) return pd.DataFrame(data) + + +def get_news_of_one_company(selected_company_id: int, session: Session) -> pd.DataFrame: + """Collects all news articles of one company. + + Args: + selected_company_id: Id of the company. + session: A session connecting to the database. + + Returns: + A dataframe containing all news of the selected company. + """ + certainty_minimum = 0.9 + data = ( + session.query( + entities.News.title, + entities.News.timestamp, + entities.News.source_url, + entities.News.source_domain, + entities.News.overall_sentiment_label, + entities.News.overall_sentiment_certainty, + entities.Sentiment.times_named, + ) + .join(entities.Sentiment, entities.News.id == entities.Sentiment.article_id) + .filter(entities.Sentiment.company_id == selected_company_id) + .filter(entities.News.overall_sentiment_certainty >= certainty_minimum) + .all() + ) + return pd.DataFrame(data) + + +def get_last_article_of_one_company( + selected_company_id: int, session: Session +) -> pd.DataFrame: + """Collects all news articles of one company. + + Args: + selected_company_id: Id of the company. + session: A session connecting to the database. + + Returns: + A dataframe containing all news of the selected company. + """ + certainty_minimum = 0.9 + data = ( + session.query( + entities.News.timestamp, + entities.News.overall_sentiment_label, + ) + .join(entities.Sentiment, entities.News.id == entities.Sentiment.article_id) + .filter(entities.Sentiment.company_id == selected_company_id) + .filter(entities.News.overall_sentiment_certainty >= certainty_minimum) + .order_by(entities.News.timestamp.desc()) + .first() + ) + if not data: + return pd.DataFrame() + return pd.DataFrame(dict(data), index=[0]) diff --git a/src/aki_prj23_transparenzregister/ui/finance_elements.py b/src/aki_prj23_transparenzregister/ui/finance_elements.py index acf242e..0a279ff 100644 --- a/src/aki_prj23_transparenzregister/ui/finance_elements.py +++ b/src/aki_prj23_transparenzregister/ui/finance_elements.py @@ -124,7 +124,7 @@ def update_figure(value: str, data: str) -> html: if value is None: return "" finance_df = pd.read_json(StringIO(data)) - if finance_df[value].count() <= 2: # noqa: PLR2004 + if not finance_df[value].count(): # noqa: PLR2004 return html.H3( className="metrics-graph-response", children=[ @@ -171,4 +171,6 @@ def financials_figure(selected_finance_df: pd.DataFrame, metric: str) -> go.Figu title=f"Entwicklungsverlauf: {METRICS[metric]}", yaxis_title=f"{METRICS[metric]} in Euro (€)", ) + + fig_line.update_xaxes(tickformat="%b %Y") return fig_line diff --git a/src/aki_prj23_transparenzregister/ui/sentiment_elements.py b/src/aki_prj23_transparenzregister/ui/sentiment_elements.py new file mode 100644 index 0000000..152acd9 --- /dev/null +++ b/src/aki_prj23_transparenzregister/ui/sentiment_elements.py @@ -0,0 +1,270 @@ +"""Sentiment elements for Dash.""" + +from typing import Final + +import dash_daq as daq +import pandas as pd +import plotly.graph_objs as go +from dash import dash_table, dcc, html +from sqlalchemy.orm import Session + +from aki_prj23_transparenzregister.ui import data_elements + +COLORS: Final[dict[str, str]] = { + "light": "#edefef", + "lavender-blush": "#f3e8ee", + "ash-gray": "#bacdb0", + "cambridge-blue": "#729b79", + "paynes-gray": "#475b63", + "raisin-black": "#2e2c2f", +} + + +def sentiment_layout(selected_company_id: int, session: Session) -> html: + """Creates the content for the sentiment tab of the company page. + + Args: + selected_company_id: Id of the company. + session: A session connecting to the database. + + Returns: + The html div. + """ + articles = data_elements.get_news_of_one_company(selected_company_id, session) + + # number of article sentiments for the rolling mean + n = 10 + + if articles.empty or (len(articles) < n): + return html.Div( + className="sentiment-wrapper", + children=[ + html.H3( + className="sentiment-title", + children=[ + "Für dieses Unternehmen wurden leider nicht genug Nachrichten bzw. Artikel gefunden." + ], + ), + ], + ) + + # replace enum types with values + articles["overall_sentiment_label"] = articles["overall_sentiment_label"].apply( + lambda _: _.value + ) + + articles = articles.sort_values(by=["timestamp"], ascending=True) + articles["rolling_mean"] = articles["overall_sentiment_label"].rolling(n).mean() + + sentiment_score = articles["rolling_mean"].iloc[-1] + 1 + + articles["timestamp"] = pd.to_datetime( + articles["timestamp"].dt.strftime("%d.%m.%Y, %H:%M"), dayfirst=True + ) + + table = ( + articles[ + [ + "timestamp", + "title", + "times_named", + "source_domain", + "overall_sentiment_label", + ] + ] + .fillna("n.a.") + .rename( + columns={ + "timestamp": "Datum", + "title": "Titel", + "times_named": "Erwähnungen", + "source_domain": "Quelle", + "overall_sentiment_label": "Stimmung", + } + ) + .sort_values(by=["Datum"], ascending=False) + ) + + table["Datum"] = table["Datum"].dt.strftime("%d.%m.%Y") + table["Stimmung"] = table["Stimmung"].replace( + {-1: "negativ", 0: "neutral", 1: "positiv"} + ) + + return ( + html.Div( + className="sentiment-wrapper", + children=[ + html.Div( + className="sentiment-line", + children=[ + dcc.Graph(figure=sentiment_trend_figure(articles, n)), + ], + ), + html.Div( + className="sentiment-gauge", + children=[ + html.H3("Aktueller Stimmungstrend*"), + sentiment_gauge_figure(sentiment_score), + ], + ), + ], + ), + html.Div( + className="sentiment-table", + children=[ + dash_table.DataTable( + table.to_dict("records"), + [{"name": i, "id": i} for i in table.columns], + style_table={ + "width": "80%", + "marginLeft": "auto", + "marginRight": "auto", + "padding": "20px", + "color": COLORS["raisin-black"], + }, + style_cell={ + "textAlign": "center", + "fontFamily": "Times", + "fontSize": "14px", + }, + style_cell_conditional=[ + {"if": {"column_id": "Titel"}, "textAlign": "left"}, + {"if": {"column_id": "Quelle"}, "textAlign": "left"}, + ], + style_header={ + "backgroundColor": COLORS["raisin-black"], + "fontWeight": "bold", + "color": "white", + "fontSize": "16px", + }, + style_data={ + "whiteSpace": "normal", + "height": "auto", + }, + style_data_conditional=[ + { + "if": { + "column_id": "Stimmung", + "filter_query": '{Stimmung} = "negativ"', + }, + "color": "red", + "fontWeight": "bold", + }, + { + "if": { + "column_id": "Stimmung", + "filter_query": '{Stimmung} = "positiv"', + }, + "color": "green", + "fontWeight": "bold", + }, + { + "if": {"state": "selected"}, # 'active' | 'selected' + "backgroundColor": COLORS["light"], + "border": "1px solid " + COLORS["paynes-gray"], + }, + ], + page_size=10, + sort_action="native", + sort_mode="multi", + column_selectable="single", + filter_action="native", + filter_options={"placeholder_text": "Filter"}, + style_filter={ + "text-align": "center", + "backgroundColor": COLORS["light"], + }, + ), + ], + ), + html.Div( + className="sentiment-disclaimer", + children=[ + f"*Für den aktuellen Stimmungstrend wurden die letzten {n} Artikel verwendet." + ], + ), + ) + + +def sentiment_trend_figure(articles: pd.DataFrame, n: int) -> go.Figure: + """Creates trend plot for the rolling mean sentiment trend. + + Args: + articles: Dataframe containing all available news including the sentiments. + n: number of minimum articles used for the rolling mean. + + Returns: + Plotly go figure. + """ + articles = articles.iloc[n:] + + sentiment_fig = go.Figure() + # add trace for company 1 + sentiment_fig.add_trace( + go.Scatter( + x=articles["timestamp"], + y=articles["overall_sentiment_label"], + line_color=COLORS["raisin-black"], + marker_color=COLORS["raisin-black"], + mode="lines+markers", + name="Einzelne Artikel", + ) + ) + + sentiment_fig.add_trace( + go.Scatter( + x=articles["timestamp"], + y=articles["rolling_mean"], + line_color=COLORS["cambridge-blue"], + marker_color=COLORS["cambridge-blue"], + mode="lines+markers", + name="Gleitender Mittelwert", + ) + ) + + # set title and labels + sentiment_fig.update_layout( + title="Stimmungsverlauf", + yaxis_range=[-1.5, 1.5], + yaxis={ + "tickmode": "array", + "tickvals": [-1, 0, 1], + "ticktext": ["Negativ (-1) ", "Neutral (0) ", "Positiv (1) "], + }, + ) + + sentiment_fig.update_xaxes(tickformat="%d.%b.%Y") + return sentiment_fig + + +def sentiment_gauge_figure(sentiment_score: float) -> daq: + """Creates gauge plot for the current sentiment trend. + + Args: + sentiment_score: Float between 0 and 2. + + Returns: + Daq gauge plot. + """ + if not sentiment_score <= 2 and sentiment_score >= 0: # noqa: PLR2004 + raise ValueError( + f"Expected sentiment score is not between 0 and 2, got {sentiment_score} ." + ) + + return daq.Gauge( + color={ + "gradient": True, + "ranges": {"red": [0, 0.6], "yellow": [0.6, 1.4], "green": [1.4, 2]}, + }, + scale={ + "custom": { + 0.3: {"label": " Negativ", "style": {"font-size": "16px"}}, + 1: {"label": "Neutral", "style": {"font-size": "16px"}}, + 1.7: {"label": "Positiv", "style": {"font-size": "16px"}}, + } + }, + value=sentiment_score, + max=2, + min=0, + size=300, + ) diff --git a/tests/conftest.py b/tests/conftest.py index 5a7aebc..eac13d2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,7 @@ from sqlalchemy.orm import Session, sessionmaker from aki_prj23_transparenzregister.config.config_template import SQLiteConnectionString from aki_prj23_transparenzregister.models.company import CapitalTypeEnum from aki_prj23_transparenzregister.utils import data_transfer +from aki_prj23_transparenzregister.utils.enum_types import SentimentLabel from aki_prj23_transparenzregister.utils.sql import entities from aki_prj23_transparenzregister.utils.sql.connector import ( get_engine, @@ -189,3 +190,277 @@ def full_db(empty_db: Session, finance_statements: list[dict[str, Any]]) -> Sess empty_db.commit() # print(pd.read_sql_table("company", empty_db.bind).to_string()) return empty_db + + +@pytest.fixture() +def news_db(full_db: Session) -> Session: + news_example = entities.News( + title="AI Revolution in Tech", + timestamp=datetime.datetime(2023, 11, 1, 15, 30), + text="The latest advancements in AI are transforming the tech industry.", + source_url="http://example-news.com/ai-revolution", + source_domain="example-news.com", + overall_sentiment_label=SentimentLabel.POSITIVE, # type: ignore + overall_sentiment_certainty=0.95, + number_of_companies=2, + sum_of_times_named=5, + ) + full_db.add(news_example) + full_db.commit() + # Print to see the object representation + + # Example instances of Sentiment for different company IDs + sentiment_examples = [ + entities.Sentiment( + company_id=1, + article_id=news_example.id, # This should be the actual ID after insertion into the database + times_named=3, + specific_sentiment_label=SentimentLabel.NEUTRAL, # type: ignore + specific_sentiment_score=0.5, + ), + entities.Sentiment( + company_id=2, + article_id=news_example.id, # This should be the actual ID after insertion into the database + times_named=2, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.2, + ), + entities.Sentiment( + company_id=3, + article_id=news_example.id, # This should be the actual ID after insertion into the database + times_named=1, + specific_sentiment_label=SentimentLabel.POSITIVE, # type: ignore + specific_sentiment_score=0.8, + ), + ] + full_db.add_all(sentiment_examples) + add_news_examples = [ + entities.News( + title="Breakthrough in Renewable Energy", + timestamp=datetime.datetime(2023, 11, 1, 15, 30), + text="Innovative solar panels are expected to revolutionize the energy sector.", + source_url="http://example-news.com/renewable-breakthrough", + source_domain="eco-news.com", + overall_sentiment_label=SentimentLabel.POSITIVE, # type: ignore + overall_sentiment_certainty=0.9, + number_of_companies=1, + sum_of_times_named=3, + ), + entities.News( + title="Global Economic Outlook", + timestamp=datetime.datetime(2023, 11, 2, 10, 0), + text="Economists predict a challenging year ahead for global markets.", + source_url="http://example-news.com/economic-outlook", + source_domain="finance-world.com", + overall_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + overall_sentiment_certainty=0.95, + number_of_companies=3, + sum_of_times_named=7, + ), + entities.News( + title="Tech Giants Merge", + timestamp=datetime.datetime(2023, 11, 3, 12, 45), + text="Two leading tech companies have announced a merger, sparking industry-wide discussions.", + source_url="http://example-news.com/tech-merger", + source_domain="tech-news.com", + overall_sentiment_label=SentimentLabel.NEUTRAL, # type: ignore + overall_sentiment_certainty=0.91, + number_of_companies=2, + sum_of_times_named=10, + ), + entities.News( + title="Deutsche Bank announces huge job loss plan", + timestamp=datetime.datetime(2023, 11, 5, 12, 45), + text="Deutsche Bank has announced plans for major job losses and will also scale down massively its operations worldwide, as part of a major shake-up of.", + source_url="http://example-news.com/deutsche-bank", + source_domain="tech-news.com", + overall_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + overall_sentiment_certainty=0.9, + number_of_companies=1, + sum_of_times_named=8, + ), + entities.News( + title="Germany approves reforms to help its tech industry compete with Silicon Valley", + timestamp=datetime.datetime(2023, 11, 6, 9, 45), + text="Germany on Friday approved a litany of changes to its rules for stock-based compensation at tech startups, listing of companies and taxation.", + source_url="http://example-news.com/german-tech-industry", + source_domain="tech-news.com", + overall_sentiment_label=SentimentLabel.POSITIVE, # type: ignore + overall_sentiment_certainty=0.97, + number_of_companies=3, + sum_of_times_named=5, + ), + entities.News( + title="German housebuilding is collapsing ", + timestamp=datetime.datetime(2023, 11, 8, 12, 30), + text="German housebuilding is on the brink of collapse as construction projects are being canceled and orders are slowing.", + source_url="http://example-news.com/german-housing", + source_domain="tech-news.com", + overall_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + overall_sentiment_certainty=0.96, + number_of_companies=3, + sum_of_times_named=2, + ), + entities.News( + title="The Berlin start-ups tearing up capitalism and putting workers first", + timestamp=datetime.datetime(2023, 11, 10, 12, 45), + text="Berlin is one of Europe's hottest start-up hubs. But companies setting up there are doing things differently and it starts with where the power lies.", + source_url="http://example-news.com/tech-merger", + source_domain="tech-news.com", + overall_sentiment_label=SentimentLabel.POSITIVE, # type: ignore + overall_sentiment_certainty=0.91, + number_of_companies=2, + sum_of_times_named=10, + ), + entities.News( + title="Air traffic across Germany disrupted due to mass walkouts", + timestamp=datetime.datetime(2023, 11, 3, 12, 45), + text="Security workers are staging a full-day walkout at airports across Germany over pay disputes.", + source_url="http://example-news.com/tech-merger", + source_domain="tech-news.com", + overall_sentiment_label=SentimentLabel.NEUTRAL, # type: ignore + overall_sentiment_certainty=0.95, + number_of_companies=3, + sum_of_times_named=17, + ), + entities.News( + title="Car makers face fraud claims", + timestamp=datetime.datetime(2023, 11, 12, 12, 45), + text="The German economy grew 0.6 percent in Q1, quarter on quarter, helped by strong exports, booming construction and higher household and state spending.", + source_url="http://example-news.com/tech-merger", + source_domain="tech-news.com", + overall_sentiment_label=SentimentLabel.NEUTRAL, # type: ignore + overall_sentiment_certainty=0.9, + number_of_companies=2, + sum_of_times_named=10, + ), + entities.News( + title="Inflation rises, unemployment falls", + timestamp=datetime.datetime(2023, 11, 15, 12, 45), + text="German inflation rose in February, reaching its highest level in four-and-a-half years, while unemployment fell more than expected.", + source_url="http://example-news.com/inflation", + source_domain="tech-news.com", + overall_sentiment_label=SentimentLabel.POSITIVE, # type: ignore + overall_sentiment_certainty=0.98, + number_of_companies=2, + sum_of_times_named=10, + ), + entities.News( + title="Exports struggle on slow factory output", + timestamp=datetime.datetime(2023, 11, 16, 12, 45), + text="Exports rebounded by less than expected in October, according to latest official figures.", + source_url="http://example-news.com/exports", + source_domain="tech-news.com", + overall_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + overall_sentiment_certainty=0.98, + number_of_companies=3, + sum_of_times_named=4, + ), + ] + full_db.add_all(add_news_examples) + full_db.commit() + # Additional examples for the Sentiment entity + sentiment_more_examples = [ + entities.Sentiment( + company_id=1, + article_id=add_news_examples[ + 2 + ].id, # Placeholder ID, replace with actual after insertion + times_named=1, + specific_sentiment_label=SentimentLabel.NEUTRAL, # type: ignore + specific_sentiment_score=0.5, + ), + entities.Sentiment( + company_id=3, + article_id=add_news_examples[ + 1 + ].id, # Placeholder ID, replace with actual after insertion + times_named=4, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.3, + ), + entities.Sentiment( + company_id=1, + article_id=add_news_examples[ + 1 + ].id, # Placeholder ID, replace with actual after insertion + times_named=4, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.3, + ), + entities.Sentiment( + company_id=1, + article_id=add_news_examples[ + 3 + ].id, # Placeholder ID, replace with actual after insertion + times_named=4, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.3, + ), + entities.Sentiment( + company_id=1, + article_id=add_news_examples[ + 4 + ].id, # Placeholder ID, replace with actual after insertion + times_named=4, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.3, + ), + entities.Sentiment( + company_id=1, + article_id=add_news_examples[ + 5 + ].id, # Placeholder ID, replace with actual after insertion + times_named=4, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.3, + ), + entities.Sentiment( + company_id=1, + article_id=add_news_examples[ + 6 + ].id, # Placeholder ID, replace with actual after insertion + times_named=4, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.3, + ), + entities.Sentiment( + company_id=1, + article_id=add_news_examples[ + 7 + ].id, # Placeholder ID, replace with actual after insertion + times_named=4, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.3, + ), + entities.Sentiment( + company_id=1, + article_id=add_news_examples[ + 8 + ].id, # Placeholder ID, replace with actual after insertion + times_named=4, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.3, + ), + entities.Sentiment( + company_id=1, + article_id=add_news_examples[ + 9 + ].id, # Placeholder ID, replace with actual after insertion + times_named=4, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.3, + ), + entities.Sentiment( + company_id=1, + article_id=add_news_examples[ + 10 + ].id, # Placeholder ID, replace with actual after insertion + times_named=4, + specific_sentiment_label=SentimentLabel.NEGATIVE, # type: ignore + specific_sentiment_score=0.3, + ), + ] + full_db.add_all(sentiment_more_examples) + full_db.commit() + return full_db diff --git a/tests/ui/data_elements_test.py b/tests/ui/data_elements_test.py index f3c6cdd..8f60a34 100644 --- a/tests/ui/data_elements_test.py +++ b/tests/ui/data_elements_test.py @@ -4,6 +4,7 @@ import pandas as pd from sqlalchemy.orm import Session from aki_prj23_transparenzregister.ui import data_elements +from aki_prj23_transparenzregister.utils.enum_types import SentimentLabel def test_import() -> None: @@ -86,3 +87,27 @@ def test_get_finance_data(full_db: Session) -> None: } ) pd.testing.assert_frame_equal(finance_df, test_data) + + +def test_get_news_of_one_company0(news_db: Session) -> None: + """Tests what happens if no sentiment can be found.""" + selected_company_id = 100 + assert data_elements.get_news_of_one_company(selected_company_id, news_db).empty + + +def test_get_news_of_one_company1(news_db: Session) -> None: + """Tests if the sentiments can be collected for a company specified.""" + selected_company_id = 2 + sentiment_df = data_elements.get_news_of_one_company(selected_company_id, news_db) + assert sentiment_df is not None + assert sentiment_df.to_dict(orient="records") == [ + { + "overall_sentiment_certainty": 0.95, + "overall_sentiment_label": SentimentLabel.POSITIVE, + "source_domain": "example-news.com", + "source_url": "http://example-news.com/ai-revolution", + "times_named": 2, + "timestamp": pd.Timestamp("2023-11-01 15:30:00"), + "title": "AI Revolution in Tech", + }, + ] diff --git a/tests/ui/sentiment_elements_test.py b/tests/ui/sentiment_elements_test.py new file mode 100644 index 0000000..498df43 --- /dev/null +++ b/tests/ui/sentiment_elements_test.py @@ -0,0 +1,32 @@ +"""Tests for sentiment ui elements.""" + +from sqlalchemy.orm import Session + +from aki_prj23_transparenzregister.ui import data_elements, sentiment_elements + + +def test_sentiment_layout(news_db: Session) -> None: + """Checks if the sentiment tab layout of the company page can be created.""" + selected_company_id = 1 + sentiment_elements.sentiment_layout(selected_company_id, news_db) + + +def test_sentiment_trend_figure(news_db: Session) -> None: + """Checks if the sentiment trend graph can be created.""" + n = 10 + company_id = 1 + articles = data_elements.get_news_of_one_company(company_id, news_db) + + articles["overall_sentiment_label"] = articles["overall_sentiment_label"].apply( + lambda _: _.value + ) + + articles = articles.sort_values(by=["timestamp"], ascending=True) + articles["rolling_mean"] = articles["overall_sentiment_label"].rolling(n).mean() + sentiment_elements.sentiment_trend_figure(articles, n) + + +def test_sentiment_gauge_figure() -> None: + """Checks if the sentiment gauge graph can be created.""" + sentiment_score = 0.2 + sentiment_elements.sentiment_gauge_figure(sentiment_score)