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"}, {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]] [[package]]
name = "deprecated" name = "deprecated"
version = "1.2.18" version = "1.2.18"
@ -402,6 +490,79 @@ uvicorn = {version = ">=0.15.0", extras = ["standard"]}
[package.extras] [package.extras]
standard = ["uvicorn[standard] (>=0.15.0)"] 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]] [[package]]
name = "greenlet" name = "greenlet"
version = "3.1.1" version = "3.1.1"
@ -642,6 +803,106 @@ MarkupSafe = ">=2.0"
[package.extras] [package.extras]
i18n = ["Babel (>=2.7)"] 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]] [[package]]
name = "limits" name = "limits"
version = "4.0.1" version = "4.0.1"
@ -764,6 +1025,63 @@ files = [
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, {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]] [[package]]
name = "mdurl" name = "mdurl"
version = "0.1.2" version = "0.1.2"
@ -775,6 +1093,70 @@ files = [
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, {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]] [[package]]
name = "openpyxl" name = "openpyxl"
version = "3.1.5" version = "3.1.5"
@ -800,6 +1182,94 @@ files = [
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, {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]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.5.0" version = "1.5.0"
@ -1037,6 +1507,20 @@ files = [
[package.extras] [package.extras]
windows-terminal = ["colorama (>=0.4.6)"] 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]] [[package]]
name = "pytest" name = "pytest"
version = "8.3.4" version = "8.3.4"
@ -1227,6 +1711,117 @@ click = ">=8.1.7"
rich = ">=13.7.1" rich = ">=13.7.1"
typing-extensions = ">=4.12.2" 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]] [[package]]
name = "shellingham" name = "shellingham"
version = "1.5.4" version = "1.5.4"
@ -1437,6 +2032,17 @@ anyio = ">=3.6.2,<5"
[package.extras] [package.extras]
full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] 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]] [[package]]
name = "tomli" name = "tomli"
version = "2.2.1" version = "2.2.1"
@ -1852,4 +2458,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "96f7fa748d939ead4ee561c98f4c4a469d13e2f6802ae4c90375b54f9736bf9b" content-hash = "da0fda4b15dd36251289a7ee4c571bbc5025ec6c412e5fbce64b3eb7e0089ae9"

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

