From e27b299c69188774e8af04a3d37be47205ca83af Mon Sep 17 00:00:00 2001 From: Cizz22 Date: Fri, 25 Jul 2025 15:15:24 +0700 Subject: [PATCH] add powerplant reliability --- poetry.lock | 619 ++++++- pyproject.toml | 1 + src/api.py | 16 +- src/calculation_time_constrains/class.py | 232 +++ src/calculation_time_constrains/flows.py | 12 +- src/calculation_time_constrains/model.py | 8 +- src/calculation_time_constrains/schema.py | 4 +- src/calculation_time_constrains/service.py | 1763 ++++++++++++++------ src/overhaul_activity/service.py | 39 +- src/overhaul_scope/service.py | 3 +- src/utils.py | 19 + 11 files changed, 2158 insertions(+), 558 deletions(-) create mode 100644 src/calculation_time_constrains/class.py diff --git a/poetry.lock b/poetry.lock index 537b185..7bcc716 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,141 @@ # This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.12.14" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohttp-3.12.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:906d5075b5ba0dd1c66fcaaf60eb09926a9fef3ca92d912d2a0bbdbecf8b1248"}, + {file = "aiohttp-3.12.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c875bf6fc2fd1a572aba0e02ef4e7a63694778c5646cdbda346ee24e630d30fb"}, + {file = "aiohttp-3.12.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbb284d15c6a45fab030740049d03c0ecd60edad9cd23b211d7e11d3be8d56fd"}, + {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e360381e02e1a05d36b223ecab7bc4a6e7b5ab15760022dc92589ee1d4238c"}, + {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aaf90137b5e5d84a53632ad95ebee5c9e3e7468f0aab92ba3f608adcb914fa95"}, + {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e532a25e4a0a2685fa295a31acf65e027fbe2bea7a4b02cdfbbba8a064577663"}, + {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eab9762c4d1b08ae04a6c77474e6136da722e34fdc0e6d6eab5ee93ac29f35d1"}, + {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abe53c3812b2899889a7fca763cdfaeee725f5be68ea89905e4275476ffd7e61"}, + {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5760909b7080aa2ec1d320baee90d03b21745573780a072b66ce633eb77a8656"}, + {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:02fcd3f69051467bbaa7f84d7ec3267478c7df18d68b2e28279116e29d18d4f3"}, + {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4dcd1172cd6794884c33e504d3da3c35648b8be9bfa946942d353b939d5f1288"}, + {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:224d0da41355b942b43ad08101b1b41ce633a654128ee07e36d75133443adcda"}, + {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e387668724f4d734e865c1776d841ed75b300ee61059aca0b05bce67061dcacc"}, + {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:dec9cde5b5a24171e0b0a4ca064b1414950904053fb77c707efd876a2da525d8"}, + {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bbad68a2af4877cc103cd94af9160e45676fc6f0c14abb88e6e092b945c2c8e3"}, + {file = "aiohttp-3.12.14-cp310-cp310-win32.whl", hash = "sha256:ee580cb7c00bd857b3039ebca03c4448e84700dc1322f860cf7a500a6f62630c"}, + {file = "aiohttp-3.12.14-cp310-cp310-win_amd64.whl", hash = "sha256:cf4f05b8cea571e2ccc3ca744e35ead24992d90a72ca2cf7ab7a2efbac6716db"}, + {file = "aiohttp-3.12.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4552ff7b18bcec18b60a90c6982049cdb9dac1dba48cf00b97934a06ce2e597"}, + {file = "aiohttp-3.12.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8283f42181ff6ccbcf25acaae4e8ab2ff7e92b3ca4a4ced73b2c12d8cd971393"}, + {file = "aiohttp-3.12.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:040afa180ea514495aaff7ad34ec3d27826eaa5d19812730fe9e529b04bb2179"}, + {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b413c12f14c1149f0ffd890f4141a7471ba4b41234fe4fd4a0ff82b1dc299dbb"}, + {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d6f607ce2e1a93315414e3d448b831238f1874b9968e1195b06efaa5c87e245"}, + {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:565e70d03e924333004ed101599902bba09ebb14843c8ea39d657f037115201b"}, + {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4699979560728b168d5ab63c668a093c9570af2c7a78ea24ca5212c6cdc2b641"}, + {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad5fdf6af93ec6c99bf800eba3af9a43d8bfd66dce920ac905c817ef4a712afe"}, + {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ac76627c0b7ee0e80e871bde0d376a057916cb008a8f3ffc889570a838f5cc7"}, + {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:798204af1180885651b77bf03adc903743a86a39c7392c472891649610844635"}, + {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4f1205f97de92c37dd71cf2d5bcfb65fdaed3c255d246172cce729a8d849b4da"}, + {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:76ae6f1dd041f85065d9df77c6bc9c9703da9b5c018479d20262acc3df97d419"}, + {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a194ace7bc43ce765338ca2dfb5661489317db216ea7ea700b0332878b392cab"}, + {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:16260e8e03744a6fe3fcb05259eeab8e08342c4c33decf96a9dad9f1187275d0"}, + {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c779e5ebbf0e2e15334ea404fcce54009dc069210164a244d2eac8352a44b28"}, + {file = "aiohttp-3.12.14-cp311-cp311-win32.whl", hash = "sha256:a289f50bf1bd5be227376c067927f78079a7bdeccf8daa6a9e65c38bae14324b"}, + {file = "aiohttp-3.12.14-cp311-cp311-win_amd64.whl", hash = "sha256:0b8a69acaf06b17e9c54151a6c956339cf46db4ff72b3ac28516d0f7068f4ced"}, + {file = "aiohttp-3.12.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a0ecbb32fc3e69bc25efcda7d28d38e987d007096cbbeed04f14a6662d0eee22"}, + {file = "aiohttp-3.12.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0400f0ca9bb3e0b02f6466421f253797f6384e9845820c8b05e976398ac1d81a"}, + {file = "aiohttp-3.12.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a56809fed4c8a830b5cae18454b7464e1529dbf66f71c4772e3cfa9cbec0a1ff"}, + {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f2e373276e4755691a963e5d11756d093e346119f0627c2d6518208483fb6d"}, + {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ca39e433630e9a16281125ef57ece6817afd1d54c9f1bf32e901f38f16035869"}, + {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c748b3f8b14c77720132b2510a7d9907a03c20ba80f469e58d5dfd90c079a1c"}, + {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a568abe1b15ce69d4cc37e23020720423f0728e3cb1f9bcd3f53420ec3bfe7"}, + {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9888e60c2c54eaf56704b17feb558c7ed6b7439bca1e07d4818ab878f2083660"}, + {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3006a1dc579b9156de01e7916d38c63dc1ea0679b14627a37edf6151bc530088"}, + {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa8ec5c15ab80e5501a26719eb48a55f3c567da45c6ea5bb78c52c036b2655c7"}, + {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:39b94e50959aa07844c7fe2206b9f75d63cc3ad1c648aaa755aa257f6f2498a9"}, + {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04c11907492f416dad9885d503fbfc5dcb6768d90cad8639a771922d584609d3"}, + {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:88167bd9ab69bb46cee91bd9761db6dfd45b6e76a0438c7e884c3f8160ff21eb"}, + {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:791504763f25e8f9f251e4688195e8b455f8820274320204f7eafc467e609425"}, + {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2785b112346e435dd3a1a67f67713a3fe692d288542f1347ad255683f066d8e0"}, + {file = "aiohttp-3.12.14-cp312-cp312-win32.whl", hash = "sha256:15f5f4792c9c999a31d8decf444e79fcfd98497bf98e94284bf390a7bb8c1729"}, + {file = "aiohttp-3.12.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b66e1a182879f579b105a80d5c4bd448b91a57e8933564bf41665064796a338"}, + {file = "aiohttp-3.12.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3143a7893d94dc82bc409f7308bc10d60285a3cd831a68faf1aa0836c5c3c767"}, + {file = "aiohttp-3.12.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3d62ac3d506cef54b355bd34c2a7c230eb693880001dfcda0bf88b38f5d7af7e"}, + {file = "aiohttp-3.12.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48e43e075c6a438937c4de48ec30fa8ad8e6dfef122a038847456bfe7b947b63"}, + {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:077b4488411a9724cecc436cbc8c133e0d61e694995b8de51aaf351c7578949d"}, + {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d8c35632575653f297dcbc9546305b2c1133391089ab925a6a3706dfa775ccab"}, + {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b8ce87963f0035c6834b28f061df90cf525ff7c9b6283a8ac23acee6502afd4"}, + {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a2cf66e32a2563bb0766eb24eae7e9a269ac0dc48db0aae90b575dc9583026"}, + {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdea089caf6d5cde975084a884c72d901e36ef9c2fd972c9f51efbbc64e96fbd"}, + {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7865f27db67d49e81d463da64a59365ebd6b826e0e4847aa111056dcb9dc88"}, + {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ab5b38a6a39781d77713ad930cb5e7feea6f253de656a5f9f281a8f5931b086"}, + {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b3b15acee5c17e8848d90a4ebc27853f37077ba6aec4d8cb4dbbea56d156933"}, + {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4c972b0bdaac167c1e53e16a16101b17c6d0ed7eac178e653a07b9f7fad7151"}, + {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7442488b0039257a3bdbc55f7209587911f143fca11df9869578db6c26feeeb8"}, + {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f68d3067eecb64c5e9bab4a26aa11bd676f4c70eea9ef6536b0a4e490639add3"}, + {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f88d3704c8b3d598a08ad17d06006cb1ca52a1182291f04979e305c8be6c9758"}, + {file = "aiohttp-3.12.14-cp313-cp313-win32.whl", hash = "sha256:a3c99ab19c7bf375c4ae3debd91ca5d394b98b6089a03231d4c580ef3c2ae4c5"}, + {file = "aiohttp-3.12.14-cp313-cp313-win_amd64.whl", hash = "sha256:3f8aad695e12edc9d571f878c62bedc91adf30c760c8632f09663e5f564f4baa"}, + {file = "aiohttp-3.12.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b8cc6b05e94d837bcd71c6531e2344e1ff0fb87abe4ad78a9261d67ef5d83eae"}, + {file = "aiohttp-3.12.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1dcb015ac6a3b8facd3677597edd5ff39d11d937456702f0bb2b762e390a21b"}, + {file = "aiohttp-3.12.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3779ed96105cd70ee5e85ca4f457adbce3d9ff33ec3d0ebcdf6c5727f26b21b3"}, + {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:717a0680729b4ebd7569c1dcd718c46b09b360745fd8eb12317abc74b14d14d0"}, + {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b5dd3a2ef7c7e968dbbac8f5574ebeac4d2b813b247e8cec28174a2ba3627170"}, + {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4710f77598c0092239bc12c1fcc278a444e16c7032d91babf5abbf7166463f7b"}, + {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f3e9f75ae842a6c22a195d4a127263dbf87cbab729829e0bd7857fb1672400b2"}, + {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f9c8d55d6802086edd188e3a7d85a77787e50d56ce3eb4757a3205fa4657922"}, + {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79b29053ff3ad307880d94562cca80693c62062a098a5776ea8ef5ef4b28d140"}, + {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23e1332fff36bebd3183db0c7a547a1da9d3b4091509f6d818e098855f2f27d3"}, + {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a564188ce831fd110ea76bcc97085dd6c625b427db3f1dbb14ca4baa1447dcbc"}, + {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a7a1b4302f70bb3ec40ca86de82def532c97a80db49cac6a6700af0de41af5ee"}, + {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1b07ccef62950a2519f9bfc1e5b294de5dd84329f444ca0b329605ea787a3de5"}, + {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:938bd3ca6259e7e48b38d84f753d548bd863e0c222ed6ee6ace3fd6752768a84"}, + {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8bc784302b6b9f163b54c4e93d7a6f09563bd01ff2b841b29ed3ac126e5040bf"}, + {file = "aiohttp-3.12.14-cp39-cp39-win32.whl", hash = "sha256:a3416f95961dd7d5393ecff99e3f41dc990fb72eda86c11f2a60308ac6dcd7a0"}, + {file = "aiohttp-3.12.14-cp39-cp39-win_amd64.whl", hash = "sha256:196858b8820d7f60578f8b47e5669b3195c21d8ab261e39b1d705346458f445f"}, + {file = "aiohttp-3.12.14.tar.gz", hash = "sha256:6e06e120e34d93100de448fd941522e11dafa78ef1a893c179901b7d66aa29f2"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.5.0" +aiosignal = ">=1.4.0" +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] + +[[package]] +name = "aiosignal" +version = "1.4.0" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" +typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} + [[package]] name = "annotated-types" version = "0.7.0" @@ -97,6 +233,26 @@ docs = ["Sphinx (>=8.1.3,<8.2.0)", "sphinx-rtd-theme (>=1.2.2)"] gssauth = ["gssapi ; platform_system != \"Windows\"", "sspilib ; platform_system == \"Windows\""] test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi ; platform_system == \"Linux\"", "k5test ; platform_system == \"Linux\"", "mypy (>=1.8.0,<1.9.0)", "sspilib ; platform_system == \"Windows\"", "uvloop (>=0.15.3) ; platform_system != \"Windows\" and python_version < \"3.14.0\""] +[[package]] +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[package.extras] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] + [[package]] name = "cachetools" version = "5.5.2" @@ -400,6 +556,120 @@ uvicorn = {version = ">=0.15.0", extras = ["standard"]} [package.extras] standard = ["uvicorn[standard] (>=0.15.0)"] +[[package]] +name = "frozenlist" +version = "1.7.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"}, + {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"}, + {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"}, + {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"}, + {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"}, + {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"}, + {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"}, + {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"}, + {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"}, + {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"}, + {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"}, + {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, +] + [[package]] name = "google-api-core" version = "2.24.2" @@ -948,6 +1218,126 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "multidict" +version = "6.6.3" +description = "multidict implementation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817"}, + {file = "multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140"}, + {file = "multidict-6.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd7793bab517e706c9ed9d7310b06c8672fd0aeee5781bfad612f56b8e0f7d14"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:72d8815f2cd3cf3df0f83cac3f3ef801d908b2d90409ae28102e0553af85545a"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:531e331a2ee53543ab32b16334e2deb26f4e6b9b28e41f8e0c87e99a6c8e2d69"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42ca5aa9329a63be8dc49040f63817d1ac980e02eeddba763a9ae5b4027b9c9c"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:208b9b9757060b9faa6f11ab4bc52846e4f3c2fb8b14d5680c8aac80af3dc751"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:acf6b97bd0884891af6a8b43d0f586ab2fcf8e717cbd47ab4bdddc09e20652d8"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68e9e12ed00e2089725669bdc88602b0b6f8d23c0c95e52b95f0bc69f7fe9b55"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05db2f66c9addb10cfa226e1acb363450fab2ff8a6df73c622fefe2f5af6d4e7"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0db58da8eafb514db832a1b44f8fa7906fdd102f7d982025f816a93ba45e3dcb"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14117a41c8fdb3ee19c743b1c027da0736fdb79584d61a766da53d399b71176c"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:877443eaaabcd0b74ff32ebeed6f6176c71850feb7d6a1d2db65945256ea535c"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:70b72e749a4f6e7ed8fb334fa8d8496384840319512746a5f42fa0aec79f4d61"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43571f785b86afd02b3855c5ac8e86ec921b760298d6f82ff2a61daf5a35330b"}, + {file = "multidict-6.6.3-cp310-cp310-win32.whl", hash = "sha256:20c5a0c3c13a15fd5ea86c42311859f970070e4e24de5a550e99d7c271d76318"}, + {file = "multidict-6.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab0a34a007704c625e25a9116c6770b4d3617a071c8a7c30cd338dfbadfe6485"}, + {file = "multidict-6.6.3-cp310-cp310-win_arm64.whl", hash = "sha256:769841d70ca8bdd140a715746199fc6473414bd02efd678d75681d2d6a8986c5"}, + {file = "multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c"}, + {file = "multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df"}, + {file = "multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183"}, + {file = "multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5"}, + {file = "multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2"}, + {file = "multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb"}, + {file = "multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6"}, + {file = "multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f"}, + {file = "multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10"}, + {file = "multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5"}, + {file = "multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17"}, + {file = "multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b"}, + {file = "multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55"}, + {file = "multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b"}, + {file = "multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6"}, + {file = "multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e"}, + {file = "multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9"}, + {file = "multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600"}, + {file = "multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134"}, + {file = "multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37"}, + {file = "multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c"}, + {file = "multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e"}, + {file = "multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d"}, + {file = "multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb"}, + {file = "multidict-6.6.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c8161b5a7778d3137ea2ee7ae8a08cce0010de3b00ac671c5ebddeaa17cefd22"}, + {file = "multidict-6.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1328201ee930f069961ae707d59c6627ac92e351ed5b92397cf534d1336ce557"}, + {file = "multidict-6.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b1db4d2093d6b235de76932febf9d50766cf49a5692277b2c28a501c9637f616"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53becb01dd8ebd19d1724bebe369cfa87e4e7f29abbbe5c14c98ce4c383e16cd"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41bb9d1d4c303886e2d85bade86e59885112a7f4277af5ad47ab919a2251f306"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:775b464d31dac90f23192af9c291dc9f423101857e33e9ebf0020a10bfcf4144"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d04d01f0a913202205a598246cf77826fe3baa5a63e9f6ccf1ab0601cf56eca0"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d25594d3b38a2e6cabfdcafef339f754ca6e81fbbdb6650ad773ea9775af35ab"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:35712f1748d409e0707b165bf49f9f17f9e28ae85470c41615778f8d4f7d9609"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1c8082e5814b662de8589d6a06c17e77940d5539080cbab9fe6794b5241b76d9"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:61af8a4b771f1d4d000b3168c12c3120ccf7284502a94aa58c68a81f5afac090"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:448e4a9afccbf297577f2eaa586f07067441e7b63c8362a3540ba5a38dc0f14a"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:233ad16999afc2bbd3e534ad8dbe685ef8ee49a37dbc2cdc9514e57b6d589ced"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:bb933c891cd4da6bdcc9733d048e994e22e1883287ff7540c2a0f3b117605092"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:37b09ca60998e87734699e88c2363abfd457ed18cfbf88e4009a4e83788e63ed"}, + {file = "multidict-6.6.3-cp39-cp39-win32.whl", hash = "sha256:f54cb79d26d0cd420637d184af38f0668558f3c4bbe22ab7ad830e67249f2e0b"}, + {file = "multidict-6.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:295adc9c0551e5d5214b45cf29ca23dbc28c2d197a9c30d51aed9e037cb7c578"}, + {file = "multidict-6.6.3-cp39-cp39-win_arm64.whl", hash = "sha256:15332783596f227db50fb261c2c251a58ac3873c457f3a550a95d5c0aa3c770d"}, + {file = "multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a"}, + {file = "multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc"}, +] + [[package]] name = "numpy" version = "2.1.3" @@ -1144,6 +1534,114 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "propcache" +version = "0.3.2" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, + {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, + {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, + {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, + {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, + {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, + {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, + {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, + {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, + {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, + {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, + {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, + {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, + {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, + {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, +] + [[package]] name = "proto-plus" version = "1.26.1" @@ -2309,7 +2807,126 @@ files = [ {file = "wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801"}, ] +[[package]] +name = "yarl" +version = "1.20.1" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, + {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, + {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, + {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, + {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, + {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, + {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, + {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, + {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, + {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, + {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, + {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, + {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, + {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, + {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.1" + [metadata] lock-version = "2.1" python-versions = "^3.11" -content-hash = "3653b5daa2723d735ad026b0e3c111971358950a359a7469d410cfcc468a556b" +content-hash = "696ea33fe5b5efd49565f0b6216a2f40a85d62d34b27693ac6271b676f94897d" diff --git a/pyproject.toml b/pyproject.toml index dcaae4c..4c8565d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ greenlet = "^3.1.1" google-api-python-client = "^2.169.0" google-auth-httplib2 = "^0.2.0" google-auth-oauthlib = "^1.2.2" +aiohttp = "^3.12.14" [build-system] diff --git a/src/api.py b/src/api.py index 9070bb5..011dad3 100644 --- a/src/api.py +++ b/src/api.py @@ -9,8 +9,8 @@ from src.calculation_budget_constrains.router import \ router as calculation_budget_constraint from src.calculation_target_reliability.router import \ router as calculation_target_reliability -# from src.calculation_time_constrains.router import \ -# router as calculation_time_constrains_router +from src.calculation_time_constrains.router import \ + router as calculation_time_constrains_router # from src.job.router import router as job_router from src.overhaul.router import router as overhaul_router @@ -138,12 +138,12 @@ authenticated_api_router.include_router( # calculation calculation_router = APIRouter(prefix="/calculation", tags=["calculations"]) -# # Time constrains -# calculation_router.include_router( -# calculation_time_constrains_router, -# prefix="/time-constraint", -# tags=["calculation", "time_constraint"], -# ) +# Time constrains +calculation_router.include_router( + calculation_time_constrains_router, + prefix="/time-constraint", + tags=["calculation", "time_constraint"], +) # Target reliability calculation_router.include_router( diff --git a/src/calculation_time_constrains/class.py b/src/calculation_time_constrains/class.py new file mode 100644 index 0000000..683d401 --- /dev/null +++ b/src/calculation_time_constrains/class.py @@ -0,0 +1,232 @@ +import requests +import numpy as np +import pandas as pd +from datetime import timedelta +from collections import defaultdict + +class OptimumCostModel: + def __init__(self, token, last_oh_date, next_oh_date,interval =30, base_url: str = "http://192.168.1.82:8000"): + self.api_base_url = base_url + self.token = token + self.last_oh_date = last_oh_date + self.next_oh_date = next_oh_date + self.interval=30 + + + def get_reliability_from_api(self, target_date, location_tag): + """ + Get reliability value from API for a specific date. + + Parameters: + target_date: datetime object for the target date + + Returns: + reliability value (float) + """ + # Format date for API call + date_str = target_date.strftime('%Y-%m-%d %H:%M:%S.%f') + + # Construct API URL + url = f"{self.api_base_url}/reliability/calculate/reliability/{location_tag}/{date_str}" + + header = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + self.token + } + + try: + response = requests.get(url, headers=header) + response.raise_for_status() + + data = response.json() + reliability = data['data']['value'] + + return reliability + + except requests.RequestException as e: + print(f"API Error: {e}") + return None + except KeyError as e: + print(f"Data parsing error: {e}") + return None + + + def get_reliability_equipment(self, location_tag): + current_date = self.last_oh_date + results = defaultdict() + + print("Fetching reliability data from API...") + + while current_date <= self.next_oh_date: + reliability = self.get_reliability_from_api(current_date, location_tag) + results[current_date] = reliability + + if reliability is not None: + print(f"Date: {current_date.strftime('%Y-%m-%d')}, Reliability: {reliability:.6f}") + else: + print(f"Date: {current_date.strftime('%Y-%m-%d')}, Reliability: Failed to fetch") + + current_date += timedelta(days=self.interval) + + return results + + + def calculate_costs_each_point(self, reliabilities, preventive_cost: float, failure_replacement_cost: float): + # Calculate costs for each time point + results = [] + dates = list(reliabilities.keys()) + + for i, (date, reliability) in enumerate(reliabilities.items()): + if reliability is None: + continue + + # Calculate time from last OH in days + time_from_last_oh = (date - self.last_oh_date).days + + # Calculate failure replacement cost + failure_prob = 1 - reliability + + # For expected operating time, we need to integrate R(t) from 0 to T + # Since we have discrete points, we'll approximate using trapezoidal rule + if i == 0: + expected_operating_time = time_from_last_oh # First point + else: + # Approximate integral using available reliability values + time_points = [(dates[j] - self.last_oh_date).days for j in range(i+1)] + rel_values = [rel for rel in reliabilities[:i+1] if rel is not None] + + if len(rel_values) > 1: + expected_operating_time = np.trapezoid(rel_values, time_points) + else: + expected_operating_time = time_from_last_oh * reliability + + # Calculate costs + if expected_operating_time > 0: + failure_cost = (failure_prob * failure_replacement_cost) / expected_operating_time + preventive_cost = (reliability * preventive_cost) / expected_operating_time + else: + # failure_cost = failure_prob * self.IDRu + # preventive_cost = reliability * self.IDRp + + continue + + total_cost = failure_cost + preventive_cost + + results.append({ + 'date': date, + 'days_from_last_oh': time_from_last_oh, + 'reliability': reliability, + 'failure_probability': failure_prob, + 'expected_operating_time': expected_operating_time, + 'failure_replacement_cost': failure_cost, + 'preventive_replacement_cost': preventive_cost, + 'total_cost': total_cost, + 'procurement_cost': 0 + }) + + return results + + def find_optimal_timing(self, results_df): + """ + Find the timing that gives the lowest total cost. + + Parameters: + results_df: DataFrame from calculate_costs_over_period + + Returns: + Dictionary with optimal timing information + """ + if results_df.empty: + return None + + # Find minimum total cost + min_cost_idx = results_df['total_cost'].idxmin() + optimal_row = results_df.loc[min_cost_idx] + + return { + 'optimal_index': min_cost_idx, + 'optimal_date': optimal_row['date'], + 'days_from_last_oh': optimal_row['days_from_last_oh'], + 'reliability': optimal_row['reliability'], + 'failure_cost': optimal_row['failure_replacement_cost'], + 'preventive_cost': optimal_row['preventive_replacement_cost'], + 'total_cost': optimal_row['total_cost'], + 'procurement_cost': 0 + } + + + async def calculate_cost_all_equipment( + self, + db_session: DbSession, + equipments: list, + preventive_cost: float, + calculation, + ) : + """ + Calculate optimization for entire fleet of equipment + """ + max_interval = self._get_months_between(start_date, end_date) + preventive_cost_per_equipment = preventive_cost / len(equipments) + + fleet_results = [] + total_corrective_costs = np.zeros(max_interval) + total_preventive_costs = np.zeros(max_interval) + total_procurement_costs = np.zeros(max_interval) + total_failures = np.zeros(max_interval) + + for equipment in equipments: + # Get reliability data + cost_per_failure = equipment.material_cost + reliabilities = await self.get_reliability_equipment( + location_tag=equipment.equipment.location_tag, + ) + + predicted_cost = self.calculate_costs_each_point(reliabilities=reliabilities, preventive_cost=preventive_cost, failure_replacement_cost=cost_per_failure) + + optimum_cost = self.find_optimal_timing(pd.DataFrame(predicted_cost)) + + # Aggregate costs + corrective_costs = [r["failure_cost"] for r in predicted_cost] + preventive_costs = [r["preventive_cost"] for r in predicted_cost] + procurement_costs = [r["procurement_cost"] for r in predicted_cost] + procurement_details = [r["procurement_details"] for r in predicted_cost] + failures = [(1-r["reliability"]) for r in predicted_cost] + + + fleet_results.append( + CalculationEquipmentResult( + corrective_costs=corrective_costs, + overhaul_costs=preventive_costs, + procurement_costs=procurement_costs, + daily_failures=failures, + assetnum=equipment.assetnum, + material_cost=equipment.material_cost, + service_cost=equipment.service_cost, + optimum_day=optimum_cost['optimal_index'], + calculation_data_id=calculation.id, + master_equipment=equipment.equipment, + procurement_details=procurement_details + ) + ) + + total_corrective_costs += np.array(corrective_costs) + total_preventive_costs += np.array(preventive_costs) + total_procurement_costs += np.array(procurement_costs) + + # Calculate fleet optimal interval + total_costs = total_corrective_costs + total_preventive_costs + total_procurement_costs + fleet_optimal_index = np.argmin(total_costs) + calculation.optimum_oh_day =fleet_optimal_index + 1 + + db_session.add_all(fleet_results) + await db_session.commit() + + return { + 'id': calculation.id, + 'fleet_results': fleet_results, + 'fleet_optimal_interval': fleet_optimal_index + 1, + 'fleet_optimal_cost': total_costs[fleet_optimal_index], + 'total_corrective_costs': total_corrective_costs.tolist(), + 'total_preventive_costs': total_preventive_costs.tolist(), + 'total_procurement_costs': total_procurement_costs.tolist(), + } diff --git a/src/calculation_time_constrains/flows.py b/src/calculation_time_constrains/flows.py index 8a64096..cbf7e1c 100644 --- a/src/calculation_time_constrains/flows.py +++ b/src/calculation_time_constrains/flows.py @@ -9,8 +9,7 @@ from sqlalchemy.orm import joinedload from src.auth.service import Token from src.database.core import DbSession from src.overhaul_scope.service import get_all -from src.scope_equipment.model import ScopeEquipment -from src.scope_equipment.service import get_by_assetnum +from src.standard_scope.model import StandardScope from src.workorder.model import MasterWorkOrder from .schema import (CalculationTimeConstrainsParametersCreate, @@ -28,6 +27,8 @@ from .service import (create_calculation_result_service, create_param_and_data, async def get_create_calculation_parameters( *, db_session: DbSession, calculation_id: Optional[str] = None ): + + if calculation_id is not None: calculation = await get_calculation_data_by_id( calculation_id=calculation_id, db_session=db_session @@ -47,12 +48,11 @@ async def get_create_calculation_parameters( stmt = ( select( - ScopeEquipment.scope_id, + StandardScope, func.avg(MasterWorkOrder.total_cost_max).label("average_cost"), ) - .outerjoin(MasterWorkOrder, ScopeEquipment.assetnum == MasterWorkOrder.assetnum) - .group_by(ScopeEquipment.scope_id) - .order_by(ScopeEquipment.scope_id) + .outerjoin(MasterWorkOrder, StandardScope.location_tag == MasterWorkOrder.location_tag) + .group_by(StandardScope.id) ) results = await db_session.execute(stmt) diff --git a/src/calculation_time_constrains/model.py b/src/calculation_time_constrains/model.py index f61272d..e848aca 100644 --- a/src/calculation_time_constrains/model.py +++ b/src/calculation_time_constrains/model.py @@ -64,10 +64,12 @@ class CalculationData(Base, DefaultMixin, IdentityMixin): UUID(as_uuid=True), ForeignKey("oh_ms_calculation_param.id"), nullable=True ) overhaul_session_id = Column( - UUID(as_uuid=True), ForeignKey("oh_ms_overhaul_scope.id") + UUID(as_uuid=True), ForeignKey("oh_ms_overhaul.id") ) optimum_oh_day = Column(Integer, nullable=True) + max_interval = Column(Integer, nullable=True) + session = relationship("OverhaulScope", lazy="raise") parameter = relationship("CalculationParam", back_populates="calculation_data") @@ -141,7 +143,7 @@ class CalculationEquipmentResult(Base, DefaultMixin): overhaul_costs = Column(JSON, nullable=False) daily_failures = Column(JSON, nullable=False) procurement_costs = Column(JSON, nullable=False) - assetnum = Column(String(255), nullable=False) + location_tag = Column(String(255), nullable=False) material_cost = Column(Float, nullable=False) service_cost = Column(Float, nullable=False) calculation_data_id = Column( @@ -154,6 +156,6 @@ class CalculationEquipmentResult(Base, DefaultMixin): master_equipment = relationship( "MasterEquipment", lazy="joined", - primaryjoin="and_(CalculationEquipmentResult.assetnum == foreign(MasterEquipment.assetnum))", + primaryjoin="and_(CalculationEquipmentResult.location_tag == foreign(MasterEquipment.location_tag))", uselist=False, # Add this if it's a one-to-one relationship ) diff --git a/src/calculation_time_constrains/schema.py b/src/calculation_time_constrains/schema.py index 426b3c4..6650fed 100644 --- a/src/calculation_time_constrains/schema.py +++ b/src/calculation_time_constrains/schema.py @@ -6,7 +6,7 @@ from uuid import UUID from pydantic import Field from src.models import DefultBase -from src.scope_equipment.schema import MasterEquipmentBase +from src.standard_scope.schema import MasterEquipmentBase class CalculationTimeConstrainsBase(DefultBase): @@ -67,7 +67,7 @@ class EquipmentResult(CalculationTimeConstrainsBase): overhaul_costs: List[float] procurement_costs: List[float] daily_failures: List[float] - assetnum: str + location_tag: str material_cost: float service_cost: float optimum_day: int # Added optimum result for each equipment diff --git a/src/calculation_time_constrains/service.py b/src/calculation_time_constrains/service.py index 0f2540f..86ed8ce 100644 --- a/src/calculation_time_constrains/service.py +++ b/src/calculation_time_constrains/service.py @@ -24,554 +24,1240 @@ from .schema import (CalculationResultsRead, CalculationTimeConstrainsRead, OptimumResult) from .utils import get_months_between -from src.scope_equipment_part.model import ScopeEquipmentPart +from src.equipment_sparepart.model import ScopeEquipmentPart import copy import random import math +from src.overhaul_activity.service import get_standard_scope_by_session_id +from collections import defaultdict +from datetime import timedelta +import pandas as pd +import logging +import aiohttp +from datetime import datetime, date +import asyncio +import json +from src.utils import save_to_pastebin + +# class ReliabilityService: +# """Service class for handling reliability API calls""" + +# def __init__(self, base_url: str = "http://192.168.1.82:8000", use_dummy_data=False): +# self.base_url = base_url +# self.use_dummy_data = use_dummy_data + +# async def get_number_of_failures(self, location_tag, start_date, end_date, token, max_interval=24): +# if self.use_dummy_data: +# return self._generate_dummy_failure_data(location_tag, start_date, end_date, max_interval) + +# url_prediction = ( +# f"{self.base_url}/main/number-of-failures/" +# f"{location_tag}/{start_date.strftime('%Y-%m-%d')}/{end_date.strftime('%Y-%m-%d')}" +# ) +# results = {} + +# try: +# response = requests.get( +# url_prediction, +# headers={ +# "Content-Type": "application/json", +# "Authorization": f"Bearer {token}", +# }, +# timeout=10 +# ) +# response.raise_for_status() +# prediction_data = response.json() +# except (requests.RequestException, ValueError) as e: +# raise Exception(f"Failed to fetch or parse prediction data: {e}") + +# if not prediction_data or "data" not in prediction_data or not isinstance(prediction_data["data"], list): +# raise Exception("Invalid or empty prediction data format.") + +# # Since data is cumulative, we need to preserve the decimal values +# last_cumulative_value = 0 + +# # Parse prediction data and preserve cumulative nature +# for item in prediction_data["data"]: +# try: +# date = datetime.datetime.strptime(item["date"], "%d %b %Y") +# last_day = calendar.monthrange(date.year, date.month)[1] +# value = item.get("num_fail", 0) + +# if date.day == last_day: # End of month +# if value is not None and value > 0: +# # PRESERVE the decimal values - don't convert to int! +# results[date.date()] = round(float(value), 3) # Keep 3 decimal places +# last_cumulative_value = float(value) +# else: +# # If no value, use previous cumulative value +# results[date.date()] = last_cumulative_value + +# except (KeyError, ValueError): +# continue + +# # Fill missing months by continuing the cumulative trend +# current = start_date.replace(day=1) + +# for _ in range(max_interval): +# last_day = calendar.monthrange(current.year, current.month)[1] +# last_day_date = datetime.date(current.year, current.month, last_day) + +# if last_day_date not in results: +# # Since it's cumulative, add a small increment to continue the trend +# # You can adjust this increment based on your typical monthly increase +# monthly_increment = 0.05 # Adjust this value based on your data pattern +# last_cumulative_value += monthly_increment +# results[last_day_date] = round(last_cumulative_value, 3) +# else: +# # Update our tracking value +# last_cumulative_value = results[last_day_date] + +# # Move to next month +# if current.month == 12: +# current = current.replace(year=current.year + 1, month=1) +# else: +# current = current.replace(month=current.month + 1) + +# # Sort results by date +# results = dict(sorted(results.items())) +# return results + +# def _generate_dummy_failure_data(self, location_tag: str, start_date: datetime.date, end_date: datetime.date, max_interval: int = 24) -> Dict[datetime.date, float]: +# """ +# Generate realistic dummy failure prediction data for demonstration purposes. +# Creates a realistic pattern with seasonal variations and some randomness. +# """ +# results = {} + +# # Set seed based on location_tag for consistent results per location +# random.seed(hash(location_tag) % 1000) + +# # Base parameters for realistic failure patterns +# base_monthly_failures = random.uniform(0.5, 1.25) # Base failures per month +# seasonal_amplitude = random.uniform(0.3, 0.8) # Seasonal variation strength +# trend_slope = random.uniform(-0.01, 0.02) # Long-term trend (slight increase over time) +# noise_level = random.uniform(0.1, 0.3) # Random variation + +# # Determine equipment factor random between 1.0 and 1.9 +# equipment_factor = random.uniform(1.0, 1.9) +# current = start_date.replace(day=1) +# cumulative_failures = 0 +# month_count = 0 + +# for _ in range(max_interval): +# #If month count == 0, set cumulative_failures to 0 +# last_day = calendar.monthrange(current.year, current.month)[1] +# last_day_date = datetime.datetime(current.year, current.month, last_day) + + +# # Stop if we've passed the end_date +# if last_day_date > end_date: +# break + +# # Calculate seasonal factor (higher in summer/winter, lower in spring/fall) +# seasonal_factor = 1 + seasonal_amplitude * math.sin(2 * math.pi * current.month / 12) + +# # Calculate trend factor (gradual increase over time) +# trend_factor = 1 + trend_slope * month_count + +# # Calculate noise (random variation) +# noise_factor = 1 + random.uniform(-noise_level, noise_level) + +# # Calculate monthly failures (non-cumulative) +# monthly_failures = (base_monthly_failures * +# equipment_factor * +# seasonal_factor * +# trend_factor * +# noise_factor) + +# # Ensure minimum realistic value +# monthly_failures = max(0.1, monthly_failures) + +# # Add to cumulative total +# cumulative_failures += monthly_failures + +# # Store cumulative value rounded to 3 decimal places +# results[last_day_date] = round(cumulative_failures, 3) if month_count > 0 else 0 + +# # Move to next month +# month_count += 1 +# if current.month == 12: +# current = current.replace(year=current.year + 1, month=1) +# else: +# current = current.replace(month=current.month + 1) + +# return dict(sorted(results.items())) + +# async def get_equipment_foh(self, location_tag: str, token: str) -> float: +# """ +# Get forced outage hours for equipment +# """ +# url = f"{self.base_url}/asset/mdt/{location_tag}" +# headers = { +# "Content-Type": "application/json", +# "Authorization": f"Bearer {token}", +# } + +# try: +# response = requests.get(url, headers=headers, timeout=10) +# response.raise_for_status() +# result = response.json() +# return result["data"]["hours"] +# except (requests.RequestException, ValueError) as e: +# raise Exception(f"Failed to fetch FOH data for {location_tag}: {e}") + +# def _parse_failure_predictions( +# self, +# prediction_data: List[dict], +# start_date: datetime.date, +# max_interval: int +# ) -> Dict[datetime.date, int]: +# """ +# Parse and normalize failure prediction data +# """ +# results = {} + +# # Parse prediction data +# for item in prediction_data: +# try: +# date = datetime.datetime.strptime(item["date"], "%d %b %Y").date() +# last_day = calendar.monthrange(date.year, date.month)[1] +# value = item.get("num_fail", 0) + +# if date.day == last_day: +# if date.month == start_date.month and date.year == start_date.year: +# results[date] = 0 +# else: +# results[date] = max(0, int(value)) if value is not None else 0 +# except (KeyError, ValueError): +# continue + +# # Fill missing months with 0 +# current = start_date.replace(day=1) +# for _ in range(max_interval): +# last_day = calendar.monthrange(current.year, current.month)[1] +# last_day_date = datetime.date(current.year, current.month, last_day) + +# if last_day_date not in results: +# results[last_day_date] = 0 + +# # Move to next month +# if current.month == 12: +# current = current.replace(year=current.year + 1, month=1) +# else: +# current = current.replace(month=current.month + 1) + +# return dict(sorted(results.items())) + + +# class SparePartsService: +# """Service class for spare parts management and procurement calculations""" + +# def __init__(self, spare_parts_db: dict): +# self.spare_parts_db = spare_parts_db + +# def calculate_stock_at_date(self, sparepart_id: UUID, target_date: datetime.date): +# """ +# Calculate projected stock for a spare part at a specific date +# """ +# if sparepart_id not in self.spare_parts_db: +# return 0 + +# spare_part = self.spare_parts_db[sparepart_id] +# projected_stock = spare_part["stock"] + +# # Add all procurements that arrive by target_date +# for procurement in spare_part["data"].sparepart_procurements: +# eta_date = getattr(procurement, procurement.status, None) +# if eta_date and eta_date <= target_date: +# projected_stock += procurement.quantity + +# return projected_stock + +# async def reduce_stock(self, db_session, location_tag: str): +# requirements_query = select(ScopeEquipmentPart).where( +# ScopeEquipmentPart.location_tag == location_tag +# ) + +# requirements = await db_session.execute(requirements_query) +# requirements = requirements.scalars().all() + +# for requirement in requirements: +# sparepart_id = requirement.sparepart_id +# quantity_needed = requirement.required_stock + +# if sparepart_id in self.spare_parts_db: +# self.spare_parts_db[sparepart_id]["stock"] -= quantity_needed + +# async def check_spare_parts_availability( +# self, +# db_session: DbSession, +# equipment: OverhaulActivity, +# overhaul_date: datetime.date +# ) -> Tuple[bool, List[dict]]: +# """ +# Check if spare parts are available for equipment overhaul at specific date. +# If not available, calculate procurement costs needed. +# """ +# procurement_costs = [] +# all_available = True + +# requirements_query = select(ScopeEquipmentPart).where( +# ScopeEquipmentPart.location_tag == equipment.location_tag +# ) + +# requirements = await db_session.execute(requirements_query) +# requirements = requirements.scalars().all() + + + +# for requirement in requirements: +# sparepart_id = requirement.sparepart_id +# quantity_needed = requirement.required_stock + +# if sparepart_id not in self.spare_parts_db: +# raise Exception(f"Spare part {sparepart_id} not found in database") + +# spare_part = self.spare_parts_db[sparepart_id] +# spare_part_data = spare_part["data"] +# available_stock = self.calculate_stock_at_date(sparepart_id, overhaul_date) + +# if available_stock < quantity_needed: +# # Need to procure additional stock +# shortage = quantity_needed - available_stock +# procurement_cost = { +# "sparepart_id": str(sparepart_id), +# "sparepart_name": spare_part_data.name, +# "quantity": shortage, +# "cost_per_unit": spare_part_data.cost_per_stock, +# "total_cost": shortage * spare_part_data.cost_per_stock, +# "description": f"Insufficient projected stock for {spare_part_data.name} on {overhaul_date} (need: {quantity_needed}, projected: {available_stock})" +# } +# procurement_costs.append(procurement_cost) +# all_available = False +# return all_available, procurement_costs + +# class OverhaulCalculator: + + + +# """Main calculator for overhaul cost optimization""" + +# def __init__( +# self, +# reliability_service: ReliabilityService, +# spare_parts_service: SparePartsService +# ): +# self.reliability_service = reliability_service +# self.spare_parts_service = spare_parts_service + +# async def simulate_equipment_overhaul( +# self, +# db_session: DbSession, +# equipment, +# preventive_cost: float, +# predicted_failures: Dict[datetime.date, int], +# interval_months: int, +# forced_outage_hours: float, +# start_date: datetime.date, +# total_months: int = 24 +# ): +# """ +# Simulate overhaul strategy for specific equipment including spare parts costs +# """ +# total_preventive_cost = 0 +# total_corrective_cost = 0 +# total_procurement_cost = 0 +# all_procurement_details = [] +# months_since_overhaul = 0 + +# # Convert failures dict to month-indexed dict +# failures_by_month = { +# i: val for i, (date, val) in enumerate(sorted(predicted_failures.items())) +# } + +# cost_per_failure = equipment.material_cost + +# # Simulate for the total period +# for month in range(total_months): +# # Calculate current date +# current_date = self._add_months_to_date(start_date, month) + +# # Check if it's time for overhaul +# if months_since_overhaul >= interval_months: +# # Perform preventive overhaul +# total_preventive_cost += preventive_cost +# months_since_overhaul = 0 + +# # Calculate corrective costs +# if months_since_overhaul == 0: +# expected_failures = 0 # No failures immediately after overhaul +# else: +# expected_failures = round(failures_by_month.get(months_since_overhaul, 0)) + +# equivalent_force_derated_hours = 0 # Can be enhanced based on requirements +# failure_cost = ( +# (expected_failures * cost_per_failure) + +# ((forced_outage_hours + equivalent_force_derated_hours) * equipment.service_cost) +# ) +# total_corrective_cost += failure_cost + +# months_since_overhaul += 1 + + +# overhaul_target_date = self._add_months_to_date(start_date, interval_months) +# # Check spare parts availability and calculate procurement costs +# parts_available, procurement_costs = await self.spare_parts_service.check_spare_parts_availability( +# db_session, +# equipment, +# overhaul_target_date +# ) + +# # Add procurement costs if parts are not available +# if not parts_available: +# month_procurement_cost = sum(pc["total_cost"] for pc in procurement_costs) +# total_procurement_cost += month_procurement_cost +# all_procurement_details.extend(procurement_costs) + +# # Calculate monthly averages +# monthly_preventive_cost = total_preventive_cost / total_months +# monthly_corrective_cost = total_corrective_cost / total_months +# monthly_total_cost = monthly_preventive_cost + monthly_corrective_cost + total_procurement_cost + + +# return { +# "interval_months":interval_months, +# "preventive_cost":monthly_preventive_cost, +# "corrective_cost":monthly_corrective_cost, +# "procurement_cost":total_procurement_cost, +# "total_cost":monthly_total_cost, +# "procurement_details":all_procurement_details +# } + +# async def find_optimal_overhaul_interval( +# self, +# db_session: DbSession, +# equipment, +# preventive_cost: float, +# predicted_failures: Dict[datetime.date, int], +# forced_outage_hours: float, +# start_date: datetime.date, +# max_interval: int = 24 +# ): +# """ +# Find optimal overhaul interval by testing different intervals +# """ +# all_results = [] + +# for interval in range(1, max_interval + 1): +# result = await self.simulate_equipment_overhaul( +# db_session=db_session, +# equipment=equipment, +# preventive_cost=preventive_cost, +# predicted_failures=predicted_failures, +# interval_months=interval, +# forced_outage_hours=forced_outage_hours, +# start_date=start_date, +# total_months=max_interval +# ) +# all_results.append(result) + +# # Find optimal result (minimum total cost) +# optimal_result = min(all_results, key=lambda x: x["total_cost"]) + +# return optimal_result, all_results + +# async def calculate_fleet_optimization( +# self, +# db_session: DbSession, +# equipments: list, +# overhaul_cost: float, +# start_date: datetime.date, +# end_date: datetime.date, +# calculation, +# token: str +# ) -> Dict: +# """ +# Calculate optimization for entire fleet of equipment +# """ +# max_interval = self._get_months_between(start_date, end_date) +# preventive_cost_per_equipment = overhaul_cost / len(equipments) + +# fleet_results = [] +# total_corrective_costs = np.zeros(max_interval) +# total_preventive_costs = np.zeros(max_interval) +# total_procurement_costs = np.zeros(max_interval) +# total_failures = np.zeros(max_interval) + +# for equipment in equipments: +# # Get reliability data +# predicted_failures = await self.reliability_service.get_number_of_failures( +# location_tag=equipment.equipment.location_tag, +# start_date=start_date, +# end_date=end_date, +# token=token, +# max_interval=max_interval +# ) + + +# forced_outage_hours = await self.reliability_service.get_equipment_foh( +# location_tag=equipment.equipment.location_tag, +# token=token +# ) + +# # Find optimal interval for this equipment +# optimal_result, all_results = await self.find_optimal_overhaul_interval( +# db_session=db_session, +# equipment=equipment, +# preventive_cost=preventive_cost_per_equipment, +# predicted_failures=predicted_failures, +# forced_outage_hours=forced_outage_hours, +# start_date=start_date, +# max_interval=max_interval +# ) + +# #reduce sparepart stock +# await self.spare_parts_service.reduce_stock(db_session, equipment.location_tag) + +# # Aggregate costs +# corrective_costs = [r["corrective_cost"] for r in all_results] +# preventive_costs = [r["preventive_cost"] for r in all_results] +# procurement_costs = [r["procurement_cost"] for r in all_results] +# procurement_details = [r["procurement_details"] for r in all_results] +# failures = [round(r) for r in predicted_failures.values()] + + +# fleet_results.append( +# CalculationEquipmentResult( +# corrective_costs=corrective_costs, +# overhaul_costs=preventive_costs, +# procurement_costs=procurement_costs, +# daily_failures=failures, +# assetnum=equipment.assetnum, +# material_cost=equipment.material_cost, +# service_cost=equipment.service_cost, +# optimum_day=optimal_result["interval_months"], +# calculation_data_id=calculation.id, +# master_equipment=equipment.equipment, +# procurement_details=procurement_details +# ) +# ) + +# total_corrective_costs += np.array(corrective_costs) +# total_preventive_costs += np.array(preventive_costs) +# total_procurement_costs += np.array(procurement_costs) + +# # Calculate fleet optimal interval +# total_costs = total_corrective_costs + total_preventive_costs + total_procurement_costs +# fleet_optimal_index = np.argmin(total_costs) +# calculation.optimum_oh_day =fleet_optimal_index + 1 + +# db_session.add_all(fleet_results) +# await db_session.commit() + +# return { +# 'id': calculation.id, +# 'fleet_results': fleet_results, +# 'fleet_optimal_interval': fleet_optimal_index + 1, +# 'fleet_optimal_cost': total_costs[fleet_optimal_index], +# 'total_corrective_costs': total_corrective_costs.tolist(), +# 'total_preventive_costs': total_preventive_costs.tolist(), +# 'total_procurement_costs': total_procurement_costs.tolist(), +# } + +# def _add_months_to_date(self, start_date: datetime.date, months: int) -> datetime.date: +# """Helper method to add months to a date""" +# year = start_date.year +# month = start_date.month + months + +# while month > 12: +# year += 1 +# month -= 12 + +# return datetime.date(year, month, start_date.day) + +# def _get_months_between(self, start_date: datetime.date, end_date: datetime.date) -> int: +# """Calculate number of months between two dates""" +# return (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) + + +# class OptimumCostModel: +# def __init__(self, token, last_oh_date, next_oh_date,interval =30, base_url: str = "http://192.168.1.82:8000"): +# self.api_base_url = base_url +# self.token = token +# self.last_oh_date = last_oh_date +# self.next_oh_date = next_oh_date +# self.interval=30 + + +# def _get_months_between(self, start_date: datetime.date, end_date: datetime.date) -> int: +# """Calculate number of months between two dates""" +# return (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) + +# def _get_reliability_from_api(self, target_date, location_tag): +# """ +# Get reliability value from API for a specific date. + +# Parameters: +# target_date: datetime object for the target date + +# Returns: +# reliability value (float) +# """ +# # Format date for API call +# date_str = target_date.strftime('%Y-%m-%d %H:%M:%S.%f') + +# # Construct API URL +# url = f"{self.api_base_url}/calculate/reliability/{location_tag}/{date_str}" + +# header = { +# 'Content-Type': 'application/json', +# 'Authorization': 'Bearer ' + self.token +# } + +# try: +# response = requests.get(url, headers=header) +# response.raise_for_status() + +# data = response.json() + +# reliability = data['data']['value'] + +# return reliability + +# except requests.RequestException as e: +# print(f"API Error: {e}") +# return None +# except KeyError as e: +# print(f"Data parsing error: {e}") + +# return None + + +# def _get_reliability_equipment(self, location_tag): +# current_date = self.last_oh_date +# results = defaultdict() + +# print("Fetching reliability data from API...") + +# while current_date <= self.next_oh_date: +# reliability = self._get_reliability_from_api(current_date, location_tag) +# results[current_date] = reliability + +# if reliability is not None: +# print(f"Date: {current_date.strftime('%Y-%m-%d')}, Reliability: {reliability:.6f}") +# else: +# print(f"Date: {current_date.strftime('%Y-%m-%d')}, Reliability: Failed to fetch") + +# current_date += timedelta(days=31) + +# return results + + +# def _calculate_costs_each_point(self, reliabilities, preventive_cost: float, failure_replacement_cost: float): +# # Calculate costs for each time point +# results = [] +# dates = list(reliabilities.keys()) +# reliabilities_list = list(reliabilities.values()) + +# for i, (date, reliability) in enumerate(reliabilities.items()): +# if reliability is None: +# continue + +# # Calculate time from last OH in days +# time_from_last_oh = (date - self.last_oh_date).days + +# # Calculate failure replacement cost +# failure_prob = 1 - reliability + +# # For expected operating time, we need to integrate R(t) from 0 to T +# # Since we have discrete points, we'll approximate using trapezoidal rule +# if i == 0: +# expected_operating_time = time_from_last_oh # First point +# else: +# # Approximate integral using available reliability values +# time_points = [(dates[j] - self.last_oh_date).days for j in range(i+1)] +# rel_values = [rel for rel in reliabilities_list[:i+1] if rel is not None] + +# if len(rel_values) > 1: +# expected_operating_time = np.trapezoid(rel_values, time_points) +# else: +# expected_operating_time = time_from_last_oh * reliability + +# # Calculate costs +# if expected_operating_time > 0: +# failure_cost = (failure_prob * failure_replacement_cost) / expected_operating_time +# preventive_cost = (reliability * preventive_cost) / expected_operating_time +# else: +# # failure_cost = failure_prob * self.IDRu +# # preventive_cost = reliability * self.IDRp + +# continue + +# total_cost = failure_cost + preventive_cost + +# results.append({ +# 'date': date, +# 'days_from_last_oh': time_from_last_oh, +# 'reliability': reliability, +# 'failure_probability': failure_prob, +# 'expected_operating_time': expected_operating_time, +# 'failure_replacement_cost': failure_cost, +# 'preventive_replacement_cost': preventive_cost, +# 'total_cost': total_cost, +# 'procurement_cost': 0, +# 'procurement_details': [] +# }) + +# return results + +# def _find_optimal_timing(self, results_df): +# """ +# Find the timing that gives the lowest total cost. + +# Parameters: +# results_df: DataFrame from calculate_costs_over_period + +# Returns: +# Dictionary with optimal timing information +# """ +# if results_df.empty: +# return None + +# # Find minimum total cost +# min_cost_idx = results_df['total_cost'].idxmin() +# optimal_row = results_df.loc[min_cost_idx] + +# return { +# 'optimal_index': min_cost_idx, +# 'optimal_date': optimal_row['date'], +# 'days_from_last_oh': optimal_row['days_from_last_oh'], +# 'reliability': optimal_row['reliability'], +# 'failure_cost': optimal_row['failure_replacement_cost'], +# 'preventive_cost': optimal_row['preventive_replacement_cost'], +# 'total_cost': optimal_row['total_cost'], +# 'procurement_cost': 0 +# } + + +# async def calculate_cost_all_equipment( +# self, +# db_session: DbSession, +# equipments: list, +# preventive_cost: float, +# calculation, +# ) : +# """ +# Calculate optimization for entire fleet of equipment +# """ +# max_interval = self._get_months_between(self.last_oh_date, self.next_oh_date) +# preventive_cost_per_equipment = preventive_cost / len(equipments) + +# fleet_results = [] +# total_corrective_costs = np.zeros(max_interval) +# total_preventive_costs = np.zeros(max_interval) +# total_procurement_costs = np.zeros(max_interval) +# total_failures = np.zeros(max_interval) + +# for equipment in equipments: +# # Get reliability data +# cost_per_failure = equipment.material_cost +# reliabilities = self._get_reliability_equipment( +# location_tag=equipment.location_tag, +# ) + +# predicted_cost = self._calculate_costs_each_point(reliabilities=reliabilities, preventive_cost=preventive_cost_per_equipment, failure_replacement_cost=cost_per_failure) + +# optimum_cost = self._find_optimal_timing(pd.DataFrame(predicted_cost)) + +# # Aggregate costs +# corrective_costs = [r["failure_replacement_cost"] for r in predicted_cost] +# preventive_costs = [r["preventive_replacement_cost"] for r in predicted_cost] +# procurement_costs = [r["procurement_cost"] for r in predicted_cost] +# procurement_details = [r["procurement_details"] for r in predicted_cost] +# failures = [(1-r["reliability"]) for r in predicted_cost] + +# fleet_results.append( +# CalculationEquipmentResult( +# corrective_costs=corrective_costs, +# overhaul_costs=preventive_costs, +# procurement_costs=procurement_costs, +# daily_failures=failures, +# location_tag=equipment.location_tag, +# material_cost=equipment.material_cost, +# service_cost=equipment.service_cost, +# optimum_day=optimum_cost['optimal_index'], +# calculation_data_id=calculation.id, +# procurement_details=procurement_details +# ) +# ) + +# total_corrective_costs += np.array(corrective_costs) +# total_preventive_costs += np.array(preventive_costs) +# total_procurement_costs += np.array(procurement_costs) + +# # Calculate fleet optimal interval +# total_costs = total_corrective_costs + total_preventive_costs + total_procurement_costs +# fleet_optimal_index = np.argmin(total_costs) + +# calculation.optimum_oh_day =fleet_optimal_index + 1 + +# db_session.add_all(fleet_results) +# await db_session.commit() + +# return { +# 'id': calculation.id, +# 'fleet_results': fleet_results, +# 'fleet_optimal_interval': fleet_optimal_index + 1, +# 'fleet_optimal_cost': total_costs[fleet_optimal_index], +# 'total_corrective_costs': total_corrective_costs.tolist(), +# 'total_preventive_costs': total_preventive_costs.tolist(), +# 'total_procurement_costs': total_procurement_costs.tolist(), +# } + + +class OptimumCostModel: + def __init__(self, token, last_oh_date, next_oh_date, interval=30, base_url: str = "http://192.168.1.82:8000"): + self.api_base_url = base_url + self.token = token + self.last_oh_date = last_oh_date + self.next_oh_date = next_oh_date + self.interval = interval + self.session = None + + # Pre-calculate date range for reuse + self.date_range = self._generate_date_range() + + # Setup logging for debugging + logging.basicConfig(level=logging.INFO) + self.logger = logging.getLogger(__name__) + + def _generate_date_range(self) -> List[datetime]: + """Pre-generate the date range to avoid repeated calculations""" + dates = [] + current_date = self.last_oh_date + while current_date <= self.next_oh_date: + dates.append(current_date) + current_date += timedelta(days=31) + return dates + + def _get_months_between(self, start_date: date, end_date: date) -> int: + """Calculate number of months between two dates""" + return (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) -class ReliabilityService: - """Service class for handling reliability API calls""" - - def __init__(self, base_url: str = "http://192.168.1.82:8000", use_dummy_data=False): - self.base_url = base_url - self.use_dummy_data = use_dummy_data - - async def get_number_of_failures(self, location_tag, start_date, end_date, token, max_interval=24): - if self.use_dummy_data: - return self._generate_dummy_failure_data(location_tag, start_date, end_date, max_interval) - - url_prediction = ( - f"{self.base_url}/main/number-of-failures/" - f"{location_tag}/{start_date.strftime('%Y-%m-%d')}/{end_date.strftime('%Y-%m-%d')}" - ) - results = {} - - try: - response = requests.get( - url_prediction, - headers={ - "Content-Type": "application/json", - "Authorization": f"Bearer {token}", - }, - timeout=10 + async def _create_session(self): + """Create aiohttp session with connection pooling""" + if self.session is None: + timeout = aiohttp.ClientTimeout() + connector = aiohttp.TCPConnector( + limit=500, # Total connection pool size + limit_per_host=200, # Max connections per host + ttl_dns_cache=00, # DNS cache TTL + use_dns_cache=True, + force_close=False, + enable_cleanup_closed=True + ) + self.session = aiohttp.ClientSession( + timeout=timeout, + connector=connector, + headers={'Authorization': f'Bearer {self.token}'} ) - response.raise_for_status() - prediction_data = response.json() - except (requests.RequestException, ValueError) as e: - raise Exception(f"Failed to fetch or parse prediction data: {e}") - if not prediction_data or "data" not in prediction_data or not isinstance(prediction_data["data"], list): - raise Exception("Invalid or empty prediction data format.") + async def _close_session(self): + """Close aiohttp session""" + if self.session: + await self.session.close() + self.session = None - # Since data is cumulative, we need to preserve the decimal values - last_cumulative_value = 0 + async def _get_reliability_from_api_async( + self, + target_date: datetime, + location_tag: str, + max_retries: int = 3, + retry_delay: float = 1.0 +) -> Optional[float]: + date_str = target_date.strftime('%Y-%m-%d %H:%M:%S.%f') + url = f"{self.api_base_url}/calculate/reliability/{location_tag}/{date_str}" - # Parse prediction data and preserve cumulative nature - for item in prediction_data["data"]: + for attempt in range(max_retries + 1): try: - date = datetime.datetime.strptime(item["date"], "%d %b %Y") - last_day = calendar.monthrange(date.year, date.month)[1] - value = item.get("num_fail", 0) - - if date.day == last_day: # End of month - if value is not None and value > 0: - # PRESERVE the decimal values - don't convert to int! - results[date.date()] = round(float(value), 3) # Keep 3 decimal places - last_cumulative_value = float(value) + async with self.session.get(url) as response: + if response.status == 200: + data = await response.json() + return data['data']['value'] else: - # If no value, use previous cumulative value - results[date.date()] = last_cumulative_value - - except (KeyError, ValueError): - continue - - # Fill missing months by continuing the cumulative trend - current = start_date.replace(day=1) - - for _ in range(max_interval): - last_day = calendar.monthrange(current.year, current.month)[1] - last_day_date = datetime.date(current.year, current.month, last_day) - - if last_day_date not in results: - # Since it's cumulative, add a small increment to continue the trend - # You can adjust this increment based on your typical monthly increase - monthly_increment = 0.05 # Adjust this value based on your data pattern - last_cumulative_value += monthly_increment - results[last_day_date] = round(last_cumulative_value, 3) - else: - # Update our tracking value - last_cumulative_value = results[last_day_date] - - # Move to next month - if current.month == 12: - current = current.replace(year=current.year + 1, month=1) - else: - current = current.replace(month=current.month + 1) - - # Sort results by date - results = dict(sorted(results.items())) - return results - - def _generate_dummy_failure_data(self, location_tag: str, start_date: datetime.date, end_date: datetime.date, max_interval: int = 24) -> Dict[datetime.date, float]: + # Server error - may be worth retrying + print(f"Server error {response.status} for {location_tag} on {date_str}") + if attempt < max_retries: + await asyncio.sleep(retry_delay * (2 ** attempt)) + continue + + except aiohttp.ClientError as e: + print(f"Network error for {location_tag} on {date_str} (attempt {attempt + 1}): {e}") + if attempt < max_retries: + await asyncio.sleep(retry_delay * (2 ** attempt)) + continue + return None + except Exception as e: + print(f"Unexpected error for {location_tag} on {date_str}: {e}") + return None + + return None """ - Generate realistic dummy failure prediction data for demonstration purposes. - Creates a realistic pattern with seasonal variations and some randomness. - """ - results = {} - - # Set seed based on location_tag for consistent results per location - random.seed(hash(location_tag) % 1000) - - # Base parameters for realistic failure patterns - base_monthly_failures = random.uniform(0.5, 1.25) # Base failures per month - seasonal_amplitude = random.uniform(0.3, 0.8) # Seasonal variation strength - trend_slope = random.uniform(-0.01, 0.02) # Long-term trend (slight increase over time) - noise_level = random.uniform(0.1, 0.3) # Random variation - - # Determine equipment factor random between 1.0 and 1.9 - equipment_factor = random.uniform(1.0, 1.9) - current = start_date.replace(day=1) - cumulative_failures = 0 - month_count = 0 - - for _ in range(max_interval): - #If month count == 0, set cumulative_failures to 0 - last_day = calendar.monthrange(current.year, current.month)[1] - last_day_date = datetime.datetime(current.year, current.month, last_day) - - - # Stop if we've passed the end_date - if last_day_date > end_date: - break - - # Calculate seasonal factor (higher in summer/winter, lower in spring/fall) - seasonal_factor = 1 + seasonal_amplitude * math.sin(2 * math.pi * current.month / 12) - - # Calculate trend factor (gradual increase over time) - trend_factor = 1 + trend_slope * month_count + Async version of reliability API call with retry logic - # Calculate noise (random variation) - noise_factor = 1 + random.uniform(-noise_level, noise_level) + Args: + target_date: Date to query + location_tag: Location identifier + max_retries: Maximum number of retry attempts + retry_delay: Initial delay between retries (exponential backoff) - # Calculate monthly failures (non-cumulative) - monthly_failures = (base_monthly_failures * - equipment_factor * - seasonal_factor * - trend_factor * - noise_factor) - - # Ensure minimum realistic value - monthly_failures = max(0.1, monthly_failures) - - # Add to cumulative total - cumulative_failures += monthly_failures - - # Store cumulative value rounded to 3 decimal places - results[last_day_date] = round(cumulative_failures, 3) if month_count > 0 else 0 - - # Move to next month - month_count += 1 - if current.month == 12: - current = current.replace(year=current.year + 1, month=1) - else: - current = current.replace(month=current.month + 1) - - return dict(sorted(results.items())) - - async def get_equipment_foh(self, location_tag: str, token: str) -> float: - """ - Get forced outage hours for equipment + Returns: + Reliability value or None if failed """ - url = f"{self.base_url}/asset/mdt/{location_tag}" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {token}", - } + date_str = target_date.strftime('%Y-%m-%d %H:%M:%S.%f') + url = f"{self.api_base_url}/calculate/reliability/{location_tag}/{date_str}" - try: - response = requests.get(url, headers=headers, timeout=10) - response.raise_for_status() - result = response.json() - return result["data"]["hours"] - except (requests.RequestException, ValueError) as e: - raise Exception(f"Failed to fetch FOH data for {location_tag}: {e}") - - def _parse_failure_predictions( + for attempt in range(max_retries + 1): + try: + async with self.session.get(url) as response: + if response.status == 200: + data = await response.json() + return data['data']['value'] + elif response.status >= 500: + # Server error - may be worth retrying + error_msg = f"Server error {response.status} for {location_tag} on {date_str}" + if attempt < max_retries: + await asyncio.sleep(retry_delay * (2 ** attempt)) + continue + else: + # Client error - no point retrying + print(f"API Error for {location_tag} on {date_str}: Status {response.status}") + return None + + except aiohttp.ClientError as e: + print(f"Network error for {location_tag} on {date_str} (attempt {attempt + 1}): {e}") + if attempt < max_retries: + await asyncio.sleep(retry_delay * (2 ** attempt)) + continue + return None + except Exception as e: + print(f"Unexpected error for {location_tag} on {date_str}: {e}") + return None + + return None + + async def _get_reliability_equipment_batch( + self, + location_tags: List[str], + batch_size: int = 100, + max_retries: int = 3, + rate_limit_delay: float = 0.1 + ): + await self._create_session() + + # Prepare all tasks with retry wrapper + tasks = [] + task_mapping = [] + + for location_tag in location_tags: + for target_date in self.date_range: + # Create a task with retry logic + task = self._execute_with_retry( + target_date, + location_tag, + max_retries=max_retries + ) + tasks.append(task) + task_mapping.append((location_tag, target_date)) + + print(f"Executing {len(tasks)} API calls in batches of {batch_size}...") + + # Process with controlled concurrency + semaphore = asyncio.Semaphore(batch_size) + results = defaultdict(dict) + completed = 0 + total_batches = (len(tasks) + batch_size - 1) // batch_size + + async def process_task(task, location_tag, target_date): + nonlocal completed + async with semaphore: + try: + reliability = await task + results[location_tag][target_date] = reliability + except Exception as e: + results[location_tag][target_date] = None + print(f"Failed for {location_tag} on {target_date}: {str(e)}") + finally: + completed += 1 + if completed % 100 == 0: + print(f"Progress: {completed}/{len(tasks)} ({completed/len(tasks):.1%})") + + # Process all tasks with rate limiting + batch_tasks = [] + for i, task in enumerate(tasks): + batch_tasks.append(process_task(task, *task_mapping[i])) + if i > 0 and i % batch_size == 0: + await asyncio.sleep(rate_limit_delay) # Gentle rate limiting + + await asyncio.gather(*batch_tasks) + results_serializable = {str(k): v for k, v in results.items()} + return dict(results) + + def json_datetime_handler(self,obj): + if isinstance(obj, datetime): + return obj.isoformat() # Or str(obj) + return str(obj) + + + async def _execute_with_retry( self, - prediction_data: List[dict], - start_date: datetime.date, - max_interval: int - ) -> Dict[datetime.date, int]: + target_date: datetime, + location_tag: str, + max_retries: int = 3, + base_delay: float = 1.0 + ) -> Optional[float]: """ - Parse and normalize failure prediction data + Execute API call with exponential backoff retry logic. """ - results = {} - - # Parse prediction data - for item in prediction_data: + for attempt in range(max_retries + 1): try: - date = datetime.datetime.strptime(item["date"], "%d %b %Y").date() - last_day = calendar.monthrange(date.year, date.month)[1] - value = item.get("num_fail", 0) - - if date.day == last_day: - if date.month == start_date.month and date.year == start_date.year: - results[date] = 0 - else: - results[date] = max(0, int(value)) if value is not None else 0 - except (KeyError, ValueError): + return await self._get_reliability_from_api_async(target_date, location_tag) + except aiohttp.ClientError as e: + if attempt == max_retries: + raise + delay = base_delay * (2 ** attempt) + await asyncio.sleep(delay) + except Exception as e: + raise # Non-retryable errors + + def _calculate_costs_vectorized(self, reliabilities: Dict[datetime, float], + preventive_cost: float, failure_replacement_cost: float) -> List[Dict]: + valid_data = [(date, rel) for date, rel in reliabilities.items() if rel is not None] + if not valid_data: + return [] + + # Sort by date (past to future) + valid_data.sort(key=lambda x: x[0]) + dates, reliability_values = zip(*valid_data) + + dates = np.array(dates) + reliability_values = np.array(reliability_values) + + # Calculate days from last OH + days_from_last_oh = np.array([(date - self.last_oh_date).days for date in dates]) + + # Calculate failure probabilities + failure_probs = 1 - reliability_values + + # Calculate expected operating times using trapezoidal integration + # This is the denominator: ∫₀ᵀ R(t) dt for each T + expected_operating_times = np.zeros_like(days_from_last_oh, dtype=float) + for i in range(len(days_from_last_oh)): + # Time points from 0 to current point T + time_points = [(d - self.last_oh_date).days for d in dates[:i+1]] + # Reliability values (assuming reliability at time 0 is 1.0) + rel_values = reliability_values[:i+1] + # Calculate expected operating time up to this point + expected_operating_times[i] = np.trapz(rel_values, time_points) + + + # Calculate costs according to the formula + # Failure cost = (1-R(T)) × IDRu / ∫₀ᵀ R(t) dt + failure_costs = (failure_probs * failure_replacement_cost) / expected_operating_times + # Preventive cost = R(T) × IDRp / ∫₀ᵀ R(t) dt + preventive_costs = (reliability_values * preventive_cost) / expected_operating_times + + + # Total cost = Failure cost + Preventive cost + total_costs = failure_costs + preventive_costs + + # Convert back to list of dictionaries + results = [] + for i in range(len(dates)): + if i == 0: continue + results.append({ + 'date': dates[i], + 'days_from_last_oh': days_from_last_oh[i], + 'reliability': reliability_values[i], + 'failure_probability': failure_probs[i], + 'expected_operating_time': expected_operating_times[i], + 'failure_replacement_cost': failure_costs[i], + 'preventive_replacement_cost': preventive_costs[i], + 'total_cost': total_costs[i], + 'procurement_cost': 0, + 'procurement_details': [] + }) - # Fill missing months with 0 - current = start_date.replace(day=1) - for _ in range(max_interval): - last_day = calendar.monthrange(current.year, current.month)[1] - last_day_date = datetime.date(current.year, current.month, last_day) - - if last_day_date not in results: - results[last_day_date] = 0 - - # Move to next month - if current.month == 12: - current = current.replace(year=current.year + 1, month=1) - else: - current = current.replace(month=current.month + 1) - - return dict(sorted(results.items())) - - -class SparePartsService: - """Service class for spare parts management and procurement calculations""" - - def __init__(self, spare_parts_db: dict): - self.spare_parts_db = spare_parts_db + return results - def calculate_stock_at_date(self, sparepart_id: UUID, target_date: datetime.date): + def _find_optimal_timing_vectorized(self, results: List[Dict]) -> Optional[Dict]: """ - Calculate projected stock for a spare part at a specific date + Vectorized optimal timing calculation """ - if sparepart_id not in self.spare_parts_db: - return 0 - - spare_part = self.spare_parts_db[sparepart_id] - projected_stock = spare_part["stock"] + if not results: + return None - # Add all procurements that arrive by target_date - for procurement in spare_part["data"].sparepart_procurements: - eta_date = getattr(procurement, procurement.status, None) - if eta_date and eta_date <= target_date: - projected_stock += procurement.quantity + total_costs = np.array([r['total_cost'] for r in results]) + min_idx = np.argmin(total_costs) + optimal_result = results[min_idx] - return projected_stock - - async def reduce_stock(self, db_session, location_tag: str): - requirements_query = select(ScopeEquipmentPart).where( - ScopeEquipmentPart.location_tag == location_tag - ) - - requirements = await db_session.execute(requirements_query) - requirements = requirements.scalars().all() - - for requirement in requirements: - sparepart_id = requirement.sparepart_id - quantity_needed = requirement.required_stock - - if sparepart_id in self.spare_parts_db: - self.spare_parts_db[sparepart_id]["stock"] -= quantity_needed - - async def check_spare_parts_availability( - self, - db_session: DbSession, - equipment: OverhaulActivity, - overhaul_date: datetime.date - ) -> Tuple[bool, List[dict]]: - """ - Check if spare parts are available for equipment overhaul at specific date. - If not available, calculate procurement costs needed. - """ - procurement_costs = [] - all_available = True - - requirements_query = select(ScopeEquipmentPart).where( - ScopeEquipmentPart.location_tag == equipment.location_tag - ) - - requirements = await db_session.execute(requirements_query) - requirements = requirements.scalars().all() - - - - for requirement in requirements: - sparepart_id = requirement.sparepart_id - quantity_needed = requirement.required_stock - - if sparepart_id not in self.spare_parts_db: - raise Exception(f"Spare part {sparepart_id} not found in database") - - spare_part = self.spare_parts_db[sparepart_id] - spare_part_data = spare_part["data"] - available_stock = self.calculate_stock_at_date(sparepart_id, overhaul_date) - - if available_stock < quantity_needed: - # Need to procure additional stock - shortage = quantity_needed - available_stock - procurement_cost = { - "sparepart_id": str(sparepart_id), - "sparepart_name": spare_part_data.name, - "quantity": shortage, - "cost_per_unit": spare_part_data.cost_per_stock, - "total_cost": shortage * spare_part_data.cost_per_stock, - "description": f"Insufficient projected stock for {spare_part_data.name} on {overhaul_date} (need: {quantity_needed}, projected: {available_stock})" - } - procurement_costs.append(procurement_cost) - all_available = False - return all_available, procurement_costs - -class OverhaulCalculator: - """Main calculator for overhaul cost optimization""" - - def __init__( - self, - reliability_service: ReliabilityService, - spare_parts_service: SparePartsService - ): - self.reliability_service = reliability_service - self.spare_parts_service = spare_parts_service + return { + 'optimal_index': min_idx, + 'optimal_date': optimal_result['date'], + 'days_from_last_oh': optimal_result['days_from_last_oh'], + 'reliability': optimal_result['reliability'], + 'failure_cost': optimal_result['failure_replacement_cost'], + 'preventive_cost': optimal_result['preventive_replacement_cost'], + 'total_cost': optimal_result['total_cost'], + 'procurement_cost': 0 + } - async def simulate_equipment_overhaul( + async def calculate_cost_all_equipment( self, - db_session: DbSession, - equipment, + db_session, # DbSession type + equipments: List, # List of equipment objects preventive_cost: float, - predicted_failures: Dict[datetime.date, int], - interval_months: int, - forced_outage_hours: float, - start_date: datetime.date, - total_months: int = 24 - ): + calculation, + ) -> Dict: """ - Simulate overhaul strategy for specific equipment including spare parts costs + Optimized calculation for entire fleet of equipment using concurrent API calls """ - total_preventive_cost = 0 - total_corrective_cost = 0 - total_procurement_cost = 0 - all_procurement_details = [] - months_since_overhaul = 0 - - # Convert failures dict to month-indexed dict - failures_by_month = { - i: val for i, (date, val) in enumerate(sorted(predicted_failures.items())) - } - - cost_per_failure = equipment.material_cost + try: - # Simulate for the total period - for month in range(total_months): - # Calculate current date - current_date = self._add_months_to_date(start_date, month) + max_interval = len(self.date_range) + preventive_cost_per_equipment = preventive_cost / len(equipments) - # Check if it's time for overhaul - if months_since_overhaul >= interval_months: - # Perform preventive overhaul - total_preventive_cost += preventive_cost - months_since_overhaul = 0 + # Extract location tags for batch processing + location_tags = [eq.location_tag for eq in equipments] - # Calculate corrective costs - if months_since_overhaul == 0: - expected_failures = 0 # No failures immediately after overhaul - else: - expected_failures = round(failures_by_month.get(months_since_overhaul, 0)) + print(f"Starting reliability data fetch for {len(equipments)} equipment...") - equivalent_force_derated_hours = 0 # Can be enhanced based on requirements - failure_cost = ( - (expected_failures * cost_per_failure) + - ((forced_outage_hours + equivalent_force_derated_hours) * equipment.service_cost) - ) - total_corrective_cost += failure_cost + # Fetch all reliability data concurrently + all_reliabilities = await self._get_reliability_equipment_batch(location_tags) - months_since_overhaul += 1 + print("Processing cost calculations...") - overhaul_target_date = self._add_months_to_date(start_date, interval_months) - # Check spare parts availability and calculate procurement costs - parts_available, procurement_costs = await self.spare_parts_service.check_spare_parts_availability( - db_session, - equipment, - overhaul_target_date - ) + # Process each equipment's costs + fleet_results = [] + total_corrective_costs = np.zeros(max_interval) + total_preventive_costs = np.zeros(max_interval) + total_procurement_costs = np.zeros(max_interval) + total_failures = np.zeros(max_interval) - # Add procurement costs if parts are not available - if not parts_available: - month_procurement_cost = sum(pc["total_cost"] for pc in procurement_costs) - total_procurement_cost += month_procurement_cost - all_procurement_details.extend(procurement_costs) + for equipment in equipments: + location_tag = equipment.location_tag + cost_per_failure = equipment.material_cost - # Calculate monthly averages - monthly_preventive_cost = total_preventive_cost / total_months - monthly_corrective_cost = total_corrective_cost / total_months - monthly_total_cost = monthly_preventive_cost + monthly_corrective_cost + total_procurement_cost + # Get pre-fetched reliability data + reliabilities = all_reliabilities.get(location_tag, {}) - return { - "interval_months":interval_months, - "preventive_cost":monthly_preventive_cost, - "corrective_cost":monthly_corrective_cost, - "procurement_cost":total_procurement_cost, - "total_cost":monthly_total_cost, - "procurement_details":all_procurement_details - } - async def find_optimal_overhaul_interval( - self, - db_session: DbSession, - equipment, - preventive_cost: float, - predicted_failures: Dict[datetime.date, int], - forced_outage_hours: float, - start_date: datetime.date, - max_interval: int = 24 - ): - """ - Find optimal overhaul interval by testing different intervals - """ - all_results = [] - - for interval in range(1, max_interval + 1): - result = await self.simulate_equipment_overhaul( - db_session=db_session, - equipment=equipment, - preventive_cost=preventive_cost, - predicted_failures=predicted_failures, - interval_months=interval, - forced_outage_hours=forced_outage_hours, - start_date=start_date, - total_months=max_interval - ) - all_results.append(result) - - # Find optimal result (minimum total cost) - optimal_result = min(all_results, key=lambda x: x["total_cost"]) - - return optimal_result, all_results + if not reliabilities: + self.logger.warning(f"No reliability data for equipment {location_tag}") + continue - async def calculate_fleet_optimization( - self, - db_session: DbSession, - equipments: list, - overhaul_cost: float, - start_date: datetime.date, - end_date: datetime.date, - calculation, - token: str - ) -> Dict: - """ - Calculate optimization for entire fleet of equipment - """ - max_interval = self._get_months_between(start_date, end_date) - preventive_cost_per_equipment = overhaul_cost / len(equipments) - - fleet_results = [] - total_corrective_costs = np.zeros(max_interval) - total_preventive_costs = np.zeros(max_interval) - total_procurement_costs = np.zeros(max_interval) - total_failures = np.zeros(max_interval) - - for equipment in equipments: - # Get reliability data - predicted_failures = await self.reliability_service.get_number_of_failures( - location_tag=equipment.equipment.location_tag, - start_date=start_date, - end_date=end_date, - token=token, - max_interval=max_interval - ) + # Calculate costs using vectorized operations + predicted_costs = self._calculate_costs_vectorized( + reliabilities=reliabilities, + preventive_cost=preventive_cost_per_equipment, + failure_replacement_cost=cost_per_failure + ) + if not predicted_costs: + self.logger.warning(f"No valid cost predictions for equipment {location_tag}") + continue + + optimum_cost = self._find_optimal_timing_vectorized(predicted_costs) + + # Pad arrays to ensure consistent length + corrective_costs = [r["failure_replacement_cost"] for r in predicted_costs] + preventive_costs = [r["preventive_replacement_cost"] for r in predicted_costs] + procurement_costs = [r["procurement_cost"] for r in predicted_costs] + procurement_details = [r["procurement_details"] for r in predicted_costs] + failures = [(1-r["reliability"]) for r in predicted_costs] + + # Pad arrays to max_interval length + def pad_array(arr, target_length): + if len(arr) < target_length: + return arr + [0] * (target_length - len(arr)) + return arr[:target_length] + + corrective_costs = pad_array(corrective_costs, max_interval) + preventive_costs = pad_array(preventive_costs, max_interval) + procurement_costs = pad_array(procurement_costs, max_interval) + failures = pad_array(failures, max_interval) + + fleet_results.append( + CalculationEquipmentResult( # Assuming this class exists + corrective_costs=corrective_costs, + overhaul_costs=preventive_costs, + procurement_costs=procurement_costs, + daily_failures=failures, + location_tag=equipment.location_tag, + material_cost=equipment.material_cost, + service_cost=equipment.service_cost, + optimum_day=optimum_cost['optimal_index'] if optimum_cost else 0, + calculation_data_id=calculation.id, + procurement_details=procurement_details + ) + ) - forced_outage_hours = await self.reliability_service.get_equipment_foh( - location_tag=equipment.equipment.location_tag, - token=token - ) + # Aggregate costs using vectorized operations + total_corrective_costs += np.array(corrective_costs) + total_preventive_costs += np.array(preventive_costs) + total_procurement_costs += np.array(procurement_costs) - # Find optimal interval for this equipment - optimal_result, all_results = await self.find_optimal_overhaul_interval( - db_session=db_session, - equipment=equipment, - preventive_cost=preventive_cost_per_equipment, - predicted_failures=predicted_failures, - forced_outage_hours=forced_outage_hours, - start_date=start_date, - max_interval=max_interval - ) + # Calculate fleet optimal interval + total_costs = total_corrective_costs + total_preventive_costs + total_procurement_costs + fleet_optimal_index = np.argmin(total_costs) - #reduce sparepart stock - await self.spare_parts_service.reduce_stock(db_session, equipment.location_tag) + calculation.optimum_oh_day = fleet_optimal_index + 1 + calculation.max_interval = max_interval-1 - # Aggregate costs - corrective_costs = [r["corrective_cost"] for r in all_results] - preventive_costs = [r["preventive_cost"] for r in all_results] - procurement_costs = [r["procurement_cost"] for r in all_results] - procurement_details = [r["procurement_details"] for r in all_results] - failures = [round(r) for r in predicted_failures.values()] + # Batch database operations + db_session.add_all(fleet_results) + await db_session.commit() - fleet_results.append( - CalculationEquipmentResult( - corrective_costs=corrective_costs, - overhaul_costs=preventive_costs, - procurement_costs=procurement_costs, - daily_failures=failures, - assetnum=equipment.assetnum, - material_cost=equipment.material_cost, - service_cost=equipment.service_cost, - optimum_day=optimal_result["interval_months"], - calculation_data_id=calculation.id, - master_equipment=equipment.equipment, - procurement_details=procurement_details - ) - ) - - total_corrective_costs += np.array(corrective_costs) - total_preventive_costs += np.array(preventive_costs) - total_procurement_costs += np.array(procurement_costs) + self.logger.info(f"Calculation completed successfully for {len(fleet_results)} equipment") - # Calculate fleet optimal interval - total_costs = total_corrective_costs + total_preventive_costs + total_procurement_costs - fleet_optimal_index = np.argmin(total_costs) - calculation.optimum_oh_day =fleet_optimal_index + 1 + return { + 'id': calculation.id, + 'fleet_results': fleet_results, + 'fleet_optimal_interval': fleet_optimal_index + 1, + 'fleet_optimal_cost': total_costs[fleet_optimal_index], + 'total_corrective_costs': total_corrective_costs.tolist(), + 'total_preventive_costs': total_preventive_costs.tolist(), + 'total_procurement_costs': total_procurement_costs.tolist(), + } - db_session.add_all(fleet_results) - await db_session.commit() + finally: + # Always clean up the session + await self._close_session() - return { - 'id': calculation.id, - 'fleet_results': fleet_results, - 'fleet_optimal_interval': fleet_optimal_index + 1, - 'fleet_optimal_cost': total_costs[fleet_optimal_index], - 'total_corrective_costs': total_corrective_costs.tolist(), - 'total_preventive_costs': total_preventive_costs.tolist(), - 'total_procurement_costs': total_procurement_costs.tolist(), - } + async def __aenter__(self): + """Async context manager entry""" + await self._create_session() + return self - def _add_months_to_date(self, start_date: datetime.date, months: int) -> datetime.date: - """Helper method to add months to a date""" - year = start_date.year - month = start_date.month + months + async def __aexit__(self, exc_type, exc_val, exc_tb): + """Async context manager exit""" + await self._close_session() - while month > 12: - year += 1 - month -= 12 - return datetime.date(year, month, start_date.day) - - def _get_months_between(self, start_date: datetime.date, end_date: datetime.date) -> int: - """Calculate number of months between two dates""" - return (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) async def run_simulation(*, db_session: DbSession, calculation: CalculationData, token: str): - equipments = await get_all_by_session_id( + equipments = await get_standard_scope_by_session_id( db_session=db_session, overhaul_session_id=calculation.overhaul_session_id ) + equipments = equipments[:10] + + scope = await get_scope(db_session=db_session, overhaul_session_id=calculation.overhaul_session_id) prev_oh_scope = await get_prev_oh(db_session=db_session, overhaul_session=scope) @@ -592,30 +1278,34 @@ async def run_simulation(*, db_session: DbSession, calculation: CalculationData, } - reliability_service = ReliabilityService(base_url=REALIBILITY_SERVICE_API, use_dummy_data=True) - spare_parts_service = SparePartsService(spareparts) - optimum_calculator_service = OverhaulCalculator(reliability_service, spare_parts_service) - - # Set the date range for the calculation - if prev_oh_scope: - # Start date is the day after the previous scope's end date - start_date = datetime.datetime.combine(prev_oh_scope.end_date + datetime.timedelta(days=1), datetime.time.min) - # End date is the start date of the current scope - end_date = datetime.datetime.combine(scope.start_date, datetime.time.min) - else: - # If there's no previous scope, use the start and end dates from the current scope - start_date = datetime.datetime.combine(scope.start_date, datetime.time.min) - end_date = datetime.datetime.combine(scope.end_date, datetime.time.min) + # reliability_service = ReliabilityService(base_url=REALIBILITY_SERVICE_API, use_dummy_data=True) + # spare_parts_service = SparePartsService(spareparts) + # optimum_calculator_service = OverhaulCalculator(reliability_service, spare_parts_service) + optimum_oh_model = OptimumCostModel( + token=token, + last_oh_date=prev_oh_scope.end_date, + next_oh_date=scope.start_date, + base_url=REALIBILITY_SERVICE_API + ) - results = await optimum_calculator_service.calculate_fleet_optimization( + # Set the date range for the calculation + # if prev_oh_scope: + # # Start date is the day after the previous scope's end date + # start_date = datetime.datetime.combine(prev_oh_scope.end_date + datetime.timedelta(days=1), datetime.time.min) + # # End date is the start date of the current scope + # end_date = datetime.datetime.combine(scope.start_date, datetime.time.min) + # else: + # # If there's no previous scope, use the start and end dates from the current scope + # start_date = datetime.datetime.combine(scope.start_date, datetime.time.min) + # end_date = datetime.datetime.combine(scope.end_date, datetime.time.min) + + + results = await optimum_oh_model.calculate_cost_all_equipment( db_session=db_session, equipments=equipments, - start_date=start_date, - end_date=end_date, - overhaul_cost=calculation_data.parameter.overhaul_cost, - calculation=calculation, - token=token + calculation=calculation_data, + preventive_cost=calculation_data.parameter.overhaul_cost, ) @@ -628,13 +1318,13 @@ async def get_corrective_cost_time_chart( service_cost: float, location_tag: str, token, - start_date: datetime.datetime, - end_date: datetime.datetime + start_date: datetime, + end_date: datetime ) -> Tuple[np.ndarray, np.ndarray]: days_difference = (end_date - start_date).days - today = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) - tomorrow = today + datetime.timedelta(days=1) + today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + tomorrow = today + timedelta(days=1) # Initialize monthly data dictionary monthly_data = {} @@ -718,11 +1408,11 @@ async def get_corrective_cost_time_chart( # Process prediction data - but only use it for future dates if prediction_data["data"]: for item in prediction_data["data"]: - date = datetime.datetime.strptime(item["date"], "%d %b %Y") + date = datetime.strptime(item["date"], "%d %b %Y") # Only apply prediction data for dates after today if date > today: - month_key = datetime.datetime(date.year, date.month, 1) + month_key = datetime(date.year, date.month, 1) monthly_data[month_key] = item["num_fail"] if item["num_fail"] is not None else 0 @@ -738,8 +1428,8 @@ async def get_corrective_cost_time_chart( # Fill in any missing months in the range - current_date = datetime.datetime(start_date.year, start_date.month, 1) - end_month = datetime.datetime(end_date.year, end_date.month, 1) + current_date = datetime(start_date.year, start_date.month, 1) + end_month = datetime(end_date.year, end_date.month, 1) while current_date <= end_month: if current_date not in monthly_data: @@ -764,9 +1454,9 @@ async def get_corrective_cost_time_chart( # Move to next month if current_date.month == 12: - current_date = datetime.datetime(current_date.year + 1, 1, 1) + current_date = datetime(current_date.year + 1, 1, 1) else: - current_date = datetime.datetime(current_date.year, current_date.month + 1, 1) + current_date = datetime(current_date.year, current_date.month + 1, 1) # Convert to list maintaining chronological order complete_data = [] @@ -1007,21 +1697,21 @@ async def get_calculation_result(db_session: DbSession, calculation_id: str): prev_oh_scope = await get_prev_oh(db_session=db_session, overhaul_session=scope_overhaul) - # Set the date range for the calculation - if prev_oh_scope: - # Start date is the day after the previous scope's end date - start_date = datetime.datetime.combine(prev_oh_scope.end_date + datetime.timedelta(days=1), datetime.time.min) - # End date is the start date of the current scope - end_date = datetime.datetime.combine(scope_overhaul.start_date, datetime.time.min) - else: - # If there's no previous scope, use the start and end dates from the current scope - start_date = datetime.datetime.combine(scope_overhaul.start_date, datetime.time.min) - end_date = datetime.datetime.combine(scope_overhaul.end_date, datetime.time.min) + # # Set the date range for the calculation + # if prev_oh_scope: + # # Start date is the day after the previous scope's end date + # start_date = datetime.combine(prev_oh_scope.end_date + timedelta(days=1), time.min) + # # End date is the start date of the current scope + # end_date = datetime.combine(scope_overhaul.start_date, datetime.time.min) + # else: + # # If there's no previous scope, use the start and end dates from the current scope + # start_date = datetime.combine(scope_overhaul.start_date, datetime.time.min) + # end_date = datetime.combine(scope_overhaul.end_date, datetime.time.min) - months_num = get_months_between(start_date, end_date) + data_num = scope_calculation.max_interval calculation_results = [] - for i in range(months_num): + for i in range(data_num): result = { "overhaul_cost": 0, "corrective_cost": 0, @@ -1034,7 +1724,8 @@ async def get_calculation_result(db_session: DbSession, calculation_id: str): # risk cost = ((Down Time1 * MW Loss 1) + (Downtime2 * Mw 2) + .... (DowntimeN * MwN) ) * Harga listrik (Efficicency HL App) for eq in scope_calculation.equipment_results: - if eq.procurement_details[i]: + + if len(eq.procurement_details[i]) > 0: result["procurement_details"][eq.location_tag] = { "is_included": eq.is_included, "details": eq.procurement_details[i], @@ -1053,7 +1744,7 @@ async def get_calculation_result(db_session: DbSession, calculation_id: str): return CalculationTimeConstrainsRead( id=scope_calculation.id, reference=scope_calculation.overhaul_session_id, - scope=scope_overhaul.type, + scope=scope_overhaul.maintenance_type.name, results=calculation_results, optimum_oh=scope_calculation.optimum_oh_day, equipment_results=scope_calculation.equipment_results, @@ -1115,14 +1806,14 @@ async def get_number_of_failures(location_tag, start_date, end_date, token, max_ raise Exception("Invalid or empty prediction data format.") last_data = prediction_data["data"][-1] - last_data_date = datetime.datetime.strptime(last_data["date"], "%d %b %Y") + last_data_date = datetime.strptime(last_data["date"], "%d %b %Y") results[datetime.date(last_data_date.year, last_data_date.month, last_data_date.day)] = round(last_data["num_fail"]) if last_data["num_fail"] is not None else 0 # Parse prediction data for item in prediction_data["data"]: try: - date = datetime.datetime.strptime(item["date"], "%d %b %Y") + date = datetime.strptime(item["date"], "%d %b %Y") last_day = calendar.monthrange(date.year, date.month)[1] value = item.get("num_fail", 0) if date.day == last_day: @@ -1253,13 +1944,13 @@ async def create_calculation_result_service( # Set the date range for the calculation if prev_oh_scope: # Start date is the day after the previous scope's end date - start_date = datetime.datetime.combine(prev_oh_scope.end_date + datetime.timedelta(days=1), datetime.time.min) + start_date = datetime.combine(prev_oh_scope.end_date + datetime.timedelta(days=1), datetime.time.min) # End date is the start date of the current scope - end_date = datetime.datetime.combine(scope.start_date, datetime.time.min) + end_date = datetime.combine(scope.start_date, datetime.time.min) else: # If there's no previous scope, use the start and end dates from the current scope - start_date = datetime.datetime.combine(scope.start_date, datetime.time.min) - end_date = datetime.datetime.combine(scope.end_date, datetime.time.min) + start_date = datetime.combine(scope.start_date, datetime.time.min) + end_date = datetime.combine(scope.end_date, datetime.time.min) max_interval = get_months_between(start_date, end_date) overhaul_cost = calculation_data.parameter.overhaul_cost / len(equipments) diff --git a/src/overhaul_activity/service.py b/src/overhaul_activity/service.py index 1f26ae1..23773c5 100644 --- a/src/overhaul_activity/service.py +++ b/src/overhaul_activity/service.py @@ -49,7 +49,8 @@ async def get_all( common: CommonParameters, overhaul_session_id: UUID, location_tag: Optional[str] = None, - scope_name: Optional[str] = None + scope_name: Optional[str] = None, + all: bool = False ): # query = ( # Select(OverhaulActivity) @@ -86,6 +87,7 @@ async def get_all( .join(EquipmentWorkscopeGroup.workscope_group) .join(MasterActivity.oh_types) .join(WorkscopeOHType.oh_type) + .join(MasterEquipment, StandardScope.location_tag == MasterEquipment.location_tag) .filter(MaintenanceType.name == overhaul.maintenance_type.name).filter( (StandardScope.is_alternating_oh == False) | (StandardScope.oh_history == None) | @@ -93,6 +95,7 @@ async def get_all( ).distinct() ) + equipments = await search_filter_sort_paginate(model=query, **common) data = equipments['items'] @@ -114,6 +117,40 @@ async def get_all( return equipments +async def get_standard_scope_by_session_id(*, db_session: DbSession, overhaul_session_id: UUID): + overhaul = await get_overhaul(db_session=db_session, overhaul_session_id=overhaul_session_id) + + query = ( + Select(StandardScope) + .outerjoin(StandardScope.oh_history) # Use outerjoin to handle None values + .join(StandardScope.workscope_groups) + .join(EquipmentWorkscopeGroup.workscope_group) + .join(MasterActivity.oh_types) + .join(WorkscopeOHType.oh_type) + .join(MasterEquipment, StandardScope.location_tag == MasterEquipment.location_tag) + .filter(MaintenanceType.name == overhaul.maintenance_type.name).filter( + (StandardScope.is_alternating_oh == False) | + (StandardScope.oh_history == None) | + (StandardScope.oh_history.has(EquipmentOHHistory.last_oh_type != overhaul.maintenance_type.name)) + ).distinct() + ) + + data = await db_session.execute(query) + results = [] + + for equipment in data.scalars().all(): + res = OverhaulActivityRead( + id=equipment.id, + material_cost=35000000000, + service_cost=200000000, + location_tag=equipment.location_tag, + equipment_name=equipment.master_equipment.name if equipment.master_equipment else None, + oh_scope=overhaul.maintenance_type.name, + ) + + results.append(res) + return results + async def get_all_by_session_id(*, db_session: DbSession, overhaul_session_id): query = ( diff --git a/src/overhaul_scope/service.py b/src/overhaul_scope/service.py index f60b964..cb7b88b 100644 --- a/src/overhaul_scope/service.py +++ b/src/overhaul_scope/service.py @@ -17,9 +17,10 @@ from .model import OverhaulScope, MaintenanceType from .schema import ScopeCreate, ScopeUpdate from .utils import get_material_cost, get_service_cost from datetime import datetime +from uuid import UUID async def get( - *, db_session: DbSession, overhaul_session_id: str + *, db_session: DbSession, overhaul_session_id: UUID ) -> Optional[OverhaulScope]: """Returns a document based on the given document id.""" query = Select(OverhaulScope).filter(OverhaulScope.id == overhaul_session_id).options(selectinload(OverhaulScope.maintenance_type)) diff --git a/src/utils.py b/src/utils.py index 9d6aa8f..3dc0219 100644 --- a/src/utils.py +++ b/src/utils.py @@ -120,3 +120,22 @@ def get_latest_numOfFail(location_tag, token) -> float: except requests.exceptions.RequestException as e: print(f"Error fetching data: {e}") return 0 + +import requests + +PASTEBIN_API_KEY = 'G9uCTzoI1CJWCupt5lcZRpijondt5jwA' # Replace this with your actual API key + +def save_to_pastebin(data, title="Result Log", expire_date="1H"): + url = "https://pastebin.com/api/api_post.php" + payload = { + 'api_dev_key': PASTEBIN_API_KEY, + 'api_option': 'paste', + 'api_paste_code': data, + 'api_paste_name': title, + 'api_paste_expire_date': expire_date, # Example: 10M, 1H, 1D, N (never) + } + response = requests.post(url, data=payload) + if response.status_code == 200: + return response.text # This will be the paste URL + else: + return f"Error: {response.status_code} - {response.text}"