Compare commits
1731
Commits
master
..
c27d1cf1d5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 +0,0 @@
|
||||
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/"
|
||||
|
||||
# 为前端暴露的变量添加 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"
|
||||
@@ -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,24 @@
|
||||
import auto_realtime
|
||||
import auto_store_non_realtime_SCADA_data
|
||||
import asyncio
|
||||
import influxdb_api
|
||||
import influxdb_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('bb')
|
||||
influxdb_api.query_pg_scada_info_non_realtime('bb')
|
||||
|
||||
# 用 asyncio 并发启动两个任务
|
||||
asyncio.run(main())
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+175
@@ -0,0 +1,175 @@
|
||||
from .project import list_project, have_project, create_project, delete_project, clean_project
|
||||
from .project import is_project_open, open_project, close_project
|
||||
from .project 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 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 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})
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
import os
|
||||
import psycopg as pg
|
||||
from psycopg.rows import dict_row
|
||||
from .connection import g_conn_dict as conn
|
||||
|
||||
# no undo/redo
|
||||
|
||||
_server_databases = ['template0', 'template1', 'postgres', 'project']
|
||||
|
||||
|
||||
def list_project() -> list[str]:
|
||||
ps = []
|
||||
with pg.connect(conninfo="dbname=postgres host=127.0.0.1", 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="dbname=postgres host=127.0.0.1", 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="dbname=postgres host=127.0.0.1", 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="dbname=postgres host=127.0.0.1", 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=f"dbname='{new}' host=127.0.0.1", 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="dbname=postgres host=127.0.0.1", 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="dbname=postgres host=127.0.0.1", 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=f"dbname={name} host=127.0.0.1", 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 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.
+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,155 @@
|
||||
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
|
||||
|
||||
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("bb")
|
||||
simulation.query_corresponding_pattern_id_and_query_id('bb')
|
||||
region_result = simulation.query_non_realtime_region('bb')
|
||||
globals.source_outflow_region_id = simulation.get_source_outflow_region_id('bb', region_result)
|
||||
globals.realtime_region_pipe_flow_and_demand_id = simulation.query_realtime_region_pipe_flow_and_demand_id('bb', region_result)
|
||||
globals.pipe_flow_region_patterns = simulation.query_pipe_flow_region_patterns('bb')
|
||||
globals.non_realtime_region_patterns = simulation.query_non_realtime_region_patterns('bb', region_result)
|
||||
globals.source_outflow_region_patterns, realtime_region_pipe_flow_and_demand_patterns = simulation.get_realtime_region_patterns('bb',
|
||||
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='bb', 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('bb')
|
||||
# 自动执行
|
||||
realtime_task()
|
||||
@@ -0,0 +1,138 @@
|
||||
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
|
||||
|
||||
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('bb')
|
||||
# 自动执行
|
||||
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,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()
|
||||
@@ -0,0 +1,7 @@
|
||||
from tjnetwork import *
|
||||
read_inp("beibeizone","beibeizone.inp")
|
||||
#open_project('beibeizone')
|
||||
#generate_service_area("beibeizone",0.00001)
|
||||
|
||||
print(list_project())
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
from tjnetwork import *
|
||||
|
||||
p = 'dev'
|
||||
|
||||
read_inp(p, f'./inp/net3.inp', '3')
|
||||
open_project(p)
|
||||
|
||||
sass = calculate_service_area(p)
|
||||
assert len(sass) == 25
|
||||
|
||||
assert sass[0]['River'] == ['River', '60', '61', '123', '601']
|
||||
assert sass[0]['3'] == ['121', '120', '119', '117', '257', '151', '157', '115', '259', '261', '149', '159', '111', '113', '263', '147', '161', '197', '193', '105', '145', '163', '195', '191', '267', '107', '141', '164', '265', '187', '189', '143', '166', '169', '204', '15', '167', '171', '269', '173', '271', '199', '201', '203', '3', '20', '127', '125', '129', '153', '131', '139']
|
||||
assert sass[0]['1'] == ['185', '184', '205', '273', '1', '40', '179', '177', '183', '181', '35']
|
||||
assert sass[0]['2'] == ['207', '275', '2', '50', '255', '247', '253', '251', '241', '249', '239', '243', '237', '211', '229', '209', '213', '231', '208', '215', '206', '217', '219', '225']
|
||||
|
||||
print(sass[1])
|
||||
assert sass[0]['River'] == ['River', '60', '61', '123', '601']
|
||||
assert sass[0]['3'] == ['121', '120', '119', '117', '257', '151', '157', '115', '259', '261', '149', '159', '111', '113', '263', '147', '161', '197', '193', '145', '163', '195', '191', '141', '164', '265', '187', '143', '166', '169', '267', '204', '15', '167', '171', '269', '173', '199', '201', '203', '3', '20', '127', '125', '129', '153', '131', '139']
|
||||
assert sass[0]['Lake'] == ['105', '107', 'Lake', '10', '101', '103', '109']
|
||||
assert sass[0]['1'] == ['189', '185', '271', '184', '205', '273', '1', '40', '179', '177', '183', '181', '35']
|
||||
assert sass[0]['2'] == ['207', '275', '2', '50', '255', '247', '253', '251', '241', '249', '239', '243', '237', '211', '229', '209', '213', '231', '208', '215', '206', '217', '219', '225']
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user