Compare commits
1741 Commits
master
..
dingsu/szh
| Author | SHA1 | Date | |
|---|---|---|---|
| 32bbe3ddcd | |||
| 38fb35a333 | |||
| c15924b8e4 | |||
| 92e2122743 | |||
| 3b1a4b4b95 | |||
| 40f3ae02fb | |||
| 065bcded8e | |||
| 2ed288830f | |||
| e4b1648041 | |||
| d745e6f011 | |||
| c27d1cf1d5 | |||
| 7086fe42a0 | |||
| 675b309a0d | |||
| 10cffbbdf1 | |||
| 977c4d6fc6 | |||
| c10fcca46b | |||
| 11a31467fa | |||
| c8b3d6b178 | |||
| 4e29ac11c5 | |||
| d53b5f0409 | |||
| 5ab7246317 | |||
| 641a879b59 | |||
| 17431dc22d | |||
| eb49e2c849 | |||
| 09ca1c43ec | |||
| 4a54659360 | |||
| 058a517280 | |||
| 4673ceb2b0 | |||
| 05f41b635d | |||
| 7e50c2d2ad | |||
| baa53130e6 | |||
| 6e9dea1527 | |||
| 9df472b11c | |||
| 0f471a1903 | |||
| 70affaa182 | |||
| 5ede29efd4 | |||
| 8ad9459459 | |||
| 320735bc85 | |||
| 1e006673e4 | |||
| b587cc7c1e | |||
| 54bf12883c | |||
| cbcbdda3e0 | |||
| 5fc5bc5ecc | |||
| 28eb0ee94c | |||
| 5f3f75a0bc | |||
| 6321146abd | |||
| 16991f37c3 | |||
| 4a080bb409 | |||
| 016722cec1 | |||
| 7edb71c625 | |||
| b8b2490c2a | |||
| 7d08c84d8f | |||
| 1ee0bb8067 | |||
| 17a8d84827 | |||
| d7d7e62d48 | |||
| d4133770c0 | |||
| f816de6426 | |||
| b9cd42ed76 | |||
| e97049eb30 | |||
| 09a55deaa1 | |||
| 6fffba9bc2 | |||
| 4f785aad82 | |||
| 893351ce61 | |||
| d7d714d879 | |||
| 3faea5602a | |||
| 495b6be057 | |||
| 36af0b56e4 | |||
| ab05458747 | |||
| 84ba85ac84 | |||
| 851e016ad2 | |||
| 583c771254 | |||
| 5e1eeb7ace | |||
| 1e6c88dabe | |||
| d679070068 | |||
| 678b98b1b4 | |||
| b055555819 | |||
| 3346a519ce | |||
| 3a6991ea6b | |||
| c4293acb2f | |||
| 3a9737173d | |||
| 2d81182c24 | |||
| 071a23112a | |||
| bcb88775ba | |||
| bf6116aa46 | |||
| 99c19f94f4 | |||
| ce557a7c6c | |||
| 24226123d6 | |||
| cca3363bda | |||
| 511e95427d | |||
| c9d9950512 | |||
| 08b04bfeb8 | |||
| 1b0b0f308c | |||
| 23faeb96e1 | |||
| 74c828cc3f | |||
| 3ba4837c9e | |||
| c55de1d3ae | |||
| 3012ddab06 | |||
| 8dfd56456e | |||
| c7724edd4a | |||
| 3af3efc43f | |||
| d775c1a9f9 | |||
| b8dcec9349 | |||
| 01be4ce1c4 | |||
| 86e06e1717 | |||
| 183c5a2bad | |||
| 17c0d6d634 | |||
| 00b5e0a3bb | |||
| 4448ea34b7 | |||
| 0a66b55a3e | |||
| 291ac846c3 | |||
| de3ac89ab8 | |||
| f0a4b68347 | |||
| 8548f0b261 | |||
| 116817aa5d | |||
| f465147b4a | |||
| ffe9a29d78 | |||
| 97b7d8fd0c | |||
| d7185fbe27 | |||
| 3dd68faad8 | |||
| dae88d5626 | |||
| a6ddc9bb0e | |||
| 355aff61b9 | |||
| 51863b495e | |||
| 565e4d10df | |||
| 4bb40e7428 | |||
| 2c2cf24976 | |||
| 2a9c9da743 | |||
| c704f8a5a8 | |||
| b35d15d244 | |||
| 2d1ff9212a | |||
| ad3a4d9461 | |||
| 5501049938 | |||
| 34d915404f | |||
| 7dca6685d1 | |||
| 9180b9c5cf | |||
| b15a7fa3ed | |||
| c38ebf45d6 | |||
| cd3a221459 | |||
| 3afcb678e5 | |||
| 9f7e26905e | |||
| 7223f4b876 | |||
| b085e4b4de | |||
| 2f8e1f1fc7 | |||
| f7b33904c7 | |||
| 734c1b61c4 | |||
| 25e97c7798 | |||
| 013929e1e5 | |||
| 387e1f5989 | |||
| 2fc204d8ed | |||
| 4a8130a748 | |||
| edc6d6d9c2 | |||
| fbd4069ed5 | |||
| 612c1b922f | |||
| ed11fefbab | |||
| 6eedd0d55a | |||
| e23761195e | |||
| 97f73a3521 | |||
| ab99d9a81c | |||
| f675ec00de | |||
| 4a2f1fc103 | |||
| 82e87cc6bb | |||
| a2ec56018f | |||
| ec4f72f508 | |||
| 52097bb58c | |||
| 510fe520e8 | |||
| 32bc4e0553 | |||
| a9a5b1573f | |||
| 6d23a515dd | |||
| 6bb3c1c22b | |||
| 72a4e8427d | |||
| 3da6650784 | |||
| 70c6d56f95 | |||
| 3f0ed41da6 | |||
| 0169c962d0 | |||
| 2d5b500bf7 | |||
| 22c2351690 | |||
| e9b01a5b50 | |||
| 0384b40baa | |||
| 361d317595 | |||
| a810456448 | |||
| 65ba34ff3a | |||
| 6235383aca | |||
| e4dacba1c0 | |||
| ff112805ec | |||
| db91bcb6f9 | |||
| 9a6de4e571 | |||
| 7aa7b26234 | |||
| 3d68da5af2 | |||
| 14477b6224 | |||
| e4aa0f20b8 | |||
| a8db5d63a2 | |||
| aba807075e | |||
| 7b77013815 | |||
| ab787d22c6 | |||
| 1bddb1656d | |||
| 1b301b5a75 | |||
| f49fdc168d | |||
| 1263146cdb | |||
| 603cb1f2aa | |||
| e07bbb70f3 | |||
| 63a8d0e3f4 | |||
| 36f3382c49 | |||
| a97f39b247 | |||
| 313ad32dcc | |||
| 5867451b63 | |||
| 017f870dc1 | |||
| 4755fac53d | |||
| 4f26ff32de | |||
| 9c5dbe6bda | |||
| 398bc1cb2a | |||
| 26c0ab7d90 | |||
| 0c32b60534 | |||
| 30e1587565 | |||
| 98462635b3 | |||
| 1c72b08095 | |||
| d7f8d1e90f | |||
| e9f305fb82 | |||
| 65d6b6aa66 | |||
| 094d6b690a | |||
| 9afeb7f6e8 | |||
| 92cd2f0bff | |||
| cefba29195 | |||
| 6f06eb43e7 | |||
| fd2d9dda64 | |||
| b8deadbd11 | |||
| 2f14395d1e | |||
| 180935a0a8 | |||
| 39bb19363e | |||
| 501b2933d0 | |||
| e452cbfa53 | |||
| 08586fc012 | |||
| 90eb873ecf | |||
| f795ca29f1 | |||
| 0431f4d82e | |||
| d1687b8464 | |||
| 6e3d149503 | |||
| 1dafaa6dcc | |||
| 94773a59f8 | |||
| b8c08ac89c | |||
| 27fd74dfbf | |||
| ff9c0a0095 | |||
| c1b4a3685c | |||
| dfb247231f | |||
| 5376b150e9 | |||
| 54b4880977 | |||
| a6a0496a86 | |||
| dee3c9c6f8 | |||
| eb267803fe | |||
| 3e7258a55d | |||
| e90a7b205f | |||
| e0b6183ff0 | |||
| 465831946e | |||
| 380a5df96f | |||
| 9b480b709e | |||
| 12dd85be21 | |||
| 99f47c94db | |||
| 4968be4a70 | |||
| 904761a7f1 | |||
| 056fa48a90 | |||
| 7e19ef83ec | |||
| 725cb7aa1e | |||
| 837c262f91 | |||
| f799630e43 | |||
| 49be1c7089 | |||
| 4a824707de | |||
| 0f18355a9c | |||
| eedd04549d | |||
| a6ebd79dae | |||
| a5c81b368e | |||
| 21966d5669 | |||
| 189ad0bf8d | |||
| 55bab8cf60 | |||
| 51d45ef192 | |||
| 3f5b7e2319 | |||
| 97ff500422 | |||
| 9ab56c941a | |||
| 3c8b308419 | |||
| cbcf964c23 | |||
| 360a3eae94 | |||
| 434805029b | |||
| ea09d71604 | |||
| 8c930f1f7d | |||
| 98739b2510 | |||
| 60d6437777 | |||
| 0132ae1a79 | |||
| aab5de2840 | |||
| bf8f7d08e1 | |||
| 790db21a8e | |||
| 9c24dab4a3 | |||
| 7108e8f70d | |||
| 442dab54da | |||
| db4718e72b | |||
| eeedcca586 | |||
| fe1fa97000 | |||
| 62fc0e1271 | |||
| b3351ba6f8 | |||
| cfd87d82cc | |||
| 38f3e3a41a | |||
| 45fb4f268b | |||
| 2d6f8d8d68 | |||
| e56e3e0078 | |||
| fb2f6aee80 | |||
| c1a6242d7d | |||
| af55aca469 | |||
| 62ca3fd206 | |||
| bdefec8c9f | |||
| 317217b5f3 | |||
| d404809111 | |||
| 4058ddaed7 | |||
| 89f87aa63c | |||
| 22ffaaf410 | |||
| 86da9b2c60 | |||
| 5bc509ed7b | |||
| 0de513cda4 | |||
| 80f1885194 | |||
| c3510b3c2c | |||
| 32b7ae9f5c | |||
| f70ec0821b | |||
| fceedf9cba | |||
| f4f6bc559c | |||
| 40bc1491cc | |||
| 6f1a0ec694 | |||
| ef2ece752c | |||
| 48d99c6bb0 | |||
| 8701cf4b0c | |||
| a3e5d55ed8 | |||
| e8e8c115d5 | |||
| 78cdc0af7e | |||
| 176d6f00cd | |||
| e67f487737 | |||
| c146f68fc7 | |||
| 9a541b0d73 | |||
| 5b51ec147c | |||
| cc6e2819e8 | |||
| 071f6ec100 | |||
| 8aa831c53e | |||
| 33252b721c | |||
| f249f0c7b0 | |||
| 6c3dc95954 | |||
| 8045e6e79f | |||
| 2000385de9 | |||
| 638406af48 | |||
| 8afdc62259 | |||
| 7b3fd12b4f | |||
| b42fa6c551 | |||
| 43db502aa0 | |||
| 5cbcf83002 | |||
| 14d9fd0f4b | |||
| ced065a455 | |||
| 637e05acdb | |||
| 92363c0b40 | |||
| 246c6592ed | |||
| 65d2031889 | |||
| f2f1660b29 | |||
| 346187329d | |||
| 0903e8c6a5 | |||
| 2cf4ce89ea | |||
| 05ea9e7045 | |||
| d8d3157d43 | |||
| b905c0316d | |||
| 3ad3d36b2a | |||
| fc2635ada7 | |||
| 011b8fa326 | |||
| ad029b1c92 | |||
| 0020a95f47 | |||
| 6e952f62b6 | |||
| d2dc114c9c | |||
| d9cb775540 | |||
| ec1dde7d43 | |||
| 5133b93c1f | |||
| acba5ef8c2 | |||
| 0884b95a81 | |||
| 4f5cdb42db | |||
| c1ba35e1b2 | |||
| f098aa42ae | |||
| f728ace4a7 | |||
| 0b66c22807 | |||
| 99cf111117 | |||
| 90b46de3dc | |||
| 47b3b8644d | |||
| e818522de8 | |||
| 6d5a70deb3 | |||
| 2d3799f10e | |||
| 30c4e40180 | |||
| afa26a561f | |||
| 05569f8859 | |||
| de0b4403d4 | |||
| bd21d71be7 | |||
| 144f786f39 | |||
| 3ef178e5b0 | |||
| a2d6391d9a | |||
| 9d0c9df18e | |||
| 8485f8d3fc | |||
| 3c9db341c8 | |||
| 65e3d7bc40 | |||
| b138d15efb | |||
| 90329966f5 | |||
| de9e9f9fb3 | |||
| 83826aa85a | |||
| 7892f8ac20 | |||
| cd35519e62 | |||
| eb801660c8 | |||
| 36e1692ccf | |||
| d14666fcff | |||
| cfce9acba9 | |||
| 7d28bd8632 | |||
| b6851ac321 | |||
| e591eba2f5 | |||
| ecf37ae50b | |||
| 30d1688c11 | |||
| 0a6d69d5de | |||
| 5bb4237010 | |||
| 0c74389874 | |||
| 305c047f20 | |||
| 373fc1facd | |||
| 0d4128c8c8 | |||
| ae60649726 | |||
| 57d11aefa3 | |||
| 7e075cb02d | |||
| 7151476709 | |||
| 64389c8a81 | |||
| 7737b4a190 | |||
| 9dc8481b3a | |||
| 953140b6ff | |||
| f7f905fdf4 | |||
| 4bfbf3ac34 | |||
| 5cf7efc3a9 | |||
| d3e2961c60 | |||
| e8c6dfb19b | |||
| 486471f3e8 | |||
| 294fea9aa8 | |||
| f9546ebf4d | |||
| 7dfeee8c22 | |||
| e02e78d2cc | |||
| cfa5df6dc4 | |||
| ef1786a4c8 | |||
| 40a79ce590 | |||
| c1878e4375 | |||
| 575c7b9dc9 | |||
| b48e67b47e | |||
| a20a1f63c9 | |||
| 14358fc533 | |||
| 23044f1338 | |||
| a7b15568aa | |||
| 86e894e783 | |||
| 13acaf2572 | |||
| bd9ed05e13 | |||
| 0decc88d49 | |||
| ec532f75c8 | |||
| cb9f7c72a1 | |||
| c4df07e081 | |||
| 8cce40ffdb | |||
| 7d8562ae38 | |||
| aed90cfc7d | |||
| bc0365144c | |||
| 895d4d2031 | |||
| 93aaf7d553 | |||
| 8745192589 | |||
| c3a51020d9 | |||
| 998d16bec7 | |||
| 0d7246b8d4 | |||
| 0f18a85641 | |||
| 9e58ca9254 | |||
| 7cd9493fa3 | |||
| fa435b105f | |||
| a8ca7e9a19 | |||
| 2d681c8349 | |||
| 9a274b0c67 | |||
| 8384f0720d | |||
| bf4e589c4e | |||
| 842abaceb2 | |||
| 4e5c9194f5 | |||
| e6a3fbd13c | |||
| 86cf0b1147 | |||
| 6847b35813 | |||
| 8c16a090ec | |||
| 1a23667b1b | |||
| 13cdae6362 | |||
| 460aa79224 | |||
| 7db2b2c0ac | |||
| e66a3dd7df | |||
| de40ac7c4f | |||
| 528c660ddc | |||
| 21a6c54f58 | |||
| b18be90207 | |||
| 3df6dfe247 | |||
| 60ae213d15 | |||
| 65c61ac35c | |||
| 5b18ec816e | |||
| 64760b62c4 | |||
| f7d612d522 | |||
| 4872772165 | |||
| 4bf2ef12aa | |||
| a17a0b3a89 | |||
| 5ceb893ad2 | |||
| 137c74348b | |||
| f8422a6ec0 | |||
| 7eddd95947 | |||
| bdcd505733 | |||
| 59875effbf | |||
| e3e46b48e1 | |||
| 29841ddf6c | |||
| b60e0fc1dc | |||
| 0015152c04 | |||
| 24083d38d1 | |||
| bde762e64e | |||
| 9671989ed3 | |||
| d395a68935 | |||
| 1bb14ad06b | |||
| 1131f7b1f9 | |||
| fa643aca49 | |||
| 9beaf72502 | |||
| bf99e7f347 | |||
| daf392f362 | |||
| 27388eb96a | |||
| 0eb190aa09 | |||
| 20d3566f30 | |||
| 3e873fad78 | |||
| a19b3c5f55 | |||
| a0793a9494 | |||
| 8a1795481a | |||
| 44b4ad2bd0 | |||
| 6b1b15f28c | |||
| d8c8eb1002 | |||
| 71e4af59eb | |||
| c7434c3bcc | |||
| c5fc5ed9ce | |||
| 60fd141b3c | |||
| e360540989 | |||
| f6f37d012b | |||
| 8e3f62e06d | |||
| 1279083303 | |||
| 6a507dca7b | |||
| 2ec2e10ff4 | |||
| 15147b97fc | |||
| c40903289e | |||
| e16d138c2c | |||
| 5e8d738a27 | |||
| c51b8524b0 | |||
| cf335052cf | |||
| 83d73a96bd | |||
| 49e85d51e8 | |||
| 44704539a4 | |||
| 8751347103 | |||
| 08a4d4d2bb | |||
| 15b560dca4 | |||
| 49a14a8343 | |||
| 03c4e5772d | |||
| 7803164f47 | |||
| 5f9b5bd310 | |||
| 1996ffd17d | |||
| ad99d75548 | |||
| 45025e9988 | |||
| 8e9859e2c1 | |||
| 35c3bf986c | |||
| c088a87d28 | |||
| a55d53a60b | |||
| ca06f31e0a | |||
| 7884d0e562 | |||
| 32a4c15b6d | |||
| ab18c801e9 | |||
| c2417c7ed9 | |||
| 02de084fb6 | |||
| f9a70eca1f | |||
| 8d82ffa234 | |||
| e3fe2ddf51 | |||
| 71bbad3862 | |||
| 5da8fb7c07 | |||
| 436a2f299e | |||
| 167fb434a2 | |||
| d133ebba84 | |||
| 618a374a29 | |||
| 7bf31af7bb | |||
| ab0e674f8c | |||
| 29883cc123 | |||
| 8e4574b167 | |||
| 97cd59cfae | |||
| 1193a4fe30 | |||
| 97d1022460 | |||
| b453779fb2 | |||
| 1b6ce083cd | |||
| 362439c52a | |||
| 01ffe3bde7 | |||
| 102f97b020 | |||
| 98e58d1e53 | |||
| cf2c90d5f9 | |||
| ff83c9a974 | |||
| 9bd4e5b84b | |||
| 9f49d17027 | |||
| 670b4afe6b | |||
| a88e79cb86 | |||
| e024e8b904 | |||
| ced0f50420 | |||
| 6f0ad1baee | |||
| fa00a5ff81 | |||
| 9e7a4616b4 | |||
| 51e3ae18bb | |||
| 212c9e7182 | |||
| 1d09d41935 | |||
| 69978c9868 | |||
| 4e9c5ed551 | |||
| 26f6c4436f | |||
| 27485ef8fc | |||
| 65b5c249a7 | |||
| cad6eeba47 | |||
| 17b47da6ea | |||
| 97b69eb5d0 | |||
| 0e44625f00 | |||
| 147f88debd | |||
| d8c964308b | |||
| 66e5dae590 | |||
| 0b74a2ece9 | |||
| c6629f9a33 | |||
| 70afb19184 | |||
| f8ac46d80d | |||
| 1e43cdb17a | |||
| 41e80212e1 | |||
| c7d51769e1 | |||
| de5ae4d1e5 | |||
| 8b818185ff | |||
| 000321c09c | |||
| c007685eaa | |||
| 103e863780 | |||
| fa26c46d7d | |||
| 41fa93cace | |||
| f8655fe8b6 | |||
| 73598b7b38 | |||
| 8d6d7b5c59 | |||
| 65c9913e83 | |||
| 9e855b4584 | |||
| dc233db163 | |||
| e8fddfb71a | |||
| 330248041f | |||
| 44e487f85f | |||
| 0976ba11af | |||
| 0510d42bbe | |||
| 4389b4ea74 | |||
| 46f898a000 | |||
| c1a5759220 | |||
| 5e80f35ae5 | |||
| e1b1f8d8ce | |||
| 20da69c7a6 | |||
| d29a1a1a1a | |||
| 6c97abdd16 | |||
| 0f7334bdaa | |||
| 2c4a4365fe | |||
| 44edeffad9 | |||
| 37f5210ab9 | |||
| 8e7b3ebc1f | |||
| 8e51c437c4 | |||
| d105557533 | |||
| 1a0bc30785 | |||
| 6f22063a72 | |||
| f29047f535 | |||
| 0151fb3424 | |||
| 855cf6758a | |||
| 34a7ad1a12 | |||
| 772aea84bd | |||
| ffb5dc7854 | |||
| 152fc9d71b | |||
| 1c7cd33f22 | |||
| cde86d4281 | |||
| 94597a2125 | |||
| c925e2f1fa | |||
| d996a364e5 | |||
| 788c6cd1bc | |||
| 664bad41fa | |||
| 925382b379 | |||
| 157f14868b | |||
| a3157b270f | |||
| 12b12a9d5a | |||
| 41e959e4da | |||
| ff7b1c921d | |||
| a8dd94bf20 | |||
| 782ec758ee | |||
| adc432b6c1 | |||
| 540d726c11 | |||
| 6bb046e1ca | |||
| 122b5462a5 | |||
| bf04c6493f | |||
| 890f8d9cf6 | |||
| 86242b13d2 | |||
| d32a7a46d6 | |||
| 9fa761e1cd | |||
| a98a44b2a4 | |||
| 769ab7d77c | |||
| 5f23ea9fad | |||
| a088165017 | |||
| 734f394d25 | |||
| bb9471321d | |||
| 3ed7e5a8bf | |||
| 8ac8822563 | |||
| 48cdf8f0dd | |||
| 6c345a29dc | |||
| 0457492b21 | |||
| f016182562 | |||
| d190a838bf | |||
| 0de74cc892 | |||
| d2d2948285 | |||
| c5d8b3e993 | |||
| 6f60d68cbb | |||
| b45c1261f2 | |||
| cdb37db2a2 | |||
| b2eb17f87e | |||
| 6c3e500eb3 | |||
| 3c8f092259 | |||
| a3f88c61fa | |||
| 72b3884c28 | |||
| 7efaf272c7 | |||
| 897dd72a07 | |||
| 12953069f1 | |||
| fb347ba03a | |||
| 4c6e257d1d | |||
| 9d90643b3b | |||
| 1604f98b95 | |||
| 7f31baee3c | |||
| daa6ecc82f | |||
| 6a35a41f51 | |||
| 80fc13b74e | |||
| b0ad14e567 | |||
| 5a96c24249 | |||
| a4f477f8eb | |||
| 2b424e4710 | |||
| 2c5ca5ef53 | |||
| 83f4bc063b | |||
| a631b36694 | |||
| c55d9b0204 | |||
| 2bd314cc91 | |||
| 54adc4dc15 | |||
| 07d144abec | |||
| 05c34e33c0 | |||
| 6dbf701318 | |||
| 491e70f79f | |||
| 61afe5522b | |||
| 905dd52198 | |||
| 634f9da5e8 | |||
| 217a59cdfe | |||
| 6c9ef0efa2 | |||
| afeeed6403 | |||
| 1cf4c40274 | |||
| 88f022d6df | |||
| 62c6ee1349 | |||
| eb07ab775c | |||
| 0e056e9507 | |||
| dda6b45170 | |||
| 4469501010 | |||
| 99c82fcf15 | |||
| 636d462ce3 | |||
| 8542b7b75d | |||
| 4e14b9398d | |||
| 2cab278c9f | |||
| 7218fc5db7 | |||
| 6046cdf01a | |||
| 76f3021d46 | |||
| 2dab018ec5 | |||
| ee56ad7410 | |||
| 19ed81e2b5 | |||
| 22e8798bd2 | |||
| 4f312a9ff2 | |||
| ee8178919b | |||
| 91976172a5 | |||
| c17629ee28 | |||
| 47d2fe9ddd | |||
| 9240708d04 | |||
| b00c870a89 | |||
| 58fd285d25 | |||
| 1bc73f5bd5 | |||
| 3247794d5b | |||
| 28cce528e7 | |||
| 16cb5ccb82 | |||
| 48f4e58029 | |||
| ee3100a61d | |||
| 553dad1d2c | |||
| a4ea72fffa | |||
| 48ecdf7285 | |||
| b06d8260ff | |||
| 6d30bf7d70 | |||
| 99775af649 | |||
| b425881202 | |||
| 4b72a4855d | |||
| d5a2e006a1 | |||
| 3114a0a92e | |||
| 0ed1bb6af2 | |||
| b4f3aa2c1b | |||
| 7054dd5457 | |||
| e6ec0018a0 | |||
| 69c5bbcb71 | |||
| 594a6b97f4 | |||
| eacc3f247b | |||
| a9c8f3c04c | |||
| 40f40f7a05 | |||
| 7679e3839d | |||
| 1d2a53e77d | |||
| ad21941239 | |||
| e587f8cf11 | |||
| 95078b23ec | |||
| 6c446afe88 | |||
| ca9e983911 | |||
| b75a717f95 | |||
| 49a9865eb2 | |||
| 28ea0e3778 | |||
| b3bdb03ab5 | |||
| 6794d02ee0 | |||
| b8df831d71 | |||
| c3898576e7 | |||
| 74b667c7ca | |||
| 32c47bb2f2 | |||
| 3d5bbe9e6f | |||
| 132eb036fb | |||
| 22f548beb9 | |||
| eda4c79cfb | |||
| a44f3cd18c | |||
| 27945c3e88 | |||
| 52d98063a4 | |||
| 64dcc8b800 | |||
| f5afb5fb6c | |||
| 440104d028 | |||
| 6dea276464 | |||
| 948f5de767 | |||
| 7b736f9812 | |||
| 126e05cd59 | |||
| adeb9dd031 | |||
| 4939312756 | |||
| 24ce579a7f | |||
| 91fd53ed23 | |||
| 3a8bb84617 | |||
| 62b086d939 | |||
| 4ca41aed33 | |||
| 52ff615f4f | |||
| ec6c238cf0 | |||
| 050325beda | |||
| b64605777e | |||
| 0398b3a954 | |||
| 19512339ac | |||
| 8bceb014c3 | |||
| 19cfd21e81 | |||
| 9626473b76 | |||
| 068281b929 | |||
| 599e221712 | |||
| abcc450781 | |||
| 96e61224f1 | |||
| 8a1a49bf6b | |||
| 06c8f366fd | |||
| 98b6354165 | |||
| 2abf861584 | |||
| 703a2db724 | |||
| 7abae71351 | |||
| 7a03c0aa09 | |||
| 7a1a8a3a7b | |||
| 0656dbe83b | |||
| 23cb1fe0e8 | |||
| ea60c36b4e | |||
| 9b53f7b51f | |||
| b3a630c5e1 | |||
| e30c81a891 | |||
| 45296960e7 | |||
| 8d49db2942 | |||
| a4dc2d62cc | |||
| 493b46fd4e | |||
| 93ec527fff | |||
| e033a3c3c7 | |||
| c4b01fc48c | |||
| 5376ead768 | |||
| 3c233f6e9c | |||
| 04a3d0057e | |||
| 538284f502 | |||
| d66087225f | |||
| a3e8f693d9 | |||
| e407f8ccd7 | |||
| 2cff3d0a2e | |||
| a15ccecaa7 | |||
| 4b452c5f0a | |||
| 35d2ce08e1 | |||
| a0166193ef | |||
| a278335bbe | |||
| 12150ef57e | |||
| c6518cbae1 | |||
| a62700d27e | |||
| 0ac4a88295 | |||
| 089a12e137 | |||
| 23cc2b2df5 | |||
| c3cd69010b | |||
| 3808d72e32 | |||
| 6eb4b34de0 | |||
| 83b7177612 | |||
| 83873b38f3 | |||
| d315dc736c | |||
| e320148e99 | |||
| 8b02b13e7e | |||
| a5cd90e3f2 | |||
| 29c70d2a48 | |||
| c0a645ce24 | |||
| e4f71b01cf | |||
| e59dac81ec | |||
| da80d689ed | |||
| a83b5bf994 | |||
| 6976aca0f2 | |||
| 3b0f7cfc98 | |||
| 1dcfa4f5be | |||
| f910b07a41 | |||
| 87d3f3688a | |||
| 450fccee51 | |||
| bfeaa9f8c0 | |||
| 098ba98a7c | |||
| 8d15df4a3b | |||
| 9d6b685439 | |||
| 02b5ef71f2 | |||
| 2e4ae1bd9f | |||
| b0c90fcc07 | |||
| e57721a29a | |||
| dcecce165e | |||
| 5f64439f9f | |||
| 453f3cbfe7 | |||
| 900e0cd0ba | |||
| a579272ea9 | |||
| 8de01a3e02 | |||
| 71f7dbc233 | |||
| 1f2733aa90 | |||
| f13d32bb0a | |||
| 610e7ee561 | |||
| 924dbc802c | |||
| 976361a3bd | |||
| 68f30cf171 | |||
| e0b6ce7559 | |||
| 43043556dd | |||
| 55e52ff4fc | |||
| f6e7d88799 | |||
| 836df97865 | |||
| 8c70af523d | |||
| e6eb137077 | |||
| 6287dcd1fe | |||
| 9e608dffe6 | |||
| 0f1d2d5435 | |||
| 7451cf9ddd | |||
| f44876427d | |||
| 069e9ac4c5 | |||
| 7446f89a34 | |||
| e3a7893d29 | |||
| dd31a04cdf | |||
| bcdaddc5cf | |||
| c6d74e3cad | |||
| 19a5b860b6 | |||
| 4c4a03fcb0 | |||
| 7a2a52b920 | |||
| 26b7f089fd | |||
| 39e2316631 | |||
| da796f0c7f | |||
| 6a304ba7e0 | |||
| 236fcf0612 | |||
| 3afb24c816 | |||
| e68154ea0b | |||
| 5e69ada90b | |||
| b9aa6c3e71 | |||
| b49b916ff8 | |||
| 34750b729d | |||
| 9d2d7ea51f | |||
| a59bb224ba | |||
| 7bcba430ce | |||
| 4366125647 | |||
| 77a73f5532 | |||
| cd39018cbd | |||
| 00c0a63720 | |||
| 15536a2113 | |||
| d32c6e9fdb | |||
| d8dff0118b | |||
| f2bd39a35a | |||
| 7727591667 | |||
| 6f9d45d982 | |||
| b9c8856528 | |||
| c93298a1b2 | |||
| 9a65990278 | |||
| a73cd3deba | |||
| cf302487d3 | |||
| 9c2e314416 | |||
| a8278e50dd | |||
| fe0819654b | |||
| 0db93b0637 | |||
| faec3cef73 | |||
| e18c504067 | |||
| 0fc34ae1ff | |||
| 398ba09106 | |||
| 6ed7e00e03 | |||
| 9f9d3227b9 | |||
| 8681a56ed7 | |||
| 3a8a6d0ea3 | |||
| 64521182ca | |||
| da4afe4a92 | |||
| def44d80b3 | |||
| 977e706a05 | |||
| 04a58bb864 | |||
| 1d2ac09c92 | |||
| 1d720c5b6c | |||
| bf1aeff1fa | |||
| b35fccad49 | |||
| f79a9cdae8 | |||
| 617c66d0cd | |||
| 81c4e0308b | |||
| 4e6864b2fc | |||
| 2f87d87233 | |||
| ed6f6daca0 | |||
| 24a39aeca6 | |||
| 696bd55ead | |||
| 5eb414d5e7 | |||
| b3db5bd027 | |||
| 1fda14c3cf | |||
| e41abe362f | |||
| 55febbe163 | |||
| 96cb99b87a | |||
| 369e636e13 | |||
| a2e4649be5 | |||
| d2d27c2ce0 | |||
| 5e6b3d02df | |||
| 28b2449487 | |||
| 41f7978788 | |||
| e06506d5dd | |||
| 0e1522f8e6 | |||
| 402451eb71 | |||
| a9008b3a86 | |||
| 6fa23ac253 | |||
| 1e51a52e1a | |||
| 6babe1e84c | |||
| 6a9b4ec87c | |||
| 9d5db44b22 | |||
| 690e3aa09a | |||
| 3c3c29de90 | |||
| 7c9eb1555a | |||
| b20dc6ab8a | |||
| ca1a065d54 | |||
| 11a3cb7a78 | |||
| dde045b6b8 | |||
| 04986a1363 | |||
| 998e81aed3 | |||
| 5ad42429e5 | |||
| c813c3d6fb | |||
| 9ad47d27cc | |||
| ece98ae07c | |||
| e9fa51cd4e | |||
| 7d47dcd1b6 | |||
| 6b1c5a8269 | |||
| 71976f42fb | |||
| 6951ffa7c7 | |||
| 3f3b2508b1 | |||
| 1e300ae258 | |||
| 7f23208022 | |||
| 43e0f8189a | |||
| b93fcb22e9 | |||
| 866019e6cb | |||
| 0344e262b4 | |||
| 1a050eb9c0 | |||
| 0ecc706000 | |||
| a6627c0e34 | |||
| 406d88a6b8 | |||
| 2f395986c3 | |||
| 844e86b34d | |||
| b2431d4ed9 | |||
| d514439bd2 | |||
| 013a86ac3a | |||
| fa79522054 | |||
| 3665e15c92 | |||
| 628258ef52 | |||
| bb3cd2bb7f | |||
| e83d3a83a5 | |||
| cf76427562 | |||
| fe04ccd9d0 | |||
| de48af215f | |||
| 0fd75701f0 | |||
| 1f85a52378 | |||
| ecbd4134fb | |||
| 69c25bb008 | |||
| ee829c0731 | |||
| 2f66ae4dff | |||
| 2e5705e2f2 | |||
| 0e8388859a | |||
| 87c9790d3b | |||
| 98cf6277a8 | |||
| 9a12091691 | |||
| 2abc190a65 | |||
| 0b9ccf5d5c | |||
| b35a0865b9 | |||
| 6b956e6c09 | |||
| ed23f119f9 | |||
| 97c409535f | |||
| 3f7bfb6554 | |||
| 7aae885ef1 | |||
| 1e5cfbe595 | |||
| 0fb621112a | |||
| aac46ac056 | |||
| 6645c7341a | |||
| db8329aa70 | |||
| fd03105614 | |||
| cef631b41b | |||
| c3ae9b306f | |||
| c5c526286b | |||
| 39dfe2bd98 | |||
| 2199f54428 | |||
| 26ca2eba02 | |||
| 52c0e8d624 | |||
| 127a6ed606 | |||
| ebe58aef14 | |||
| 064b165678 | |||
| e88478fc13 | |||
| 68b42ef2a0 | |||
| 178ab15318 | |||
| 28480500d7 | |||
| 1ce85146d5 | |||
| 78f56ac025 | |||
| 7556f5e1c2 | |||
| 1984c537a1 | |||
| 366f75c78b | |||
| b40e8862a7 | |||
| 83f64963f4 | |||
| 8f55b21275 | |||
| 3c7903e742 | |||
| 11a5bb4333 | |||
| 93f5321c89 | |||
| e62107da07 | |||
| 6ba4dc2ed7 | |||
| 61dfd0ffc0 | |||
| 8c21ebb48c | |||
| e5f67af36b | |||
| 62a1303b8f | |||
| 5edd986ce6 | |||
| 14c57bb313 | |||
| e18a2a529e | |||
| 1c5de25694 | |||
| de6e4284d7 | |||
| 1520847813 | |||
| e0d8649839 | |||
| d04dea6b60 | |||
| 9033c2731d | |||
| 8979885eac | |||
| c5f269ac5b | |||
| 7f9c9a8e8c | |||
| 61ce7f7744 | |||
| 73ec2faa97 | |||
| 719e63e572 | |||
| ae793d8ab5 | |||
| 133ba32cdf | |||
| d3e8e63e1b | |||
| 41b923a838 | |||
| 2d0598ca5d | |||
| 6e6fc67677 | |||
| 685c8a3a33 | |||
| 63175956ef | |||
| 15d14765bd | |||
| 2c584f1a15 | |||
| b3b59eb4e0 | |||
| 4f83e59f0d | |||
| edc6d423ec | |||
| 32d81cb6f2 | |||
| 517c8dd261 | |||
| 0d5502a752 | |||
| 804e1c7796 | |||
| 633a616d39 | |||
| 591b52a718 | |||
| 4f103be303 | |||
| e1bc826ab6 | |||
| 58959786c6 | |||
| 4ab35709a8 | |||
| 4238a550f9 | |||
| fde25105d7 | |||
| 7a784d12c5 | |||
| b715f330dd | |||
| dcf3398d47 | |||
| f6c14f1c81 | |||
| 6dce827f1c | |||
| 08022bab27 | |||
| a799e30b8c | |||
| 4bdff4fc1d | |||
| 21c820cdd3 | |||
| 78bb270c05 | |||
| e140ce14c6 | |||
| 316966b202 | |||
| ce7139a9e9 | |||
| 5d9323cbf2 | |||
| 26086596b1 | |||
| 8be14377c8 | |||
| 251d544978 | |||
| 832e06108b | |||
| 859ad5403c | |||
| 6ff2436cea | |||
| 9118d67a14 | |||
| f571a36d34 | |||
| fc046358c7 | |||
| 008cd2b3a2 | |||
| eb74e641e2 | |||
| e13bb53b98 | |||
| fc71b5f7b2 | |||
| 181ecf0cd5 | |||
| f3db2657ba | |||
| 0a0e71b429 | |||
| 9396b58638 | |||
| e002c1d653 | |||
| 407f8eaf99 | |||
| 2a470446b5 | |||
| 5f5cf961ef | |||
| 3ec966d491 | |||
| 110def2c05 | |||
| ad96003e8e | |||
| 410f0daca0 | |||
| c314eaed6d | |||
| c2a62bb297 | |||
| 80b88976a4 | |||
| e2a0f6f95c | |||
| 94e35851cd | |||
| 25b5de9e2d | |||
| dbc8b04114 | |||
| 0d946af50b | |||
| 0b4d01d2ad | |||
| 36d121a425 | |||
| 5faab15da6 | |||
| 74509acc50 | |||
| 2c43e5ad6d | |||
| eea0001458 | |||
| 62a84f571b | |||
| 2897770d4f | |||
| 02c1defba0 | |||
| b6a792f7b4 | |||
| b45fb99cea | |||
| 1c612108b9 | |||
| ec062ace48 | |||
| 7b0e79fb9a | |||
| 1b64dce365 | |||
| 4c40824b9d | |||
| 971c07e2f2 | |||
| 72a17888c9 | |||
| e8ef643c77 | |||
| 000e7f5dd8 | |||
| 6b3798dd5c | |||
| 1ec72bc220 | |||
| d144b83a61 | |||
| 5046f7d246 | |||
| 9d4cdfb915 | |||
| c077598a3e | |||
| bb9852cd8d | |||
| 32ade1ed5f | |||
| d73b90c20a | |||
| 55a511f7fd | |||
| c0a79f49d8 | |||
| 191b270804 | |||
| 587dcd49a9 | |||
| 6a9221df7b | |||
| 9af20f6967 | |||
| 72bd9eb4b0 | |||
| 37d7b0192b | |||
| 315e94f4ad | |||
| 2c1d323cde | |||
| 0b180af5d5 | |||
| 7c3d274817 | |||
| f164e10619 | |||
| 7e7592077f | |||
| c620faadca | |||
| 661f05ca46 | |||
| 53ffcb6e0f | |||
| 7a1462e547 | |||
| 2e5f2d8d0b | |||
| 2781d75ceb | |||
| d7cebd6e63 | |||
| eb19b193bd | |||
| b247f8cc0c | |||
| d4acd75955 | |||
| dd78371f9a | |||
| 1e2f19e3c8 | |||
| 1ee4a83d4c | |||
| 466903c900 | |||
| 1c63ee0d49 | |||
| 1193b656a4 | |||
| f59c6e4d52 | |||
| 1348c6626d | |||
| 0b5ce70966 | |||
| 0acf2d98ed | |||
| 4f619c649f | |||
| 3c254aca7d | |||
| f0c0577ed3 | |||
| 285e6d3b9c | |||
| 7a85867483 | |||
| b97d795484 | |||
| b2d0a727d2 | |||
| 2cd78ceaf0 | |||
| 6e84aef52a | |||
| 4837d4ee45 | |||
| bc030fe8dc | |||
| eacbca6eda | |||
| 98d5ffe58a | |||
| 0fa51935d6 | |||
| 7f691de6e7 | |||
| 4b1943ce25 | |||
| e4023717a5 | |||
| 1865d111df | |||
| 2f8f7c4918 | |||
| 2002b1f246 | |||
| 3c9192b8d9 | |||
| d190f790bb | |||
| 02813e84a6 | |||
| 9c0cbc885b | |||
| 11e732f9b1 | |||
| 45c69033a8 | |||
| 2e15bdce4b | |||
| 2891c1a4db | |||
| 08fde4a2d4 | |||
| 75a825da37 | |||
| 8e85928827 | |||
| dff73445c5 | |||
| b0815f75eb | |||
| f7dea58cbe | |||
| e5c5f6a1e5 | |||
| 106db09474 | |||
| 41ba0e4c71 | |||
| 1534301618 | |||
| 6bd9afdc2e | |||
| 7648b2d116 | |||
| 1dae56939f | |||
| 27c112d1fb | |||
| 30131ac1d7 | |||
| 0d3f9e7244 | |||
| 26c4fc431b | |||
| 236c0f9f9b | |||
| 9d7420df8f | |||
| d54ce2df71 | |||
| 375001acd6 | |||
| 8cc6f60398 | |||
| 22545f5626 | |||
| 94f5fae812 | |||
| 2403a1a75d | |||
| fe3345316a | |||
| 8fa9fc5b98 | |||
| c160ae974a | |||
| 2bd17d6dd7 | |||
| 73cc6f7e62 | |||
| e9b1aaa539 | |||
| 48f14c6dad | |||
| 801f5bb878 | |||
| fb9bef103a | |||
| 749361fd47 | |||
| 6e433e7120 | |||
| a7140813a0 | |||
| 69eb6660fb | |||
| 047de0b792 | |||
| 356c34551e | |||
| 67c95069c1 | |||
| 1631592b0a | |||
| 0b4b81cb5b | |||
| 3d6d8995ef | |||
| 14a7bd3cf1 | |||
| 453e6391e2 | |||
| cd857009ff | |||
| 026e582b84 | |||
| a715b2a9ee | |||
| 9d6b7f2b4a | |||
| cd9f2985a4 | |||
| a4b4634bed | |||
| 3a9b0d2dbc | |||
| 2e4661d716 | |||
| 0dba1e0b6d | |||
| 2d51c4494c | |||
| 8c7b1f63d7 | |||
| 3cd0a88eba | |||
| c31cfd319c | |||
| 21474192be | |||
| 03ac80a285 | |||
| 9b40220477 | |||
| 5de5f2f9dc | |||
| 0e45c8e825 | |||
| 36f5112f32 | |||
| f9de76ee1d | |||
| 91ae7608a3 | |||
| a5818ec463 | |||
| 1d465b8a64 | |||
| 789136a881 | |||
| 865d1d2cf1 | |||
| 47e029e118 | |||
| 63e1b31c76 | |||
| 6cf57e4850 | |||
| 851421b14b | |||
| c697fb158b | |||
| e8abbef5a6 | |||
| a9704fe4b1 | |||
| 8bd59827e4 | |||
| 23ce9d73d0 | |||
| 7860faadd7 | |||
| d3319a715f | |||
| 1732927f6a | |||
| 39b53a3713 | |||
| 019c0e12f1 | |||
| 7f14b541ed | |||
| 42eebf5bd3 | |||
| 1109ca4b6d | |||
| 44475cf005 | |||
| a04dcdd1ee | |||
| ba7d99eba7 | |||
| e0e209ddd1 | |||
| 2f26cb58ad | |||
| 1e41be4ff9 | |||
| f68ff0f312 | |||
| 9aaf3475fe | |||
| 89cc1acc66 | |||
| 65e2ce2541 | |||
| 78d7be1d9a | |||
| 6307ecf074 | |||
| 31a9f05b51 | |||
| 934de535d9 | |||
| 3dbb6e280c | |||
| 8cfd22e17d | |||
| 4371d551f9 | |||
| 5597d3afd0 | |||
| 3dae83b18d | |||
| 15ed42e5ac | |||
| e8fea3878d | |||
| e3d3021286 | |||
| 50d6341510 | |||
| 20d1fbe263 | |||
| eca95309ed | |||
| 40e97fd166 | |||
| 85b879e2c4 | |||
| 9e8a84c4c0 | |||
| bb1d772eaa | |||
| c1b99bc7eb | |||
| 6fa2f77a60 | |||
| 4ba2e210d8 | |||
| b377d5f70c | |||
| d93d72a03e | |||
| be850e2bf6 | |||
| 8c8674e4ec | |||
| b6496348bf | |||
| e68cd998b3 | |||
| 5613e0eb14 | |||
| 1d4699a107 | |||
| 2a99f97b7f | |||
| 78a098818f | |||
| 95ec7d3eaf | |||
| 9fabfbbbd3 | |||
| 508822c91c | |||
| 2341d7f675 | |||
| b15348f857 | |||
| 9254427915 | |||
| 30e6813c0c | |||
| 99f673c98e | |||
| a48c21f49b | |||
| 2373353822 | |||
| a3e491091e | |||
| 6a66f8c967 | |||
| a8cd417188 | |||
| 2f474b160f | |||
| 6afd6c3411 | |||
| 77d0a5f91e | |||
| 5a2d97b534 | |||
| 0222a49cc3 | |||
| b867c88bf2 | |||
| 76c598a167 | |||
| d04ac2ea9d | |||
| 5bbcc1f728 | |||
| d87d8243a6 | |||
| 4dc3657c34 | |||
| 01efc2136c | |||
| 23be653faa | |||
| 469735d23f | |||
| 2f6409e130 | |||
| 8c00bf9a6f | |||
| 62e29245f4 | |||
| e1f63fa403 | |||
| 3cefd9dc89 | |||
| 75aec8e40e | |||
| a5badd1d32 | |||
| 56d5e7f6a1 | |||
| 6b92952d96 | |||
| 402368ce58 | |||
| a813afcd02 | |||
| 419fa188bf | |||
| fc6ff82bed | |||
| e4853906b3 | |||
| c8ddc79308 | |||
| f880abf10d | |||
| a31cf07262 | |||
| a8e1e7a1a0 | |||
| d095a7d670 | |||
| 803d7c12f1 | |||
| 99a2fd4b65 | |||
| edbe03a759 | |||
| 5203cb9989 | |||
| a24a00adbe | |||
| 10814ebf17 | |||
| 341e38603c | |||
| 3ee688d0d6 | |||
| 6782fae984 | |||
| 80ecb4a212 | |||
| 91d99d4024 | |||
| 981a1765ea | |||
| 848f713848 | |||
| 9834d2b1ea | |||
| 7bf1493a41 | |||
| 4a6cdb9d07 | |||
| 17e70bb0a3 | |||
| 73dfc6852e | |||
| 4f66e8e684 | |||
| 93266be581 | |||
| df35215b5f | |||
| 37bafed958 | |||
| 4f25352e32 | |||
| fa34d86b8a | |||
| abb01fa49e | |||
| c5480d55ca | |||
| 200aaaca99 | |||
| 231304f73a | |||
| 69db29d981 | |||
| 02cdbbf100 | |||
| 9accb467b2 | |||
| d99c0eacbf | |||
| 507783b1bc | |||
| 6db9f03b19 | |||
| 48e149b7b4 | |||
| f8c5bb85b3 | |||
| fdf02979ee | |||
| a4489ab5cf | |||
| cf7d3abaea | |||
| 85f27cf655 | |||
| 964e9af033 | |||
| 950875e4db | |||
| 78573a32c5 | |||
| a469f1c42c | |||
| c7269a960e | |||
| 843d22cc3f | |||
| 7d7a525f54 | |||
| 22784dd796 | |||
| c050aabd78 | |||
| 9c83b4c643 | |||
| 6b14f7bd57 | |||
| c9ffd646ce | |||
| d981b74485 | |||
| 9730dc980e | |||
| 7a18752de4 | |||
| cd467b71c8 | |||
| 3de39975ca | |||
| dce54171fb | |||
| 3b20811605 | |||
| 79fa7d3fae | |||
| 277f5a3501 | |||
| baeef6c859 | |||
| c7f47bf06d | |||
| 11e30cc49f | |||
| 46df1beedb | |||
| 1938d3d522 | |||
| 1a584424d9 | |||
| 65df8c9f8d | |||
| 579a4be000 | |||
| cb087ec7df | |||
| cef4e90704 | |||
| 9d01fabf27 | |||
| 3f1f2435eb | |||
| 62642e4356 | |||
| 6f088bb291 | |||
| 23050fd3d1 | |||
| 9905d7f022 | |||
| b30aa7cbca | |||
| 90f02752e9 | |||
| 345ce9a98d | |||
| edb8f2cc8b | |||
| 886b37630c | |||
| aa1c967812 | |||
| e050cccaa2 | |||
| 9c2ae2b6b2 | |||
| 287fa463cf | |||
| b23fd91dc9 | |||
| 73836d728b | |||
| a30de5d8e1 | |||
| 0144295b43 | |||
| 5c732e3e77 | |||
| 28fa751fbf | |||
| 9454870d5c | |||
| d15000d291 | |||
| a65b9b1cc5 | |||
| ebba7c31d8 | |||
| 6a8db3f661 | |||
| 0b2fb70f5e | |||
| 5f87bb9e97 | |||
| 2c2217b784 | |||
| fc0283b417 | |||
| 5670ea3a6f | |||
| 36a8fa2f79 | |||
| d94f853cb3 | |||
| 19917fe119 | |||
| 96c01d7fc8 | |||
| ec3dff12b0 | |||
| 22e815cc6f | |||
| a645195eb5 | |||
| 2aa967546a | |||
| 939cef4f1e | |||
| 5dd83d7652 | |||
| edd731904e | |||
| 6ebaa21a7c | |||
| 35c5881114 | |||
| b8d2efdc66 | |||
| 89ffd15b7b | |||
| 3f8c218bb0 | |||
| 8cf5895f1d | |||
| 47e0b48025 | |||
| b52515b26f | |||
| a72115e15f | |||
| 58e1e447f9 | |||
| 391ea0386c | |||
| 032932a901 | |||
| 6f1d836df1 | |||
| 4350b5d19e | |||
| 3250d58733 | |||
| 311f067c22 | |||
| 01225c309c | |||
| ab11d2d134 | |||
| 62961cba70 | |||
| 4761a0d60b | |||
| 435411356f | |||
| efe5b796f3 | |||
| 4f9ffc3b11 | |||
| 146048ac62 | |||
| 64b6a1521d | |||
| ee39d96120 | |||
| bb93a51817 | |||
| 7e34c0bd86 | |||
| 732990237b | |||
| 2575fec547 | |||
| 41acd13995 | |||
| 2fe4826bf2 | |||
| 3ed4afeb85 | |||
| 7f26c21435 | |||
| 06240f1eba | |||
| cef6ea2cf7 | |||
| 4b398e2619 | |||
| fd03bdec41 | |||
| 8ffb4ed720 | |||
| f4fa121456 | |||
| e488686c3c | |||
| f01079cc90 | |||
| cd98c27de4 | |||
| 3ecbb8b523 | |||
| 28615df1b6 | |||
| e7cb70f2fa | |||
| 592826b0f4 | |||
| a66cdf9654 | |||
| e197b0d0e3 | |||
| ee0d0000f1 | |||
| 713ff0bedb | |||
| 26cbd15a70 | |||
| 844d4b9abc | |||
| c6ba5bb886 | |||
| 904c49d94c | |||
| 7940b34386 | |||
| f7f1d9c0f3 | |||
| bb859bb946 | |||
| b3bb99b18f | |||
| 7455fc25bb | |||
| 679e50354f | |||
| 00606f16fc | |||
| 06c4b2f1c2 | |||
| 25243d5111 | |||
| c304627c8e | |||
| 5e5072d365 | |||
| 63f8aa3ac7 | |||
| 846b3fc090 | |||
| 35f66d88f5 | |||
| fb28c3c610 | |||
| 7f51f8accf | |||
| bca5f61fff | |||
| a588b42778 | |||
| 6721b043d8 | |||
| 9a11f5756f | |||
| 66e2e50321 | |||
| 5d4474204c | |||
| 3a694d5e03 | |||
| c99a1d728b | |||
| 910fecf24f | |||
| c3b87db9ff | |||
| cd8585ceca | |||
| 57728681b6 | |||
| 03a0d6cf64 | |||
| c70d798ee0 | |||
| 133feef4d2 | |||
| 2b23d52ed3 | |||
| 7d2cc5c1fe | |||
| 19b690b665 | |||
| be39975756 | |||
| 8f26827a90 | |||
| 45bb0483ce | |||
| 8fe2eebe03 | |||
| c3dcf70a04 | |||
| f16a16991e | |||
| 0832e557fe | |||
| a44977974d | |||
| 8430b02785 | |||
| bf188494f5 | |||
| 5f3db14067 | |||
| 555cab3627 | |||
| dc07e4811c | |||
| e68c1776c4 | |||
| eaefc61a87 | |||
| bdb08c6798 | |||
| efc33a7a86 | |||
| b5a8051c61 | |||
| 122f3ebddc | |||
| cb429ad822 | |||
| 0eb42567d2 | |||
| 12e61b7b9c | |||
| e79f887542 | |||
| 1a23664028 | |||
| cce5f55a24 | |||
| 9b2b54e9ab | |||
| 2955dc94d7 | |||
| 3eb7aa5a46 | |||
| 069e49430e | |||
| 99bcdde8ca | |||
| 013d037b0a | |||
| 99958a24ca | |||
| 71c454c8cf | |||
| 49bea820b8 | |||
| d90f7d6f8e | |||
| bed5741558 | |||
| 4474f12d2c | |||
| 248f56cbea | |||
| a7294bf6a3 | |||
| 3ca92b6c46 | |||
| 0479a7e3ca | |||
| b8eed02366 | |||
| 1fa8a4ac2a | |||
| 1c5d0984bf | |||
| 5e2b5acd9a | |||
| c42d705232 | |||
| a177edbfcb | |||
| 5ac03bb9bf | |||
| 6c6bd50c67 | |||
| 7f5b4c9d74 | |||
| 5256243c76 | |||
| 96c5857653 | |||
| d7922351c0 | |||
| 4022823f1d | |||
| 690e924dfd | |||
| ad60cf262e | |||
| b77349cfe6 | |||
| cbb88986c2 | |||
| 633b7867f4 | |||
| b707664ffc | |||
| 482e7729b7 | |||
| f5ad0fde3d | |||
| f8010a3f0d | |||
| 7ca9cb5d28 |
@@ -1,10 +0,0 @@
|
||||
node_modules
|
||||
.next
|
||||
out
|
||||
build
|
||||
.git
|
||||
.env*.local
|
||||
README.md
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
@@ -1,15 +1,11 @@
|
||||
KEYCLOAK_CLIENT_ID="tjwater"
|
||||
KEYCLOAK_CLIENT_SECRET="83h0n413hau9bldzWdEaq6xRfASv24s5"
|
||||
KEYCLOAK_ISSUER="https://keycloak.waternetwork.cn/realms/tjwater"
|
||||
NEXTAUTH_SECRET="eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiS"
|
||||
NEXTAUTH_URL="https://demo.waternetwork.cn/"
|
||||
DB_NAME=szh
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=5433
|
||||
DB_USER=tjwater
|
||||
DB_PASSWORD=Tjwater@123456
|
||||
|
||||
# 为前端暴露的变量添加 NEXT_PUBLIC_ 前缀
|
||||
NEXT_PUBLIC_BACKEND_URL="https://server.waternetwork.cn"
|
||||
NEXT_PUBLIC_MAP_URL="https://geoserver.waternetwork.cn/geoserver"
|
||||
NEXT_PUBLIC_MAP_WORKSPACE="szh"
|
||||
NEXT_PUBLIC_MAP_EXTENT="13490131, 3630016, 13525879, 3666968.25"
|
||||
# NEXT_PUBLIC_MAP_AVAILABLE_LAYERS="junctions, pipes, reservoirs, scada"
|
||||
NEXT_PUBLIC_NETWORK_NAME="szh"
|
||||
NEXT_PUBLIC_MAPBOX_TOKEN="pk.eyJ1IjoiemhpZnUiLCJhIjoiY205azNyNGY1MGkyZDJxcTJleDUwaHV1ZCJ9.wOmSdOnDDdre-mB1Lpy6Fg"
|
||||
NEXT_PUBLIC_TIANDITU_TOKEN="e3e8ad95ee911741fa71ed7bff2717ec"
|
||||
TIMESCALEDB_DB_NAME=szh
|
||||
TIMESCALEDB_DB_HOST=127.0.0.1
|
||||
TIMESCALEDB_DB_PORT=5435
|
||||
TIMESCALEDB_DB_USER=tjwater
|
||||
TIMESCALEDB_DB_PASSWORD=Tjwater@123456
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
# Copilot Instructions for TJWater Frontend
|
||||
|
||||
## Project Overview
|
||||
|
||||
A Next.js 16 + TypeScript water network management system built with Refine framework, featuring real-time hydraulic simulation, SCADA data management, and GIS visualization using OpenLayers and Deck.gl.
|
||||
|
||||
## Build, Test, and Lint Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
npm run dev # Start dev server (uses 4GB memory allocation)
|
||||
|
||||
# Production
|
||||
npm run build # Build for production (standalone output)
|
||||
npm run start # Start production server
|
||||
|
||||
# Testing
|
||||
npm run test # Run all tests
|
||||
npm run test:watch # Run tests in watch mode
|
||||
npm run test:coverage # Generate coverage report
|
||||
|
||||
# Linting
|
||||
npm run lint # Run ESLint
|
||||
```
|
||||
|
||||
**Run single test file:**
|
||||
|
||||
```bash
|
||||
npm test -- path/to/test-file.test.ts
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Framework Stack
|
||||
|
||||
- **Next.js 16** with App Router (not Pages Router)
|
||||
- **Refine** framework for admin/CRUD operations
|
||||
- **NextAuth.js** with Keycloak for SSO authentication
|
||||
- **Material-UI (MUI) v6** for UI components
|
||||
- **OpenLayers** + **Deck.gl** for map visualization
|
||||
|
||||
### Route Structure
|
||||
|
||||
- `src/app/layout.tsx` - Root layout with RefineContext
|
||||
- `src/app/(main)/` - Protected routes with shared layout
|
||||
- `/network-simulation` - Real-time network simulation
|
||||
- `/scada-data-cleaning` - SCADA data management
|
||||
- `/monitoring-place-optimization` - Sensor placement optimization
|
||||
- `/health-risk-analysis` - Health risk assessment
|
||||
- `/risk-analysis-location` - Risk location analysis
|
||||
- `/network-partition-optimization` - Network partitioning
|
||||
- `src/app/OlMap/` - Standalone map route with custom controls
|
||||
- `src/app/login/` - Public authentication pages
|
||||
|
||||
### Key Directories
|
||||
|
||||
- `src/app/_refine_context.tsx` - Refine configuration with resources, auth provider, and data provider
|
||||
- `src/providers/data-provider/` - REST API data provider (currently mock, update API_URL for production)
|
||||
- `src/contexts/color-mode/` - Theme switching (light/dark mode persisted in cookies)
|
||||
- `src/components/` - Reusable UI components (header, loading, olmap, title)
|
||||
- `src/utils/` - Map utilities (layers.ts, mapQueryService.ts, color parsing)
|
||||
- `src/config/config.ts` - Environment-based configuration with fallback defaults
|
||||
|
||||
### Path Aliases (TypeScript)
|
||||
|
||||
```typescript
|
||||
@app/* -> src/app/*
|
||||
@assets/* -> src/assets/*
|
||||
@components/* -> src/components/*
|
||||
@config/* -> src/config/*
|
||||
@contexts/* -> src/contexts/*
|
||||
@interfaces/* -> src/interfaces/*
|
||||
@libs/* -> src/libs/*
|
||||
@providers/* -> src/providers/*
|
||||
@utils/* -> src/utils/*
|
||||
@/* -> src/*
|
||||
```
|
||||
|
||||
### Map Architecture
|
||||
|
||||
The map system uses a hybrid approach:
|
||||
|
||||
- **OpenLayers** as the base map engine (vector tiles from GeoServer)
|
||||
- **Deck.gl** overlays for advanced visualizations (trips, contours, text labels)
|
||||
- **DeckLayer** custom class bridges OL and Deck.gl (`@utils/layers`)
|
||||
- Map data sourced from GeoServer MVT tiles (configured in `@config/config.ts`)
|
||||
- Layers: junctions, pipes, valves, reservoirs, pumps, tanks, scada
|
||||
|
||||
### Client vs Server Components
|
||||
|
||||
- Most interactive components use `"use client"` directive (~35 files)
|
||||
- Map components are always client-side (OpenLayers requires browser APIs)
|
||||
- Layout and page files without interactivity can be server components
|
||||
|
||||
## Key Conventions
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
- Keycloak SSO via NextAuth.js (`src/app/api/auth/[...nextauth]/`)
|
||||
- Session managed with `SessionProvider` wrapper
|
||||
- Auth check redirects to `/login` if unauthenticated
|
||||
- Use `useSession()` hook for current user data
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- All frontend-accessible variables must have `NEXT_PUBLIC_` prefix
|
||||
- Backend URL: `NEXT_PUBLIC_BACKEND_URL` (defaults to http://192.168.1.42:8000)
|
||||
- GeoServer URL: `NEXT_PUBLIC_MAP_URL` (defaults to http://127.0.0.1:8080/geoserver)
|
||||
- Map layers: `NEXT_PUBLIC_MAP_AVAILABLE_LAYERS` (comma-separated)
|
||||
- Keycloak config in `.env.local` (not committed)
|
||||
|
||||
### Refine Resources
|
||||
|
||||
Resources defined in `_refine_context.tsx` use Chinese labels and route to pages in `(main)/`:
|
||||
|
||||
- Each resource has: name (Chinese), list (route path), meta (icon + label)
|
||||
- Icons from `react-icons` library
|
||||
- No CRUD operations defined (list-only pages)
|
||||
|
||||
### Map Styling
|
||||
|
||||
- Default styles in `config.MAP_DEFAULT_STYLE` (stroke, circle, colors)
|
||||
- Circle radius uses zoom-based interpolation (1px at z12, 8px at z24)
|
||||
- WebGL rendering for vector tiles
|
||||
- Style legends generated dynamically in map controls
|
||||
|
||||
### TypeScript Configuration
|
||||
|
||||
- Strict mode enabled
|
||||
- Path aliases match jest.config.js mappings
|
||||
- Target ES5 for broader compatibility
|
||||
- Incremental builds enabled
|
||||
|
||||
### Next.js Configuration
|
||||
|
||||
- **Standalone output** for Docker deployment
|
||||
- SVG files handled by `@svgr/webpack` (imported as React components)
|
||||
- No custom server or middleware
|
||||
|
||||
### Testing Setup
|
||||
|
||||
- Jest with React Testing Library
|
||||
- jsdom environment for component testing
|
||||
- Path aliases configured to match tsconfig.json
|
||||
- Setup file at `jest.setup.js`
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Adding a New Route
|
||||
|
||||
1. Create directory in `src/app/(main)/your-route/`
|
||||
2. Add `page.tsx` and optional `loading.tsx`
|
||||
3. Register resource in `src/app/_refine_context.tsx` resources array
|
||||
4. Import icon from `react-icons`
|
||||
|
||||
### Working with Maps
|
||||
|
||||
- Use `MapComponent` from `src/app/OlMap/MapComponent.tsx`
|
||||
- Access map context via `useMapData()` hook
|
||||
- Vector tile layers auto-load from GeoServer workspace
|
||||
- Custom overlays use Deck.gl layers (TextLayer, TripsLayer, ContourLayer)
|
||||
|
||||
### API Calls
|
||||
|
||||
- Update `dataProvider` in `src/providers/data-provider/index.ts` for real backend
|
||||
- Currently points to `https://api.fake-rest.refine.dev`
|
||||
- Use Refine hooks (`useList`, `useOne`, etc.) for data fetching
|
||||
|
||||
### Theme Management
|
||||
|
||||
- Theme stored in cookies (not localStorage)
|
||||
- Toggle via `ColorModeContext` from `@contexts/color-mode`
|
||||
- Supports light/dark modes only
|
||||
- Default mode read from cookie in root layout (server-side)
|
||||
+24
-29
@@ -1,36 +1,31 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
# vscode
|
||||
.vscode/
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
# python cache
|
||||
__pycache__/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
# pytest
|
||||
.pytest_cache/
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
# dev
|
||||
dev_demo.py
|
||||
build_db.py
|
||||
|
||||
# production
|
||||
/build
|
||||
# db inp
|
||||
*.db.inp
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
# calculation
|
||||
*.rpt
|
||||
*.opt
|
||||
*.opt.json
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
# todo
|
||||
TODO.md
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
# build pyd
|
||||
build
|
||||
*.c
|
||||
*.pyd
|
||||
temp/
|
||||
source_db_structure.dump
|
||||
source_db.dump
|
||||
|
||||
+79974
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
@echo off
|
||||
|
||||
:loop
|
||||
echo 正在执行 git pull...
|
||||
git pull
|
||||
echo 等待10分钟...
|
||||
timeout /t 600 /nobreak >nul
|
||||
|
||||
goto loop
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
FROM refinedev/node:22 AS base
|
||||
|
||||
FROM base AS deps
|
||||
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
|
||||
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
FROM base AS builder
|
||||
|
||||
# 只定义 ARG 接收来自构建命令或 docker-compose.yaml 的参数
|
||||
# Next.js 在 build 时会自动读取同名的 ARG 作为环境变量
|
||||
ARG NEXT_PUBLIC_BACKEND_URL
|
||||
ARG NEXT_PUBLIC_MAP_URL
|
||||
ARG NEXT_PUBLIC_MAP_WORKSPACE
|
||||
ARG NEXT_PUBLIC_MAP_EXTENT
|
||||
ARG NEXT_PUBLIC_NETWORK_NAME
|
||||
ARG NEXT_PUBLIC_MAPBOX_TOKEN
|
||||
ARG NEXT_PUBLIC_TIANDITU_TOKEN
|
||||
|
||||
COPY --from=deps /app/refine/node_modules ./node_modules
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM base AS runner
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
COPY --from=builder /app/refine/public ./public
|
||||
|
||||
RUN mkdir .next
|
||||
RUN chown refine:nodejs .next
|
||||
|
||||
COPY --from=builder --chown=refine:nodejs /app/refine/.next/standalone ./
|
||||
COPY --from=builder --chown=refine:nodejs /app/refine/.next/static ./.next/static
|
||||
|
||||
USER refine
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
@@ -0,0 +1,14 @@
|
||||
UserName tjwater
|
||||
Password Tjwater@123456
|
||||
Organizatio TJWATEORG
|
||||
Bucket TJWATERBUCKET
|
||||
|
||||
API Token : xGDM5RZqRJAuzAGS-otXUdC2NFdY75qJAjRLqAB4p5WcIIAlIUpOpT8_yA16AOHmJWerwQ_08gwb84sy42jnZQ==
|
||||
|
||||
|
||||
|
||||
influx config create --config-name onboarding `
|
||||
--host-url "http://localhost:8086" `
|
||||
--org "TJWATERORG" `
|
||||
--token "p4Hq6DQ4xI6yA2tZQgo-VGzjWObylyWd4B45vMoiae0XJeNUlL87FdEUU5cJ63O87W7-nAhhGWl_0FGJiL801w==" `
|
||||
--active
|
||||
@@ -1,48 +0,0 @@
|
||||
# my-refine-app
|
||||
|
||||
<div align="center" style="margin: 30px;">
|
||||
<a href="https://refine.dev">
|
||||
<img alt="refine logo" src="https://refine.ams3.cdn.digitaloceanspaces.com/readme/refine-readme-banner.png">
|
||||
</a>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
This [Refine](https://github.com/refinedev/refine) project was generated with [create refine-app](https://github.com/refinedev/refine/tree/master/packages/create-refine-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility ✨
|
||||
|
||||
Refine's hooks and components simplifies the development process and eliminates the repetitive tasks by providing industry-standard solutions for crucial aspects of a project, including authentication, access control, routing, networking, state management, and i18n.
|
||||
|
||||
## Available Scripts
|
||||
|
||||
### Running the development server.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Building for production.
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Running the production server.
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about **Refine**, please check out the [Documentation](https://refine.dev/docs)
|
||||
|
||||
- **REST Data Provider** [Docs](https://refine.dev/docs/core/providers/data-provider/#overview)
|
||||
- **Material UI** [Docs](https://refine.dev/docs/ui-frameworks/mui/tutorial/)
|
||||
- **Custom Auth Provider** [Docs](https://refine.dev/docs/core/providers/auth-provider/)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1 @@
|
||||
当前 适配 szh 项目的分支 是 dingsu/szh
|
||||
@@ -0,0 +1,25 @@
|
||||
import auto_realtime
|
||||
import auto_store_non_realtime_SCADA_data
|
||||
import asyncio
|
||||
import influxdb_api
|
||||
import influxdb_info
|
||||
import project_info
|
||||
|
||||
# 为了让多个任务并发运行,我们可以用 asyncio.to_thread 分别启动它们
|
||||
async def main():
|
||||
task1 = asyncio.to_thread(auto_realtime.realtime_task)
|
||||
task2 = asyncio.to_thread(auto_store_non_realtime_SCADA_data.store_non_realtime_SCADA_data_task)
|
||||
await asyncio.gather(task1, task2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
url = influxdb_info.url
|
||||
token = influxdb_info.token
|
||||
org_name = influxdb_info.org
|
||||
|
||||
influxdb_api.query_pg_scada_info_realtime(project_info.name)
|
||||
influxdb_api.query_pg_scada_info_non_realtime(project_info.name)
|
||||
|
||||
# 用 asyncio 并发启动两个任务
|
||||
asyncio.run(main())
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+175
@@ -0,0 +1,175 @@
|
||||
from .project_backup import list_project, have_project, create_project, delete_project, clean_project
|
||||
from .project_backup import is_project_open, open_project, close_project
|
||||
from .project_backup import copy_project
|
||||
|
||||
#DingZQ, 2024-12-28, convert inp v3 to v2
|
||||
from .inp_in import read_inp, import_inp, convert_inp_v3_to_v2
|
||||
from .inp_out import dump_inp, export_inp
|
||||
|
||||
from .database import API_ADD, API_UPDATE, API_DELETE
|
||||
from .database import ChangeSet
|
||||
from .database import get_current_operation
|
||||
from .database import execute_undo, execute_redo
|
||||
from .database import list_snapshot
|
||||
from .database import have_snapshot, have_snapshot_for_operation, have_snapshot_for_current_operation
|
||||
from .database import take_snapshot_for_operation, take_snapshot_for_current_operation, take_snapshot
|
||||
from .database import update_snapshot, update_snapshot_for_current_operation
|
||||
from .database import delete_snapshot, delete_snapshot_by_operation
|
||||
from .database import get_operation_by_snapshot, get_snapshot_by_operation
|
||||
from .database import pick_snapshot
|
||||
from .database import pick_operation, sync_with_server
|
||||
from .database import get_restore_operation, set_restore_operation, set_restore_operation_to_current, restore
|
||||
from .database import read, try_read, read_all, write
|
||||
|
||||
from .batch_exe import execute_batch_commands, execute_batch_command
|
||||
|
||||
from .extension_data import get_all_extension_data_keys, get_all_extension_data, get_extension_data, set_extension_data
|
||||
|
||||
from .s0_base import JUNCTION, RESERVOIR, TANK, PIPE, PUMP, VALVE, PATTERN, CURVE
|
||||
from .s0_base import is_node, is_junction, is_reservoir, is_tank
|
||||
from .s0_base import is_link, is_pipe, is_pump, is_valve
|
||||
from .s0_base import is_curve
|
||||
from .s0_base import is_pattern
|
||||
from .s0_base import get_nodes, get_nodes_id_and_type, get_junctions, get_reservoirs, get_tanks, get_links, get_links_id_and_type, get_pipes, get_pumps, get_valves, get_curves, get_patterns
|
||||
from .s0_base import get_node_type, get_link_type, get_element_type, get_element_type_value
|
||||
from .s0_base import get_node_links, get_link_nodes
|
||||
from .s0_base import get_major_nodes, get_major_pipes
|
||||
|
||||
from .s1_title import get_title_schema, get_title, set_title
|
||||
|
||||
from .s2_junctions import get_junction_schema, add_junction, get_junction, set_junction, get_all_junctions
|
||||
from .batch_api import delete_junction_cascade
|
||||
|
||||
from .s3_reservoirs import get_reservoir_schema, add_reservoir, get_reservoir, set_reservoir, get_all_reservoirs
|
||||
from .batch_api import delete_reservoir_cascade
|
||||
|
||||
from .s4_tanks import OVERFLOW_YES, OVERFLOW_NO
|
||||
from .s4_tanks import get_tank_schema, add_tank, get_tank, set_tank, get_all_tanks
|
||||
from .batch_api import delete_tank_cascade
|
||||
|
||||
from .s5_pipes import PIPE_STATUS_OPEN, PIPE_STATUS_CLOSED, PIPE_STATUS_CV
|
||||
from .s5_pipes import get_pipe_schema, add_pipe, get_pipe, set_pipe, get_all_pipes
|
||||
from .batch_api import delete_pipe_cascade
|
||||
|
||||
from .s6_pumps import get_pump_schema, add_pump, get_pump, set_pump, get_all_pumps
|
||||
from .batch_api import delete_pump_cascade
|
||||
|
||||
from .s7_valves import VALVES_TYPE_PRV, VALVES_TYPE_PSV, VALVES_TYPE_PBV, VALVES_TYPE_FCV, VALVES_TYPE_TCV, VALVES_TYPE_GPV
|
||||
from .s7_valves import get_valve_schema, add_valve, get_valve, set_valve, get_all_valves
|
||||
from .batch_api import delete_valve_cascade
|
||||
|
||||
from .s8_tags import TAG_TYPE_NODE, TAG_TYPE_LINK
|
||||
from .s8_tags import get_tag_schema, get_tags, get_tag, set_tag
|
||||
|
||||
from .s9_demands import get_demand_schema, get_demand, set_demand
|
||||
|
||||
from .s10_status import LINK_STATUS_OPEN, LINK_STATUS_CLOSED, LINK_STATUS_ACTIVE
|
||||
from .s10_status import get_status_schema, get_status, set_status
|
||||
|
||||
from .s11_patterns import get_pattern_schema, get_pattern, set_pattern, add_pattern
|
||||
from .batch_api import delete_pattern_cascade
|
||||
|
||||
from .s12_curves import CURVE_TYPE_PUMP, CURVE_TYPE_EFFICIENCY, CURVE_TYPE_VOLUME, CURVE_TYPE_HEADLOSS
|
||||
from .s12_curves import get_curve_schema, get_curve, set_curve, add_curve
|
||||
from .batch_api import delete_curve_cascade
|
||||
|
||||
from .s13_controls import get_control_schema, get_control, set_control
|
||||
|
||||
from .s14_rules import get_rule_schema, get_rule, set_rule
|
||||
|
||||
from .s15_energy import get_energy_schema, get_energy, set_energy
|
||||
from .s15_energy import get_pump_energy_schema, get_pump_energy, set_pump_energy
|
||||
|
||||
from .s16_emitters import get_emitter_schema, get_emitter, set_emitter
|
||||
|
||||
from .s17_quality import get_quality_schema, get_quality, set_quality
|
||||
|
||||
from .s18_sources import SOURCE_TYPE_CONCEN, SOURCE_TYPE_MASS, SOURCE_TYPE_FLOWPACED, SOURCE_TYPE_SETPOINT
|
||||
from .s18_sources import get_source_schema, get_source, set_source, add_source, delete_source
|
||||
|
||||
from .s19_reactions import get_reaction_schema, get_reaction, set_reaction
|
||||
from .s19_reactions import get_pipe_reaction_schema, get_pipe_reaction, set_pipe_reaction
|
||||
from .s19_reactions import get_tank_reaction_schema, get_tank_reaction, set_tank_reaction
|
||||
|
||||
from .s20_mixing import MIXING_MODEL_MIXED, MIXING_MODEL_2COMP, MIXING_MODEL_FIFO, MIXING_MODEL_LIFO
|
||||
from .s20_mixing import get_mixing_schema, get_mixing, set_mixing, add_mixing, delete_mixing
|
||||
|
||||
from .s21_times import TIME_STATISTIC_NONE, TIME_STATISTIC_AVERAGED, TIME_STATISTIC_MINIMUM, TIME_STATISTIC_MAXIMUM, TIME_STATISTIC_RANGE
|
||||
from .s21_times import get_time_schema, get_time, set_time
|
||||
|
||||
from .s23_options_util import OPTION_UNITS_CFS, OPTION_UNITS_GPM, OPTION_UNITS_MGD, OPTION_UNITS_IMGD, OPTION_UNITS_AFD, OPTION_UNITS_LPS, OPTION_UNITS_LPM, OPTION_UNITS_MLD, OPTION_UNITS_CMH, OPTION_UNITS_CMD
|
||||
from .s23_options_util import OPTION_PRESSURE_PSI, OPTION_PRESSURE_KPA, OPTION_PRESSURE_METERS
|
||||
from .s23_options_util import OPTION_HEADLOSS_HW, OPTION_HEADLOSS_DW, OPTION_HEADLOSS_CM
|
||||
from .s23_options_util import OPTION_UNBALANCED_STOP, OPTION_UNBALANCED_CONTINUE
|
||||
from .s23_options_util import OPTION_DEMAND_MODEL_DDA, OPTION_DEMAND_MODEL_PDA
|
||||
from .s23_options_util import OPTION_QUALITY_NONE, OPTION_QUALITY_CHEMICAL, OPTION_QUALITY_AGE, OPTION_QUALITY_TRACE
|
||||
from .s23_options_util import get_option_schema, get_option
|
||||
from .batch_api import set_option_ex
|
||||
|
||||
from .s23_options_util import OPTION_V3_FLOW_UNITS_CFS, OPTION_V3_FLOW_UNITS_GPM, OPTION_V3_FLOW_UNITS_MGD, OPTION_V3_FLOW_UNITS_IMGD, OPTION_V3_FLOW_UNITS_AFD, OPTION_V3_FLOW_UNITS_LPS, OPTION_V3_FLOW_UNITS_LPM, OPTION_V3_FLOW_UNITS_MLD, OPTION_V3_FLOW_UNITS_CMH, OPTION_V3_FLOW_UNITS_CMD
|
||||
from .s23_options_util import OPTION_V3_PRESSURE_UNITS_PSI, OPTION_V3_PRESSURE_UNITS_KPA, OPTION_V3_PRESSURE_UNITS_METERS
|
||||
from .s23_options_util import OPTION_V3_HEADLOSS_MODEL_HW, OPTION_V3_HEADLOSS_MODEL_DW, OPTION_V3_HEADLOSS_MODEL_CM
|
||||
from .s23_options_util import OPTION_V3_STEP_SIZING_FULL, OPTION_V3_STEP_SIZING_RELAXATION, OPTION_V3_STEP_SIZING_LINESEARCH
|
||||
from .s23_options_util import OPTION_V3_IF_UNBALANCED_STOP, OPTION_V3_IF_UNBALANCED_CONTINUE
|
||||
from .s23_options_util import OPTION_V3_DEMAND_MODEL_FIXED, OPTION_V3_DEMAND_MODEL_CONSTRAINED, OPTION_V3_DEMAND_MODEL_POWER, OPTION_V3_DEMAND_MODEL_LOGISTIC
|
||||
from .s23_options_util import OPTION_V3_LEAKAGE_MODEL_NONE, OPTION_V3_LEAKAGE_MODEL_POWER, OPTION_V3_LEAKAGE_MODEL_FAVAD
|
||||
from .s23_options_util import OPTION_V3_QUALITY_MODEL_NONE, OPTION_V3_QUALITY_MODEL_CHEMICAL, OPTION_V3_QUALITY_MODEL_AGE, OPTION_V3_QUALITY_MODEL_TRACE
|
||||
from .s23_options_util import OPTION_V3_QUALITY_UNITS_HRS, OPTION_V3_QUALITY_UNITS_PCNT, OPTION_V3_QUALITY_UNITS_MGL, OPTION_V3_QUALITY_UNITS_UGL
|
||||
from .s23_options_util import get_option_v3_schema, get_option_v3
|
||||
from .batch_api import set_option_v3_ex
|
||||
|
||||
from .s24_coordinates import get_node_coord, get_nodes_in_extent, get_links_in_extent
|
||||
|
||||
from .s25_vertices import get_vertex_schema, get_vertex, set_vertex, add_vertex, delete_vertex
|
||||
from .s25_vertices import get_all_vertex_links, get_all_vertices
|
||||
|
||||
from .s26_labels import get_label_schema, get_label, set_label, add_label, delete_label
|
||||
|
||||
from .s27_backdrop import get_backdrop_schema, get_backdrop, set_backdrop
|
||||
|
||||
from .s29_scada_device import SCADA_DEVICE_TYPE_PRESSURE, SCADA_DEVICE_TYPE_DEMAND, SCADA_DEVICE_TYPE_QUALITY, SCADA_DEVICE_TYPE_LEVEL, SCADA_DEVICE_TYPE_FLOW, SCADA_DEVICE_TYPE_UNKNOWN
|
||||
from .s29_scada_device import get_scada_device_schema, get_scada_device, set_scada_device, add_scada_device, delete_scada_device
|
||||
from .s29_scada_device import get_all_scada_device_ids, get_all_scada_devices
|
||||
from .clean_api import clean_scada_device
|
||||
|
||||
from .s30_scada_device_data import get_scada_device_data_schema, get_scada_device_data, set_scada_device_data, add_scada_device_data, delete_scada_device_data
|
||||
from .clean_api import clean_scada_device_data
|
||||
|
||||
from .s31_scada_element import SCADA_MODEL_TYPE_JUNCTION, SCADA_MODEL_TYPE_RESERVOIR, SCADA_MODEL_TYPE_TANK, SCADA_MODEL_TYPE_PIPE, SCADA_MODEL_TYPE_PUMP, SCADA_MODEL_TYPE_VALVE
|
||||
from .s31_scada_element import SCADA_ELEMENT_STATUS_OFFLINE, SCADA_ELEMENT_STATUS_ONLINE
|
||||
from .s31_scada_element import get_scada_element_schema, get_scada_element, set_scada_element, add_scada_element, delete_scada_element
|
||||
from .s31_scada_element import get_all_scada_element_ids, get_all_scada_elements
|
||||
from .clean_api import clean_scada_element
|
||||
|
||||
from .s32_region_util import get_nodes_in_boundary, get_nodes_in_region, get_links_on_region_boundary, calculate_convex_hull, calculate_boundary, inflate_boundary, inflate_region
|
||||
from .s32_region import get_region_schema, get_region, set_region, add_region, delete_region
|
||||
|
||||
from .s33_dma_cal import PARTITION_TYPE_RB, PARTITION_TYPE_KWAY
|
||||
from .s33_dma_cal import calculate_district_metering_area_for_nodes, calculate_district_metering_area_for_region, calculate_district_metering_area_for_network
|
||||
from .s33_dma import get_district_metering_area_schema, get_district_metering_area, set_district_metering_area, add_district_metering_area, delete_district_metering_area
|
||||
from .s33_dma import get_all_district_metering_area_ids, get_all_district_metering_areas
|
||||
from .s33_dma_gen import generate_district_metering_area, generate_sub_district_metering_area
|
||||
|
||||
from .s34_sa_cal import calculate_service_area
|
||||
from .s34_sa import get_service_area_schema, get_service_area, set_service_area, add_service_area, delete_service_area
|
||||
from .s34_sa import get_all_service_area_ids, get_all_service_areas
|
||||
from .s34_sa_gen import generate_service_area
|
||||
|
||||
from .s35_vd_cal import calculate_virtual_district
|
||||
from .s35_vd import get_virtual_district_schema, get_virtual_district, set_virtual_district, add_virtual_district, delete_virtual_district
|
||||
from .s35_vd import get_all_virtual_district_ids, get_all_virtual_districts
|
||||
from .s35_vd_gen import generate_virtual_district
|
||||
|
||||
from .s36_wda_cal import calculate_demand_to_nodes, calculate_demand_to_region, calculate_demand_to_network
|
||||
|
||||
from .s38_scada_info import get_scada_info_schema, get_scada_info, get_all_scada_info
|
||||
|
||||
from .s39_user import get_user_schema, get_user, get_all_users
|
||||
|
||||
from .s40_schema import get_scheme_schema, get_scheme, get_all_schemes
|
||||
|
||||
from .s41_pipe_risk_probability import get_pipe_risk_probability_now, get_pipe_risk_probability, get_network_pipe_risk_probability_now, get_pipes_risk_probability, get_pipe_risk_probability_geometries
|
||||
|
||||
from .s42_sensor_placement import get_all_sensor_placements
|
||||
|
||||
from .s43_burst_locate_result import get_all_burst_locate_results
|
||||
@@ -0,0 +1,53 @@
|
||||
from .sections import *
|
||||
from .database import ChangeSet, API_DELETE, API_UPDATE
|
||||
from .batch_exe import execute_batch_command
|
||||
|
||||
|
||||
def delete_junction_cascade(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s2_junction }
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def delete_reservoir_cascade(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s3_reservoir }
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def delete_tank_cascade(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s4_tank }
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def delete_pipe_cascade(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s5_pipe }
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def delete_pump_cascade(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s6_pump }
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def delete_valve_cascade(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s7_valve }
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def delete_pattern_cascade(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s11_pattern }
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def delete_curve_cascade(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s12_curve }
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def set_option_ex(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0] |= { 'operation' : API_UPDATE, 'type' : s23_option }
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def set_option_v3_ex(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0] |= { 'operation' : API_UPDATE, 'type' : s23_option_v3 }
|
||||
return execute_batch_command(name, cs)
|
||||
@@ -0,0 +1,238 @@
|
||||
from .database import ChangeSet, g_delete_prefix, API_DELETE, API_UPDATE, try_read
|
||||
from .sections import *
|
||||
|
||||
from .s0_base import *
|
||||
|
||||
from .s3_reservoirs import unset_reservoir_by_pattern
|
||||
from .s4_tanks import unset_tank_by_curve
|
||||
from .s6_pumps import unset_pump_by_curve, unset_pump_by_pattern
|
||||
from .s8_tags import delete_tag_by_node, delete_tag_by_link
|
||||
from .s9_demands import delete_demand_by_junction, unset_demand_by_pattern
|
||||
from .s10_status import delete_status_by_link
|
||||
from .s15_energy import delete_pump_energy_by_pump, unset_pump_energy_by_pattern, unset_pump_energy_by_curve
|
||||
from .s16_emitters import delete_emitter_by_junction
|
||||
from .s17_quality import delete_quality_by_node
|
||||
from .s18_sources import delete_source_by_node, unset_source_by_pattern
|
||||
from .s19_reactions import delete_pipe_reaction_by_pipe, delete_tank_reaction_by_tank
|
||||
from .s20_mixing import delete_mixing_by_tank
|
||||
from .s25_vertices import delete_vertex_by_link
|
||||
from .s26_labels import unset_label_by_node
|
||||
|
||||
from .s23_options_util import generate_v2, generate_v3
|
||||
|
||||
|
||||
def delete_junction_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, f"select * from junctions where id = '{id}'")
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
links = get_node_links(name, id)
|
||||
|
||||
for link in links:
|
||||
if is_pipe(name, link):
|
||||
result.merge(delete_pipe_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
|
||||
if is_pump(name, link):
|
||||
result.merge(delete_pump_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
|
||||
if is_valve(name, link):
|
||||
result.merge(delete_valve_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
|
||||
|
||||
result.merge(delete_tag_by_node(name, id))
|
||||
result.merge(delete_demand_by_junction(name, id))
|
||||
result.merge(delete_emitter_by_junction(name, id))
|
||||
result.merge(delete_quality_by_node(name, id))
|
||||
result.merge(delete_source_by_node(name, id))
|
||||
result.merge(unset_label_by_node(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def delete_reservoir_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, f"select * from reservoirs where id = '{id}'")
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
links = get_node_links(name, id)
|
||||
|
||||
for link in links:
|
||||
if is_pipe(name, link):
|
||||
result.merge(delete_pipe_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
|
||||
if is_pump(name, link):
|
||||
result.merge(delete_pump_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
|
||||
if is_valve(name, link):
|
||||
result.merge(delete_valve_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
|
||||
|
||||
result.merge(delete_tag_by_node(name, id))
|
||||
result.merge(delete_quality_by_node(name, id))
|
||||
result.merge(delete_source_by_node(name, id))
|
||||
result.merge(unset_label_by_node(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def delete_tank_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, f"select * from tanks where id = '{id}'")
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
links = get_node_links(name, id)
|
||||
|
||||
for link in links:
|
||||
if is_pipe(name, link):
|
||||
result.merge(delete_pipe_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
|
||||
if is_pump(name, link):
|
||||
result.merge(delete_pump_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
|
||||
if is_valve(name, link):
|
||||
result.merge(delete_valve_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
|
||||
|
||||
result.merge(delete_tag_by_node(name, id))
|
||||
result.merge(delete_quality_by_node(name, id))
|
||||
result.merge(delete_source_by_node(name, id))
|
||||
result.merge(delete_tank_reaction_by_tank(name, id))
|
||||
result.merge(delete_mixing_by_tank(name, id))
|
||||
result.merge(unset_label_by_node(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def delete_pipe_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, f"select * from pipes where id = '{id}'")
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
result.merge(delete_tag_by_link(name, id))
|
||||
result.merge(delete_status_by_link(name, id))
|
||||
result.merge(delete_pipe_reaction_by_pipe(name, id))
|
||||
result.merge(delete_vertex_by_link(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def delete_pump_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, f"select * from pumps where id = '{id}'")
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
result.merge(delete_tag_by_link(name, id))
|
||||
result.merge(delete_status_by_link(name, id))
|
||||
result.merge(delete_pump_energy_by_pump(name, id))
|
||||
result.merge(delete_vertex_by_link(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def delete_valve_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, f"select * from valves where id = '{id}'")
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
result.merge(delete_tag_by_link(name, id))
|
||||
result.merge(delete_status_by_link(name, id))
|
||||
result.merge(delete_vertex_by_link(name, id))
|
||||
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def delete_pattern_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, f"select * from _pattern where id = '{id}'")
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
result.merge(unset_reservoir_by_pattern(name, id))
|
||||
result.merge(unset_pump_by_pattern(name, id))
|
||||
result.merge(unset_demand_by_pattern(name, id))
|
||||
result.merge(unset_pump_energy_by_pattern(name, id))
|
||||
result.merge(unset_source_by_pattern(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def delete_curve_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, f"select * from _curve where id = '{id}'")
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
result.merge(unset_tank_by_curve(name, id))
|
||||
result.merge(unset_pump_by_curve(name, id))
|
||||
result.merge(unset_pump_energy_by_curve(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def set_option_cs(cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0]['operation'] = API_UPDATE
|
||||
cs.operations[0]['type'] = 'option'
|
||||
new_cs = cs
|
||||
new_cs.merge(generate_v3(cs))
|
||||
return new_cs
|
||||
|
||||
|
||||
def set_option_v3_cs(cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0]['operation'] = API_UPDATE
|
||||
cs.operations[0]['type'] = 'option_v3'
|
||||
new_cs = cs
|
||||
new_cs.merge(generate_v2(cs))
|
||||
return new_cs
|
||||
|
||||
|
||||
def rewrite_batch_api(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
op = cs.operations[0]
|
||||
api = op['operation']
|
||||
type = op['type']
|
||||
|
||||
if api == API_DELETE:
|
||||
if type == s2_junction:
|
||||
return delete_junction_cascade_batch_cs(name, cs)
|
||||
elif type == s3_reservoir:
|
||||
return delete_reservoir_cascade_batch_cs(name, cs)
|
||||
elif type == s4_tank:
|
||||
return delete_tank_cascade_batch_cs(name, cs)
|
||||
elif type == s5_pipe:
|
||||
return delete_pipe_cascade_batch_cs(name, cs)
|
||||
elif type == s6_pump:
|
||||
return delete_pump_cascade_batch_cs(name, cs)
|
||||
elif type == s7_valve:
|
||||
return delete_valve_cascade_batch_cs(name, cs)
|
||||
elif type == s11_pattern:
|
||||
return delete_pattern_cascade_batch_cs(name, cs)
|
||||
elif type == s12_curve:
|
||||
return delete_curve_cascade_batch_cs(name, cs)
|
||||
elif api == API_UPDATE:
|
||||
if type == s23_option:
|
||||
return set_option_cs(cs)
|
||||
elif type == s23_option_v3:
|
||||
return set_option_v3_cs(cs)
|
||||
|
||||
return cs
|
||||
@@ -0,0 +1,380 @@
|
||||
from typing import Any
|
||||
from .sections import *
|
||||
from .database import API_ADD, API_UPDATE, API_DELETE, ChangeSet, write, read, read_all, get_current_operation
|
||||
from .extension_data import set_extension_data
|
||||
from .s1_title import set_title
|
||||
from .s2_junctions import set_junction, add_junction, delete_junction
|
||||
from .s3_reservoirs import set_reservoir, add_reservoir, delete_reservoir
|
||||
from .s4_tanks import set_tank, add_tank, delete_tank
|
||||
from .s5_pipes import set_pipe, add_pipe, delete_pipe
|
||||
from .s6_pumps import set_pump, add_pump, delete_pump
|
||||
from .s7_valves import set_valve, add_valve, delete_valve
|
||||
from .s8_tags import set_tag
|
||||
from .s9_demands import set_demand
|
||||
from .s10_status import set_status
|
||||
from .s11_patterns import set_pattern, add_pattern, delete_pattern
|
||||
from .s12_curves import set_curve, add_curve, delete_curve
|
||||
from .s13_controls import set_control
|
||||
from .s14_rules import set_rule
|
||||
from .s15_energy import set_energy, set_pump_energy
|
||||
from .s16_emitters import set_emitter
|
||||
from .s17_quality import set_quality
|
||||
from .s18_sources import set_source, add_source, delete_source
|
||||
from .s19_reactions import set_reaction, set_pipe_reaction, set_tank_reaction
|
||||
from .s20_mixing import set_mixing, add_mixing, delete_mixing
|
||||
from .s21_times import set_time
|
||||
from .s23_options_util import set_option, set_option_v3
|
||||
from .s25_vertices import set_vertex, add_vertex, delete_vertex
|
||||
from .s26_labels import set_label, add_label, delete_label
|
||||
from .s27_backdrop import set_backdrop
|
||||
from .s29_scada_device import set_scada_device, add_scada_device, delete_scada_device
|
||||
from .s30_scada_device_data import set_scada_device_data, add_scada_device_data, delete_scada_device_data
|
||||
from .s31_scada_element import set_scada_element, add_scada_element, delete_scada_element
|
||||
from .s32_region import set_region, add_region, delete_region
|
||||
from .s33_dma import set_district_metering_area, add_district_metering_area, delete_district_metering_area
|
||||
from .s34_sa import set_service_area, add_service_area, delete_service_area
|
||||
from .s35_vd import set_virtual_district, add_virtual_district, delete_virtual_district
|
||||
from .batch_api_cs import rewrite_batch_api
|
||||
|
||||
|
||||
def _execute_add_command(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
type = cs.operations[0]['type']
|
||||
|
||||
if type == s1_title:
|
||||
return ChangeSet()
|
||||
if type == s2_junction:
|
||||
return add_junction(name, cs)
|
||||
elif type == s3_reservoir:
|
||||
return add_reservoir(name, cs)
|
||||
elif type == s4_tank:
|
||||
return add_tank(name, cs)
|
||||
elif type == s5_pipe:
|
||||
return add_pipe(name, cs)
|
||||
elif type == s6_pump:
|
||||
return add_pump(name, cs)
|
||||
elif type == s7_valve:
|
||||
return add_valve(name, cs)
|
||||
elif type == s8_tag:
|
||||
return ChangeSet()
|
||||
elif type == s9_demand:
|
||||
return ChangeSet()
|
||||
elif type == s10_status:
|
||||
return ChangeSet()
|
||||
elif type == s11_pattern:
|
||||
return add_pattern(name, cs)
|
||||
elif type == s12_curve:
|
||||
return add_curve(name, cs)
|
||||
elif type == s13_control:
|
||||
return ChangeSet()
|
||||
elif type == s14_rule:
|
||||
return ChangeSet()
|
||||
elif type == s15_energy:
|
||||
return ChangeSet()
|
||||
elif type == s15_pump_energy:
|
||||
return ChangeSet()
|
||||
elif type == s16_emitter:
|
||||
return ChangeSet()
|
||||
elif type == s17_quality:
|
||||
return ChangeSet()
|
||||
elif type == s18_source:
|
||||
return add_source(name, cs)
|
||||
elif type == s19_reaction:
|
||||
return ChangeSet()
|
||||
elif type == s19_pipe_reaction:
|
||||
return ChangeSet()
|
||||
elif type == s19_tank_reaction:
|
||||
return ChangeSet()
|
||||
elif type == s20_mixing:
|
||||
return add_mixing(name, cs)
|
||||
elif type == s21_time:
|
||||
return ChangeSet()
|
||||
elif type == s22_report:
|
||||
return ChangeSet()
|
||||
elif type == s23_option:
|
||||
return ChangeSet()
|
||||
elif type == s23_option_v3:
|
||||
return ChangeSet()
|
||||
elif type == s24_coordinate:
|
||||
return ChangeSet()
|
||||
elif type == s25_vertex:
|
||||
return add_vertex(name, cs)
|
||||
elif type == s26_label:
|
||||
return add_label(name, cs)
|
||||
elif type == s27_backdrop:
|
||||
return ChangeSet()
|
||||
elif type == s28_end:
|
||||
return ChangeSet()
|
||||
elif type == s29_scada_device:
|
||||
return add_scada_device(name, cs)
|
||||
elif type == s30_scada_device_data:
|
||||
return add_scada_device_data(name, cs)
|
||||
elif type == s31_scada_element:
|
||||
return add_scada_element(name, cs)
|
||||
elif type == s32_region:
|
||||
return add_region(name, cs)
|
||||
elif type == s33_dma:
|
||||
return add_district_metering_area(name, cs)
|
||||
elif type == s34_sa:
|
||||
return add_service_area(name, cs)
|
||||
elif type == s35_vd:
|
||||
return add_virtual_district(name, cs)
|
||||
|
||||
return ChangeSet()
|
||||
|
||||
|
||||
def _execute_update_command(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
type = cs.operations[0]['type']
|
||||
|
||||
if type == 'extension_data':
|
||||
return set_extension_data(name, cs)
|
||||
if type == s1_title:
|
||||
return set_title(name, cs)
|
||||
if type == s2_junction:
|
||||
return set_junction(name, cs)
|
||||
elif type == s3_reservoir:
|
||||
return set_reservoir(name, cs)
|
||||
elif type == s4_tank:
|
||||
return set_tank(name, cs)
|
||||
elif type == s5_pipe:
|
||||
return set_pipe(name, cs)
|
||||
elif type == s6_pump:
|
||||
return set_pump(name, cs)
|
||||
elif type == s7_valve:
|
||||
return set_valve(name, cs)
|
||||
elif type == s8_tag:
|
||||
return set_tag(name, cs)
|
||||
elif type == s9_demand:
|
||||
return set_demand(name, cs)
|
||||
elif type == s10_status:
|
||||
return set_status(name, cs)
|
||||
elif type == s11_pattern:
|
||||
return set_pattern(name, cs)
|
||||
elif type == s12_curve:
|
||||
return set_curve(name, cs)
|
||||
elif type == s13_control:
|
||||
return set_control(name, cs)
|
||||
elif type == s14_rule:
|
||||
return set_rule(name, cs)
|
||||
elif type == s15_energy:
|
||||
return set_energy(name, cs)
|
||||
elif type == s15_pump_energy:
|
||||
return set_pump_energy(name, cs)
|
||||
elif type == s16_emitter:
|
||||
return set_emitter(name, cs)
|
||||
elif type == s17_quality:
|
||||
return set_quality(name, cs)
|
||||
elif type == s18_source:
|
||||
return set_source(name, cs)
|
||||
elif type == s19_reaction:
|
||||
return set_reaction(name, cs)
|
||||
elif type == s19_pipe_reaction:
|
||||
return set_pipe_reaction(name, cs)
|
||||
elif type == s19_tank_reaction:
|
||||
return set_tank_reaction(name, cs)
|
||||
elif type == s20_mixing:
|
||||
return set_mixing(name, cs)
|
||||
elif type == s21_time:
|
||||
return set_time(name, cs)
|
||||
elif type == s22_report: # no api now
|
||||
return ChangeSet()
|
||||
elif type == s23_option:
|
||||
return set_option(name, cs)
|
||||
elif type == s23_option_v3:
|
||||
return set_option_v3(name, cs)
|
||||
elif type == s24_coordinate: # do not support update here
|
||||
return ChangeSet()
|
||||
elif type == s25_vertex:
|
||||
return set_vertex(name, cs)
|
||||
elif type == s26_label:
|
||||
return set_label(name, cs)
|
||||
elif type == s27_backdrop:
|
||||
return set_backdrop(name, cs)
|
||||
elif type == s28_end: # end
|
||||
return ChangeSet()
|
||||
elif type == s29_scada_device:
|
||||
return set_scada_device(name, cs)
|
||||
elif type == s30_scada_device_data:
|
||||
return set_scada_device_data(name, cs)
|
||||
elif type == s31_scada_element:
|
||||
return set_scada_element(name, cs)
|
||||
elif type == s32_region:
|
||||
return set_region(name, cs)
|
||||
elif type == s33_dma:
|
||||
return set_district_metering_area(name, cs)
|
||||
elif type == s34_sa:
|
||||
return set_service_area(name, cs)
|
||||
elif type == s35_vd:
|
||||
return set_virtual_district(name, cs)
|
||||
|
||||
return ChangeSet()
|
||||
|
||||
|
||||
def _execute_delete_command(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
type = cs.operations[0]['type']
|
||||
|
||||
if type == s1_title:
|
||||
return ChangeSet()
|
||||
if type == s2_junction:
|
||||
return delete_junction(name, cs)
|
||||
elif type == s3_reservoir:
|
||||
return delete_reservoir(name, cs)
|
||||
elif type == s4_tank:
|
||||
return delete_tank(name, cs)
|
||||
elif type == s5_pipe:
|
||||
return delete_pipe(name, cs)
|
||||
elif type == s6_pump:
|
||||
return delete_pump(name, cs)
|
||||
elif type == s7_valve:
|
||||
return delete_valve(name, cs)
|
||||
elif type == s8_tag:
|
||||
return ChangeSet()
|
||||
elif type == s9_demand:
|
||||
return ChangeSet()
|
||||
elif type == s10_status:
|
||||
return ChangeSet()
|
||||
elif type == s11_pattern:
|
||||
return delete_pattern(name, cs)
|
||||
elif type == s12_curve:
|
||||
return delete_curve(name, cs)
|
||||
elif type == s13_control:
|
||||
return ChangeSet()
|
||||
elif type == s14_rule:
|
||||
return ChangeSet()
|
||||
elif type == s15_energy:
|
||||
return ChangeSet()
|
||||
elif type == s15_pump_energy:
|
||||
return ChangeSet()
|
||||
elif type == s16_emitter:
|
||||
return ChangeSet()
|
||||
elif type == s17_quality:
|
||||
return ChangeSet()
|
||||
elif type == s18_source:
|
||||
return delete_source(name, cs)
|
||||
elif type == s19_reaction:
|
||||
return ChangeSet()
|
||||
elif type == s19_pipe_reaction:
|
||||
return ChangeSet()
|
||||
elif type == s19_tank_reaction:
|
||||
return ChangeSet()
|
||||
elif type == s20_mixing:
|
||||
return delete_mixing(name, cs)
|
||||
elif type == s21_time:
|
||||
return ChangeSet()
|
||||
elif type == s22_report:
|
||||
return ChangeSet()
|
||||
elif type == s23_option:
|
||||
return ChangeSet()
|
||||
elif type == s23_option_v3:
|
||||
return ChangeSet()
|
||||
elif type == s24_coordinate:
|
||||
return ChangeSet()
|
||||
elif type == s25_vertex:
|
||||
return delete_vertex(name, cs)
|
||||
elif type == s26_label:
|
||||
return delete_label(name, cs)
|
||||
elif type == s27_backdrop:
|
||||
return ChangeSet()
|
||||
elif type == s28_end:
|
||||
return ChangeSet()
|
||||
elif type == s29_scada_device:
|
||||
return delete_scada_device(name, cs)
|
||||
elif type == s30_scada_device_data:
|
||||
return delete_scada_device_data(name, cs)
|
||||
elif type == s31_scada_element:
|
||||
return delete_scada_element(name, cs)
|
||||
elif type == s32_region:
|
||||
return delete_region(name, cs)
|
||||
elif type == s33_dma:
|
||||
return delete_district_metering_area(name, cs)
|
||||
elif type == s34_sa:
|
||||
return delete_service_area(name, cs)
|
||||
elif type == s35_vd:
|
||||
return delete_virtual_district(name, cs)
|
||||
|
||||
return ChangeSet()
|
||||
|
||||
|
||||
def execute_batch_commands(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
new_cs = ChangeSet()
|
||||
for op in cs.operations:
|
||||
new_cs.merge(rewrite_batch_api(name, ChangeSet(op)))
|
||||
|
||||
result = ChangeSet()
|
||||
|
||||
todo = {}
|
||||
|
||||
try:
|
||||
for op in new_cs.operations:
|
||||
todo = op
|
||||
operation = op['operation']
|
||||
if operation == API_ADD:
|
||||
result.merge(_execute_add_command(name, ChangeSet(op)))
|
||||
elif operation == API_UPDATE:
|
||||
result.merge(_execute_update_command(name, ChangeSet(op)))
|
||||
elif operation == API_DELETE:
|
||||
result.merge(_execute_delete_command(name, ChangeSet(op)))
|
||||
except:
|
||||
print(f'ERROR: Fail to execute {todo}')
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def execute_batch_command(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
write(name, 'delete from batch_operation where id > 0')
|
||||
write(name, "update operation_table set option = 'batch_operation' where option = 'operation'")
|
||||
|
||||
new_cs = ChangeSet()
|
||||
for op in cs.operations:
|
||||
new_cs.merge(rewrite_batch_api(name, ChangeSet(op)))
|
||||
|
||||
result = ChangeSet()
|
||||
|
||||
todo = {}
|
||||
|
||||
try:
|
||||
for op in new_cs.operations:
|
||||
todo = op
|
||||
operation = op['operation']
|
||||
if operation == API_ADD:
|
||||
result.merge(_execute_add_command(name, ChangeSet(op)))
|
||||
elif operation == API_UPDATE:
|
||||
result.merge(_execute_update_command(name, ChangeSet(op)))
|
||||
elif operation == API_DELETE:
|
||||
result.merge(_execute_delete_command(name, ChangeSet(op)))
|
||||
except:
|
||||
print(f'ERROR: Fail to execute {todo}')
|
||||
|
||||
count = read(name, 'select count(*) as count from batch_operation')['count']
|
||||
if count == 1:
|
||||
write(name, 'delete from batch_operation where id > 0')
|
||||
write(name, "update operation_table set option = 'operation' where option = 'batch_operation'")
|
||||
return ChangeSet()
|
||||
|
||||
redo_list: list[str] = []
|
||||
redo_cs_list: list[dict[str, Any]] = []
|
||||
redo_rows = read_all(name, 'select redo, redo_cs from batch_operation where id > 0 order by id asc')
|
||||
for row in redo_rows:
|
||||
redo_list.append(row['redo'])
|
||||
redo_cs_list += eval(row['redo_cs'])
|
||||
|
||||
undo_list: list[str] = []
|
||||
undo_cs_list: list[dict[str, Any]] = []
|
||||
undo_rows = read_all(name, 'select undo, undo_cs from batch_operation where id > 0 order by id desc')
|
||||
for row in undo_rows:
|
||||
undo_list.append(row['undo'])
|
||||
undo_cs_list += eval(row['undo_cs'])
|
||||
|
||||
redo = '\n'.join(redo_list).replace("'", "''")
|
||||
redo_cs = str(redo_cs_list).replace("'", "''")
|
||||
undo = '\n'.join(undo_list).replace("'", "''")
|
||||
undo_cs = str(undo_cs_list).replace("'", "''")
|
||||
|
||||
parent = get_current_operation(name)
|
||||
write(name, f"insert into operation (id, redo, undo, parent, redo_cs, undo_cs) values (default, '{redo}', '{undo}', {parent}, '{redo_cs}', '{undo_cs}')")
|
||||
current = read(name, 'select max(id) as id from operation')['id']
|
||||
write(name, f"update current_operation set id = {current}")
|
||||
|
||||
write(name, 'delete from batch_operation where id > 0')
|
||||
write(name, "update operation_table set option = 'operation' where option = 'batch_operation'")
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,45 @@
|
||||
from .database import ChangeSet, read_all
|
||||
from .batch_exe import execute_batch_command
|
||||
|
||||
# TODO: merge to batch_api
|
||||
|
||||
def clean_scada_device_cs(name: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, 'select id from scada_device acs')
|
||||
for row in rows:
|
||||
cs.delete({ 'type': 'scada_device', 'id': row['id'] })
|
||||
|
||||
return cs
|
||||
|
||||
|
||||
def clean_scada_device_data_cs(name: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, 'select distinct device_id from scada_device_data acs')
|
||||
for row in rows:
|
||||
cs.update({ 'type': 'scada_device_data', 'device_id': row['device_id'], 'data': [] })
|
||||
|
||||
return cs
|
||||
|
||||
|
||||
def clean_scada_element_cs(name: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, 'select id from scada_element acs')
|
||||
for row in rows:
|
||||
cs.delete({ 'type': 'scada_element', 'id': row['id'] })
|
||||
|
||||
return cs
|
||||
|
||||
|
||||
def clean_scada_device(name: str) -> ChangeSet:
|
||||
return execute_batch_command(name, clean_scada_device_cs(name))
|
||||
|
||||
|
||||
def clean_scada_device_data(name: str) -> ChangeSet:
|
||||
return execute_batch_command(name, clean_scada_device_data_cs(name))
|
||||
|
||||
|
||||
def clean_scada_element(name: str) -> ChangeSet:
|
||||
return execute_batch_command(name, clean_scada_element_cs(name))
|
||||
@@ -0,0 +1,3 @@
|
||||
import psycopg as pg
|
||||
|
||||
g_conn_dict : dict[str, pg.Connection] = {}
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
from typing import Any
|
||||
from psycopg.rows import dict_row, Row
|
||||
from .connection import g_conn_dict as conn
|
||||
|
||||
API_ADD = 'add'
|
||||
API_UPDATE = 'update'
|
||||
API_DELETE = 'delete'
|
||||
|
||||
g_add_prefix = { 'operation': API_ADD }
|
||||
g_update_prefix = { 'operation': API_UPDATE }
|
||||
g_delete_prefix = { 'operation': API_DELETE }
|
||||
|
||||
|
||||
class ChangeSet:
|
||||
def __init__(self, ps: dict[str, Any] | None = None):
|
||||
self.operations : list[dict[str, Any]] = []
|
||||
if ps != None:
|
||||
self.append(ps)
|
||||
|
||||
@staticmethod
|
||||
def from_list(ps: list[dict[str, Any]]):
|
||||
cs = ChangeSet()
|
||||
for _cs in ps:
|
||||
cs.append(_cs)
|
||||
return cs
|
||||
|
||||
def add(self, ps: dict[str, Any]):
|
||||
self.operations.append(g_add_prefix | ps)
|
||||
return self
|
||||
|
||||
def update(self, ps: dict[str, Any]):
|
||||
self.operations.append(g_update_prefix | ps)
|
||||
return self
|
||||
|
||||
def delete(self, ps: dict[str, Any]):
|
||||
self.operations.append(g_delete_prefix | ps)
|
||||
return self
|
||||
|
||||
def append(self, ps: dict[str, Any]):
|
||||
self.operations.append(ps)
|
||||
return self
|
||||
|
||||
def merge(self, cs):
|
||||
if len(cs.operations) > 0:
|
||||
self.operations += cs.operations
|
||||
return self
|
||||
|
||||
def dump(self):
|
||||
for op in self.operations:
|
||||
print(op)
|
||||
|
||||
def compress(self):
|
||||
return self
|
||||
|
||||
|
||||
class DbChangeSet:
|
||||
def __init__(self, redo_sql: str, undo_sql: str, redo_cs: list[dict[str, Any]], undo_cs: list[dict[str, Any]]) -> None:
|
||||
self.redo_sql = redo_sql
|
||||
self.undo_sql = undo_sql
|
||||
self.redo_cs = redo_cs
|
||||
self.undo_cs = undo_cs
|
||||
|
||||
@staticmethod
|
||||
def from_list(css):
|
||||
redo_sql_s : list[str] = []
|
||||
undo_sql_s : list[str] = []
|
||||
redo_cs_s : list[dict[str, Any]] = []
|
||||
undo_cs_s : list[dict[str, Any]] = []
|
||||
|
||||
for r in css:
|
||||
redo_sql_s.append(r.redo_sql)
|
||||
undo_sql_s.append(r.undo_sql)
|
||||
redo_cs_s += r.redo_cs
|
||||
r.undo_cs.reverse() # reverse again...
|
||||
undo_cs_s += r.undo_cs
|
||||
|
||||
redo_sql = '\n'.join(redo_sql_s)
|
||||
undo_sql_s.reverse()
|
||||
undo_sql = '\n'.join(undo_sql_s)
|
||||
undo_cs_s.reverse()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, redo_cs_s, undo_cs_s)
|
||||
|
||||
|
||||
def read(name: str, sql: str) -> Row:
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(sql)
|
||||
row = cur.fetchone()
|
||||
if row == None:
|
||||
raise Exception(sql)
|
||||
return row
|
||||
|
||||
|
||||
def read_all(name: str, sql: str) -> list[Row]:
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(sql)
|
||||
return cur.fetchall()
|
||||
|
||||
|
||||
def try_read(name: str, sql: str) -> Row | None:
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(sql)
|
||||
return cur.fetchone()
|
||||
|
||||
|
||||
def write(name: str, sql: str) -> None:
|
||||
with conn[name].cursor() as cur:
|
||||
cur.execute(sql)
|
||||
|
||||
|
||||
def get_current_operation(name: str) -> int:
|
||||
return int(read(name, 'select id from current_operation')['id'])
|
||||
|
||||
|
||||
def execute_command(name: str, command: DbChangeSet, undo_redo: bool = True) -> ChangeSet:
|
||||
write(name, command.redo_sql)
|
||||
|
||||
if undo_redo:
|
||||
op_table = read(name, "select * from operation_table")['option']
|
||||
parent = get_current_operation(name)
|
||||
redo_sql = command.redo_sql.replace("'", "''")
|
||||
undo_sql = command.undo_sql.replace("'", "''")
|
||||
redo_cs_str = str(command.redo_cs).replace("'", "''")
|
||||
undo_cs_str = str(command.undo_cs).replace("'", "''")
|
||||
write(name, f"insert into {op_table} (id, redo, undo, parent, redo_cs, undo_cs) values (default, '{redo_sql}', '{undo_sql}', {parent}, '{redo_cs_str}', '{undo_cs_str}')")
|
||||
|
||||
if op_table == 'operation':
|
||||
current = read(name, 'select max(id) as id from operation')['id']
|
||||
write(name, f"update current_operation set id = {current}")
|
||||
|
||||
return ChangeSet.from_list(command.redo_cs)
|
||||
|
||||
|
||||
def execute_undo(name: str, discard: bool = False) -> ChangeSet:
|
||||
row = read(name, f'select * from operation where id = {get_current_operation(name)}')
|
||||
|
||||
write(name, row['undo'])
|
||||
|
||||
# update foreign key
|
||||
write(name, f"update current_operation set id = {row['parent']} where id = {row['id']}")
|
||||
|
||||
if discard:
|
||||
# update foreign key
|
||||
write(name, f"update operation set redo_child = null where id = {row['parent']}")
|
||||
# on delete cascade => child & snapshot
|
||||
write(name, f"delete from operation where id = {row['id']}")
|
||||
else:
|
||||
write(name, f"update operation set redo_child = {row['id']} where id = {row['parent']}")
|
||||
|
||||
e = eval(row['undo_cs'])
|
||||
return ChangeSet.from_list(e)
|
||||
|
||||
|
||||
def execute_redo(name: str) -> ChangeSet:
|
||||
row = read(name, f'select * from operation where id = {get_current_operation(name)}')
|
||||
if row['redo_child'] == None:
|
||||
return ChangeSet()
|
||||
|
||||
row = read(name, f"select * from operation where id = {row['redo_child']}")
|
||||
write(name, row['redo'])
|
||||
|
||||
write(name, f"update current_operation set id = {row['id']} where id = {row['parent']}")
|
||||
|
||||
e = eval(row['redo_cs'])
|
||||
return ChangeSet.from_list(e)
|
||||
|
||||
|
||||
def list_snapshot(name: str) -> list[tuple[int, str]]:
|
||||
rows = read_all(name, f'select * from snapshot_operation order by id')
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append((int(row['id']), str(row['tag'])))
|
||||
return result
|
||||
|
||||
|
||||
def have_snapshot(name: str, tag: str) -> bool:
|
||||
return try_read(name, f"select id from snapshot_operation where tag = '{tag}'") != None
|
||||
|
||||
|
||||
def have_snapshot_for_operation(name: str, operation: int) -> bool:
|
||||
return try_read(name, f"select id from snapshot_operation where id = {operation}") != None
|
||||
|
||||
|
||||
def have_snapshot_for_current_operation(name: str) -> bool:
|
||||
return have_snapshot_for_operation(name, get_current_operation(name))
|
||||
|
||||
|
||||
def take_snapshot_for_operation(name: str, operation: int, tag: str) -> None:
|
||||
if tag == None or tag == '':
|
||||
return None
|
||||
write(name, f"insert into snapshot_operation (id, tag) values ({operation}, '{tag}')")
|
||||
|
||||
|
||||
def take_snapshot_for_current_operation(name: str, tag: str) -> None:
|
||||
take_snapshot_for_operation(name, get_current_operation(name), tag)
|
||||
|
||||
|
||||
# deprecated ! use take_snapshot_for_current_operation instead
|
||||
def take_snapshot(name: str, tag: str) -> None:
|
||||
take_snapshot_for_current_operation(name, tag)
|
||||
|
||||
|
||||
def update_snapshot(name: str, operation: int, tag: str) -> None:
|
||||
if tag == None or tag == '':
|
||||
return None
|
||||
if have_snapshot_for_operation(name, operation):
|
||||
write(name, f"update snapshot_operation set tag = '{tag}' where id = {operation}")
|
||||
else:
|
||||
take_snapshot_for_operation(name, operation, tag)
|
||||
|
||||
|
||||
def update_snapshot_for_current_operation(name: str, tag: str) -> None:
|
||||
return update_snapshot(name, get_current_operation(name), tag)
|
||||
|
||||
|
||||
def delete_snapshot(name: str, tag: str) -> None:
|
||||
write(name, f"delete from snapshot_operation where tag = '{tag}'")
|
||||
|
||||
|
||||
def delete_snapshot_by_operation(name: str, operation: int) -> None:
|
||||
write(name, f"delete from snapshot_operation where id = {operation}")
|
||||
|
||||
|
||||
def get_operation_by_snapshot(name: str, tag: str) -> int | None:
|
||||
row = try_read(name, f"select id from snapshot_operation where tag = '{tag}'")
|
||||
return int(row['id']) if row != None else None
|
||||
|
||||
|
||||
def get_snapshot_by_operation(name: str, operation: int) -> str | None:
|
||||
row = try_read(name, f"select tag from snapshot_operation where id = {operation}")
|
||||
return str(row['tag']) if row != None else None
|
||||
|
||||
|
||||
def _get_parents(name: str, id: int) -> list[int]:
|
||||
ids = [id]
|
||||
while ids[-1] != 0:
|
||||
row = read(name, f'select parent from operation where id = {ids[-1]}')
|
||||
ids.append(int(row['parent']))
|
||||
return ids
|
||||
|
||||
|
||||
def pick_operation(name: str, operation: int, discard: bool) -> ChangeSet:
|
||||
target = operation
|
||||
curr = get_current_operation(name)
|
||||
|
||||
curr_parents = _get_parents(name, curr)
|
||||
target_parents = _get_parents(name, target)
|
||||
|
||||
change = ChangeSet()
|
||||
|
||||
if target in curr_parents:
|
||||
for _ in range(curr_parents.index(target)):
|
||||
change.merge(execute_undo(name, discard))
|
||||
|
||||
elif curr in target_parents:
|
||||
target_parents.reverse()
|
||||
curr_index = target_parents.index(curr)
|
||||
for i in range(curr_index, len(target_parents) - 1):
|
||||
write(name, f"update operation set redo_child = '{target_parents[i + 1]}' where id = '{target_parents[i]}'")
|
||||
change.merge(execute_redo(name))
|
||||
|
||||
else:
|
||||
ancestor_index = -1
|
||||
while curr_parents[ancestor_index] == target_parents[ancestor_index]:
|
||||
ancestor_index -= 1
|
||||
ancestor = curr_parents[ancestor_index + 1]
|
||||
|
||||
for _ in range(curr_parents.index(ancestor)):
|
||||
change.merge(execute_undo(name, discard))
|
||||
|
||||
target_parents.reverse()
|
||||
curr_index = target_parents.index(ancestor)
|
||||
for i in range(curr_index, len(target_parents) - 1):
|
||||
write(name, f"update operation set redo_child = '{target_parents[i + 1]}' where id = '{target_parents[i]}'")
|
||||
change.merge(execute_redo(name))
|
||||
|
||||
return change.compress()
|
||||
|
||||
|
||||
def pick_snapshot(name: str, tag: str, discard: bool) -> ChangeSet:
|
||||
if not have_snapshot(name, tag):
|
||||
return ChangeSet()
|
||||
|
||||
target = int(read(name, f"select id from snapshot_operation where tag = '{tag}'")['id'])
|
||||
return pick_operation(name, target, discard)
|
||||
|
||||
|
||||
def _get_change_set(name: str, operation: int, undo: bool) -> ChangeSet:
|
||||
row = read(name, f'select * from operation where id = {operation}')
|
||||
field= 'undo_cs' if undo else 'redo_cs'
|
||||
return ChangeSet.from_list(eval(row[field]))
|
||||
|
||||
|
||||
def sync_with_server(name: str, operation: int) -> ChangeSet:
|
||||
fr = operation
|
||||
to = get_current_operation(name)
|
||||
|
||||
fr_parents = _get_parents(name, fr)
|
||||
to_parents = _get_parents(name, to)
|
||||
|
||||
change = ChangeSet()
|
||||
|
||||
if fr in to_parents:
|
||||
index = to_parents.index(fr) - 1
|
||||
while index >= 0:
|
||||
change.merge(_get_change_set(name, to_parents[index], False)) #redo
|
||||
index -= 1
|
||||
|
||||
elif to in fr_parents:
|
||||
index = 0
|
||||
while index <= fr_parents.index(to) - 1:
|
||||
change.merge(_get_change_set(name, fr_parents[index], True))
|
||||
index += 1
|
||||
|
||||
else:
|
||||
ancestor_index = -1
|
||||
while fr_parents[ancestor_index] == to_parents[ancestor_index]:
|
||||
ancestor_index -= 1
|
||||
|
||||
ancestor = fr_parents[ancestor_index + 1]
|
||||
|
||||
index = 0
|
||||
while index <= fr_parents.index(ancestor) - 1:
|
||||
change.merge(_get_change_set(name, fr_parents[index], True))
|
||||
index += 1
|
||||
|
||||
index = to_parents.index(ancestor) - 1
|
||||
while index >= 0:
|
||||
change.merge(_get_change_set(name, to_parents[index], False))
|
||||
index -= 1
|
||||
|
||||
return change.compress()
|
||||
|
||||
|
||||
def get_restore_operation(name: str) -> int:
|
||||
return read(name, f'select * from restore_operation')['id']
|
||||
|
||||
|
||||
def set_restore_operation(name: str, operation: int) -> None:
|
||||
write(name, f'update restore_operation set id = {operation}')
|
||||
|
||||
|
||||
def set_restore_operation_to_current(name: str) -> None:
|
||||
return set_restore_operation(name, get_current_operation(name))
|
||||
|
||||
|
||||
def restore(name: str, discard: bool) -> ChangeSet:
|
||||
op = get_restore_operation(name)
|
||||
return pick_operation(name, op, discard)
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,62 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_all_extension_data_keys(name: str) -> list[str]:
|
||||
result: list[str] = []
|
||||
for row in read_all(name, 'select key from extension_data'):
|
||||
result.append(row['key'])
|
||||
return result
|
||||
|
||||
|
||||
def get_all_extension_data(name: str) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {}
|
||||
for row in read_all(name, 'select key, value from extension_data'):
|
||||
result[row['key']] = row['value']
|
||||
return result
|
||||
|
||||
|
||||
def get_extension_data(name: str, key: str) -> str | None:
|
||||
if key == None or key == '':
|
||||
return None
|
||||
row = try_read(name, f"select value from extension_data where key = '{key}'")
|
||||
if row == None:
|
||||
return None
|
||||
return row['value']
|
||||
|
||||
|
||||
def _set_extension_data(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
op = cs.operations[0]
|
||||
key, new_val = op['key'], op['value']
|
||||
|
||||
f_new_val = f"'{new_val}'" if new_val != None else 'null'
|
||||
|
||||
old_val = get_extension_data(name, key)
|
||||
f_old_val = f"'{old_val}'" if old_val != None else 'null'
|
||||
|
||||
redo_sql = f"delete from extension_data where key = '{key}';"
|
||||
if new_val != None:
|
||||
redo_sql += f"insert into extension_data (key, value) values ('{key}', {f_new_val});"
|
||||
|
||||
undo_sql = f"delete from extension_data where key = '{key}';"
|
||||
if old_val != None:
|
||||
undo_sql += f"insert into extension_data (key, value) values ('{key}', {f_old_val});"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'extension_data', 'key': key, 'value': new_val }
|
||||
undo_cs = g_update_prefix | { 'type': 'extension_data', 'key': key, 'value': old_val }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_extension_data(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if len(cs.operations) != 1:
|
||||
return ChangeSet()
|
||||
|
||||
op = cs.operations[0]
|
||||
if 'key' not in op or 'value' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
key = op['key']
|
||||
if key == None or key == '':
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _set_extension_data(name, cs))
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
import datetime
|
||||
import os
|
||||
from .project_backup import *
|
||||
from .database import ChangeSet, write
|
||||
from .sections import *
|
||||
from .s0_base import get_region_type
|
||||
from .s1_title import inp_in_title
|
||||
from .s2_junctions import inp_in_junction
|
||||
from .s3_reservoirs import inp_in_reservoir
|
||||
from .s4_tanks import inp_in_tank
|
||||
from .s5_pipes import inp_in_pipe
|
||||
from .s6_pumps import inp_in_pump
|
||||
from .s7_valves import inp_in_valve
|
||||
from .s8_tags import inp_in_tag
|
||||
from .s9_demands import inp_in_demand
|
||||
from .s10_status import inp_in_status
|
||||
from .s11_patterns import pattern_v3_types, inp_in_pattern
|
||||
from .s12_curves import curve_types, inp_in_curve
|
||||
from .s13_controls import inp_in_control
|
||||
from .s14_rules import inp_in_rule
|
||||
from .s15_energy import inp_in_energy
|
||||
from .s16_emitters import inp_in_emitter
|
||||
from .s17_quality import inp_in_quality
|
||||
from .s18_sources import inp_in_source
|
||||
from .s19_reactions import inp_in_reaction
|
||||
from .s20_mixing import inp_in_mixing
|
||||
from .s21_times import inp_in_time
|
||||
from .s22_report import inp_in_report
|
||||
from .s23_options import inp_in_option
|
||||
from .s23_options_v3 import inp_in_option_v3
|
||||
from .s24_coordinates import inp_in_coord
|
||||
from .s25_vertices import inp_in_vertex
|
||||
from .s26_labels import inp_in_label
|
||||
from .s27_backdrop import inp_in_backdrop
|
||||
from .s32_region import inp_in_region,inp_in_bound,inp_in_regionnodes
|
||||
from .s32_region_util import from_postgis_polygon,to_postgis_polygon
|
||||
|
||||
#DingZQ, 2024-12-28, export inp
|
||||
from .inp_out import export_inp
|
||||
|
||||
_S = 'S'
|
||||
_L = 'L'
|
||||
|
||||
def _inp_in_option(section: list[str], version: str = '3') -> str:
|
||||
return inp_in_option_v3(section) if version == '3' else inp_in_option(section)
|
||||
|
||||
_handler = {
|
||||
TITLE : (_S, inp_in_title),
|
||||
JUNCTIONS : (_L, inp_in_junction), # line, demand_outside
|
||||
RESERVOIRS : (_L, inp_in_reservoir),
|
||||
TANKS : (_L, inp_in_tank),
|
||||
PIPES : (_L, inp_in_pipe),
|
||||
PUMPS : (_L, inp_in_pump),
|
||||
VALVES : (_L, inp_in_valve),
|
||||
TAGS : (_L, inp_in_tag),
|
||||
DEMANDS : (_L, inp_in_demand),
|
||||
STATUS : (_L, inp_in_status),
|
||||
PATTERNS : (_L, inp_in_pattern), # line, fixed
|
||||
CURVES : (_L, inp_in_curve),
|
||||
CONTROLS : (_L, inp_in_control),
|
||||
RULES : (_L, inp_in_rule),
|
||||
ENERGY : (_L, inp_in_energy),
|
||||
EMITTERS : (_L, inp_in_emitter),
|
||||
QUALITY : (_L, inp_in_quality),
|
||||
SOURCES : (_L, inp_in_source),
|
||||
REACTIONS : (_L, inp_in_reaction),
|
||||
MIXING : (_L, inp_in_mixing),
|
||||
TIMES : (_S, inp_in_time),
|
||||
REPORT : (_S, inp_in_report),
|
||||
OPTIONS : (_S, _inp_in_option), # line, version
|
||||
COORDINATES : (_L, inp_in_coord),
|
||||
VERTICES : (_L, inp_in_vertex),
|
||||
REGION : (_L, inp_in_region),
|
||||
BOUND : (_L, inp_in_bound),
|
||||
REGION_NODES : (_L, inp_in_regionnodes),
|
||||
LABELS : (_L, inp_in_label),
|
||||
BACKDROP : (_S, inp_in_backdrop),
|
||||
#END : 'END',
|
||||
}
|
||||
|
||||
_level_1 = [
|
||||
TITLE,
|
||||
PATTERNS,
|
||||
CURVES,
|
||||
CONTROLS,
|
||||
RULES,
|
||||
TIMES,
|
||||
REPORT,
|
||||
OPTIONS,
|
||||
BACKDROP,
|
||||
]
|
||||
|
||||
_level_2 = [
|
||||
JUNCTIONS,
|
||||
RESERVOIRS,
|
||||
TANKS,
|
||||
]
|
||||
|
||||
_level_3 = [
|
||||
PIPES,
|
||||
PUMPS,
|
||||
VALVES,
|
||||
DEMANDS,
|
||||
EMITTERS,
|
||||
QUALITY,
|
||||
SOURCES,
|
||||
MIXING,
|
||||
COORDINATES,
|
||||
LABELS,
|
||||
]
|
||||
|
||||
_level_4 = [
|
||||
TAGS,
|
||||
STATUS,
|
||||
ENERGY,
|
||||
REACTIONS,
|
||||
VERTICES,
|
||||
REGION,
|
||||
BOUND,
|
||||
REGION_NODES,
|
||||
]
|
||||
|
||||
map_regiontype={
|
||||
# map the region types from desktop to server
|
||||
'DISTRIBUTION':'WDA',
|
||||
'DMA':'DMA',
|
||||
'PMA':'PMA',
|
||||
'VD':'VD',
|
||||
'SA':'SA',
|
||||
}
|
||||
class SQLBatch:
|
||||
def __init__(self, project: str, count: int = 100) -> None:
|
||||
self.batch: list[str] = []
|
||||
self.project = project
|
||||
self.count = count
|
||||
|
||||
def add(self, sql: str) -> None:
|
||||
self.batch.append(sql)
|
||||
if len(self.batch) == self.count:
|
||||
self.flush()
|
||||
|
||||
def flush(self) -> None:
|
||||
write(self.project, ''.join(self.batch))
|
||||
self.batch.clear()
|
||||
|
||||
|
||||
def _print_time(desc: str) -> datetime.datetime:
|
||||
now = datetime.datetime.now()
|
||||
time = now.strftime('%Y-%m-%d %H:%M:%S')
|
||||
print(f"{time}: {desc}")
|
||||
return now
|
||||
|
||||
|
||||
def _get_file_offset(inp: str) -> tuple[dict[str, list[int]], bool]:
|
||||
offset: dict[str, list[int]] = {}
|
||||
|
||||
current = ''
|
||||
demand_outside = False
|
||||
|
||||
with open(inp) as f:
|
||||
while True:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
line = line.strip()
|
||||
if line.startswith('['):
|
||||
for s in section_name:
|
||||
if line.startswith(f'[{s}'):
|
||||
if s not in offset:
|
||||
offset[s] = []
|
||||
offset[s].append(f.tell())
|
||||
current = s
|
||||
break
|
||||
elif line != '' and line.startswith(';') == False:
|
||||
if current == DEMANDS:
|
||||
demand_outside = True
|
||||
|
||||
return (offset, demand_outside)
|
||||
|
||||
|
||||
def parse_file(project: str, inp: str, version: str = '3') -> None:
|
||||
start = _print_time(f'Start reading file "{inp}"...')
|
||||
|
||||
_print_time("First scan...")
|
||||
offset, demand_outside = _get_file_offset(inp)
|
||||
|
||||
levels = _level_1 + _level_2 + _level_3 + _level_4
|
||||
|
||||
# parse the whole section rather than line
|
||||
sections : dict[str, list[str]]= {}
|
||||
for [s, t] in _handler.items():
|
||||
if t[0] == _S:
|
||||
sections[s] = []
|
||||
|
||||
variable_patterns = []
|
||||
current_pattern = None
|
||||
current_curve = None
|
||||
curve_type_desc_line = None
|
||||
current_region =None
|
||||
current_bound=[]
|
||||
current_bound.clear()
|
||||
region_list={}
|
||||
current_region_nodes=[]
|
||||
current_region_nodes.clear()
|
||||
|
||||
sql_batch = SQLBatch(project)
|
||||
_print_time("Second scan...")
|
||||
with open(inp) as f:
|
||||
for s in levels:
|
||||
if s not in offset:
|
||||
continue
|
||||
|
||||
if s == DEMANDS and demand_outside == False:
|
||||
continue
|
||||
|
||||
_print_time(f"[{s}]")
|
||||
|
||||
is_s = _handler[s][0] == _S
|
||||
handler = _handler[s][1]
|
||||
|
||||
for ptr in offset[s]:
|
||||
f.seek(ptr)
|
||||
|
||||
while True:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
line = line.strip()
|
||||
if line.startswith('['):
|
||||
break
|
||||
elif line == '':
|
||||
continue
|
||||
|
||||
if is_s:
|
||||
sections[s].append(line)
|
||||
else:
|
||||
if line.startswith(';'):
|
||||
if version != '3': #v2
|
||||
line = line.removeprefix(';')
|
||||
if s == PATTERNS: # ;desc
|
||||
pass
|
||||
elif s == CURVES: # ;type: desc
|
||||
curve_type_desc_line = line
|
||||
continue
|
||||
|
||||
if s == PATTERNS:
|
||||
tokens = line.split()
|
||||
|
||||
if tokens[1].upper() in pattern_v3_types: #v3
|
||||
sql_batch.add(f"insert into _pattern (id) values ('{tokens[0]}');")
|
||||
current_pattern = tokens[0]
|
||||
if tokens[1].upper() == 'VARIABLE':
|
||||
variable_patterns.append(tokens[0])
|
||||
continue
|
||||
|
||||
if current_pattern != tokens[0]:
|
||||
sql_batch.add(f"insert into _pattern (id) values ('{tokens[0]}');")
|
||||
current_pattern = tokens[0]
|
||||
|
||||
elif s == CURVES:
|
||||
tokens = line.split()
|
||||
|
||||
if tokens[1].upper() in curve_types: #v3
|
||||
sql_batch.add(f"insert into _curve (id, type) values ('{tokens[0]}', '{tokens[1].upper()}');")
|
||||
current_curve = tokens[0]
|
||||
continue
|
||||
|
||||
if current_curve != tokens[0]:
|
||||
type = curve_types[0]
|
||||
if curve_type_desc_line != None:
|
||||
type = curve_type_desc_line.split(':')[0].strip()
|
||||
sql_batch.add(f"insert into _curve (id, type) values ('{tokens[0]}', '{type}');")
|
||||
current_curve = tokens[0]
|
||||
curve_type_desc_line = None
|
||||
elif s== REGION:
|
||||
tokens = line.split()
|
||||
region_list[tokens[0]]=tokens[1]
|
||||
elif s == BOUND:
|
||||
tokens = line.split()
|
||||
if(tokens[0]!=current_region and len(current_bound)>0):
|
||||
#insert the previous region after get all the vertex of the attatched geometry
|
||||
current_bound.append(current_bound[0])
|
||||
current_geometry=to_postgis_polygon(current_bound)
|
||||
region_type=map_regiontype[region_list[tokens[0]]]
|
||||
sql_batch.add(f"insert into region(id, boundary,r_type) values ('{current_region}', '{current_geometry}','{region_type}');")
|
||||
#start the new region
|
||||
current_bound.clear()
|
||||
vertex_point=(float(tokens[1]),float(tokens[2]))
|
||||
current_bound.append(vertex_point)
|
||||
current_region=tokens[0]
|
||||
elif s==REGION_NODES:
|
||||
tokens = line.split()
|
||||
if(tokens[0]!=current_region and len(current_region_nodes)>0):
|
||||
#insert the previous region after get all the vertex of the attatched geometry
|
||||
sql_batch.add(get_insert_into_region_sql(current_region,current_region_nodes))
|
||||
#start the new region
|
||||
current_region_nodes.clear()
|
||||
current_region_nodes.append(tokens[1])
|
||||
current_region=tokens[0]
|
||||
if s == JUNCTIONS:
|
||||
sql_batch.add(handler(line, demand_outside))
|
||||
elif s == PATTERNS:
|
||||
sql_batch.add(handler(line, current_pattern not in variable_patterns))
|
||||
elif s==BOUND or s==REGION_NODES:
|
||||
continue
|
||||
else:
|
||||
sql_batch.add(handler(line))
|
||||
|
||||
f.seek(0)
|
||||
|
||||
if is_s:
|
||||
if s == OPTIONS:
|
||||
sql_batch.add(handler(sections[s], version))
|
||||
else:
|
||||
sql_batch.add(handler(sections[s]))
|
||||
#need to insert the last region into database
|
||||
if len(current_bound)>0:
|
||||
current_bound.append(current_bound[0])
|
||||
current_geometry=to_postgis_polygon(current_bound)
|
||||
region_type=map_regiontype[region_list[current_region]]
|
||||
sql_batch.add(f"insert into region(id, boundary,r_type) values ('{current_region}', '{current_geometry}','{region_type}');")
|
||||
#reset the current region to none for the [REGION_NODES] session reading
|
||||
#current_region=None
|
||||
#need to insert the last region_nodes into database
|
||||
if len(current_region_nodes)>0:
|
||||
sql_batch.add(get_insert_into_region_sql(current_region,current_region_nodes))
|
||||
#current_region=None
|
||||
sql_batch.flush()
|
||||
|
||||
end = _print_time(f'End reading file "{inp}"')
|
||||
print(f"Total (in second): {(end-start).seconds}(s)")
|
||||
|
||||
def get_insert_into_region_sql(region:str,nodes:list[str])->str:
|
||||
str_sql=''
|
||||
str_nodes = str(nodes).replace("'", "''")
|
||||
r_type=region[0:region.index('_')]
|
||||
if r_type == 'DMA' or r_type == 'SA' or r_type == 'VD':
|
||||
table = ''
|
||||
if r_type == 'DMA':
|
||||
table = 'region_dma'
|
||||
elif r_type == 'SA':
|
||||
table = 'region_sa'
|
||||
source=region[region.index('_')+1:]
|
||||
str_sql=f"insert into region_sa(id,time_index,source,nodes) values ('{region}', 0,'{source}','{str_nodes}');"
|
||||
elif r_type == 'VD':
|
||||
table = 'region_vd'
|
||||
|
||||
return str_sql
|
||||
|
||||
def read_inp(project: str, inp: str, version: str = '3') -> bool:
|
||||
if version != '3' and version != '2':
|
||||
version = '2'
|
||||
|
||||
if is_project_open(project):
|
||||
close_project(project)
|
||||
|
||||
if have_project(project):
|
||||
delete_project(project)
|
||||
|
||||
create_project(project)
|
||||
open_project(project)
|
||||
|
||||
parse_file(project, inp, version)
|
||||
|
||||
'''try:
|
||||
parse_file(project, inp, version)
|
||||
except:
|
||||
close_project(project)
|
||||
delete_project(project)
|
||||
return False'''
|
||||
|
||||
close_project(project)
|
||||
return True
|
||||
|
||||
#DingZQ, 2024-12-28, convert v3 to v2
|
||||
def convert_inp_v3_to_v2(inp: str) -> ChangeSet:
|
||||
project = 'v3Tov2'
|
||||
|
||||
if is_project_open(project):
|
||||
close_project(project)
|
||||
|
||||
if have_project(project):
|
||||
delete_project(project)
|
||||
|
||||
create_project(project)
|
||||
open_project(project)
|
||||
|
||||
filename = f'inp/{project}_temp.inp'
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
f.write(inp)
|
||||
|
||||
parse_file(project, filename, '3')
|
||||
|
||||
'''try:
|
||||
parse_file(project, inp, version)
|
||||
except:
|
||||
close_project(project)
|
||||
delete_project(project)
|
||||
return False'''
|
||||
|
||||
return export_inp(project, '2')
|
||||
|
||||
def import_inp(project: str, cs: ChangeSet, version: str = '3') -> bool:
|
||||
if version != '3' and version != '2':
|
||||
version = '2'
|
||||
|
||||
if 'inp' not in cs.operations[0]:
|
||||
return False
|
||||
|
||||
filename = f'inp/{project}_temp.inp'
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
|
||||
_print_time(f'Start writing temp file "{filename}"...')
|
||||
with open(filename, 'w',encoding="GBK") as f:
|
||||
f.write(str(cs.operations[0]['inp']))
|
||||
_print_time(f'End writing temp file "{filename}"...')
|
||||
|
||||
result = read_inp(project, filename, version)
|
||||
|
||||
#os.remove(filename)
|
||||
|
||||
return result
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
import os
|
||||
from .project_backup import *
|
||||
from .database import ChangeSet
|
||||
from .sections import *
|
||||
from .s1_title import inp_out_title
|
||||
from .s2_junctions import inp_out_junction
|
||||
from .s3_reservoirs import inp_out_reservoir
|
||||
from .s4_tanks import inp_out_tank
|
||||
from .s5_pipes import inp_out_pipe
|
||||
from .s6_pumps import inp_out_pump
|
||||
from .s7_valves import inp_out_valve
|
||||
from .s8_tags import inp_out_tag
|
||||
from .s9_demands import inp_out_demand
|
||||
from .s10_status import inp_out_status
|
||||
from .s11_patterns import inp_out_pattern, inp_out_pattern_v3
|
||||
from .s12_curves import inp_out_curve, inp_out_curve_v3
|
||||
from .s13_controls import inp_out_control
|
||||
from .s14_rules import inp_out_rule
|
||||
from .s15_energy import inp_out_energy
|
||||
from .s16_emitters import inp_out_emitter
|
||||
from .s17_quality import inp_out_quality
|
||||
from .s18_sources import inp_out_source
|
||||
from .s19_reactions import inp_out_reaction
|
||||
from .s20_mixing import inp_out_mixing
|
||||
from .s21_times import inp_out_time
|
||||
from .s22_report import inp_out_report
|
||||
from .s23_options import inp_out_option
|
||||
from .s23_options_v3 import inp_out_option_v3
|
||||
from .s24_coordinates import inp_out_coord
|
||||
from .s25_vertices import inp_out_vertex
|
||||
from .s26_labels import inp_out_label
|
||||
from .s27_backdrop import inp_out_backdrop
|
||||
#from .s28_end import *
|
||||
|
||||
|
||||
def dump_inp(project: str, inp: str, version: str = '3'):
|
||||
if version != '3' and version != '2':
|
||||
version = '2'
|
||||
|
||||
if not have_project(project):
|
||||
return
|
||||
|
||||
project_open = is_project_open(project)
|
||||
|
||||
if not project_open:
|
||||
open_project(project)
|
||||
|
||||
dir = os.getcwd()
|
||||
path = os.path.join(dir, inp)
|
||||
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
file = open(path, mode='w',encoding="UTF-8")
|
||||
|
||||
# REGION, BOUND, REGION_NODES 在 epanet v2 中没有,是我们自己定制的
|
||||
# v2 需要去掉我们自己定制的 section
|
||||
sections = section_names_for_epanetv2
|
||||
if version == '3':
|
||||
sections = section_name
|
||||
|
||||
for name in sections:
|
||||
if name == TITLE:
|
||||
file.write(f'[{name}]\n')
|
||||
else:
|
||||
file.write(f'\n[{name}]\n')
|
||||
|
||||
if name == TITLE:
|
||||
file.write('\n'.join(inp_out_title(project)))
|
||||
|
||||
elif name == JUNCTIONS: # + coords
|
||||
file.write('\n'.join(inp_out_junction(project)))
|
||||
|
||||
elif name == RESERVOIRS: # + coords
|
||||
file.write('\n'.join(inp_out_reservoir(project)))
|
||||
|
||||
elif name == TANKS: # + coords
|
||||
file.write('\n'.join(inp_out_tank(project)))
|
||||
|
||||
elif name == PIPES:
|
||||
file.write('\n'.join(inp_out_pipe(project)))
|
||||
|
||||
elif name == PUMPS:
|
||||
file.write('\n'.join(inp_out_pump(project)))
|
||||
|
||||
elif name == VALVES:
|
||||
file.write('\n'.join(inp_out_valve(project)))
|
||||
|
||||
elif name == TAGS:
|
||||
file.write('\n'.join(inp_out_tag(project)))
|
||||
|
||||
elif name == DEMANDS:
|
||||
file.write('\n'.join(inp_out_demand(project)))
|
||||
|
||||
elif name == STATUS:
|
||||
file.write('\n'.join(inp_out_status(project)))
|
||||
|
||||
elif name == PATTERNS:
|
||||
if version == '3':
|
||||
file.write('\n'.join(inp_out_pattern_v3(project)))
|
||||
else:
|
||||
file.write('\n'.join(inp_out_pattern(project)))
|
||||
|
||||
elif name == CURVES:
|
||||
if version == '3':
|
||||
file.write('\n'.join(inp_out_curve_v3(project)))
|
||||
else:
|
||||
file.write('\n'.join(inp_out_curve(project)))
|
||||
|
||||
elif name == CONTROLS:
|
||||
file.write('\n'.join(inp_out_control(project)))
|
||||
|
||||
elif name == RULES:
|
||||
file.write('\n'.join(inp_out_rule(project)))
|
||||
|
||||
elif name == ENERGY:
|
||||
file.write('\n'.join(inp_out_energy(project)))
|
||||
|
||||
elif name == EMITTERS:
|
||||
file.write('\n'.join(inp_out_emitter(project)))
|
||||
|
||||
elif name == QUALITY:
|
||||
file.write('\n'.join(inp_out_quality(project)))
|
||||
|
||||
elif name == SOURCES:
|
||||
file.write('\n'.join(inp_out_source(project)))
|
||||
|
||||
elif name == REACTIONS:
|
||||
file.write('\n'.join(inp_out_reaction(project)))
|
||||
|
||||
elif name == MIXING:
|
||||
file.write('\n'.join(inp_out_mixing(project)))
|
||||
|
||||
elif name == TIMES:
|
||||
file.write('\n'.join(inp_out_time(project)))
|
||||
|
||||
elif name == REPORT:
|
||||
file.write('\n'.join(inp_out_report(project)))
|
||||
|
||||
elif name == OPTIONS:
|
||||
if version == '3':
|
||||
file.write('\n'.join(inp_out_option_v3(project)))
|
||||
else:
|
||||
file.write('\n'.join(inp_out_option(project)))
|
||||
|
||||
elif name == COORDINATES:
|
||||
file.write('\n'.join(inp_out_coord(project)))
|
||||
|
||||
elif name == VERTICES:
|
||||
file.write('\n'.join(inp_out_vertex(project)))
|
||||
|
||||
elif name == LABELS:
|
||||
file.write('\n'.join(inp_out_label(project)))
|
||||
|
||||
elif name == BACKDROP:
|
||||
file.write('\n'.join(inp_out_backdrop(project)))
|
||||
|
||||
elif name == END:
|
||||
pass # :)
|
||||
|
||||
file.write('\n')
|
||||
|
||||
file.close()
|
||||
|
||||
if not project_open:
|
||||
close_project(project)
|
||||
|
||||
|
||||
def export_inp(project: str, version: str = '3') -> ChangeSet:
|
||||
if version != '3' and version != '2':
|
||||
version = '2'
|
||||
|
||||
if not have_project(project):
|
||||
return ChangeSet()
|
||||
|
||||
project_open = is_project_open(project)
|
||||
|
||||
if not project_open:
|
||||
open_project(project)
|
||||
|
||||
inp = ''
|
||||
|
||||
for name in section_name:
|
||||
if name == TITLE:
|
||||
inp += f'[{name}]\n'
|
||||
else:
|
||||
inp += f'\n[{name}]\n'
|
||||
|
||||
if name == TITLE:
|
||||
inp += '\n'.join(inp_out_title(project))
|
||||
|
||||
elif name == JUNCTIONS: # + coords
|
||||
inp += '\n'.join(inp_out_junction(project))
|
||||
|
||||
elif name == RESERVOIRS: # + coords
|
||||
inp += '\n'.join(inp_out_reservoir(project))
|
||||
|
||||
elif name == TANKS: # + coords
|
||||
inp += '\n'.join(inp_out_tank(project))
|
||||
|
||||
elif name == PIPES:
|
||||
inp += '\n'.join(inp_out_pipe(project))
|
||||
|
||||
elif name == PUMPS:
|
||||
inp += '\n'.join(inp_out_pump(project))
|
||||
|
||||
elif name == VALVES:
|
||||
inp += '\n'.join(inp_out_valve(project))
|
||||
|
||||
elif name == TAGS:
|
||||
inp += '\n'.join(inp_out_tag(project))
|
||||
|
||||
elif name == DEMANDS:
|
||||
inp += '\n'.join(inp_out_demand(project))
|
||||
|
||||
elif name == STATUS:
|
||||
inp += '\n'.join(inp_out_status(project))
|
||||
|
||||
elif name == PATTERNS:
|
||||
if version == '3':
|
||||
inp += '\n'.join(inp_out_pattern_v3(project))
|
||||
else:
|
||||
inp += '\n'.join(inp_out_pattern(project))
|
||||
|
||||
elif name == CURVES:
|
||||
if version == '3':
|
||||
inp += '\n'.join(inp_out_curve_v3(project))
|
||||
else:
|
||||
inp += '\n'.join(inp_out_curve(project))
|
||||
|
||||
elif name == CONTROLS:
|
||||
inp += '\n'.join(inp_out_control(project))
|
||||
|
||||
elif name == RULES:
|
||||
inp += '\n'.join(inp_out_rule(project))
|
||||
|
||||
elif name == ENERGY:
|
||||
inp += '\n'.join(inp_out_energy(project))
|
||||
|
||||
elif name == EMITTERS:
|
||||
inp += '\n'.join(inp_out_emitter(project))
|
||||
|
||||
elif name == QUALITY:
|
||||
inp += '\n'.join(inp_out_quality(project))
|
||||
|
||||
elif name == SOURCES:
|
||||
inp += '\n'.join(inp_out_source(project))
|
||||
|
||||
elif name == REACTIONS:
|
||||
inp += '\n'.join(inp_out_reaction(project))
|
||||
|
||||
elif name == MIXING:
|
||||
inp += '\n'.join(inp_out_mixing(project))
|
||||
|
||||
elif name == TIMES:
|
||||
inp += '\n'.join(inp_out_time(project))
|
||||
|
||||
elif name == REPORT:
|
||||
inp += '\n'.join(inp_out_report(project))
|
||||
|
||||
elif name == OPTIONS:
|
||||
if version == '3':
|
||||
inp += '\n'.join(inp_out_option_v3(project))
|
||||
else:
|
||||
inp += '\n'.join(inp_out_option(project))
|
||||
|
||||
elif name == COORDINATES:
|
||||
inp += '\n'.join(inp_out_coord(project))
|
||||
|
||||
elif name == VERTICES:
|
||||
inp += '\n'.join(inp_out_vertex(project))
|
||||
|
||||
elif name == LABELS:
|
||||
inp += '\n'.join(inp_out_label(project))
|
||||
|
||||
elif name == BACKDROP:
|
||||
inp += '\n'.join(inp_out_backdrop(project))
|
||||
|
||||
elif name == END:
|
||||
pass # :)
|
||||
|
||||
inp += '\n'
|
||||
|
||||
if not project_open:
|
||||
close_project(project)
|
||||
|
||||
return ChangeSet({'operation': 'export', 'inp': inp})
|
||||
@@ -0,0 +1,36 @@
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
load_dotenv()
|
||||
|
||||
pg_name = os.getenv("DB_NAME")
|
||||
pg_host = os.getenv("DB_HOST")
|
||||
pg_port = os.getenv("DB_PORT")
|
||||
pg_user = os.getenv("DB_USER")
|
||||
pg_password = os.getenv("DB_PASSWORD")
|
||||
|
||||
|
||||
def get_pgconn_string(
|
||||
db_name=pg_name,
|
||||
db_host=pg_host,
|
||||
db_port=pg_port,
|
||||
db_user=pg_user,
|
||||
db_password=pg_password,
|
||||
):
|
||||
"""返回 PostgreSQL 连接字符串"""
|
||||
return f"dbname={db_name} host={db_host} port={db_port} user={db_user} password={db_password}"
|
||||
|
||||
|
||||
def get_pg_config():
|
||||
"""返回 PostgreSQL 配置变量的字典"""
|
||||
return {
|
||||
"name": pg_name,
|
||||
"host": pg_host,
|
||||
"port": pg_port,
|
||||
"user": pg_user,
|
||||
}
|
||||
|
||||
|
||||
def get_pg_password():
|
||||
"""返回密码(谨慎使用)"""
|
||||
return pg_password
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
import os
|
||||
import psycopg as pg
|
||||
from psycopg.rows import dict_row
|
||||
from .connection import g_conn_dict as conn
|
||||
from .postgresql_info import get_pgconn_string, get_pg_config, get_pg_password
|
||||
|
||||
# no undo/redo
|
||||
|
||||
_server_databases = ["template0", "template1", "postgres", "project"]
|
||||
|
||||
|
||||
def list_project() -> list[str]:
|
||||
ps = []
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
for p in cur.execute(
|
||||
f"select datname from pg_database where datname <> 'postgres' and datname <> 'template0' and datname <> 'template1' and datname <> 'project'"
|
||||
):
|
||||
ps.append(p["datname"])
|
||||
return ps
|
||||
|
||||
|
||||
def have_project(name: str) -> bool:
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"select * from pg_database where datname = '{name}'")
|
||||
return cur.rowcount > 0
|
||||
|
||||
|
||||
def copy_project(source: str, new: str) -> None:
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f'create database "{new}" with template = {source}')
|
||||
|
||||
|
||||
# 2025-02-07, WMH
|
||||
# copyproject会把pg中operation这个表的全部内容也加进去,我们实际项目运行一周后operation这个表会变得特别大,导致CopyProject花费的时间很长,CopyProjectEx把operation的在复制时没有一块复制过去,节省时间
|
||||
class CopyProjectEx:
|
||||
@staticmethod
|
||||
def create_database(connection, new_db):
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f'create database "{new_db}"')
|
||||
connection.commit()
|
||||
|
||||
@staticmethod
|
||||
def execute_pg_dump(source_db, exclude_table_list):
|
||||
|
||||
os.environ["PGPASSWORD"] = get_pg_password() # 设置密码环境变量
|
||||
pg_config = get_pg_config()
|
||||
host = pg_config["host"]
|
||||
port = pg_config["port"]
|
||||
user = pg_config["user"]
|
||||
dump_command_structure = f"pg_dump -h {host} -p {port} -U {user} -F c -s -f source_db_structure.dump {source_db}"
|
||||
os.system(dump_command_structure)
|
||||
|
||||
if exclude_table_list is not None:
|
||||
exclude_table = " ".join(["-T {}".format(i) for i in exclude_table_list])
|
||||
dump_command_db = f"pg_dump -h {host} -p {port} -U {user} -F c -a {exclude_table} -f source_db.dump {source_db}"
|
||||
else:
|
||||
dump_command_db = f"pg_dump -h {host} -p {port} -U {user} -F c -a -f source_db.dump {source_db}"
|
||||
os.system(dump_command_db)
|
||||
|
||||
@staticmethod
|
||||
def execute_pg_restore(new_db):
|
||||
os.environ["PGPASSWORD"] = get_pg_password() # 设置密码环境变量
|
||||
pg_config = get_pg_config()
|
||||
host = pg_config["host"]
|
||||
port = pg_config["port"]
|
||||
user = pg_config["user"]
|
||||
restore_command_structure = f"pg_restore -h {host} -p {port} -U {user} -d {new_db} source_db_structure.dump"
|
||||
os.system(restore_command_structure)
|
||||
|
||||
restore_command_db = (
|
||||
f"pg_restore -h {host} -p {port} -U {user} -d {new_db} source_db.dump"
|
||||
)
|
||||
os.system(restore_command_db)
|
||||
|
||||
@staticmethod
|
||||
def init_operation_table(connection, excluded_table):
|
||||
with connection.cursor() as cursor:
|
||||
if "operation" in excluded_table:
|
||||
insert_query = "insert into operation (id, redo, undo, redo_cs, undo_cs) values (0, '', '', '', '')"
|
||||
cursor.execute(insert_query)
|
||||
|
||||
if "current_operation" in excluded_table:
|
||||
insert_query = "insert into current_operation (id) values (0)"
|
||||
cursor.execute(insert_query)
|
||||
|
||||
if "restore_operation" in excluded_table:
|
||||
insert_query = "insert into restore_operation (id) values (0)"
|
||||
cursor.execute(insert_query)
|
||||
|
||||
if "batch_operation" in excluded_table:
|
||||
insert_query = "insert into batch_operation (id, redo, undo, redo_cs, undo_cs) values (0, '', '', '', '')"
|
||||
cursor.execute(insert_query)
|
||||
|
||||
if "operation_table" in excluded_table:
|
||||
insert_query = (
|
||||
"insert into operation_table (option) values ('operation')"
|
||||
)
|
||||
cursor.execute(insert_query)
|
||||
connection.commit()
|
||||
|
||||
def __call__(self, source: str, new_db: str, excluded_tables: [str] = None) -> None:
|
||||
source_connection = pg.connect(conninfo=get_pgconn_string(), autocommit=True)
|
||||
|
||||
self.create_database(source_connection, new_db)
|
||||
|
||||
self.execute_pg_dump(source, excluded_tables)
|
||||
self.execute_pg_restore(new_db)
|
||||
source_connection.close()
|
||||
|
||||
new_db_connection = pg.connect(
|
||||
conninfo=get_pgconn_string(db_name=new_db), autocommit=True
|
||||
)
|
||||
self.init_operation_table(new_db_connection, excluded_tables)
|
||||
new_db_connection.close()
|
||||
|
||||
|
||||
def create_project(name: str) -> None:
|
||||
return copy_project("project", name)
|
||||
|
||||
|
||||
def delete_project(name: str) -> None:
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
f"select pg_terminate_backend(pid) from pg_stat_activity where datname = '{name}'"
|
||||
)
|
||||
cur.execute(f'drop database "{name}"')
|
||||
|
||||
|
||||
def clean_project(excluded: list[str] = []) -> None:
|
||||
projects = list_project()
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
row = cur.execute(f"select current_database()").fetchone()
|
||||
if row != None:
|
||||
current_db = row["current_database"]
|
||||
if current_db in projects:
|
||||
projects.remove(current_db)
|
||||
for project in projects:
|
||||
if project in _server_databases or project in excluded:
|
||||
continue
|
||||
cur.execute(
|
||||
f"select pg_terminate_backend(pid) from pg_stat_activity where datname = '{project}'"
|
||||
)
|
||||
cur.execute(f'drop database "{project}"')
|
||||
|
||||
|
||||
def open_project(name: str) -> None:
|
||||
if name not in conn:
|
||||
conn[name] = pg.connect(
|
||||
conninfo=get_pgconn_string(db_name=name), autocommit=True
|
||||
)
|
||||
|
||||
|
||||
def is_project_open(name: str) -> bool:
|
||||
return name in conn
|
||||
|
||||
|
||||
def close_project(name: str) -> None:
|
||||
if name in conn:
|
||||
conn[name].close()
|
||||
del conn[name]
|
||||
@@ -0,0 +1,152 @@
|
||||
import os
|
||||
import psycopg as pg
|
||||
from psycopg.rows import dict_row
|
||||
from .connection import g_conn_dict as conn
|
||||
from .postgresql_info import get_pgconn_string
|
||||
# no undo/redo
|
||||
|
||||
_server_databases = ['template0', 'template1', 'postgres', 'project']
|
||||
|
||||
|
||||
def list_project() -> list[str]:
|
||||
ps = []
|
||||
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
for p in cur.execute(f"select datname from pg_database where datname <> 'postgres' and datname <> 'template0' and datname <> 'template1' and datname <> 'project'"):
|
||||
ps.append(p['datname'])
|
||||
return ps
|
||||
|
||||
|
||||
def have_project(name: str) -> bool:
|
||||
with pg.connect(conninfo=get_pgconn_string(db_name=name), autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"select * from pg_database where datname = '{name}'")
|
||||
return cur.rowcount > 0
|
||||
|
||||
|
||||
def copy_project(source: str, new: str) -> None:
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f'create database "{new}" with template = {source}')
|
||||
|
||||
# 2025-02-07, WMH
|
||||
# copyproject会把pg中operation这个表的全部内容也加进去,我们实际项目运行一周后operation这个表会变得特别大,导致CopyProject花费的时间很长,CopyProjectEx把operation的在复制时没有一块复制过去,节省时间
|
||||
class CopyProjectEx:
|
||||
@ staticmethod
|
||||
def create_database(connection, new_db):
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f'create database "{new_db}"')
|
||||
connection.commit()
|
||||
|
||||
@staticmethod
|
||||
def execute_pg_dump(hostname, source_db, exclude_table_list):
|
||||
dump_command_structure = (
|
||||
f'pg_dump -h {hostname} -F c -s -f source_db_structure.dump {source_db}'
|
||||
)
|
||||
os.system(dump_command_structure)
|
||||
|
||||
if exclude_table_list is not None:
|
||||
exclude_table = ' '.join(['-T {}'.format(i) for i in exclude_table_list])
|
||||
dump_command_db = (
|
||||
f'pg_dump -h {hostname} -F c -a {exclude_table} -f source_db.dump {source_db}'
|
||||
)
|
||||
else:
|
||||
dump_command_db = (
|
||||
f'pg_dump -h {hostname} -F c -a -f source_db.dump {source_db}'
|
||||
)
|
||||
os.system(dump_command_db)
|
||||
|
||||
@staticmethod
|
||||
def execute_pg_restore(hostname, new_db):
|
||||
restore_command_structure = (
|
||||
f'pg_restore -h {hostname} -d {new_db} source_db_structure.dump'
|
||||
)
|
||||
os.system(restore_command_structure)
|
||||
|
||||
restore_command_db = (
|
||||
f'pg_restore -h {hostname} -d {new_db} source_db.dump'
|
||||
)
|
||||
os.system(restore_command_db)
|
||||
|
||||
@staticmethod
|
||||
def init_operation_table(connection, excluded_table):
|
||||
with connection.cursor() as cursor:
|
||||
if 'operation' in excluded_table:
|
||||
insert_query \
|
||||
= "insert into operation (id, redo, undo, redo_cs, undo_cs) values (0, '', '', '', '')"
|
||||
cursor.execute(insert_query)
|
||||
|
||||
if 'current_operation' in excluded_table:
|
||||
insert_query \
|
||||
= "insert into current_operation (id) values (0)"
|
||||
cursor.execute(insert_query)
|
||||
|
||||
if 'restore_operation' in excluded_table:
|
||||
insert_query \
|
||||
= "insert into restore_operation (id) values (0)"
|
||||
cursor.execute(insert_query)
|
||||
|
||||
if 'batch_operation' in excluded_table:
|
||||
insert_query \
|
||||
= "insert into batch_operation (id, redo, undo, redo_cs, undo_cs) values (0, '', '', '', '')"
|
||||
cursor.execute(insert_query)
|
||||
|
||||
if 'operation_table' in excluded_table:
|
||||
insert_query \
|
||||
= "insert into operation_table (option) values ('operation')"
|
||||
cursor.execute(insert_query)
|
||||
connection.commit()
|
||||
|
||||
def __call__(self, source: str, new: str, excluded_table: [str] = None) -> None:
|
||||
connection = pg.connect(conninfo=get_pgconn_string(), autocommit=True)
|
||||
|
||||
self.create_database(connection, new)
|
||||
self.execute_pg_dump('127.0.0.1', source, excluded_table)
|
||||
self.execute_pg_restore('127.0.0.1', new)
|
||||
|
||||
connection = pg.connect(conninfo=get_pgconn_string(db_name=new), autocommit=True)
|
||||
self.init_operation_table(connection, excluded_table)
|
||||
|
||||
|
||||
def create_project(name: str) -> None:
|
||||
return copy_project('project', name)
|
||||
|
||||
|
||||
def delete_project(name: str) -> None:
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"select pg_terminate_backend(pid) from pg_stat_activity where datname = '{name}'")
|
||||
cur.execute(f'drop database "{name}"')
|
||||
|
||||
|
||||
def clean_project(excluded: list[str] = []) -> None:
|
||||
projects = list_project()
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
row = cur.execute(f"select current_database()").fetchone()
|
||||
if row != None:
|
||||
current_db = row['current_database']
|
||||
if current_db in projects:
|
||||
projects.remove(current_db)
|
||||
for project in projects:
|
||||
if project in _server_databases or project in excluded:
|
||||
continue
|
||||
cur.execute(f"select pg_terminate_backend(pid) from pg_stat_activity where datname = '{project}'")
|
||||
cur.execute(f'drop database "{project}"')
|
||||
|
||||
|
||||
def open_project(name: str) -> None:
|
||||
if name not in conn:
|
||||
conn[name] = pg.connect(conninfo=get_pgconn_string(db_name=name), autocommit=True)
|
||||
|
||||
|
||||
def is_project_open(name: str) -> bool:
|
||||
return name in conn
|
||||
|
||||
|
||||
def close_project(name: str) -> None:
|
||||
if name in conn:
|
||||
conn[name].close()
|
||||
del conn[name]
|
||||
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
from psycopg.rows import dict_row, Row
|
||||
from .connection import g_conn_dict as conn
|
||||
from .database import read
|
||||
from typing import Any
|
||||
|
||||
_NODE = '_node'
|
||||
_LINK = '_link'
|
||||
_CURVE = '_curve'
|
||||
_PATTERN = '_pattern'
|
||||
_REGION = '_region'
|
||||
|
||||
JUNCTION = 'junction'
|
||||
RESERVOIR = 'reservoir'
|
||||
TANK = 'tank'
|
||||
PIPE = 'pipe'
|
||||
PUMP = 'pump'
|
||||
VALVE = 'valve'
|
||||
|
||||
PATTERN = 'pattern'
|
||||
CURVE = 'curve'
|
||||
|
||||
REGION = 'region'
|
||||
|
||||
# DingZQ, 2025-02-05
|
||||
'''
|
||||
C++ 代码里已经定义了这些 enum 值
|
||||
{
|
||||
kNothing = -1,
|
||||
|
||||
//Node
|
||||
kReservoir = 0,
|
||||
kTank,
|
||||
kJunction,
|
||||
|
||||
//Link
|
||||
kPipe,
|
||||
kPump,
|
||||
kValve,
|
||||
'''
|
||||
ELEMENT_TYPES : dict[str, int] = {
|
||||
RESERVOIR : 0,
|
||||
TANK : 1,
|
||||
JUNCTION : 2,
|
||||
PIPE : 3,
|
||||
PUMP : 4,
|
||||
VALVE : 5,
|
||||
}
|
||||
|
||||
def _get_from(name: str, id: str, base_type: str) -> Row | None:
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select * from {base_type} where id = '{id}'")
|
||||
return cur.fetchone()
|
||||
|
||||
|
||||
def is_node(name: str, id: str) -> bool:
|
||||
return _get_from(name, id, _NODE) != None
|
||||
|
||||
|
||||
def is_junction(name: str, id: str) -> bool:
|
||||
row = _get_from(name, id, _NODE)
|
||||
return row != None and row['type'] == JUNCTION
|
||||
|
||||
|
||||
def is_reservoir(name: str, id: str) -> bool:
|
||||
row = _get_from(name, id, _NODE)
|
||||
return row != None and row['type'] == RESERVOIR
|
||||
|
||||
|
||||
def is_tank(name: str, id: str) -> bool:
|
||||
row = _get_from(name, id, _NODE)
|
||||
return row != None and row['type'] == TANK
|
||||
|
||||
|
||||
def is_link(name: str, id: str) -> bool:
|
||||
return _get_from(name, id, _LINK) != None
|
||||
|
||||
|
||||
def is_pipe(name: str, id: str) -> bool:
|
||||
row = _get_from(name, id, _LINK)
|
||||
return row != None and row['type'] == PIPE
|
||||
|
||||
|
||||
def is_pump(name: str, id: str) -> bool:
|
||||
row = _get_from(name, id, _LINK)
|
||||
return row != None and row['type'] == PUMP
|
||||
|
||||
|
||||
def is_valve(name: str, id: str) -> bool:
|
||||
row = _get_from(name, id, _LINK)
|
||||
return row != None and row['type'] == VALVE
|
||||
|
||||
# DingZQ, 2025-02-05
|
||||
def get_node_type(name: str, node_id: str) -> str:
|
||||
row = _get_from(name, node_id, _NODE)
|
||||
return row['type']
|
||||
|
||||
|
||||
def get_link_type(name: str, link_id: str) -> str:
|
||||
row = _get_from(name, link_id, _LINK)
|
||||
return row['type']
|
||||
|
||||
def get_element_type(name: str, element_id: str) -> str:
|
||||
if is_node(name, element_id):
|
||||
return get_node_type(name, element_id)
|
||||
elif is_link(name, element_id):
|
||||
return get_link_type(name, element_id)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_element_type_value(name: str, element_id: str) -> int:
|
||||
return ELEMENT_TYPES[get_element_type(name, element_id)]
|
||||
|
||||
def is_curve(name: str, id: str) -> bool:
|
||||
|
||||
return _get_from(name, id, _CURVE) != None
|
||||
|
||||
|
||||
def is_pattern(name: str, id: str) -> bool:
|
||||
return _get_from(name, id, _PATTERN) != None
|
||||
|
||||
|
||||
def is_region(name: str, id: str) -> bool:
|
||||
return _get_from(name, id, _REGION) != None
|
||||
|
||||
|
||||
def _get_all(name: str, base_type: str) -> list[str]:
|
||||
ids : list[str] = []
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select id from {base_type} order by id")
|
||||
for record in cur:
|
||||
ids.append(record['id'])
|
||||
return ids
|
||||
|
||||
|
||||
def get_nodes(name: str) -> list[str]:
|
||||
return _get_all(name, _NODE)
|
||||
|
||||
# DingZQ
|
||||
def _get_nodes_by_type(name: str, type: str) -> list[str]:
|
||||
ids : list[str] = []
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select id from {_NODE} where type = '{type}' order by id")
|
||||
for record in cur:
|
||||
ids.append(record['id'])
|
||||
return ids
|
||||
|
||||
# DingZQ
|
||||
def get_nodes_id_and_type(name: str) -> dict[str, str]:
|
||||
nodes_id_and_type: dict[str, str] = {}
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select id, type from {_NODE} order by id")
|
||||
for record in cur:
|
||||
nodes_id_and_type[record['id']] = record['type']
|
||||
return nodes_id_and_type
|
||||
|
||||
# DingZQ 2024-12-31
|
||||
def get_major_nodes(name: str, diameter: int) -> list[str]:
|
||||
major_nodes_set = set()
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select node1, node2 from pipes where diameter > {diameter}")
|
||||
for record in cur:
|
||||
major_nodes_set.add(record['node1'])
|
||||
major_nodes_set.add(record['node2'])
|
||||
|
||||
return list(major_nodes_set)
|
||||
|
||||
# DingZQs
|
||||
def get_junctions(name: str) -> list[str]:
|
||||
return _get_nodes_by_type(name, JUNCTION)
|
||||
|
||||
# DingZQ
|
||||
def get_reservoirs(name: str) -> list[str]:
|
||||
return _get_nodes_by_type(name, RESERVOIR)
|
||||
|
||||
# DingZQ
|
||||
def get_tanks(name: str) -> list[str]:
|
||||
return _get_nodes_by_type(name, TANK)
|
||||
|
||||
# DingZQ
|
||||
def get_links(name: str) -> list[str]:
|
||||
return _get_all(name, _LINK)
|
||||
|
||||
# DingZQ
|
||||
def _get_links_by_type(name: str, type: str) -> list[str]:
|
||||
ids : list[str] = []
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select id from {_LINK} where type = '{type}' order by id")
|
||||
for record in cur:
|
||||
ids.append(record['id'])
|
||||
return ids
|
||||
|
||||
# DingZQ
|
||||
def get_links_id_and_type(name: str) -> dict[str, str]:
|
||||
links_id_and_type: dict[str, str] = {}
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select id, type from {_LINK} order by id")
|
||||
for record in cur:
|
||||
links_id_and_type[record['id']] = record['type']
|
||||
return links_id_and_type
|
||||
|
||||
# DingZQ 2024-12-31
|
||||
# 获取直径大于800的管道
|
||||
def get_major_pipes(name: str, diameter: int) -> list[str]:
|
||||
major_pipe_ids: list[str] = []
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select id from pipes where diameter > {diameter} order by id")
|
||||
for record in cur:
|
||||
major_pipe_ids.append(record['id'])
|
||||
return major_pipe_ids
|
||||
|
||||
# DingZQ
|
||||
def get_pipes(name: str) -> list[str]:
|
||||
return _get_links_by_type(name, PIPE)
|
||||
|
||||
# DingZQ
|
||||
def get_pumps(name: str) -> list[str]:
|
||||
return _get_links_by_type(name, PUMP)
|
||||
|
||||
# DingZQ
|
||||
def get_valves(name: str) -> list[str]:
|
||||
return _get_links_by_type(name, VALVE)
|
||||
|
||||
|
||||
def get_curves(name: str) -> list[str]:
|
||||
return _get_all(name, _CURVE)
|
||||
|
||||
|
||||
def get_patterns(name: str) -> list[str]:
|
||||
return _get_all(name, _PATTERN)
|
||||
|
||||
def get_regions(name: str) -> list[str]:
|
||||
return _get_all(name, _REGION)
|
||||
|
||||
def get_node_links(name: str, id: str) -> list[str]:
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
links: list[str] = []
|
||||
for p in cur.execute(f"select id from pipes where node1 = '{id}' or node2 = '{id}'").fetchall():
|
||||
links.append(p['id'])
|
||||
for p in cur.execute(f"select id from pumps where node1 = '{id}' or node2 = '{id}'").fetchall():
|
||||
links.append(p['id'])
|
||||
for p in cur.execute(f"select id from valves where node1 = '{id}' or node2 = '{id}'").fetchall():
|
||||
links.append(p['id'])
|
||||
return links
|
||||
|
||||
|
||||
def get_link_nodes(name: str, id: str) -> list[str]:
|
||||
row = {}
|
||||
if is_pipe(name, id):
|
||||
row = read(name, f"select node1, node2 from pipes where id = '{id}'")
|
||||
elif is_pump(name, id):
|
||||
row = read(name, f"select node1, node2 from pumps where id = '{id}'")
|
||||
elif is_valve(name, id):
|
||||
row = read(name, f"select node1, node2 from valves where id = '{id}'")
|
||||
return [str(row['node1']), str(row['node2'])]
|
||||
|
||||
def get_region_type(name: str, id: str)->str:
|
||||
if(is_region(name,id)):
|
||||
type = read(name, f"select type from _region where id = '{id}'")
|
||||
return type
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
LINK_STATUS_OPEN = 'OPEN'
|
||||
LINK_STATUS_CLOSED = 'CLOSED'
|
||||
LINK_STATUS_ACTIVE = 'ACTIVE'
|
||||
|
||||
|
||||
def get_status_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'link' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'status' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'setting' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_status(name: str, link: str) -> dict[str, Any]:
|
||||
s = try_read(name, f"select * from status where link = '{link}'")
|
||||
if s == None:
|
||||
return { 'link': link, 'status': None, 'setting': None }
|
||||
d = {}
|
||||
d['link'] = str(s['link'])
|
||||
d['status'] = str(s['status']) if s['status'] != None else None
|
||||
d['setting'] = float(s['setting']) if s['setting'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Status(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'status'
|
||||
self.link = str(input['link'])
|
||||
self.status = str(input['status']) if 'status' in input and input['status'] != None else None
|
||||
self.setting = float(input['setting']) if 'setting' in input and input['setting'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_link = f"'{self.link}'"
|
||||
self.f_status = f"'{self.status}'" if self.status != None else 'null'
|
||||
self.f_setting = self.setting if self.setting != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'link': self.link, 'status': self.status, 'setting': self.setting }
|
||||
|
||||
|
||||
def _set_status(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Status(get_status(name, cs.operations[0]['link']))
|
||||
raw_new = get_status(name, cs.operations[0]['link'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_status_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Status(raw_new)
|
||||
|
||||
redo_sql = f"delete from status where link = {new.f_link};"
|
||||
if new.status != None or new.setting != None:
|
||||
redo_sql += f"\ninsert into status (link, status, setting) values ({new.f_link}, {new.f_status}, {new.f_setting});"
|
||||
|
||||
undo_sql = f"delete from status where link = {old.f_link};"
|
||||
if old.status != None or old.setting != None:
|
||||
undo_sql += f"\ninsert into status (link, status, setting) values ({old.f_link}, {old.f_status}, {old.f_setting});"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_status(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_status(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# link value
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_status(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
link = str(tokens[0])
|
||||
value = tokens[1].upper()
|
||||
if value == LINK_STATUS_OPEN or value == LINK_STATUS_CLOSED or value == LINK_STATUS_ACTIVE:
|
||||
return str(f"insert into status (link, status, setting) values ('{link}', '{value}', null);")
|
||||
else:
|
||||
return str(f"insert into status (link, status, setting) values ('{link}', null, {float(value)});")
|
||||
|
||||
|
||||
def inp_out_status(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from status')
|
||||
for obj in objs:
|
||||
link = obj['link']
|
||||
status = obj['status'] if obj['status'] != None else ''
|
||||
setting = obj['setting'] if obj['setting'] != None else ''
|
||||
if status != '':
|
||||
lines.append(f'{link} {status}')
|
||||
if setting != '':
|
||||
lines.append(f'{link} {setting}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_status_by_link(name: str, link: str) -> ChangeSet:
|
||||
row = try_read(name, f"select * from status where link = '{link}'")
|
||||
if row == None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_update_prefix | {'type': 'status', 'link': link, 'status': None, 'setting': None})
|
||||
@@ -0,0 +1,163 @@
|
||||
from .database import *
|
||||
|
||||
PATTERN_V3_TYPE_FIXED = 'FIXED'
|
||||
PATTERN_V3_TYPE_VARIABLE = 'VARIABLE'
|
||||
|
||||
pattern_v3_types = [PATTERN_V3_TYPE_FIXED, PATTERN_V3_TYPE_VARIABLE]
|
||||
|
||||
def get_pattern_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'factors' : {'type': 'float_list' , 'optional': False , 'readonly': False } }
|
||||
|
||||
|
||||
def get_pattern(name: str, id: str) -> dict[str, Any]:
|
||||
p_one = try_read(name, f"select * from _pattern where id = '{id}'")
|
||||
if p_one == None:
|
||||
return {}
|
||||
pas = read_all(name, f"select * from patterns where id = '{id}' order by _order")
|
||||
ps = []
|
||||
for r in pas:
|
||||
ps.append(float(r['factor']))
|
||||
return { 'id': id, 'factors': ps }
|
||||
|
||||
|
||||
def _set_pattern(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = f"'{id}'"
|
||||
|
||||
old = get_pattern(name, id)
|
||||
|
||||
new = { 'id': id }
|
||||
if 'factors' in cs.operations[0]:
|
||||
new['factors'] = cs.operations[0]['factors']
|
||||
else:
|
||||
new['factors'] = old['factors']
|
||||
|
||||
# TODO: transaction ?
|
||||
redo_sql = f"delete from patterns where id = {f_id};"
|
||||
for f_factor in new['factors']:
|
||||
redo_sql += f"\ninsert into patterns (id, factor) values ({f_id}, {f_factor});"
|
||||
|
||||
undo_sql = f"delete from patterns where id = {f_id};"
|
||||
for f_factor in old['factors']:
|
||||
undo_sql += f"\ninsert into patterns (id, factor) values ({f_id}, {f_factor});"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'pattern' } | new
|
||||
undo_cs = g_update_prefix | { 'type': 'pattern' } | old
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_pattern(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pattern(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_pattern(name, cs))
|
||||
|
||||
|
||||
def _add_pattern(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = f"'{id}'"
|
||||
|
||||
new = { 'id': id, 'factors': cs.operations[0]['factors'] }
|
||||
|
||||
# TODO: transaction ?
|
||||
redo_sql = f"insert into _pattern (id) values ({f_id});"
|
||||
for f_factor in new['factors']:
|
||||
redo_sql += f"\ninsert into patterns (id, factor) values ({f_id}, {f_factor});"
|
||||
|
||||
undo_sql = f"delete from patterns where id = {f_id};"
|
||||
undo_sql += f"\ndelete from _pattern where id = {f_id};"
|
||||
|
||||
redo_cs = g_add_prefix | { 'type': 'pattern' } | new
|
||||
undo_cs = g_delete_prefix | { 'type': 'pattern' } | { 'id': id }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_pattern(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pattern(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_pattern(name, cs))
|
||||
|
||||
|
||||
def _delete_pattern(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = f"'{id}'"
|
||||
|
||||
old = get_pattern(name, id)
|
||||
|
||||
redo_sql = f"delete from patterns where id = {f_id};"
|
||||
redo_sql += f"\ndelete from _pattern where id = {f_id};"
|
||||
|
||||
# TODO: transaction ?
|
||||
undo_sql = f"insert into _pattern (id) values ({f_id});"
|
||||
for f_factor in old['factors']:
|
||||
undo_sql += f"\ninsert into patterns (id, factor) values ({f_id}, {f_factor});"
|
||||
|
||||
redo_cs = g_delete_prefix | { 'type': 'pattern' } | { 'id': id }
|
||||
undo_cs = g_add_prefix | { 'type': 'pattern' } | old
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_pattern(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pattern(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_pattern(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][IN][OUT]
|
||||
# ;desc
|
||||
# id mult1 mult2 .....
|
||||
#--------------------------------------------------------------
|
||||
#--------------------------------------------------------------
|
||||
# [EPA3][IN][OUT]
|
||||
# id FIXED (interval)
|
||||
# id factor1 factor2 ...
|
||||
# id VARIABLE
|
||||
# id time1 factor1 time2 factor2 ...
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_pattern(line: str, fixed: bool = True) -> str:
|
||||
tokens = line.split()
|
||||
sql = ''
|
||||
if fixed:
|
||||
for token in tokens[1:]:
|
||||
sql += f"insert into patterns (id, factor) values ('{tokens[0]}', {float(token)});"
|
||||
else:
|
||||
for token in tokens[1::2]:
|
||||
sql += f"insert into patterns (id, factor) values ('{tokens[0]}', {float(token)});"
|
||||
return sql
|
||||
|
||||
|
||||
def inp_out_pattern(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, f"select * from patterns order by _order")
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
factor = obj['factor']
|
||||
lines.append(f'{id} {factor}')
|
||||
return lines
|
||||
|
||||
|
||||
def inp_out_pattern_v3(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, f"select * from patterns order by _order")
|
||||
ids = []
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
if id not in ids:
|
||||
# for EPA3, ignore time of variable pattern...
|
||||
lines.append(f'{id} FIXED')
|
||||
ids.append(id)
|
||||
factor = obj['factor']
|
||||
lines.append(f'{id} {factor}')
|
||||
return lines
|
||||
@@ -0,0 +1,186 @@
|
||||
from .database import *
|
||||
|
||||
CURVE_TYPE_PUMP = 'PUMP'
|
||||
CURVE_TYPE_EFFICIENCY = 'EFFICIENCY'
|
||||
CURVE_TYPE_VOLUME = 'VOLUME'
|
||||
CURVE_TYPE_HEADLOSS = 'HEADLOSS'
|
||||
|
||||
curve_types = [CURVE_TYPE_PUMP, CURVE_TYPE_EFFICIENCY, CURVE_TYPE_VOLUME, CURVE_TYPE_HEADLOSS]
|
||||
|
||||
def get_curve_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'c_type' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'coords' : {'type': 'list' , 'optional': False , 'readonly': False,
|
||||
'element': { 'x' : {'type': 'float' , 'optional': False , 'readonly': False },
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False } }}}
|
||||
|
||||
|
||||
def get_curve(name: str, id: str) -> dict[str, Any]:
|
||||
c_one = try_read(name, f"select * from _curve where id = '{id}'")
|
||||
if c_one == None:
|
||||
return {}
|
||||
cus = read_all(name, f"select * from curves where id = '{id}' order by _order")
|
||||
cs = []
|
||||
for r in cus:
|
||||
cs.append({ 'x': float(r['x']), 'y': float(r['y']) })
|
||||
d = {}
|
||||
d['id'] = id
|
||||
d['c_type'] = c_one['type']
|
||||
d['coords'] = cs
|
||||
return d
|
||||
|
||||
|
||||
def _set_curve(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = f"'{id}'"
|
||||
|
||||
old = get_curve(name, id)
|
||||
old_f_type = f"'{old['c_type']}'"
|
||||
|
||||
new = { 'id': id }
|
||||
if 'coords' in cs.operations[0]:
|
||||
new['coords'] = cs.operations[0]['coords']
|
||||
else:
|
||||
new['coords'] = old['coords']
|
||||
if 'c_type' in cs.operations[0]:
|
||||
new['c_type'] = cs.operations[0]['c_type']
|
||||
else:
|
||||
new['c_type'] = old['c_type']
|
||||
new_f_type = f"'{new['c_type']}'"
|
||||
|
||||
# TODO: transaction ?
|
||||
redo_sql = f"delete from curves where id = {f_id};"
|
||||
redo_sql += f"\nupdate _curve set type = {new_f_type} where id = {f_id};"
|
||||
for xy in new['coords']:
|
||||
f_x, f_y = xy['x'], xy['y']
|
||||
redo_sql += f"\ninsert into curves (id, x, y) values ({f_id}, {f_x}, {f_y});"
|
||||
|
||||
undo_sql = f"delete from curves where id = {f_id};"
|
||||
undo_sql += f"\nupdate _curve set type = {old_f_type} where id = {f_id};"
|
||||
for xy in old['coords']:
|
||||
f_x, f_y = xy['x'], xy['y']
|
||||
undo_sql += f"\ninsert into curves (id, x, y) values ({f_id}, {f_x}, {f_y});"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'curve' } | new
|
||||
undo_cs = g_update_prefix | { 'type': 'curve' } | old
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_curve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_curve(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_curve(name, cs))
|
||||
|
||||
|
||||
def _add_curve(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = f"'{id}'"
|
||||
|
||||
new = { 'id': id, 'c_type': cs.operations[0]['c_type'], 'coords': [] }
|
||||
new_f_type = f"'{new['c_type']}'"
|
||||
|
||||
# TODO: transaction ?
|
||||
redo_sql = f"insert into _curve (id, type) values ({f_id}, {new_f_type});"
|
||||
for xy in cs.operations[0]['coords']:
|
||||
x, y = float(xy['x']), float(xy['y'])
|
||||
f_x, f_y = x, y
|
||||
redo_sql += f"\ninsert into curves (id, x, y) values ({f_id}, {f_x}, {f_y});"
|
||||
new['coords'].append({ 'x': x, 'y': y })
|
||||
|
||||
undo_sql = f"delete from curves where id = {f_id};"
|
||||
undo_sql += f"\ndelete from _curve where id = {f_id};"
|
||||
|
||||
redo_cs = g_add_prefix | { 'type': 'curve' } | new
|
||||
undo_cs = g_delete_prefix | { 'type': 'curve' } | { 'id' : id }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_curve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_curve(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_curve(name, cs))
|
||||
|
||||
|
||||
def _delete_curve(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = f"'{id}'"
|
||||
|
||||
old = get_curve(name, id)
|
||||
old_f_type = f"'{old['c_type']}'"
|
||||
|
||||
redo_sql = f"delete from curves where id = {f_id};"
|
||||
redo_sql += f"\ndelete from _curve where id = {f_id};"
|
||||
|
||||
# TODO: transaction ?
|
||||
undo_sql = f"insert into _curve (id, type) values ({f_id}, {old_f_type});"
|
||||
for xy in old['coords']:
|
||||
f_x, f_y = xy['x'], xy['y']
|
||||
undo_sql += f"\ninsert into curves (id, x, y) values ({f_id}, {f_x}, {f_y});"
|
||||
|
||||
redo_cs = g_delete_prefix | { 'type': 'curve' } | { 'id' : id }
|
||||
undo_cs = g_add_prefix | { 'type': 'curve' } | old
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_curve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_curve(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_curve(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][IN][OUT]
|
||||
# ;type: desc
|
||||
# id x y
|
||||
#--------------------------------------------------------------
|
||||
#--------------------------------------------------------------
|
||||
# [EPA3][IN][OUT]
|
||||
# id type
|
||||
# id x y
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_curve(line: str) -> str:
|
||||
tokens = line.split()
|
||||
return str(f"insert into curves (id, x, y) values ('{tokens[0]}', {float(tokens[1])}, {float(tokens[2])});")
|
||||
|
||||
|
||||
def inp_out_curve(name: str) -> list[str]:
|
||||
lines = []
|
||||
types = read_all(name, f"select * from _curve")
|
||||
for type in types:
|
||||
id = type['id']
|
||||
# ;type: desc
|
||||
lines.append(f";{type['type']}:")
|
||||
objs = read_all(name, f"select * from curves where id = '{id}' order by _order")
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
x = obj['x']
|
||||
y = obj['y']
|
||||
lines.append(f'{id} {x} {y}')
|
||||
return lines
|
||||
|
||||
|
||||
def inp_out_curve_v3(name: str) -> list[str]:
|
||||
lines = []
|
||||
types = read_all(name, f"select * from _curve")
|
||||
for type in types:
|
||||
id = type['id']
|
||||
# id type
|
||||
lines.append(f"{id} {type['type']}")
|
||||
objs = read_all(name, f"select * from curves where id = '{id}' order by _order")
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
x = obj['x']
|
||||
y = obj['y']
|
||||
lines.append(f'{id} {x} {y}')
|
||||
return lines
|
||||
@@ -0,0 +1,52 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_control_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'controls' : {'type': 'str_list' , 'optional': False , 'readonly': False} }
|
||||
|
||||
|
||||
def get_control(name: str) -> dict[str, Any]:
|
||||
cs = read_all(name, f"select * from controls")
|
||||
ds = []
|
||||
for c in cs:
|
||||
ds.append(c['line'])
|
||||
return { 'controls': ds }
|
||||
|
||||
|
||||
def _set_control(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = get_control(name)
|
||||
|
||||
redo_sql = 'delete from controls;'
|
||||
for line in cs.operations[0]['controls']:
|
||||
redo_sql += f"\ninsert into controls (line) values ('{line}');"
|
||||
|
||||
undo_sql = 'delete from controls;'
|
||||
for line in old['controls']:
|
||||
undo_sql += f"\ninsert into controls (line) values ('{line}');"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'control', 'controls': cs.operations[0]['controls'] }
|
||||
undo_cs = g_update_prefix | { 'type': 'control', 'controls': old['controls'] }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_control(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_control(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3]
|
||||
# LINK linkID setting IF NODE nodeID {BELOW/ABOVE} level
|
||||
# LINK linkID setting AT TIME value (units)
|
||||
# LINK linkID setting AT CLOCKTIME value (units)
|
||||
# (0) (1) (2) (3) (4) (5) (6) (7)
|
||||
# todo...
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_control(line: str) -> str:
|
||||
return str(f"insert into controls (line) values ('{line}');")
|
||||
|
||||
|
||||
def inp_out_control(name: str) -> list[str]:
|
||||
return get_control(name)['controls']
|
||||
@@ -0,0 +1,48 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_rule_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'rules' : {'type': 'str_list' , 'optional': False , 'readonly': False} }
|
||||
|
||||
|
||||
def get_rule(name: str) -> dict[str, Any]:
|
||||
cs = read_all(name, f"select * from rules")
|
||||
ds = []
|
||||
for c in cs:
|
||||
ds.append(c['line'])
|
||||
return { 'rules': ds }
|
||||
|
||||
|
||||
def _set_rule(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = get_rule(name)
|
||||
|
||||
redo_sql = 'delete from rules;'
|
||||
for line in cs.operations[0]['rules']:
|
||||
redo_sql += f"\ninsert into rules (line) values ('{line}');"
|
||||
|
||||
undo_sql = 'delete from rules;'
|
||||
for line in old['rules']:
|
||||
undo_sql += f"\ninsert into rules (line) values ('{line}');"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'rule', 'rules': cs.operations[0]['rules'] }
|
||||
undo_cs = g_update_prefix | { 'type': 'rule', 'rules': old['rules'] }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_rule(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_rule(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3]
|
||||
# TODO...
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_rule(line: str) -> str:
|
||||
return str(f"insert into rules (line) values ('{line}');")
|
||||
|
||||
|
||||
def inp_out_rule(name: str) -> list[str]:
|
||||
return get_rule(name)['rules']
|
||||
@@ -0,0 +1,240 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
element_schema = {'type': 'str' , 'optional': True , 'readonly': False}
|
||||
|
||||
|
||||
def get_energy_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'GLOBAL PRICE' : element_schema,
|
||||
'GLOBAL PATTERN' : element_schema,
|
||||
'GLOBAL EFFIC' : element_schema,
|
||||
'DEMAND CHARGE' : element_schema }
|
||||
|
||||
|
||||
def get_energy(name: str) -> dict[str, Any]:
|
||||
ts = read_all(name, f"select * from energy")
|
||||
d = {}
|
||||
for e in ts:
|
||||
d[e['key']] = str(e['value'])
|
||||
return d
|
||||
|
||||
|
||||
def _set_energy(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
raw_old = get_energy(name)
|
||||
|
||||
old = {}
|
||||
new = {}
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_energy_schema(name)
|
||||
for key in schema.keys():
|
||||
if key in new_dict:
|
||||
old[key] = str(raw_old[key])
|
||||
new[key] = str(new_dict[key])
|
||||
|
||||
redo_cs = g_update_prefix | { 'type' : 'energy' }
|
||||
|
||||
redo_sql = ''
|
||||
for key, value in new.items():
|
||||
if redo_sql != '':
|
||||
redo_sql += '\n'
|
||||
redo_sql += f"update energy set value = '{value}' where key = '{key}';"
|
||||
redo_cs |= { key: value }
|
||||
|
||||
undo_cs = g_update_prefix | { 'type' : 'energy' }
|
||||
|
||||
undo_sql = ''
|
||||
for key, value in old.items():
|
||||
if undo_sql != '':
|
||||
undo_sql += '\n'
|
||||
undo_sql += f"update energy set value = '{value}' where key = '{key}';"
|
||||
undo_cs |= { key: value }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_energy(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_energy(name, cs))
|
||||
|
||||
|
||||
def get_pump_energy_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'pump' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'price' : {'type': 'float' , 'optional': True , 'readonly': False},
|
||||
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'effic' : {'type': 'str' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_pump_energy(name: str, pump: str) -> dict[str, Any]:
|
||||
d = {}
|
||||
d['pump'] = pump
|
||||
pe = try_read(name, f"select * from energy_pump_price where pump = '{pump}'")
|
||||
d['price'] = float(pe['price']) if pe != None else None
|
||||
pe = try_read(name, f"select * from energy_pump_pattern where pump = '{pump}'")
|
||||
d['pattern'] = str(pe['pattern']) if pe != None else None
|
||||
pe = try_read(name, f"select * from energy_pump_effic where pump = '{pump}'")
|
||||
d['effic'] = str(pe['effic']) if pe != None else None
|
||||
return d
|
||||
|
||||
|
||||
class PumpEnergy(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'pump_energy'
|
||||
self.pump = str(input['pump'])
|
||||
self.price = float(input['price']) if 'price' in input and input['price'] != None else None
|
||||
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
|
||||
self.effic = str(input['effic']) if 'effic' in input and input['effic'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_pump = f"'{self.pump}'"
|
||||
self.f_price = self.price if self.price != None else 'null'
|
||||
self.f_pattern = f"'{self.pattern}'" if self.pattern != None else 'null'
|
||||
self.f_effic = f"'{self.effic}'" if self.effic != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'pump': self.pump, 'price': self.price, 'pattern': self.pattern, 'effic': self.effic }
|
||||
|
||||
|
||||
def _set_pump_energy(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = PumpEnergy(get_pump_energy(name, cs.operations[0]['pump']))
|
||||
raw_new = get_pump_energy(name, cs.operations[0]['pump'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_pump_energy_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = PumpEnergy(raw_new)
|
||||
|
||||
redo_sql = f"delete from energy_pump_price where pump = {new.f_pump};\ndelete from energy_pump_pattern where pump = {new.f_pump};\ndelete from energy_pump_effic where pump = {new.f_pump};"
|
||||
if new.price != None:
|
||||
redo_sql += f"\ninsert into energy_pump_price (pump, price) values ({new.f_pump}, {new.f_price});"
|
||||
if new.pattern != None:
|
||||
redo_sql += f"\ninsert into energy_pump_pattern (pump, pattern) values ({new.f_pump}, {new.f_pattern});"
|
||||
if new.effic != None:
|
||||
redo_sql += f"\ninsert into energy_pump_effic (pump, effic) values ({new.f_pump}, {new.f_effic});"
|
||||
|
||||
undo_sql = f"delete from energy_pump_price where pump = {old.f_pump};\ndelete from energy_pump_pattern where pump = {old.f_pump};\ndelete from energy_pump_effic where pump = {old.f_pump};"
|
||||
if old.price != None:
|
||||
undo_sql += f"\ninsert into energy_pump_price (pump, price) values ({old.f_pump}, {old.f_price});"
|
||||
if old.pattern != None:
|
||||
undo_sql += f"\ninsert into energy_pump_pattern (pump, pattern) values ({old.f_pump}, {old.f_pattern});"
|
||||
if old.effic != None:
|
||||
undo_sql += f"\ninsert into energy_pump_effic (pump, effic) values ({old.f_pump}, {old.f_effic});"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_pump_energy(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_pump_energy(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# GLOBAL {PRICE/PATTERN/EFFIC} value
|
||||
# PUMP id {PRICE/PATTERN/EFFIC} value
|
||||
# DEMAND CHARGE value
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_energy(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
if tokens[0].upper() == 'PUMP':
|
||||
pump = tokens[1]
|
||||
key = tokens[2].lower()
|
||||
value = tokens[3]
|
||||
if key == 'price':
|
||||
value = float(value)
|
||||
else:
|
||||
value = f"'{value}'"
|
||||
if key == 'efficiency':
|
||||
key = 'effic'
|
||||
|
||||
return str(f"insert into energy_pump_{key} (pump, {key}) values ('{pump}', {value});")
|
||||
|
||||
else:
|
||||
line = line.upper().strip()
|
||||
for key in get_energy_schema('').keys():
|
||||
if line.startswith(key):
|
||||
value = line.removeprefix(key).strip()
|
||||
|
||||
# exception here
|
||||
if line.startswith('GLOBAL EFFICIENCY'):
|
||||
value = line.removeprefix('GLOBAL EFFICIENCY').strip()
|
||||
|
||||
return str(f"update energy set value = '{value}' where key = '{key}';")
|
||||
|
||||
return str('')
|
||||
|
||||
|
||||
def inp_out_energy(name: str) -> list[str]:
|
||||
lines = []
|
||||
|
||||
objs = read_all(name, f"select * from energy")
|
||||
for obj in objs:
|
||||
key = obj['key']
|
||||
value = obj['value']
|
||||
if value.strip() != '':
|
||||
lines.append(f'{key} {value}')
|
||||
|
||||
objs = read_all(name, f"select * from energy_pump_price")
|
||||
for obj in objs:
|
||||
pump = obj['pump']
|
||||
value = obj['price']
|
||||
lines.append(f'PUMP {pump} PRICE {value}')
|
||||
|
||||
objs = read_all(name, f"select * from energy_pump_pattern")
|
||||
for obj in objs:
|
||||
pump = obj['pump']
|
||||
value = obj['pattern']
|
||||
lines.append(f'PUMP {pump} PATTERN {value}')
|
||||
|
||||
objs = read_all(name, f"select * from energy_pump_effic")
|
||||
for obj in objs:
|
||||
pump = obj['pump']
|
||||
value = obj['effic']
|
||||
lines.append(f'PUMP {pump} EFFIC {value}')
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def delete_pump_energy_by_pump(name: str, pump: str) -> ChangeSet:
|
||||
row1 = try_read(name, f"select * from energy_pump_price where pump = '{pump}'")
|
||||
row2 = try_read(name, f"select * from energy_pump_pattern where pump = '{pump}'")
|
||||
row3 = try_read(name, f"select * from energy_pump_effic where pump = '{pump}'")
|
||||
if row1 == None and row2 == None and row3 == None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_update_prefix | {'type': 'pump_energy', 'pump' : pump, 'price': None, 'pattern': None, 'effic': None})
|
||||
|
||||
|
||||
def unset_pump_energy_by_pattern(name: str, pattern: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select * from energy_pump_pattern where pattern = '{pattern}'")
|
||||
for row in rows:
|
||||
pump = row['pump']
|
||||
row1 = try_read(name, f"select * from energy_pump_price where pump = '{pump}'")
|
||||
price = float(row1['price']) if row1 != None else None
|
||||
row2 = try_read(name, f"select * from energy_pump_effic where pump = '{pump}'")
|
||||
effic = str(row2['effic']) if row2 != None else None
|
||||
cs.append(g_update_prefix | {'type': 'pump_energy', 'pump' : pump, 'price': price, 'pattern': None, 'effic': effic})
|
||||
|
||||
return cs
|
||||
|
||||
|
||||
def unset_pump_energy_by_curve(name: str, curve: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select * from energy_pump_effic where effic = '{curve}'")
|
||||
for row in rows:
|
||||
pump = row['pump']
|
||||
row1 = try_read(name, f"select * from energy_pump_price where pump = '{pump}'")
|
||||
price = float(row1['price']) if row1 != None else None
|
||||
row2 = try_read(name, f"select * from energy_pump_pattern where pump = '{pump}'")
|
||||
pattern = str(row2['pattern']) if row2 != None else None
|
||||
cs.append(g_update_prefix | {'type': 'pump_energy', 'pump' : pump, 'price': price, 'pattern': pattern, 'effic': None})
|
||||
|
||||
return cs
|
||||
@@ -0,0 +1,98 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_emitter_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'junction' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'coefficient' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_emitter(name: str, junction: str) -> dict[str, Any]:
|
||||
e = try_read(name, f"select * from emitters where junction = '{junction}'")
|
||||
if e == None:
|
||||
return { 'junction': junction, 'coefficient': None }
|
||||
d = {}
|
||||
d['junction'] = str(e['junction'])
|
||||
d['coefficient'] = float(e['coefficient']) if e['coefficient'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Emitter(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'emitter'
|
||||
self.junction = str(input['junction'])
|
||||
self.coefficient = float(input['coefficient']) if 'coefficient' in input and input['coefficient'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_junction = f"'{self.junction}'"
|
||||
self.f_coefficient = self.coefficient if self.coefficient != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'junction': self.junction, 'coefficient': self.coefficient }
|
||||
|
||||
|
||||
def _set_emitter(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Emitter(get_emitter(name, cs.operations[0]['junction']))
|
||||
raw_new = get_emitter(name, cs.operations[0]['junction'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_emitter_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Emitter(raw_new)
|
||||
|
||||
redo_sql = f"delete from emitters where junction = {new.f_junction};"
|
||||
if new.coefficient != None:
|
||||
redo_sql += f"\ninsert into emitters (junction, coefficient) values ({new.f_junction}, {new.f_coefficient});"
|
||||
|
||||
undo_sql = f"delete from emitters where junction = {old.f_junction};"
|
||||
if old.coefficient != None:
|
||||
undo_sql += f"\ninsert into emitters (junction, coefficient) values ({old.f_junction}, {old.f_coefficient});"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_emitter(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_emitter(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][IN][OUT]
|
||||
# node Ke
|
||||
#--------------------------------------------------------------
|
||||
# [EPA3][IN][OUT]
|
||||
# node Ke (exponent pattern)
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_emitter(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
junction = str(tokens[0])
|
||||
coefficient = float(tokens[1])
|
||||
|
||||
return str(f"insert into emitters (junction, coefficient) values ('{junction}', {coefficient});")
|
||||
|
||||
|
||||
def inp_out_emitter(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from emitters')
|
||||
for obj in objs:
|
||||
junction = obj['junction']
|
||||
coefficient = obj['coefficient']
|
||||
lines.append(f'{junction} {coefficient}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_emitter_by_junction(name: str, junction: str) -> ChangeSet:
|
||||
row = try_read(name, f"select * from emitters where junction = '{junction}'")
|
||||
if row == None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_update_prefix | {'type' : 'emitter', 'junction': junction, 'coefficient': None})
|
||||
@@ -0,0 +1,95 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_quality_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'node' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'quality' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_quality(name: str, node: str) -> dict[str, Any]:
|
||||
e = try_read(name, f"select * from quality where node = '{node}'")
|
||||
if e == None:
|
||||
return { 'node': node, 'quality': None }
|
||||
d = {}
|
||||
d['node'] = str(e['node'])
|
||||
d['quality'] = float(e['quality']) if e['quality'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Quality(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'quality'
|
||||
self.node = str(input['node'])
|
||||
self.quality = float(input['quality']) if 'quality' in input and input['quality'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_node = f"'{self.node}'"
|
||||
self.f_quality = self.quality if self.quality != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'node': self.node, 'quality': self.quality }
|
||||
|
||||
|
||||
def _set_quality(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Quality(get_quality(name, cs.operations[0]['node']))
|
||||
raw_new = get_quality(name, cs.operations[0]['node'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_quality_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Quality(raw_new)
|
||||
|
||||
redo_sql = f"delete from quality where node = {new.f_node};"
|
||||
if new.quality != None:
|
||||
redo_sql += f"\ninsert into quality (node, quality) values ({new.f_node}, {new.f_quality});"
|
||||
|
||||
undo_sql = f"delete from quality where node = {old.f_node};"
|
||||
if old.quality != None:
|
||||
undo_sql += f"\ninsert into quality (node, quality) values ({old.f_node}, {old.f_quality});"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_quality(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_quality(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# node initqual
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_quality(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
node = str(tokens[0])
|
||||
quality = float(tokens[1])
|
||||
|
||||
return str(f"insert into quality (node, quality) values ('{node}', {quality});")
|
||||
|
||||
|
||||
def inp_out_quality(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from quality')
|
||||
for obj in objs:
|
||||
node = obj['node']
|
||||
quality = obj['quality']
|
||||
lines.append(f'{node} {quality}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_quality_by_node(name: str, node: str) -> ChangeSet:
|
||||
row = try_read(name, f"select * from quality where node = '{node}'")
|
||||
if row == None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_update_prefix | {'type' : 'quality', 'node': node, 'quality': None})
|
||||
@@ -0,0 +1,153 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
|
||||
SOURCE_TYPE_CONCEN = 'CONCEN'
|
||||
SOURCE_TYPE_MASS = 'MASS'
|
||||
SOURCE_TYPE_FLOWPACED = 'FLOWPACED'
|
||||
SOURCE_TYPE_SETPOINT = 'SETPOINT'
|
||||
|
||||
def get_source_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'node' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
's_type' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'strength' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_source(name: str, node: str) -> dict[str, Any]:
|
||||
s = try_read(name, f"select * from sources where node = '{node}'")
|
||||
if s == None:
|
||||
return {}
|
||||
d = {}
|
||||
d['node'] = str(s['node'])
|
||||
d['s_type'] = str(s['s_type'])
|
||||
d['strength'] = float(s['strength'])
|
||||
d['pattern'] = str(s['pattern']) if s['pattern'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Source(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'source'
|
||||
self.node = str(input['node'])
|
||||
self.s_type = str(input['s_type'])
|
||||
self.strength = float(input['strength'])
|
||||
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_node = f"'{self.node}'"
|
||||
self.f_s_type = f"'{self.s_type}'"
|
||||
self.f_strength = self.strength
|
||||
self.f_pattern = f"'{self.pattern}'" if self.pattern != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'node': self.node, 's_type': self.s_type, 'strength': self.strength, 'pattern': self.pattern }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'node': self.node }
|
||||
|
||||
|
||||
def _set_source(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Source(get_source(name, cs.operations[0]['node']))
|
||||
raw_new = get_source(name, cs.operations[0]['node'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_source_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Source(raw_new)
|
||||
|
||||
redo_sql = f"update sources set s_type = {new.f_s_type}, strength = {new.f_strength}, pattern = {new.f_pattern} where node = {new.f_node};"
|
||||
undo_sql = f"update sources set s_type = {old.f_s_type}, strength = {old.f_strength}, pattern = {old.f_pattern} where node = {old.f_node};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_source(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_source(name, cs))
|
||||
|
||||
|
||||
def _add_source(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = Source(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into sources (node, s_type, strength, pattern) values ({new.f_node}, {new.f_s_type}, {new.f_strength}, {new.f_pattern});"
|
||||
undo_sql = f"delete from sources where node = {new.f_node};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_id_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_source(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _add_source(name, cs))
|
||||
|
||||
|
||||
def _delete_source(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Source(get_source(name, cs.operations[0]['node']))
|
||||
|
||||
redo_sql = f"delete from sources where node = {old.f_node};"
|
||||
undo_sql = f"insert into sources (node, s_type, strength, pattern) values ({old.f_node}, {old.f_s_type}, {old.f_strength}, {old.f_pattern});"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_id_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_source(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _delete_source(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# node sourcetype quality (pattern)
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_source(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
node = str(tokens[0])
|
||||
s_type = str(tokens[1].upper())
|
||||
strength = float(tokens[2])
|
||||
pattern = str(tokens[3]) if num_without_desc >= 4 else None
|
||||
pattern = f"'{pattern}'" if pattern != None else 'null'
|
||||
|
||||
return str(f"insert into sources (node, s_type, strength, pattern) values ('{node}', '{s_type}', {strength}, {pattern});")
|
||||
|
||||
|
||||
def inp_out_source(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from sources')
|
||||
for obj in objs:
|
||||
node = obj['node']
|
||||
s_type = obj['s_type']
|
||||
strength = obj['strength']
|
||||
pattern = obj['pattern'] if obj['pattern'] != None else ''
|
||||
lines.append(f'{node} {s_type} {strength} {pattern}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_source_by_node(name: str, node: str) -> ChangeSet:
|
||||
row = try_read(name, f"select * from sources where node = '{node}'")
|
||||
if row == None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_delete_prefix | {'type' : 'source', 'node': node})
|
||||
|
||||
|
||||
def unset_source_by_pattern(name: str, pattern: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select node from sources where pattern = '{pattern}'")
|
||||
for row in rows:
|
||||
cs.append(g_update_prefix | {'type': 'source', 'node': row['node'], 'pattern': None})
|
||||
|
||||
return cs
|
||||
@@ -0,0 +1,263 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
element_schema = {'type': 'str' , 'optional': True , 'readonly': False}
|
||||
|
||||
|
||||
def get_reaction_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'ORDER BULK' : element_schema,
|
||||
'ORDER WALL' : element_schema,
|
||||
'ORDER TANK' : element_schema,
|
||||
'GLOBAL BULK' : element_schema,
|
||||
'GLOBAL WALL' : element_schema,
|
||||
'LIMITING POTENTIAL' : element_schema,
|
||||
'ROUGHNESS CORRELATION' : element_schema }
|
||||
|
||||
|
||||
def get_reaction(name: str) -> dict[str, Any]:
|
||||
ts = read_all(name, f"select * from reactions")
|
||||
d = {}
|
||||
for e in ts:
|
||||
d[e['key']] = str(e['value'])
|
||||
return d
|
||||
|
||||
|
||||
def _set_reaction(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
raw_old = get_reaction(name)
|
||||
|
||||
old = {}
|
||||
new = {}
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_reaction_schema(name)
|
||||
for key in schema.keys():
|
||||
if key in new_dict:
|
||||
old[key] = str(raw_old[key])
|
||||
new[key] = str(new_dict[key])
|
||||
|
||||
redo_cs = g_update_prefix | { 'type' : 'reaction' }
|
||||
|
||||
redo_sql = ''
|
||||
for key, value in new.items():
|
||||
if redo_sql != '':
|
||||
redo_sql += '\n'
|
||||
redo_sql += f"update reactions set value = '{value}' where key = '{key}';"
|
||||
redo_cs |= { key: value }
|
||||
|
||||
undo_cs = g_update_prefix | { 'type' : 'reaction' }
|
||||
|
||||
undo_sql = ''
|
||||
for key, value in old.items():
|
||||
if undo_sql != '':
|
||||
undo_sql += '\n'
|
||||
undo_sql += f"update reactions set value = '{value}' where key = '{key}';"
|
||||
undo_cs |= { key: value }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_reaction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_reaction(name, cs))
|
||||
|
||||
|
||||
def get_pipe_reaction_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'pipe' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'bulk' : {'type': 'float' , 'optional': True , 'readonly': False},
|
||||
'wall' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_pipe_reaction(name: str, pipe: str) -> dict[str, Any]:
|
||||
d = {}
|
||||
d['pipe'] = pipe
|
||||
pr = try_read(name, f"select * from reactions_pipe_bulk where pipe = '{pipe}'")
|
||||
d['bulk'] = float(pr['value']) if pr != None else None
|
||||
pr = try_read(name, f"select * from reactions_pipe_wall where pipe = '{pipe}'")
|
||||
d['wall'] = float(pr['value']) if pr != None else None
|
||||
return d
|
||||
|
||||
|
||||
class PipeReaction(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'pipe_reaction'
|
||||
self.pipe = str(input['pipe'])
|
||||
self.bulk = float(input['bulk']) if 'bulk' in input and input['bulk'] != None else None
|
||||
self.wall = float(input['wall']) if 'wall' in input and input['wall'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_pipe = f"'{self.pipe}'"
|
||||
self.f_bulk = self.bulk if self.bulk != None else 'null'
|
||||
self.f_wall = self.wall if self.wall != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'pipe': self.pipe, 'bulk': self.bulk, 'wall': self.wall }
|
||||
|
||||
|
||||
def _set_pipe_reaction(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = PipeReaction(get_pipe_reaction(name, cs.operations[0]['pipe']))
|
||||
raw_new = get_pipe_reaction(name, cs.operations[0]['pipe'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_pipe_reaction_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = PipeReaction(raw_new)
|
||||
|
||||
redo_sql = f"delete from reactions_pipe_bulk where pipe = {new.f_pipe};\ndelete from reactions_pipe_wall where pipe = {new.f_pipe};"
|
||||
if new.bulk != None:
|
||||
redo_sql += f"\ninsert into reactions_pipe_bulk (pipe, value) values ({new.f_pipe}, {new.f_bulk});"
|
||||
if new.wall != None:
|
||||
redo_sql += f"\ninsert into reactions_pipe_wall (pipe, value) values ({new.f_pipe}, {new.f_wall});"
|
||||
|
||||
undo_sql = f"delete from reactions_pipe_bulk where pipe = {old.f_pipe};\ndelete from reactions_pipe_wall where pipe = {old.f_pipe};"
|
||||
if old.bulk != None:
|
||||
undo_sql += f"\ninsert into reactions_pipe_bulk (pipe, value) values ({old.f_pipe}, {old.f_bulk});"
|
||||
if old.wall != None:
|
||||
undo_sql += f"\ninsert into reactions_pipe_wall (pipe, value) values ({old.f_pipe}, {old.f_wall});"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_pipe_reaction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_pipe_reaction(name, cs))
|
||||
|
||||
|
||||
def get_tank_reaction_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'tank' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'value' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_tank_reaction(name: str, tank: str) -> dict[str, Any]:
|
||||
d = {}
|
||||
d['tank'] = tank
|
||||
pr = try_read(name, f"select * from reactions_tank where tank = '{tank}'")
|
||||
d['value'] = float(pr['value']) if pr != None else None
|
||||
return d
|
||||
|
||||
|
||||
class TankReaction(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'tank_reaction'
|
||||
self.tank = str(input['tank'])
|
||||
self.value = float(input['value']) if 'value' in input and input['value'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_tank = f"'{self.tank}'"
|
||||
self.f_value = self.value if self.value != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'tank': self.tank, 'value': self.value }
|
||||
|
||||
|
||||
def _set_tank_reaction(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = TankReaction(get_tank_reaction(name, cs.operations[0]['tank']))
|
||||
raw_new = get_tank_reaction(name, cs.operations[0]['tank'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_tank_reaction_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = TankReaction(raw_new)
|
||||
|
||||
redo_sql = f"delete from reactions_tank where tank = {new.f_tank};"
|
||||
if new.value != None:
|
||||
redo_sql += f"\ninsert into reactions_tank (tank, value) values ({new.f_tank}, {new.f_value});"
|
||||
|
||||
undo_sql = f"delete from reactions_tank where tank = {old.f_tank};"
|
||||
if old.value != None:
|
||||
undo_sql += f"\ninsert into reactions_tank (tank, value) values ({old.f_tank}, {old.f_value});"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_tank_reaction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_tank_reaction(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# ORDER {BULK/WALL/TANK} value
|
||||
# GLOBAL BULK coeff
|
||||
# GLOBAL WALL coeff
|
||||
# BULK link1 (link2) coeff
|
||||
# WALL link1 (link2) coeff
|
||||
# TANK node1 (node2) coeff
|
||||
# LIMITING POTENTIAL value
|
||||
# ROUGHNESS CORRELATION value
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_reaction(line: str) -> str:
|
||||
tokens = line.split()
|
||||
token0 = tokens[0].upper()
|
||||
if token0 == 'BULK' or token0 == 'WALL':
|
||||
pipe = tokens[1]
|
||||
key = token0.lower()
|
||||
value = tokens[2]
|
||||
return str(f"insert into reactions_pipe_{key} (pipe, value) values ('{pipe}', {value});")
|
||||
|
||||
elif token0 == 'TANK':
|
||||
tank = tokens[1]
|
||||
value = tokens[2]
|
||||
return str(f"insert into reactions_tank (tank, value) values ('{tank}', {value});")
|
||||
|
||||
else:
|
||||
line = line.upper().strip()
|
||||
for key in get_reaction_schema('').keys():
|
||||
if line.startswith(key):
|
||||
value = line.removeprefix(key).strip()
|
||||
return str(f"update reactions set value = '{value}' where key = '{key}';")
|
||||
|
||||
return str('')
|
||||
|
||||
|
||||
def inp_out_reaction(name: str) -> list[str]:
|
||||
lines = []
|
||||
|
||||
objs = read_all(name, f"select * from reactions")
|
||||
for obj in objs:
|
||||
key = obj['key']
|
||||
value = obj['value']
|
||||
lines.append(f'{key} {value}')
|
||||
|
||||
objs = read_all(name, f"select * from reactions_pipe_bulk")
|
||||
for obj in objs:
|
||||
pipe = obj['pipe']
|
||||
value = obj['value']
|
||||
lines.append(f'BULK {pipe} {value}')
|
||||
|
||||
objs = read_all(name, f"select * from reactions_pipe_wall")
|
||||
for obj in objs:
|
||||
pipe = obj['pipe']
|
||||
value = obj['value']
|
||||
lines.append(f'WALL {pipe} {value}')
|
||||
|
||||
objs = read_all(name, f"select * from reactions_tank")
|
||||
for obj in objs:
|
||||
tank = obj['tank']
|
||||
value = obj['value']
|
||||
lines.append(f'TANK {tank} {value}')
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def delete_pipe_reaction_by_pipe(name: str, pipe: str) -> ChangeSet:
|
||||
row1 = try_read(name, f"select * from reactions_pipe_bulk where pipe = '{pipe}'")
|
||||
row2 = try_read(name, f"select * from reactions_pipe_wall where pipe = '{pipe}'")
|
||||
if row1 == None and row2 == None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_update_prefix | {'type': 'pipe_reaction', 'pipe': pipe, 'bulk': None, 'wall': None})
|
||||
|
||||
|
||||
def delete_tank_reaction_by_tank(name: str, tank: str) -> ChangeSet:
|
||||
row = try_read(name, f"select * from reactions_tank where tank = '{tank}'")
|
||||
if row == None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_update_prefix | {'type': 'tank_reaction', 'tank': tank, 'value': None})
|
||||
@@ -0,0 +1,40 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_title_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return {'value': {'type': 'float', 'optional': False, 'readonly': False}}
|
||||
|
||||
|
||||
def get_title(name: str) -> dict[str, Any]:
|
||||
title = read(name, 'select * from title')
|
||||
return { 'value': title['value'] }
|
||||
|
||||
|
||||
def _set_title(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = cs.operations[0]['value']
|
||||
old = get_title(name)['value']
|
||||
|
||||
redo_sql = f"update title set value = '{new}';"
|
||||
undo_sql = f"update title set value = '{old}';"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'title', 'value': new }
|
||||
undo_cs = g_update_prefix | { 'type': 'title', 'value': old }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_title(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_title(name ,cs))
|
||||
|
||||
|
||||
def inp_in_title(section: list[str]) -> str:
|
||||
if section == []:
|
||||
return str('')
|
||||
|
||||
title = '\n'.join(section)
|
||||
return str(f"update title set value = '{title}';")
|
||||
|
||||
|
||||
def inp_out_title(name: str) -> list[str]:
|
||||
obj = str(get_title(name)['value'])
|
||||
return obj.split('\n')
|
||||
@@ -0,0 +1,150 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
|
||||
MIXING_MODEL_MIXED = 'MIXED'
|
||||
MIXING_MODEL_2COMP = '2COMP'
|
||||
MIXING_MODEL_FIFO = 'FIFO'
|
||||
MIXING_MODEL_LIFO = 'LIFO'
|
||||
|
||||
def get_mixing_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'tank' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'model' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'value' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_mixing(name: str, tank: str) -> dict[str, Any]:
|
||||
m = try_read(name, f"select * from mixing where tank = '{tank}'")
|
||||
if m == None:
|
||||
return {}
|
||||
d = {}
|
||||
d['tank'] = str(m['tank'])
|
||||
d['model'] = str(m['model'])
|
||||
d['value'] = float(m['value']) if m['value'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Mixing(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'mixing'
|
||||
self.tank = str(input['tank'])
|
||||
self.model = str(input['model'])
|
||||
self.value = float(input['value']) if 'value' in input and input['value'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_tank = f"'{self.tank}'"
|
||||
self.f_model = f"'{self.model}'"
|
||||
self.f_value = self.value if self.value != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'tank': self.tank, 'model': self.model, 'value': self.value }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'tank': self.tank }
|
||||
|
||||
|
||||
def _set_mixing(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Mixing(get_mixing(name, cs.operations[0]['tank']))
|
||||
raw_new = get_mixing(name, cs.operations[0]['tank'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_mixing_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Mixing(raw_new)
|
||||
|
||||
redo_sql = f"update mixing set model = {new.f_model}, value = {new.f_value} where tank = {new.f_tank};"
|
||||
undo_sql = f"update mixing set model = {old.f_model}, value = {old.f_value} where tank = {old.f_tank};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_mixing(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'tank' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_mixing(name, cs.operations[0]['tank']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_mixing(name, cs))
|
||||
|
||||
|
||||
def _add_mixing(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = Mixing(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into mixing (tank, model, value) values ({new.f_tank}, {new.f_model}, {new.f_value});"
|
||||
undo_sql = f"delete from mixing where tank = {new.f_tank};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_id_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_mixing(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'tank' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_mixing(name, cs.operations[0]['tank']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_mixing(name, cs))
|
||||
|
||||
|
||||
def _delete_mixing(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Mixing(get_mixing(name, cs.operations[0]['tank']))
|
||||
|
||||
redo_sql = f"delete from mixing where tank = {old.f_tank};"
|
||||
undo_sql = f"insert into mixing (tank, model, value) values ({old.f_tank}, {old.f_model}, {old.f_value});"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_id_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_mixing(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'tank' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_mixing(name, cs.operations[0]['tank']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_mixing(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# TankID MixModel FractVolume
|
||||
# FractVolume if type == MIX2
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_mixing(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
tank = str(tokens[0])
|
||||
model = str(tokens[1].upper())
|
||||
value = float(tokens[3]) if num_without_desc >= 4 else None
|
||||
value = value if value != None else 'null'
|
||||
|
||||
return str(f"insert into mixing (tank, model, value) values ('{tank}', '{model}', {value});")
|
||||
|
||||
|
||||
def inp_out_mixing(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from mixing')
|
||||
for obj in objs:
|
||||
tank = obj['tank']
|
||||
model = obj['model']
|
||||
value = obj['value'] if obj['value'] != None else ''
|
||||
lines.append(f'{tank} {model} {value}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_mixing_by_tank(name: str, tank: str) -> ChangeSet:
|
||||
row = try_read(name, f"select * from mixing where tank = '{tank}'")
|
||||
if row == None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_delete_prefix | {'type' : 'mixing', 'tank': tank})
|
||||
@@ -0,0 +1,112 @@
|
||||
from .database import *
|
||||
|
||||
TIME_STATISTIC_NONE = 'NONE'
|
||||
TIME_STATISTIC_AVERAGED = 'AVERAGED'
|
||||
TIME_STATISTIC_MINIMUM = 'MINIMUM'
|
||||
TIME_STATISTIC_MAXIMUM = 'MAXIMUM'
|
||||
TIME_STATISTIC_RANGE = 'RANGE'
|
||||
|
||||
element_schema = {'type': 'str' , 'optional': True , 'readonly': False}
|
||||
|
||||
def get_time_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'DURATION' : element_schema,
|
||||
'HYDRAULIC TIMESTEP' : element_schema,
|
||||
'QUALITY TIMESTEP' : element_schema,
|
||||
'RULE TIMESTEP' : element_schema,
|
||||
'PATTERN TIMESTEP' : element_schema,
|
||||
'PATTERN START' : element_schema,
|
||||
'REPORT TIMESTEP' : element_schema,
|
||||
'REPORT START' : element_schema,
|
||||
'START CLOCKTIME' : element_schema,
|
||||
'STATISTIC' : element_schema}
|
||||
|
||||
|
||||
def get_time(name: str) -> dict[str, Any]:
|
||||
ts = read_all(name, f"select * from times")
|
||||
d = {}
|
||||
for e in ts:
|
||||
d[e['key']] = str(e['value'])
|
||||
return d
|
||||
|
||||
|
||||
def _set_time(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
raw_old = get_time(name)
|
||||
|
||||
old = {}
|
||||
new = {}
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_time_schema(name)
|
||||
for key in schema.keys():
|
||||
if key in new_dict:
|
||||
old[key] = str(raw_old[key])
|
||||
new[key] = str(new_dict[key])
|
||||
|
||||
redo_cs = g_update_prefix | { 'type' : 'time' }
|
||||
|
||||
redo_sql = ''
|
||||
for key, value in new.items():
|
||||
if redo_sql != '':
|
||||
redo_sql += '\n'
|
||||
redo_sql += f"update times set value = '{value}' where key = '{key}';"
|
||||
redo_cs |= { key: value }
|
||||
|
||||
undo_cs = g_update_prefix | { 'type' : 'time' }
|
||||
|
||||
undo_sql = ''
|
||||
for key, value in old.items():
|
||||
if undo_sql != '':
|
||||
undo_sql += '\n'
|
||||
undo_sql += f"update times set value = '{value}' where key = '{key}';"
|
||||
undo_cs |= { key: value }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_time(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_time(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3]
|
||||
# STATISTIC {NONE/AVERAGE/MIN/MAX/RANGE}
|
||||
# DURATION value (units)
|
||||
# HYDRAULIC TIMESTEP value (units)
|
||||
# QUALITY TIMESTEP value (units)
|
||||
# RULE TIMESTEP value (units)
|
||||
# PATTERN TIMESTEP value (units)
|
||||
# PATTERN START value (units)
|
||||
# REPORT TIMESTEP value (units)
|
||||
# REPORT START value (units)
|
||||
# START CLOCKTIME value (AM PM)
|
||||
# [EPA3] supports [EPA2] keyword
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_time(section: list[str]) -> str:
|
||||
sql = ''
|
||||
for s in section:
|
||||
if s.startswith(';'):
|
||||
continue
|
||||
|
||||
line = s.upper().strip()
|
||||
|
||||
# TOTAL DURATION => DURATION
|
||||
if line.startswith('TOTAL DURATION'):
|
||||
line = line.replace('TOTAL DURATION', 'DURATION')
|
||||
|
||||
for key in get_time_schema('').keys():
|
||||
if line.startswith(key):
|
||||
value = line.removeprefix(key).strip()
|
||||
sql += f"update times set value = '{value}' where key = '{key}';"
|
||||
return sql
|
||||
|
||||
|
||||
def inp_out_time(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, f"select * from times")
|
||||
for obj in objs:
|
||||
key = obj['key']
|
||||
value = obj['value']
|
||||
lines.append(f'{key} {value}')
|
||||
return lines
|
||||
@@ -0,0 +1,34 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2]
|
||||
# PAGE linesperpage
|
||||
# STATUS {NONE/YES/FULL}
|
||||
# SUMMARY {YES/NO}
|
||||
# MESSAGES {YES/NO}
|
||||
# ENERGY {NO/YES}
|
||||
# NODES {NONE/ALL}
|
||||
# NODES node1 node2 ...
|
||||
# LINKS {NONE/ALL}
|
||||
# LINKS link1 link2 ...
|
||||
# FILE filename
|
||||
# variable {YES/NO}
|
||||
# variable {BELOW/ABOVE/PRECISION} value
|
||||
# [EPA3][NOT SUPPORT]
|
||||
# TRIALS {YES/NO}
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_report(section: list[str]) -> str:
|
||||
return ''
|
||||
|
||||
|
||||
def inp_out_report(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, f"select * from report")
|
||||
for obj in objs:
|
||||
key = obj['key']
|
||||
value = obj['value']
|
||||
lines.append(f'{key} {value}')
|
||||
return lines
|
||||
@@ -0,0 +1,81 @@
|
||||
from .database import *
|
||||
from .s23_options_util import get_option_schema, generate_v3
|
||||
|
||||
|
||||
def _inp_in_option(section: list[str]) -> ChangeSet:
|
||||
if len(section) <= 0:
|
||||
return ChangeSet()
|
||||
|
||||
cs = g_update_prefix | { 'type' : 'option' }
|
||||
for s in section:
|
||||
if s.startswith(';'):
|
||||
continue
|
||||
|
||||
tokens = s.strip().split()
|
||||
if tokens[0].upper() == 'PATTERN': # can not upper id
|
||||
value = tokens[1] if len(tokens) > 1 else ''
|
||||
cs |= { 'PATTERN' : value }
|
||||
elif tokens[0].upper() == 'QUALITY': # can not upper trace node
|
||||
value = tokens[1] if len(tokens) > 1 else ''
|
||||
if len(tokens) > 2:
|
||||
value += f' {tokens[2]}'
|
||||
cs |= { 'QUALITY' : value }
|
||||
else:
|
||||
line = s.upper().strip()
|
||||
for key in get_option_schema('').keys():
|
||||
if line.startswith(key):
|
||||
value = line.removeprefix(key).strip()
|
||||
cs |= { key : value }
|
||||
|
||||
result = ChangeSet(cs)
|
||||
result.merge(generate_v3(result))
|
||||
return result
|
||||
|
||||
|
||||
def inp_in_option(section: list[str]) -> str:
|
||||
sql = ''
|
||||
result = _inp_in_option(section)
|
||||
for op in result.operations:
|
||||
for key in op.keys():
|
||||
if key == 'operation' or key == 'type':
|
||||
continue
|
||||
if op['type'] == 'option':
|
||||
sql += f"update options set value = '{op[key]}' where key = '{key}';"
|
||||
else:
|
||||
sql += f"update options_v3 set value = '{op[key]}' where key = '{key}';"
|
||||
return sql
|
||||
|
||||
|
||||
def inp_out_option(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, f"select * from options")
|
||||
|
||||
is_dda = False
|
||||
|
||||
for obj in objs:
|
||||
if obj['key'] == 'DEMAND MODEL':
|
||||
is_dda = obj['value'] == 'DDA'
|
||||
|
||||
dda_ignore = [
|
||||
'HEADERROR', # TODO: default is 0 which is conflict with PDA
|
||||
'FLOWCHANGE', # TODO: default is 0 which is conflict with PDA
|
||||
'MINIMUM PRESSURE',
|
||||
'REQUIRED PRESSURE',
|
||||
'PRESSURE EXPONENT'
|
||||
]
|
||||
|
||||
for obj in objs:
|
||||
key = obj['key']
|
||||
# why write this ?
|
||||
if key == 'PRESSURE':
|
||||
continue
|
||||
# release version does not support new keys and has error message
|
||||
if key == 'HTOL' or key == 'QTOL' or key == 'RQTOL':
|
||||
continue
|
||||
# ignore some weird settings for DDA
|
||||
if is_dda and key in dda_ignore:
|
||||
continue
|
||||
value = obj['value']
|
||||
if str(value).strip() != '':
|
||||
lines.append(f'{key} {value}')
|
||||
return lines
|
||||
@@ -0,0 +1,401 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPANET2][IN][OUT]
|
||||
# UNITS CFS/GPM/MGD/IMGD/AFD/LPS/LPM/MLD/CMH/CMD/SI
|
||||
# PRESSURE PSI/KPA/M
|
||||
# HEADLOSS H-W/D-W/C-M
|
||||
# QUALITY NONE/AGE/TRACE/CHEMICAL (TraceNode)
|
||||
# UNBALANCED STOP/CONTINUE {Niter}
|
||||
# PATTERN id
|
||||
# DEMAND MODEL DDA/PDA
|
||||
# DEMAND MULTIPLIER value
|
||||
# EMITTER EXPONENT value
|
||||
# VISCOSITY value
|
||||
# DIFFUSIVITY value
|
||||
# SPECIFIC GRAVITY value
|
||||
# TRIALS value
|
||||
# ACCURACY value#
|
||||
# HEADERROR value
|
||||
# FLOWCHANGE value
|
||||
# MINIMUM PRESSURE value
|
||||
# REQUIRED PRESSURE value
|
||||
# PRESSURE EXPONENT value#
|
||||
# TOLERANCE value
|
||||
# HTOL value
|
||||
# QTOL value
|
||||
# RQTOL value
|
||||
# CHECKFREQ value
|
||||
# MAXCHECK value
|
||||
# DAMPLIMIT value
|
||||
# ---- Unsupported Options -----
|
||||
# HYDRAULICS USE/SAVE filename
|
||||
# MAP filename
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
element_schema = {'type': 'str' , 'optional': True , 'readonly': False}
|
||||
|
||||
|
||||
OPTION_UNITS_CFS = 'CFS'
|
||||
OPTION_UNITS_GPM = 'GPM'
|
||||
OPTION_UNITS_MGD = 'MGD'
|
||||
OPTION_UNITS_IMGD = 'IMGD'
|
||||
OPTION_UNITS_AFD = 'AFD'
|
||||
OPTION_UNITS_LPS = 'LPS'
|
||||
OPTION_UNITS_LPM = 'LPM'
|
||||
OPTION_UNITS_MLD = 'MLD'
|
||||
OPTION_UNITS_CMH = 'CMH'
|
||||
OPTION_UNITS_CMD = 'CMD'
|
||||
|
||||
OPTION_PRESSURE_PSI = 'PSI'
|
||||
OPTION_PRESSURE_KPA = 'KPA'
|
||||
OPTION_PRESSURE_METERS = 'METERS'
|
||||
|
||||
OPTION_HEADLOSS_HW = 'H-W'
|
||||
OPTION_HEADLOSS_DW = 'D-W'
|
||||
OPTION_HEADLOSS_CM = 'C-M'
|
||||
|
||||
OPTION_UNBALANCED_STOP = 'STOP'
|
||||
OPTION_UNBALANCED_CONTINUE = 'CONTINUE'
|
||||
|
||||
OPTION_DEMAND_MODEL_DDA = 'DDA'
|
||||
OPTION_DEMAND_MODEL_PDA = 'PDA'
|
||||
|
||||
OPTION_QUALITY_NONE = 'NONE'
|
||||
OPTION_QUALITY_CHEMICAL = 'CHEMICAL'
|
||||
OPTION_QUALITY_AGE = 'AGE'
|
||||
OPTION_QUALITY_TRACE = 'TRACE'
|
||||
|
||||
|
||||
def get_option_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'UNITS' : element_schema,
|
||||
'PRESSURE' : element_schema,
|
||||
'HEADLOSS' : element_schema,
|
||||
'QUALITY' : element_schema,
|
||||
'UNBALANCED' : element_schema,
|
||||
'PATTERN' : element_schema,
|
||||
'DEMAND MODEL' : element_schema,
|
||||
'DEMAND MULTIPLIER' : element_schema,
|
||||
'EMITTER EXPONENT' : element_schema,
|
||||
'VISCOSITY' : element_schema,
|
||||
'DIFFUSIVITY' : element_schema,
|
||||
'SPECIFIC GRAVITY' : element_schema,
|
||||
'TRIALS' : element_schema,
|
||||
'ACCURACY' : element_schema,
|
||||
'HEADERROR' : element_schema,
|
||||
'FLOWCHANGE' : element_schema,
|
||||
'MINIMUM PRESSURE' : element_schema,
|
||||
'REQUIRED PRESSURE' : element_schema,
|
||||
'PRESSURE EXPONENT' : element_schema,
|
||||
'TOLERANCE' : element_schema,
|
||||
'HTOL' : element_schema,
|
||||
'QTOL' : element_schema,
|
||||
'RQTOL' : element_schema,
|
||||
'CHECKFREQ' : element_schema,
|
||||
'MAXCHECK' : element_schema,
|
||||
'DAMPLIMIT' : element_schema }
|
||||
|
||||
|
||||
def get_option(name: str) -> dict[str, Any]:
|
||||
ts = read_all(name, f"select * from options")
|
||||
d = {}
|
||||
for e in ts:
|
||||
d[e['key']] = str(e['value'])
|
||||
return d
|
||||
|
||||
|
||||
def _set_option(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
raw_old = get_option(name)
|
||||
|
||||
old = {}
|
||||
new = {}
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_option_schema(name)
|
||||
for key in schema.keys():
|
||||
if key in new_dict:
|
||||
old[key] = str(raw_old[key])
|
||||
new[key] = str(new_dict[key])
|
||||
|
||||
redo_cs = g_update_prefix | { 'type' : 'option' }
|
||||
|
||||
redo_sql = ''
|
||||
for key, value in new.items():
|
||||
if redo_sql != '':
|
||||
redo_sql += '\n'
|
||||
redo_sql += f"update options set value = '{value}' where key = '{key}';"
|
||||
redo_cs |= { key: value }
|
||||
|
||||
undo_cs = g_update_prefix | { 'type' : 'option' }
|
||||
|
||||
undo_sql = ''
|
||||
for key, value in old.items():
|
||||
if undo_sql != '':
|
||||
undo_sql += '\n'
|
||||
undo_sql += f"update options set value = '{value}' where key = '{key}';"
|
||||
undo_cs |= { key: value }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_option(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_option(name, cs))
|
||||
|
||||
|
||||
OPTION_V3_FLOW_UNITS_CFS = OPTION_UNITS_CFS
|
||||
OPTION_V3_FLOW_UNITS_GPM = OPTION_UNITS_GPM
|
||||
OPTION_V3_FLOW_UNITS_MGD = OPTION_UNITS_MGD
|
||||
OPTION_V3_FLOW_UNITS_IMGD = OPTION_UNITS_IMGD
|
||||
OPTION_V3_FLOW_UNITS_AFD = OPTION_UNITS_AFD
|
||||
OPTION_V3_FLOW_UNITS_LPS = OPTION_UNITS_LPS
|
||||
OPTION_V3_FLOW_UNITS_LPM = OPTION_UNITS_LPM
|
||||
OPTION_V3_FLOW_UNITS_MLD = OPTION_UNITS_MLD
|
||||
OPTION_V3_FLOW_UNITS_CMH = OPTION_UNITS_CMH
|
||||
OPTION_V3_FLOW_UNITS_CMD = OPTION_UNITS_CMD
|
||||
|
||||
OPTION_V3_PRESSURE_UNITS_PSI = OPTION_PRESSURE_PSI
|
||||
OPTION_V3_PRESSURE_UNITS_KPA = OPTION_PRESSURE_KPA
|
||||
OPTION_V3_PRESSURE_UNITS_METERS = OPTION_PRESSURE_METERS
|
||||
|
||||
OPTION_V3_HEADLOSS_MODEL_HW = OPTION_HEADLOSS_HW
|
||||
OPTION_V3_HEADLOSS_MODEL_DW = OPTION_HEADLOSS_DW
|
||||
OPTION_V3_HEADLOSS_MODEL_CM = OPTION_HEADLOSS_CM
|
||||
|
||||
OPTION_V3_STEP_SIZING_FULL = 'FULL'
|
||||
OPTION_V3_STEP_SIZING_RELAXATION = 'RELAXATION'
|
||||
OPTION_V3_STEP_SIZING_LINESEARCH = 'LINESEARCH'
|
||||
|
||||
OPTION_V3_IF_UNBALANCED_STOP = OPTION_UNBALANCED_STOP
|
||||
OPTION_V3_IF_UNBALANCED_CONTINUE = OPTION_UNBALANCED_CONTINUE
|
||||
|
||||
OPTION_V3_DEMAND_MODEL_FIXED = 'FIXED'
|
||||
OPTION_V3_DEMAND_MODEL_CONSTRAINED = 'CONSTRAINED'
|
||||
OPTION_V3_DEMAND_MODEL_POWER = 'POWER'
|
||||
OPTION_V3_DEMAND_MODEL_LOGISTIC = 'LOGISTIC'
|
||||
|
||||
OPTION_V3_LEAKAGE_MODEL_NONE = 'NONE'
|
||||
OPTION_V3_LEAKAGE_MODEL_POWER = 'POWER'
|
||||
OPTION_V3_LEAKAGE_MODEL_FAVAD = 'FAVAD'
|
||||
|
||||
OPTION_V3_QUALITY_MODEL_NONE = OPTION_QUALITY_NONE
|
||||
OPTION_V3_QUALITY_MODEL_CHEMICAL = OPTION_QUALITY_CHEMICAL
|
||||
OPTION_V3_QUALITY_MODEL_AGE = OPTION_QUALITY_AGE
|
||||
OPTION_V3_QUALITY_MODEL_TRACE = OPTION_QUALITY_TRACE
|
||||
|
||||
OPTION_V3_QUALITY_UNITS_HRS = 'HRS'
|
||||
OPTION_V3_QUALITY_UNITS_PCNT = 'PCNT'
|
||||
OPTION_V3_QUALITY_UNITS_MGL = 'MG/L'
|
||||
OPTION_V3_QUALITY_UNITS_UGL = 'UG/L'
|
||||
|
||||
|
||||
def get_option_v3_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'FLOW_UNITS' : element_schema,
|
||||
'PRESSURE_UNITS' : element_schema,
|
||||
'HEADLOSS_MODEL' : element_schema,
|
||||
'SPECIFIC_GRAVITY' : element_schema,
|
||||
'SPECIFIC_VISCOSITY' : element_schema,
|
||||
'MAXIMUM_TRIALS' : element_schema,
|
||||
'HEAD_TOLERANCE' : element_schema,
|
||||
'FLOW_TOLERANCE' : element_schema,
|
||||
'FLOW_CHANGE_LIMIT' : element_schema,
|
||||
'RELATIVE_ACCURACY' : element_schema,
|
||||
'TIME_WEIGHT' : element_schema,
|
||||
'STEP_SIZING' : element_schema,
|
||||
'IF_UNBALANCED' : element_schema,
|
||||
'DEMAND_MODEL' : element_schema,
|
||||
'DEMAND_PATTERN' : element_schema,
|
||||
'DEMAND_MULTIPLIER' : element_schema,
|
||||
'MINIMUM_PRESSURE' : element_schema,
|
||||
'SERVICE_PRESSURE' : element_schema,
|
||||
'PRESSURE_EXPONENT' : element_schema,
|
||||
'LEAKAGE_MODEL' : element_schema,
|
||||
'LEAKAGE_COEFF1' : element_schema,
|
||||
'LEAKAGE_COEFF2' : element_schema,
|
||||
'EMITTER_EXPONENT' : element_schema,
|
||||
'QUALITY_MODEL' : element_schema,
|
||||
'QUALITY_NAME' : element_schema,
|
||||
'QUALITY_UNITS' : element_schema,
|
||||
'TRACE_NODE' : element_schema,
|
||||
'SPECIFIC_DIFFUSIVITY' : element_schema,
|
||||
'QUALITY_TOLERANCE' : element_schema }
|
||||
|
||||
|
||||
def get_option_v3(name: str) -> dict[str, Any]:
|
||||
ts = read_all(name, f"select * from options_v3")
|
||||
d = {}
|
||||
for e in ts:
|
||||
d[e['key']] = str(e['value'])
|
||||
return d
|
||||
|
||||
|
||||
def _set_option_v3(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
raw_old = get_option_v3(name)
|
||||
|
||||
old = {}
|
||||
new = {}
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_option_v3_schema(name)
|
||||
for key in schema.keys():
|
||||
if key in new_dict:
|
||||
old[key] = str(raw_old[key])
|
||||
new[key] = str(new_dict[key])
|
||||
|
||||
redo_cs = g_update_prefix | { 'type' : 'option_v3' }
|
||||
|
||||
redo_sql = ''
|
||||
for key, value in new.items():
|
||||
if redo_sql != '':
|
||||
redo_sql += '\n'
|
||||
redo_sql += f"update options_v3 set value = '{value}' where key = '{key}';"
|
||||
redo_cs |= { key: value }
|
||||
|
||||
undo_cs = g_update_prefix | { 'type' : 'option_v3' }
|
||||
|
||||
undo_sql = ''
|
||||
for key, value in old.items():
|
||||
if undo_sql != '':
|
||||
undo_sql += '\n'
|
||||
undo_sql += f"update options_v3 set value = '{value}' where key = '{key}';"
|
||||
undo_cs |= { key: value }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_option_v3(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_option_v3(name, cs))
|
||||
|
||||
|
||||
_key_map_23 = {
|
||||
'UNITS' : 'FLOW_UNITS',
|
||||
'PRESSURE' : 'PRESSURE_UNITS',
|
||||
'HEADLOSS' : 'HEADLOSS_MODEL',
|
||||
'QUALITY' : 'QUALITY_MODEL',
|
||||
'UNBALANCED' : 'IF_UNBALANCED',
|
||||
'PATTERN' : 'DEMAND_PATTERN',
|
||||
'DEMAND MODEL' : 'DEMAND_MODEL',
|
||||
'DEMAND MULTIPLIER' : 'DEMAND_MULTIPLIER',
|
||||
'EMITTER EXPONENT' : 'EMITTER_EXPONENT',
|
||||
'VISCOSITY' : 'SPECIFIC_VISCOSITY',
|
||||
'DIFFUSIVITY' : 'SPECIFIC_DIFFUSIVITY',
|
||||
'SPECIFIC GRAVITY' : 'SPECIFIC_GRAVITY',
|
||||
'TRIALS' : 'MAXIMUM_TRIALS',
|
||||
'ACCURACY' : 'RELATIVE_ACCURACY',
|
||||
#'HEADERROR' : '',
|
||||
'FLOWCHANGE' : 'FLOW_CHANGE_LIMIT',
|
||||
'MINIMUM PRESSURE' : 'MINIMUM_PRESSURE',
|
||||
'REQUIRED PRESSURE' : 'SERVICE_PRESSURE',
|
||||
'PRESSURE EXPONENT' : 'PRESSURE_EXPONENT',
|
||||
'TOLERANCE' : 'QUALITY_TOLERANCE',
|
||||
'HTOL' : 'HEAD_TOLERANCE',
|
||||
'QTOL' : 'FLOW_TOLERANCE',
|
||||
#'RQTOL' : '',
|
||||
#'CHECKFREQ' : '',
|
||||
#'MAXCHECK' : '',
|
||||
#'DAMPLIMIT' : '',
|
||||
}
|
||||
|
||||
|
||||
_key_map_32 = {
|
||||
'FLOW_UNITS' : 'UNITS',
|
||||
'PRESSURE_UNITS' : 'PRESSURE',
|
||||
'HEADLOSS_MODEL' : 'HEADLOSS',
|
||||
'SPECIFIC_GRAVITY' : 'SPECIFIC GRAVITY',
|
||||
'SPECIFIC_VISCOSITY' : 'VISCOSITY',
|
||||
'MAXIMUM_TRIALS' : 'TRIALS',
|
||||
'HEAD_TOLERANCE' : 'HTOL',
|
||||
'FLOW_TOLERANCE' : 'QTOL',
|
||||
'FLOW_CHANGE_LIMIT' : 'FLOWCHANGE',
|
||||
'RELATIVE_ACCURACY' : 'ACCURACY',
|
||||
#'TIME_WEIGHT' : '',
|
||||
#'STEP_SIZING' : '',
|
||||
'IF_UNBALANCED' : 'UNBALANCED',
|
||||
'DEMAND_MODEL' : 'DEMAND MODEL',
|
||||
'DEMAND_PATTERN' : 'PATTERN',
|
||||
'DEMAND_MULTIPLIER' : 'DEMAND MULTIPLIER',
|
||||
'MINIMUM_PRESSURE' : 'MINIMUM PRESSURE',
|
||||
'SERVICE_PRESSURE' : 'REQUIRED PRESSURE',
|
||||
'PRESSURE_EXPONENT' : 'PRESSURE EXPONENT',
|
||||
#'LEAKAGE_MODEL' : '',
|
||||
#'LEAKAGE_COEFF1' : '',
|
||||
#'LEAKAGE_COEFF2' : '',
|
||||
'EMITTER_EXPONENT' : 'EMITTER EXPONENT',
|
||||
'QUALITY_MODEL' : 'QUALITY',
|
||||
#'QUALITY_NAME' : '',
|
||||
#'QUALITY_UNITS' : '',
|
||||
#'TRACE_NODE' : '',
|
||||
'SPECIFIC_DIFFUSIVITY' : 'DIFFUSIVITY',
|
||||
'QUALITY_TOLERANCE' : 'TOLERANCE'
|
||||
}
|
||||
|
||||
|
||||
def generate_v2(cs: ChangeSet) -> ChangeSet:
|
||||
op = cs.operations[0]
|
||||
|
||||
if op['type'] == 'option':
|
||||
return cs
|
||||
|
||||
map = _key_map_32
|
||||
|
||||
cs_v2 = {}
|
||||
for key in op:
|
||||
if key == 'operation' or key == 'type':
|
||||
continue
|
||||
|
||||
if key in map.keys():
|
||||
if key != 'QUALITY_MODEL' and key != 'DEMAND_MODEL':
|
||||
cs_v2 |= { map[key] : op[key] }
|
||||
elif key == 'QUALITY_MODEL':
|
||||
if str(op[key]).upper() == OPTION_QUALITY_TRACE and 'TRACE_NODE' in op.keys():
|
||||
cs_v2 |= { map[key] : f"{OPTION_QUALITY_TRACE} {op['TRACE_NODE']}" }
|
||||
else:
|
||||
cs_v2 |= { map[key] : str(op[key]).upper() }
|
||||
elif key == 'DEMAND_MODEL':
|
||||
if op[key] == OPTION_V3_DEMAND_MODEL_FIXED:
|
||||
cs_v2 |= { map[key] : OPTION_DEMAND_MODEL_DDA }
|
||||
else:
|
||||
cs_v2 |= { map[key] : OPTION_DEMAND_MODEL_PDA }
|
||||
|
||||
if len(cs_v2) > 0:
|
||||
cs_v2 |= g_update_prefix | { 'type' : 'option' }
|
||||
return ChangeSet(cs_v2)
|
||||
|
||||
return ChangeSet()
|
||||
|
||||
|
||||
def generate_v3(cs: ChangeSet) -> ChangeSet:
|
||||
op = cs.operations[0]
|
||||
|
||||
if op['type'] == 'option_v3':
|
||||
return cs
|
||||
|
||||
map = _key_map_23
|
||||
|
||||
cs_v3 = {}
|
||||
for key in op:
|
||||
if key == 'operation' or key == 'type':
|
||||
continue
|
||||
|
||||
if key in map.keys():
|
||||
if key != 'QUALITY' and key != 'DEMAND MODEL':
|
||||
cs_v3 |= { map[key] : op[key] }
|
||||
elif key == 'QUALITY':
|
||||
tokens = str(op[key]).split()
|
||||
if len(tokens) >= 1:
|
||||
cs_v3 |= { map[key] : tokens[0].upper() }
|
||||
if tokens[0].upper() == OPTION_QUALITY_TRACE and len(tokens) >= 2:
|
||||
cs_v3 |= { 'TRACE_NODE' : tokens[1] }
|
||||
elif key == 'DEMAND MODEL':
|
||||
if op[key] == OPTION_DEMAND_MODEL_DDA:
|
||||
cs_v3 |= { map[key] : OPTION_V3_DEMAND_MODEL_FIXED }
|
||||
else:
|
||||
cs_v3 |= { map[key] : OPTION_V3_DEMAND_MODEL_POWER }
|
||||
|
||||
if len(cs_v3) > 0:
|
||||
cs_v3 |= g_update_prefix | { 'type' : 'option_v3' }
|
||||
return ChangeSet(cs_v3)
|
||||
|
||||
return ChangeSet()
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
from .database import *
|
||||
from .s23_options_util import get_option_schema, get_option_v3_schema, generate_v2, generate_v3
|
||||
|
||||
|
||||
def _parse_v2(v2_lines: list[str]) -> dict[str, str]:
|
||||
cs_v2 = g_update_prefix | { 'type' : 'option' }
|
||||
for s in v2_lines:
|
||||
tokens = s.split()
|
||||
if tokens[0].upper() == 'PATTERN': # can not upper id
|
||||
value = tokens[1] if len(tokens) > 1 else ''
|
||||
cs_v2 |= { 'PATTERN' : value }
|
||||
elif tokens[0].upper() == 'QUALITY': # can not upper trace node
|
||||
value = tokens[1]
|
||||
if len(tokens) > 2:
|
||||
value += f' {tokens[2]}'
|
||||
cs_v2 |= { 'QUALITY' : value }
|
||||
else:
|
||||
line = s.upper().strip()
|
||||
for key in get_option_schema('').keys():
|
||||
if line.startswith(key):
|
||||
value = line.removeprefix(key).strip()
|
||||
cs_v2 |= { key : value }
|
||||
return cs_v2
|
||||
|
||||
|
||||
def _inp_in_option_v3(section: list[str]) -> ChangeSet:
|
||||
if len(section) <= 0:
|
||||
return ChangeSet()
|
||||
|
||||
cs_v3 = g_update_prefix | { 'type' : 'option_v3' }
|
||||
v2_lines = []
|
||||
for s in section:
|
||||
if s.startswith(';'):
|
||||
continue
|
||||
|
||||
tokens = s.strip().split()
|
||||
key = tokens[0]
|
||||
if key in get_option_v3_schema('').keys():
|
||||
value = ''
|
||||
if len(tokens) == 2:
|
||||
value = tokens[1]
|
||||
elif len(tokens) > 2:
|
||||
value = ' '.join(tokens[1:])
|
||||
cs_v3 |= { key : value }
|
||||
else:
|
||||
v2_lines.append(s.strip())
|
||||
|
||||
# unlikely...
|
||||
cs_v2 = _parse_v2(v2_lines)
|
||||
|
||||
result = ChangeSet(cs_v3)
|
||||
result.merge(generate_v3(ChangeSet(cs_v2)))
|
||||
result.merge(generate_v2(result))
|
||||
return result
|
||||
|
||||
|
||||
def inp_in_option_v3(section: list[str]) -> str:
|
||||
sql = ''
|
||||
result = _inp_in_option_v3(section)
|
||||
for op in result.operations:
|
||||
for key in op.keys():
|
||||
if key == 'operation' or key == 'type':
|
||||
continue
|
||||
if op['type'] == 'option_v3':
|
||||
sql += f"update options_v3 set value = '{op[key]}' where key = '{key}';"
|
||||
else:
|
||||
sql += f"update options set value = '{op[key]}' where key = '{key}';"
|
||||
return sql
|
||||
|
||||
|
||||
def inp_out_option_v3(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, f"select * from options_v3")
|
||||
for obj in objs:
|
||||
key = obj['key']
|
||||
value = obj['value']
|
||||
if str(value).strip() != '':
|
||||
lines.append(f'{key} {value}')
|
||||
return lines
|
||||
@@ -0,0 +1,92 @@
|
||||
from .database import *
|
||||
from .s0_base import get_link_nodes
|
||||
|
||||
def sql_update_coord(node: str, x: float, y: float) -> str:
|
||||
coord = f"st_geomfromtext('point({x} {y})')"
|
||||
return str(f"update coordinates set coord = {coord} where node = '{node}';")
|
||||
|
||||
|
||||
def sql_insert_coord(node: str, x: float, y: float) -> str:
|
||||
coord = f"st_geomfromtext('point({x} {y})')"
|
||||
return str(f"insert into coordinates (node, coord) values ('{node}', {coord});")
|
||||
|
||||
|
||||
def sql_delete_coord(node: str) -> str:
|
||||
return str(f"delete from coordinates where node = '{node}';")
|
||||
|
||||
|
||||
def from_postgis_point(coord: str) -> dict[str, float]:
|
||||
xy = coord.lower().removeprefix('point(').removesuffix(')').split(' ')
|
||||
return { 'x': float(xy[0]), 'y': float(xy[1]) }
|
||||
|
||||
|
||||
def get_node_coord(name: str, node: str) -> dict[str, float]:
|
||||
row = try_read(name, f"select st_astext(coord) as coord_geom from coordinates where node = '{node}'")
|
||||
if row == None:
|
||||
write(name, sql_insert_coord(node, 0.0, 0.0))
|
||||
return {'x': 0.0, 'y': 0.0}
|
||||
return from_postgis_point(row['coord_geom'])
|
||||
|
||||
# DingZQ 2025-01-03, get nodes in extent
|
||||
# return node id list
|
||||
# node_id:junction:x:y
|
||||
def get_nodes_in_extent(name: str, x1: float, y1: float, x2: float, y2: float) -> list[str]:
|
||||
nodes = []
|
||||
objs = read_all(name, 'select node, st_astext(coord) as coord_geom from coordinates')
|
||||
for obj in objs:
|
||||
node_id = obj['node']
|
||||
coord = from_postgis_point(obj['coord_geom'])
|
||||
x = coord['x']
|
||||
y = coord['y']
|
||||
if x1 <= x <= x2 and y1 <= y <= y2:
|
||||
nodes.append(f"{node_id}:junction:{x}:{y}")
|
||||
return nodes
|
||||
|
||||
# DingZQ 2025-01-03, get links in extent
|
||||
# return link id list
|
||||
# link_id:pipe:node_id1:node_id2
|
||||
def get_links_in_extent(name: str, x1: float, y1: float, x2: float, y2: float) -> list[str]:
|
||||
node_ids = set([s.split(':')[0] for s in get_nodes_in_extent(name, x1, y1, x2, y2)])
|
||||
|
||||
all_link_ids = []
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select id from pipes")
|
||||
for record in cur:
|
||||
all_link_ids.append(record['id'])
|
||||
|
||||
links = []
|
||||
for link_id in all_link_ids:
|
||||
nodes = get_link_nodes(name, link_id)
|
||||
if nodes[0] in node_ids and nodes[1] in node_ids:
|
||||
links.append(f"{link_id}:pipe:{nodes[0]}:{nodes[1]}")
|
||||
return links
|
||||
|
||||
|
||||
def node_has_coord(name: str, node: str) -> bool:
|
||||
return try_read(name, f"select node from coordinates where node = '{node}'") != None
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# id x y
|
||||
#--------------------------------------------------------------
|
||||
# exception ! need merge to node change set !
|
||||
|
||||
|
||||
def inp_in_coord(line: str) -> str:
|
||||
tokens = line.split()
|
||||
node = tokens[0]
|
||||
coord = f"st_geomfromtext('point({tokens[1]} {tokens[2]})')"
|
||||
return str(f"insert into coordinates (node, coord) values ('{node}', {coord});")
|
||||
|
||||
|
||||
def inp_out_coord(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select node, st_astext(coord) as coord_geom from coordinates')
|
||||
for obj in objs:
|
||||
node = obj['node']
|
||||
coord = from_postgis_point(obj['coord_geom'])
|
||||
x = coord['x']
|
||||
y = coord['y']
|
||||
lines.append(f'{node} {x} {y}')
|
||||
return lines
|
||||
@@ -0,0 +1,120 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_vertex_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'link' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'coords' : {'type': 'list' , 'optional': False , 'readonly': False,
|
||||
'element': { 'x' : {'type': 'float' , 'optional': False , 'readonly': False },
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False } }}}
|
||||
|
||||
|
||||
def get_vertex(name: str, link: str) -> dict[str, Any]:
|
||||
cus = read_all(name, f"select * from vertices where link = '{link}' order by _order")
|
||||
cs = []
|
||||
for r in cus:
|
||||
cs.append({ 'x': float(r['x']), 'y': float(r['y']) })
|
||||
return { 'link': link, 'coords': cs }
|
||||
|
||||
|
||||
def _set_vertex(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
link = cs.operations[0]['link']
|
||||
|
||||
old = get_vertex(name, link)
|
||||
new = { 'link': link, 'coords': [] }
|
||||
|
||||
f_link = f"'{link}'"
|
||||
|
||||
# TODO: transaction ?
|
||||
redo_sql = f"delete from vertices where link = {f_link};"
|
||||
for xy in cs.operations[0]['coords']:
|
||||
x, y = float(xy['x']), float(xy['y'])
|
||||
f_x, f_y = x, y
|
||||
redo_sql += f"\ninsert into vertices (link, x, y) values ({f_link}, {f_x}, {f_y});"
|
||||
new['coords'].append({ 'x': x, 'y': y })
|
||||
|
||||
undo_sql = f"delete from vertices where link = {f_link};"
|
||||
for xy in old['coords']:
|
||||
f_x, f_y = xy['x'], xy['y']
|
||||
undo_sql += f"\ninsert into vertices (link, x, y) values ({f_link}, {f_x}, {f_y});"
|
||||
|
||||
redo_cs = { 'type': 'vertex' } | new
|
||||
undo_cs = { 'type': 'vertex' } | old
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_vertex(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = _set_vertex(name, cs)
|
||||
result.redo_cs[0] |= g_update_prefix
|
||||
result.undo_cs[0] |= g_update_prefix
|
||||
return execute_command(name, result)
|
||||
|
||||
|
||||
def _add_vertex(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
result = _set_vertex(name, cs)
|
||||
result.redo_cs[0] |= g_add_prefix
|
||||
result.undo_cs[0] |= g_delete_prefix
|
||||
return result
|
||||
|
||||
|
||||
def _delete_vertex(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
cs.operations[0]['coords'] = []
|
||||
result = _set_vertex(name, cs)
|
||||
result.redo_cs[0] |= g_delete_prefix
|
||||
result.undo_cs[0] |= g_add_prefix
|
||||
return result
|
||||
|
||||
|
||||
def add_vertex(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = _add_vertex(name, cs)
|
||||
return execute_command(name, result)
|
||||
|
||||
|
||||
def delete_vertex(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = _delete_vertex(name, cs)
|
||||
return execute_command(name, result)
|
||||
|
||||
|
||||
def get_all_vertex_links(name: str) -> list[str]:
|
||||
result : list[str] = []
|
||||
rows = read_all(name, 'select link from vertices order by link')
|
||||
for row in rows:
|
||||
result.append(str(row['link']))
|
||||
return result
|
||||
|
||||
|
||||
def get_all_vertices(name: str) -> list[dict[str, Any]]:
|
||||
return read_all(name, 'select * from vertices order by link')
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][IN][OUT]
|
||||
# id x y
|
||||
# [EPA3][NOT SUPPORT]
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_vertex(line: str) -> str:
|
||||
tokens = line.split()
|
||||
link = tokens[0]
|
||||
x = float(tokens[1])
|
||||
y = float(tokens[2])
|
||||
return str(f"insert into vertices (link, x, y) values ('{link}', {x}, {y});")
|
||||
|
||||
|
||||
def inp_out_vertex(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, f"select * from vertices order by _order")
|
||||
for obj in objs:
|
||||
link = obj['link']
|
||||
x = obj['x']
|
||||
y = obj['y']
|
||||
lines.append(f"{link} {x} {y}")
|
||||
return lines
|
||||
|
||||
|
||||
def delete_vertex_by_link(name: str, link: str) -> ChangeSet:
|
||||
row = try_read(name, f"select * from vertices where link = '{link}'")
|
||||
if row == None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_delete_prefix | {'type': 'vertex', 'link' : link})
|
||||
@@ -0,0 +1,137 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_label_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'x' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'label' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'node' : {'type': 'str' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_label(name: str, x: float, y: float) -> dict[str, Any]:
|
||||
d = {}
|
||||
d['x'] = x
|
||||
d['y'] = y
|
||||
l = try_read(name, f'select * from labels where x = {x} and y = {y}')
|
||||
if l == None:
|
||||
d['label'] = None
|
||||
d['node'] = None
|
||||
else:
|
||||
d['label'] = str(l['label'])
|
||||
d['node'] = str(l['node']) if l['node'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Label(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'label'
|
||||
self.x = float(input['x'])
|
||||
self.y = float(input['y'])
|
||||
self.label = str(input['label'])
|
||||
self.node = str(input['node']) if 'node' in input and input['node'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_x = self.x
|
||||
self.f_y = self.y
|
||||
self.f_label = f"'{self.label}'"
|
||||
self.f_node = f"'{self.node}'" if self.node != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'x': self.x, 'y': self.y, 'label': self.label, 'node': self.node }
|
||||
|
||||
def as_xy_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'x': self.x, 'y': self.y }
|
||||
|
||||
|
||||
def _set_label(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Label(get_label(name, cs.operations[0]['x'], cs.operations[0]['y']))
|
||||
raw_new = get_label(name, cs.operations[0]['x'], cs.operations[0]['y'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_label_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Label(raw_new)
|
||||
|
||||
redo_sql = f"update labels set label = {new.f_label}, node = {new.f_node} where x = {new.f_x} and y = {new.f_y};"
|
||||
undo_sql = f"update labels set label = {old.f_label}, node = {old.f_node} where x = {old.f_x} and y = {old.f_y};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_label(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_label(name, cs))
|
||||
|
||||
|
||||
def _add_label(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = Label(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into labels (x, y, label, node) values ({new.f_x}, {new.f_y}, {new.f_label}, {new.f_node});"
|
||||
undo_sql = f"delete from labels where x = {new.f_x} and y = {new.f_y};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_xy_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_label(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _add_label(name, cs))
|
||||
|
||||
|
||||
def _delete_label(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Label(get_label(name, cs.operations[0]['x'], cs.operations[0]['y']))
|
||||
|
||||
redo_sql = f"delete from labels where x = {old.f_x} and y = {old.f_y};"
|
||||
undo_sql = f"insert into labels (x, y, label, node) values ({old.f_x}, {old.f_y}, {old.f_label}, {old.f_node});"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_xy_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_label(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _delete_label(name, cs))
|
||||
|
||||
|
||||
def inp_in_label(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
x = float(tokens[0])
|
||||
y = float(tokens[1])
|
||||
label = str(tokens[2])
|
||||
node = str(tokens[3]) if num >= 4 else None
|
||||
node = f"'{node}'" if node != None else 'null'
|
||||
|
||||
return str(f"insert into labels (x, y, label, node) values ({x}, {y}, '{label}', {node});")
|
||||
|
||||
|
||||
def inp_out_label(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from labels')
|
||||
for obj in objs:
|
||||
x = obj['x']
|
||||
y = obj['y']
|
||||
label = obj['label']
|
||||
node = obj['node'] if obj['node'] != None else ''
|
||||
lines.append(f'{x} {y} {label} {node}')
|
||||
return lines
|
||||
|
||||
|
||||
def unset_label_by_node(name: str, node: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select x, y from labels where node = '{node}'")
|
||||
for row in rows:
|
||||
cs.append(g_update_prefix | {'type': 'label', 'x': row['x'], 'y': row['y'], 'node': None})
|
||||
|
||||
return cs
|
||||
@@ -0,0 +1,39 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_backdrop_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'content' : {'type': 'str' , 'optional': False , 'readonly': False} }
|
||||
|
||||
|
||||
def get_backdrop(name: str) -> dict[str, Any]:
|
||||
e = read(name, f"select * from backdrop")
|
||||
return { 'content': e['content'] }
|
||||
|
||||
|
||||
def _set_backdrop(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = get_backdrop(name)
|
||||
|
||||
redo_sql = f"update backdrop set content = '{cs.operations[0]['content']}' where content = '{old['content']}';"
|
||||
undo_sql = f"update backdrop set content = '{old['content']}' where content = '{cs.operations[0]['content']}';"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'backdrop', 'content': cs.operations[0]['content'] }
|
||||
undo_cs = g_update_prefix | { 'type': 'backdrop', 'content': old['content'] }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_backdrop(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_backdrop(name, cs))
|
||||
|
||||
|
||||
def inp_in_backdrop(section: list[str]) -> str:
|
||||
if section == []:
|
||||
return str('')
|
||||
|
||||
content = '\n'.join(section)
|
||||
return str(f"update backdrop set content = '{content}';")
|
||||
|
||||
|
||||
def inp_out_backdrop(name: str) -> list[str]:
|
||||
obj = str(get_backdrop(name)['content'])
|
||||
return obj.split('\n')
|
||||
@@ -0,0 +1,123 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
SCADA_DEVICE_TYPE_PRESSURE = 'PRESSURE'
|
||||
SCADA_DEVICE_TYPE_DEMAND = 'DEMAND'
|
||||
SCADA_DEVICE_TYPE_QUALITY = 'QUALITY'
|
||||
SCADA_DEVICE_TYPE_LEVEL = 'LEVEL'
|
||||
SCADA_DEVICE_TYPE_FLOW = 'FLOW'
|
||||
SCADA_DEVICE_TYPE_UNKNOWN = 'UNKNOWN'
|
||||
|
||||
|
||||
def get_scada_device_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str', 'optional': False, 'readonly': True },
|
||||
'name' : {'type': 'str', 'optional': True , 'readonly': False},
|
||||
'address': {'type': 'str', 'optional': True , 'readonly': False},
|
||||
'sd_type': {'type': 'str', 'optional': True , 'readonly': False}}
|
||||
|
||||
|
||||
def get_scada_device(name: str, id: str) -> dict[str, Any]:
|
||||
sm = try_read(name, f"select * from scada_device where id = '{id}'")
|
||||
if sm == None:
|
||||
return {}
|
||||
d = {}
|
||||
d['id'] = str(sm['id'])
|
||||
d['name'] = str(sm['name']) if sm['name'] != None else None
|
||||
d['address'] = str(sm['address']) if sm['address'] != None else None
|
||||
d['sd_type'] = str(sm['sd_type']) if sm['sd_type'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class ScadaDevice(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'scada_device'
|
||||
self.id = str(input['id'])
|
||||
self.name = str(input['name']) if 'name' in input and input['name'] != None else None
|
||||
self.address = str(input['address']) if 'address' in input and input['address'] != None else None
|
||||
self.sd_type = str(input['sd_type']) if 'sd_type' in input and input['sd_type'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_id = f"'{self.id}'"
|
||||
self.f_name = f"'{self.name}'" if self.name != None else 'null'
|
||||
self.f_address = f"'{self.address}'" if self.address != None else 'null'
|
||||
self.f_sd_type = f"'{self.sd_type}'" if self.sd_type != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id, 'name': self.name, 'address': self.address, 'sd_type': self.sd_type }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id }
|
||||
|
||||
|
||||
def _set_scada_device(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = ScadaDevice(get_scada_device(name, cs.operations[0]['id']))
|
||||
raw_new = get_scada_device(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_scada_device_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = ScadaDevice(raw_new)
|
||||
|
||||
redo_sql = f"update scada_device set name = {new.f_name}, address = {new.f_address}, sd_type = {new.f_sd_type} where id = {new.f_id};"
|
||||
undo_sql = f"update scada_device set name = {old.f_name}, address = {old.f_address}, sd_type = {old.f_sd_type} where id = {old.f_id};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_scada_device(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if get_scada_device(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_scada_device(name, cs), False)
|
||||
|
||||
|
||||
def _add_scada_device(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = ScadaDevice(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into scada_device (id, name, address, sd_type) values ({new.f_id}, {new.f_name}, {new.f_address}, {new.f_sd_type});"
|
||||
undo_sql = f"delete from scada_device where id = {new.f_id};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_id_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_scada_device(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if get_scada_device(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_scada_device(name, cs), False)
|
||||
|
||||
|
||||
def _delete_scada_device(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = ScadaDevice(get_scada_device(name, cs.operations[0]['id']))
|
||||
|
||||
redo_sql = f"delete from scada_device where id = {old.f_id};"
|
||||
undo_sql = f"insert into scada_device (id, name, address, sd_type) values ({old.f_id}, {old.f_name}, {old.f_address}, {old.f_sd_type});"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_id_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_scada_device(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if get_scada_device(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_scada_device(name, cs), False)
|
||||
|
||||
|
||||
def get_all_scada_device_ids(name: str) -> list[str]:
|
||||
result : list[str] = []
|
||||
rows = read_all(name, 'select id from scada_device order by id')
|
||||
for row in rows:
|
||||
result.append(str(row['id']))
|
||||
return result
|
||||
|
||||
|
||||
def get_all_scada_devices(name: str) -> list[dict[str, Any]]:
|
||||
return read_all(name, 'select * from scada_device order by id')
|
||||
@@ -0,0 +1,191 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
from .s24_coordinates import *
|
||||
|
||||
|
||||
def get_junction_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'x' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'elevation' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'links' : {'type': 'str_list' , 'optional': False , 'readonly': True } }
|
||||
|
||||
|
||||
def get_junction(name: str, id: str) -> dict[str, Any]:
|
||||
j = try_read(name, f"select * from junctions where id = '{id}'")
|
||||
if j == None:
|
||||
return {}
|
||||
xy = get_node_coord(name, id)
|
||||
d = {}
|
||||
d['id'] = str(j['id'])
|
||||
d['x'] = float(xy['x'])
|
||||
d['y'] = float(xy['y'])
|
||||
d['elevation'] = float(j['elevation'])
|
||||
d['links'] = get_node_links(name, id)
|
||||
return d
|
||||
|
||||
# DingZQ, 2025-03-29
|
||||
def get_all_junctions(name: str) -> list[dict[str, Any]]:
|
||||
rows = read_all(name, f"select * from junctions")
|
||||
if rows == None:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
d = {}
|
||||
id = str(row['id'])
|
||||
xy = get_node_coord(name, id)
|
||||
d['id'] = id
|
||||
d['x'] = float(xy['x'])
|
||||
d['y'] = float(xy['y'])
|
||||
d['elevation'] = float(row['elevation'])
|
||||
d['links'] = get_node_links(name, id)
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
class Junction(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'junction'
|
||||
self.id = str(input['id'])
|
||||
self.x = float(input['x'])
|
||||
self.y = float(input['y'])
|
||||
self.elevation = float(input['elevation'])
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_id = f"'{self.id}'"
|
||||
self.f_elevation = self.elevation
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'elevation': self.elevation }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id }
|
||||
|
||||
|
||||
def _set_junction(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Junction(get_junction(name, cs.operations[0]['id']))
|
||||
raw_new = get_junction(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_junction_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Junction(raw_new)
|
||||
|
||||
redo_sql = f"update junctions set elevation = {new.f_elevation} where id = {new.f_id};"
|
||||
redo_sql += f"\n{sql_update_coord(new.id, new.x, new.y)}"
|
||||
|
||||
undo_sql = sql_update_coord(old.id, old.x, old.y)
|
||||
undo_sql += f"\nupdate junctions set elevation = {old.f_elevation} where id = {old.f_id};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_junction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_junction(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_junction(name, cs))
|
||||
|
||||
|
||||
def _add_junction(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = Junction(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into _node (id, type) values ({new.f_id}, {new.f_type});"
|
||||
redo_sql += f"\ninsert into junctions (id, elevation) values ({new.f_id}, {new.f_elevation});"
|
||||
redo_sql += f"\n{sql_insert_coord(new.id, new.x, new.y)}"
|
||||
|
||||
undo_sql = sql_delete_coord(new.id)
|
||||
undo_sql += f"\ndelete from junctions where id = {new.f_id};"
|
||||
undo_sql += f"\ndelete from _node where id = {new.f_id};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_id_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_junction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_junction(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_junction(name, cs))
|
||||
|
||||
|
||||
def _delete_junction(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Junction(get_junction(name, cs.operations[0]['id']))
|
||||
|
||||
redo_sql = sql_delete_coord(old.id)
|
||||
redo_sql += f"\ndelete from junctions where id = {old.f_id};"
|
||||
redo_sql += f"\ndelete from _node where id = {old.f_id};"
|
||||
|
||||
undo_sql = f"insert into _node (id, type) values ({old.f_id}, {old.f_type});"
|
||||
undo_sql += f"\ninsert into junctions (id, elevation) values ({old.f_id}, {old.f_elevation});"
|
||||
undo_sql += f"\n{sql_insert_coord(old.id, old.x, old.y)}"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_id_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_junction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_junction(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_junction(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2]
|
||||
# [IN]
|
||||
# id elev. (demand) (demand pattern) ;desc
|
||||
# [OUT]
|
||||
# id elev. ;desc
|
||||
#--------------------------------------------------------------
|
||||
# [EPA3]
|
||||
# [IN]
|
||||
# id elev. (demand) (demand pattern)
|
||||
# [OUT]
|
||||
# id elev. * * minpressure fullpressure
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_junction(line: str, demand_outside: bool) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
elevation = float(tokens[1])
|
||||
demand = float(tokens[2]) if num_without_desc >= 3 and tokens[2] != '*' else None
|
||||
pattern = str(tokens[3]) if num_without_desc >= 4 and tokens[3] != '*' else None
|
||||
pattern = f"'{pattern}'" if pattern != None else 'null'
|
||||
desc = str(tokens[-1]) if has_desc else None
|
||||
|
||||
sql = f"insert into _node (id, type) values ('{id}', 'junction');insert into junctions (id, elevation) values ('{id}', {elevation});"
|
||||
if demand != None and demand_outside == False:
|
||||
sql += f"insert into demands (junction, demand, pattern) values ('{id}', {demand}, {pattern});"
|
||||
|
||||
return str(sql)
|
||||
|
||||
|
||||
def inp_out_junction(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from junctions')
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
elev = obj['elevation']
|
||||
desc = ';'
|
||||
lines.append(f'{id} {elev} {desc}')
|
||||
return lines
|
||||
@@ -0,0 +1,90 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_scada_device_data_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'device_id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'data' : {'type': 'list' , 'optional': False , 'readonly': False,
|
||||
'element': { 'time' : {'type': 'str' , 'optional': False , 'readonly': False },
|
||||
'value' : {'type': 'float' , 'optional': False , 'readonly': False } }}}
|
||||
|
||||
|
||||
def get_scada_device_data(name: str, device_id: str) -> dict[str, Any]:
|
||||
sds = read_all(name, f"select * from scada_device_data where device_id = '{device_id}' order by time")
|
||||
ds = []
|
||||
for r in sds:
|
||||
ds.append({ 'time': str(r['time']), 'value': float(r['value']) })
|
||||
return { 'device_id': device_id, 'data': ds }
|
||||
|
||||
|
||||
def _set_scada_device_data(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
device_id = cs.operations[0]['device_id']
|
||||
|
||||
old = get_scada_device_data(name, device_id)
|
||||
new = { 'device_id': device_id, 'data': [] }
|
||||
|
||||
f_device_id = f"'{device_id}'"
|
||||
|
||||
# TODO: transaction ?
|
||||
redo_sql = f"delete from scada_device_data where device_id = {f_device_id};"
|
||||
for tv in cs.operations[0]['data']:
|
||||
time, value = str(tv['time']), float(tv['value'])
|
||||
f_time, f_value = f"'{time}'", value
|
||||
redo_sql += f"\ninsert into scada_device_data (device_id, time, value) values ({f_device_id}, {f_time}, {f_value});"
|
||||
new['data'].append({ 'time': time, 'value': value })
|
||||
|
||||
undo_sql = f"delete from scada_device_data where device_id = {f_device_id};"
|
||||
for tv in old['data']:
|
||||
time, value = str(tv['time']), float(tv['value'])
|
||||
f_time, f_value = f"'{time}'", value
|
||||
undo_sql += f"\ninsert into scada_device_data (device_id, time, value) values ({f_device_id}, {f_time}, {f_value});"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'scada_device_data' } | new
|
||||
undo_cs = g_update_prefix | { 'type': 'scada_device_data' } | old
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_scada_device_data(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_scada_device_data(name, cs), False)
|
||||
|
||||
|
||||
def _add_scada_device_data(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
values = cs.operations[0]
|
||||
device_id = values['device_id']
|
||||
time = values['time']
|
||||
value = float(values['value'])
|
||||
|
||||
redo_sql = f"insert into scada_device_data (device_id, time, value) values ('{device_id}', '{time}', {value});"
|
||||
undo_sql = f"delete from scada_device_data where device_id = '{device_id}' and time = '{time}';"
|
||||
redo_cs = g_add_prefix | { 'type': 'scada_device_data', 'device_id': device_id, 'time': time, 'value': value }
|
||||
undo_cs = g_delete_prefix | { 'type': 'scada_device_data', 'device_id': device_id, 'time': time }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_scada_device_data(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
row = try_read(name, f"select * from scada_device_data where device_id = '{cs.operations[0]['device_id']}' and time = '{cs.operations[0]['time']}'")
|
||||
if row != None:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_scada_device_data(name, cs), False)
|
||||
|
||||
|
||||
def _delete_scada_device_data(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
values = cs.operations[0]
|
||||
device_id = values['device_id']
|
||||
time = values['time']
|
||||
value = float(read(name, f"select * from scada_device_data where device_id = '{device_id}' and time = '{time}'")['value'])
|
||||
|
||||
redo_sql = f"delete from scada_device_data where device_id = '{device_id}' and time = '{time}';"
|
||||
undo_sql = f"insert into scada_device_data (device_id, time, value) values ('{device_id}', '{time}', {value});"
|
||||
redo_cs = g_delete_prefix | { 'type': 'scada_device_data', 'device_id': device_id, 'time': time }
|
||||
undo_cs = g_add_prefix | { 'type': 'scada_device_data', 'device_id': device_id, 'time': time, 'value': value }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_scada_device_data(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
row = try_read(name, f"select * from scada_device_data where device_id = '{cs.operations[0]['device_id']}' and time = '{cs.operations[0]['time']}'")
|
||||
if row == None:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_scada_device_data(name, cs), False)
|
||||
@@ -0,0 +1,197 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
|
||||
|
||||
SCADA_TYPE_PRESSURE = 'PRESSURE'
|
||||
SCADA_TYPE_DEMAND = 'DEMAND'
|
||||
SCADA_TYPE_QUALITY = 'QUALITY'
|
||||
SCADA_TYPE_LEVEL = 'LEVEL'
|
||||
SCADA_TYPE_FLOW = 'FLOW'
|
||||
|
||||
|
||||
SCADA_MODEL_TYPE_JUNCTION = 'JUNCTION'
|
||||
SCADA_MODEL_TYPE_RESERVOIR = 'RESERVOIR'
|
||||
SCADA_MODEL_TYPE_TANK = 'TANK'
|
||||
SCADA_MODEL_TYPE_PIPE = 'PIPE'
|
||||
SCADA_MODEL_TYPE_PUMP = 'PUMP'
|
||||
SCADA_MODEL_TYPE_VALVE = 'VALVE'
|
||||
|
||||
|
||||
SCADA_ELEMENT_STATUS_OFFLINE = 'OFF'
|
||||
SCADA_ELEMENT_STATUS_ONLINE = 'ON'
|
||||
|
||||
|
||||
_scada_model_types = [SCADA_MODEL_TYPE_JUNCTION, SCADA_MODEL_TYPE_RESERVOIR, SCADA_MODEL_TYPE_TANK, SCADA_MODEL_TYPE_PIPE, SCADA_MODEL_TYPE_PUMP, SCADA_MODEL_TYPE_VALVE]
|
||||
|
||||
|
||||
def _check_model(name: str, cs: ChangeSet) -> bool:
|
||||
has_model_id = 'model_id' in cs.operations[0]
|
||||
has_model_type = 'model_type' in cs.operations[0]
|
||||
|
||||
if has_model_id and has_model_type:
|
||||
pass
|
||||
elif has_model_id and not has_model_type:
|
||||
return False
|
||||
elif not has_model_id and has_model_type:
|
||||
return False
|
||||
elif not has_model_id and not has_model_type:
|
||||
return True
|
||||
|
||||
_model_id = cs.operations[0]['model_id']
|
||||
_model_type = cs.operations[0]['model_type']
|
||||
if _model_type == SCADA_MODEL_TYPE_JUNCTION:
|
||||
return is_junction(name, _model_id)
|
||||
elif _model_type == SCADA_MODEL_TYPE_RESERVOIR:
|
||||
return is_reservoir(name, _model_id)
|
||||
elif _model_type == SCADA_MODEL_TYPE_TANK:
|
||||
return is_tank(name, _model_id)
|
||||
elif _model_type == SCADA_MODEL_TYPE_PIPE:
|
||||
return is_pipe(name, _model_id)
|
||||
elif _model_type == SCADA_MODEL_TYPE_PUMP:
|
||||
return is_pump(name, _model_id)
|
||||
elif _model_type == SCADA_MODEL_TYPE_VALVE:
|
||||
return is_valve(name, _model_id)
|
||||
return False
|
||||
|
||||
|
||||
def get_scada_element_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'x' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'device_id' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'model_id' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'model_type' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'status' : {'type': 'str' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_scada_element(name: str, id: str) -> dict[str, Any]:
|
||||
sm = try_read(name, f"select * from scada_element where id = '{id}'")
|
||||
if sm == None:
|
||||
return {}
|
||||
d = {}
|
||||
d['id'] = str(sm['id'])
|
||||
d['x'] = float(sm['x'])
|
||||
d['y'] = float(sm['y'])
|
||||
d['device_id'] = str(sm['device_id']) if sm['device_id'] != None else None
|
||||
d['model_id'] = str(sm['model_id']) if sm['model_id'] != None else None
|
||||
d['model_type'] = str(sm['model_type']) if sm['model_type'] != None else None
|
||||
d['status'] = str(sm['status'])
|
||||
return d
|
||||
|
||||
|
||||
class ScadaModel(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'scada_element'
|
||||
self.id = str(input['id'])
|
||||
self.x = float(input['x'])
|
||||
self.y = float(input['y'])
|
||||
self.device_id = str(input['device_id']) if 'device_id' in input and input['device_id'] != None else None
|
||||
self.model_id = str(input['model_id']) if 'model_id' in input and input['model_id'] != None else None
|
||||
self.model_type = str(input['model_type']) if 'model_type' in input and input['model_type'] != None else None
|
||||
self.status = str(input['status']) if 'status' in input and input['status'] != None else SCADA_ELEMENT_STATUS_OFFLINE
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_id = f"'{self.id}'"
|
||||
self.f_x = self.x
|
||||
self.f_y = self.y
|
||||
self.f_device_id = f"'{self.device_id}'" if self.device_id != None else 'null'
|
||||
self.f_model_id = f"'{self.model_id}'" if self.model_id != None else 'null'
|
||||
self.f_model_type = f"'{self.model_type}'" if self.model_type != None else 'null'
|
||||
self.f_status = f"'{self.status}'"
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'device_id': self.device_id, 'model_id': self.model_id, 'model_type': self.model_type, 'status': self.status }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id }
|
||||
|
||||
|
||||
def _set_scada_element(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = ScadaModel(get_scada_element(name, cs.operations[0]['id']))
|
||||
raw_new = get_scada_element(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_scada_element_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = ScadaModel(raw_new)
|
||||
|
||||
redo_sql = f"update scada_element set x = {new.f_x}, y = {new.f_y}, device_id = {new.f_device_id}, model_id = {new.f_model_id}, model_type = {new.f_model_type}, status = {new.f_status} where id = {new.f_id};"
|
||||
undo_sql = f"update scada_element set x = {old.f_x}, y = {old.f_y}, device_id = {old.f_device_id}, model_id = {old.f_model_id}, model_type = {old.f_model_type}, status = {old.f_status} where id = {old.f_id};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_scada_element(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if get_scada_element(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
if _check_model(name, cs) == False:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_scada_element(name, cs))
|
||||
|
||||
|
||||
def _add_scada_element(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = ScadaModel(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into scada_element (id, x, y, device_id, model_id, model_type, status) values ({new.f_id}, {new.f_x}, {new.f_y}, {new.f_device_id}, {new.f_model_id}, {new.f_model_type}, {new.f_status});"
|
||||
undo_sql = f"delete from scada_element where id = {new.f_id};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_id_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_scada_element(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if get_scada_element(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
if _check_model(name, cs) == False:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_scada_element(name, cs))
|
||||
|
||||
|
||||
def _delete_scada_element(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = ScadaModel(get_scada_element(name, cs.operations[0]['id']))
|
||||
|
||||
redo_sql = f"delete from scada_element where id = {old.f_id};"
|
||||
undo_sql = f"insert into scada_element (id, x, y, device_id, model_id, model_type, status) values ({old.f_id}, {old.f_x}, {old.f_y}, {old.f_device_id}, {old.f_model_id}, {old.f_model_type}, {old.f_status});"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_id_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_scada_element(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if get_scada_element(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_scada_element(name, cs))
|
||||
|
||||
|
||||
def get_all_scada_element_ids(name: str) -> list[str]:
|
||||
result : list[str] = []
|
||||
rows = read_all(name, 'select id from scada_element order by id')
|
||||
for row in rows:
|
||||
result.append(str(row['id']))
|
||||
return result
|
||||
|
||||
#
|
||||
# create table scada_element
|
||||
# (
|
||||
# id text primary key
|
||||
# , x float8 not null
|
||||
# , y float8 not null
|
||||
# , device_id text references scada_device(id)
|
||||
# , model_id varchar(32) -- add constraint in API
|
||||
# , model_type scada_model_type
|
||||
# , status scada_element_status not null default 'OFF'
|
||||
# );
|
||||
#
|
||||
# 返回list,list里每个item是dict,内容是 'id':'abc' 这样
|
||||
# scada_model type 是类似pressure,flow之类的,是由Device 决定 的
|
||||
def get_all_scada_elements(name: str) -> list[dict[str, Any]]:
|
||||
return read_all(name, 'select * from scada_element order by id')
|
||||
@@ -0,0 +1,95 @@
|
||||
from .database import *
|
||||
from .s32_region_util import from_postgis_polygon, to_postgis_polygon
|
||||
|
||||
def get_region_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False} }
|
||||
|
||||
|
||||
def get_region(name: str, id: str) -> dict[str, Any]:
|
||||
r = try_read(name, f"select id, st_astext(boundary) as boundary_geom from region where id = '{id}'")
|
||||
if r == None:
|
||||
return {}
|
||||
d = {}
|
||||
d['id'] = str(r['id'])
|
||||
d['boundary'] = from_postgis_polygon(str(r['boundary_geom']))
|
||||
return d
|
||||
|
||||
|
||||
def _set_region(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
new = cs.operations[0]['boundary']
|
||||
old = get_region(name, id)['boundary']
|
||||
|
||||
redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new)}') where id = '{id}';"
|
||||
undo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old)}') where id = '{id}';"
|
||||
redo_cs = g_update_prefix | { 'type': 'region', 'id': id, 'boundary': new }
|
||||
undo_cs = g_update_prefix | { 'type': 'region', 'id': id, 'boundary': old }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_region(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0] or 'boundary' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
b = cs.operations[0]['boundary']
|
||||
if len(b) < 4 or b[0] != b[-1]:
|
||||
return ChangeSet()
|
||||
if get_region(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_region(name, cs))
|
||||
|
||||
|
||||
def _add_region(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
new = cs.operations[0]['boundary']
|
||||
|
||||
redo_sql = f"insert into region (id, boundary) values ('{id}', '{to_postgis_polygon(new)}');"
|
||||
undo_sql = f"delete from region where id = '{id}';"
|
||||
redo_cs = g_add_prefix | { 'type': 'region', 'id': id, 'boundary': new }
|
||||
undo_cs = g_delete_prefix | { 'type': 'region', 'id': id }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_region(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0] or 'boundary' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
b = cs.operations[0]['boundary']
|
||||
if len(b) < 4 or b[0] != b[-1]:
|
||||
return ChangeSet()
|
||||
if get_region(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_region(name, cs))
|
||||
|
||||
|
||||
def _delete_region(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
old = get_region(name, id)['boundary']
|
||||
|
||||
redo_sql = f"delete from region where id = '{id}';"
|
||||
undo_sql = f"insert into region (id, boundary) values ('{id}', '{to_postgis_polygon(old)}');"
|
||||
redo_cs = g_delete_prefix | { 'type': 'region', 'id': id }
|
||||
undo_cs = g_add_prefix | { 'type': 'region', 'id': id, 'boundary': old }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_region(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_region(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_region(name, cs))
|
||||
|
||||
def inp_in_region(line: str) -> str:
|
||||
tokens = line.split()
|
||||
return str(f"insert into _region (id, type) values ('{tokens[0]}', '{tokens[1]}');")
|
||||
|
||||
def inp_in_bound(line: str) -> str:
|
||||
tokens = line.split()
|
||||
return tokens[0]
|
||||
|
||||
def inp_in_regionnodes(line: str)->str:
|
||||
tokens = line.split()
|
||||
return tokens[0]
|
||||
@@ -0,0 +1,463 @@
|
||||
import ctypes
|
||||
import platform
|
||||
import os
|
||||
import math
|
||||
from typing import Any
|
||||
from .s0_base import get_node_links, get_link_nodes, is_pipe
|
||||
from .s5_pipes import get_pipe
|
||||
from .database import read, try_read, read_all, write
|
||||
from .s24_coordinates import node_has_coord, get_node_coord
|
||||
|
||||
|
||||
def from_postgis_polygon(polygon: str) -> list[tuple[float, float]]:
|
||||
boundary = polygon.lower().removeprefix('polygon((').removesuffix('))').split(',')
|
||||
xys = []
|
||||
for pt in boundary:
|
||||
xy = pt.split(' ')
|
||||
xys.append((float(xy[0]), float(xy[1])))
|
||||
return xys
|
||||
|
||||
|
||||
def to_postgis_polygon(boundary: list[tuple[float, float]]) -> str:
|
||||
polygon = ''
|
||||
for pt in boundary:
|
||||
polygon += f'{pt[0]} {pt[1]},'
|
||||
return str(f'polygon(({polygon[:-1]}))')
|
||||
|
||||
|
||||
def to_postgis_linestring(boundary: list[tuple[float, float]]) -> str:
|
||||
line = ''
|
||||
for pt in boundary:
|
||||
line += f'{pt[0]} {pt[1]},'
|
||||
return str(f'linestring({line[:-1]})')
|
||||
|
||||
|
||||
def get_nodes_in_boundary(name: str, boundary: list[tuple[float, float]]) -> list[str]:
|
||||
api = 'get_nodes_in_boundary'
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
write(name, f"insert into temp_region (id, boundary) values ('{api}', '{to_postgis_polygon(boundary)}')")
|
||||
|
||||
nodes: list[str] = []
|
||||
for row in read_all(name, f"select c.node from coordinates as c, temp_region as r where ST_Intersects(c.coord, r.boundary) and r.id = '{api}'"):
|
||||
nodes.append(row['node'])
|
||||
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
|
||||
return nodes
|
||||
|
||||
|
||||
def _get_links_on_boundary(name: str, nodes: list[str]) -> list[str]:
|
||||
links: list[str] = []
|
||||
|
||||
for node in nodes:
|
||||
node_links = get_node_links(name, node)
|
||||
for link in node_links:
|
||||
if link in links:
|
||||
continue
|
||||
|
||||
link_nodes = get_link_nodes(name, link)
|
||||
if link_nodes[0] in nodes and link_nodes[1] not in nodes:
|
||||
links.append(link)
|
||||
elif link_nodes[0] not in nodes and link_nodes[1] in nodes:
|
||||
links.append(link)
|
||||
|
||||
return links
|
||||
|
||||
|
||||
# if region is general or wda => get_nodes_in_boundary
|
||||
# if region is dma, sa or vd => get stored nodes in table
|
||||
def get_nodes_in_region(name: str, region_id: str) -> list[str]:
|
||||
nodes: list[str] = []
|
||||
|
||||
row = try_read(name, f"select r_type from region where id = '{region_id}'")
|
||||
if row == None:
|
||||
return nodes
|
||||
|
||||
r_type = str(row['r_type'])
|
||||
|
||||
if r_type == 'DMA' or r_type == 'SA' or r_type == 'VD':
|
||||
table = ''
|
||||
if r_type == 'DMA':
|
||||
table = 'region_dma'
|
||||
elif r_type == 'SA':
|
||||
table = 'region_sa'
|
||||
elif r_type == 'VD':
|
||||
table = 'region_vd'
|
||||
|
||||
if table != '':
|
||||
row = try_read(name, f"select nodes from {table} where id = '{region_id}'")
|
||||
if row != None:
|
||||
nodes = eval(str(row['nodes']))
|
||||
|
||||
if nodes == []:
|
||||
for row in read_all(name, f"select c.node from coordinates as c, region as r where ST_Intersects(c.coord, r.boundary) and r.id = '{region_id}'"):
|
||||
nodes.append(row['node'])
|
||||
|
||||
return nodes
|
||||
|
||||
|
||||
def get_links_on_region_boundary(name: str, region_id: str) -> list[str]:
|
||||
nodes = get_nodes_in_region(name, region_id)
|
||||
print(nodes)
|
||||
return _get_links_on_boundary(name, nodes)
|
||||
|
||||
|
||||
def calculate_convex_hull(name: str, nodes: list[str]) -> list[tuple[float, float]]:
|
||||
write(name, f'delete from temp_node')
|
||||
for node in nodes:
|
||||
write(name, f"insert into temp_node values ('{node}')")
|
||||
|
||||
# TODO: check none
|
||||
polygon = read(name, f'select st_astext(st_convexhull(st_collect(array(select coord from coordinates where node in (select * from temp_node))))) as boundary' )['boundary']
|
||||
write(name, f'delete from temp_node')
|
||||
|
||||
return from_postgis_polygon(polygon)
|
||||
|
||||
|
||||
def _verify_platform():
|
||||
_platform = platform.system()
|
||||
if _platform != "Windows":
|
||||
raise Exception(f'Platform {_platform} unsupported (not yet)')
|
||||
|
||||
|
||||
def _normal(v: tuple[float, float]) -> tuple[float, float]:
|
||||
l = math.sqrt(v[0] * v[0] + v[1] * v[1])
|
||||
return (v[0] / l, v[1] / l)
|
||||
|
||||
|
||||
def _angle(v: tuple[float, float]) -> float:
|
||||
if v[0] >= 0 and v[1] >= 0:
|
||||
return math.asin(v[1])
|
||||
elif v[0] <= 0 and v[1] >= 0:
|
||||
return math.pi - math.asin(v[1])
|
||||
elif v[0] <= 0 and v[1] <= 0:
|
||||
return math.asin(-v[1]) + math.pi
|
||||
elif v[0] >= 0 and v[1] <= 0:
|
||||
return math.pi * 2 - math.asin(-v[1])
|
||||
return 0
|
||||
|
||||
|
||||
def _angle_of_node_link(node: str, link: str, nodes, links) -> float:
|
||||
n1 = node
|
||||
n2 = links[link]['node1'] if n1 == links[link]['node2'] else links[link]['node2']
|
||||
x1, y1 = nodes[n1]['x'], nodes[n1]['y']
|
||||
x2, y2 = nodes[n2]['x'], nodes[n2]['y']
|
||||
if y1 == y2:
|
||||
v = ((x2 - x1) / abs(x2 - x1), 0.0)
|
||||
else:
|
||||
v = _normal((x2 - x1, y2 - y1))
|
||||
return _angle(v)
|
||||
|
||||
|
||||
class Topology:
|
||||
def __init__(self, db: str, nodes: list[str]) -> None:
|
||||
self._nodes: dict[str, Any] = {}
|
||||
self._max_x_node = ''
|
||||
self._node_list: list[str] = []
|
||||
for node in nodes:
|
||||
if not node_has_coord(db, node):
|
||||
continue
|
||||
if get_node_links(db, node) == 0:
|
||||
continue
|
||||
self._nodes[node] = get_node_coord(db, node) | { 'links': [] }
|
||||
self._node_list.append(node)
|
||||
if self._max_x_node == '' or self._nodes[node]['x'] > self._nodes[self._max_x_node]['x']:
|
||||
self._max_x_node = node
|
||||
|
||||
self._links: dict[str, Any] = {}
|
||||
self._link_list: list[str] = []
|
||||
for node in self._nodes:
|
||||
for link in get_node_links(db, node):
|
||||
candidate = True
|
||||
link_nodes = get_link_nodes(db, link)
|
||||
for link_node in link_nodes:
|
||||
if link_node not in self._nodes:
|
||||
candidate = False
|
||||
break
|
||||
if candidate:
|
||||
length = get_pipe(db, link)['length'] if is_pipe(db, link) else 0.0
|
||||
self._links[link] = { 'node1' : link_nodes[0], 'node2' : link_nodes[1], 'length' : length }
|
||||
self._link_list.append(link)
|
||||
if link not in self._nodes[link_nodes[0]]['links']:
|
||||
self._nodes[link_nodes[0]]['links'].append(link)
|
||||
if link not in self._nodes[link_nodes[1]]['links']:
|
||||
self._nodes[link_nodes[1]]['links'].append(link)
|
||||
|
||||
def nodes(self):
|
||||
return self._nodes
|
||||
|
||||
def node_list(self):
|
||||
return self._node_list
|
||||
|
||||
def max_x_node(self):
|
||||
return self._max_x_node
|
||||
|
||||
def links(self):
|
||||
return self._links
|
||||
|
||||
def link_list(self):
|
||||
return self._link_list
|
||||
|
||||
|
||||
def _calculate_boundary(cursor: str, t_nodes: dict[str, Any], t_links: dict[str, Any]) -> tuple[list[str], dict[str, list[str]], list[tuple[float, float]]]:
|
||||
in_angle = 0
|
||||
|
||||
vertices: list[str] = []
|
||||
path: dict[str, list[str]] = {}
|
||||
while True:
|
||||
# prevent duplicated node
|
||||
if len(vertices) > 0 and cursor == vertices[-1]:
|
||||
break
|
||||
|
||||
# prevent duplicated path
|
||||
if len(vertices) >= 3 and vertices[0] == vertices[-1] and vertices[1] == cursor:
|
||||
break
|
||||
|
||||
vertices.append(cursor)
|
||||
|
||||
sorted_links = []
|
||||
overlapped_link = ''
|
||||
for link in t_nodes[cursor]['links']:
|
||||
angle = _angle_of_node_link(cursor, link, t_nodes, t_links)
|
||||
if angle == in_angle:
|
||||
overlapped_link = link
|
||||
continue
|
||||
sorted_links.append((angle, link))
|
||||
|
||||
# work into a branch, return
|
||||
if len(sorted_links) == 0:
|
||||
path[overlapped_link] = []
|
||||
cursor = vertices[-2]
|
||||
in_angle = _angle_of_node_link(cursor, overlapped_link, t_nodes, t_links)
|
||||
continue
|
||||
|
||||
sorted_links = sorted(sorted_links, key=lambda s:s[0])
|
||||
out_link = sorted_links[0][1]
|
||||
for angle, link in sorted_links:
|
||||
if angle > in_angle:
|
||||
out_link = link
|
||||
break
|
||||
|
||||
path[out_link] = []
|
||||
cursor = t_links[out_link]['node1'] if cursor == t_links[out_link]['node2'] else t_links[out_link]['node2']
|
||||
in_angle = _angle_of_node_link(cursor, out_link, t_nodes, t_links)
|
||||
|
||||
boundary: list[tuple[float, float]] = []
|
||||
for node in vertices:
|
||||
boundary.append((t_nodes[node]['x'], t_nodes[node]['y']))
|
||||
|
||||
return (vertices, path, boundary)
|
||||
|
||||
|
||||
def _collect_new_links(in_links: dict[str, list[str]], t_nodes: dict[str, Any], t_links: dict[str, Any], new_nodes: dict[str, Any], new_links: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
for link, pts in in_links.items():
|
||||
node1 = t_links[link]['node1']
|
||||
node2 = t_links[link]['node2']
|
||||
x1, x2 = t_nodes[node1]['x'], t_nodes[node2]['x']
|
||||
y1, y2 = t_nodes[node1]['y'], t_nodes[node2]['y']
|
||||
|
||||
if node1 not in new_nodes:
|
||||
new_nodes[node1] = { 'x': x1, 'y': y1, 'links': [] }
|
||||
if node2 not in new_nodes:
|
||||
new_nodes[node2] = { 'x': x2, 'y': y2, 'links': [] }
|
||||
|
||||
x_delta = x2 - x1
|
||||
y_delta = y2 - y1
|
||||
use_x = abs(x_delta) > abs(y_delta)
|
||||
|
||||
if len(pts) == 0:
|
||||
new_links[link] = t_links[link]
|
||||
else:
|
||||
sorted_nodes: list[tuple[float, str]] = []
|
||||
sorted_nodes.append((0.0, node1))
|
||||
sorted_nodes.append((1.0, node2))
|
||||
i = 0
|
||||
for pt in pts:
|
||||
x, y = new_nodes[pt]['x'], new_nodes[pt]['y']
|
||||
percent = ((x - x1) / x_delta) if use_x else ((y - y1) / y_delta)
|
||||
sorted_nodes.append((percent, pt))
|
||||
i += 1
|
||||
sorted_nodes = sorted(sorted_nodes, key=lambda s:s[0])
|
||||
|
||||
for i in range(1, len(sorted_nodes)):
|
||||
l = sorted_nodes[i - 1][1]
|
||||
r = sorted_nodes[i][1]
|
||||
new_link = f'LINK_[{l}]_[{r}]'
|
||||
new_links[new_link] = { 'node1': l, 'node2': r }
|
||||
|
||||
return (new_nodes, new_links)
|
||||
|
||||
|
||||
def calculate_boundary(name: str, nodes: list[str], accurate = False) -> list[tuple[float, float]]:
|
||||
topology = Topology(name, nodes)
|
||||
t_nodes = topology.nodes()
|
||||
t_links = topology.links()
|
||||
|
||||
vertices, path, boundary = _calculate_boundary(topology.max_x_node(), t_nodes, t_links)
|
||||
|
||||
if not accurate:
|
||||
return boundary
|
||||
|
||||
api = 'calculate_boundary'
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
# use linestring instead of polygon to reduce strict limitation
|
||||
# TODO: linestring can not work well
|
||||
write(name, f"insert into temp_region (id, boundary) values ('{api}', '{to_postgis_polygon(boundary)}')")
|
||||
|
||||
write(name, f'delete from temp_node')
|
||||
for node in nodes:
|
||||
write(name, f"insert into temp_node values ('{node}')")
|
||||
|
||||
for row in read_all(name, f"select n.node from coordinates as c, temp_node as n, temp_region as r where c.node = n.node and ST_Intersects(c.coord, r.boundary) and r.id = '{api}'"):
|
||||
node = row['node']
|
||||
write(name, f"delete from temp_node where node = '{node}'")
|
||||
|
||||
outside_nodes: list[str] = []
|
||||
for row in read_all(name, "select node from temp_node"):
|
||||
outside_nodes.append(row['node'])
|
||||
|
||||
# no outside nodes, return
|
||||
if len(outside_nodes) == 0:
|
||||
write(name, f'delete from temp_node')
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
return boundary
|
||||
|
||||
new_nodes: dict[str, Any] = {}
|
||||
new_links: dict[str, Any] = {}
|
||||
|
||||
boundary_links: dict[str, list[str]] = {}
|
||||
write(name, "delete from temp_link_2")
|
||||
for node in outside_nodes:
|
||||
for link in t_nodes[node]['links']:
|
||||
node1 = t_links[link]['node1']
|
||||
node2 = t_links[link]['node2']
|
||||
if node1 in outside_nodes and node2 not in outside_nodes and node2 not in vertices and link:
|
||||
if link not in boundary:
|
||||
boundary_links[link] = []
|
||||
line = f"LINESTRING({t_nodes[node1]['x']} {t_nodes[node1]['y']}, {t_nodes[node2]['x']} {t_nodes[node2]['y']})"
|
||||
write(name, f"insert into temp_link_2 values ('{link}', '{line}')")
|
||||
if node2 in outside_nodes and node1 not in outside_nodes and node1 not in vertices:
|
||||
if link not in boundary:
|
||||
boundary_links[link] = []
|
||||
line = f"LINESTRING({t_nodes[node1]['x']} {t_nodes[node1]['y']}, {t_nodes[node2]['x']} {t_nodes[node2]['y']})"
|
||||
write(name, f"insert into temp_link_2 values ('{link}', '{line}')")
|
||||
if node1 in outside_nodes and node2 in outside_nodes:
|
||||
x1, x2 = t_nodes[node1]['x'], t_nodes[node2]['x']
|
||||
y1, y2 = t_nodes[node1]['y'], t_nodes[node2]['y']
|
||||
if node1 not in new_nodes:
|
||||
new_nodes[node1] = { 'x': x1, 'y': y1, 'links': [] }
|
||||
if node2 not in new_nodes:
|
||||
new_nodes[node2] = { 'x': x2, 'y': y2, 'links': [] }
|
||||
if link not in new_links:
|
||||
new_links[link] = t_links[link]
|
||||
|
||||
# no boundary links, return
|
||||
if len(boundary_links) == 0:
|
||||
write(name, "delete from temp_link_2")
|
||||
write(name, f'delete from temp_node')
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
return boundary
|
||||
|
||||
write(name, "delete from temp_link_1")
|
||||
for link, _ in path.items():
|
||||
node1 = t_links[link]['node1']
|
||||
node2 = t_links[link]['node2']
|
||||
line = f"LINESTRING({t_nodes[node1]['x']} {t_nodes[node1]['y']}, {t_nodes[node2]['x']} {t_nodes[node2]['y']})"
|
||||
write(name, f"insert into temp_link_1 (link, geom) values ('{link}', '{line}')")
|
||||
|
||||
has_intersection = False
|
||||
for row in read_all(name, f"select l1.link as l, l2.link as r, st_astext(st_intersection(l1.geom, l2.geom)) as p from temp_link_1 as l1, temp_link_2 as l2 where st_intersects(l1.geom, l2.geom)"):
|
||||
has_intersection = True
|
||||
|
||||
link1, link2, pt = str(row['l']), str(row['r']), str(row['p'])
|
||||
pts = pt.lower().removeprefix('point(').removesuffix(')').split(' ')
|
||||
xy = (float(pts[0]), float(pts[1]))
|
||||
|
||||
new_node = f'NODE_[{link1}]_[{link2}]'
|
||||
new_nodes[new_node] = { 'x': xy[0], 'y': xy[1], 'links': [] }
|
||||
|
||||
path[link1].append(new_node)
|
||||
boundary_links[link2].append(new_node)
|
||||
|
||||
# no intersection, return
|
||||
if not has_intersection:
|
||||
write(name, "delete from temp_link_1")
|
||||
write(name, "delete from temp_link_2")
|
||||
write(name, 'delete from temp_node')
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
return boundary
|
||||
|
||||
new_nodes, new_links = _collect_new_links(path, t_nodes, t_links, new_nodes, new_links)
|
||||
new_nodes, new_links = _collect_new_links(boundary_links, t_nodes, t_links, new_nodes, new_links)
|
||||
|
||||
for link, values in new_links.items():
|
||||
new_nodes[values['node1']]['links'].append(link)
|
||||
new_nodes[values['node2']]['links'].append(link)
|
||||
|
||||
_, _, boundary = _calculate_boundary(topology.max_x_node(), new_nodes, new_links)
|
||||
|
||||
write(name, "delete from temp_link_1")
|
||||
write(name, "delete from temp_link_2")
|
||||
write(name, 'delete from temp_node')
|
||||
write(name, f"delete from temp_region where id = '{api}'")
|
||||
|
||||
return boundary
|
||||
|
||||
|
||||
'''
|
||||
# CClipper2.dll
|
||||
# int inflate_paths(double* path, size_t size, double delta, int jt, int et, double miter_limit, int precision, double arc_tolerance, double** out_path, size_t* out_size);
|
||||
# int simplify_paths(double* path, size_t size, double epsilon, int is_closed_path, double** out_path, size_t* out_size);
|
||||
# void free_paths(double** paths);
|
||||
'''
|
||||
def inflate_boundary(name: str, boundary: list[tuple[float, float]], delta: float = 0.5) -> list[tuple[float, float]]:
|
||||
if boundary[0] == boundary[-1]:
|
||||
del(boundary[-1])
|
||||
|
||||
lib = ctypes.CDLL(os.path.join(os.getcwd(), 'api', 'CClipper2.dll'))
|
||||
|
||||
c_size = ctypes.c_size_t(len(boundary) * 2)
|
||||
c_path = (ctypes.c_double * c_size.value)()
|
||||
i = 0
|
||||
for xy in boundary:
|
||||
c_path[i] = xy[0]
|
||||
i += 1
|
||||
c_path[i] = xy[1]
|
||||
i += 1
|
||||
c_delta = ctypes.c_double(delta)
|
||||
JoinType_Square, JoinType_Round, JoinType_Miter = 0, 1, 2
|
||||
c_jt = ctypes.c_int(JoinType_Square)
|
||||
EndType_Polygon, EndType_Joined, EndType_Butt, EndType_Square, EndType_Round = 0, 1, 2, 3, 4
|
||||
c_et = ctypes.c_int(EndType_Polygon)
|
||||
c_miter_limit = ctypes.c_double(2.0)
|
||||
c_precision = ctypes.c_int(2)
|
||||
c_arc_tolerance = ctypes.c_double(0.0)
|
||||
c_out_path = ctypes.POINTER(ctypes.c_double)()
|
||||
c_out_size = ctypes.c_size_t(0)
|
||||
|
||||
lib.inflate_paths(c_path, c_size, c_delta, c_jt, c_et, c_miter_limit, c_precision, c_arc_tolerance, ctypes.byref(c_out_path), ctypes.byref(c_out_size))
|
||||
if c_out_size.value == 0:
|
||||
lib.free_paths(ctypes.byref(c_out_path))
|
||||
return []
|
||||
|
||||
# TODO: simplify_paths :)
|
||||
|
||||
result: list[tuple[float, float]] = []
|
||||
for i in range(0, c_out_size.value, 2):
|
||||
result.append((c_out_path[i], c_out_path[i + 1]))
|
||||
result.append(result[0])
|
||||
|
||||
lib.free_paths(ctypes.byref(c_out_path))
|
||||
return result
|
||||
|
||||
|
||||
def inflate_region(name: str, region_id: str, delta: float = 0.5) -> list[tuple[float, float]]:
|
||||
r = try_read(name, f"select id, st_astext(boundary) as boundary_geom from region where id = '{region_id}'")
|
||||
if r == None:
|
||||
return []
|
||||
boundary = from_postgis_polygon(str(r['boundary_geom']))
|
||||
return inflate_boundary(name, boundary, delta)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_verify_platform()
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
from .database import *
|
||||
from .s0_base import is_node
|
||||
from .s32_region_util import to_postgis_polygon
|
||||
from .s32_region import get_region
|
||||
|
||||
def get_district_metering_area_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False },
|
||||
'parent' : {'type': 'str' , 'optional': True , 'readonly': False },
|
||||
'level' : {'type': 'int' , 'optional': False , 'readonly': True } }
|
||||
|
||||
|
||||
def get_district_metering_area(name: str, id: str) -> dict[str, Any]:
|
||||
dma = get_region(name, id)
|
||||
if dma == {}:
|
||||
return {}
|
||||
r = try_read(name, f"select * from region_dma where id = '{id}'")
|
||||
if r == None:
|
||||
return {}
|
||||
dma['parent'] = r['parent']
|
||||
dma['nodes'] = list(eval(r['nodes']))
|
||||
dma['level'] = 1
|
||||
|
||||
if dma['parent'] != None:
|
||||
parent = dma['parent']
|
||||
while parent != None:
|
||||
parent = read(name, f"select parent from region_dma where id = '{parent}'")['parent']
|
||||
dma['level'] += 1
|
||||
|
||||
return dma
|
||||
|
||||
|
||||
def _set_district_metering_area(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
|
||||
new_boundary = cs.operations[0]['boundary']
|
||||
old_boundary = get_region(name, id)['boundary']
|
||||
|
||||
new_parent = cs.operations[0]['parent']
|
||||
f_new_parent = f"'{new_parent}'" if new_parent != None else 'null'
|
||||
|
||||
new_nodes = cs.operations[0]['nodes']
|
||||
str_new_nodes = str(new_nodes).replace("'", "''")
|
||||
|
||||
old = get_district_metering_area(name, id)
|
||||
old_parent = old['parent']
|
||||
f_old_parent = f"'{old_parent}'" if old_parent != None else 'null'
|
||||
|
||||
old_nodes = old['nodes']
|
||||
str_old_nodes = str(old_nodes).replace("'", "''")
|
||||
|
||||
redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new_boundary)}') where id = '{id}';"
|
||||
redo_sql += f"update region_dma set parent = {f_new_parent}, nodes = '{str_new_nodes}' where id = '{id}';"
|
||||
|
||||
undo_sql = f"update region_dma set parent = {f_old_parent}, nodes = '{str_old_nodes}' where id = '{id}';"
|
||||
undo_sql += f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old_boundary)}') where id = '{id}';"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'district_metering_area', 'id': id, 'boundary': new_boundary, 'parent': new_parent, 'nodes': new_nodes }
|
||||
undo_cs = g_update_prefix | { 'type': 'district_metering_area', 'id': id, 'boundary': old_boundary, 'parent': old_parent, 'nodes': old_nodes }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_district_metering_area(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
ops = cs.operations
|
||||
|
||||
if len(cs.operations) == 0:
|
||||
return ChangeSet()
|
||||
|
||||
op = ops[0]
|
||||
|
||||
if 'id' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
dma = get_district_metering_area(name, op['id'])
|
||||
if dma == {}:
|
||||
return ChangeSet()
|
||||
|
||||
if 'boundary' not in op:
|
||||
op['boundary'] = dma['boundary']
|
||||
else:
|
||||
b = op['boundary']
|
||||
if len(b) < 4 or b[0] != b[-1]:
|
||||
return ChangeSet()
|
||||
|
||||
if 'parent' not in op:
|
||||
op['parent'] = dma['parent']
|
||||
|
||||
if op['parent'] != None and get_district_metering_area(name, op['parent']) == {}:
|
||||
return ChangeSet()
|
||||
|
||||
if 'nodes' not in op:
|
||||
op['nodes'] = dma['nodes']
|
||||
else:
|
||||
for node in op['nodes']:
|
||||
if not is_node(name, node):
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _set_district_metering_area(name, cs))
|
||||
|
||||
|
||||
def _add_district_metering_area(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
|
||||
boundary = cs.operations[0]['boundary']
|
||||
|
||||
parent = cs.operations[0]['parent']
|
||||
f_parent = f"'{parent}'" if parent != None else 'null'
|
||||
|
||||
nodes = cs.operations[0]['nodes']
|
||||
str_nodes = str(nodes).replace("'", "''")
|
||||
|
||||
redo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'DMA');"
|
||||
redo_sql += f"insert into region_dma (id, parent, nodes) values ('{id}', {f_parent}, '{str_nodes}');"
|
||||
|
||||
undo_sql = f"delete from region_dma where id = '{id}';"
|
||||
undo_sql += f"delete from region where id = '{id}';"
|
||||
|
||||
redo_cs = g_add_prefix | { 'type': 'district_metering_area', 'id': id, 'boundary': boundary, 'parent': parent, 'nodes': nodes }
|
||||
undo_cs = g_delete_prefix | { 'type': 'district_metering_area', 'id': id }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_district_metering_area(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
ops = cs.operations
|
||||
|
||||
if len(cs.operations) == 0:
|
||||
return ChangeSet()
|
||||
|
||||
op = ops[0]
|
||||
|
||||
if 'id' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
dma = get_district_metering_area(name, op['id'])
|
||||
if dma != {}:
|
||||
return ChangeSet()
|
||||
|
||||
if 'boundary' not in op:
|
||||
return ChangeSet()
|
||||
else:
|
||||
b = op['boundary']
|
||||
if len(b) < 4 or b[0] != b[-1]:
|
||||
return ChangeSet()
|
||||
|
||||
if 'parent' not in op:
|
||||
op['parent'] = None
|
||||
|
||||
if op['parent'] != None and get_district_metering_area(name, op['parent']) == {}:
|
||||
return ChangeSet()
|
||||
|
||||
if 'nodes' not in op:
|
||||
op['nodes'] = []
|
||||
else:
|
||||
for node in op['nodes']:
|
||||
if not is_node(name, node):
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _add_district_metering_area(name, cs))
|
||||
|
||||
|
||||
def _delete_district_metering_area(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
dma = get_district_metering_area(name, id)
|
||||
boundary = dma['boundary']
|
||||
parent = dma['parent']
|
||||
f_parent = f"'{parent}'" if parent != None else 'null'
|
||||
nodes = dma['nodes']
|
||||
str_nodes = str(nodes).replace("'", "''")
|
||||
|
||||
redo_sql = f"delete from region_dma where id = '{id}';"
|
||||
redo_sql += f"delete from region where id = '{id}';"
|
||||
|
||||
undo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'DMA');"
|
||||
undo_sql += f"insert into region_dma (id, parent, nodes) values ('{id}', {f_parent}, '{str_nodes}');"
|
||||
|
||||
redo_cs = g_delete_prefix | { 'type': 'district_metering_area', 'id': id }
|
||||
undo_cs = g_add_prefix | { 'type': 'district_metering_area', 'id': id, 'boundary': boundary, 'parent': parent, 'nodes': nodes }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def _has_child(name: str, parent: str) -> bool:
|
||||
return try_read(name, f"select * from region_dma where parent = '{parent}'") != None
|
||||
|
||||
|
||||
def is_descendant_of(name: str, descendant: str, ancestor: str) -> bool:
|
||||
parent = descendant
|
||||
while parent != None:
|
||||
parent = read(name, f"select parent from region_dma where id = '{parent}'")['parent']
|
||||
if parent == ancestor:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def delete_district_metering_area(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
ops = cs.operations
|
||||
|
||||
if len(cs.operations) == 0:
|
||||
return ChangeSet()
|
||||
|
||||
op = ops[0]
|
||||
|
||||
if 'id' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
dma = get_district_metering_area(name, op['id'])
|
||||
if dma == {}:
|
||||
return ChangeSet()
|
||||
|
||||
#TODO: cascade ?
|
||||
if _has_child(name, dma['id']):
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _delete_district_metering_area(name, cs))
|
||||
|
||||
|
||||
def get_all_district_metering_area_ids(name: str) -> list[str]:
|
||||
ids = []
|
||||
for row in read_all(name, f"select id from region_dma"):
|
||||
ids.append(row['id'])
|
||||
return ids
|
||||
|
||||
|
||||
def get_all_district_metering_areas(name: str) -> list[dict[str, Any]]:
|
||||
result = []
|
||||
for id in get_all_district_metering_area_ids(name):
|
||||
result.append(get_district_metering_area(name, id))
|
||||
return result
|
||||
@@ -0,0 +1,152 @@
|
||||
import ctypes
|
||||
import os
|
||||
import numpy as np
|
||||
import pymetis
|
||||
from .database import *
|
||||
from .s0_base import get_nodes
|
||||
from .s32_region_util import get_nodes_in_region
|
||||
from .s32_region_util import Topology
|
||||
|
||||
|
||||
PARTITION_TYPE_RB = 0
|
||||
PARTITION_TYPE_KWAY = 1
|
||||
|
||||
'''
|
||||
adjacency_list = [np.array([4, 2, 1]),
|
||||
np.array([0, 2, 3]),
|
||||
np.array([4, 3, 1, 0]),
|
||||
np.array([1, 2, 5, 6]),
|
||||
np.array([0, 2, 5]),
|
||||
np.array([4, 3, 6]),
|
||||
np.array([5, 3])]
|
||||
n_cuts, membership = pymetis.part_graph(2, adjacency=adjacency_list)
|
||||
# n_cuts = 3
|
||||
# membership = [1, 1, 1, 0, 1, 0, 0]
|
||||
|
||||
nodes_part_0 = np.argwhere(np.array(membership) == 0).ravel() # [3, 5, 6]
|
||||
nodes_part_1 = np.argwhere(np.array(membership) == 1).ravel() # [0, 1, 2, 4]
|
||||
|
||||
print(nodes_part_0)
|
||||
print(nodes_part_1)
|
||||
'''
|
||||
|
||||
|
||||
def calculate_district_metering_area_for_nodes(name: str, nodes: list[str], part_count: int = 1, part_type: int = PARTITION_TYPE_RB) -> list[list[str]]:
|
||||
topology = Topology(name, nodes)
|
||||
t_nodes = topology.nodes()
|
||||
t_links = topology.links()
|
||||
t_node_list = topology.node_list()
|
||||
|
||||
adjacency_list = []
|
||||
|
||||
for node in t_node_list:
|
||||
links: list[str] = t_nodes[node]['links']
|
||||
a_nodes: list[int] = []
|
||||
for link in links:
|
||||
if t_links[link]['node1'] == node:
|
||||
i = t_node_list.index(t_links[link]['node2'])
|
||||
a_nodes.append(i)
|
||||
elif t_links[link]['node2'] == node:
|
||||
i = t_node_list.index(t_links[link]['node1'])
|
||||
a_nodes.append(i)
|
||||
adjacency_list.append(np.array(a_nodes))
|
||||
|
||||
recursive = part_type == PARTITION_TYPE_RB
|
||||
n_cuts, membership = pymetis.part_graph(nparts=part_count, adjacency=adjacency_list, recursive=recursive, contiguous=True)
|
||||
|
||||
result: list[list[str]] = []
|
||||
for i in range(0, part_count):
|
||||
indices: list[int] = list(np.argwhere(np.array(membership) == i).ravel())
|
||||
index_strs: list[str] = []
|
||||
for index in indices:
|
||||
index_strs.append(t_node_list[index])
|
||||
result.append(index_strs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _calculate_district_metering_area_for_nodes(name: str, nodes: list[str], part_count: int = 1, part_type: int = PARTITION_TYPE_RB) -> list[list[str]]:
|
||||
if part_type != PARTITION_TYPE_RB and part_type != PARTITION_TYPE_KWAY:
|
||||
return []
|
||||
if part_count <= 0:
|
||||
return []
|
||||
elif part_count == 1:
|
||||
return [nodes]
|
||||
|
||||
lib = ctypes.CDLL(os.path.join(os.getcwd(), 'api', 'CMetis.dll'))
|
||||
|
||||
METIS_NOPTIONS = 40
|
||||
c_options = (ctypes.c_int64 * METIS_NOPTIONS)()
|
||||
|
||||
METIS_OK = 1
|
||||
result = lib.set_default_options(c_options)
|
||||
if result != METIS_OK:
|
||||
return []
|
||||
|
||||
METIS_OPTION_PTYPE , METIS_OPTION_CONTIG = 0, 13
|
||||
c_options[METIS_OPTION_PTYPE] = part_type
|
||||
c_options[METIS_OPTION_CONTIG] = 1
|
||||
|
||||
topology = Topology(name, nodes)
|
||||
t_nodes = topology.nodes()
|
||||
t_links = topology.links()
|
||||
t_node_list = topology.node_list()
|
||||
t_link_list = topology.link_list()
|
||||
|
||||
nedges = len(t_link_list) * 2
|
||||
|
||||
c_nvtxs = ctypes.c_int64(len(t_node_list))
|
||||
c_ncon = ctypes.c_int64(1)
|
||||
c_xadj = (ctypes.c_int64 * (c_nvtxs.value + 1))()
|
||||
c_adjncy = (ctypes.c_int64 * nedges)()
|
||||
c_vwgt = (ctypes.c_int64 * (c_ncon.value * c_nvtxs.value))()
|
||||
c_adjwgt = (ctypes.c_int64 * nedges)()
|
||||
c_vsize = (ctypes.c_int64 * c_nvtxs.value)()
|
||||
|
||||
c_xadj[0] = 0
|
||||
|
||||
l, n = 0, 0
|
||||
c_xadj_i = 1
|
||||
for node in t_node_list:
|
||||
links = t_nodes[node]['links']
|
||||
for link in links:
|
||||
node1 = t_links[link]['node1']
|
||||
node2 = t_links[link]['node2']
|
||||
c_adjncy[l] = t_node_list.index(node2) if node2 != node else t_node_list.index(node1)
|
||||
c_adjwgt[l] = 1
|
||||
l += 1
|
||||
if len(links) > 0:
|
||||
c_xadj[c_xadj_i] = l # adjncy.size()
|
||||
c_xadj_i += 1
|
||||
c_vwgt[n] = 1
|
||||
c_vsize[n] = 1
|
||||
n += 1
|
||||
|
||||
part_func = lib.part_graph_recursive if part_type == PARTITION_TYPE_RB else lib.part_graph_kway
|
||||
|
||||
c_nparts = ctypes.c_int64(part_count)
|
||||
c_tpwgts = ctypes.POINTER(ctypes.c_double)()
|
||||
c_ubvec = ctypes.POINTER(ctypes.c_double)()
|
||||
c_out_edgecut = ctypes.c_int64(0)
|
||||
c_out_part = (ctypes.c_int64 * c_nvtxs.value)()
|
||||
result = part_func(ctypes.byref(c_nvtxs), ctypes.byref(c_ncon), c_xadj, c_adjncy, c_vwgt, c_vsize, c_adjwgt, ctypes.byref(c_nparts), c_tpwgts, c_ubvec, c_options, ctypes.byref(c_out_edgecut), c_out_part)
|
||||
if result != METIS_OK:
|
||||
return []
|
||||
|
||||
dmas : list[list[str]]= []
|
||||
for i in range(part_count):
|
||||
dmas.append([])
|
||||
for i in range(c_nvtxs.value):
|
||||
dmas[c_out_part[i]].append(t_node_list[i])
|
||||
|
||||
return dmas
|
||||
|
||||
|
||||
def calculate_district_metering_area_for_region(name: str, region: str, part_count: int = 1, part_type: int = PARTITION_TYPE_RB) -> list[list[str]]:
|
||||
nodes = get_nodes_in_region(name, region)
|
||||
return calculate_district_metering_area_for_nodes(name, nodes, part_count, part_type)
|
||||
|
||||
|
||||
def calculate_district_metering_area_for_network(name: str, part_count: int = 1, part_type: int = PARTITION_TYPE_RB) -> list[list[str]]:
|
||||
nodes = get_nodes(name)
|
||||
return calculate_district_metering_area_for_nodes(name, nodes, part_count, part_type)
|
||||
@@ -0,0 +1,47 @@
|
||||
from .s32_region_util import calculate_boundary, inflate_boundary
|
||||
from .s33_dma_cal import *
|
||||
from .s33_dma import get_all_district_metering_area_ids, get_all_district_metering_areas, get_district_metering_area, is_descendant_of
|
||||
from .batch_exe import execute_batch_command
|
||||
|
||||
|
||||
def generate_district_metering_area(name: str, part_count: int = 1, part_type: int = PARTITION_TYPE_RB, inflate_delta: float = 0.5) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
dmas = get_all_district_metering_areas(name)
|
||||
max_level = 0
|
||||
for dma in dmas:
|
||||
if dma['level'] > max_level:
|
||||
max_level = dma['level']
|
||||
while max_level > 0:
|
||||
for dma in dmas:
|
||||
if dma['level'] == max_level:
|
||||
cs.delete({ 'type': 'district_metering_area', 'id': dma['id'] })
|
||||
max_level -= 1
|
||||
|
||||
i = 1
|
||||
for nodes in calculate_district_metering_area_for_network(name, part_count, part_type):
|
||||
boundary = calculate_boundary(name, nodes)
|
||||
boundary = inflate_boundary(name, boundary, inflate_delta)
|
||||
cs.add({ 'type': 'district_metering_area', 'id': f"DMA_1_{i}", 'boundary': boundary, 'parent': None, 'nodes': nodes })
|
||||
i += 1
|
||||
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def generate_sub_district_metering_area(name: str, dma: str, part_count: int = 1, part_type: int = PARTITION_TYPE_RB, inflate_delta: float = 0.5) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
for id in get_all_district_metering_area_ids(name):
|
||||
if is_descendant_of(name, id, dma):
|
||||
cs.delete({ 'type': 'district_metering_area', 'id': id })
|
||||
|
||||
level = get_district_metering_area(name, dma)['level'] + 1
|
||||
|
||||
i = 1
|
||||
for nodes in calculate_district_metering_area_for_region(name, dma, part_count, part_type):
|
||||
boundary = calculate_boundary(name, nodes)
|
||||
boundary = inflate_boundary(name, boundary, inflate_delta)
|
||||
cs.add({ 'type': 'district_metering_area', 'id': f"DMA_[{dma}]_{level}_{i}", 'boundary': boundary, 'parent': dma, 'nodes': nodes })
|
||||
i += 1
|
||||
|
||||
return execute_batch_command(name, cs)
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
from .database import *
|
||||
from .s0_base import is_node
|
||||
from .s32_region_util import to_postgis_polygon
|
||||
from .s32_region import get_region
|
||||
|
||||
def get_service_area_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False },
|
||||
'source' : {'type': 'str' , 'optional': False , 'readonly': False },
|
||||
'time_index' : {'type': 'int' , 'optional': False , 'readonly': False } }
|
||||
|
||||
def get_service_area(name: str, id: str) -> dict[str, Any]:
|
||||
sa = get_region(name, id)
|
||||
if sa == {}:
|
||||
return {}
|
||||
r = try_read(name, f"select * from region_sa where id = '{id}'")
|
||||
if r == None:
|
||||
return {}
|
||||
sa['source'] = r['source']
|
||||
sa['nodes'] = list(eval(r['nodes']))
|
||||
sa['time_index'] = r['time_index']
|
||||
return sa
|
||||
|
||||
def _set_service_area(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
|
||||
new_boundary = cs.operations[0]['boundary']
|
||||
old_boundary = get_region(name, id)['boundary']
|
||||
|
||||
new_source = cs.operations[0]['source']
|
||||
f_new_source = f"'{new_source}'"
|
||||
|
||||
new_nodes = cs.operations[0]['nodes']
|
||||
str_new_nodes = str(new_nodes).replace("'", "''")
|
||||
|
||||
new_time_index = cs.operations[0]['time_index']
|
||||
|
||||
old = get_service_area(name, id)
|
||||
old_source = old['source']
|
||||
f_old_source = f"'{old_source}'"
|
||||
|
||||
old_nodes = old['nodes']
|
||||
str_old_nodes = str(old_nodes).replace("'", "''")
|
||||
|
||||
old_time_index = old['time_index']
|
||||
|
||||
redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new_boundary)}') where id = '{id}';"
|
||||
redo_sql += f"update region_sa set time_index = {new_time_index}, source = {f_new_source}, nodes = '{str_new_nodes}' where id = '{id}';"
|
||||
|
||||
undo_sql = f"update region_sa set time_index = {old_time_index}, source = {f_old_source}, nodes = '{str_old_nodes}' where id = '{id}';"
|
||||
undo_sql += f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old_boundary)}') where id = '{id}';"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'service_area', 'id': id, 'boundary': new_boundary, 'time_index': new_time_index, 'source': new_source, 'nodes': new_nodes }
|
||||
undo_cs = g_update_prefix | { 'type': 'service_area', 'id': id, 'boundary': old_boundary, 'time_index': old_time_index, 'source': old_source, 'nodes': old_nodes }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_service_area(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
ops = cs.operations
|
||||
|
||||
if len(cs.operations) == 0:
|
||||
return ChangeSet()
|
||||
|
||||
op = ops[0]
|
||||
|
||||
if 'id' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
sa = get_service_area(name, op['id'])
|
||||
if sa == {}:
|
||||
return ChangeSet()
|
||||
|
||||
if 'boundary' not in op:
|
||||
op['boundary'] = sa['boundary']
|
||||
else:
|
||||
b = op['boundary']
|
||||
if len(b) < 4 or b[0] != b[-1]:
|
||||
return ChangeSet()
|
||||
|
||||
if 'time_index' not in op:
|
||||
op['time_index'] = sa['time_index']
|
||||
|
||||
if 'source' not in op:
|
||||
op['source'] = sa['source']
|
||||
|
||||
if not is_node(name, op['source']):
|
||||
return ChangeSet()
|
||||
|
||||
if 'nodes' not in op:
|
||||
op['nodes'] = sa['nodes']
|
||||
else:
|
||||
for node in op['nodes']:
|
||||
if not is_node(name, node):
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _set_service_area(name, cs))
|
||||
|
||||
|
||||
def _add_service_area(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
|
||||
boundary = cs.operations[0]['boundary']
|
||||
|
||||
time_index = cs.operations[0]['time_index']
|
||||
|
||||
source = cs.operations[0]['source']
|
||||
f_source = f"'{source}'"
|
||||
|
||||
nodes = cs.operations[0]['nodes']
|
||||
str_nodes = str(nodes).replace("'", "''")
|
||||
|
||||
redo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'SA');"
|
||||
redo_sql += f"insert into region_sa (id, time_index, source, nodes) values ('{id}', {time_index}, {f_source}, '{str_nodes}');"
|
||||
|
||||
undo_sql = f"delete from region_sa where id = '{id}';"
|
||||
undo_sql += f"delete from region where id = '{id}';"
|
||||
|
||||
redo_cs = g_add_prefix | { 'type': 'service_area', 'id': id, 'boundary': boundary, 'time_index': time_index, 'source': source, 'nodes': nodes }
|
||||
undo_cs = g_delete_prefix | { 'type': 'service_area', 'id': id }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_service_area(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
ops = cs.operations
|
||||
|
||||
if len(cs.operations) == 0:
|
||||
return ChangeSet()
|
||||
|
||||
op = ops[0]
|
||||
|
||||
if 'id' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
sa = get_service_area(name, op['id'])
|
||||
if sa != {}:
|
||||
return ChangeSet()
|
||||
|
||||
if 'boundary' not in op:
|
||||
return ChangeSet()
|
||||
else:
|
||||
b = op['boundary']
|
||||
if len(b) < 4 or b[0] != b[-1]:
|
||||
return ChangeSet()
|
||||
|
||||
if 'time_index' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
if 'source' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
if not is_node(name, op['source']):
|
||||
return ChangeSet()
|
||||
|
||||
if 'nodes' not in op:
|
||||
op['nodes'] = []
|
||||
else:
|
||||
for node in op['nodes']:
|
||||
if not is_node(name, node):
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _add_service_area(name, cs))
|
||||
|
||||
|
||||
def _delete_service_area(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
sa = get_service_area(name, id)
|
||||
boundary = sa['boundary']
|
||||
time_index = sa['time_index']
|
||||
source = sa['source']
|
||||
f_source = f"'{source}'"
|
||||
nodes = sa['nodes']
|
||||
str_nodes = str(nodes).replace("'", "''")
|
||||
|
||||
redo_sql = f"delete from region_sa where id = '{id}';"
|
||||
redo_sql += f"delete from region where id = '{id}';"
|
||||
|
||||
undo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'SA');"
|
||||
undo_sql += f"insert into region_sa (id, time_index, source, nodes) values ('{id}', {time_index}, {f_source}, '{str_nodes}');"
|
||||
|
||||
redo_cs = g_delete_prefix | { 'type': 'service_area', 'id': id }
|
||||
undo_cs = g_add_prefix | { 'type': 'service_area', 'id': id, 'boundary': boundary, 'time_index': time_index, 'source': source, 'nodes': nodes }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_service_area(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
ops = cs.operations
|
||||
|
||||
if len(cs.operations) == 0:
|
||||
return ChangeSet()
|
||||
|
||||
op = ops[0]
|
||||
|
||||
if 'id' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
sa = get_service_area(name, op['id'])
|
||||
if sa == {}:
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _delete_service_area(name, cs))
|
||||
|
||||
|
||||
def get_all_service_area_ids(name: str) -> list[str]:
|
||||
ids = []
|
||||
for row in read_all(name, f"select id from region_sa"):
|
||||
ids.append(row['id'])
|
||||
return ids
|
||||
|
||||
|
||||
def get_all_service_areas(name: str) -> list[dict[str, Any]]:
|
||||
result = []
|
||||
for id in get_all_service_area_ids(name):
|
||||
result.append(get_service_area(name, id))
|
||||
return result
|
||||
@@ -0,0 +1,198 @@
|
||||
import os
|
||||
import ctypes
|
||||
from .project_backup import have_project
|
||||
from .inp_out import dump_inp
|
||||
|
||||
def calculate_service_area(name: str) -> list[dict[str, list[str]]]:
|
||||
if not have_project(name):
|
||||
raise Exception(f'Not found project [{name}]')
|
||||
|
||||
dir = os.path.abspath(os.getcwd())
|
||||
|
||||
inp_str = os.path.join(os.path.join(dir, 'db_inp'), name + '.db.inp')
|
||||
dump_inp(name, inp_str, '2')
|
||||
|
||||
toolkit = ctypes.CDLL(os.path.join(os.path.join(dir, 'api'), 'toolkit.dll'))
|
||||
|
||||
inp = ctypes.c_char_p(inp_str.encode())
|
||||
|
||||
handle = ctypes.c_ulonglong()
|
||||
toolkit.TK_ServiceArea_Start(inp, ctypes.byref(handle))
|
||||
|
||||
c_nodeCount = ctypes.c_size_t()
|
||||
toolkit.TK_ServiceArea_GetNodeCount(handle, ctypes.byref(c_nodeCount))
|
||||
nodeCount = c_nodeCount.value
|
||||
|
||||
nodeIds: list[str] = []
|
||||
|
||||
for n in range(0, nodeCount):
|
||||
id = ctypes.c_char_p()
|
||||
toolkit.TK_ServiceArea_GetNodeId(handle, ctypes.c_size_t(n), ctypes.byref(id))
|
||||
nodeIds.append(id.value.decode())
|
||||
|
||||
c_timeCount = ctypes.c_size_t()
|
||||
toolkit.TK_ServiceArea_GetTimeCount(handle, ctypes.byref(c_timeCount))
|
||||
timeCount = c_timeCount.value
|
||||
|
||||
results: list[dict[str, list[str]]] = []
|
||||
|
||||
for t in range(0, timeCount):
|
||||
c_sourceCount = ctypes.c_size_t()
|
||||
toolkit.TK_ServiceArea_GetSourceCount(handle, ctypes.c_size_t(t), ctypes.byref(c_sourceCount))
|
||||
sourceCount = c_sourceCount.value
|
||||
|
||||
sources = ctypes.POINTER(ctypes.c_size_t)()
|
||||
toolkit.TK_ServiceArea_GetSources(handle, ctypes.c_size_t(t), ctypes.byref(sources))
|
||||
|
||||
result: dict[str, list[str]] = {}
|
||||
for s in range(0, sourceCount):
|
||||
result[nodeIds[sources[s]]] = []
|
||||
|
||||
for n in range(0, nodeCount):
|
||||
concentration = ctypes.POINTER(ctypes.c_double)()
|
||||
toolkit.TK_ServiceArea_GetConcentration(handle, ctypes.c_size_t(t), ctypes.c_size_t(n), ctypes.byref(concentration))
|
||||
|
||||
maxS = sources[0]
|
||||
maxC = concentration[0]
|
||||
for s in range(1, sourceCount):
|
||||
if concentration[s] > maxC:
|
||||
maxS = sources[s]
|
||||
maxC = concentration[s]
|
||||
|
||||
result[nodeIds[maxS]].append(nodeIds[n])
|
||||
|
||||
results.append(result)
|
||||
|
||||
toolkit.TK_ServiceArea_End(handle)
|
||||
|
||||
return results
|
||||
|
||||
'''
|
||||
import sys
|
||||
import json
|
||||
from queue import Queue
|
||||
from .database import *
|
||||
from .s0_base import get_node_links, get_link_nodes
|
||||
|
||||
sys.path.append('..')
|
||||
from epanet.epanet import run_project
|
||||
|
||||
def _calculate_service_area(name: str, inp, time_index: int = 0) -> dict[str, list[str]]:
|
||||
sources : dict[str, list[str]] = {}
|
||||
for node_result in inp['node_results']:
|
||||
result = node_result['result'][time_index]
|
||||
if result['demand'] < 0:
|
||||
sources[node_result['node']] = []
|
||||
|
||||
link_flows: dict[str, float] = {}
|
||||
for link_result in inp['link_results']:
|
||||
result = link_result['result'][time_index]
|
||||
link_flows[link_result['link']] = float(result['flow'])
|
||||
|
||||
# build source to nodes map
|
||||
for source in sources:
|
||||
queue = Queue()
|
||||
queue.put(source)
|
||||
|
||||
while not queue.empty():
|
||||
cursor = queue.get()
|
||||
if cursor not in sources[source]:
|
||||
sources[source].append(cursor)
|
||||
|
||||
links = get_node_links(name, cursor)
|
||||
for link in links:
|
||||
node1, node2 = get_link_nodes(name, link)
|
||||
if node1 == cursor and link_flows[link] > 0:
|
||||
queue.put(node2)
|
||||
elif node2 == cursor and link_flows[link] < 0:
|
||||
queue.put(node1)
|
||||
|
||||
#return sources
|
||||
|
||||
# calculation concentration
|
||||
concentration_map: dict[str, dict[str, float]] = {}
|
||||
node_wip: list[str] = []
|
||||
for source, nodes in sources.items():
|
||||
for node in nodes:
|
||||
if node not in concentration_map:
|
||||
concentration_map[node] = {}
|
||||
concentration_map[node][source] = 0.0
|
||||
if node not in node_wip:
|
||||
node_wip.append(node)
|
||||
|
||||
# if only one source, done
|
||||
for node, concentrations in concentration_map.items():
|
||||
if len(concentrations) == 1:
|
||||
node_wip.remove(node)
|
||||
for key in concentrations.keys():
|
||||
concentration_map[node][key] = 1.0
|
||||
|
||||
node_upstream : dict[str, list[tuple[str, str]]] = {}
|
||||
for node in node_wip:
|
||||
if node not in node_upstream:
|
||||
node_upstream[node] = []
|
||||
|
||||
links = get_node_links(name, node)
|
||||
for link in links:
|
||||
node1, node2 = get_link_nodes(name, link)
|
||||
if node2 == node and link_flows[link] > 0:
|
||||
node_upstream[node].append((link, node1))
|
||||
elif node1 == node and link_flows[link] < 0:
|
||||
node_upstream[node].append((link, node2))
|
||||
|
||||
while len(node_wip) != 0:
|
||||
done = []
|
||||
for node in node_wip:
|
||||
up_link_nodes = node_upstream[node]
|
||||
ready = True
|
||||
for link_node in up_link_nodes:
|
||||
if link_node[1] in node_wip:
|
||||
ready = False
|
||||
break
|
||||
if ready:
|
||||
for link_node in up_link_nodes:
|
||||
if link_node[1] not in concentration_map.keys():
|
||||
continue
|
||||
for source, concentration in concentration_map[link_node[1]].items():
|
||||
concentration_map[node][source] += concentration * abs(link_flows[link_node[0]])
|
||||
|
||||
# normalize
|
||||
sum = 0.0
|
||||
for source, concentration in concentration_map[node].items():
|
||||
sum += concentration
|
||||
for source in concentration_map[node].keys():
|
||||
concentration_map[node][source] /= sum
|
||||
|
||||
done.append(node)
|
||||
|
||||
for node in done:
|
||||
node_wip.remove(node)
|
||||
|
||||
source_to_main_node: dict[str, list[str]] = {}
|
||||
for node, value in concentration_map.items():
|
||||
max_source = ''
|
||||
max_concentration = 0.0
|
||||
for s, c in value.items():
|
||||
if c > max_concentration:
|
||||
max_concentration = c
|
||||
max_source = s
|
||||
if max_source not in source_to_main_node:
|
||||
source_to_main_node[max_source] = []
|
||||
source_to_main_node[max_source].append(node)
|
||||
|
||||
return source_to_main_node
|
||||
|
||||
|
||||
def calculate_service_area(name: str) -> list[dict[str, list[str]]]:
|
||||
inp = json.loads(run_project(name, True))
|
||||
|
||||
result: list[dict[str, list[str]]] = []
|
||||
|
||||
time_count = len(inp['node_results'][0]['result'])
|
||||
|
||||
for i in range(time_count):
|
||||
sas = _calculate_service_area(name, inp, i)
|
||||
result.append(sas)
|
||||
|
||||
return result
|
||||
'''
|
||||
@@ -0,0 +1,23 @@
|
||||
from .s32_region_util import calculate_boundary, inflate_boundary
|
||||
from .s34_sa_cal import *
|
||||
from .s34_sa import get_all_service_area_ids
|
||||
from .batch_exe import execute_batch_command
|
||||
from .database import ChangeSet
|
||||
|
||||
def generate_service_area(name: str, inflate_delta: float = 0.5) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
for id in get_all_service_area_ids(name):
|
||||
cs.delete({'type': 'service_area', 'id': id})
|
||||
|
||||
sass = calculate_service_area(name)
|
||||
|
||||
time_index = 0
|
||||
for sas in sass:
|
||||
for source, nodes in sas.items():
|
||||
boundary = calculate_boundary(name, nodes)
|
||||
boundary = inflate_boundary(name, boundary, inflate_delta)
|
||||
cs.add({ 'type': 'service_area', 'id': f"SA_{source}_{time_index}", 'boundary': boundary, 'time_index': time_index, 'source': source, 'nodes': nodes })
|
||||
time_index += 1
|
||||
|
||||
return execute_batch_command(name, cs)
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
from .database import *
|
||||
from .s0_base import is_node
|
||||
from .s32_region_util import to_postgis_polygon
|
||||
from .s32_region import get_region
|
||||
|
||||
def get_virtual_district_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False },
|
||||
'center' : {'type': 'str' , 'optional': False , 'readonly': False } }
|
||||
|
||||
def get_virtual_district(name: str, id: str) -> dict[str, Any]:
|
||||
vd = get_region(name, id)
|
||||
if vd == {}:
|
||||
return {}
|
||||
r = try_read(name, f"select * from region_vd where id = '{id}'")
|
||||
if r == None:
|
||||
return {}
|
||||
vd['center'] = r['center']
|
||||
vd['nodes'] = list(eval(r['nodes']))
|
||||
return vd
|
||||
|
||||
def _set_virtual_district(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
|
||||
new_boundary = cs.operations[0]['boundary']
|
||||
old_boundary = get_region(name, id)['boundary']
|
||||
|
||||
new_center = cs.operations[0]['center']
|
||||
f_new_center = f"'{new_center}'"
|
||||
|
||||
new_nodes = cs.operations[0]['nodes']
|
||||
str_new_nodes = str(new_nodes).replace("'", "''")
|
||||
|
||||
old = get_virtual_district(name, id)
|
||||
old_center = old['center']
|
||||
f_old_center = f"'{old_center}'"
|
||||
|
||||
old_nodes = old['nodes']
|
||||
str_old_nodes = str(old_nodes).replace("'", "''")
|
||||
|
||||
redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new_boundary)}') where id = '{id}';"
|
||||
redo_sql += f"update region_vd set center = {f_new_center}, nodes = '{str_new_nodes}' where id = '{id}';"
|
||||
|
||||
undo_sql = f"update region_vd set center = {f_old_center}, nodes = '{str_old_nodes}' where id = '{id}';"
|
||||
undo_sql += f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old_boundary)}') where id = '{id}';"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': new_boundary, 'center': new_center, 'nodes': new_nodes }
|
||||
undo_cs = g_update_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': old_boundary, 'center': old_center, 'nodes': old_nodes }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_virtual_district(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
ops = cs.operations
|
||||
|
||||
if len(cs.operations) == 0:
|
||||
return ChangeSet()
|
||||
|
||||
op = ops[0]
|
||||
|
||||
if 'id' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
vd = get_virtual_district(name, op['id'])
|
||||
if vd == {}:
|
||||
return ChangeSet()
|
||||
|
||||
if 'boundary' not in op:
|
||||
op['boundary'] = vd['boundary']
|
||||
else:
|
||||
b = op['boundary']
|
||||
if len(b) < 4 or b[0] != b[-1]:
|
||||
return ChangeSet()
|
||||
|
||||
if 'center' not in op:
|
||||
op['center'] = vd['center']
|
||||
|
||||
if not is_node(name, op['center']):
|
||||
return ChangeSet()
|
||||
|
||||
if 'nodes' not in op:
|
||||
op['nodes'] = vd['nodes']
|
||||
else:
|
||||
for node in op['nodes']:
|
||||
if not is_node(name, node):
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _set_virtual_district(name, cs))
|
||||
|
||||
|
||||
def _add_virtual_district(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
|
||||
boundary = cs.operations[0]['boundary']
|
||||
|
||||
center = cs.operations[0]['center']
|
||||
f_center = f"'{center}'"
|
||||
|
||||
nodes = cs.operations[0]['nodes']
|
||||
str_nodes = str(nodes).replace("'", "''")
|
||||
|
||||
redo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'VD');"
|
||||
redo_sql += f"insert into region_vd (id, center, nodes) values ('{id}', {f_center}, '{str_nodes}');"
|
||||
|
||||
undo_sql = f"delete from region_vd where id = '{id}';"
|
||||
undo_sql += f"delete from region where id = '{id}';"
|
||||
|
||||
redo_cs = g_add_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': boundary, 'center': center, 'nodes': nodes }
|
||||
undo_cs = g_delete_prefix | { 'type': 'virtual_district', 'id': id }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_virtual_district(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
ops = cs.operations
|
||||
|
||||
if len(cs.operations) == 0:
|
||||
return ChangeSet()
|
||||
|
||||
op = ops[0]
|
||||
|
||||
if 'id' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
vd = get_virtual_district(name, op['id'])
|
||||
if vd != {}:
|
||||
return ChangeSet()
|
||||
|
||||
if 'boundary' not in op:
|
||||
return ChangeSet()
|
||||
else:
|
||||
b = op['boundary']
|
||||
if len(b) < 4 or b[0] != b[-1]:
|
||||
return ChangeSet()
|
||||
|
||||
if 'center' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
if not is_node(name, op['center']):
|
||||
return ChangeSet()
|
||||
|
||||
if 'nodes' not in op:
|
||||
op['nodes'] = []
|
||||
else:
|
||||
for node in op['nodes']:
|
||||
if not is_node(name, node):
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _add_virtual_district(name, cs))
|
||||
|
||||
|
||||
def _delete_virtual_district(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
id = cs.operations[0]['id']
|
||||
vd = get_virtual_district(name, id)
|
||||
boundary = vd['boundary']
|
||||
center = vd['center']
|
||||
f_center = f"'{center}'"
|
||||
nodes = vd['nodes']
|
||||
str_nodes = str(nodes).replace("'", "''")
|
||||
|
||||
redo_sql = f"delete from region_vd where id = '{id}';"
|
||||
redo_sql += f"delete from region where id = '{id}';"
|
||||
|
||||
undo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'VD');"
|
||||
undo_sql += f"insert into region_vd (id, center, nodes) values ('{id}', {f_center}, '{str_nodes}');"
|
||||
|
||||
redo_cs = g_delete_prefix | { 'type': 'virtual_district', 'id': id }
|
||||
undo_cs = g_add_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': boundary, 'center': center, 'nodes': nodes }
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_virtual_district(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
ops = cs.operations
|
||||
|
||||
if len(cs.operations) == 0:
|
||||
return ChangeSet()
|
||||
|
||||
op = ops[0]
|
||||
|
||||
if 'id' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
vd = get_virtual_district(name, op['id'])
|
||||
if vd == {}:
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _delete_virtual_district(name, cs))
|
||||
|
||||
|
||||
def get_all_virtual_district_ids(name: str) -> list[str]:
|
||||
ids = []
|
||||
for row in read_all(name, f"select id from region_vd"):
|
||||
ids.append(row['id'])
|
||||
return ids
|
||||
|
||||
|
||||
def get_all_virtual_districts(name: str) -> list[dict[str, Any]]:
|
||||
result = []
|
||||
for id in get_all_virtual_district_ids(name):
|
||||
result.append(get_virtual_district(name, id))
|
||||
return result
|
||||
@@ -0,0 +1,66 @@
|
||||
from .database import *
|
||||
from .s0_base import get_node_links
|
||||
|
||||
|
||||
def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, list[Any]]:
|
||||
write(name, 'delete from temp_vd_topology')
|
||||
|
||||
# map node name to index
|
||||
i = 0
|
||||
isolated_nodes = []
|
||||
node_index: dict[str, int] = {}
|
||||
for row in read_all(name, 'select id from _node'):
|
||||
node = str(row['id'])
|
||||
if get_node_links(name, node) == []:
|
||||
isolated_nodes.append(node)
|
||||
continue
|
||||
i += 1
|
||||
node_index[node] = i
|
||||
|
||||
# build topology graph
|
||||
pipes = read_all(name, 'select node1, node2, length from pipes')
|
||||
for pipe in pipes:
|
||||
source = node_index[str(pipe['node1'])]
|
||||
target = node_index[str(pipe['node2'])]
|
||||
cost = float(pipe['length'])
|
||||
write(name, f"insert into temp_vd_topology (source, target, cost) values ({source}, {target}, {cost})")
|
||||
pumps = read_all(name, 'select node1, node2 from pumps')
|
||||
for pump in pumps:
|
||||
source = node_index[str(pump['node1'])]
|
||||
target = node_index[str(pump['node2'])]
|
||||
write(name, f"insert into temp_vd_topology (source, target, cost) values ({source}, {target}, 0.0)")
|
||||
valves = read_all(name, 'select node1, node2 from valves')
|
||||
for valve in valves:
|
||||
source = node_index[str(valve['node1'])]
|
||||
target = node_index[str(valve['node2'])]
|
||||
write(name, f"insert into temp_vd_topology (source, target, cost) values ({source}, {target}, 0.0)")
|
||||
|
||||
# dijkstra distance
|
||||
node_distance: dict[str, dict[str, Any]] = {}
|
||||
for center in centers:
|
||||
for node, index in node_index.items():
|
||||
if node == center:
|
||||
node_distance[node] = { 'center': center, 'distance' : 0.0 }
|
||||
continue
|
||||
# TODO: check none
|
||||
distance = float(read(name, f"select max(agg_cost) as distance from pgr_dijkstraCost('select id, source, target, cost from temp_vd_topology', {index}, {node_index[center]}, false)")['distance'])
|
||||
if node not in node_distance:
|
||||
node_distance[node] = { 'center': center, 'distance' : distance }
|
||||
elif distance < node_distance[node]['distance']:
|
||||
node_distance[node] = { 'center': center, 'distance' : distance }
|
||||
|
||||
write(name, 'delete from temp_vd_topology')
|
||||
|
||||
# reorganize the distance result
|
||||
center_node: dict[str, list[str]] = {}
|
||||
for node, value in node_distance.items():
|
||||
if value['center'] not in center_node:
|
||||
center_node[value['center']] = []
|
||||
center_node[value['center']].append(node)
|
||||
|
||||
vds: list[dict[str, Any]] = []
|
||||
|
||||
for center, value in center_node.items():
|
||||
vds.append({ 'center': center, 'nodes': value })
|
||||
|
||||
return { 'virtual_districts': vds, 'isolated_nodes': isolated_nodes }
|
||||
@@ -0,0 +1,21 @@
|
||||
from .s32_region_util import calculate_boundary, inflate_boundary
|
||||
from .s35_vd_cal import *
|
||||
from .s35_vd import get_all_virtual_district_ids
|
||||
from .batch_exe import execute_batch_command
|
||||
|
||||
def generate_virtual_district(name: str, centers: list[str], inflate_delta: float = 0.5) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
for id in get_all_virtual_district_ids(name):
|
||||
cs.delete({'type': 'virtual_district', 'id': id})
|
||||
|
||||
vds = calculate_virtual_district(name, centers)['virtual_districts']
|
||||
|
||||
for vd in vds:
|
||||
center = vd['center']
|
||||
nodes = vd['nodes']
|
||||
boundary = calculate_boundary(name, nodes)
|
||||
boundary = inflate_boundary(name, boundary, inflate_delta)
|
||||
cs.add({ 'type': 'virtual_district', 'id': f"VD_{center}", 'boundary': boundary, 'center': center, 'nodes': nodes })
|
||||
|
||||
return execute_batch_command(name, cs)
|
||||
@@ -0,0 +1,104 @@
|
||||
from .database import ChangeSet
|
||||
from .s0_base import is_junction, get_nodes
|
||||
from .s9_demands import get_demand
|
||||
from .s32_region_util import Topology, get_nodes_in_region
|
||||
from .batch_exe import execute_batch_command
|
||||
|
||||
|
||||
DISTRIBUTION_TYPE_ADD = 'ADD'
|
||||
DISTRIBUTION_TYPE_OVERRIDE = 'OVERRIDE'
|
||||
|
||||
|
||||
def calculate_demand_to_nodes(name: str, demand: float, nodes: list[str]) -> dict[str, float]:
|
||||
if len(nodes) == 0 or demand == 0.0:
|
||||
return {}
|
||||
|
||||
topology = Topology(name, nodes)
|
||||
t_nodes = topology.nodes()
|
||||
t_links = topology.links()
|
||||
|
||||
length_sum = 0.0
|
||||
for value in t_links.values():
|
||||
length_sum += abs(value['length'])
|
||||
|
||||
if length_sum <= 0.0:
|
||||
return {}
|
||||
|
||||
demand_per_length = demand / length_sum
|
||||
|
||||
result: dict[str, float] = {}
|
||||
for node, value in t_nodes.items():
|
||||
if not is_junction(name, node):
|
||||
continue
|
||||
demand_per_node = 0.0
|
||||
for link in value['links']:
|
||||
demand_per_node += abs(t_links[link]['length']) * demand_per_length * 0.5
|
||||
result[node] = demand_per_node
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def calculate_demand_to_region(name: str, demand: float, region: str) -> dict[str, float]:
|
||||
nodes = get_nodes_in_region(name, region)
|
||||
return calculate_demand_to_nodes(name, demand, nodes)
|
||||
|
||||
|
||||
def calculate_demand_to_network(name: str, demand: float) -> dict[str, float]:
|
||||
nodes = get_nodes(name)
|
||||
return calculate_demand_to_nodes(name, demand, nodes)
|
||||
|
||||
|
||||
def distribute_demand_to_nodes(name: str, demand: float, nodes: list[str], type: str = DISTRIBUTION_TYPE_ADD) -> ChangeSet:
|
||||
if len(nodes) == 0 or demand == 0.0:
|
||||
return ChangeSet()
|
||||
if type != DISTRIBUTION_TYPE_ADD and type != DISTRIBUTION_TYPE_OVERRIDE:
|
||||
return ChangeSet()
|
||||
|
||||
topology = Topology(name, nodes)
|
||||
t_nodes = topology.nodes()
|
||||
t_links = topology.links()
|
||||
|
||||
length_sum = 0.0
|
||||
for value in t_links.values():
|
||||
length_sum += abs(value['length'])
|
||||
|
||||
if length_sum <= 0.0:
|
||||
return ChangeSet()
|
||||
|
||||
demand_per_length = demand / length_sum
|
||||
|
||||
cs = ChangeSet()
|
||||
|
||||
for node, value in t_nodes.items():
|
||||
if not is_junction(name, node):
|
||||
continue
|
||||
demand_per_node = 0.0
|
||||
for link in value['links']:
|
||||
demand_per_node += abs(t_links[link]['length']) * demand_per_length * 0.5
|
||||
|
||||
ds = get_demand(name, node)['demands']
|
||||
if len(ds) == 0:
|
||||
ds = [{'demand': demand_per_node, 'pattern': None, 'category': None}]
|
||||
elif type == DISTRIBUTION_TYPE_ADD:
|
||||
ds[0]['demand'] += demand_per_node
|
||||
else:
|
||||
ds[0]['demand'] = demand_per_node
|
||||
cs.update({'type': 'demand', 'junction': node, 'demands': ds})
|
||||
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def distribute_demand_to_region(name: str, demand: float, region: str, type: str = DISTRIBUTION_TYPE_ADD) -> ChangeSet:
|
||||
nodes = get_nodes_in_region(name, region)
|
||||
return distribute_demand_to_nodes(name, demand, nodes, type)
|
||||
|
||||
def get_total_base_demand(name:str,region:str)->float:
|
||||
nodes = get_nodes_in_region(name, region)
|
||||
t_demands=0.0
|
||||
for node in nodes:
|
||||
if not is_junction(name, node):
|
||||
continue
|
||||
ds = get_demand(name, node)['demands']
|
||||
t_demands= t_demands+ds[0]['demand']
|
||||
|
||||
return t_demands
|
||||
@@ -0,0 +1,42 @@
|
||||
from .database import *
|
||||
|
||||
|
||||
def get_scada_info_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'type' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'x' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'query_api_id' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'associated_element_id' : {'type': 'str' , 'optional': False , 'readonly': True } }
|
||||
|
||||
|
||||
def get_scada_info(name: str, id: str) -> dict[str, Any]:
|
||||
si = try_read(name, f"select * from scada_info where id = '{id}'")
|
||||
if si is None:
|
||||
return {}
|
||||
|
||||
d = {}
|
||||
d['id'] = si['id']
|
||||
d['type'] = si['type']
|
||||
d['x'] = float(si['x_coor'])
|
||||
d['y'] = float(si['y_coor'])
|
||||
d['api_query_id'] = si['api_query_id']
|
||||
d['associated_element_id'] = si['associated_element_id']
|
||||
|
||||
return d
|
||||
|
||||
def get_all_scada_info(name: str) -> list[dict[str, Any]]:
|
||||
sis = read_all(name, f"select * from scada_info")
|
||||
if sis is None:
|
||||
return []
|
||||
|
||||
d = []
|
||||
for si in sis:
|
||||
d.append({ 'id': si['id'],
|
||||
'type': si['type'],
|
||||
'x': float(si['x_coor']),
|
||||
'y': float(si['y_coor']),
|
||||
'api_query_id': si['api_query_id'],
|
||||
'associated_element_id': si['associated_element_id'] })
|
||||
|
||||
return d
|
||||
@@ -0,0 +1,37 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
|
||||
class User(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'user'
|
||||
self.id = str(input['user_id'])
|
||||
self.name = str(input['username'])
|
||||
self.password = str(input['password'])
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id, 'name': self.name, 'password': self.password }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id }
|
||||
|
||||
|
||||
def get_user_schema(name: str) -> dict[str, dict[Any, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'name' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'password' : {'type': 'str' , 'optional': False , 'readonly': False} }
|
||||
|
||||
def get_user(name: str, user_name: str) -> dict[Any, Any]:
|
||||
t = try_read(name, f"select * from users where username = '{user_name}'")
|
||||
if t == None:
|
||||
return {}
|
||||
|
||||
d = {}
|
||||
d['id'] = str(t['user_id'])
|
||||
d['name'] = str(t['username'])
|
||||
# d['password'] = str(t['password'])
|
||||
|
||||
return d
|
||||
|
||||
def get_all_users(name: str) -> list[dict[Any, Any]]:
|
||||
return read_all(name, "select * from users")
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
from .s24_coordinates import *
|
||||
|
||||
|
||||
def get_reservoir_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'x' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'head' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'links' : {'type': 'str_list' , 'optional': False , 'readonly': True } }
|
||||
|
||||
|
||||
def get_reservoir(name: str, id: str) -> dict[str, Any]:
|
||||
r = try_read(name, f"select * from reservoirs where id = '{id}'")
|
||||
if r == None:
|
||||
return {}
|
||||
xy = get_node_coord(name, id)
|
||||
d = {}
|
||||
d['id'] = str(r['id'])
|
||||
d['x'] = float(xy['x'])
|
||||
d['y'] = float(xy['y'])
|
||||
d['head'] = float(r['head'])
|
||||
d['pattern'] = str(r['pattern']) if r['pattern'] != None else None
|
||||
d['links'] = get_node_links(name, id)
|
||||
return d
|
||||
|
||||
# DingZQ, 2025-03-29
|
||||
def get_all_reservoirs(name: str) -> list[dict[str, Any]]:
|
||||
rows = read_all(name, f"select * from reservoirs")
|
||||
if rows == None:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
d = {}
|
||||
id = str(row['id'])
|
||||
xy = get_node_coord(name, id)
|
||||
d['id'] = id
|
||||
d['x'] = float(xy['x'])
|
||||
d['y'] = float(xy['y'])
|
||||
d['head'] = float(row['head']) if row['head'] != None else None
|
||||
d['pattern'] = str(row['pattern']) if row['pattern'] != None else None
|
||||
d['links'] = get_node_links(name, id)
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
class Reservoir(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'reservoir'
|
||||
self.id = str(input['id'])
|
||||
self.x = float(input['x'])
|
||||
self.y = float(input['y'])
|
||||
self.head = float(input['head'])
|
||||
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_id = f"'{self.id}'"
|
||||
self.f_head = self.head
|
||||
self.f_pattern = f"'{self.pattern}'" if self.pattern != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'head': self.head, 'pattern': self.pattern }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id }
|
||||
|
||||
|
||||
def _set_reservoir(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Reservoir(get_reservoir(name, cs.operations[0]['id']))
|
||||
raw_new = get_reservoir(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_reservoir_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Reservoir(raw_new)
|
||||
|
||||
redo_sql = f"update reservoirs set head = {new.f_head}, pattern = {new.f_pattern} where id = {new.f_id};"
|
||||
redo_sql += f"\n{sql_update_coord(new.id, new.x, new.y)}"
|
||||
|
||||
undo_sql = sql_update_coord(old.id, old.x, old.y)
|
||||
undo_sql += f"\nupdate reservoirs set head = {old.f_head}, pattern = {old.f_pattern} where id = {old.f_id};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_reservoir(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_reservoir(name, cs))
|
||||
|
||||
|
||||
def _add_reservoir(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = Reservoir(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into _node (id, type) values ({new.f_id}, {new.f_type});"
|
||||
redo_sql += f"\ninsert into reservoirs (id, head, pattern) values ({new.f_id}, {new.f_head}, {new.f_pattern});"
|
||||
redo_sql += f"\n{sql_insert_coord(new.id, new.x, new.y)}"
|
||||
|
||||
undo_sql = sql_delete_coord(new.id)
|
||||
undo_sql += f"\ndelete from reservoirs where id = {new.f_id};"
|
||||
undo_sql += f"\ndelete from _node where id = {new.f_id};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_id_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_reservoir(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_reservoir(name, cs))
|
||||
|
||||
|
||||
def _delete_reservoir(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Reservoir(get_reservoir(name, cs.operations[0]['id']))
|
||||
|
||||
redo_sql = sql_delete_coord(old.id)
|
||||
redo_sql += f"\ndelete from reservoirs where id = {old.f_id};"
|
||||
redo_sql += f"\ndelete from _node where id = {old.f_id};"
|
||||
|
||||
undo_sql = f"insert into _node (id, type) values ({old.f_id}, {old.f_type});"
|
||||
undo_sql += f"\ninsert into reservoirs (id, head, pattern) values ({old.f_id}, {old.f_head}, {old.f_pattern});"
|
||||
undo_sql += f"\n{sql_insert_coord(old.id, old.x, old.y)}"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_id_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_reservoir(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_reservoir(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# id elev (pattern) ;desc
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_reservoir(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
head = float(tokens[1])
|
||||
pattern = str(tokens[2]) if num_without_desc >= 3 else None
|
||||
pattern = f"'{pattern}'" if pattern != None else 'null'
|
||||
desc = str(tokens[-1]) if has_desc else None
|
||||
|
||||
return str(f"insert into _node (id, type) values ('{id}', 'reservoir');insert into reservoirs (id, head, pattern) values ('{id}', {head}, {pattern});")
|
||||
|
||||
|
||||
def inp_out_reservoir(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from reservoirs')
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
head = obj['head']
|
||||
pattern = obj['pattern'] if obj['pattern'] != None else ''
|
||||
desc = ';'
|
||||
lines.append(f'{id} {head} {pattern} {desc}')
|
||||
return lines
|
||||
|
||||
|
||||
def unset_reservoir_by_pattern(name: str, pattern: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select id from reservoirs where pattern = '{pattern}'")
|
||||
for row in rows:
|
||||
cs.append(g_update_prefix | {'type': 'reservoir', 'id': row['id'], 'pattern': None})
|
||||
|
||||
return cs
|
||||
@@ -0,0 +1,30 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
|
||||
def get_scheme_schema(name: str) -> dict[str, dict[Any, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'name' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'type' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
"create_time": {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
"start_time" : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
"detail" : {'type': 'str' , 'optional': False , 'readonly': True } }
|
||||
|
||||
|
||||
def get_scheme(name: str, schema_name: str) -> dict[Any, Any]:
|
||||
t = try_read(name, f"select * from scheme_list where scheme_name = '{schema_name}'")
|
||||
if t == None:
|
||||
return {}
|
||||
|
||||
d = {}
|
||||
d['id'] = str(t['scheme_id'])
|
||||
d['name'] = str(t['scheme_name'])
|
||||
d['type'] = str(t['scheme_type'])
|
||||
d['create_time'] = str(t['create_time'])
|
||||
d['start_time'] = str(t['start_time'])
|
||||
d['detail'] = str(t['detail'])
|
||||
|
||||
return d
|
||||
|
||||
def get_all_schemes(name: str) -> list[dict[Any, Any]]:
|
||||
return read_all(name, "select * from scheme_list")
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
import json
|
||||
|
||||
def get_pipe_risk_probability_now(name: str, pipe_id: str) -> dict[str, Any]:
|
||||
t = try_read(name, f"select * from pipe_risk_probability where pipeid = '{pipe_id}'")
|
||||
if t == None:
|
||||
return {}
|
||||
|
||||
d = {}
|
||||
d['pipeid'] = str(t['pipeid'])
|
||||
d['pipeage'] = t['pipeage']
|
||||
d['risk_probability_now'] = t['risk_probability_now']
|
||||
|
||||
return d
|
||||
|
||||
def get_pipe_risk_probability(name: str, pipe_id: str) -> dict[str, Any]:
|
||||
t = try_read(name, f"select * from pipe_risk_probability where pipeid = '{pipe_id}'")
|
||||
if t == None:
|
||||
return {}
|
||||
|
||||
d = {}
|
||||
d['pipeid'] = t['pipeid']
|
||||
d['x'] = t['x']
|
||||
d['y'] = t['y']
|
||||
|
||||
return d
|
||||
|
||||
def get_network_pipe_risk_probability_now(name: str) -> list[dict[str, Any]]:
|
||||
pipe_risk_probability_list = []
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select * from pipe_risk_probability")
|
||||
for record in cur:
|
||||
#pipe_risk_probability_list.append(record)
|
||||
t = {}
|
||||
t['pipeid'] = record['pipeid']
|
||||
t['pipeage'] = record['pipeage']
|
||||
t['risk_probability_now'] = record['risk_probability_now']
|
||||
pipe_risk_probability_list.append(t)
|
||||
|
||||
return pipe_risk_probability_list
|
||||
|
||||
def get_pipes_risk_probability(name: str, pipe_ids: list[str]) -> list[dict[str, Any]]:
|
||||
pipe_risk_probability_list = []
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select * from pipe_risk_probability")
|
||||
for record in cur:
|
||||
if record['pipeid'] in pipe_ids:
|
||||
t = {}
|
||||
t['pipeid'] = record['pipeid']
|
||||
t['x'] = record['x']
|
||||
t['y'] = record['y']
|
||||
pipe_risk_probability_list.append(t)
|
||||
|
||||
return pipe_risk_probability_list
|
||||
|
||||
def get_pipe_risk_probability_geometries(name: str) -> dict[str, Any]:
|
||||
'''
|
||||
获取管道的几何信息
|
||||
返回一个字典,key 是管道的 id,value 是管道的几何信息
|
||||
几何信息是一个字典,包含 start 和 end 两个 key,value 是管道的起点和终点的坐标
|
||||
'''
|
||||
pipe_risk_probability_geometries = {}
|
||||
|
||||
key_pipeId = '编码'
|
||||
# key_startnode = '上游节点'
|
||||
# key_endnode = '下游节点'
|
||||
key_geometry = 'geometry'
|
||||
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select *, ST_AsGeoJSON(geometry) AS {key_geometry} from gis_pipe")
|
||||
|
||||
for record in cur:
|
||||
id = record[key_pipeId]
|
||||
geom = json.loads(record[key_geometry])
|
||||
|
||||
pipe_risk_probability_geometries[id] = {
|
||||
'points': geom['coordinates']
|
||||
}
|
||||
|
||||
for col in record:
|
||||
if col != key_geometry:
|
||||
pipe_risk_probability_geometries[id][col] = record[col]
|
||||
|
||||
# print(len(pipe_risk_probability_geometries))
|
||||
|
||||
return pipe_risk_probability_geometries
|
||||
@@ -0,0 +1,7 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
from .s42_sensor_placement import *
|
||||
import json
|
||||
|
||||
def get_all_sensor_placements(name: str) -> list[dict[Any, Any]]:
|
||||
return read_all(name, "select * from sensor_placement")
|
||||
@@ -0,0 +1,6 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
import json
|
||||
|
||||
def get_all_burst_locate_results(name: str) -> list[dict[Any, Any]]:
|
||||
return read_all(name, "select * from burst_locate_result")
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
from .s24_coordinates import *
|
||||
|
||||
|
||||
OVERFLOW_YES = 'YES'
|
||||
OVERFLOW_NO = 'NO'
|
||||
|
||||
|
||||
def get_tank_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'x' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'elevation' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'init_level' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'min_level' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'max_level' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'diameter' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'min_vol' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'vol_curve' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'overflow' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'links' : {'type': 'str_list' , 'optional': False , 'readonly': True } }
|
||||
|
||||
|
||||
def get_tank(name: str, id: str) -> dict[str, Any]:
|
||||
t = try_read(name, f"select * from tanks where id = '{id}'")
|
||||
if t == None:
|
||||
return {}
|
||||
xy = get_node_coord(name, id)
|
||||
d = {}
|
||||
d['id'] = str(t['id'])
|
||||
d['x'] = float(xy['x'])
|
||||
d['y'] = float(xy['y'])
|
||||
d['elevation'] = float(t['elevation'])
|
||||
d['init_level'] = float(t['init_level'])
|
||||
d['min_level'] = float(t['min_level'])
|
||||
d['max_level'] = float(t['max_level'])
|
||||
d['diameter'] = float(t['diameter'])
|
||||
d['min_vol'] = float(t['min_vol'])
|
||||
d['vol_curve'] = str(t['vol_curve']) if t['vol_curve'] != None else None
|
||||
d['overflow'] = str(t['overflow']) if t['overflow'] != None else None
|
||||
d['links'] = get_node_links(name, id)
|
||||
return d
|
||||
|
||||
# DingZQ, 2025-03-29
|
||||
def get_all_tanks(name: str) -> list[dict[str, Any]]:
|
||||
rows = read_all(name, f"select * from tanks")
|
||||
if rows == None:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
d = {}
|
||||
id = str(row['id'])
|
||||
xy = get_node_coord(name, id)
|
||||
d['id'] = id
|
||||
d['x'] = float(xy['x'])
|
||||
d['y'] = float(xy['y'])
|
||||
d['elevation'] = float(row['elevation'])
|
||||
d['init_level'] = float(row['init_level'])
|
||||
d['min_level'] = float(row['min_level'])
|
||||
d['max_level'] = float(row['max_level'])
|
||||
d['diameter'] = float(row['diameter'])
|
||||
d['min_vol'] = float(row['min_vol'])
|
||||
d['vol_curve'] = str(row['vol_curve']) if row['vol_curve'] != None else None
|
||||
d['overflow'] = str(row['overflow']) if row['overflow'] != None else None
|
||||
d['links'] = get_node_links(name, id)
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
class Tank(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'tank'
|
||||
self.id = str(input['id'])
|
||||
self.x = float(input['x'])
|
||||
self.y = float(input['y'])
|
||||
self.elevation = float(input['elevation'])
|
||||
self.init_level = float(input['init_level'])
|
||||
self.min_level = float(input['min_level'])
|
||||
self.max_level = float(input['max_level'])
|
||||
self.diameter = float(input['diameter'])
|
||||
self.min_vol = float(input['min_vol'])
|
||||
self.vol_curve = str(input['vol_curve']) if 'vol_curve' in input and input['vol_curve'] != None else None
|
||||
self.overflow = str(input['overflow']) if 'overflow' in input and input['overflow'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_id = f"'{self.id}'"
|
||||
self.f_elevation = self.elevation
|
||||
self.f_init_level = self.init_level
|
||||
self.f_min_level = self.min_level
|
||||
self.f_max_level = self.max_level
|
||||
self.f_diameter = self.diameter
|
||||
self.f_min_vol = self.min_vol
|
||||
self.f_vol_curve = f"'{self.vol_curve}'" if self.vol_curve != None else 'null'
|
||||
self.f_overflow = f"'{self.overflow}'" if self.overflow != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'elevation': self.elevation, 'init_level': self.init_level, 'min_level': self.min_level, 'max_level': self.max_level, 'diameter': self.diameter, 'min_vol': self.min_vol, 'vol_curve': self.vol_curve, 'overflow': self.overflow }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id }
|
||||
|
||||
|
||||
def _set_tank(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Tank(get_tank(name, cs.operations[0]['id']))
|
||||
raw_new = get_tank(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_tank_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Tank(raw_new)
|
||||
|
||||
redo_sql = f"update tanks set elevation = {new.f_elevation}, init_level = {new.f_init_level}, min_level = {new.f_min_level}, max_level = {new.f_max_level}, diameter = {new.f_diameter}, min_vol = {new.f_min_vol}, vol_curve = {new.f_vol_curve}, overflow = {new.f_overflow} where id = {new.f_id};"
|
||||
redo_sql += f"\n{sql_update_coord(new.id, new.x, new.y)}"
|
||||
|
||||
undo_sql = sql_update_coord(old.id, old.x, old.y)
|
||||
undo_sql += f"\nupdate tanks set elevation = {old.f_elevation}, init_level = {old.f_init_level}, min_level = {old.f_min_level}, max_level = {old.f_max_level}, diameter = {old.f_diameter}, min_vol = {old.f_min_vol}, vol_curve = {old.f_vol_curve}, overflow = {old.f_overflow} where id = {old.f_id};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_tank(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_tank(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_tank(name, cs))
|
||||
|
||||
|
||||
def _add_tank(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = Tank(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into _node (id, type) values ({new.f_id}, {new.f_type});"
|
||||
redo_sql += f"\ninsert into tanks (id, elevation, init_level, min_level, max_level, diameter, min_vol, vol_curve, overflow) values ({new.f_id}, {new.f_elevation}, {new.f_init_level}, {new.f_min_level}, {new.f_max_level}, {new.f_diameter}, {new.f_min_vol}, {new.f_vol_curve}, {new.f_overflow});"
|
||||
redo_sql += f"\n{sql_insert_coord(new.id, new.x, new.y)}"
|
||||
|
||||
undo_sql = sql_delete_coord(new.id)
|
||||
undo_sql += f"\ndelete from tanks where id = {new.f_id};"
|
||||
undo_sql += f"\ndelete from _node where id = {new.f_id};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_id_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_tank(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_tank(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_tank(name, cs))
|
||||
|
||||
|
||||
def _delete_tank(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Tank(get_tank(name, cs.operations[0]['id']))
|
||||
|
||||
redo_sql = sql_delete_coord(old.id)
|
||||
redo_sql += f"\ndelete from tanks where id = {old.f_id};"
|
||||
redo_sql += f"\ndelete from _node where id = {old.f_id};"
|
||||
|
||||
undo_sql = f"insert into _node (id, type) values ({old.f_id}, {old.f_type});"
|
||||
undo_sql += f"\ninsert into tanks (id, elevation, init_level, min_level, max_level, diameter, min_vol, vol_curve, overflow) values ({old.f_id}, {old.f_elevation}, {old.f_init_level}, {old.f_min_level}, {old.f_max_level}, {old.f_diameter}, {old.f_min_vol}, {old.f_vol_curve}, {old.f_overflow});"
|
||||
undo_sql += f"\n{sql_insert_coord(old.id, old.x, old.y)}"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_id_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_tank(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_tank(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_tank(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2]
|
||||
# [IN]
|
||||
# id elev initlevel minlevel maxlevel diam (minvol vcurve overflow) ;desc
|
||||
# xxx
|
||||
# * YES
|
||||
# [OUT]
|
||||
# id elev initlevel minlevel maxlevel diam minvol (vcurve overflow) ;desc
|
||||
#--------------------------------------------------------------
|
||||
# [EPA3]
|
||||
# id elev initlevel minlevel maxlevel diam minvol (vcurve)
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_tank(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
elevation = float(tokens[1])
|
||||
init_level = float(tokens[2])
|
||||
min_level = float(tokens[3])
|
||||
max_level = float(tokens[4])
|
||||
diameter = float(tokens[5])
|
||||
min_vol = float(tokens[6]) if num_without_desc >= 7 else 0.0
|
||||
vol_curve = str(tokens[7]) if num_without_desc >= 8 and tokens[7] != '*' else None
|
||||
vol_curve = f"'{vol_curve}'" if vol_curve != None else 'null'
|
||||
overflow = str(tokens[8].upper()) if num_without_desc >= 9 else None
|
||||
overflow = f"'{overflow}'" if overflow != None else 'null'
|
||||
desc = str(tokens[-1]) if has_desc else None
|
||||
|
||||
return str(f"insert into _node (id, type) values ('{id}', 'tank');insert into tanks (id, elevation, init_level, min_level, max_level, diameter, min_vol, vol_curve, overflow) values ('{id}', {elevation}, {init_level}, {min_level}, {max_level}, {diameter}, {min_vol}, {vol_curve}, {overflow});")
|
||||
|
||||
|
||||
def inp_out_tank(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from tanks')
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
elevation = obj['elevation']
|
||||
init_level = obj['init_level']
|
||||
min_level = obj['min_level']
|
||||
max_level = obj['max_level']
|
||||
diameter = obj['diameter']
|
||||
min_vol = obj['min_vol']
|
||||
vol_curve = obj['vol_curve'] if obj['vol_curve'] != None else ''
|
||||
overflow = obj['overflow'] if obj['overflow'] != None else ''
|
||||
if vol_curve == '' and overflow != '':
|
||||
vol_curve = '*'
|
||||
desc = ';'
|
||||
lines.append(f'{id} {elevation} {init_level} {min_level} {max_level} {diameter} {min_vol} {vol_curve} {overflow} {desc}')
|
||||
return lines
|
||||
|
||||
|
||||
def unset_tank_by_curve(name: str, curve: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select id from tanks where vol_curve = '{curve}'")
|
||||
for row in rows:
|
||||
cs.append(g_update_prefix | {'type': 'tank', 'id': row['id'], 'vol_curve': None})
|
||||
|
||||
return cs
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
|
||||
|
||||
PIPE_STATUS_OPEN = 'OPEN'
|
||||
PIPE_STATUS_CLOSED = 'CLOSED'
|
||||
PIPE_STATUS_CV = 'CV'
|
||||
|
||||
|
||||
def get_pipe_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'node1' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'node2' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'length' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'diameter' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'roughness' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'minor_loss' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'status' : {'type': 'str' , 'optional': False , 'readonly': False} }
|
||||
|
||||
|
||||
def get_pipe(name: str, id: str) -> dict[str, Any]:
|
||||
p = try_read(name, f"select * from pipes where id = '{id}'")
|
||||
if p == None:
|
||||
return {}
|
||||
d = {}
|
||||
d['id'] = str(p['id'])
|
||||
d['node1'] = str(p['node1'])
|
||||
d['node2'] = str(p['node2'])
|
||||
d['length'] = float(p['length'])
|
||||
d['diameter'] = float(p['diameter'])
|
||||
d['roughness'] = float(p['roughness'])
|
||||
d['minor_loss'] = float(p['minor_loss'])
|
||||
d['status'] = str(p['status'])
|
||||
return d
|
||||
|
||||
# DingZQ, 2025-03-29
|
||||
def get_all_pipes(name: str) -> list[dict[str, Any]]:
|
||||
rows = read_all(name, f"select * from pipes")
|
||||
if rows == None:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
d = {}
|
||||
d['id'] = str(row['id'])
|
||||
d['node1'] = str(row['node1'])
|
||||
d['node2'] = str(row['node2'])
|
||||
d['length'] = float(row['length'])
|
||||
d['diameter'] = float(row['diameter'])
|
||||
d['roughness'] = float(row['roughness'])
|
||||
d['minor_loss'] = float(row['minor_loss'])
|
||||
d['status'] = str(row['status'])
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
class Pipe(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'pipe'
|
||||
self.id = str(input['id'])
|
||||
self.node1 = str(input['node1'])
|
||||
self.node2 = str(input['node2'])
|
||||
self.length = float(input['length'])
|
||||
self.diameter = float(input['diameter'])
|
||||
self.roughness = float(input['roughness'])
|
||||
self.minor_loss = float(input['minor_loss'])
|
||||
self.status = str(input['status'])
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_id = f"'{self.id}'"
|
||||
self.f_node1 = f"'{self.node1}'"
|
||||
self.f_node2 = f"'{self.node2}'"
|
||||
self.f_length = self.length
|
||||
self.f_diameter = self.diameter
|
||||
self.f_roughness = self.roughness
|
||||
self.f_minor_loss = self.minor_loss
|
||||
self.f_status = f"'{self.status}'"
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2, 'length': self.length, 'diameter': self.diameter, 'roughness': self.roughness, 'minor_loss': self.minor_loss, 'status': self.status }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id }
|
||||
|
||||
|
||||
def _set_pipe(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Pipe(get_pipe(name, cs.operations[0]['id']))
|
||||
raw_new = get_pipe(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_pipe_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Pipe(raw_new)
|
||||
|
||||
redo_sql = f"update pipes set node1 = {new.f_node1}, node2 = {new.f_node2}, length = {new.f_length}, diameter = {new.f_diameter}, roughness = {new.f_roughness}, minor_loss = {new.f_minor_loss}, status = {new.f_status} where id = {new.f_id};"
|
||||
undo_sql = f"update pipes set node1 = {old.f_node1}, node2 = {old.f_node2}, length = {old.f_length}, diameter = {old.f_diameter}, roughness = {old.f_roughness}, minor_loss = {old.f_minor_loss}, status = {old.f_status} where id = {old.f_id};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_pipe(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pipe(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_pipe(name, cs))
|
||||
|
||||
|
||||
def _add_pipe(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = Pipe(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into _link (id, type) values ({new.f_id}, {new.f_type});"
|
||||
redo_sql += f"\ninsert into pipes (id, node1, node2, length, diameter, roughness, minor_loss, status) values ({new.f_id}, {new.f_node1}, {new.f_node2}, {new.f_length}, {new.f_diameter}, {new.f_roughness}, {new.f_minor_loss}, {new.f_status});"
|
||||
|
||||
undo_sql = f"delete from pipes where id = {new.f_id};"
|
||||
undo_sql += f"\ndelete from _link where id = {new.f_id};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_id_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_pipe(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pipe(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_pipe(name, cs))
|
||||
|
||||
|
||||
def _delete_pipe(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Pipe(get_pipe(name, cs.operations[0]['id']))
|
||||
|
||||
redo_sql = f"delete from pipes where id = {old.f_id};"
|
||||
redo_sql += f"\ndelete from _link where id = {old.f_id};"
|
||||
|
||||
undo_sql = f"insert into _link (id, type) values ({old.f_id}, {old.f_type});"
|
||||
undo_sql += f"\ninsert into pipes (id, node1, node2, length, diameter, roughness, minor_loss, status) values ({old.f_id}, {old.f_node1}, {old.f_node2}, {old.f_length}, {old.f_diameter}, {old.f_roughness}, {old.f_minor_loss}, {old.f_status});"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_id_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_pipe(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pipe(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_pipe(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3]
|
||||
# [IN]
|
||||
# id node1 node2 length diam rcoeff (lcoeff status) ;desc
|
||||
# [OUT]
|
||||
# id node1 node2 length diam rcoeff lcoeff (status) ;desc
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_pipe(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
node1 = str(tokens[1])
|
||||
node2 = str(tokens[2])
|
||||
length = float(tokens[3])
|
||||
diameter = float(tokens[4])
|
||||
roughness = float(tokens[5])
|
||||
minor_loss = float(tokens[6])
|
||||
# status is must-have, here fix input
|
||||
status = str(tokens[7].upper()) if num_without_desc >= 8 else PIPE_STATUS_OPEN
|
||||
desc = str(tokens[-1]) if has_desc else None
|
||||
|
||||
return str(f"insert into _link (id, type) values ('{id}', 'pipe');insert into pipes (id, node1, node2, length, diameter, roughness, minor_loss, status) values ('{id}', '{node1}', '{node2}', {length}, {diameter}, {roughness}, {minor_loss}, '{status}');")
|
||||
|
||||
|
||||
def inp_out_pipe(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from pipes')
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
node1 = obj['node1']
|
||||
node2 = obj['node2']
|
||||
length = obj['length']
|
||||
diameter = obj['diameter']
|
||||
roughness = obj['roughness']
|
||||
minor_loss = obj['minor_loss']
|
||||
status = obj['status']
|
||||
desc = ';'
|
||||
lines.append(f'{id} {node1} {node2} {length} {diameter} {roughness} {minor_loss} {status} {desc}')
|
||||
return lines
|
||||
|
||||
|
||||
'''def delete_pipe_by_node(name: str, node: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select id from pipes where node1 = '{node}' or node2 = '{node}'")
|
||||
for row in rows:
|
||||
cs.append(g_delete_prefix | {'type': 'pipe', 'id': row['id']})
|
||||
|
||||
return cs'''
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
|
||||
|
||||
def get_pump_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'node1' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'node2' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'power' : {'type': 'float' , 'optional': True , 'readonly': False},
|
||||
'head' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'speed' : {'type': 'float' , 'optional': True , 'readonly': False},
|
||||
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_pump(name: str, id: str) -> dict[str, Any]:
|
||||
p = try_read(name, f"select * from pumps where id = '{id}'")
|
||||
if p == None:
|
||||
return {}
|
||||
d = {}
|
||||
d['id'] = str(p['id'])
|
||||
d['node1'] = str(p['node1'])
|
||||
d['node2'] = str(p['node2'])
|
||||
d['power'] = float(p['power']) if p['power'] != None else None
|
||||
d['head'] = str(p['head']) if p['head'] != None else None
|
||||
d['speed'] = float(p['speed']) if p['speed'] != None else None
|
||||
d['pattern'] = str(p['pattern']) if p['pattern'] != None else None
|
||||
return d
|
||||
|
||||
# DingZQ, 2025-03-29
|
||||
def get_all_pumps(name: str) -> list[dict[str, Any]]:
|
||||
rows = read_all(name, f"select * from pumps")
|
||||
if rows == None:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
d = {}
|
||||
d['id'] = str(row['id'])
|
||||
d['node1'] = str(row['node1'])
|
||||
d['node2'] = str(row['node2'])
|
||||
d['power'] = float(row['power']) if row['power'] != None else None
|
||||
d['head'] = str(row['head']) if row['head'] != None else None
|
||||
d['speed'] = float(row['speed']) if row['speed'] != None else None
|
||||
d['pattern'] = str(row['pattern']) if row['pattern'] != None else None
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
class Pump(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'pump'
|
||||
self.id = str(input['id'])
|
||||
self.node1 = str(input['node1'])
|
||||
self.node2 = str(input['node2'])
|
||||
self.power = float(input['power']) if 'power' in input and input['power'] != None else None
|
||||
self.head = str(input['head']) if 'head' in input and input['head'] != None else None
|
||||
self.speed = float(input['speed']) if 'speed' in input and input['speed'] != None else None
|
||||
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_id = f"'{self.id}'"
|
||||
self.f_node1 = f"'{self.node1}'"
|
||||
self.f_node2 = f"'{self.node2}'"
|
||||
self.f_power = self.power if self.power != None else 'null'
|
||||
self.f_head = f"'{self.head}'" if self.head != None else 'null'
|
||||
self.f_speed = self.speed if self.speed != None else 'null'
|
||||
self.f_pattern = f"'{self.pattern}'" if self.pattern != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2, 'power': self.power, 'head': self.head, 'speed': self.speed, 'pattern': self.pattern }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id }
|
||||
|
||||
|
||||
def _set_pump(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Pump(get_pump(name, cs.operations[0]['id']))
|
||||
raw_new = get_pump(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_pump_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Pump(raw_new)
|
||||
|
||||
redo_sql = f"update pumps set node1 = {new.f_node1}, node2 = {new.f_node2}, power = {new.f_power}, head = {new.f_head}, speed = {new.f_speed}, pattern = {new.f_pattern} where id = {new.f_id};"
|
||||
undo_sql = f"update pumps set node1 = {old.f_node1}, node2 = {old.f_node2}, power = {old.f_power}, head = {old.f_head}, speed = {old.f_speed}, pattern = {old.f_pattern} where id = {old.f_id};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_pump(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pump(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_pump(name, cs))
|
||||
|
||||
|
||||
def _add_pump(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = Pump(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into _link (id, type) values ({new.f_id}, {new.f_type});"
|
||||
redo_sql += f"\ninsert into pumps (id, node1, node2, power, head, speed, pattern) values ({new.f_id}, {new.f_node1}, {new.f_node2}, {new.f_power}, {new.f_head}, {new.f_speed}, {new.f_pattern});"
|
||||
|
||||
undo_sql = f"delete from pumps where id = {new.f_id};"
|
||||
undo_sql += f"\ndelete from _link where id = {new.f_id};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_id_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_pump(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pump(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_pump(name, cs))
|
||||
|
||||
|
||||
def _delete_pump(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Pump(get_pump(name, cs.operations[0]['id']))
|
||||
|
||||
redo_sql = f"delete from pumps where id = {old.f_id};"
|
||||
redo_sql += f"\ndelete from _link where id = {old.f_id};"
|
||||
|
||||
undo_sql = f"insert into _link (id, type) values ({old.f_id}, {old.f_type});"
|
||||
undo_sql += f"\ninsert into pumps (id, node1, node2, power, head, speed, pattern) values ({old.f_id}, {old.f_node1}, {old.f_node2}, {old.f_power}, {old.f_head}, {old.f_speed}, {old.f_pattern});"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_id_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_pump(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pump(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_pump(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# id node1 node2 KEYWORD value {KEYWORD value ...} ;desc
|
||||
# where KEYWORD = [POWER,HEAD,PATTERN,SPEED]
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_pump(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
node1 = str(tokens[1])
|
||||
node2 = str(tokens[2])
|
||||
props = {}
|
||||
for i in range(3, num_without_desc, 2):
|
||||
props |= { tokens[i].lower(): tokens[i + 1] }
|
||||
power = float(props['power']) if 'power' in props else None
|
||||
power = power if power != None else 'null'
|
||||
head = str(props['head']) if 'head' in props else None
|
||||
head = f"'{head}'" if head != None else 'null'
|
||||
speed = float(props['speed']) if 'speed' in props else None
|
||||
speed = speed if speed != None else 'null'
|
||||
pattern = str(props['pattern']) if 'pattern' in props else None
|
||||
pattern = f"'{pattern}'" if pattern != None else 'null'
|
||||
desc = str(tokens[-1]) if has_desc else None
|
||||
|
||||
return str(f"insert into _link (id, type) values ('{id}', 'pump');insert into pumps (id, node1, node2, power, head, speed, pattern) values ('{id}', '{node1}', '{node2}', {power}, {head}, {speed}, {pattern});")
|
||||
|
||||
|
||||
def inp_out_pump(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from pumps')
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
node1 = obj['node1']
|
||||
node2 = obj['node2']
|
||||
power = f"POWER {obj['power']}" if obj['power'] != None else ''
|
||||
head = f"HEAD {obj['head']}" if obj['head'] != None else ''
|
||||
speed = f"SPEED {obj['speed']}" if obj['speed'] != None else ''
|
||||
pattern = f"PATTERN {obj['pattern']}" if obj['pattern'] != None else ''
|
||||
desc = ';'
|
||||
lines.append(f'{id} {node1} {node2} {power} {head} {speed} {pattern} {desc}')
|
||||
return lines
|
||||
|
||||
|
||||
'''def delete_pump_by_node(name: str, node: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select id from pumps where node1 = '{node}' or node2 = '{node}'")
|
||||
for row in rows:
|
||||
cs.append(g_delete_prefix | {'type': 'pump', 'id': row['id']})
|
||||
|
||||
return cs'''
|
||||
|
||||
|
||||
def unset_pump_by_curve(name: str, curve: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select * from pumps where head = '{curve}'")
|
||||
for row in rows:
|
||||
if row['power'] != None:
|
||||
cs.append(g_update_prefix | {'type': 'pump', 'id': row['id'], 'head': None})
|
||||
else: # workaround to prevent pump deletion... and I don't want to remove constraint...
|
||||
cs.append(g_update_prefix | {'type': 'pump', 'id': row['id'], 'head': None, 'power': 0.0})
|
||||
|
||||
return cs
|
||||
|
||||
|
||||
def unset_pump_by_pattern(name: str, pattern: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select id from pumps where pattern = '{pattern}'")
|
||||
for row in rows:
|
||||
cs.append(g_update_prefix | {'type': 'pump', 'id': row['id'], 'pattern': None})
|
||||
|
||||
return cs
|
||||
@@ -0,0 +1,210 @@
|
||||
from .database import *
|
||||
from .s0_base import *
|
||||
|
||||
|
||||
VALVES_TYPE_PRV = 'PRV'
|
||||
VALVES_TYPE_PSV = 'PSV'
|
||||
VALVES_TYPE_PBV = 'PBV'
|
||||
VALVES_TYPE_FCV = 'FCV'
|
||||
VALVES_TYPE_TCV = 'TCV'
|
||||
VALVES_TYPE_GPV = 'GPV'
|
||||
|
||||
|
||||
def get_valve_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'node1' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'node2' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'diameter' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'v_type' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'setting' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'minor_loss' : {'type': 'float' , 'optional': False , 'readonly': False} }
|
||||
|
||||
|
||||
def get_valve(name: str, id: str) -> dict[str, Any]:
|
||||
p = try_read(name, f"select * from valves where id = '{id}'")
|
||||
if p == None:
|
||||
return {}
|
||||
d = {}
|
||||
d['id'] = str(p['id'])
|
||||
d['node1'] = str(p['node1'])
|
||||
d['node2'] = str(p['node2'])
|
||||
d['diameter'] = float(p['diameter'])
|
||||
d['v_type'] = str(p['v_type'])
|
||||
d['setting'] = str(p['setting'])
|
||||
d['minor_loss'] = float(p['minor_loss'])
|
||||
return d
|
||||
|
||||
def get_all_valves(name: str) -> list[dict[str, Any]]:
|
||||
rows = read_all(name, f"select * from valves")
|
||||
if rows == None:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
d = {}
|
||||
d['id'] = str(row['id'])
|
||||
d['node1'] = str(row['node1'])
|
||||
d['node2'] = str(row['node2'])
|
||||
d['diameter'] = float(row['diameter'])
|
||||
d['v_type'] = str(row['v_type'])
|
||||
d['setting'] = str(row['setting'])
|
||||
d['minor_loss'] = float(row['minor_loss'])
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
class Valve(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'valve'
|
||||
self.id = str(input['id'])
|
||||
self.node1 = str(input['node1'])
|
||||
self.node2 = str(input['node2'])
|
||||
self.diameter = float(input['diameter'])
|
||||
self.v_type = str(input['v_type'])
|
||||
self.setting = str(input['setting'])
|
||||
self.minor_loss = float(input['minor_loss'])
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_id = f"'{self.id}'"
|
||||
self.f_node1 = f"'{self.node1}'"
|
||||
self.f_node2 = f"'{self.node2}'"
|
||||
self.f_diameter = self.diameter
|
||||
self.f_v_type = f"'{self.v_type}'"
|
||||
self.f_setting = f"'{self.setting}'"
|
||||
self.f_minor_loss = self.minor_loss
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2, 'diameter': self.diameter, 'v_type': self.v_type, 'setting': self.setting, 'minor_loss': self.minor_loss }
|
||||
|
||||
def as_id_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 'id': self.id }
|
||||
|
||||
|
||||
def _set_valve(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Valve(get_valve(name, cs.operations[0]['id']))
|
||||
raw_new = get_valve(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_valve_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Valve(raw_new)
|
||||
|
||||
redo_sql = f"update valves set node1 = {new.f_node1}, node2 = {new.f_node2}, diameter = {new.f_diameter}, v_type = {new.f_v_type}, setting = {new.f_setting}, minor_loss = {new.f_minor_loss} where id = {new.f_id};"
|
||||
undo_sql = f"update valves set node1 = {old.f_node1}, node2 = {old.f_node2}, diameter = {old.f_diameter}, v_type = {old.f_v_type}, setting = {old.f_setting}, minor_loss = {old.f_minor_loss} where id = {old.f_id};"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_valve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_valve(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_valve(name, cs))
|
||||
|
||||
|
||||
def _add_valve(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
new = Valve(cs.operations[0])
|
||||
|
||||
redo_sql = f"insert into _link (id, type) values ({new.f_id}, {new.f_type});"
|
||||
redo_sql += f"\ninsert into valves (id, node1, node2, diameter, v_type, setting, minor_loss) values ({new.f_id}, {new.f_node1}, {new.f_node2}, {new.f_diameter}, {new.f_v_type}, {new.f_setting}, {new.f_minor_loss});"
|
||||
|
||||
undo_sql = f"delete from valves where id = {new.f_id};"
|
||||
undo_sql += f"\ndelete from _link where id = {new.f_id};"
|
||||
|
||||
redo_cs = g_add_prefix | new.as_dict()
|
||||
undo_cs = g_delete_prefix | new.as_id_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def add_valve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_valve(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_valve(name, cs))
|
||||
|
||||
|
||||
def _delete_valve(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Valve(get_valve(name, cs.operations[0]['id']))
|
||||
|
||||
redo_sql = f"delete from valves where id = {old.f_id};"
|
||||
redo_sql += f"\ndelete from _link where id = {old.f_id};"
|
||||
|
||||
undo_sql = f"insert into _link (id, type) values ({old.f_id}, {old.f_type});"
|
||||
undo_sql += f"\ninsert into valves (id, node1, node2, diameter, v_type, setting, minor_loss) values ({old.f_id}, {old.f_node1}, {old.f_node2}, {old.f_diameter}, {old.f_v_type}, {old.f_setting}, {old.f_minor_loss});"
|
||||
|
||||
redo_cs = g_delete_prefix | old.as_id_dict()
|
||||
undo_cs = g_add_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def delete_valve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_valve(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_valve(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# id node1 node2 diam type setting (lcoeff lcurve)
|
||||
# for GPV, setting is string = head curve id
|
||||
# [NOT SUPPORT] for PCV, add loss curve if present
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_valve(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
node1 = str(tokens[1])
|
||||
node2 = str(tokens[2])
|
||||
diameter = float(tokens[3])
|
||||
v_type = str(tokens[4].upper())
|
||||
setting = str(tokens[5])
|
||||
minor_loss = float(tokens[6]) if len(tokens) >= 7 else 0.0
|
||||
desc = str(tokens[-1]) if has_desc else None
|
||||
|
||||
return str(f"insert into _link (id, type) values ('{id}', 'valve');insert into valves (id, node1, node2, diameter, v_type, setting, minor_loss) values ('{id}', '{node1}', '{node2}', {diameter}, '{v_type}', '{setting}', {minor_loss});")
|
||||
|
||||
|
||||
def inp_out_valve(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from valves')
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
node1 = obj['node1']
|
||||
node2 = obj['node2']
|
||||
diameter = obj['diameter']
|
||||
v_type = obj['v_type']
|
||||
setting = obj['setting']
|
||||
minor_loss = obj['minor_loss']
|
||||
desc = ';'
|
||||
lines.append(f'{id} {node1} {node2} {diameter} {v_type} {setting} {minor_loss} {desc}')
|
||||
return lines
|
||||
|
||||
|
||||
'''def delete_valve_by_node(name: str, node: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select id from valves where node1 = '{node}' or node2 = '{node}'")
|
||||
for row in rows:
|
||||
cs.append(g_delete_prefix | {'type': 'valve', 'id': row['id']})
|
||||
|
||||
return cs'''
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
from typing import Any
|
||||
from .database import ChangeSet, execute_command, try_read, read_all, DbChangeSet, g_update_prefix
|
||||
|
||||
TAG_TYPE_NODE = 'NODE'
|
||||
TAG_TYPE_LINK = 'LINK'
|
||||
|
||||
def get_tag_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 't_type' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'id' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'tag' : {'type': 'str' , 'optional': True , 'readonly': False},}
|
||||
|
||||
|
||||
def get_tags(name: str) -> list[dict[str, Any]]:
|
||||
results: list[dict[str, Any]] = []
|
||||
rows = read_all(name, "select * from tags_node")
|
||||
for row in rows:
|
||||
tag = str(row['tag']) if row['tag'] != None else None
|
||||
results.append({ 't_type': TAG_TYPE_NODE, 'id': str(row['id']), 'tag': tag })
|
||||
rows = read_all(name, "select * from tags_link")
|
||||
for row in rows:
|
||||
tag = str(row['tag']) if row['tag'] != None else None
|
||||
results.append({ 't_type': TAG_TYPE_LINK, 'id': str(row['id']), 'tag': tag })
|
||||
return results
|
||||
|
||||
|
||||
def get_tag(name: str, t_type: str, id: str) -> dict[str, Any]:
|
||||
t = None
|
||||
if t_type == TAG_TYPE_NODE:
|
||||
t = try_read(name, f"select * from tags_node where id = '{id}'")
|
||||
elif t_type == TAG_TYPE_LINK:
|
||||
t = try_read(name, f"select * from tags_link where id = '{id}'")
|
||||
if t is None:
|
||||
return { 't_type': t_type, 'id': id, 'tag': None }
|
||||
d = {}
|
||||
d['t_type'] = t_type
|
||||
d['id'] = str(t['id'])
|
||||
d['tag'] = str(t['tag']) if t['tag'] is not None else None
|
||||
return d
|
||||
|
||||
|
||||
class Tag(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'tag'
|
||||
self.t_type = str(input['t_type'])
|
||||
self.id = str(input['id'])
|
||||
self.tag = str(input['tag']) if 'tag' in input and input['tag'] != None else None
|
||||
|
||||
self.f_type = f"'{self.type}'"
|
||||
self.f_t_type = f"'{self.t_type}'"
|
||||
self.f_id = f"'{self.id}'"
|
||||
self.f_tag = f"'{self.tag}'" if self.tag != None else 'null'
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return { 'type': self.type, 't_type': self.t_type, 'id': self.id, 'tag': self.tag }
|
||||
|
||||
|
||||
def _set_tag(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
old = Tag(get_tag(name, cs.operations[0]['t_type'], cs.operations[0]['id']))
|
||||
raw_new = get_tag(name, cs.operations[0]['t_type'], cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_tag_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Tag(raw_new)
|
||||
|
||||
table = ''
|
||||
if cs.operations[0]['t_type'] == TAG_TYPE_NODE:
|
||||
table = 'tags_node'
|
||||
elif cs.operations[0]['t_type'] == TAG_TYPE_LINK:
|
||||
table = 'tags_link'
|
||||
else:
|
||||
raise Exception('Only support NODE and Link')
|
||||
|
||||
redo_sql = f"delete from {table} where id = {new.f_id};"
|
||||
if new.tag is not None:
|
||||
redo_sql += f"\ninsert into {table} (id, tag) values ({new.f_id}, {new.f_tag});"
|
||||
|
||||
undo_sql = f"delete from {table} where id = {old.f_id};"
|
||||
if old.tag is not None:
|
||||
undo_sql += f"\ninsert into {table} (id, tag) values ({old.f_id}, {old.f_tag});"
|
||||
|
||||
redo_cs = g_update_prefix | new.as_dict()
|
||||
undo_cs = g_update_prefix | old.as_dict()
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_tag(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 't_type' not in cs.operations[0] or 'id' not in cs.operations[0] or 'tag' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_tag(name, cs))
|
||||
|
||||
|
||||
def inp_in_tag(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
t_type = str(tokens[0].upper())
|
||||
id = str(tokens[1])
|
||||
tag = str(tokens[2])
|
||||
|
||||
if t_type == TAG_TYPE_NODE:
|
||||
return str(f"insert into tags_node (id, tag) values ('{id}', '{tag}');")
|
||||
elif t_type == TAG_TYPE_LINK:
|
||||
return str(f"insert into tags_link (id, tag) values ('{id}', '{tag}');")
|
||||
return str('')
|
||||
|
||||
|
||||
def inp_out_tag(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select * from tags_node')
|
||||
for obj in objs:
|
||||
t_type = TAG_TYPE_NODE
|
||||
id = obj['id']
|
||||
tag = obj['tag']
|
||||
lines.append(f'{t_type} {id} {tag}')
|
||||
objs = read_all(name, 'select * from tags_link')
|
||||
for obj in objs:
|
||||
t_type = TAG_TYPE_LINK
|
||||
id = obj['id']
|
||||
tag = obj['tag']
|
||||
lines.append(f'{t_type} {id} {tag}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_tag_by_node(name: str, node: str) -> ChangeSet:
|
||||
row = try_read(name, f"select * from tags_node where id = '{node}'")
|
||||
if row is None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_update_prefix | {'type': 'tag', 't_type': TAG_TYPE_NODE, 'id': node, 'tag': None })
|
||||
|
||||
|
||||
def delete_tag_by_link(name: str, link: str) -> ChangeSet:
|
||||
row = try_read(name, f"select * from tags_link where id = '{link}'")
|
||||
if row is None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_update_prefix | {'type': 'tag', 't_type': TAG_TYPE_LINK, 'id': link, 'tag': None })
|
||||
@@ -0,0 +1,115 @@
|
||||
from .database import read_all, ChangeSet, DbChangeSet, g_update_prefix, execute_command, try_read
|
||||
from typing import Any
|
||||
|
||||
def get_demand_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'junction' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'demands' : {'type': 'list' , 'optional': False , 'readonly': False,
|
||||
'element': { 'demand' : {'type': 'float' , 'optional': False , 'readonly': False },
|
||||
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False },
|
||||
'category': {'type': 'str' , 'optional': True , 'readonly': False }}}}
|
||||
|
||||
|
||||
def get_demand(name: str, junction: str) -> dict[str, Any]:
|
||||
des = read_all(name, f"select * from demands where junction = '{junction}' order by _order")
|
||||
ds = []
|
||||
for r in des:
|
||||
d = {}
|
||||
d['demand'] = float(r['demand'])
|
||||
d['pattern'] = str(r['pattern']) if r['pattern'] != None else None
|
||||
d['category'] = str(r['category']) if r['category'] != None else None
|
||||
ds.append(d)
|
||||
return { 'junction': junction, 'demands': ds }
|
||||
|
||||
|
||||
def _set_demand(name: str, cs: ChangeSet) -> DbChangeSet:
|
||||
junction = cs.operations[0]['junction']
|
||||
old = get_demand(name, junction)
|
||||
new = { 'junction': junction, 'demands': [] }
|
||||
|
||||
f_junction = f"'{junction}'"
|
||||
|
||||
# TODO: transaction ?
|
||||
redo_sql = f"delete from demands where junction = {f_junction};"
|
||||
for r in cs.operations[0]['demands']:
|
||||
demand = float(r['demand'])
|
||||
pattern = str(r['pattern']) if 'pattern' in r and r['pattern'] != None else None
|
||||
category = str(r['category']) if 'category' in r and r['category'] != None else None
|
||||
f_demand = demand
|
||||
f_pattern = f"'{pattern}'" if pattern is not None else 'null'
|
||||
f_category = f"'{category}'" if category is not None else 'null'
|
||||
redo_sql += f"\ninsert into demands (junction, demand, pattern, category) values ({f_junction}, {f_demand}, {f_pattern}, {f_category});"
|
||||
new['demands'].append({ 'demand': demand, 'pattern': pattern, 'category': category })
|
||||
|
||||
undo_sql = f"delete from demands where junction = {f_junction};"
|
||||
for r in old['demands']:
|
||||
demand = float(r['demand'])
|
||||
pattern = str(r['pattern']) if 'pattern' in r and r['pattern'] != None else None
|
||||
category = str(r['category']) if 'category' in r and r['category'] != None else None
|
||||
f_demand = demand
|
||||
f_pattern = f"'{pattern}'" if pattern is not None else 'null'
|
||||
f_category = f"'{category}'" if category is not None else 'null'
|
||||
undo_sql += f"\ninsert into demands (junction, demand, pattern, category) values ({f_junction}, {f_demand}, {f_pattern}, {f_category});"
|
||||
|
||||
redo_cs = g_update_prefix | { 'type': 'demand' } | new
|
||||
undo_cs = g_update_prefix | { 'type': 'demand' } | old
|
||||
|
||||
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
|
||||
|
||||
|
||||
def set_demand(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_demand(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# node base_demand (pattern) ;category
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_demand(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
junction = str(tokens[0])
|
||||
demand = float(tokens[1])
|
||||
pattern = str(tokens[2]) if num_without_desc >= 3 else None
|
||||
pattern = f"'{pattern}'" if pattern is not None else 'null'
|
||||
category = str(tokens[3]) if num_without_desc >= 4 else None
|
||||
category = f"'{category}'" if category is not None else 'null'
|
||||
|
||||
return str(f"insert into demands (junction, demand, pattern, category) values ('{junction}', {demand}, {pattern}, {category});")
|
||||
|
||||
|
||||
def inp_out_demand(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, "select * from demands order by _order")
|
||||
for obj in objs:
|
||||
junction = obj['junction']
|
||||
demand = obj['demand']
|
||||
pattern = obj['pattern'] if obj['pattern'] is not None else ''
|
||||
category = f";{obj['category']}" if obj['category'] is not None else ';'
|
||||
lines.append(f'{junction} {demand} {pattern} {category}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_demand_by_junction(name: str, junction: str) -> ChangeSet:
|
||||
row = try_read(name, f"select * from demands where junction = '{junction}'")
|
||||
if row is None:
|
||||
return ChangeSet()
|
||||
return ChangeSet(g_update_prefix | {'type': 'demand', 'junction': junction, 'demands': []})
|
||||
|
||||
|
||||
def unset_demand_by_pattern(name: str, pattern: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
rows = read_all(name, f"select distinct junction from demands where pattern = '{pattern}'")
|
||||
for row in rows:
|
||||
ds = get_demand(name, row['junction'])
|
||||
for d in ds['demands']:
|
||||
d['pattern'] = None
|
||||
cs.append(g_update_prefix | {'type': 'demand', 'junction': row['junction'], 'demands': ds['demands']})
|
||||
|
||||
return cs
|
||||
@@ -0,0 +1,90 @@
|
||||
s1_title = 'title'
|
||||
s2_junction = 'junction'
|
||||
s3_reservoir = 'reservoir'
|
||||
s4_tank = 'tank'
|
||||
s5_pipe = 'pipe'
|
||||
s6_pump = 'pump'
|
||||
s7_valve = 'valve'
|
||||
s8_tag = 'tag'
|
||||
s9_demand = 'demand'
|
||||
s10_status = 'status'
|
||||
s11_pattern = 'pattern'
|
||||
s12_curve = 'curve'
|
||||
s13_control = 'control'
|
||||
s14_rule = 'rule'
|
||||
s15_energy = 'energy'
|
||||
s15_pump_energy = 'pump_energy'
|
||||
s16_emitter = 'emitter'
|
||||
s17_quality = 'quality'
|
||||
s18_source = 'source'
|
||||
s19_reaction = 'reaction'
|
||||
s19_pipe_reaction = 'pipe_reaction'
|
||||
s19_tank_reaction = 'tank_reaction'
|
||||
s20_mixing = 'mixing'
|
||||
s21_time = 'time'
|
||||
s22_report = 'report'
|
||||
s23_option = 'option'
|
||||
s23_option_v3 = 'option_v3'
|
||||
s24_coordinate = 'coordinate'
|
||||
s25_vertex = 'vertex'
|
||||
s26_label = 'label'
|
||||
s27_backdrop = 'backdrop'
|
||||
s28_end = 'end'
|
||||
s29_scada_device = 'scada_device'
|
||||
s30_scada_device_data = 'scada_device_data'
|
||||
s31_scada_element = 'scada_element'
|
||||
s32_region = 'region'
|
||||
s33_dma = 'district_metering_area'
|
||||
s34_sa = 'service_area'
|
||||
s35_vd = 'virtual_district'
|
||||
|
||||
TITLE = 'TITLE'
|
||||
JUNCTIONS = 'JUNCTIONS'
|
||||
RESERVOIRS = 'RESERVOIRS'
|
||||
TANKS = 'TANKS'
|
||||
PIPES = 'PIPES'
|
||||
PUMPS = 'PUMPS'
|
||||
VALVES = 'VALVES'
|
||||
TAGS = 'TAGS'
|
||||
DEMANDS = 'DEMANDS'
|
||||
STATUS = 'STATUS'
|
||||
PATTERNS = 'PATTERNS'
|
||||
CURVES = 'CURVES'
|
||||
CONTROLS = 'CONTROLS'
|
||||
RULES = 'RULES'
|
||||
ENERGY = 'ENERGY'
|
||||
EMITTERS = 'EMITTERS'
|
||||
QUALITY = 'QUALITY'
|
||||
SOURCES = 'SOURCES'
|
||||
REACTIONS = 'REACTIONS'
|
||||
MIXING = 'MIXING'
|
||||
TIMES = 'TIMES'
|
||||
REPORT = 'REPORT'
|
||||
OPTIONS = 'OPTIONS'
|
||||
COORDINATES = 'COORDINATES'
|
||||
VERTICES = 'VERTICES'
|
||||
REGION='REGION'
|
||||
BOUND='BOUND'
|
||||
REGION_NODES='DATA_NODE_OF_REGION'
|
||||
LABELS = 'LABELS'
|
||||
BACKDROP = 'BACKDROP'
|
||||
END = 'END'
|
||||
|
||||
section_name = [TITLE, JUNCTIONS, RESERVOIRS, TANKS, PIPES,
|
||||
PUMPS, VALVES, TAGS, DEMANDS, STATUS,
|
||||
PATTERNS, CURVES, CONTROLS, RULES, ENERGY,
|
||||
EMITTERS, QUALITY, SOURCES, REACTIONS, MIXING,
|
||||
TIMES, REPORT, OPTIONS, COORDINATES, VERTICES,
|
||||
REGION, BOUND, REGION_NODES, LABELS, BACKDROP, END]
|
||||
|
||||
# DingZQ, 2025-02-04
|
||||
# 我们在从服务器调用run_project的时候
|
||||
# 会将 database的project内容dump成 epanet v2 的inp文件,然后调用 runepanet.exe 去计算结果
|
||||
# 其中上面的 SECTION : REGION, BOUND, REGION_NODES 在 epanet v2 中没有,是我们自己定制的
|
||||
# 所以需要将这些 section 从 section_name 中移除
|
||||
section_names_for_epanetv2 = [TITLE, JUNCTIONS, RESERVOIRS, TANKS, PIPES,
|
||||
PUMPS, VALVES, TAGS, DEMANDS, STATUS,
|
||||
PATTERNS, CURVES, CONTROLS, RULES, ENERGY,
|
||||
EMITTERS, QUALITY, SOURCES, REACTIONS, MIXING,
|
||||
TIMES, REPORT, OPTIONS, COORDINATES, VERTICES,
|
||||
LABELS, BACKDROP, END]
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
import wntr
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import sklearn.cluster
|
||||
import os
|
||||
|
||||
|
||||
|
||||
class QD_KMeans(object):
|
||||
def __init__(self, wn, num_monitors):
|
||||
# self.inp = inp
|
||||
self.cluster_num = num_monitors # 聚类中心个数,也即测压点个数
|
||||
self.wn=wn
|
||||
self.monitor_nodes = []
|
||||
self.coords = []
|
||||
self.junction_nodes = {} # Added missing initialization
|
||||
|
||||
|
||||
def get_junctions_coordinates(self):
|
||||
|
||||
for junction_name in self.wn.junction_name_list:
|
||||
junction = self.wn.get_node(junction_name)
|
||||
self.junction_nodes[junction_name] = junction.coordinates
|
||||
self.coords.append(junction.coordinates )
|
||||
|
||||
# print(f"Total junctions: {self.junction_coordinates}")
|
||||
|
||||
def select_monitoring_points(self):
|
||||
if not self.coords: # Add check if coordinates are collected
|
||||
self.get_junctions_coordinates()
|
||||
coords = np.array(self.coords)
|
||||
coords_normalized = (coords - coords.min(axis=0)) / (coords.max(axis=0) - coords.min(axis=0))
|
||||
kmeans = sklearn.cluster.KMeans(n_clusters= self.cluster_num, random_state=42)
|
||||
kmeans.fit(coords_normalized)
|
||||
|
||||
for center in kmeans.cluster_centers_:
|
||||
distances = np.sum((coords_normalized - center) ** 2, axis=1)
|
||||
nearest_node = self.wn.junction_name_list[np.argmin(distances)]
|
||||
self.monitor_nodes.append(nearest_node)
|
||||
|
||||
return self.monitor_nodes
|
||||
|
||||
|
||||
def visualize_network(self):
|
||||
"""Visualize network with monitoring points"""
|
||||
ax=wntr.graphics.plot_network(self.wn,
|
||||
node_attribute=self.monitor_nodes,
|
||||
node_size=30,
|
||||
title='Optimal sensor')
|
||||
plt.show()
|
||||
|
||||
|
||||
|
||||
|
||||
def kmeans_sensor_placement(name: str, sensor_num: int, min_diameter: int) -> list:
|
||||
inp_name = f'./db_inp/{name}.db.inp'
|
||||
wn= wntr.network.WaterNetworkModel(inp_name)
|
||||
wn_cluster=QD_KMeans(wn, sensor_num)
|
||||
|
||||
# Select monitoring pointse
|
||||
sensor_ids= wn_cluster.select_monitoring_points()
|
||||
|
||||
# wn_cluster.visualize_network()
|
||||
|
||||
return sensor_ids
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#sensorindex = get_ID(name='suzhouhe_2024_cloud_0817', sensor_num=30, min_diameter=500)
|
||||
sensorindex = kmeans_sensor_placement(name='szh', sensor_num=50, min_diameter=300)
|
||||
print(sensorindex)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
import schedule
|
||||
import time
|
||||
import datetime
|
||||
import shutil
|
||||
import redis
|
||||
import urllib.request
|
||||
import influxdb_api
|
||||
import msgpack
|
||||
import datetime
|
||||
|
||||
# 将 Query的信息 序列号到 redis/json, 默认不支持datetime,需要自定义
|
||||
# 自定义序列化函数
|
||||
# 序列化处理器
|
||||
def encode_datetime(obj):
|
||||
"""将datetime转换为可序列化的字典结构"""
|
||||
if isinstance(obj, datetime.datetime):
|
||||
return {
|
||||
'__datetime__': True,
|
||||
'as_str': obj.strftime("%Y%m%dT%H:%M:%S.%f")
|
||||
}
|
||||
return obj
|
||||
|
||||
# 反序列化处理器
|
||||
def decode_datetime(obj):
|
||||
"""将字典还原为datetime对象"""
|
||||
if '__datetime__' in obj:
|
||||
return datetime.datetime.strptime(
|
||||
obj['as_str'], "%Y%m%dT%H:%M:%S.%f"
|
||||
)
|
||||
return obj
|
||||
|
||||
##########################
|
||||
# 需要用Python 3.12 来运行才能提高performance
|
||||
##########################
|
||||
|
||||
def queryallrecordsbydate(querydate: str, redis_client: redis.Redis):
|
||||
cache_key = f"queryallrecordsbydate_{querydate}"
|
||||
exists = redis_client.exists(cache_key)
|
||||
|
||||
if not exists:
|
||||
nodes_links: tuple = influxdb_api.query_all_records_by_date(query_date=querydate)
|
||||
redis_client.set(cache_key, msgpack.packb(nodes_links, default=encode_datetime))
|
||||
|
||||
def queryallrecordsbydate_by_url(querydate: str):
|
||||
print(f'queryallrecordsbydate: {querydate}')
|
||||
|
||||
try:
|
||||
response = urllib.request.urlopen(
|
||||
f"http://localhost/queryallrecordsbydate/?querydate={querydate}"
|
||||
)
|
||||
html = response.read().decode("utf-8")
|
||||
|
||||
except urllib.error.URLError as e:
|
||||
print("Error")
|
||||
|
||||
def queryallscadarecordsbydate(querydate: str, redis_client: redis.Redis):
|
||||
cache_key = f"queryallscadarecordsbydate_{querydate}"
|
||||
exists = redis_client.exists(cache_key)
|
||||
|
||||
if not exists:
|
||||
result_dict = influxdb_api.query_all_SCADA_records_by_date(query_date=querydate)
|
||||
redis_client.set(cache_key, msgpack.packb(result_dict, default=encode_datetime))
|
||||
|
||||
def queryallscadarecordsbydate_by_url(querydate: str):
|
||||
print(f'queryallscadarecordsbydate: {querydate}')
|
||||
|
||||
try:
|
||||
response = urllib.request.urlopen(
|
||||
f"http://localhost/queryallscadarecordsbydate/?querydate={querydate}"
|
||||
)
|
||||
html = response.read().decode("utf-8")
|
||||
|
||||
except urllib.error.URLError as e:
|
||||
print("Error")
|
||||
|
||||
|
||||
def auto_cache_data():
|
||||
# 初始化 Redis 连接
|
||||
# 用redis 限制并发访u
|
||||
redis_client = redis.Redis(host="localhost", port=6379, db=0)
|
||||
|
||||
# auto cache data for the last 3 days
|
||||
today = datetime.date.today()
|
||||
for i in range(1, 4):
|
||||
prev_day = today - datetime.timedelta(days=i)
|
||||
str_prev_day = prev_day.strftime('%Y-%m-%d')
|
||||
print(str_prev_day)
|
||||
|
||||
queryallrecordsbydate(str_prev_day, redis_client)
|
||||
queryallscadarecordsbydate(str_prev_day, redis_client)
|
||||
|
||||
redis_client.close()
|
||||
|
||||
def auto_cache_data_by_url():
|
||||
# auto cache data for the last 3 days
|
||||
today = datetime.date.today()
|
||||
for i in range(1, 4):
|
||||
prev_day = today - datetime.timedelta(days=i)
|
||||
str_prev_day = prev_day.strftime('%Y-%m-%d')
|
||||
print(str_prev_day)
|
||||
|
||||
queryallrecordsbydate_by_url(str_prev_day)
|
||||
queryallscadarecordsbydate_by_url(str_prev_day)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
auto_cache_data_by_url()
|
||||
|
||||
# auto run in the midnight
|
||||
schedule.every().day.at("03:00").do(auto_cache_data_by_url)
|
||||
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,156 @@
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
import influxdb_api
|
||||
import os
|
||||
import logging
|
||||
import globals
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import schedule
|
||||
import time
|
||||
import shutil
|
||||
from influxdb_client import InfluxDBClient, BucketsApi, WriteApi, OrganizationsApi, Point, QueryApi
|
||||
import simulation
|
||||
import influxdb_info
|
||||
import project_info
|
||||
|
||||
def setup_logger():
|
||||
# 创建日志目录
|
||||
log_dir = "logs"
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
# 配置基础日志格式
|
||||
log_format = "%(asctime)s - %(levelname)s - %(message)s"
|
||||
formatter = logging.Formatter(log_format)
|
||||
|
||||
# 创建主 Logger
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO) # 全局日志级别
|
||||
|
||||
# --- 1. 按日期分割的日志文件 Handler ---
|
||||
log_file = os.path.join(log_dir, "simulation.log")
|
||||
file_handler = TimedRotatingFileHandler(
|
||||
filename=log_file,
|
||||
when="midnight", # 每天午夜轮转
|
||||
interval=1,
|
||||
backupCount=7,
|
||||
encoding="utf-8"
|
||||
)
|
||||
file_handler.suffix = "simulation-%Y-%m-%d.log" # 文件名格式
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.setLevel(logging.INFO) # 文件记录所有级别日志
|
||||
|
||||
# --- 2. 控制台实时输出 Handler ---
|
||||
console_handler = logging.StreamHandler() # 默认输出到 sys.stderr (控制台)
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.setLevel(logging.INFO) # 控制台仅显示 INFO 及以上级别
|
||||
|
||||
# 将 Handler 添加到 Logger
|
||||
logger.addHandler(file_handler)
|
||||
#logger.addHandler(console_handler)
|
||||
|
||||
return logger
|
||||
|
||||
logger = setup_logger()
|
||||
|
||||
# 2025/02/01
|
||||
def get_next_time() -> str:
|
||||
"""
|
||||
获取下一个1分钟时间点,返回格式为字符串'YYYY-MM-DDTHH:MM:00+08:00'
|
||||
:return: 返回字符串格式的时间,表示下一个1分钟的时间点
|
||||
"""
|
||||
# 获取当前时间,并设定为北京时间
|
||||
now = datetime.now() # now 类型为 datetime,表示当前本地时间
|
||||
# 获取当前的分钟,并且将秒和微秒置为零
|
||||
current_time = now.replace(second=0, microsecond=0) # current_time 类型为 datetime,时间的秒和微秒部分被清除
|
||||
return current_time.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
||||
|
||||
|
||||
# 2025/02/06
|
||||
def store_realtime_SCADA_data_job() -> None:
|
||||
"""
|
||||
定义的任务1,每分钟执行1次,每次执行时,更新get_real_value_time并调用store_realtime_SCADA_data_to_influxdb函数
|
||||
:return: None
|
||||
"""
|
||||
# 获取当前时间并更新get_real_value_time,转换为字符串格式
|
||||
get_real_value_time: str = get_next_time() # get_real_value_time 类型为 str,格式为'2025-02-01T18:45:00+08:00'
|
||||
# 调用函数执行任务
|
||||
influxdb_api.store_realtime_SCADA_data_to_influxdb(get_real_value_time)
|
||||
logger.info('{} -- Successfully store realtime SCADA data.'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
|
||||
|
||||
|
||||
# 2025/02/06
|
||||
def get_next_15minute_time() -> str:
|
||||
"""
|
||||
获取下一个15分钟的时间点,返回格式为字符串'YYYY-MM-DDTHH:MM:00+08:00'
|
||||
:return: 返回字符串格式的时间,表示下一个15分钟执行时间点
|
||||
"""
|
||||
now = datetime.now()
|
||||
# 向上舍入到下一个15分钟
|
||||
next_15minute = (now.minute // 15 + 1) * 15 - 15
|
||||
if next_15minute == 60:
|
||||
next_15minute = 0
|
||||
now = now + timedelta(hours=1)
|
||||
next_time = now.replace(minute=next_15minute, second=0, microsecond=0)
|
||||
return next_time.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
||||
|
||||
|
||||
# 2025/02/07
|
||||
def run_simulation_job() -> None:
|
||||
"""
|
||||
定义的任务3,每15分钟执行一次在store_realtime_SCADA_data_to_influxdb之后执行run_simulation。
|
||||
:return: None
|
||||
"""
|
||||
# 获取当前时间,并检查是否是整点15分钟
|
||||
current_time = datetime.now()
|
||||
if current_time.minute % 15 == 0:
|
||||
print(f"{current_time.strftime('%Y-%m-%d %H:%M:%S')} -- Start simulation task.")
|
||||
# 计算前,获取scada_info中的信息,按照设定的方法修改pg数据库
|
||||
simulation.query_corresponding_element_id_and_query_id(project_info.name)
|
||||
simulation.query_corresponding_pattern_id_and_query_id(project_info.name)
|
||||
region_result = simulation.query_non_realtime_region(project_info.name)
|
||||
globals.source_outflow_region_id = simulation.get_source_outflow_region_id(project_info.name, region_result)
|
||||
globals.realtime_region_pipe_flow_and_demand_id = simulation.query_realtime_region_pipe_flow_and_demand_id(project_info.name, region_result)
|
||||
globals.pipe_flow_region_patterns = simulation.query_pipe_flow_region_patterns(project_info.name)
|
||||
globals.non_realtime_region_patterns = simulation.query_non_realtime_region_patterns(project_info.name, region_result)
|
||||
globals.source_outflow_region_patterns, realtime_region_pipe_flow_and_demand_patterns = simulation.get_realtime_region_patterns(project_info.name,
|
||||
globals.source_outflow_region_id,
|
||||
globals.realtime_region_pipe_flow_and_demand_id)
|
||||
modify_pattern_start_time: str = get_next_15minute_time() # 获取下一个15分钟时间点
|
||||
# print(modify_pattern_start_time)
|
||||
simulation.run_simulation(name=project_info.name, simulation_type="realtime", modify_pattern_start_time=modify_pattern_start_time)
|
||||
|
||||
logger.info('{} -- Successfully run simulation and store realtime simulation result.'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
|
||||
else:
|
||||
logger.info(f"{current_time.strftime('%Y-%m-%d %H:%M:%S')} -- Skipping the simulation task.")
|
||||
|
||||
|
||||
# 2025/02/06
|
||||
def realtime_task() -> None:
|
||||
"""
|
||||
定时执行任务1和,使用schedule库每1分钟执行一次store_realtime_SCADA_data_job函数。
|
||||
该任务会一直运行,定期调用store_realtime_SCADA_data_job获取SCADA数据。
|
||||
:return:
|
||||
"""
|
||||
# 等待到整分对齐
|
||||
now = datetime.now()
|
||||
wait_seconds = 60 - now.second
|
||||
time.sleep(wait_seconds)
|
||||
# 使用 .at(":00") 指定在每分钟的第0秒执行
|
||||
schedule.every(1).minute.at(":00").do(store_realtime_SCADA_data_job)
|
||||
# 每15分钟执行一次run_simulation_job
|
||||
schedule.every(1).minute.at(":00").do(run_simulation_job)
|
||||
# 持续执行任务,检查是否有待执行的任务
|
||||
while True:
|
||||
schedule.run_pending() # 执行所有待处理的定时任务
|
||||
time.sleep(1) # 暂停1秒,避免过于频繁的任务检查
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
url = influxdb_info.url
|
||||
token = influxdb_info.token
|
||||
org_name = influxdb_info.org
|
||||
|
||||
client = InfluxDBClient(url=url, token=token)
|
||||
# step2: 先查询pg数据库中scada_info的信息,然后存储SCADA数据到SCADA_data这个bucket里
|
||||
influxdb_api.query_pg_scada_info_realtime(project_info.name)
|
||||
# 自动执行
|
||||
realtime_task()
|
||||
@@ -0,0 +1,139 @@
|
||||
import influxdb_api
|
||||
import globals
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import schedule
|
||||
import os
|
||||
import logging
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
import time
|
||||
from influxdb_client import InfluxDBClient, BucketsApi, WriteApi, OrganizationsApi, Point, QueryApi
|
||||
import influxdb_info
|
||||
import project_info
|
||||
|
||||
def setup_logger():
|
||||
# 创建日志目录
|
||||
log_dir = "logs"
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
# 配置基础日志格式
|
||||
log_format = "%(asctime)s - %(levelname)s - %(message)s"
|
||||
formatter = logging.Formatter(log_format)
|
||||
|
||||
# 创建主 Logger
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO) # 全局日志级别
|
||||
|
||||
# --- 1. 按日期分割的日志文件 Handler ---
|
||||
log_file = os.path.join(log_dir, "scada.log")
|
||||
file_handler = TimedRotatingFileHandler(
|
||||
filename=log_file,
|
||||
when="midnight", # 每天午夜轮转
|
||||
interval=1,
|
||||
backupCount=7,
|
||||
encoding="utf-8"
|
||||
)
|
||||
file_handler.suffix = "scada-%Y-%m-%d.log" # 文件名格式
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.setLevel(logging.INFO) # 文件记录 INFO 及以上级别
|
||||
|
||||
# --- 2. 控制台实时输出 Handler ---
|
||||
console_handler = logging.StreamHandler() # 默认输出到 sys.stderr (控制台)
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.setLevel(logging.INFO) # 控制台仅显示 INFO 及以上级别
|
||||
|
||||
# 将 Handler 添加到 Logger
|
||||
logger.addHandler(file_handler)
|
||||
# logger.addHandler(console_handler)
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
logger = setup_logger()
|
||||
|
||||
# 2025/02/01
|
||||
def get_next_time() -> str:
|
||||
"""
|
||||
获取下一个1分钟时间点,返回格式为字符串'YYYY-MM-DDTHH:MM:00+08:00'
|
||||
:return: 返回字符串格式的时间,表示下一个1分钟的时间点
|
||||
"""
|
||||
# 获取当前时间,并设定为北京时间
|
||||
now = datetime.now() # now 类型为 datetime,表示当前本地时间
|
||||
# 获取当前的分钟,并且将秒和微秒置为零
|
||||
current_time = now.replace(second=0, microsecond=0) # current_time 类型为 datetime,时间的秒和微秒部分被清除
|
||||
return current_time.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
||||
|
||||
|
||||
# 2025/02/06
|
||||
def get_next_period_time() -> str:
|
||||
"""
|
||||
获取下一个6小时时间点,返回格式为字符串'YYYY-MM-DDTHH:00:00+08:00'
|
||||
:return: 返回字符串格式的时间,表示下一个6小时执行时间点
|
||||
"""
|
||||
# 获取当前时间,并设定为北京时间
|
||||
now = datetime.now() # now 类型为 datetime,表示当前本地时间
|
||||
# 获取当前的小时数并计算下一个6小时时间点
|
||||
next_period_hour = (now.hour // 6 + 1) * 6 - 6 # next_period_hour 类型为 int,表示下一个6小时时间点的小时部分
|
||||
# 如果计算的小时大于23,表示进入第二天,调整为00:00
|
||||
if next_period_hour >= 24:
|
||||
next_period_hour = 0
|
||||
now = now + timedelta(days=1) # 如果超过24小时,日期增加1天
|
||||
# 将秒和微秒部分清除,构建出下一个6小时点的datetime对象
|
||||
next_period_time = now.replace(hour=next_period_hour, minute=0, second=0, microsecond=0)
|
||||
return next_period_time.strftime('%Y-%m-%dT%H:%M:%S+08:00') # 格式化为指定的字符串格式并返回
|
||||
|
||||
|
||||
# 2025/02/06
|
||||
def store_non_realtime_SCADA_data_job() -> None:
|
||||
"""
|
||||
定义的任务2,每6小时执行一次,在0点、6点、12点、18点执行,执行时,更新get_history_data_end_time并调用store_non_realtime_SCADA_data_to_influxdb函数
|
||||
:return: None
|
||||
"""
|
||||
# 获取当前时间
|
||||
current_time = datetime.now()
|
||||
# 只在0点、6点、12点、18点执行任务
|
||||
# if current_time.hour % 6 == 0 and current_time.minute == 0:
|
||||
if current_time.minute % 10 == 0:
|
||||
logger.info(f"{current_time.strftime('%Y-%m-%d %H:%M:%S')} -- Start store non realtime SCADA data task.")
|
||||
# 获取下一个6小时的时间点,并更新get_history_data_end_time
|
||||
get_history_data_end_time: str = get_next_time() # get_history_data_end_time 类型为 str,格式为'2025-02-06T12:00:00+08:00'
|
||||
# print(get_next_time)
|
||||
# 调用函数执行任务
|
||||
influxdb_api.store_non_realtime_SCADA_data_to_influxdb(get_history_data_end_time)
|
||||
logger.info('{} -- Successfully store non realtime SCADA data.'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
|
||||
else:
|
||||
logger.info(f"{current_time.strftime('%Y-%m-%d %H:%M:%S')} -- Skipping store non realtime SCADA data task.")
|
||||
|
||||
|
||||
# 2025/02/06
|
||||
def store_non_realtime_SCADA_data_task() -> None:
|
||||
"""
|
||||
定时执行6小时的任务,使用schedule库每分钟执行一次store_non_realtime_SCADA_data_job函数。
|
||||
该任务会一直运行,定期调用store_non_realtime_SCADA_data_job获取SCADA数据。
|
||||
:return:
|
||||
"""
|
||||
# 等待到整分对齐
|
||||
now = datetime.now()
|
||||
wait_seconds = 60 - now.second
|
||||
time.sleep(wait_seconds)
|
||||
try:
|
||||
# 每分钟检查一次,执行store_non_realtime_SCADA_data_job
|
||||
schedule.every(1).minute.at(":00").do(store_non_realtime_SCADA_data_job)
|
||||
# 持续执行任务,检查是否有待执行的任务
|
||||
while True:
|
||||
schedule.run_pending() # 执行所有待处理的定时任务
|
||||
time.sleep(1) # 暂停1秒,避免过于频繁的任务检查
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error(f"Error occurred in store_non_realtime_SCADA_data_task: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
url = influxdb_info.url
|
||||
token = influxdb_info.token
|
||||
org_name = influxdb_info.org
|
||||
|
||||
client = InfluxDBClient(url=url, token=token)
|
||||
# step2: 先查询pg数据库中scada_info的信息,然后存储SCADA数据到SCADA_data这个bucket里
|
||||
influxdb_api.query_pg_scada_info_non_realtime(project_info.name)
|
||||
# 自动执行
|
||||
store_non_realtime_SCADA_data_task()
|
||||
@@ -0,0 +1 @@
|
||||
python build_pyd.py build
|
||||
@@ -0,0 +1,25 @@
|
||||
from distutils.core import setup
|
||||
from Cython.Build import cythonize
|
||||
|
||||
setup(ext_modules=cythonize([
|
||||
"main.py",
|
||||
"auto_realtime.py",
|
||||
"auto_store_non_realtime_SCADA_data.py",
|
||||
"tjnetwork.py",
|
||||
"online_Analysis.py",
|
||||
"sensitivity.py",
|
||||
"run_simlation.py",
|
||||
"run_simulation.py",
|
||||
"get_hist_data.py",
|
||||
"get_realValue.py",
|
||||
"get_data.py",
|
||||
"get_current_total_Q.py",
|
||||
"get_current_status.py",
|
||||
"influxdb_api.py",
|
||||
"influxdb_query_SCADA_data.py",
|
||||
"sensor_placement.py",
|
||||
"simulation.py",
|
||||
"time_api.py",
|
||||
"api/*.py",
|
||||
"epanet/*.py"
|
||||
]))
|
||||
@@ -0,0 +1,6 @@
|
||||
from distutils.core import setup
|
||||
from Cython.Build import cythonize
|
||||
|
||||
setup(ext_modules=cythonize([
|
||||
"api/project.py"
|
||||
]))
|
||||
@@ -0,0 +1,5 @@
|
||||
from tjnetwork import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
clean_project()
|
||||
delete_project('project')
|
||||
@@ -0,0 +1,22 @@
|
||||
import sys
|
||||
from tjnetwork import *
|
||||
|
||||
def main():
|
||||
argc = len(sys.argv)
|
||||
if argc < 2 or argc > 4:
|
||||
print("copy_project source [count]")
|
||||
return
|
||||
|
||||
source = sys.argv[1]
|
||||
if not have_project(source):
|
||||
print(f"{source} is not available")
|
||||
|
||||
if argc == 2:
|
||||
copy_project(source, f"{source}_1")
|
||||
elif argc == 3:
|
||||
count = int(sys.argv[2])
|
||||
for i in range(1, 1 + count):
|
||||
copy_project(source, f"{source}_{i}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from tjnetwork import *
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("create_project which_inp")
|
||||
return
|
||||
|
||||
inp = sys.argv[1]
|
||||
read_inp(inp, f'./inp/{inp}.inp', '2')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from tjnetwork import *
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("create_project which_inp")
|
||||
return
|
||||
|
||||
inp = sys.argv[1]
|
||||
read_inp(inp, f'./inp/{inp}.inp', '3')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,136 @@
|
||||
import psycopg as pg
|
||||
|
||||
sql_create = [
|
||||
"script/sql/create/0.base.sql",
|
||||
"script/sql/create/1.title.sql",
|
||||
"script/sql/create/2.junctions.sql",
|
||||
"script/sql/create/3.reservoirs.sql",
|
||||
"script/sql/create/4.tanks.sql",
|
||||
"script/sql/create/5.pipes.sql",
|
||||
"script/sql/create/6.pumps.sql",
|
||||
"script/sql/create/7.valves.sql",
|
||||
"script/sql/create/8.tags.sql",
|
||||
"script/sql/create/9.demands.sql",
|
||||
"script/sql/create/10.status.sql",
|
||||
"script/sql/create/11.patterns.sql",
|
||||
"script/sql/create/12.curves.sql",
|
||||
"script/sql/create/13.controls.sql",
|
||||
"script/sql/create/14.rules.sql",
|
||||
"script/sql/create/15.energy.sql",
|
||||
"script/sql/create/16.emitters.sql",
|
||||
"script/sql/create/17.quality.sql",
|
||||
"script/sql/create/18.sources.sql",
|
||||
"script/sql/create/19.reactions.sql",
|
||||
"script/sql/create/20.mixing.sql",
|
||||
"script/sql/create/21.times.sql",
|
||||
"script/sql/create/22.report.sql",
|
||||
"script/sql/create/23.options.sql",
|
||||
"script/sql/create/24.coordinates.sql",
|
||||
"script/sql/create/25.vertices.sql",
|
||||
"script/sql/create/26.labels.sql",
|
||||
"script/sql/create/27.backdrop.sql",
|
||||
"script/sql/create/28.end.sql",
|
||||
"script/sql/create/29.scada_device.sql",
|
||||
"script/sql/create/30.scada_device_data.sql",
|
||||
"script/sql/create/31.scada_element.sql",
|
||||
"script/sql/create/32.region.sql",
|
||||
"script/sql/create/33.dma.sql",
|
||||
"script/sql/create/34.sa.sql",
|
||||
"script/sql/create/35.vd.sql",
|
||||
"script/sql/create/36.wda.sql",
|
||||
"script/sql/create/37.history_patterns_flows.sql",
|
||||
"script/sql/create/38.scada_info.sql",
|
||||
"script/sql/create/39.users.sql",
|
||||
"script/sql/create/40.scheme_list.sql",
|
||||
"script/sql/create/41.pipe_risk_probability.sql",
|
||||
"script/sql/create/42.sensor_placement.sql",
|
||||
"script/sql/create/43.burst_locate_result.sql",
|
||||
"script/sql/create/extension_data.sql",
|
||||
"script/sql/create/operation.sql"
|
||||
]
|
||||
|
||||
sql_drop = [
|
||||
"script/sql/drop/operation.sql",
|
||||
"script/sql/drop/extension_data.sql",
|
||||
"script/sql/drop/43.burst_locate_result.sql",
|
||||
"script/sql/drop/42.sensor_placement.sql",
|
||||
"script/sql/drop/41.pipe_risk_probability.sql",
|
||||
"script/sql/drop/40.scheme_list.sql",
|
||||
"script/sql/drop/39.users.sql",
|
||||
"script/sql/drop/38.scada_info.sql",
|
||||
"script/sql/drop/37.history_patterns_flows.sql",
|
||||
"script/sql/drop/36.wda.sql",
|
||||
"script/sql/drop/35.vd.sql",
|
||||
"script/sql/drop/34.sa.sql",
|
||||
"script/sql/drop/33.dma.sql",
|
||||
"script/sql/drop/32.region.sql",
|
||||
"script/sql/drop/31.scada_element.sql",
|
||||
"script/sql/drop/30.scada_device_data.sql",
|
||||
"script/sql/drop/29.scada_device.sql",
|
||||
"script/sql/drop/28.end.sql",
|
||||
"script/sql/drop/27.backdrop.sql",
|
||||
"script/sql/drop/26.labels.sql",
|
||||
"script/sql/drop/25.vertices.sql",
|
||||
"script/sql/drop/24.coordinates.sql",
|
||||
"script/sql/drop/23.options.sql",
|
||||
"script/sql/drop/22.report.sql",
|
||||
"script/sql/drop/21.times.sql",
|
||||
"script/sql/drop/20.mixing.sql",
|
||||
"script/sql/drop/19.reactions.sql",
|
||||
"script/sql/drop/18.sources.sql",
|
||||
"script/sql/drop/17.quality.sql",
|
||||
"script/sql/drop/16.emitters.sql",
|
||||
"script/sql/drop/15.energy.sql",
|
||||
"script/sql/drop/14.rules.sql",
|
||||
"script/sql/drop/13.controls.sql",
|
||||
"script/sql/drop/12.curves.sql",
|
||||
"script/sql/drop/11.patterns.sql",
|
||||
"script/sql/drop/10.status.sql",
|
||||
"script/sql/drop/9.demands.sql",
|
||||
"script/sql/drop/8.tags.sql",
|
||||
"script/sql/drop/7.valves.sql",
|
||||
"script/sql/drop/6.pumps.sql",
|
||||
"script/sql/drop/5.pipes.sql",
|
||||
"script/sql/drop/4.tanks.sql",
|
||||
"script/sql/drop/3.reservoirs.sql",
|
||||
"script/sql/drop/2.junctions.sql",
|
||||
"script/sql/drop/1.title.sql",
|
||||
"script/sql/drop/0.base.sql"
|
||||
]
|
||||
|
||||
def create_template():
|
||||
with pg.connect(conninfo="dbname=postgres host=127.0.0.1", autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("create database project")
|
||||
with pg.connect(conninfo="dbname=project host=127.0.0.1") as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute('create extension postgis cascade')
|
||||
cur.execute('create extension pgrouting cascade')
|
||||
for sql in sql_create:
|
||||
with open(sql, "r", encoding="utf-8") as f:
|
||||
cur.execute(f.read())
|
||||
print(f'executed {sql}')
|
||||
conn.commit()
|
||||
|
||||
def have_template():
|
||||
with pg.connect(conninfo="dbname=postgres host=127.0.0.1", autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("select * from pg_database where datname = 'project'")
|
||||
return cur.rowcount > 0
|
||||
|
||||
def delete_template():
|
||||
with pg.connect(conninfo="dbname=project host=127.0.0.1") as conn:
|
||||
with conn.cursor() as cur:
|
||||
for sql in sql_drop:
|
||||
with open(sql, "r", encoding="utf-8") as f:
|
||||
cur.execute(f.read())
|
||||
print(f'executed {sql}')
|
||||
conn.commit()
|
||||
with pg.connect(conninfo="dbname=postgres host=127.0.0.1", autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("drop database project")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if (have_template()):
|
||||
delete_template()
|
||||
create_template()
|
||||
@@ -0,0 +1,12 @@
|
||||
import sys
|
||||
from tjnetwork import *
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("delete_project name")
|
||||
return
|
||||
|
||||
delete_project(sys.argv[1])
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user