@ -31,6 +31,7 @@ def get_env_tags(tag_list: List[str]) -> dict:
return tags return tags
def get_config(): def get_config():
try: try:
# Try to load from .env file first # Try to load from .env file first
@ -41,6 +42,7 @@ def get_config():
return config return config
config = get_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_NAME = config("DATABASE_NAME", default="digital_twin")
DATABASE_PORT = config("DATABASE_PORT", default="5432") DATABASE_PORT = config("DATABASE_PORT", default="5432")
DATABASE_ENGINE_POOL_SIZE = config( DATABASE_ENGINE_POOL_SIZE = config("DATABASE_ENGINE_POOL_SIZE", cast=int, default=20)
"DATABASE_ENGINE_POOL_SIZE", cast=int, default=20)
DATABASE_ENGINE_MAX_OVERFLOW = config( 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 # Deal with DB disconnects
# https://docs.sqlalchemy.org/en/20/core/pooling.html#pool-disconnects # https://docs.sqlalchemy.org/en/20/core/pooling.html#pool-disconnects
DATABASE_ENGINE_POOL_PING = config("DATABASE_ENGINE_POOL_PING", default=False) 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" TIMEZONE = "Asia/Jakarta"
AUTH_SERVICE_API = config( AUTH_SERVICE_API = config("AUTH_SERVICE_API", default="http://192.168.1.82:8000/auth")
"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 import Select, Delete, Float, func, cast, String
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
@ -9,8 +10,11 @@ from typing import Optional
from src.database.core import DbSession from src.database.core import DbSession
from src.auth.service import CurrentUser from src.auth.service import CurrentUser
from src.config import RELIABILITY_APP_URL
import httpx import httpx
from src.modules.equipment.run import main
async def get_master_by_assetnum( async def get_master_by_assetnum(
*, db_session: DbSession, assetnum: str *, db_session: DbSession, assetnum: str
@ -111,108 +115,110 @@ async def generate_transaction(
await db_session.execute(query) await db_session.execute(query)
await db_session.commit() await db_session.commit()
"""Generate transaction for equipment.""" """Generate transaction for equipment."""
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: # # Fetch data from external API
async with httpx.AsyncClient() as client: # async def fetch_api_data(assetnum: str, year: int) -> dict:
try: # async with httpx.AsyncClient() as client:
response = await client.get( # try:
f"http://192.168.1.82:8000/reliability/main/number-of-failures/{assetnum}/{year}/{year}", # response = await client.get(
timeout=30.0, # f"{os.environ.get('RELIABILITY_APP_URL')}/main/number-of-failures/{assetnum}/{year}/{year}",
headers={"Authorization": f"Bearer {token}"}, # timeout=30.0,
) # headers={"Authorization": f"Bearer {token}"},
response.raise_for_status() # )
return response.json() # response.raise_for_status()
except httpx.HTTPError as e: # return response.json()
print(f"HTTP error occurred: {e}") # except httpx.HTTPError as e:
return {} # print(f"HTTP error occurred: {e}")
# return {}
# Initialize base transaction with default values
base_transaction = { # # Initialize base transaction with default values
"assetnum": data_in.assetnum, # base_transaction = {
"is_actual": 0, # "assetnum": data_in.assetnum,
"raw_cm_interval": None, # "is_actual": 0,
"raw_cm_material_cost": None, # "raw_cm_interval": None,
"raw_cm_labor_time": None, # "raw_cm_material_cost": None,
"raw_cm_labor_human": None, # "raw_cm_labor_time": None,
"raw_pm_interval": None, # "raw_cm_labor_human": None,
"raw_pm_material_cost": None, # "raw_pm_interval": None,
"raw_pm_labor_time": None, # "raw_pm_material_cost": None,
"raw_pm_labor_human": None, # "raw_pm_labor_time": None,
"raw_predictive_labor_time": None, # "raw_pm_labor_human": None,
"raw_predictive_labor_human": None, # "raw_predictive_labor_time": None,
"raw_oh_material_cost": None, # "raw_predictive_labor_human": None,
"raw_oh_labor_time": None, # "raw_oh_material_cost": None,
"raw_oh_labor_human": None, # "raw_oh_labor_time": None,
"raw_project_task_material_cost": None, # "raw_oh_labor_human": None,
"raw_loss_output_MW": None, # "raw_project_task_material_cost": None,
"raw_loss_output_price": None, # "raw_loss_output_MW": None,
"raw_operational_cost": None, # "raw_loss_output_price": None,
"raw_maintenance_cost": None, # "raw_operational_cost": None,
"rc_cm_material_cost": None, # "raw_maintenance_cost": None,
"rc_cm_labor_cost": None, # "rc_cm_material_cost": None,
"rc_pm_material_cost": None, # "rc_cm_labor_cost": None,
"rc_pm_labor_cost": None, # "rc_pm_material_cost": None,
"rc_predictive_labor_cost": None, # "rc_pm_labor_cost": None,
"rc_oh_material_cost": None, # "rc_predictive_labor_cost": None,
"rc_oh_labor_cost": None, # "rc_oh_material_cost": None,
"rc_project_material_cost": None, # "rc_oh_labor_cost": None,
"rc_lost_cost": None, # "rc_project_material_cost": None,
"rc_operation_cost": None, # "rc_lost_cost": None,
"rc_maintenance_cost": None, # "rc_operation_cost": None,
"rc_total_cost": None, # "rc_maintenance_cost": None,
"eac_npv": None, # "rc_total_cost": None,
"eac_annual_mnt_cost": None, # "eac_npv": None,
"eac_annual_acq_cost": None, # "eac_annual_mnt_cost": None,
"eac_eac": None, # "eac_annual_acq_cost": None,
} # "eac_eac": None,
# }
transactions = []
# transactions = []
# Query existing records with is_actual=1
actual_years_query = ( # # Query existing records with is_actual=1
Select(MasterRecords.tahun) # actual_years_query = (
.filter(MasterRecords.assetnum == data_in.assetnum) # Select(MasterRecords.tahun)
.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.assetnum == data_in.assetnum)
# .filter(MasterRecords.tahun < year) # .filter(MasterRecords.is_actual == 1)
# ) # )
# previous_failures_result = await db_session.execute(previous_failures_query) # result = await db_session.execute(actual_years_query)
# previous_failures_sum = previous_failures_result.scalar() or 0 # actual_years = {row[0] for row in result.all()}
# # Update with current minus sum of previous # for sequence, year in enumerate(
# transaction.update({"raw_cm_interval": current_num_fail - previous_failures_sum}) # range(data_in.acquisition_year - 1, data_in.forecasting_target_year + 1), 0
transaction.update({"raw_cm_interval": current_num_fail}) # ):
transaction.update({"tahun": int(year), "seq": int(sequence)}) # # Skip if year already has actual data
transactions.append(MasterRecords(**transaction)) # if year in actual_years:
# continue
db_session.add_all(transactions)
await db_session.commit() # transaction = base_transaction.copy()
# # Update values from API
# Return the number of transactions created # api_data = await fetch_api_data(data_in.assetnum, year)
return len(transactions)
# 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): 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 import psycopg2
def get_connection(): def get_connection():
try: try:
# Konfigurasi koneksi database # Konfigurasi koneksi database
@ -16,9 +17,16 @@ def get_connection():
user="postgres", user="postgres",
password="postgres", password="postgres",
host="192.168.1.85", host="192.168.1.85",
port = "5432" 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: except Exception as e:
print("Error saat koneksi ke database:", e) print("Error saat koneksi ke database:", e)
return None 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 pandas as pd
import numpy as np import numpy as np
import numpy_financial as npf # Gunakan numpy-financial untuk fungsi keuangan 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.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor from sklearn.tree import DecisionTreeRegressor
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from starlette.config import Config
from uuid import uuid4 from uuid import uuid4
from modules.config import get_connection
from psycopg2.extras import DictCursor 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: class Prediksi:
def __init__(self): def __init__(self, RELIABILITY_APP_URL):
pass self.RELIABILITY_APP_URL = RELIABILITY_APP_URL
# Fungsi untuk mengambil data dari database # Fungsi untuk mengambil data dari database
def __get_param(self, equipment_id): def __get_param(self, equipment_id):
try: try:
# Mendapatkan koneksi dari config.py # Mendapatkan koneksi dari config.py
connection = get_connection() connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None: if connection is None:
print("Koneksi ke database gagal.") print("Database connection failed.")
return None return None
# Membuat cursor menggunakan DictCursor # Membuat cursor menggunakan DictCursor
cursor = connection.cursor(cursor_factory=DictCursor) cursor = connection.cursor(cursor_factory=DictCursor)
# print(f"Getting params for equipment_id: {equipment_id}")
# Query untuk mendapatkan data # Query untuk mendapatkan data
query = """ query = """
SELECT 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,)) cursor.execute(query, (equipment_id,))
par1 = cursor.fetchone() par1 = cursor.fetchone()
return par1["forecasting_target_year"] return par1["forecasting_target_year"]
except Exception as e: except Exception as e:
print(f"Error saat mengambil data dari database: {e}") print(f"Error saat get params dari database: {e}")
return None return None
finally: finally:
@ -43,15 +59,18 @@ class Prediksi:
# Fungsi untuk mengambil data dari database # Fungsi untuk mengambil data dari database
def __fetch_data_from_db(self, equipment_id): def __fetch_data_from_db(self, equipment_id):
try: try:
# Mendapatkan koneksi dari config.py # Get connection from config.py (using only the first connection)
connection = get_connection() connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None: if connection is None:
print("Koneksi ke database gagal.") print("Database connection failed.")
return None return None
# Membuat cursor menggunakan DictCursor # Membuat cursor menggunakan DictCursor
cursor = connection.cursor(cursor_factory=DictCursor) cursor = connection.cursor(cursor_factory=DictCursor)
# print(f"Fetcing data for equipment_id: {equipment_id}")
# Query untuk mendapatkan data # Query untuk mendapatkan data
query = """ query = """
SELECT SELECT
@ -68,8 +87,8 @@ class Prediksi:
raw_oh_labor_human AS oh_labor_human, raw_oh_labor_human AS oh_labor_human,
"raw_loss_output_MW" AS loss_output_mw, "raw_loss_output_MW" AS loss_output_mw,
raw_loss_output_price AS loss_price raw_loss_output_price AS loss_price
FROM lcc_tr_data FROM lcc_equipment_tr_data
WHERE equipment_id = %s WHERE assetnum = %s
and is_actual=1 and is_actual=1
; ;
""" """
@ -77,7 +96,9 @@ class Prediksi:
# Mengambil hasil dan mengonversi ke DataFrame pandas # Mengambil hasil dan mengonversi ke DataFrame pandas
data = cursor.fetchall() 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) df = pd.DataFrame(data, columns=columns)
return df return df
@ -101,23 +122,26 @@ class Prediksi:
# Fungsi untuk menghapus data proyeksi pada tahun tertentu # Fungsi untuk menghapus data proyeksi pada tahun tertentu
def __delete_predictions_from_db(self, equipment_id): def __delete_predictions_from_db(self, equipment_id):
try: try:
connection = get_connection() connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None: if connection is None:
print("Koneksi ke database gagal.") print("Database connection failed.")
return return None
cursor = connection.cursor() cursor = connection.cursor()
# Query untuk menghapus data berdasarkan tahun proyeksi # Query untuk menghapus data berdasarkan tahun proyeksi
delete_query = """ delete_query = """
DELETE FROM lcc_tr_data DELETE FROM lcc_equipment_tr_data
WHERE equipment_id = %s AND is_actual = 0; WHERE assetnum = %s AND is_actual = 0;
""" # Asumsikan kolom is_actual digunakan untuk membedakan data proyeksi dan data aktual """ # Asumsikan kolom is_actual digunakan untuk membedakan data proyeksi dan data aktual
# Eksekusi query delete # Eksekusi query delete
cursor.execute(delete_query, (equipment_id,)) cursor.execute(delete_query, (equipment_id,))
connection.commit() 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: except Exception as e:
print(f"Error saat menghapus data proyeksi dari database: {e}") print(f"Error saat menghapus data proyeksi dari database: {e}")
@ -127,30 +151,33 @@ class Prediksi:
connection.close() connection.close()
# Fungsi untuk menyimpan data proyeksi ke database # 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: try:
connection = get_connection() connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None: if connection is None:
print("Koneksi ke database gagal.") print("Database connection failed.")
return return None
cursor = connection.cursor() cursor = connection.cursor()
# Query untuk mendapatkan nilai maksimum seq # Query untuk mendapatkan nilai maksimum seq
get_max_seq_query = """ 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,)) cursor.execute(get_max_seq_query, (equipment_id,))
max_seq = cursor.fetchone()[0] max_seq = cursor.fetchone()[0]
# Query untuk insert data # Query untuk insert data
insert_query = """ insert_query = """
INSERT INTO lcc_tr_data ( INSERT INTO lcc_equipment_tr_data (
id, id,
seq, seq,
is_actual, is_actual,
raw_pm_interval, raw_pm_interval,
tahun, equipment_id, tahun, assetnum,
raw_cm_interval, raw_cm_material_cost, raw_cm_labor_time, raw_cm_labor_human, 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_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human,
raw_oh_material_cost, raw_oh_labor_time, raw_oh_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 # Menyiapkan data untuk batch insert
# print(f"Data to be inserted: {data}")
records_to_insert = [] records_to_insert = []
for _, row in data.iterrows(): for _, row in data.iterrows():
max_seq = max_seq + 1 max_seq = max_seq + 1
records_to_insert.append(( # Update values from API
str(uuid4()), max_seq, row["year"], equipment_id, api_data = await fetch_api_data(equipment_id, row["year"])
row["cm_interval"] if row["cm_interval"] >= 1 else 1, row["cm_cost"], row["cm_labor_time"], if api_data:
row["cm_labor_human"], # # Get current num_fail
row["pm_cost"], row["pm_labor_time"], row["pm_labor_human"], cm_interval_prediction = api_data["data"][0]["num_fail"]
row["oh_cost"], row["oh_labor_time"], row["oh_labor_human"], else:
row["loss_output_mw"], row["loss_price"] 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 # Eksekusi batch insert
cursor.executemany(insert_query, records_to_insert) cursor.executemany(insert_query, records_to_insert)
connection.commit() connection.commit()
print("Data proyeksi berhasil dimasukkan ke database.") # print("Data proyeksi berhasil dimasukkan ke database.")
except Exception as e: except Exception as e:
print(f"Error saat menyimpan data ke database: {e}") print(f"Error saat menyimpan data ke database: {e}")
@ -189,51 +262,54 @@ class Prediksi:
# Fungsi untuk menghapus data proyeksi pada tahun tertentu # Fungsi untuk menghapus data proyeksi pada tahun tertentu
def __update_date_lcc(self, equipment_id): def __update_date_lcc(self, equipment_id):
try: try:
connection = get_connection() connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None: if connection is None:
print("Koneksi ke database gagal.") print("Database connection failed.")
return return None
cursor = connection.cursor() cursor = connection.cursor()
# Query untuk menghapus data berdasarkan tahun proyeksi # Query untuk menghapus data berdasarkan tahun proyeksi
up_query = """ up_query = """
update lcc_tr_data update lcc_equipment_tr_data
set set
rc_cm_material_cost = raw_cm_material_cost 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_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_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_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) , 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_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_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_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_operation_cost = coalesce(raw_operational_cost, 0)
,rc_maintenance_cost = coalesce(raw_maintenance_cost, 0) ,rc_maintenance_cost = coalesce(raw_maintenance_cost, 0)
,rc_total_cost = ( ,rc_total_cost = (
raw_cm_material_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_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)) ) + (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_tr_data.tahun), coalesce((select value_num from lcc_ms_master where name='manhours_rate'), 0)) ) , 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_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_project_task_material_cost, 0)
+ coalesce(("raw_loss_output_MW" * raw_loss_output_price * raw_cm_interval), 0) * 1000 + coalesce(("raw_loss_output_MW" * raw_loss_output_price * raw_cm_interval), 0) * 1000
+ coalesce(raw_operational_cost, 0) + coalesce(raw_operational_cost, 0)
+ coalesce(raw_maintenance_cost, 0) + coalesce(raw_maintenance_cost, 0)
) )
, updated_by = 'Sys', updated_at = NOW() , 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 """ # Asumsikan kolom is_actual digunakan untuk membedakan data proyeksi dan data aktual
# Eksekusi query delete # Eksekusi query delete
cursor.execute(up_query, (equipment_id, equipment_id)) cursor.execute(up_query, (equipment_id, equipment_id))
connection.commit() connection.commit()
print(f"Data berhasil diupdate.") # print(f"Data berhasil diupdate.")
except Exception as e: except Exception as e:
print(f"Error saat update data proyeksi dari database: {e}") print(f"Error saat update data proyeksi dari database: {e}")
@ -245,10 +321,13 @@ class Prediksi:
# Fungsi untuk mengambil parameter dari database # Fungsi untuk mengambil parameter dari database
def __get_rate_and_max_year(self, equipment_id): def __get_rate_and_max_year(self, equipment_id):
try: try:
connection = get_connection() connections = get_connection()
connection = (
connections[0] if isinstance(connections, tuple) else connections
)
if connection is None: if connection is None:
print("Koneksi ke database gagal.") print("Database connection failed.")
return None, None return None
cursor = connection.cursor(cursor_factory=DictCursor) cursor = connection.cursor(cursor_factory=DictCursor)
@ -256,22 +335,26 @@ class Prediksi:
query = """ query = """
SELECT SELECT
(SELECT value_num / 100 FROM lcc_ms_master where name='inflation_rate') AS rate, (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,)) cursor.execute(query, (equipment_id,))
result = cursor.fetchone() result = cursor.fetchone()
# Debug hasil query # Debug hasil query
print(f"Result: {result}") # print(f"Result: {result}")
rate = result["rate"] rate = result["rate"]
max_year = result["max_year"] max_year = result["max_year"]
# Validasi nilai rate dan max_year # Validasi nilai rate dan max_year
if rate is None: 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: 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 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: try:
# Mengambil data dari database # Mengambil data dari database
df = self.__fetch_data_from_db(p_equipment_id) df = self.__fetch_data_from_db(p_equipment_id)
@ -316,7 +399,9 @@ class Prediksi:
# Fungsi untuk prediksi menggunakan Exponential Smoothing # Fungsi untuk prediksi menggunakan Exponential Smoothing
def exponential_smoothing_predict(column, years): def exponential_smoothing_predict(column, years):
data_series = df[column].fillna(0).values 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() model_fit = model.fit()
preds = model_fit.forecast(len(years)) preds = model_fit.forecast(len(years))
return np.abs(preds) return np.abs(preds)
@ -342,22 +427,36 @@ class Prediksi:
# Prediksi Future Value # Prediksi Future Value
nper = max_year - df["year"].max() nper = max_year - df["year"].max()
pv = -df[column].iloc[-1] 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: 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: elif df[column].isnull().sum() > 0:
predictions[column] = decision_tree_predict(column, future_years) predictions[column] = decision_tree_predict(
column, future_years
)
else: else:
predictions[column] = linear_regression_predict(column, future_years) predictions[column] = linear_regression_predict(
column, future_years
)
# Konversi hasil ke DataFrame # Konversi hasil ke DataFrame
predictions_df = pd.DataFrame(predictions) predictions_df = pd.DataFrame(predictions)
# print(predictions_df)
# Hapus data prediksi yang ada sebelumnya # Hapus data prediksi yang ada sebelumnya
self.__delete_predictions_from_db(p_equipment_id) self.__delete_predictions_from_db(p_equipment_id)
# Insert hasil prediksi ke database # 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 # Update data untuk total RiskCost per tahun
self.__update_date_lcc(p_equipment_id) self.__update_date_lcc(p_equipment_id)
@ -365,3 +464,17 @@ class Prediksi:
except Exception as e: except Exception as e:
print(f"Program dihentikan: {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 time
import sys import sys
import os 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 from config import get_connection
ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
file_ref = f"{ROOT_DIR}/wlc.xlsx" file_ref = f"{ROOT_DIR}/wlc.xlsx"
file_hasil = f"{ROOT_DIR}/hasil/wlc.xlsx" file_hasil = f"{ROOT_DIR}/hasil/wlc.xlsx"
start_time = time.time() 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: if conn is None:
print("Koneksi ke database gagal.") print("Koneksi ke database gagal.")
sys.exit(1) sys.exit(1)
@ -37,6 +45,7 @@ def fetch_data_from_postgresql(q=""):
print(f"Error: {e}") print(f"Error: {e}")
return None return None
def insert_and_shift_right(file_path, sheet_name): def insert_and_shift_right(file_path, sheet_name):
# Load workbook and select sheet # Load workbook and select sheet
wb = openpyxl.load_workbook(file_path) wb = openpyxl.load_workbook(file_path)
@ -57,7 +66,9 @@ def insert_and_shift_right(file_path, sheet_name):
current_value = current_cell.value current_value = current_cell.value
if isinstance(current_value, str) and current_value.startswith("="): if isinstance(current_value, str) and current_value.startswith("="):
# Adjust formula to the new column # 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) sheet.cell(row=row, column=new_col_idx, value=formula)
else: else:
sheet.cell(row=row, column=new_col_idx, value=current_value) 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) # Copy cell style (fill color)
new_cell = sheet.cell(row=row, column=new_col_idx) new_cell = sheet.cell(row=row, column=new_col_idx)
if current_cell.fill: if current_cell.fill:
new_cell.fill = PatternFill(start_color=current_cell.fill.start_color.rgb, new_cell.fill = PatternFill(
start_color=current_cell.fill.start_color.rgb,
end_color=current_cell.fill.end_color.rgb, end_color=current_cell.fill.end_color.rgb,
fill_type=current_cell.fill.fill_type) fill_type=current_cell.fill.fill_type,
)
# Save workbook with changes # Save workbook with changes
wb.save(file_path) 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(): for row_name, value in data.items():
row_index = None row_index = None
# Cari indeks baris berdasarkan nama di kolom pertama # 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: if sheet.cell(row=row, column=1).value == row_name:
row_index = row row_index = row
break break
@ -115,6 +130,7 @@ def insert_column_data(file_path, sheet_name, col_name, data):
wb.close() wb.close()
wb.save(file_path) wb.save(file_path)
def insert_param(file_path): def insert_param(file_path):
wb = openpyxl.load_workbook(file_path, data_only=False) wb = openpyxl.load_workbook(file_path, data_only=False)
sheet = wb["Params"] sheet = wb["Params"]
@ -138,11 +154,13 @@ def insert_param(file_path):
mapping_data = {} mapping_data = {}
for item in data_map: 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 # Jika kolom ditemukan, masukkan data per baris
if col_index: 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 param_name = sheet.cell(row=row, column=1).value # Kolom 1 == value_str
if param_name in mapping_data: if param_name in mapping_data:
sheet.cell(row=row, column=col_index, value=mapping_data[param_name]) 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.save(file_path)
wb.close() wb.close()
def get_abjad(i): def get_abjad(i):
if i < 0: if i < 0:
raise ValueError("Indeks harus berupa angka 0 atau lebih.") raise ValueError("Indeks harus berupa angka 0 atau lebih.")
@ -162,18 +181,16 @@ def get_abjad(i):
result = chr(65 + remainder) + result result = chr(65 + remainder) + result
i -= 1 # Mengurangi 1 untuk menangani offset ke basis 0 i -= 1 # Mengurangi 1 untuk menangani offset ke basis 0
return result return result
def validate_number(n): def validate_number(n):
return n if n is not None else 0 return n if n is not None else 0
# Example usage # Example usage
# insert_and_shift_right(file_ref, "Calc") # insert_and_shift_right(file_ref, "Calc")
# ====================================================== 00000 ================================================= # ====================================================== 00000 =================================================
# =============================================== SET DATA KE EXCEL ================================================= # =============================================== SET DATA KE EXCEL =================================================
# ====================================================== 00000 ================================================= # ====================================================== 00000 =================================================
@ -193,32 +210,49 @@ if data:
col_data = {} col_data = {}
# Tambahkan data tambahan berdasarkan kondisi # Tambahkan data tambahan berdasarkan kondisi
if record['is_actual'] == 1: if record["is_actual"] == 1:
col_data.update({ col_data.update(
"Net Capacity Factor": validate_number(record['net_capacity_factor']), {
"EAF": validate_number(record['eaf']), "Net Capacity Factor": validate_number(
"Biaya Investasi Tambahan": validate_number(record['cost_a_pinjaman'])/1000000, record["net_capacity_factor"]
"O & M Cost": validate_number(record['cost_bd_om'])/1000000, ),
"Periodic Maintenance Cost (Non MI)": validate_number(record['cost_bd_pm_nonmi'])/1000000, "EAF": validate_number(record["eaf"]),
"Biaya Investasi Tambahan": validate_number(
"Production (Bruto)": validate_number(record['production_bruto']), record["cost_a_pinjaman"]
"Production (Netto)": validate_number(record['production_netto']), )
"Fuel Consumption": validate_number(record['fuel_consumption']), / 1000000,
"Revenue A": validate_number(record['revenue_a'])/1000000, "O & M Cost": validate_number(record["cost_bd_om"]) / 1000000,
"Revenue B": validate_number(record['revenue_b'])/1000000, "Periodic Maintenance Cost (Non MI)": validate_number(
"Revenue C": validate_number(record['revenue_c'])/1000000, record["cost_bd_pm_nonmi"]
"Revenue D": validate_number(record['revenue_d'])/1000000, )
"Fuel Cost": validate_number(record['cost_c_fuel'])/1000000 / 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: else:
seq_offset = record['seq'] + 2 seq_offset = record["seq"] + 2
col_data.update({ col_data.update(
"Net Capacity Factor": validate_number(record['net_capacity_factor']), {
"EAF": validate_number(record['eaf']), "Net Capacity Factor": validate_number(
"Biaya Investasi Tambahan": validate_number(record['cost_a_pinjaman'])/1000000, record["net_capacity_factor"]
"O & M Cost": validate_number(record['cost_bd_om'])/1000000, ),
"Periodic Maintenance Cost (Non MI)": validate_number(record['cost_bd_pm_nonmi'])/1000000, "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 (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", "Production (Netto)": f"={get_abjad(seq_offset)}4*8760*Params!$C$15/100",
"Fuel Consumption": f"={get_abjad(seq_offset)}6*Params!$C$18", "Fuel Consumption": f"={get_abjad(seq_offset)}6*Params!$C$18",
@ -226,20 +260,16 @@ if data:
"Revenue B": f"=(Params!$C$20*{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 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", "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" "Fuel Cost": f"={get_abjad(seq_offset)}9*Params!$C$23/10^6",
}) }
)
# Masukkan data ke Excel # 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: else:
print("No data found.") print("No data found.")
# ====================================================== 00000 ================================================= # ====================================================== 00000 =================================================
# =============================================== SIMPAN UNTUK CHART ================================================= # =============================================== SIMPAN UNTUK CHART =================================================
# ====================================================== 00000 ================================================= # ====================================================== 00000 =================================================
@ -274,8 +304,10 @@ column_mapping = {
"Capex (Component A)": "chart_capex_component_a", "Capex (Component A)": "chart_capex_component_a",
"Biaya Investasi Tambahan": "chart_capex_biaya_investasi_tambahan", "Biaya Investasi Tambahan": "chart_capex_biaya_investasi_tambahan",
"Acquisition Cost": "chart_capex_acquisition_cost", "Acquisition Cost": "chart_capex_acquisition_cost",
"Capex Annualized": "chart_capex_annualized" "Capex Annualized": "chart_capex_annualized",
} }
# Fungsi untuk memperbarui database # Fungsi untuk memperbarui database
def update_database_from_excel(): def update_database_from_excel():
# Buka koneksi ke PostgreSQL # Buka koneksi ke PostgreSQL
@ -286,17 +318,23 @@ def update_database_from_excel():
sheet = wb["Upload"] sheet = wb["Upload"]
# Ambil tahun dari baris pertama mulai dari kolom ketiga # 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 # Loop melalui setiap baris di Excel mulai dari baris kedua
for row in range(2, sheet.max_row + 1): 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 = 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) db_column = column_mapping.get(row_name)
if db_column: 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 value = sheet.cell(row=row, column=col_index).value
if value is None: if value is None:
@ -308,7 +346,9 @@ def update_database_from_excel():
SET {db_column} = %s SET {db_column} = %s
WHERE tahun = %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 cur.execute(query, (value, year)) # Jalankan query dengan parameter
conn.commit() conn.commit()
@ -317,14 +357,9 @@ def update_database_from_excel():
except Exception as e: except Exception as e:
conn.rollback() conn.rollback()
print(f"Error: {e}") print(f"Error: {e}")
update_database_from_excel()
update_database_from_excel()
cur.close() cur.close()
@ -336,8 +371,3 @@ minutes = int(elapsed_time // 60)
seconds = int(elapsed_time % 60) seconds = int(elapsed_time % 60)
# Cetak hasil # Cetak hasil
print(f"Execution time: {minutes} minutes and {seconds} seconds") print(f"Execution time: {minutes} minutes and {seconds} seconds")

Binary file not shown.
Loading…
Cancel
Save