feat: prediction data equipment transaction

main
MrWaradana 11 months ago
parent 4fd187237a
commit 62eb892f7e

608
poetry.lock generated

@ -247,6 +247,94 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "contourpy"
version = "1.3.1"
description = "Python library for calculating contours of 2D quadrilateral grids"
optional = false
python-versions = ">=3.10"
files = [
{file = "contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab"},
{file = "contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124"},
{file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1"},
{file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b"},
{file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453"},
{file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3"},
{file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277"},
{file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595"},
{file = "contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697"},
{file = "contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e"},
{file = "contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b"},
{file = "contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc"},
{file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86"},
{file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6"},
{file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85"},
{file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c"},
{file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291"},
{file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f"},
{file = "contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375"},
{file = "contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9"},
{file = "contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509"},
{file = "contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc"},
{file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454"},
{file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80"},
{file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec"},
{file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9"},
{file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b"},
{file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d"},
{file = "contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e"},
{file = "contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d"},
{file = "contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2"},
{file = "contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5"},
{file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81"},
{file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2"},
{file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7"},
{file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c"},
{file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3"},
{file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1"},
{file = "contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82"},
{file = "contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd"},
{file = "contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30"},
{file = "contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751"},
{file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342"},
{file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c"},
{file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f"},
{file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda"},
{file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242"},
{file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1"},
{file = "contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1"},
{file = "contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546"},
{file = "contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6"},
{file = "contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750"},
{file = "contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53"},
{file = "contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699"},
]
[package.dependencies]
numpy = ">=1.23"
[package.extras]
bokeh = ["bokeh", "selenium"]
docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"]
mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"]
test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"]
[[package]]
name = "cycler"
version = "0.12.1"
description = "Composable style cycles"
optional = false
python-versions = ">=3.8"
files = [
{file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"},
{file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"},
]
[package.extras]
docs = ["ipython", "matplotlib", "numpydoc", "sphinx"]
tests = ["pytest", "pytest-cov", "pytest-xdist"]
[[package]]
name = "deprecated"
version = "1.2.18"
@ -402,6 +490,79 @@ uvicorn = {version = ">=0.15.0", extras = ["standard"]}
[package.extras]
standard = ["uvicorn[standard] (>=0.15.0)"]
[[package]]
name = "fonttools"
version = "4.56.0"
description = "Tools to manipulate font files"
optional = false
python-versions = ">=3.8"
files = [
{file = "fonttools-4.56.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:331954d002dbf5e704c7f3756028e21db07097c19722569983ba4d74df014000"},
{file = "fonttools-4.56.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d1613abd5af2f93c05867b3a3759a56e8bf97eb79b1da76b2bc10892f96ff16"},
{file = "fonttools-4.56.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:705837eae384fe21cee5e5746fd4f4b2f06f87544fa60f60740007e0aa600311"},
{file = "fonttools-4.56.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc871904a53a9d4d908673c6faa15689874af1c7c5ac403a8e12d967ebd0c0dc"},
{file = "fonttools-4.56.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:38b947de71748bab150259ee05a775e8a0635891568e9fdb3cdd7d0e0004e62f"},
{file = "fonttools-4.56.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:86b2a1013ef7a64d2e94606632683f07712045ed86d937c11ef4dde97319c086"},
{file = "fonttools-4.56.0-cp310-cp310-win32.whl", hash = "sha256:133bedb9a5c6376ad43e6518b7e2cd2f866a05b1998f14842631d5feb36b5786"},
{file = "fonttools-4.56.0-cp310-cp310-win_amd64.whl", hash = "sha256:17f39313b649037f6c800209984a11fc256a6137cbe5487091c6c7187cae4685"},
{file = "fonttools-4.56.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ef04bc7827adb7532be3d14462390dd71287644516af3f1e67f1e6ff9c6d6df"},
{file = "fonttools-4.56.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ffda9b8cd9cb8b301cae2602ec62375b59e2e2108a117746f12215145e3f786c"},
{file = "fonttools-4.56.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e993e8db36306cc3f1734edc8ea67906c55f98683d6fd34c3fc5593fdbba4c"},
{file = "fonttools-4.56.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:003548eadd674175510773f73fb2060bb46adb77c94854af3e0cc5bc70260049"},
{file = "fonttools-4.56.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd9825822e7bb243f285013e653f6741954d8147427aaa0324a862cdbf4cbf62"},
{file = "fonttools-4.56.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b23d30a2c0b992fb1c4f8ac9bfde44b5586d23457759b6cf9a787f1a35179ee0"},
{file = "fonttools-4.56.0-cp311-cp311-win32.whl", hash = "sha256:47b5e4680002ae1756d3ae3b6114e20aaee6cc5c69d1e5911f5ffffd3ee46c6b"},
{file = "fonttools-4.56.0-cp311-cp311-win_amd64.whl", hash = "sha256:14a3e3e6b211660db54ca1ef7006401e4a694e53ffd4553ab9bc87ead01d0f05"},
{file = "fonttools-4.56.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6f195c14c01bd057bc9b4f70756b510e009c83c5ea67b25ced3e2c38e6ee6e9"},
{file = "fonttools-4.56.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa760e5fe8b50cbc2d71884a1eff2ed2b95a005f02dda2fa431560db0ddd927f"},
{file = "fonttools-4.56.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d54a45d30251f1d729e69e5b675f9a08b7da413391a1227781e2a297fa37f6d2"},
{file = "fonttools-4.56.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661a8995d11e6e4914a44ca7d52d1286e2d9b154f685a4d1f69add8418961563"},
{file = "fonttools-4.56.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9d94449ad0a5f2a8bf5d2f8d71d65088aee48adbe45f3c5f8e00e3ad861ed81a"},
{file = "fonttools-4.56.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f59746f7953f69cc3290ce2f971ab01056e55ddd0fb8b792c31a8acd7fee2d28"},
{file = "fonttools-4.56.0-cp312-cp312-win32.whl", hash = "sha256:bce60f9a977c9d3d51de475af3f3581d9b36952e1f8fc19a1f2254f1dda7ce9c"},
{file = "fonttools-4.56.0-cp312-cp312-win_amd64.whl", hash = "sha256:300c310bb725b2bdb4f5fc7e148e190bd69f01925c7ab437b9c0ca3e1c7cd9ba"},
{file = "fonttools-4.56.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f20e2c0dfab82983a90f3d00703ac0960412036153e5023eed2b4641d7d5e692"},
{file = "fonttools-4.56.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f36a0868f47b7566237640c026c65a86d09a3d9ca5df1cd039e30a1da73098a0"},
{file = "fonttools-4.56.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62b4c6802fa28e14dba010e75190e0e6228513573f1eeae57b11aa1a39b7e5b1"},
{file = "fonttools-4.56.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a05d1f07eb0a7d755fbe01fee1fd255c3a4d3730130cf1bfefb682d18fd2fcea"},
{file = "fonttools-4.56.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0073b62c3438cf0058488c002ea90489e8801d3a7af5ce5f7c05c105bee815c3"},
{file = "fonttools-4.56.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cad98c94833465bcf28f51c248aaf07ca022efc6a3eba750ad9c1e0256d278"},
{file = "fonttools-4.56.0-cp313-cp313-win32.whl", hash = "sha256:d0cb73ccf7f6d7ca8d0bc7ea8ac0a5b84969a41c56ac3ac3422a24df2680546f"},
{file = "fonttools-4.56.0-cp313-cp313-win_amd64.whl", hash = "sha256:62cc1253827d1e500fde9dbe981219fea4eb000fd63402283472d38e7d8aa1c6"},
{file = "fonttools-4.56.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3fd3fccb7b9adaaecfa79ad51b759f2123e1aba97f857936ce044d4f029abd71"},
{file = "fonttools-4.56.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:193b86e9f769320bc98ffdb42accafb5d0c8c49bd62884f1c0702bc598b3f0a2"},
{file = "fonttools-4.56.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e81c1cc80c1d8bf071356cc3e0e25071fbba1c75afc48d41b26048980b3c771"},
{file = "fonttools-4.56.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9270505a19361e81eecdbc2c251ad1e1a9a9c2ad75fa022ccdee533f55535dc"},
{file = "fonttools-4.56.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53f5e9767978a4daf46f28e09dbeb7d010319924ae622f7b56174b777258e5ba"},
{file = "fonttools-4.56.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9da650cb29bc098b8cfd15ef09009c914b35c7986c8fa9f08b51108b7bc393b4"},
{file = "fonttools-4.56.0-cp38-cp38-win32.whl", hash = "sha256:965d0209e6dbdb9416100123b6709cb13f5232e2d52d17ed37f9df0cc31e2b35"},
{file = "fonttools-4.56.0-cp38-cp38-win_amd64.whl", hash = "sha256:654ac4583e2d7c62aebc6fc6a4c6736f078f50300e18aa105d87ce8925cfac31"},
{file = "fonttools-4.56.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca7962e8e5fc047cc4e59389959843aafbf7445b6c08c20d883e60ced46370a5"},
{file = "fonttools-4.56.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1af375734018951c31c0737d04a9d5fd0a353a0253db5fbed2ccd44eac62d8c"},
{file = "fonttools-4.56.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:442ad4122468d0e47d83bc59d0e91b474593a8c813839e1872e47c7a0cb53b10"},
{file = "fonttools-4.56.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cf4f8d2a30b454ac682e12c61831dcb174950c406011418e739de592bbf8f76"},
{file = "fonttools-4.56.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96a4271f63a615bcb902b9f56de00ea225d6896052c49f20d0c91e9f43529a29"},
{file = "fonttools-4.56.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6c1d38642ca2dddc7ae992ef5d026e5061a84f10ff2b906be5680ab089f55bb8"},
{file = "fonttools-4.56.0-cp39-cp39-win32.whl", hash = "sha256:2d351275f73ebdd81dd5b09a8b8dac7a30f29a279d41e1c1192aedf1b6dced40"},
{file = "fonttools-4.56.0-cp39-cp39-win_amd64.whl", hash = "sha256:d6ca96d1b61a707ba01a43318c9c40aaf11a5a568d1e61146fafa6ab20890793"},
{file = "fonttools-4.56.0-py3-none-any.whl", hash = "sha256:1088182f68c303b50ca4dc0c82d42083d176cba37af1937e1a976a31149d4d14"},
{file = "fonttools-4.56.0.tar.gz", hash = "sha256:a114d1567e1a1586b7e9e7fc2ff686ca542a82769a296cef131e4c4af51e58f4"},
]
[package.extras]
all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"]
graphite = ["lz4 (>=1.7.4.2)"]
interpolatable = ["munkres", "pycairo", "scipy"]
lxml = ["lxml (>=4.0)"]
pathops = ["skia-pathops (>=0.5.0)"]
plot = ["matplotlib"]
repacker = ["uharfbuzz (>=0.23.0)"]
symfont = ["sympy"]
type1 = ["xattr"]
ufo = ["fs (>=2.2.0,<3)"]
unicode = ["unicodedata2 (>=15.1.0)"]
woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
[[package]]
name = "greenlet"
version = "3.1.1"
@ -642,6 +803,106 @@ MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "joblib"
version = "1.4.2"
description = "Lightweight pipelining with Python functions"
optional = false
python-versions = ">=3.8"
files = [
{file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"},
{file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"},
]
[[package]]
name = "kiwisolver"
version = "1.4.8"
description = "A fast implementation of the Cassowary constraint solver"
optional = false
python-versions = ">=3.10"
files = [
{file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db"},
{file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b"},
{file = "kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d"},
{file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d"},
{file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c"},
{file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3"},
{file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed"},
{file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f"},
{file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff"},
{file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d"},
{file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c"},
{file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605"},
{file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e"},
{file = "kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751"},
{file = "kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271"},
{file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84"},
{file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561"},
{file = "kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7"},
{file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03"},
{file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954"},
{file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79"},
{file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6"},
{file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0"},
{file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab"},
{file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc"},
{file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25"},
{file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc"},
{file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67"},
{file = "kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34"},
{file = "kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2"},
{file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502"},
{file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31"},
{file = "kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb"},
{file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f"},
{file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc"},
{file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a"},
{file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a"},
{file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a"},
{file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3"},
{file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b"},
{file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4"},
{file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d"},
{file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8"},
{file = "kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50"},
{file = "kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476"},
{file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09"},
{file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1"},
{file = "kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c"},
{file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b"},
{file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47"},
{file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16"},
{file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc"},
{file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246"},
{file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794"},
{file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b"},
{file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3"},
{file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957"},
{file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb"},
{file = "kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2"},
{file = "kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30"},
{file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c"},
{file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc"},
{file = "kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712"},
{file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e"},
{file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880"},
{file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062"},
{file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7"},
{file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed"},
{file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d"},
{file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165"},
{file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6"},
{file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90"},
{file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85"},
{file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a"},
{file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8"},
{file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0"},
{file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c"},
{file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b"},
{file = "kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b"},
{file = "kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e"},
]
[[package]]
name = "limits"
version = "4.0.1"
@ -764,6 +1025,63 @@ files = [
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
]
[[package]]
name = "matplotlib"
version = "3.10.0"
description = "Python plotting package"
optional = false
python-versions = ">=3.10"
files = [
{file = "matplotlib-3.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6"},
{file = "matplotlib-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e"},
{file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:607b16c8a73943df110f99ee2e940b8a1cbf9714b65307c040d422558397dac5"},
{file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6"},
{file = "matplotlib-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e6c6461e1fc63df30bf6f80f0b93f5b6784299f721bc28530477acd51bfc3d1"},
{file = "matplotlib-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:994c07b9d9fe8d25951e3202a68c17900679274dadfc1248738dcfa1bd40d7f3"},
{file = "matplotlib-3.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363"},
{file = "matplotlib-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997"},
{file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef"},
{file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683"},
{file = "matplotlib-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765"},
{file = "matplotlib-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a"},
{file = "matplotlib-3.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59"},
{file = "matplotlib-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a"},
{file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95"},
{file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b0558bae37f154fffda54d779a592bc97ca8b4701f1c710055b609a3bac44c8"},
{file = "matplotlib-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:503feb23bd8c8acc75541548a1d709c059b7184cde26314896e10a9f14df5f12"},
{file = "matplotlib-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:c40ba2eb08b3f5de88152c2333c58cee7edcead0a2a0d60fcafa116b17117adc"},
{file = "matplotlib-3.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96f2886f5c1e466f21cc41b70c5a0cd47bfa0015eb2d5793c88ebce658600e25"},
{file = "matplotlib-3.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:12eaf48463b472c3c0f8dbacdbf906e573013df81a0ab82f0616ea4b11281908"},
{file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fbbabc82fde51391c4da5006f965e36d86d95f6ee83fb594b279564a4c5d0d2"},
{file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad2e15300530c1a94c63cfa546e3b7864bd18ea2901317bae8bbf06a5ade6dcf"},
{file = "matplotlib-3.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3547d153d70233a8496859097ef0312212e2689cdf8d7ed764441c77604095ae"},
{file = "matplotlib-3.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c55b20591ced744aa04e8c3e4b7543ea4d650b6c3c4b208c08a05b4010e8b442"},
{file = "matplotlib-3.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ade1003376731a971e398cc4ef38bb83ee8caf0aee46ac6daa4b0506db1fd06"},
{file = "matplotlib-3.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95b710fea129c76d30be72c3b38f330269363fbc6e570a5dd43580487380b5ff"},
{file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdbaf909887373c3e094b0318d7ff230b2ad9dcb64da7ade654182872ab2593"},
{file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d907fddb39f923d011875452ff1eca29a9e7f21722b873e90db32e5d8ddff12e"},
{file = "matplotlib-3.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3b427392354d10975c1d0f4ee18aa5844640b512d5311ef32efd4dd7db106ede"},
{file = "matplotlib-3.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5fd41b0ec7ee45cd960a8e71aea7c946a28a0b8a4dcee47d2856b2af051f334c"},
{file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81713dd0d103b379de4516b861d964b1d789a144103277769238c732229d7f03"},
{file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:359f87baedb1f836ce307f0e850d12bb5f1936f70d035561f90d41d305fdacea"},
{file = "matplotlib-3.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae80dc3a4add4665cf2faa90138384a7ffe2a4e37c58d83e115b54287c4f06ef"},
{file = "matplotlib-3.10.0.tar.gz", hash = "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278"},
]
[package.dependencies]
contourpy = ">=1.0.1"
cycler = ">=0.10"
fonttools = ">=4.22.0"
kiwisolver = ">=1.3.1"
numpy = ">=1.23"
packaging = ">=20.0"
pillow = ">=8"
pyparsing = ">=2.3.1"
python-dateutil = ">=2.7"
[package.extras]
dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"]
[[package]]
name = "mdurl"
version = "0.1.2"
@ -775,6 +1093,70 @@ files = [
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "numpy"
version = "2.2.2"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.10"
files = [
{file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"},
{file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"},
{file = "numpy-2.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715"},
{file = "numpy-2.2.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a"},
{file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97"},
{file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957"},
{file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d"},
{file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd"},
{file = "numpy-2.2.2-cp310-cp310-win32.whl", hash = "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160"},
{file = "numpy-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014"},
{file = "numpy-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189"},
{file = "numpy-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323"},
{file = "numpy-2.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac"},
{file = "numpy-2.2.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e"},
{file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c"},
{file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f"},
{file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826"},
{file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8"},
{file = "numpy-2.2.2-cp311-cp311-win32.whl", hash = "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50"},
{file = "numpy-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2"},
{file = "numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467"},
{file = "numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a"},
{file = "numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825"},
{file = "numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37"},
{file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748"},
{file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0"},
{file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278"},
{file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba"},
{file = "numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283"},
{file = "numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb"},
{file = "numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc"},
{file = "numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369"},
{file = "numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd"},
{file = "numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be"},
{file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84"},
{file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff"},
{file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0"},
{file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de"},
{file = "numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9"},
{file = "numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369"},
{file = "numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391"},
{file = "numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39"},
{file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317"},
{file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49"},
{file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2"},
{file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7"},
{file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb"},
{file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648"},
{file = "numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4"},
{file = "numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576"},
{file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495"},
{file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df"},
{file = "numpy-2.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a"},
{file = "numpy-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60"},
{file = "numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"},
]
[[package]]
name = "openpyxl"
version = "3.1.5"
@ -800,6 +1182,94 @@ files = [
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
[[package]]
name = "pillow"
version = "11.1.0"
description = "Python Imaging Library (Fork)"
optional = false
python-versions = ">=3.9"
files = [
{file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"},
{file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"},
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"},
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"},
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"},
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"},
{file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"},
{file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"},
{file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"},
{file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"},
{file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"},
{file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"},
{file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"},
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"},
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"},
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"},
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"},
{file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"},
{file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"},
{file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"},
{file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"},
{file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"},
{file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"},
{file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"},
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"},
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"},
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"},
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"},
{file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"},
{file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"},
{file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"},
{file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"},
{file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"},
{file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"},
{file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"},
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"},
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"},
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"},
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"},
{file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"},
{file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"},
{file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"},
{file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"},
{file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"},
{file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"},
{file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"},
{file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"},
{file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"},
{file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"},
{file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"},
{file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"},
{file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"},
{file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"},
{file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"},
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"},
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"},
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"},
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"},
{file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"},
{file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"},
{file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"},
{file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"},
{file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"},
{file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"},
{file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"},
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"},
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"},
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"},
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"},
{file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"},
{file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"},
]
[package.extras]
docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
fpx = ["olefile"]
mic = ["olefile"]
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"]
typing = ["typing-extensions"]
xmp = ["defusedxml"]
[[package]]
name = "pluggy"
version = "1.5.0"
@ -1037,6 +1507,20 @@ files = [
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pyparsing"
version = "3.2.1"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
optional = false
python-versions = ">=3.9"
files = [
{file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"},
{file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"},
]
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pytest"
version = "8.3.4"
@ -1227,6 +1711,117 @@ click = ">=8.1.7"
rich = ">=13.7.1"
typing-extensions = ">=4.12.2"
[[package]]
name = "scikit-learn"
version = "1.6.1"
description = "A set of python modules for machine learning and data mining"
optional = false
python-versions = ">=3.9"
files = [
{file = "scikit_learn-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d056391530ccd1e501056160e3c9673b4da4805eb67eb2bdf4e983e1f9c9204e"},
{file = "scikit_learn-1.6.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0c8d036eb937dbb568c6242fa598d551d88fb4399c0344d95c001980ec1c7d36"},
{file = "scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8634c4bd21a2a813e0a7e3900464e6d593162a29dd35d25bdf0103b3fce60ed5"},
{file = "scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:775da975a471c4f6f467725dff0ced5c7ac7bda5e9316b260225b48475279a1b"},
{file = "scikit_learn-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:8a600c31592bd7dab31e1c61b9bbd6dea1b3433e67d264d17ce1017dbdce8002"},
{file = "scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33"},
{file = "scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d"},
{file = "scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2"},
{file = "scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8"},
{file = "scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415"},
{file = "scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b"},
{file = "scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2"},
{file = "scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f"},
{file = "scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86"},
{file = "scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52"},
{file = "scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322"},
{file = "scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1"},
{file = "scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348"},
{file = "scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97"},
{file = "scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb"},
{file = "scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236"},
{file = "scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35"},
{file = "scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691"},
{file = "scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f"},
{file = "scikit_learn-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6849dd3234e87f55dce1db34c89a810b489ead832aaf4d4550b7ea85628be6c1"},
{file = "scikit_learn-1.6.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e7be3fa5d2eb9be7d77c3734ff1d599151bb523674be9b834e8da6abe132f44e"},
{file = "scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44a17798172df1d3c1065e8fcf9019183f06c87609b49a124ebdf57ae6cb0107"},
{file = "scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b7a3b86e411e4bce21186e1c180d792f3d99223dcfa3b4f597ecc92fa1a422"},
{file = "scikit_learn-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7a73d457070e3318e32bdb3aa79a8d990474f19035464dfd8bede2883ab5dc3b"},
{file = "scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e"},
]
[package.dependencies]
joblib = ">=1.2.0"
numpy = ">=1.19.5"
scipy = ">=1.6.0"
threadpoolctl = ">=3.1.0"
[package.extras]
benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"]
build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"]
docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.17.1)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)", "towncrier (>=24.8.0)"]
examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"]
install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"]
maintenance = ["conda-lock (==2.5.6)"]
tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.5.1)", "scikit-image (>=0.17.2)"]
[[package]]
name = "scipy"
version = "1.15.1"
description = "Fundamental algorithms for scientific computing in Python"
optional = false
python-versions = ">=3.10"
files = [
{file = "scipy-1.15.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:c64ded12dcab08afff9e805a67ff4480f5e69993310e093434b10e85dc9d43e1"},
{file = "scipy-1.15.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5b190b935e7db569960b48840e5bef71dc513314cc4e79a1b7d14664f57fd4ff"},
{file = "scipy-1.15.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:4b17d4220df99bacb63065c76b0d1126d82bbf00167d1730019d2a30d6ae01ea"},
{file = "scipy-1.15.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:63b9b6cd0333d0eb1a49de6f834e8aeaefe438df8f6372352084535ad095219e"},
{file = "scipy-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f151e9fb60fbf8e52426132f473221a49362091ce7a5e72f8aa41f8e0da4f25"},
{file = "scipy-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e10b1dd56ce92fba3e786007322542361984f8463c6d37f6f25935a5a6ef52"},
{file = "scipy-1.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5dff14e75cdbcf07cdaa1c7707db6017d130f0af9ac41f6ce443a93318d6c6e0"},
{file = "scipy-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:f82fcf4e5b377f819542fbc8541f7b5fbcf1c0017d0df0bc22c781bf60abc4d8"},
{file = "scipy-1.15.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:5bd8d27d44e2c13d0c1124e6a556454f52cd3f704742985f6b09e75e163d20d2"},
{file = "scipy-1.15.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:be3deeb32844c27599347faa077b359584ba96664c5c79d71a354b80a0ad0ce0"},
{file = "scipy-1.15.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:5eb0ca35d4b08e95da99a9f9c400dc9f6c21c424298a0ba876fdc69c7afacedf"},
{file = "scipy-1.15.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:74bb864ff7640dea310a1377d8567dc2cb7599c26a79ca852fc184cc851954ac"},
{file = "scipy-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:667f950bf8b7c3a23b4199db24cb9bf7512e27e86d0e3813f015b74ec2c6e3df"},
{file = "scipy-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395be70220d1189756068b3173853029a013d8c8dd5fd3d1361d505b2aa58fa7"},
{file = "scipy-1.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce3a000cd28b4430426db2ca44d96636f701ed12e2b3ca1f2b1dd7abdd84b39a"},
{file = "scipy-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:3fe1d95944f9cf6ba77aa28b82dd6bb2a5b52f2026beb39ecf05304b8392864b"},
{file = "scipy-1.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c09aa9d90f3500ea4c9b393ee96f96b0ccb27f2f350d09a47f533293c78ea776"},
{file = "scipy-1.15.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0ac102ce99934b162914b1e4a6b94ca7da0f4058b6d6fd65b0cef330c0f3346f"},
{file = "scipy-1.15.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:09c52320c42d7f5c7748b69e9f0389266fd4f82cf34c38485c14ee976cb8cb04"},
{file = "scipy-1.15.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:cdde8414154054763b42b74fe8ce89d7f3d17a7ac5dd77204f0e142cdc9239e9"},
{file = "scipy-1.15.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c9d8fc81d6a3b6844235e6fd175ee1d4c060163905a2becce8e74cb0d7554ce"},
{file = "scipy-1.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb57b30f0017d4afa5fe5f5b150b8f807618819287c21cbe51130de7ccdaed2"},
{file = "scipy-1.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491d57fe89927fa1aafbe260f4cfa5ffa20ab9f1435025045a5315006a91b8f5"},
{file = "scipy-1.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:900f3fa3db87257510f011c292a5779eb627043dd89731b9c461cd16ef76ab3d"},
{file = "scipy-1.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:100193bb72fbff37dbd0bf14322314fc7cbe08b7ff3137f11a34d06dc0ee6b85"},
{file = "scipy-1.15.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:2114a08daec64980e4b4cbdf5bee90935af66d750146b1d2feb0d3ac30613692"},
{file = "scipy-1.15.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6b3e71893c6687fc5e29208d518900c24ea372a862854c9888368c0b267387ab"},
{file = "scipy-1.15.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:837299eec3d19b7e042923448d17d95a86e43941104d33f00da7e31a0f715d3c"},
{file = "scipy-1.15.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82add84e8a9fb12af5c2c1a3a3f1cb51849d27a580cb9e6bd66226195142be6e"},
{file = "scipy-1.15.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070d10654f0cb6abd295bc96c12656f948e623ec5f9a4eab0ddb1466c000716e"},
{file = "scipy-1.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55cc79ce4085c702ac31e49b1e69b27ef41111f22beafb9b49fea67142b696c4"},
{file = "scipy-1.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:c352c1b6d7cac452534517e022f8f7b8d139cd9f27e6fbd9f3cbd0bfd39f5bef"},
{file = "scipy-1.15.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0458839c9f873062db69a03de9a9765ae2e694352c76a16be44f93ea45c28d2b"},
{file = "scipy-1.15.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:af0b61c1de46d0565b4b39c6417373304c1d4f5220004058bdad3061c9fa8a95"},
{file = "scipy-1.15.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:71ba9a76c2390eca6e359be81a3e879614af3a71dfdabb96d1d7ab33da6f2364"},
{file = "scipy-1.15.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14eaa373c89eaf553be73c3affb11ec6c37493b7eaaf31cf9ac5dffae700c2e0"},
{file = "scipy-1.15.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f735bc41bd1c792c96bc426dece66c8723283695f02df61dcc4d0a707a42fc54"},
{file = "scipy-1.15.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2722a021a7929d21168830790202a75dbb20b468a8133c74a2c0230c72626b6c"},
{file = "scipy-1.15.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc7136626261ac1ed988dca56cfc4ab5180f75e0ee52e58f1e6aa74b5f3eacd5"},
{file = "scipy-1.15.1.tar.gz", hash = "sha256:033a75ddad1463970c96a88063a1df87ccfddd526437136b6ee81ff0312ebdf6"},
]
[package.dependencies]
numpy = ">=1.23.5,<2.5"
[package.extras]
dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"]
doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.16.5)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"]
test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
[[package]]
name = "shellingham"
version = "1.5.4"
@ -1437,6 +2032,17 @@ anyio = ">=3.6.2,<5"
[package.extras]
full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
[[package]]
name = "threadpoolctl"
version = "3.5.0"
description = "threadpoolctl"
optional = false
python-versions = ">=3.8"
files = [
{file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"},
{file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"},
]
[[package]]
name = "tomli"
version = "2.2.1"
@ -1852,4 +2458,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "96f7fa748d939ead4ee561c98f4c4a469d13e2f6802ae4c90375b54f9736bf9b"
content-hash = "da0fda4b15dd36251289a7ee4c571bbc5025ec6c412e5fbce64b3eb7e0089ae9"

@ -23,6 +23,8 @@ psycopg2-binary = "^2.9.10"
asyncpg = "^0.30.0"
requests = "^2.32.3"
openpyxl = "^3.1.5"
scikit-learn = "^1.6.1"
matplotlib = "^3.10.0"
[build-system]

@ -31,6 +31,7 @@ def get_env_tags(tag_list: List[str]) -> dict:
return tags
def get_config():
try:
# Try to load from .env file first
@ -41,6 +42,7 @@ def get_config():
return config
config = get_config()
@ -58,10 +60,10 @@ _QUOTED_DATABASE_PASSWORD = parse.quote(str(_DATABASE_CREDENTIAL_PASSWORD))
DATABASE_NAME = config("DATABASE_NAME", default="digital_twin")
DATABASE_PORT = config("DATABASE_PORT", default="5432")
DATABASE_ENGINE_POOL_SIZE = config(
"DATABASE_ENGINE_POOL_SIZE", cast=int, default=20)
DATABASE_ENGINE_POOL_SIZE = config("DATABASE_ENGINE_POOL_SIZE", cast=int, default=20)
DATABASE_ENGINE_MAX_OVERFLOW = config(
"DATABASE_ENGINE_MAX_OVERFLOW", cast=int, default=0)
"DATABASE_ENGINE_MAX_OVERFLOW", cast=int, default=0
)
# Deal with DB disconnects
# https://docs.sqlalchemy.org/en/20/core/pooling.html#pool-disconnects
DATABASE_ENGINE_POOL_PING = config("DATABASE_ENGINE_POOL_PING", default=False)
@ -70,5 +72,8 @@ SQLALCHEMY_DATABASE_URI = f"postgresql+asyncpg://{_DATABASE_CREDENTIAL_USER}:{_Q
TIMEZONE = "Asia/Jakarta"
AUTH_SERVICE_API = config(
"AUTH_SERVICE_API", default="http://192.168.1.82:8000/auth")
AUTH_SERVICE_API = config("AUTH_SERVICE_API", default="http://192.168.1.82:8000/auth")
RELIABILITY_APP_URL = config(
"RELIABILITY_APP_URL", default="http://192.168.1.82:8000/reliability"
)

@ -1,3 +1,4 @@
import os
from sqlalchemy import Select, Delete, Float, func, cast, String
from sqlalchemy.orm import selectinload
@ -9,8 +10,11 @@ from typing import Optional
from src.database.core import DbSession
from src.auth.service import CurrentUser
from src.config import RELIABILITY_APP_URL
import httpx
from src.modules.equipment.run import main
async def get_master_by_assetnum(
*, db_session: DbSession, assetnum: str
@ -111,108 +115,110 @@ async def generate_transaction(
await db_session.execute(query)
await db_session.commit()
"""Generate transaction for equipment."""
# Fetch data from external API
async def fetch_api_data(assetnum: str, year: int) -> dict:
async with httpx.AsyncClient() as client:
try:
response = await client.get(
f"http://192.168.1.82:8000/reliability/main/number-of-failures/{assetnum}/{year}/{year}",
timeout=30.0,
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status()
return response.json()
except httpx.HTTPError as e:
print(f"HTTP error occurred: {e}")
return {}
# Initialize base transaction with default values
base_transaction = {
"assetnum": data_in.assetnum,
"is_actual": 0,
"raw_cm_interval": None,
"raw_cm_material_cost": None,
"raw_cm_labor_time": None,
"raw_cm_labor_human": None,
"raw_pm_interval": None,
"raw_pm_material_cost": None,
"raw_pm_labor_time": None,
"raw_pm_labor_human": None,
"raw_predictive_labor_time": None,
"raw_predictive_labor_human": None,
"raw_oh_material_cost": None,
"raw_oh_labor_time": None,
"raw_oh_labor_human": None,
"raw_project_task_material_cost": None,
"raw_loss_output_MW": None,
"raw_loss_output_price": None,
"raw_operational_cost": None,
"raw_maintenance_cost": None,
"rc_cm_material_cost": None,
"rc_cm_labor_cost": None,
"rc_pm_material_cost": None,
"rc_pm_labor_cost": None,
"rc_predictive_labor_cost": None,
"rc_oh_material_cost": None,
"rc_oh_labor_cost": None,
"rc_project_material_cost": None,
"rc_lost_cost": None,
"rc_operation_cost": None,
"rc_maintenance_cost": None,
"rc_total_cost": None,
"eac_npv": None,
"eac_annual_mnt_cost": None,
"eac_annual_acq_cost": None,
"eac_eac": None,
}
transactions = []
# Query existing records with is_actual=1
actual_years_query = (
Select(MasterRecords.tahun)
.filter(MasterRecords.assetnum == data_in.assetnum)
.filter(MasterRecords.is_actual == 1)
)
result = await db_session.execute(actual_years_query)
actual_years = {row[0] for row in result.all()}
for sequence, year in enumerate(
range(data_in.acquisition_year - 1, data_in.forecasting_target_year + 1), 0
):
# Skip if year already has actual data
if year in actual_years:
continue
transaction = base_transaction.copy()
# Update values from API
api_data = await fetch_api_data(data_in.assetnum, year)
if api_data:
# # Get current num_fail
current_num_fail = api_data["data"][0]["num_fail"]
# # Calculate sum of previous failures for this asset
# previous_failures_query = (
# Select(func.sum(MasterRecords.raw_cm_interval))
# .filter(MasterRecords.assetnum == data_in.assetnum)
# .filter(MasterRecords.tahun < year)
# )
# previous_failures_result = await db_session.execute(previous_failures_query)
# previous_failures_sum = previous_failures_result.scalar() or 0
# # Update with current minus sum of previous
# transaction.update({"raw_cm_interval": current_num_fail - previous_failures_sum})
transaction.update({"raw_cm_interval": current_num_fail})
transaction.update({"tahun": int(year), "seq": int(sequence)})
transactions.append(MasterRecords(**transaction))
db_session.add_all(transactions)
await db_session.commit()
# Return the number of transactions created
return len(transactions)
prediction = await main(data_in.assetnum, token, RELIABILITY_APP_URL)
# # Fetch data from external API
# async def fetch_api_data(assetnum: str, year: int) -> dict:
# async with httpx.AsyncClient() as client:
# try:
# response = await client.get(
# f"{os.environ.get('RELIABILITY_APP_URL')}/main/number-of-failures/{assetnum}/{year}/{year}",
# timeout=30.0,
# headers={"Authorization": f"Bearer {token}"},
# )
# response.raise_for_status()
# return response.json()
# except httpx.HTTPError as e:
# print(f"HTTP error occurred: {e}")
# return {}
# # Initialize base transaction with default values
# base_transaction = {
# "assetnum": data_in.assetnum,
# "is_actual": 0,
# "raw_cm_interval": None,
# "raw_cm_material_cost": None,
# "raw_cm_labor_time": None,
# "raw_cm_labor_human": None,
# "raw_pm_interval": None,
# "raw_pm_material_cost": None,
# "raw_pm_labor_time": None,
# "raw_pm_labor_human": None,
# "raw_predictive_labor_time": None,
# "raw_predictive_labor_human": None,
# "raw_oh_material_cost": None,
# "raw_oh_labor_time": None,
# "raw_oh_labor_human": None,
# "raw_project_task_material_cost": None,
# "raw_loss_output_MW": None,
# "raw_loss_output_price": None,
# "raw_operational_cost": None,
# "raw_maintenance_cost": None,
# "rc_cm_material_cost": None,
# "rc_cm_labor_cost": None,
# "rc_pm_material_cost": None,
# "rc_pm_labor_cost": None,
# "rc_predictive_labor_cost": None,
# "rc_oh_material_cost": None,
# "rc_oh_labor_cost": None,
# "rc_project_material_cost": None,
# "rc_lost_cost": None,
# "rc_operation_cost": None,
# "rc_maintenance_cost": None,
# "rc_total_cost": None,
# "eac_npv": None,
# "eac_annual_mnt_cost": None,
# "eac_annual_acq_cost": None,
# "eac_eac": None,
# }
# transactions = []
# # Query existing records with is_actual=1
# actual_years_query = (
# Select(MasterRecords.tahun)
# .filter(MasterRecords.assetnum == data_in.assetnum)
# .filter(MasterRecords.is_actual == 1)
# )
# result = await db_session.execute(actual_years_query)
# actual_years = {row[0] for row in result.all()}
# for sequence, year in enumerate(
# range(data_in.acquisition_year - 1, data_in.forecasting_target_year + 1), 0
# ):
# # Skip if year already has actual data
# if year in actual_years:
# continue
# transaction = base_transaction.copy()
# # Update values from API
# api_data = await fetch_api_data(data_in.assetnum, year)
# if api_data:
# # # Get current num_fail
# current_num_fail = api_data["data"][0]["num_fail"]
# # # Calculate sum of previous failures for this asset
# # previous_failures_query = (
# # Select(func.sum(MasterRecords.raw_cm_interval))
# # .filter(MasterRecords.assetnum == data_in.assetnum)
# # .filter(MasterRecords.tahun < year)
# # )
# # previous_failures_result = await db_session.execute(previous_failures_query)
# # previous_failures_sum = previous_failures_result.scalar() or 0
# # # Update with current minus sum of previous
# # transaction.update({"raw_cm_interval": current_num_fail - previous_failures_sum})
# transaction.update({"raw_cm_interval": current_num_fail})
# transaction.update({"tahun": int(year), "seq": int(sequence)})
# transactions.append(MasterRecords(**transaction))
# db_session.add_all(transactions)
# await db_session.commit()
# # Return the number of transactions created
# return len(transactions)
return prediction
async def create(*, db_session: DbSession, equipment_in: EquipmentCreate, token):

@ -1,227 +0,0 @@
import psycopg2
from psycopg2.extras import DictCursor
from uuid import uuid4
from datetime import datetime
from config import get_connection
def get_recursive_query(cursor, equipment_id, worktype='CM'):
"""
Fungsi untuk menjalankan query rekursif berdasarkan equipment_id dan worktype.
worktype memiliki nilai default 'CM'.
"""
query = f"""
SELECT
ROW_NUMBER() OVER (ORDER BY tbl.assetnum, tbl.year, tbl.worktype) AS seq,
*
FROM
(
SELECT
a.worktype,
a.assetnum,
DATE_PART('year', TO_TIMESTAMP(a.reportdate, 'YYYY-MM-DD HH24:MI:SS.US')) AS year,
COUNT(a.wonum) AS raw_corrective_failure_interval,
SUM(a.total_cost_max) AS raw_corrective_material_cost,
-- SUM(
-- (
-- (EXTRACT(HOUR FROM TO_TIMESTAMP(wo_finish, 'YYYY-MM-DD-HH24.MI.SS.US')) +
-- EXTRACT(MINUTE FROM TO_TIMESTAMP(wo_finish, 'YYYY-MM-DD-HH24.MI.SS.US')) / 60)
-- -
-- (EXTRACT(HOUR FROM TO_TIMESTAMP(wo_start, 'YYYY-MM-DD-HH24.MI.SS.US')) +
-- EXTRACT(MINUTE FROM TO_TIMESTAMP(wo_start, 'YYYY-MM-DD-HH24.MI.SS.US')) / 60)
-- )
-- *
-- ((DATE_PART('day', TO_TIMESTAMP(a.wo_finish, 'YYYY-MM-DD-HH24.MI.SS.US') -
-- TO_TIMESTAMP(a.wo_start, 'YYYY-MM-DD-HH24.MI.SS.US'))) + 1)
-- ) AS raw_corrective_labor_time_jam,
ROUND(SUM(EXTRACT(EPOCH FROM (TO_TIMESTAMP(a.actfinish, 'YYYY-MM-DD HH24:MI:SS') - TO_TIMESTAMP(a.actstart, 'YYYY-MM-DD HH24:MI:SS'))) / 3600), 2) AS raw_corrective_labor_time_jam,
SUM(a.jumlah_labor) AS raw_corrective_labor_technician
FROM
public.dl_wo_staging AS a
WHERE
a.unit = '3'
GROUP BY
a.assetnum,
DATE_PART('year', TO_TIMESTAMP(a.reportdate, 'YYYY-MM-DD HH24:MI:SS.US')),
a.worktype
) AS tbl
WHERE
tbl.worktype = '{worktype}'
AND tbl.assetnum = '{equipment_id}'
ORDER BY
tbl.assetnum,
tbl.year,
tbl.worktype;
"""
# Eksekusi query dan fetch hasil
cursor.execute(query)
return cursor.fetchall()
def get_data_tahun(cursor):
query = f"""
select * from lcc_ms_year_data
"""
# Eksekusi query dan fetch hasil
cursor.execute(query)
return cursor.fetchall()
def query_data():
connection = None
try:
# Mendapatkan koneksi dari config.py
connection = get_connection()
if connection is None:
print("Koneksi ke database gagal.")
return
# Membuat cursor menggunakan DictCursor
cursor = connection.cursor(cursor_factory=DictCursor)
# TRUNCATE DATA
truncate_query = "TRUNCATE TABLE lcc_tr_data"
cursor.execute(truncate_query)
# Query untuk mendapatkan semua data dari tabel `lcc_ms_equipment_data`
query_main = "SELECT * FROM lcc_ms_equipment_data"
cursor.execute(query_main)
# Fetch semua hasil query
results = cursor.fetchall()
# Tahun sekarang
current_year = datetime.now().year
# Looping untuk setiap equipment_id
for row in results:
equipment_id = row['equipment_id'] # Mengambil equipment_id dari hasil query
forecasting_start_year = row['forecasting_start_year'] - 1
# CM
recursive_results = get_recursive_query(cursor, equipment_id, worktype='CM')
# PM
data_pm = get_recursive_query(cursor, equipment_id, worktype='PM')
# OH
data_oh = get_recursive_query(cursor, equipment_id, worktype='OH')
# Data Tahun
data_tahunan = get_data_tahun(cursor)
seq = 0
# Looping untuk setiap tahun
for year in range(forecasting_start_year, current_year + 1):
# Filter data berdasarkan tahun
recursive_row = next((r for r in recursive_results if r['year'] == year), None) # CM Corrective Maintenance
data_pm_row = next((r for r in data_pm if r['year'] == year), None)
data_oh_row = next((r for r in data_oh if r['year'] == year), None)
data_tahunan_row = next((r for r in data_tahunan if r['year'] == year), None)
# Cek apakah data sudah ada
check_query = """
SELECT COUNT(*) FROM lcc_tr_data
WHERE equipment_id = %s AND tahun = %s
"""
cursor.execute(check_query, (equipment_id, year))
data_exists = cursor.fetchone()[0]
if not data_exists:
# Insert data jika belum ada
if not recursive_row and not data_pm_row and not data_oh_row:
# Jika data recursive_row tidak ada
insert_query = """
INSERT INTO lcc_tr_data (
id, equipment_id, tahun, seq, is_actual,
raw_cm_interval, raw_cm_material_cost,
raw_cm_labor_time, raw_cm_labor_human
, raw_pm_interval, raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human
, raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human
, "raw_loss_output_MW", raw_loss_output_price
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s
, %s, %s, %s, %s
, %s, %s, %s
, %s, %s
)
"""
cursor.execute(
insert_query,
(
str(uuid4()), # id
equipment_id, # equipment_id
year, # tahun
seq, # seq
1, # is_actual
1, # raw_cm_interval (minimal 1 karena minimal 1x OH)
0, # raw_cm_material_cost
0, # raw_cm_labor_time
0 # raw_cm_labor_human
, 1 # pm interval set default 1
,0,0,0
, 0,0,0
, (data_tahunan_row['total_lost'] if data_tahunan_row else 0)
, data_tahunan_row['rp_per_kwh'] if data_tahunan_row else 0
)
)
else:
# Jika data recursive_row ada
insert_query = """
INSERT INTO lcc_tr_data (
id, equipment_id, tahun, seq, is_actual,
raw_cm_interval, raw_cm_material_cost,
raw_cm_labor_time, raw_cm_labor_human
, raw_pm_interval, raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human
, raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human
, "raw_loss_output_MW", raw_loss_output_price
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s
, %s, %s, %s, %s
, %s, %s, %s
, %s, %s
)
"""
cursor.execute(
insert_query,
(
str(uuid4()), # id
equipment_id, # equipment_id
year, # tahun
seq, # seq
1, # is_actual
recursive_row['raw_corrective_failure_interval']+1 if recursive_row else 1, # raw_cm_interval
recursive_row['raw_corrective_material_cost'] if recursive_row else 0, # raw_cm_material_cost
(recursive_row['raw_corrective_labor_time_jam'] or 0) if recursive_row else 0, # raw_cm_labor_time
(max(recursive_row['raw_corrective_labor_technician'], 1) if recursive_row['raw_corrective_labor_time_jam'] else 0) if recursive_row else 0, # raw_cm_labor_human
1, # raw_pm_interval
data_pm_row['raw_corrective_material_cost'] if data_pm_row else 0, # raw_pm_material_cost
(data_pm_row['raw_corrective_labor_time_jam'] or 0) if data_pm_row else 0, # raw_pm_labor_time
(max(data_pm_row['raw_corrective_labor_technician'], 1) if data_pm_row['raw_corrective_labor_time_jam'] else 0) if data_pm_row else 0, # raw_pm_labor_human
data_oh_row['raw_corrective_material_cost'] if data_oh_row else 0, # raw_oh_material_cost
(data_oh_row['raw_corrective_labor_time_jam'] or 0) if data_oh_row else 0, # raw_oh_labor_time
(max(data_oh_row['raw_corrective_labor_technician'], 1) if data_oh_row['raw_corrective_labor_time_jam'] else 0) if data_oh_row else 0, # raw_oh_labor_human
(data_tahunan_row['total_lost'] if data_tahunan_row else 0)/(recursive_row['raw_corrective_failure_interval']+1 if recursive_row else 1), # raw_loss_output_MW
data_tahunan_row['rp_per_kwh'] if data_tahunan_row else 0
)
)
print(f"Data inserted for {equipment_id} in year {year}")
seq = seq+1
# Commit perubahan
connection.commit()
except Exception as e:
print("Error saat menjalankan query:", e)
finally:
# Menutup koneksi
if connection:
cursor.close()
connection.close()
# Panggil fungsi
if __name__ == "__main__":
query_data()

@ -1,180 +0,0 @@
from modules.config import get_connection
from psycopg2.extras import DictCursor
import numpy_financial as npf
import json
class Eac:
def __init__(self):
pass
def __calculate_npv_with_db_inflation_rate(self, equipment_id):
try:
# Mendapatkan koneksi dari config.py
connection = get_connection()
if connection is None:
print("Koneksi ke database gagal.")
return None
# Membuat cursor menggunakan DictCursor
cursor = connection.cursor(cursor_factory=DictCursor)
# Query untuk mendapatkan data2 dasar
query_inflation_rate = """
select
(SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'inflation_rate') as inflation_rate
, (SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'discount_rate') as discount_rate
, (select COALESCE(rc_total_cost,0) from lcc_tr_data ltd where equipment_id = %s and seq = 0) as rc_total_cost_0
;
"""
cursor.execute(query_inflation_rate, (equipment_id,))
inflation_rate_result = cursor.fetchone()
if not inflation_rate_result:
print("Inflation rate tidak ditemukan.")
return None
inflation_rate = inflation_rate_result['inflation_rate']
disc_rate = inflation_rate_result['discount_rate']
rc_total_cost_0 = inflation_rate_result['rc_total_cost_0']
last_seq = 0
last_npv = 0
# Query untuk mendapatkan data dengan seq dan is_actual
query_data_actual = """
SELECT equipment_id, tahun, seq, is_actual, rc_total_cost
FROM lcc_tr_data
WHERE is_actual = 1 AND seq != 0
AND equipment_id = %s
ORDER BY seq;
"""
cursor.execute(query_data_actual, (equipment_id,))
data_actual = cursor.fetchall()
# Variabel untuk menyimpan hasil NPV dan PMT per baris
npv_results = []
cumulative_values = [] # Menyimpan nilai kumulatif hingga baris ke-n
# Menghitung NPV dan PMT secara bertahap untuk data aktual
for idx, row in enumerate(data_actual):
cumulative_values.append(row['rc_total_cost'])
# Menghitung NPV menggunakan rumus diskonto
final_value = sum(
value / ((1 + inflation_rate) ** (i + 1)) for i, value in enumerate(cumulative_values))
# Menghitung PMT
pmt_value = -npf.pmt(inflation_rate, row['seq'], final_value)
pmt_aq_cost = -npf.pmt(disc_rate, row['seq'], rc_total_cost_0)
eac = pmt_value + pmt_aq_cost
npv_results.append({
'seq': row['seq'],
'year': row['tahun'],
'npv': final_value,
'pmt': pmt_value,
'pmt_aq_cost': pmt_aq_cost,
'eac': eac,
'is_actual': 1
})
# Update lcc_tr_data
update_query = """
UPDATE lcc_tr_data
SET eac_npv = %s, eac_annual_mnt_cost = %s, eac_annual_acq_cost = %s, eac_eac = %s
, updated_by = 'Sys', updated_at = NOW()
WHERE equipment_id = %s AND tahun = %s;
"""
cursor.execute(update_query, (final_value, pmt_value, pmt_aq_cost, eac, equipment_id, row['tahun']))
last_seq = row['seq']
last_npv = final_value
# Commit perubahan
connection.commit()
# Query untuk mendapatkan data dengan seq dan is_actual = 0
query_data_proyeksi = """
SELECT equipment_id, tahun, seq, is_actual, rc_total_cost
FROM lcc_tr_data
WHERE is_actual = 0
ORDER BY seq;
"""
cursor.execute(query_data_proyeksi)
data_proyeksi = cursor.fetchall()
cumulative_values = []
# Menghitung NPV dan PMT secara bertahap untuk data proyeksi
for idx, row in enumerate(data_proyeksi):
cumulative_values.append(row['rc_total_cost'])
npv_value = sum(value / ((1 + inflation_rate) ** (i + 1)) for i, value in enumerate(cumulative_values))
pv_value = npf.pv(inflation_rate, last_seq, 0, npv_value)
final_value = -pv_value + last_npv
# Menghitung PMT
pmt_value = -npf.pmt(inflation_rate, row['seq'], final_value)
pmt_aq_cost = -npf.pmt(disc_rate, row['seq'], rc_total_cost_0)
eac = pmt_value + pmt_aq_cost
npv_results.append({
'seq': row['seq'],
'year': row['tahun'],
'npv': final_value,
'pmt': pmt_value,
'pmt_aq_cost': pmt_aq_cost,
'eac': eac,
'is_actual': 0
})
# Update lcc_tr_data
update_query = """
UPDATE lcc_tr_data
SET eac_npv = %s, eac_annual_mnt_cost = %s, eac_annual_acq_cost = %s, eac_eac = %s
, updated_by = 'Sys', updated_at = NOW()
WHERE equipment_id = %s AND tahun = %s;
"""
cursor.execute(update_query, (final_value, pmt_value, pmt_aq_cost, eac, equipment_id, row['tahun']))
# Commit perubahan
connection.commit()
# Menutup koneksi
cursor.close()
connection.close()
# Menampilkan hasil
for result in npv_results:
print(
f"Seq: {result['seq']}, Is Actual: {result['is_actual']}, NPV: {result['npv']:.2f}, PMT: {result['pmt']:.2f}, AQ_COST: {result['pmt_aq_cost']:.2f}, EAC: {result['eac']:.2f}")
return npv_results
except Exception as e:
print("Terjadi kesalahan:", str(e))
# ======================================================================================================================================================
def hitung_eac_equipment(self, p_equipment_id):
try:
# Mendapatkan koneksi dari config.py
connection = get_connection()
if connection is None:
print("Koneksi ke database gagal.")
return None
cursor = connection.cursor(cursor_factory=DictCursor)
rslt = self.__calculate_npv_with_db_inflation_rate(p_equipment_id)
lowest_eac_record = min(rslt, key=lambda x: x['eac'])
print(json.dumps(lowest_eac_record))
# Update lcc_tr_data
update_query = """
UPDATE lcc_ms_equipment_data
SET min_eac_info = %s, updated_by = 'Sys', updated_at = NOW()
WHERE equipment_id = %s;
"""
cursor.execute(update_query, (json.dumps(lowest_eac_record), p_equipment_id))
connection.commit()
cursor.close()
connection.close()
except Exception as e:
print("Terjadi kesalahan saat memproses semua equipment:", str(e))

@ -1,5 +1,6 @@
import psycopg2
def get_connection():
try:
# Konfigurasi koneksi database
@ -12,13 +13,20 @@ def get_connection():
# password="ariwa"
# )
connection = psycopg2.connect(
dbname = "digital_twin",
user = "postgres",
password = "postgres",
host = "192.168.1.85",
port = "5432"
dbname="digital_twin",
user="postgres",
password="postgres",
host="192.168.1.85",
port="5432",
)
connection_wo_db = psycopg2.connect(
dbname="digital_twin",
user="postgres",
password="postgres",
host="192.168.1.86",
port="5432",
)
return connection
return connection, connection_wo_db
except Exception as e:
print("Error saat koneksi ke database:", e)
return None

@ -0,0 +1,231 @@
from psycopg2.extras import DictCursor
import numpy_financial as npf
import json
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import get_connection
class Eac:
def __init__(self):
pass
def __calculate_npv_with_db_inflation_rate(self, equipment_id):
try:
# Mendapatkan koneksi dari config.py
connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None:
print("Database connection failed.")
return None
# Membuat cursor menggunakan DictCursor
cursor = connection.cursor(cursor_factory=DictCursor)
# Query untuk mendapatkan data2 dasar
query_inflation_rate = """
select
(SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'inflation_rate') as inflation_rate
, (SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'discount_rate') as discount_rate
, (select COALESCE(rc_total_cost,0) from lcc_equipment_tr_data ltd where assetnum = %s and seq = 0) as rc_total_cost_0
;
"""
cursor.execute(query_inflation_rate, (equipment_id,))
inflation_rate_result = cursor.fetchone()
if not inflation_rate_result:
print("Inflation rate tidak ditemukan.")
return None
inflation_rate = inflation_rate_result["inflation_rate"]
disc_rate = inflation_rate_result["discount_rate"]
rc_total_cost_0 = inflation_rate_result["rc_total_cost_0"]
last_seq = 0
last_npv = 0
# Query untuk mendapatkan data dengan seq dan is_actual
query_data_actual = """
SELECT assetnum, tahun, seq, is_actual, rc_total_cost
FROM lcc_equipment_tr_data
WHERE is_actual = 1 AND seq != 0
AND assetnum = %s
ORDER BY seq;
"""
cursor.execute(query_data_actual, (equipment_id,))
data_actual = cursor.fetchall()
# Variabel untuk menyimpan hasil NPV dan PMT per baris
npv_results = []
cumulative_values = [] # Menyimpan nilai kumulatif hingga baris ke-n
# Menghitung NPV dan PMT secara bertahap untuk data aktual
for idx, row in enumerate(data_actual):
cumulative_values.append(row["rc_total_cost"])
# Menghitung NPV menggunakan rumus diskonto
final_value = sum(
value / ((1 + inflation_rate) ** (i + 1))
for i, value in enumerate(cumulative_values)
)
# Menghitung PMT
pmt_value = -npf.pmt(inflation_rate, row["seq"], final_value)
pmt_aq_cost = -npf.pmt(disc_rate, row["seq"], rc_total_cost_0)
eac = pmt_value + pmt_aq_cost
npv_results.append(
{
"seq": row["seq"],
"year": row["tahun"],
"npv": final_value,
"pmt": pmt_value,
"pmt_aq_cost": pmt_aq_cost,
"eac": eac,
"is_actual": 1,
}
)
# Update lcc_equipment_tr_data
update_query = """
UPDATE lcc_equipment_tr_data
SET eac_npv = %s, eac_annual_mnt_cost = %s, eac_annual_acq_cost = %s, eac_eac = %s
, updated_by = 'Sys', updated_at = NOW()
WHERE assetnum = %s AND tahun = %s;
"""
cursor.execute(
update_query,
(
float(final_value),
float(pmt_value),
float(pmt_aq_cost),
float(eac),
equipment_id,
row["tahun"],
),
)
last_seq = row["seq"]
last_npv = float(final_value)
# Commit perubahan
connection.commit()
# Query untuk mendapatkan data dengan seq dan is_actual = 0
query_data_proyeksi = """
SELECT assetnum, tahun, seq, is_actual, rc_total_cost
FROM lcc_equipment_tr_data
WHERE assetnum = %s AND is_actual = 0
ORDER BY seq;
"""
cursor.execute(query_data_proyeksi, (equipment_id,))
data_proyeksi = cursor.fetchall()
cumulative_values = []
# Menghitung NPV dan PMT secara bertahap untuk data proyeksi
for idx, row in enumerate(data_proyeksi):
# print(row)
cumulative_values.append(row["rc_total_cost"])
npv_value = sum(
value / ((1 + inflation_rate) ** (i + 1))
for i, value in enumerate(cumulative_values)
)
pv_value = npf.pv(inflation_rate, last_seq, 0, npv_value)
final_value = -pv_value + last_npv
# Menghitung PMT
pmt_value = -npf.pmt(inflation_rate, row["seq"], final_value)
pmt_aq_cost = -npf.pmt(disc_rate, row["seq"], rc_total_cost_0)
eac = pmt_value + pmt_aq_cost
npv_results.append(
{
"seq": row["seq"],
"year": row["tahun"],
"npv": final_value,
"pmt": pmt_value,
"pmt_aq_cost": pmt_aq_cost,
"eac": eac,
"is_actual": 0,
}
)
# Update lcc_equipment_tr_data
update_query = """
UPDATE lcc_equipment_tr_data
SET eac_npv = %s, eac_annual_mnt_cost = %s, eac_annual_acq_cost = %s, eac_eac = %s
, updated_by = 'Sys', updated_at = NOW()
WHERE assetnum = %s AND tahun = %s;
"""
cursor.execute(
update_query,
(
float(final_value),
float(pmt_value),
float(pmt_aq_cost),
float(eac),
equipment_id,
row["tahun"],
),
)
# Commit perubahan
connection.commit()
# Menutup koneksi
cursor.close()
connection.close()
# Menampilkan hasil
# for result in npv_results:
# print(
# f"Seq: {result['seq']}, Is Actual: {result['is_actual']}, NPV: {result['npv']:.2f}, PMT: {result['pmt']:.2f}, AQ_COST: {result['pmt_aq_cost']:.2f}, EAC: {result['eac']:.2f}"
# )
return npv_results
except Exception as e:
print("Terjadi kesalahan:", str(e))
# ======================================================================================================================================================
def hitung_eac_equipment(self, p_equipment_id):
try:
# Mendapatkan koneksi dari config.py
connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None:
print("Database connection failed.")
return None
cursor = connection.cursor(cursor_factory=DictCursor)
rslt = self.__calculate_npv_with_db_inflation_rate(p_equipment_id)
# print(rslt)
lowest_eac_record = min(rslt, key=lambda x: x["eac"])
# print(json.dumps(lowest_eac_record))
# Update lcc_equipment_tr_data
update_query = """
UPDATE lcc_ms_equipment_data
SET min_eac_info = %s, updated_by = 'Sys', updated_at = NOW()
WHERE assetnum = %s;
"""
cursor.execute(
update_query, (json.dumps(lowest_eac_record), p_equipment_id)
)
connection.commit()
cursor.close()
connection.close()
except Exception as e:
print("Terjadi kesalahan saat memproses semua equipment:", str(e))
if __name__ == "__main__":
eac = Eac()
eac.hitung_eac_equipment("A22277")
print("EAC calculation finished.")

@ -1,3 +1,4 @@
import os
import pandas as pd
import numpy as np
import numpy_financial as npf # Gunakan numpy-financial untuk fungsi keuangan
@ -5,35 +6,50 @@ from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
import matplotlib.pyplot as plt
from starlette.config import Config
from uuid import uuid4
from modules.config import get_connection
from psycopg2.extras import DictCursor
import httpx
from dotenv import load_dotenv
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import get_connection
load_dotenv()
class Prediksi:
def __init__(self):
pass
def __init__(self, RELIABILITY_APP_URL):
self.RELIABILITY_APP_URL = RELIABILITY_APP_URL
# Fungsi untuk mengambil data dari database
def __get_param(self, equipment_id):
try:
# Mendapatkan koneksi dari config.py
connection = get_connection()
connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None:
print("Koneksi ke database gagal.")
print("Database connection failed.")
return None
# Membuat cursor menggunakan DictCursor
cursor = connection.cursor(cursor_factory=DictCursor)
# print(f"Getting params for equipment_id: {equipment_id}")
# Query untuk mendapatkan data
query = """
SELECT
(select COALESCE(forecasting_target_year, 2060) from lcc_ms_equipment_data where equipment_id = %s) AS forecasting_target_year
(select COALESCE(forecasting_target_year, 2060) from lcc_ms_equipment_data where assetnum = %s) AS forecasting_target_year
"""
cursor.execute(query, (equipment_id,))
par1 = cursor.fetchone()
return par1["forecasting_target_year"]
except Exception as e:
print(f"Error saat mengambil data dari database: {e}")
print(f"Error saat get params dari database: {e}")
return None
finally:
@ -43,15 +59,18 @@ class Prediksi:
# Fungsi untuk mengambil data dari database
def __fetch_data_from_db(self, equipment_id):
try:
# Mendapatkan koneksi dari config.py
connection = get_connection()
# Get connection from config.py (using only the first connection)
connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None:
print("Koneksi ke database gagal.")
print("Database connection failed.")
return None
# Membuat cursor menggunakan DictCursor
cursor = connection.cursor(cursor_factory=DictCursor)
# print(f"Fetcing data for equipment_id: {equipment_id}")
# Query untuk mendapatkan data
query = """
SELECT
@ -68,8 +87,8 @@ class Prediksi:
raw_oh_labor_human AS oh_labor_human,
"raw_loss_output_MW" AS loss_output_mw,
raw_loss_output_price AS loss_price
FROM lcc_tr_data
WHERE equipment_id = %s
FROM lcc_equipment_tr_data
WHERE assetnum = %s
and is_actual=1
;
"""
@ -77,7 +96,9 @@ class Prediksi:
# Mengambil hasil dan mengonversi ke DataFrame pandas
data = cursor.fetchall()
columns = [desc[0] for desc in cursor.description] # Mengambil nama kolom dari hasil query
columns = [
desc[0] for desc in cursor.description
] # Mengambil nama kolom dari hasil query
df = pd.DataFrame(data, columns=columns)
return df
@ -101,23 +122,26 @@ class Prediksi:
# Fungsi untuk menghapus data proyeksi pada tahun tertentu
def __delete_predictions_from_db(self, equipment_id):
try:
connection = get_connection()
connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None:
print("Koneksi ke database gagal.")
return
print("Database connection failed.")
return None
cursor = connection.cursor()
# Query untuk menghapus data berdasarkan tahun proyeksi
delete_query = """
DELETE FROM lcc_tr_data
WHERE equipment_id = %s AND is_actual = 0;
DELETE FROM lcc_equipment_tr_data
WHERE assetnum = %s AND is_actual = 0;
""" # Asumsikan kolom is_actual digunakan untuk membedakan data proyeksi dan data aktual
# Eksekusi query delete
cursor.execute(delete_query, (equipment_id,))
connection.commit()
print(f"Data proyeksi untuk tahun {equipment_id} berhasil dihapus.")
# print(f"Data proyeksi untuk tahun {equipment_id} berhasil dihapus.")
except Exception as e:
print(f"Error saat menghapus data proyeksi dari database: {e}")
@ -127,30 +151,33 @@ class Prediksi:
connection.close()
# Fungsi untuk menyimpan data proyeksi ke database
def __insert_predictions_to_db(self, data, equipment_id):
async def __insert_predictions_to_db(self, data, equipment_id, token):
try:
connection = get_connection()
connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None:
print("Koneksi ke database gagal.")
return
print("Database connection failed.")
return None
cursor = connection.cursor()
# Query untuk mendapatkan nilai maksimum seq
get_max_seq_query = """
SELECT COALESCE(MAX(seq), 0) FROM lcc_tr_data WHERE equipment_id = %s
SELECT COALESCE(MAX(seq), 0) FROM lcc_equipment_tr_data WHERE assetnum = %s
"""
cursor.execute(get_max_seq_query, (equipment_id,))
max_seq = cursor.fetchone()[0]
# Query untuk insert data
insert_query = """
INSERT INTO lcc_tr_data (
INSERT INTO lcc_equipment_tr_data (
id,
seq,
is_actual,
raw_pm_interval,
tahun, equipment_id,
tahun, assetnum,
raw_cm_interval, raw_cm_material_cost, raw_cm_labor_time, raw_cm_labor_human,
raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human,
raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human,
@ -161,23 +188,69 @@ class Prediksi:
)
"""
# Fetch data from external API
async def fetch_api_data(assetnum: str, year: int) -> dict:
url = self.RELIABILITY_APP_URL
# print(f"Using URL: {url}") # Add this for debugging
async with httpx.AsyncClient() as client:
# print(
# f"{url}/main/number-of-failures/{assetnum}/{int(year)}/{int(year)}"
# )
try:
response = await client.get(
f"{url}/main/number-of-failures/{assetnum}/{int(year)}/{int(year)}",
timeout=30.0,
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status()
return response.json()
except httpx.HTTPError as e:
print(f"HTTP error occurred: {e}")
return {}
# Menyiapkan data untuk batch insert
# print(f"Data to be inserted: {data}")
records_to_insert = []
for _, row in data.iterrows():
max_seq = max_seq + 1
records_to_insert.append((
str(uuid4()), max_seq, row["year"], equipment_id,
row["cm_interval"] if row["cm_interval"] >= 1 else 1, row["cm_cost"], row["cm_labor_time"],
row["cm_labor_human"],
row["pm_cost"], row["pm_labor_time"], row["pm_labor_human"],
row["oh_cost"], row["oh_labor_time"], row["oh_labor_human"],
row["loss_output_mw"], row["loss_price"]
))
# Update values from API
api_data = await fetch_api_data(equipment_id, row["year"])
if api_data:
# # Get current num_fail
cm_interval_prediction = api_data["data"][0]["num_fail"]
else:
cm_interval_prediction = (
(
float(row["cm_interval"])
if float(row["cm_interval"]) >= 1
else 1
),
)
records_to_insert.append(
(
str(uuid4()),
int(max_seq),
float(row["year"]),
equipment_id,
cm_interval_prediction,
float(row["cm_cost"]),
float(row["cm_labor_time"]),
float(row["cm_labor_human"]),
float(row["pm_cost"]),
float(row["pm_labor_time"]),
float(row["pm_labor_human"]),
float(row["oh_cost"]),
float(row["oh_labor_time"]),
float(row["oh_labor_human"]),
float(row["loss_output_mw"]),
float(row["loss_price"]),
)
)
# Eksekusi batch insert
cursor.executemany(insert_query, records_to_insert)
connection.commit()
print("Data proyeksi berhasil dimasukkan ke database.")
# print("Data proyeksi berhasil dimasukkan ke database.")
except Exception as e:
print(f"Error saat menyimpan data ke database: {e}")
@ -189,51 +262,54 @@ class Prediksi:
# Fungsi untuk menghapus data proyeksi pada tahun tertentu
def __update_date_lcc(self, equipment_id):
try:
connection = get_connection()
connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None:
print("Koneksi ke database gagal.")
return
print("Database connection failed.")
return None
cursor = connection.cursor()
# Query untuk menghapus data berdasarkan tahun proyeksi
up_query = """
update lcc_tr_data
update lcc_equipment_tr_data
set
rc_cm_material_cost = raw_cm_material_cost
,rc_cm_labor_cost = (raw_cm_interval * raw_cm_labor_time * raw_cm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
,rc_cm_labor_cost = (raw_cm_interval * raw_cm_labor_time * raw_cm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
,rc_pm_material_cost = raw_pm_material_cost
,rc_pm_labor_cost = (raw_pm_labor_time * raw_pm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
,rc_predictive_labor_cost = COALESCE( (raw_predictive_labor_time * raw_predictive_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) , 0)
,rc_pm_labor_cost = (raw_pm_labor_time * raw_pm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
,rc_predictive_labor_cost = COALESCE( (raw_predictive_labor_time * raw_predictive_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) , 0)
,rc_oh_material_cost = raw_oh_material_cost
,rc_oh_labor_cost = (raw_oh_labor_time * raw_oh_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
,rc_oh_labor_cost = (raw_oh_labor_time * raw_oh_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
,rc_project_material_cost = coalesce(raw_project_task_material_cost, 0)
,rc_lost_cost = coalesce(("raw_loss_output_MW" * raw_loss_output_price * raw_cm_interval), 0) * 1000
,rc_operation_cost = coalesce(raw_operational_cost, 0)
,rc_maintenance_cost = coalesce(raw_maintenance_cost, 0)
,rc_total_cost = (
raw_cm_material_cost
+ (raw_cm_interval * raw_cm_labor_time * raw_cm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
+ (raw_cm_interval * raw_cm_labor_time * raw_cm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
+ raw_pm_material_cost
+ (raw_pm_labor_time * raw_pm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
+ COALESCE( (raw_predictive_labor_time * raw_predictive_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) , 0)
+ (raw_pm_labor_time * raw_pm_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
+ COALESCE( (raw_predictive_labor_time * raw_predictive_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) , 0)
+ raw_oh_material_cost
+ (raw_oh_labor_time * raw_oh_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
+ (raw_oh_labor_time * raw_oh_labor_human * COALESCE((SELECT man_hour FROM lcc_ms_year_data WHERE year = lcc_equipment_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) )
+ coalesce(raw_project_task_material_cost, 0)
+ coalesce(("raw_loss_output_MW" * raw_loss_output_price * raw_cm_interval), 0) * 1000
+ coalesce(raw_operational_cost, 0)
+ coalesce(raw_maintenance_cost, 0)
)
, updated_by = 'Sys', updated_at = NOW()
where equipment_id = %s;
where assetnum = %s;
update lcc_tr_data set rc_total_cost = (select acquisition_cost from lcc_ms_equipment_data where equipment_id=lcc_tr_data.equipment_id) where equipment_id = %s and seq=0;
update lcc_equipment_tr_data set rc_total_cost = (select acquisition_cost from lcc_ms_equipment_data where assetnum=lcc_equipment_tr_data.assetnum) where assetnum = %s and seq=0;
""" # Asumsikan kolom is_actual digunakan untuk membedakan data proyeksi dan data aktual
# Eksekusi query delete
cursor.execute(up_query, (equipment_id, equipment_id))
connection.commit()
print(f"Data berhasil diupdate.")
# print(f"Data berhasil diupdate.")
except Exception as e:
print(f"Error saat update data proyeksi dari database: {e}")
@ -245,10 +321,13 @@ class Prediksi:
# Fungsi untuk mengambil parameter dari database
def __get_rate_and_max_year(self, equipment_id):
try:
connection = get_connection()
connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None:
print("Koneksi ke database gagal.")
return None, None
print("Database connection failed.")
return None
cursor = connection.cursor(cursor_factory=DictCursor)
@ -256,22 +335,26 @@ class Prediksi:
query = """
SELECT
(SELECT value_num / 100 FROM lcc_ms_master where name='inflation_rate') AS rate,
(SELECT MAX(tahun) FROM lcc_tr_data WHERE is_actual = 1 AND equipment_id = %s) AS max_year
(SELECT MAX(tahun) FROM lcc_equipment_tr_data WHERE is_actual = 1 AND assetnum = %s) AS max_year
"""
cursor.execute(query, (equipment_id,))
result = cursor.fetchone()
# Debug hasil query
print(f"Result: {result}")
# print(f"Result: {result}")
rate = result["rate"]
max_year = result["max_year"]
# Validasi nilai rate dan max_year
if rate is None:
raise Exception("Nilai 'rate' tidak boleh kosong. Periksa tabel 'lcc_ms_master'.")
raise Exception(
"Nilai 'rate' tidak boleh kosong. Periksa tabel 'lcc_ms_master'."
)
if max_year is None:
raise Exception("Nilai 'max_year' tidak boleh kosong. Periksa tabel 'lcc_tr_data'.")
raise Exception(
"Nilai 'max_year' tidak boleh kosong. Periksa tabel 'lcc_equipment_tr_data'."
)
return rate, max_year
@ -285,7 +368,7 @@ class Prediksi:
# ======================================================================================================================================================
def predict_equipment_data(self, p_equipment_id):
async def predict_equipment_data(self, p_equipment_id, token):
try:
# Mengambil data dari database
df = self.__fetch_data_from_db(p_equipment_id)
@ -316,7 +399,9 @@ class Prediksi:
# Fungsi untuk prediksi menggunakan Exponential Smoothing
def exponential_smoothing_predict(column, years):
data_series = df[column].fillna(0).values
model = ExponentialSmoothing(data_series, trend="add", seasonal=None, seasonal_periods=None)
model = ExponentialSmoothing(
data_series, trend="add", seasonal=None, seasonal_periods=None
)
model_fit = model.fit()
preds = model_fit.forecast(len(years))
return np.abs(preds)
@ -342,22 +427,36 @@ class Prediksi:
# Prediksi Future Value
nper = max_year - df["year"].max()
pv = -df[column].iloc[-1]
predictions[column] = self.__future_value_predict(rate, nper, pmt, pv, future_years)
predictions[column] = self.__future_value_predict(
rate, nper, pmt, pv, future_years
)
elif df[column].nunique() < 5:
predictions[column] = exponential_smoothing_predict(column, future_years)
predictions[column] = exponential_smoothing_predict(
column, future_years
)
elif df[column].isnull().sum() > 0:
predictions[column] = decision_tree_predict(column, future_years)
predictions[column] = decision_tree_predict(
column, future_years
)
else:
predictions[column] = linear_regression_predict(column, future_years)
predictions[column] = linear_regression_predict(
column, future_years
)
# Konversi hasil ke DataFrame
predictions_df = pd.DataFrame(predictions)
# print(predictions_df)
# Hapus data prediksi yang ada sebelumnya
self.__delete_predictions_from_db(p_equipment_id)
# Insert hasil prediksi ke database
self.__insert_predictions_to_db(predictions_df, p_equipment_id)
try:
await self.__insert_predictions_to_db(
predictions_df, p_equipment_id, token
)
except Exception as e:
print(f"Error saat insert data ke database: {e}")
# self.__insert_predictions_to_db(predictions_df, p_equipment_id)
# Update data untuk total RiskCost per tahun
self.__update_date_lcc(p_equipment_id)
@ -365,3 +464,17 @@ class Prediksi:
except Exception as e:
print(f"Program dihentikan: {e}")
import asyncio
if __name__ == "__main__":
async def main():
prediksi = Prediksi()
await prediksi.predict_equipment_data(
"A22277",
token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczOTUxODc4Ni4yOTM5ODUsImp0aSI6Ilo5clRUOFhGa3RweFZUQlBmNGxvRmciLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiNWUxNmY4YTgtMWEwMy00MTVjLWIwZjItMTVmZjczOWY1OGE4IiwibmJmIjoxNzM5NTE4Nzg2LCJjc3JmIjoiZWI0MjAzOTMtYTg1ZS00NDJjLWIyMjItZTU5MGU5MGVkYjkyIiwiZXhwIjoxNzM5NjA1MTg2LCJub25jZSI6IjVkZDdhOGYyMWIzZWUxZDZmYmI1YThhMDBlMmYyYjczIn0.3Jv943cU5FuxJ9K92JmVoOtTBqexF4Dke8TrrC4l0Uk",
)
print("Selesai.")
asyncio.run(main())

@ -0,0 +1,331 @@
import psycopg2
from psycopg2.extras import DictCursor
from uuid import uuid4
from datetime import datetime
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import get_connection
def get_recursive_query(cursor, equipment_id, worktype="CM"):
"""
Fungsi untuk menjalankan query rekursif berdasarkan equipment_id dan worktype.
worktype memiliki nilai default 'CM'.
"""
query = f"""
SELECT
ROW_NUMBER() OVER (ORDER BY tbl.assetnum, tbl.year, tbl.worktype) AS seq,
*
FROM (
SELECT
a.worktype,
a.assetnum,
EXTRACT(YEAR FROM a.reportdate) AS year,
COUNT(a.wonum) AS raw_corrective_failure_interval,
SUM(a.total_cost_max) AS raw_corrective_material_cost,
ROUND(
SUM(
EXTRACT(EPOCH FROM (
a.actfinish -
a.actstart
))
) / 3600
, 2) AS raw_corrective_labor_time_jam,
SUM(a.jumlah_labor) AS raw_corrective_labor_technician
FROM
public.wo_staging_3 AS a
WHERE
a.unit = '3'
GROUP BY
a.worktype,
a.assetnum,
EXTRACT(YEAR FROM a.reportdate)
) AS tbl
WHERE
tbl.worktype = '{worktype}'
AND tbl.assetnum = '{equipment_id}'
ORDER BY
tbl.assetnum,
tbl.year,
tbl.worktype
"""
# Eksekusi query dan fetch hasil
cursor.execute(query)
return cursor.fetchall()
def get_data_tahun(cursor):
query = f"""
select * from lcc_ms_year_data
"""
# Eksekusi query dan fetch hasil
cursor.execute(query)
return cursor.fetchall()
def query_data():
connection = None
try:
# Mendapatkan koneksi dari config.py
connection, connection_wo_db = get_connection()
if connection is None or connection_wo_db is None:
print("Database connection failed.")
return
# Membuat cursor menggunakan DictCursor
cursor = connection.cursor(cursor_factory=DictCursor)
cursor_wo = connection_wo_db.cursor(cursor_factory=DictCursor)
# TRUNCATE DATA
# truncate_query = "TRUNCATE TABLE lcc_equipment_tr_data"
# cursor.execute(truncate_query)
# Query untuk mendapatkan semua data dari tabel `lcc_ms_equipment_data`
query_main = "SELECT * FROM lcc_ms_equipment_data"
cursor.execute(query_main)
# Fetch semua hasil query
results = cursor.fetchall()
# Tahun sekarang
current_year = datetime.now().year
# Looping untuk setiap equipment_id
for row in results:
equipment_id = row["assetnum"] # Mengambil equipment_id dari hasil query
forecasting_start_year = row["forecasting_start_year"] - 1
# CM
recursive_results = get_recursive_query(
cursor_wo, equipment_id, worktype="CM"
)
# PM
data_pm = get_recursive_query(cursor_wo, equipment_id, worktype="PM")
# OH
data_oh = get_recursive_query(cursor_wo, equipment_id, worktype="OH")
# Data Tahun
data_tahunan = get_data_tahun(cursor)
seq = 0
# Looping untuk setiap tahun
for year in range(forecasting_start_year, current_year + 1):
# print(f"Processing equipment_id {equipment_id} in year {year}")
# Filter data berdasarkan tahun
recursive_row = next(
(r for r in recursive_results if r["year"] == year), None
) # CM Corrective Maintenance
data_pm_row = next((r for r in data_pm if r["year"] == year), None)
data_oh_row = next((r for r in data_oh if r["year"] == year), None)
data_tahunan_row = next(
(r for r in data_tahunan if r["year"] == year), None
)
# Cek apakah data sudah ada
check_query = """
SELECT COUNT(*) FROM lcc_equipment_tr_data
WHERE assetnum = %s AND tahun = %s
"""
try:
cursor.execute(check_query, (equipment_id, year))
data_exists = cursor.fetchone()[0]
# print("Data exists for equipment_id", equipment_id)
except Exception as e:
print(f"Error checking data for equipment_id {equipment_id}: {e}")
continue
if not data_exists:
print("Data not exists for equipment_id", equipment_id)
# Insert data jika belum ada
if not recursive_row and not data_pm_row and not data_oh_row:
# Jika data recursive_row tidak ada
insert_query = """
INSERT INTO lcc_equipment_tr_data (
id, assetnum, tahun, seq, is_actual,
raw_cm_interval, raw_cm_material_cost,
raw_cm_labor_time, raw_cm_labor_human
, raw_pm_interval, raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human
, raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human
, "raw_loss_output_MW", raw_loss_output_price
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s
, %s, %s, %s, %s
, %s, %s, %s
, %s, %s
)
"""
cursor.execute(
insert_query,
(
str(uuid4()), # id
equipment_id, # equipment_id
year, # tahun
seq, # seq
1, # is_actual
1, # raw_cm_interval (minimal 1 karena minimal 1x OH)
0, # raw_cm_material_cost
0, # raw_cm_labor_time
0, # raw_cm_labor_human
1, # pm interval set default 1
0,
0,
0,
0,
0,
0,
(
data_tahunan_row["total_lost"]
if data_tahunan_row
else 0
),
(
data_tahunan_row["rp_per_kwh"]
if data_tahunan_row
else 0
),
),
)
else:
print("Data exists for equipment_id", equipment_id)
# Jika data recursive_row ada
# raw_cm_interval ambil dari reliability predict
insert_query = """
INSERT INTO lcc_equipment_tr_data (
id, equipment_id, tahun, seq, is_actual,
raw_cm_interval, raw_cm_material_cost,
raw_cm_labor_time, raw_cm_labor_human
, raw_pm_interval, raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human
, raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human
, "raw_loss_output_MW", raw_loss_output_price
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s
, %s, %s, %s, %s
, %s, %s, %s
, %s, %s
)
"""
cursor.execute(
insert_query,
(
str(uuid4()), # id
equipment_id, # equipment_id
year, # tahun
seq, # seq
1, # is_actual
(
recursive_row["raw_corrective_failure_interval"] + 1
if recursive_row
else 1
), # raw_cm_interval nanti ambil dari API reliability predict
(
recursive_row["raw_corrective_material_cost"]
if recursive_row
else 0
), # raw_cm_material_cost
(
(
recursive_row["raw_corrective_labor_time_jam"]
or 0
)
if recursive_row
else 0
), # raw_cm_labor_time
(
(
max(
recursive_row[
"raw_corrective_labor_technician"
],
1,
)
if recursive_row[
"raw_corrective_labor_time_jam"
]
else 0
)
if recursive_row
else 0
), # raw_cm_labor_human
1, # raw_pm_interval
(
data_pm_row["raw_corrective_material_cost"]
if data_pm_row
else 0
), # raw_pm_material_cost
(
(data_pm_row["raw_corrective_labor_time_jam"] or 0)
if data_pm_row
else 0
), # raw_pm_labor_time
(
(
max(
data_pm_row[
"raw_corrective_labor_technician"
],
1,
)
if data_pm_row["raw_corrective_labor_time_jam"]
else 0
)
if data_pm_row
else 0
), # raw_pm_labor_human
(
data_oh_row["raw_corrective_material_cost"]
if data_oh_row
else 0
), # raw_oh_material_cost
(
(data_oh_row["raw_corrective_labor_time_jam"] or 0)
if data_oh_row
else 0
), # raw_oh_labor_time
(
(
max(
data_oh_row[
"raw_corrective_labor_technician"
],
1,
)
if data_oh_row["raw_corrective_labor_time_jam"]
else 0
)
if data_oh_row
else 0
), # raw_oh_labor_human
(
data_tahunan_row["total_lost"]
if data_tahunan_row
else 0
)
/ (
recursive_row["raw_corrective_failure_interval"] + 1
if recursive_row
else 1
), # raw_loss_output_MW
(
data_tahunan_row["rp_per_kwh"]
if data_tahunan_row
else 0
),
),
)
print(f"Data inserted for {equipment_id} in year {year}")
seq = seq + 1
# Commit perubahan
connection.commit()
except Exception as e:
print("Error saat menjalankan query:", e)
finally:
# Menutup koneksi
if connection or connection_wo_db:
cursor.close()
cursor_wo.close()
connection.close()
connection_wo_db.close()
# print("========Process finished and connection closed.========")

@ -0,0 +1,32 @@
from .insert_actual_data import query_data
from .Prediksi import Prediksi
from .Eac import Eac
import asyncio
import time
# Panggil fungsi
async def main(assetnum, token, RELIABILITY_APP_URL):
start_time = time.time()
query_data()
prediksi = Prediksi(RELIABILITY_APP_URL)
await prediksi.predict_equipment_data(
assetnum,
token=token,
)
eac = Eac()
eac.hitung_eac_equipment(assetnum)
end_time = time.time()
execution_time = end_time - start_time
print(f"EAC calculation finished in {execution_time:.2f} seconds.")
if __name__ == "__main__":
asyncio.run(
main(
"A22277",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczOTUxODc4Ni4yOTM5ODUsImp0aSI6Ilo5clRUOFhGa3RweFZUQlBmNGxvRmciLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiNWUxNmY4YTgtMWEwMy00MTVjLWIwZjItMTVmZjczOWY1OGE4IiwibmJmIjoxNzM5NTE4Nzg2LCJjc3JmIjoiZWI0MjAzOTMtYTg1ZS00NDJjLWIyMjItZTU5MGU5MGVkYjkyIiwiZXhwIjoxNzM5NjA1MTg2LCJub25jZSI6IjVkZDdhOGYyMWIzZWUxZDZmYmI1YThhMDBlMmYyYjczIn0.3Jv943cU5FuxJ9K92JmVoOtTBqexF4Dke8TrrC4l0Uk",
)
)

@ -7,15 +7,23 @@ from datetime import datetime
import time
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import get_connection
ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
file_ref = f"{ROOT_DIR}/wlc.xlsx"
file_hasil = f"{ROOT_DIR}/hasil/wlc.xlsx"
start_time = time.time()
conn = get_connection()
# conn = get_connection()
# if conn is None:
# print("Koneksi ke database gagal.")
# sys.exit(1)
connections = get_connection()
conn = connections[0] if isinstance(connections, tuple) else connections
if conn is None:
print("Koneksi ke database gagal.")
sys.exit(1)
@ -37,6 +45,7 @@ def fetch_data_from_postgresql(q=""):
print(f"Error: {e}")
return None
def insert_and_shift_right(file_path, sheet_name):
# Load workbook and select sheet
wb = openpyxl.load_workbook(file_path)
@ -57,7 +66,9 @@ def insert_and_shift_right(file_path, sheet_name):
current_value = current_cell.value
if isinstance(current_value, str) and current_value.startswith("="):
# Adjust formula to the new column
formula = current_value.replace(get_column_letter(col), get_column_letter(new_col_idx))
formula = current_value.replace(
get_column_letter(col), get_column_letter(new_col_idx)
)
sheet.cell(row=row, column=new_col_idx, value=formula)
else:
sheet.cell(row=row, column=new_col_idx, value=current_value)
@ -65,9 +76,11 @@ def insert_and_shift_right(file_path, sheet_name):
# Copy cell style (fill color)
new_cell = sheet.cell(row=row, column=new_col_idx)
if current_cell.fill:
new_cell.fill = PatternFill(start_color=current_cell.fill.start_color.rgb,
end_color=current_cell.fill.end_color.rgb,
fill_type=current_cell.fill.fill_type)
new_cell.fill = PatternFill(
start_color=current_cell.fill.start_color.rgb,
end_color=current_cell.fill.end_color.rgb,
fill_type=current_cell.fill.fill_type,
)
# Save workbook with changes
wb.save(file_path)
@ -100,7 +113,9 @@ def insert_column_data(file_path, sheet_name, col_name, data):
for row_name, value in data.items():
row_index = None
# Cari indeks baris berdasarkan nama di kolom pertama
for row in range(2, sheet.max_row + 1): # Mulai dari baris ke-2 untuk menghindari header
for row in range(
2, sheet.max_row + 1
): # Mulai dari baris ke-2 untuk menghindari header
if sheet.cell(row=row, column=1).value == row_name:
row_index = row
break
@ -115,6 +130,7 @@ def insert_column_data(file_path, sheet_name, col_name, data):
wb.close()
wb.save(file_path)
def insert_param(file_path):
wb = openpyxl.load_workbook(file_path, data_only=False)
sheet = wb["Params"]
@ -138,11 +154,13 @@ def insert_param(file_path):
mapping_data = {}
for item in data_map:
mapping_data[item['value_str']] = round(item['value_num'], 2)
mapping_data[item["value_str"]] = round(item["value_num"], 2)
# Jika kolom ditemukan, masukkan data per baris
if col_index:
for row in range(2, sheet.max_row + 1): # Mulai dari baris ke-2 untuk menghindari header
for row in range(
2, sheet.max_row + 1
): # Mulai dari baris ke-2 untuk menghindari header
param_name = sheet.cell(row=row, column=1).value # Kolom 1 == value_str
if param_name in mapping_data:
sheet.cell(row=row, column=col_index, value=mapping_data[param_name])
@ -153,6 +171,7 @@ def insert_param(file_path):
wb.save(file_path)
wb.close()
def get_abjad(i):
if i < 0:
raise ValueError("Indeks harus berupa angka 0 atau lebih.")
@ -162,18 +181,16 @@ def get_abjad(i):
result = chr(65 + remainder) + result
i -= 1 # Mengurangi 1 untuk menangani offset ke basis 0
return result
def validate_number(n):
return n if n is not None else 0
# Example usage
# insert_and_shift_right(file_ref, "Calc")
# ====================================================== 00000 =================================================
# =============================================== SET DATA KE EXCEL =================================================
# ====================================================== 00000 =================================================
@ -193,53 +210,66 @@ if data:
col_data = {}
# Tambahkan data tambahan berdasarkan kondisi
if record['is_actual'] == 1:
col_data.update({
"Net Capacity Factor": validate_number(record['net_capacity_factor']),
"EAF": validate_number(record['eaf']),
"Biaya Investasi Tambahan": validate_number(record['cost_a_pinjaman'])/1000000,
"O & M Cost": validate_number(record['cost_bd_om'])/1000000,
"Periodic Maintenance Cost (Non MI)": validate_number(record['cost_bd_pm_nonmi'])/1000000,
"Production (Bruto)": validate_number(record['production_bruto']),
"Production (Netto)": validate_number(record['production_netto']),
"Fuel Consumption": validate_number(record['fuel_consumption']),
"Revenue A": validate_number(record['revenue_a'])/1000000,
"Revenue B": validate_number(record['revenue_b'])/1000000,
"Revenue C": validate_number(record['revenue_c'])/1000000,
"Revenue D": validate_number(record['revenue_d'])/1000000,
"Fuel Cost": validate_number(record['cost_c_fuel'])/1000000
})
if record["is_actual"] == 1:
col_data.update(
{
"Net Capacity Factor": validate_number(
record["net_capacity_factor"]
),
"EAF": validate_number(record["eaf"]),
"Biaya Investasi Tambahan": validate_number(
record["cost_a_pinjaman"]
)
/ 1000000,
"O & M Cost": validate_number(record["cost_bd_om"]) / 1000000,
"Periodic Maintenance Cost (Non MI)": validate_number(
record["cost_bd_pm_nonmi"]
)
/ 1000000,
"Production (Bruto)": validate_number(record["production_bruto"]),
"Production (Netto)": validate_number(record["production_netto"]),
"Fuel Consumption": validate_number(record["fuel_consumption"]),
"Revenue A": validate_number(record["revenue_a"]) / 1000000,
"Revenue B": validate_number(record["revenue_b"]) / 1000000,
"Revenue C": validate_number(record["revenue_c"]) / 1000000,
"Revenue D": validate_number(record["revenue_d"]) / 1000000,
"Fuel Cost": validate_number(record["cost_c_fuel"]) / 1000000,
}
)
else:
seq_offset = record['seq'] + 2
col_data.update({
"Net Capacity Factor": validate_number(record['net_capacity_factor']),
"EAF": validate_number(record['eaf']),
"Biaya Investasi Tambahan": validate_number(record['cost_a_pinjaman'])/1000000,
"O & M Cost": validate_number(record['cost_bd_om'])/1000000,
"Periodic Maintenance Cost (Non MI)": validate_number(record['cost_bd_pm_nonmi'])/1000000,
"Production (Bruto)": f"={get_abjad(seq_offset)}7/(100-SUM(Params!$C$16:$C$17))*100",
"Production (Netto)": f"={get_abjad(seq_offset)}4*8760*Params!$C$15/100",
"Fuel Consumption": f"={get_abjad(seq_offset)}6*Params!$C$18",
"Revenue A": f"=(Params!$C$19*{get_abjad(seq_offset)}5*Params!$C$15*1000*12/100)/1000000",
"Revenue B": f"=(Params!$C$20*{get_abjad(seq_offset)}5*Params!$C$15*1000*12/100)/1000000",
"Revenue C": f"=Params!$C$21*{get_abjad(seq_offset)}7*1000/1000000",
"Revenue D": f"=Params!$C$22*{get_abjad(seq_offset)}7*1000/1000000",
"Fuel Cost": f"={get_abjad(seq_offset)}9*Params!$C$23/10^6"
})
seq_offset = record["seq"] + 2
col_data.update(
{
"Net Capacity Factor": validate_number(
record["net_capacity_factor"]
),
"EAF": validate_number(record["eaf"]),
"Biaya Investasi Tambahan": validate_number(
record["cost_a_pinjaman"]
)
/ 1000000,
"O & M Cost": validate_number(record["cost_bd_om"]) / 1000000,
"Periodic Maintenance Cost (Non MI)": validate_number(
record["cost_bd_pm_nonmi"]
)
/ 1000000,
"Production (Bruto)": f"={get_abjad(seq_offset)}7/(100-SUM(Params!$C$16:$C$17))*100",
"Production (Netto)": f"={get_abjad(seq_offset)}4*8760*Params!$C$15/100",
"Fuel Consumption": f"={get_abjad(seq_offset)}6*Params!$C$18",
"Revenue A": f"=(Params!$C$19*{get_abjad(seq_offset)}5*Params!$C$15*1000*12/100)/1000000",
"Revenue B": f"=(Params!$C$20*{get_abjad(seq_offset)}5*Params!$C$15*1000*12/100)/1000000",
"Revenue C": f"=Params!$C$21*{get_abjad(seq_offset)}7*1000/1000000",
"Revenue D": f"=Params!$C$22*{get_abjad(seq_offset)}7*1000/1000000",
"Fuel Cost": f"={get_abjad(seq_offset)}9*Params!$C$23/10^6",
}
)
# Masukkan data ke Excel
insert_column_data(file_ref, "Calc", record['tahun'], col_data)
insert_column_data(file_ref, "Calc", record["tahun"], col_data)
else:
print("No data found.")
# ====================================================== 00000 =================================================
# =============================================== SIMPAN UNTUK CHART =================================================
# ====================================================== 00000 =================================================
@ -274,8 +304,10 @@ column_mapping = {
"Capex (Component A)": "chart_capex_component_a",
"Biaya Investasi Tambahan": "chart_capex_biaya_investasi_tambahan",
"Acquisition Cost": "chart_capex_acquisition_cost",
"Capex Annualized": "chart_capex_annualized"
"Capex Annualized": "chart_capex_annualized",
}
# Fungsi untuk memperbarui database
def update_database_from_excel():
# Buka koneksi ke PostgreSQL
@ -286,17 +318,23 @@ def update_database_from_excel():
sheet = wb["Upload"]
# Ambil tahun dari baris pertama mulai dari kolom ketiga
years = [sheet.cell(row=1, column=col).value for col in range(3, sheet.max_column + 1)]
years = [
sheet.cell(row=1, column=col).value
for col in range(3, sheet.max_column + 1)
]
# Loop melalui setiap baris di Excel mulai dari baris kedua
for row in range(2, sheet.max_row + 1):
row_name = sheet.cell(row=row, column=1).value # Nama data di kolom pertama
row_name = row_name if row_name else sheet.cell(row=row,column=2).value # Nama data di kolom kedua jika kolom pertama kosong
row_name = (
row_name if row_name else sheet.cell(row=row, column=2).value
) # Nama data di kolom kedua jika kolom pertama kosong
db_column = column_mapping.get(row_name)
if db_column:
for col_index, year in enumerate(years, start=3): # Iterasi mulai dari kolom ke-3
for col_index, year in enumerate(
years, start=3
): # Iterasi mulai dari kolom ke-3
value = sheet.cell(row=row, column=col_index).value
if value is None:
@ -308,7 +346,9 @@ def update_database_from_excel():
SET {db_column} = %s
WHERE tahun = %s
"""
seq = sheet.cell(row=row, column=2).value # Ambil seq dari kolom kedua jika dibutuhkan
seq = sheet.cell(
row=row, column=2
).value # Ambil seq dari kolom kedua jika dibutuhkan
cur.execute(query, (value, year)) # Jalankan query dengan parameter
conn.commit()
@ -317,14 +357,9 @@ def update_database_from_excel():
except Exception as e:
conn.rollback()
print(f"Error: {e}")
update_database_from_excel()
update_database_from_excel()
cur.close()
@ -336,8 +371,3 @@ minutes = int(elapsed_time // 60)
seconds = int(elapsed_time % 60)
# Cetak hasil
print(f"Execution time: {minutes} minutes and {seconds} seconds")

Binary file not shown.
Loading…
Cancel
Save