Compare commits
122
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdbcc5c033 | ||
|
|
d63d7ef1b6 | ||
|
|
6b09662de6 | ||
|
|
b21eaffe40 | ||
|
|
8853877fcd | ||
|
|
2581631b51 | ||
|
|
c250e97b87 | ||
|
|
69a7d53aff | ||
|
|
e4975b7be3 | ||
|
|
a7e1ce6ef4 | ||
|
|
4350612807 | ||
|
|
b0a23a8012 | ||
|
|
70c5b0e445 | ||
|
|
29f691731c | ||
|
|
1432934f12 | ||
|
|
b0e9d480ef | ||
|
|
87d922ea61 | ||
|
|
c126e99b60 | ||
|
|
b6a6527bab | ||
|
|
f010f071eb | ||
|
|
0be31869b5 | ||
|
|
eac6b78598 | ||
|
|
1d88f8efbe | ||
|
|
ba947b616b | ||
|
|
ae1a657554 | ||
|
|
3fbb17bb30 | ||
|
|
ddbb50173c | ||
|
|
437eb5a19a | ||
|
|
31e2728db1 | ||
|
|
03bb2d75c2 | ||
|
|
b977bf6725 | ||
|
|
045d6c5b49 | ||
|
|
db6032bd84 | ||
|
|
b4ecfbb87a | ||
|
|
a204980944 | ||
|
|
2b5f9b8514 | ||
|
|
ca1579dcc2 | ||
|
|
775ecb8a58 | ||
|
|
f72b56845f | ||
|
|
baeaa8a2e1 | ||
|
|
ca97de2e51 | ||
|
|
71fa2ae18c | ||
|
|
76cf6c32bc | ||
|
|
5a91da0904 | ||
|
|
d62bcae85e | ||
|
|
4c0a4b29e9 | ||
|
|
80ca985c28 | ||
|
|
5a55d65002 | ||
|
|
d99f4cec6a | ||
|
|
a6e7a2e75c | ||
|
|
23c008f602 | ||
|
|
2a762e63a7 | ||
|
|
f6939f5516 | ||
|
|
bbf6a0f7ba | ||
|
|
5fd82b8e7c | ||
|
|
2a823b2616 | ||
|
|
2af89eea1c | ||
|
|
26643d68c7 | ||
|
|
f35287d3cf | ||
|
|
4fa8e55748 | ||
|
|
7a9fcaae81 | ||
|
|
a1e9673d9a | ||
|
|
e588d1cf33 | ||
|
|
1712ecd4c7 | ||
|
|
441979f581 | ||
|
|
e336ffcd46 | ||
|
|
52b8f07abd | ||
|
|
7efaeb41e8 | ||
|
|
9a7aad2d36 | ||
|
|
b7872f29a9 | ||
|
|
233960d8db | ||
|
|
b9410b0ff3 | ||
|
|
4982efba5e | ||
|
|
f87dd91b2b | ||
|
|
c16e6e3d0c | ||
|
|
40e699e173 | ||
|
|
9b8a517092 | ||
|
|
f274cf5122 | ||
|
|
60db2a7193 | ||
|
|
b72e42521c | ||
|
|
c2ccb7bc4e | ||
|
|
88be97ddeb | ||
|
|
2317f4d527 | ||
|
|
751950e5b5 | ||
|
|
a1dcbd4230 | ||
|
|
3b712ea467 | ||
|
|
bf2aaa5ff7 | ||
|
|
51b481d174 | ||
|
|
644babf77e | ||
|
|
6b09c6b20d | ||
|
|
93cbd7e7b3 | ||
|
|
0196206ed3 | ||
|
|
88eec2787b | ||
|
|
621cd9d2f9 | ||
|
|
600ddd329c | ||
|
|
c184610035 | ||
|
|
21dd393aee | ||
|
|
b0acfb21ec | ||
|
|
20ec7d9c8d | ||
|
|
7c44654195 | ||
|
|
c5d3075ae2 | ||
|
|
2ea5ce14ba | ||
|
|
adb5dc01fb | ||
|
|
fb9f3217e2 | ||
|
|
5e8600a0a7 | ||
|
|
1dcaf5ae9f | ||
|
|
a792838e80 | ||
|
|
3cd76b9b52 | ||
|
|
e6d00e9bc6 | ||
|
|
68c12cc4eb | ||
|
|
e0c247f3b2 | ||
|
|
c3bf48499b | ||
|
|
102cfffefe | ||
|
|
1a76c89054 | ||
|
|
1673396e1a | ||
|
|
c137adedad | ||
|
|
5041922c84 | ||
|
|
cfe69e581b | ||
|
|
b513d05611 | ||
|
|
9a8d851275 | ||
|
|
50a1e78073 | ||
|
|
83a6143146 |
+4
-1
@@ -11,9 +11,12 @@ dist/
|
||||
package/
|
||||
temp/
|
||||
data/
|
||||
# db_inp/
|
||||
db_inp/
|
||||
inp/
|
||||
.env
|
||||
.env.*
|
||||
logs/
|
||||
coverage/
|
||||
*.pyc
|
||||
*.dump
|
||||
app/algorithms/health/model/my_survival_forest_model_quxi.joblib
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# TJWater Server 环境变量配置模板
|
||||
# 复制此文件为 .env 并填写实际值
|
||||
# CI/CD: 将生产 .env 的完整内容保存为 Gitea 仓库密钥 TJWATER_SERVER_ENV。
|
||||
# CI/CD: 生产环境变量由 Dev 主机的受控 backend.env 注入,不要将完整 .env 保存为 Gitea 仓库密钥。
|
||||
ENVIRONMENT="production"
|
||||
NETWORK_NAME="tjwater"
|
||||
# ============================================
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Server CI/CD v2
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
build-test-publish-and-deploy:
|
||||
uses: OrgTJWater/ci-templates/.gitea/workflows/container-cd.yml@main
|
||||
with:
|
||||
image_name: gitea.waternetwork.cn/orgtjwater/tjwater-backend
|
||||
dockerfile: Dockerfile
|
||||
build_context: .
|
||||
test_command: |
|
||||
test -f app/api/v1/endpoints/access.py
|
||||
grep -Fq 'api_router.include_router(access.router' app/api/v1/router.py
|
||||
grep -Fq '@router.get("/projects"' app/api/v1/endpoints/meta.py
|
||||
grep -Fq '@router.get("/projects/current"' app/api/v1/endpoints/project.py
|
||||
grep -Fq '@router.post("/audit-events"' app/api/v1/endpoints/audit.py
|
||||
deploy_service: backend
|
||||
deploy_host: 192.168.1.114
|
||||
secrets:
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
DEV_DEPLOY_SSH_KEY: ${{ secrets.DEV_DEPLOY_SSH_KEY }}
|
||||
@@ -7,3 +7,4 @@ build/
|
||||
*.dump
|
||||
.vscode/
|
||||
app/algorithms/health/model/my_survival_forest_model_quxi.joblib
|
||||
inp/
|
||||
|
||||
@@ -1,73 +1,38 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Purpose
|
||||
## Project Structure & Module Organization
|
||||
|
||||
This repository is the customer-delivery edition of the TJWater backend. Treat it as a deployable delivery package, not as the primary internal development repository. Changes should be limited to customer-facing fixes, deployment compatibility, configuration templates, packaging, and delivery documentation.
|
||||
This repository contains the TJWater Python backend. Main application code lives in `app/`: API routes under `app/api`, authentication in `app/auth`, configuration in `app/core`, database and repository code in `app/infra`, domain models/schemas in `app/domain`, and business logic in `app/services` and `app/algorithms`.
|
||||
|
||||
Do not introduce internal-only experiments, debug utilities, local data, or source material that is not required for customer operation.
|
||||
Tests are under `tests/`, split into `tests/unit`, `tests/api`, and `tests/auth`. SQL and sample assets are stored in `resources/`; deployment files are in `Dockerfile`, `.gitea/workflows/package.yml`, and `infra/docker/docker-compose.yml`. Local data directories such as `db_inp/`, `temp/`, `data/`, and `.env` are ignored and should not be committed.
|
||||
|
||||
## Source Encapsulation Requirement
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
Core backend source code must be encapsulated before delivery. The sensitive implementation areas include business services, native integrations, hydraulic/network algorithms, and EPANET-related logic, especially:
|
||||
|
||||
- `app/services`
|
||||
- `app/native`
|
||||
- `app/algorithms`
|
||||
- `app/infra/epanet`
|
||||
|
||||
Use the existing Cython packaging flow in `scripts/compile.py` for these areas. Do not ship uncompiled core `.py` files in the final customer package when compiled extension modules are expected. Keep public entry points, configuration loading, route wiring, and minimal package files readable only where required for runtime and operations.
|
||||
|
||||
Before removing source files with `--delete-source`, verify the build from a clean working tree or disposable copy. The delete mode is destructive by design.
|
||||
|
||||
## Workspace Structure
|
||||
|
||||
- `app/main.py` is the FastAPI entry point.
|
||||
- `app/api` contains HTTP route handlers.
|
||||
- `app/services` contains core business orchestration and must be protected in delivery builds.
|
||||
- `app/native`, `app/algorithms`, and `app/infra/epanet` contain specialized computation and integration code that must be protected in delivery builds.
|
||||
- `infra/` and `Dockerfile` contain deployment assets.
|
||||
- `scripts/` contains operational and packaging helpers.
|
||||
- `tests/` contains backend tests.
|
||||
|
||||
## Common Commands
|
||||
|
||||
Run commands from this repository root:
|
||||
Use the existing conda environment when available:
|
||||
|
||||
```bash
|
||||
conda run -n server python -m pytest tests -q
|
||||
conda run -n server python scripts/run_server.py
|
||||
conda run -n server python scripts/compile.py
|
||||
conda run -n server python -m pytest tests/unit tests/auth -q
|
||||
conda run -n server uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
docker build -t tjwater-server:local .
|
||||
docker compose -f infra/docker/docker-compose.yml config
|
||||
```
|
||||
|
||||
Clean compiled extensions when needed:
|
||||
`pytest` runs backend tests. `uvicorn` starts the FastAPI app locally. `docker build` verifies the container image. `docker compose config` validates compose syntax and variable expansion.
|
||||
|
||||
```bash
|
||||
conda run -n server python scripts/compile.py --clean
|
||||
```
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
Only use source deletion in a prepared delivery copy:
|
||||
|
||||
```bash
|
||||
conda run -n server python scripts/compile.py --delete-source
|
||||
```
|
||||
|
||||
## Change Policy
|
||||
|
||||
Keep changes scoped and conservative. Prefer compatibility patches, configuration adjustments, packaging fixes, and customer deployment hardening. If a change requires substantial business logic updates, make it first in the internal backend repository and then port the reviewed result here.
|
||||
|
||||
Do not weaken the encapsulation process to make debugging easier. If debugging requires readable source, do it in the internal repository or a non-delivery branch/copy.
|
||||
Use Python 3.12, four-space indentation, type hints for new public functions, and explicit imports. Keep API endpoint modules grouped by domain under `app/api/v1/endpoints`. Use `snake_case` for files, functions, and variables; `PascalCase` for classes and Pydantic models. Prefer existing repository/service patterns in `app/infra/db` and `app/services` over introducing new abstractions.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
Run the narrowest useful test command for the affected area. For delivery packaging changes, verify both:
|
||||
The project uses `pytest`. Name test files `test_*.py` and test functions `test_*`. Keep unit tests isolated with fakes or monkeypatching from `tests/conftest.py`. Some existing tests depend on local data outside the repository; avoid adding new tests that require untracked files. For API changes, add or update tests in `tests/api`.
|
||||
|
||||
- tests still pass before packaging;
|
||||
- the packaged/compiled runtime can import and start the FastAPI app.
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
Do not add tests that depend on untracked customer data, local database dumps, or machine-specific files.
|
||||
History uses a mix of Conventional Commit prefixes and concise Chinese messages, for example `feat(api): add Tianditu geocoding` or `fix(auth): validate project context`. Prefer `feat(scope): ...`, `fix(scope): ...`, or a clear Chinese summary.
|
||||
|
||||
## Security & Delivery Rules
|
||||
Pull requests should describe the behavior change, list verification commands, mention configuration or migration impacts, and link related issues. Include API examples or screenshots only when they clarify user-facing behavior.
|
||||
|
||||
Never commit `.env`, production credentials, customer data, logs, generated caches, `node_modules/`, database dumps, or temporary delivery archives. Use `.env.example` or deployment documentation for required configuration.
|
||||
## Security & Configuration Tips
|
||||
|
||||
Review Docker and deployment files carefully before delivery. Customer packages should contain only the files needed to run, operate, and diagnose the deployed service.
|
||||
Do not commit `.env`, database dumps, generated caches, or local project data. Use `.env.example` as the configuration template. CI/CD only uses Gitea repository secrets `REGISTRY_USERNAME`, `REGISTRY_PASSWORD`, and `DEV_DEPLOY_SSH_KEY`; production application settings are injected on the Dev host.
|
||||
|
||||
@@ -90,6 +90,19 @@ python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().d
|
||||
Keep keys stable for the lifetime of encrypted metadata. Rotating a key requires
|
||||
decrypting with the old key and re-encrypting with the new key.
|
||||
|
||||
## Metadata Schema Patches
|
||||
|
||||
Apply metadata patches in order:
|
||||
|
||||
1. `resources/sql/004_metadata_auth_management.sql`
|
||||
2. `resources/sql/005_metadata_project_configuration.sql`
|
||||
3. `resources/sql/006_metadata_rbac_roles.sql`
|
||||
|
||||
`004` creates Keycloak-backed metadata users and project memberships. `005`
|
||||
creates project and project database routing tables with uniqueness, role/type,
|
||||
and pool-size constraints. `006` extends existing membership constraints with
|
||||
the fixed delivery roles.
|
||||
|
||||
## Frontend System Management
|
||||
|
||||
`/system-admin` is shown only when `GET /api/v1/access/context` returns
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Backend Naming Audit
|
||||
|
||||
DOC-003 audit for the customer-delivery `TJWaterServerCustomer` backend.
|
||||
DOC-003 audit for the internal `TJWaterServerBinary` backend.
|
||||
|
||||
## Scope
|
||||
|
||||
@@ -12,14 +12,15 @@ The backend is mounted only under `/api/v1` from `app/main.py`; the old no-prefi
|
||||
|
||||
These newer routes already follow the naming rule for public HTTP paths:
|
||||
|
||||
- Metadata/admin: `/api/v1/admin/projects`, `/api/v1/admin/users/sync`, `/api/v1/admin/projects/{project_id}/members`
|
||||
- Audit: `/api/v1/audit/logs`, `/api/v1/audit/logs/count`
|
||||
- Metadata: `/api/v1/meta/project`, `/api/v1/meta/projects`, `/api/v1/meta/db/health`
|
||||
- Agent auth: `/api/v1/agent/auth/context`
|
||||
- Business APIs: `/api/v1/burst-detection/detect`, `/api/v1/burst-location/locate`, `/api/v1/leakage/identify`
|
||||
- Time-series APIs: `/api/v1/scada/by-ids-time-range`, `/api/v1/scada/by-ids-field-time-range`, `/api/v1/composite/clean-scada`
|
||||
- Project data APIs: `/api/v1/scada-info`, `/api/v1/scheme-list`, `/api/v1/burst-locate-result`
|
||||
- Web integrations: `/api/v1/web-search`, `/api/v1/geocode`
|
||||
|
||||
Path template parameters such as `{user_id}`, `{device_id}`, `{scheme_name}`, and `{link_id}` intentionally remain `snake_case`.
|
||||
Path template parameters such as `{project_id}`, `{user_id}`, `{device_id}`, `{scheme_name}`, and `{link_id}` intentionally remain `snake_case`.
|
||||
|
||||
## Legacy URL Categories
|
||||
|
||||
@@ -48,7 +49,7 @@ These route groups expose many command-style concatenated paths. They should not
|
||||
- Network object CRUD: `addjunction`, `getjunctionelevation`, `setpipediameter`, `getvalvesetting`, and similar junction/pipe/pump/tank/reservoir/valve routes
|
||||
- Region/DMA/VD commands: `calculatedistrictmeteringareaforregion`, `getdistrictmeteringarea`, `generatevirtualdistrict`, and related routes
|
||||
- SCADA native CRUD: `getscadadevice`, `setscadadevicedata`, `cleanscadaelement`, and related routes
|
||||
- Snapshot/cache utilities: `takesnapshotforoperation`, `syncwithserver`, `clearrediskey`, `queryredis`
|
||||
- Snapshot/synchronization utilities: `takesnapshotforoperation`, `syncwithserver`
|
||||
- Advanced simulation endpoints with underscore paths: `pressure_regulation`, `daily_scheduling_analysis`, `network_update`, `pressure_sensor_placement_kmeans`
|
||||
|
||||
### Direct Cleanup Candidates
|
||||
@@ -61,7 +62,7 @@ These are likely safe only after confirming no caller uses them:
|
||||
|
||||
## Field Naming
|
||||
|
||||
Most public JSON, query, and SSE fields are already `snake_case`, including `user_id`, `scheme_name`, `scheme_type`, `start_time`, `end_time`, `device_ids`, `session_id`, and `request_id`.
|
||||
Most public JSON, query, and SSE fields are already `snake_case`, including `project_id`, `user_id`, `scheme_name`, `scheme_type`, `start_time`, `end_time`, `device_ids`, `session_id`, and `request_id`.
|
||||
|
||||
Known legacy exception:
|
||||
|
||||
@@ -71,10 +72,6 @@ Headers keep standard HTTP casing:
|
||||
|
||||
- `X-Project-Id`
|
||||
|
||||
## Internal vs Customer Difference
|
||||
|
||||
Both backend editions use Keycloak-backed metadata users and expose user administration through `/api/v1/admin/users`. Project-local user routes such as `/api/v1/users` are no longer part of the public contract.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Do not rename existing legacy routes in place. For each active legacy route, keep the new `kebab-case` alias as the documented path, keep the old route marked deprecated, migrate remaining Agent/customer/script callers, then remove only after a documented compatibility window.
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
# tjwater-server Customer Image Packaging Notes
|
||||
|
||||
This repository is the customer-delivery backend package. Build delivery images from this repository root.
|
||||
|
||||
## Image Build
|
||||
|
||||
Build the customer backend image:
|
||||
|
||||
```bash
|
||||
docker build -t tjwater-server:latest .
|
||||
```
|
||||
|
||||
The `Dockerfile` already performs source encapsulation in the builder stage:
|
||||
|
||||
```bash
|
||||
python scripts/compile.py
|
||||
python scripts/compile.py --delete-source
|
||||
```
|
||||
|
||||
The compiled and source-deleted `app/` directory is then copied into the final runner stage. The source deletion happens inside the Docker builder layer only; it does not delete local workspace files.
|
||||
|
||||
## Encapsulation Scope
|
||||
|
||||
`scripts/compile.py` defaults to compiling these sensitive areas:
|
||||
|
||||
- `app/services`
|
||||
- `app/native/wndb`
|
||||
- `app/algorithms`
|
||||
- `app/infra/epanet/epanet.py`
|
||||
|
||||
These areas should not contain uncompiled `.py` source files in the delivered image. Public entry points, API route wiring, schemas, configuration, and operational files may remain readable when required for runtime.
|
||||
|
||||
## Verification
|
||||
|
||||
After building, verify the image tag:
|
||||
|
||||
```bash
|
||||
docker image ls tjwater-server
|
||||
```
|
||||
|
||||
Verify that core source files were removed and compiled extensions exist:
|
||||
|
||||
```bash
|
||||
docker run --rm --entrypoint sh tjwater-server:latest -c "\
|
||||
printf 'core_py_count='; \
|
||||
find /app/app/services /app/app/native/wndb /app/app/algorithms /app/app/infra/epanet/epanet.py -name '*.py' 2>/dev/null | wc -l; \
|
||||
printf 'core_so_count='; \
|
||||
find /app/app/services /app/app/native/wndb /app/app/algorithms /app/app/infra/epanet -name '*.so' 2>/dev/null | wc -l; \
|
||||
python -c 'import app.main; print(\"import_app_main=ok\")'"
|
||||
```
|
||||
|
||||
Expected delivery result:
|
||||
|
||||
```text
|
||||
core_py_count=0
|
||||
core_so_count=<non-zero>
|
||||
import_app_main=ok
|
||||
```
|
||||
|
||||
Warnings from third-party packages during import are not necessarily build failures. Treat non-zero exit codes, failed imports, or leftover core `.py` files as blockers.
|
||||
|
||||
## Export For Windows Delivery
|
||||
|
||||
Export the image tarball to the Windows desktop from WSL:
|
||||
|
||||
```bash
|
||||
docker save -o /mnt/c/Users/admin/Desktop/tjwater-server-latest.tar tjwater-server:latest
|
||||
```
|
||||
|
||||
Adjust the Windows username if needed. To find available desktop paths:
|
||||
|
||||
```bash
|
||||
find /mnt/c/Users -maxdepth 2 -type d \( -name Desktop -o -name 桌面 \) 2>/dev/null
|
||||
```
|
||||
|
||||
On the target machine, import the image with:
|
||||
|
||||
```bash
|
||||
docker load -i tjwater-server-latest.tar
|
||||
```
|
||||
|
||||
## Deployment Caution
|
||||
|
||||
The development `infra/docker/docker-compose.yml` bind-mounts local source:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ../../app:/app/app
|
||||
- ../../resources:/app/resources
|
||||
```
|
||||
|
||||
Do not use that source mount for customer delivery of the encapsulated image. Mounting `../../app` over `/app/app` replaces the compiled code inside the image with local source files and defeats the encapsulation. For delivery compose files, use the built image directly and mount only required runtime data/config paths.
|
||||
|
||||
## Local Safety
|
||||
|
||||
Do not run this destructive command in the normal working tree:
|
||||
|
||||
```bash
|
||||
python scripts/compile.py --delete-source
|
||||
```
|
||||
|
||||
Only use `--delete-source` in Docker builder stages or in a disposable delivery copy. The normal delivery image build already handles this safely.
|
||||
+3
-22
@@ -1,12 +1,7 @@
|
||||
FROM condaforge/miniforge3:latest AS runtime-base
|
||||
FROM condaforge/miniforge3:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 使用清华大学开源软件镜像站加速 Conda/Mamba 包下载。
|
||||
RUN conda config --system --remove-key channels || true && \
|
||||
conda config --system --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ && \
|
||||
conda config --system --set show_channel_urls yes
|
||||
|
||||
ENV PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \
|
||||
PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn \
|
||||
UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
@@ -19,25 +14,11 @@ COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir uv
|
||||
RUN uv pip install --system --no-cache-dir -r requirements.txt
|
||||
|
||||
FROM runtime-base AS builder
|
||||
|
||||
RUN mamba install -y c-compiler cxx-compiler && \
|
||||
mamba clean -afy
|
||||
|
||||
# 本地数据目录和环境变量在运行时通过 Compose 挂载或注入,
|
||||
# 不应进入镜像构建上下文。
|
||||
COPY app ./app
|
||||
COPY scripts ./scripts
|
||||
|
||||
RUN python scripts/compile.py && \
|
||||
python scripts/compile.py --delete-source
|
||||
|
||||
FROM runtime-base AS runner
|
||||
|
||||
# 将代码放入子目录 'app',将数据放入子目录 'db_inp'
|
||||
# 这样临时文件默认会生成在 /app 下,而代码在 /app/app 下,实现了分离
|
||||
COPY --from=builder /app/app ./app
|
||||
RUN python -c "from pathlib import Path; from zipfile import ZipFile; model_dir = Path('app/algorithms/health/model'); zip_path = model_dir / 'my_survival_forest_model_quxi.zip'; joblib_name = 'my_survival_forest_model_quxi.joblib'; joblib_path = model_dir / joblib_name; assert zip_path.exists(), f'Model archive not found: {zip_path}'; archive = ZipFile(zip_path); archive.extract(joblib_name, model_dir); archive.close(); assert joblib_path.exists(), f'Model file not extracted: {joblib_path}'" && \
|
||||
rm -f app/algorithms/health/model/my_survival_forest_model_quxi.zip
|
||||
# COPY db_inp ./db_inp
|
||||
RUN mkdir -p db_inp temp data inp
|
||||
|
||||
# 设置 PYTHONPATH 以便 uvicorn 找到 app 模块
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# TJWaterServerCustomer 客户版后端
|
||||
# TJWaterServerBinary 内部后端
|
||||
|
||||
`TJWaterServerCustomer` 是 TJWater 客户交付版 Python 后端。该仓库应被视为可部署交付包,只保留客户运行、配置、部署、诊断和交付说明所需内容。
|
||||
`TJWaterServerBinary` 是 TJWater 内部版 Python 后端,基于 FastAPI 提供认证、项目、管网、模拟、爆管、漏损、SCADA 和地图服务集成能力。该仓库用于内部开发和完整功能维护。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Python 3.12
|
||||
- FastAPI / Uvicorn
|
||||
- Pydantic / SQLAlchemy / psycopg
|
||||
- Redis、PostgreSQL、PostGIS、TimescaleDB
|
||||
- PostgreSQL、PostGIS、TimescaleDB
|
||||
- WNTR、EPANET、Cython、科学计算与空间分析依赖
|
||||
- pytest
|
||||
|
||||
@@ -19,89 +19,78 @@ app/api/ HTTP API 路由
|
||||
app/auth/ 认证和权限上下文
|
||||
app/core/ 配置、日志和基础设施初始化
|
||||
app/domain/ 领域模型和 Pydantic schema
|
||||
app/infra/ 数据库、缓存、EPANET 和外部集成
|
||||
app/services/ 核心业务服务,交付镜像中必须封装
|
||||
app/algorithms/ 核心算法,交付镜像中必须封装
|
||||
app/native/ 本地管网数据读写与转换,交付镜像中必须封装
|
||||
scripts/compile.py Cython 封装脚本
|
||||
infra/docker/ Docker Compose 编排
|
||||
app/infra/ 数据库、EPANET 和外部集成
|
||||
app/services/ 业务服务编排
|
||||
app/algorithms/ 管网算法、模拟、爆管、漏损、清洗和健康分析
|
||||
app/native/ 本地管网数据读写与转换
|
||||
tests/ 后端测试
|
||||
resources/ SQL、模板和示例资源
|
||||
infra/docker/ Docker Compose 编排
|
||||
```
|
||||
|
||||
## 本地开发
|
||||
|
||||
推荐使用已有 conda 环境:
|
||||
|
||||
```bash
|
||||
conda run -n server python -m pytest tests/unit tests/auth -q
|
||||
conda run -n server uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
```
|
||||
|
||||
如需要进入环境:
|
||||
|
||||
```bash
|
||||
conda activate server
|
||||
```
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
conda run -n server python -m pytest tests -q
|
||||
conda run -n server python scripts/run_server.py
|
||||
docker build -t tjwater-server:local .
|
||||
docker compose -f infra/docker/docker-compose.yml config
|
||||
```
|
||||
|
||||
本地调试不要在正常工作树执行源码删除命令。
|
||||
- `pytest`:运行自动化测试。
|
||||
- `scripts/run_server.py`:使用项目脚本启动服务。
|
||||
- `docker build`:构建后端镜像。
|
||||
- `docker compose config`:检查 compose 配置和变量展开。
|
||||
|
||||
## 客户版镜像打包
|
||||
## 开发规范
|
||||
|
||||
交付镜像标签通常为:
|
||||
- Python 文件、函数、变量、Pydantic 字段、JSON body 字段和 query 参数使用 `snake_case`。
|
||||
- Python 类和 Pydantic 模型使用 `PascalCase`。
|
||||
- 新 HTTP 路径使用 `kebab-case`,例如 `/api/v1/pressure-status/analyze`。
|
||||
- 优先复用现有 FastAPI/service/repository 边界。
|
||||
- 不要把临时数据、数据库 dump、日志或本地运行产物纳入提交。
|
||||
|
||||
## 项目数据库路由
|
||||
|
||||
项目级 REST 请求通过 `X-Project-Id` 解析元数据中的数据库配置:
|
||||
|
||||
- `biz_data` DSN 用于管网业务数据;`{project_code}_template` 和模拟临时库沿用该 DSN 的主机、端口与凭据,仅替换数据库名。
|
||||
- `iot_data` DSN 用于 TimescaleDB,始终使用元数据配置的完整 DSN,不再从项目代码推导数据库名。
|
||||
- 元数据、业务库和 TimescaleDB 可以部署在同一主机,也可以分别部署。
|
||||
|
||||
使用模板复制或临时方案库的模拟功能时,`biz_data` 账号必须具备现有数据库创建、删除和连接终止操作所需的 PostgreSQL 权限。
|
||||
|
||||
## 测试与发布
|
||||
|
||||
提交前根据改动范围运行最小有效测试:
|
||||
|
||||
```bash
|
||||
docker build -t tjwater-server:latest .
|
||||
conda run -n server python -m pytest tests/unit tests/auth -q
|
||||
```
|
||||
|
||||
`Dockerfile` 会在 builder 阶段执行:
|
||||
发布镜像前建议运行:
|
||||
|
||||
```bash
|
||||
python scripts/compile.py
|
||||
python scripts/compile.py --delete-source
|
||||
docker build -t tjwater-server:local .
|
||||
```
|
||||
|
||||
源码删除只发生在 Docker 构建层内,不会删除本地工作树源码。详细封装、验证和 Windows 导出说明见:
|
||||
|
||||
```text
|
||||
DELIVERY_PACKAGING_NOTES.md
|
||||
```
|
||||
|
||||
## 封装范围
|
||||
|
||||
`scripts/compile.py` 默认封装:
|
||||
|
||||
- `app/services`
|
||||
- `app/native/wndb`
|
||||
- `app/algorithms`
|
||||
- `app/infra/epanet/epanet.py`
|
||||
|
||||
交付镜像中这些核心目录不应残留未编译的 `.py` 源码,应以 `.so` 扩展模块运行。
|
||||
|
||||
## 验证命令
|
||||
|
||||
构建后检查镜像:
|
||||
|
||||
```bash
|
||||
docker image ls tjwater-server
|
||||
```
|
||||
|
||||
检查核心源码是否已删除、FastAPI 入口是否可导入:
|
||||
|
||||
```bash
|
||||
docker run --rm --entrypoint sh tjwater-server:latest -c "\
|
||||
printf 'core_py_count='; \
|
||||
find /app/app/services /app/app/native/wndb /app/app/algorithms /app/app/infra/epanet/epanet.py -name '*.py' 2>/dev/null | wc -l; \
|
||||
printf 'core_so_count='; \
|
||||
find /app/app/services /app/app/native/wndb /app/app/algorithms /app/app/infra/epanet -name '*.so' 2>/dev/null | wc -l; \
|
||||
python -c 'import app.main; print(\"import_app_main=ok\")'"
|
||||
```
|
||||
|
||||
期望结果:
|
||||
|
||||
```text
|
||||
core_py_count=0
|
||||
core_so_count=<非零>
|
||||
import_app_main=ok
|
||||
```
|
||||
|
||||
## 部署注意
|
||||
|
||||
客户交付时不要把本地 `../../app` 挂载到容器 `/app/app`,否则会覆盖镜像内已封装代码。交付 compose 文件应使用构建好的镜像,只挂载必要的运行数据和配置。
|
||||
Gitea 包工作流位于 `.gitea/workflows/package.yml`,通常由 tag 触发构建、推送镜像并通知部署 webhook。
|
||||
|
||||
## 安全规则
|
||||
|
||||
不要提交 `.env`、生产凭据、客户数据、数据库 dump、日志、生成缓存、临时交付压缩包或本地运行目录。客户版仓库不应加入内部实验、调试工具或非交付源码材料。
|
||||
不要提交 `.env`、客户数据、数据库 dump、日志、生成缓存、`db_inp/`、`temp/`、`data/` 或本地密钥。CI/CD 凭据应放在 Gitea secrets 和仓库变量中。
|
||||
|
||||
@@ -121,7 +121,7 @@ def _worker_evaluate(raw_ratios: np.ndarray) -> float:
|
||||
_cleanup_temp_files(prefix)
|
||||
|
||||
|
||||
class LeakageIdentifier:
|
||||
class LeakageIdentifier:
|
||||
FLOW_UNIT_TO_M3S = {
|
||||
"m3/s": 1.0,
|
||||
"m³/s": 1.0,
|
||||
|
||||
@@ -316,6 +316,7 @@ def flushing_analysis(
|
||||
flushing_flow: float = 0,
|
||||
scheme_name: str = None,
|
||||
username: str | None = None,
|
||||
valve_control: dict[str, dict] = None,
|
||||
) -> None:
|
||||
"""
|
||||
管道冲洗模拟
|
||||
@@ -323,6 +324,7 @@ def flushing_analysis(
|
||||
:param modify_pattern_start_time: 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
|
||||
:param modify_total_duration: 模拟总历时,秒
|
||||
:param modify_valve_opening: dict中包含多个阀门开启度,str为阀门的id,float为修改后的阀门开启度
|
||||
:param valve_control: dict中可分别指定阀门的status、setting和k
|
||||
:param drainage_node_ID: 冲洗排放口所在节点ID
|
||||
:param flushing_flow: 冲洗水量,传入参数单位为m3/h
|
||||
:param scheme_name: 方案名称
|
||||
@@ -334,6 +336,7 @@ def flushing_analysis(
|
||||
scheme_detail: dict = {
|
||||
"duration": modify_total_duration,
|
||||
"valve_opening": modify_valve_opening,
|
||||
"valve_control": valve_control,
|
||||
"drainage_node_ID": drainage_node_ID,
|
||||
"flushing_flow": flushing_flow,
|
||||
}
|
||||
@@ -450,6 +453,7 @@ def flushing_analysis(
|
||||
modify_pattern_start_time=modify_pattern_start_time,
|
||||
modify_total_duration=modify_total_duration,
|
||||
modify_valve_opening=modify_valve_opening,
|
||||
valve_control=valve_control,
|
||||
scheme_type="flushing_analysis",
|
||||
scheme_name=scheme_name,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class PaginatedList(list[T], Generic[T]):
|
||||
"""A page of items carrying the total count from its data source."""
|
||||
|
||||
def __init__(self, items: Iterable[T], *, total: int) -> None:
|
||||
super().__init__(items)
|
||||
self.total = total
|
||||
@@ -10,6 +10,7 @@ from app.auth.metadata_dependencies import (
|
||||
get_current_metadata_admin,
|
||||
get_current_metadata_user,
|
||||
)
|
||||
from app.api.pagination import PaginatedList
|
||||
from app.core.audit import AuditAction, log_audit_event
|
||||
from app.domain.schemas.audit import AuditLogResponse
|
||||
from app.infra.db.metadb.database import get_metadata_session
|
||||
@@ -46,7 +47,7 @@ async def get_audit_logs(
|
||||
_current_user=Depends(get_current_metadata_admin),
|
||||
audit_repo: AuditRepository = Depends(get_audit_repository),
|
||||
) -> list[AuditLogResponse]:
|
||||
return await audit_repo.get_logs(
|
||||
items = await audit_repo.get_logs(
|
||||
user_id=user_id,
|
||||
project_id=project_id,
|
||||
action=action,
|
||||
@@ -56,6 +57,15 @@ async def get_audit_logs(
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
total = await audit_repo.get_log_count(
|
||||
user_id=user_id,
|
||||
project_id=project_id,
|
||||
action=action,
|
||||
resource_type=resource_type,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
)
|
||||
return PaginatedList(items, total=total)
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -119,7 +129,7 @@ async def get_my_audit_logs(
|
||||
current_user=Depends(get_current_metadata_user),
|
||||
audit_repo: AuditRepository = Depends(get_audit_repository),
|
||||
) -> list[AuditLogResponse]:
|
||||
return await audit_repo.get_logs(
|
||||
items = await audit_repo.get_logs(
|
||||
user_id=current_user.id,
|
||||
action=action,
|
||||
start_time=start_time,
|
||||
@@ -127,3 +137,10 @@ async def get_my_audit_logs(
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
total = await audit_repo.get_log_count(
|
||||
user_id=current_user.id,
|
||||
action=action,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
)
|
||||
return PaginatedList(items, total=total)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
from fastapi import APIRouter, Query
|
||||
from app.infra.cache.redis_client import redis_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.delete("/redis-keys/detail", summary="清除单个缓存键", description="根据键名清除单个Redis缓存")
|
||||
async def fastapi_clear_redis_key(key: str = Query(..., description="缓存键名")):
|
||||
"""
|
||||
清除单个缓存键
|
||||
|
||||
根据指定的键名删除Redis中对应的缓存
|
||||
"""
|
||||
redis_client.delete(key)
|
||||
return True
|
||||
|
||||
|
||||
@router.delete("/redis-keys", summary="清除匹配的缓存键", description="根据模式清除匹配的Redis缓存键")
|
||||
async def fastapi_clear_redis_keys(keys: str = Query(..., description="缓存键模式(支持通配符)")):
|
||||
"""
|
||||
清除匹配的缓存键
|
||||
|
||||
根据指定的模式删除Redis中所有匹配的缓存键
|
||||
"""
|
||||
# delete keys contains the key
|
||||
matched_keys = redis_client.keys(f"*{keys}*")
|
||||
if matched_keys:
|
||||
redis_client.delete(*matched_keys)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@router.delete("/all-redis", summary="清除所有缓存", description="清空整个Redis数据库的所有缓存")
|
||||
async def fastapi_clear_all_redis():
|
||||
"""
|
||||
清除所有缓存
|
||||
|
||||
清空Redis数据库中的所有缓存键值对
|
||||
"""
|
||||
redis_client.flushdb()
|
||||
return True
|
||||
|
||||
|
||||
@router.get("/redis", summary="查询缓存键列表", description="获取Redis中所有的缓存键")
|
||||
async def fastapi_query_redis():
|
||||
"""
|
||||
查询缓存键列表
|
||||
|
||||
获取Redis数据库中所有的缓存键列表
|
||||
"""
|
||||
# Helper to decode bytes to str for JSON response if needed,
|
||||
# but original just returned keys (which might be bytes in redis-py unless decode_responses=True)
|
||||
# create_redis_client usually sets decode_responses=False by default.
|
||||
# We will assume user handles bytes or we should decode.
|
||||
# Original just returned redis_client.keys("*")
|
||||
keys = redis_client.keys("*")
|
||||
# Clean output for API
|
||||
return [k.decode('utf-8') if isinstance(k, bytes) else k for k in keys]
|
||||
@@ -33,7 +33,7 @@ async def get_project_metadata(
|
||||
"""
|
||||
获取项目元数据
|
||||
|
||||
返回当前项目的完整元数据
|
||||
返回当前项目的完整元数据,包括项目基本信息和项目权限
|
||||
"""
|
||||
project = await metadata_repo.get_project_by_id(ctx.project_id)
|
||||
if not project:
|
||||
|
||||
@@ -17,8 +17,13 @@ from app.auth.metadata_dependencies import (
|
||||
get_current_metadata_admin,
|
||||
get_metadata_repository,
|
||||
)
|
||||
from app.auth.project_dependencies import (
|
||||
ProjectContext,
|
||||
resolve_project_business_routing,
|
||||
)
|
||||
from app.core.audit import AuditAction, log_audit_event
|
||||
from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository
|
||||
from app.infra.db.project_routing import activate_project_routing
|
||||
from app.services.network_import import network_update
|
||||
from app.services.tjnetwork import run_inp
|
||||
|
||||
@@ -118,21 +123,21 @@ async def _run_uploaded_inp(content: bytes) -> str:
|
||||
return run_inp(model_name)
|
||||
|
||||
|
||||
async def _update_from_inp(content: bytes) -> None:
|
||||
async def _update_from_inp(content: bytes, project_code: str) -> None:
|
||||
temp_path: Path | None = None
|
||||
try:
|
||||
with NamedTemporaryFile(suffix=".inp", delete=False) as temp_file:
|
||||
temp_file.write(content)
|
||||
temp_path = Path(temp_file.name)
|
||||
network_update(str(temp_path))
|
||||
network_update(str(temp_path), project_code)
|
||||
finally:
|
||||
if temp_path is not None:
|
||||
temp_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
async def _apply_model_update(content: bytes) -> None:
|
||||
async def _apply_model_update(content: bytes, project_code: str) -> None:
|
||||
try:
|
||||
await _update_from_inp(content)
|
||||
await _update_from_inp(content, project_code)
|
||||
except Exception as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -177,7 +182,19 @@ async def update_project_model(
|
||||
) -> dict:
|
||||
project = await _get_active_project(project_id, metadata_repo)
|
||||
content, filename = await _read_upload(file)
|
||||
await _apply_model_update(content)
|
||||
routing = await resolve_project_business_routing(
|
||||
ProjectContext(
|
||||
project_id=project.id,
|
||||
project_code=project.code,
|
||||
user_id=current_user.id,
|
||||
project_role="owner",
|
||||
system_role=current_user.role,
|
||||
is_superuser=current_user.is_superuser,
|
||||
),
|
||||
metadata_repo,
|
||||
)
|
||||
with activate_project_routing(routing):
|
||||
await _apply_model_update(content, project.code)
|
||||
await _audit_model_change(
|
||||
request=request,
|
||||
current_user=current_user,
|
||||
|
||||
@@ -333,7 +333,6 @@ async def fastapi_get_all_junction_properties(
|
||||
list: 包含所有节点属性的列表
|
||||
"""
|
||||
# 缓存查询结果提高性能
|
||||
# global redis_client # Redis logic removed for clean split, can be re-added if needed or imported
|
||||
results = get_all_junctions(network)
|
||||
return results
|
||||
|
||||
|
||||
@@ -385,7 +385,6 @@ async def fastapi_get_all_pipe_properties(
|
||||
包含所有管道属性的字典列表
|
||||
"""
|
||||
# 缓存查询结果提高性能
|
||||
# global redis_client
|
||||
results = get_all_pipes(network)
|
||||
return results
|
||||
|
||||
|
||||
@@ -177,7 +177,6 @@ async def fastapi_get_all_pump_properties(
|
||||
包含所有水泵属性的字典列表
|
||||
"""
|
||||
# 缓存查询结果提高性能
|
||||
# global redis_client
|
||||
results = get_all_pumps(network)
|
||||
return results
|
||||
|
||||
|
||||
@@ -540,7 +540,6 @@ async def fastapi_get_all_tank_properties(
|
||||
包含所有水箱属性的字典列表
|
||||
"""
|
||||
# 缓存查询结果提高性能
|
||||
# global redis_client
|
||||
results = get_all_tanks(network)
|
||||
return results
|
||||
|
||||
|
||||
@@ -307,7 +307,6 @@ async def fastapi_get_all_valve_properties(
|
||||
返回指定水网中所有阀门的完整属性列表。
|
||||
"""
|
||||
# 缓存查询结果提高性能
|
||||
# global redis_client
|
||||
results = get_all_valves(network)
|
||||
return results
|
||||
|
||||
|
||||
@@ -52,13 +52,12 @@ async def get_project_info_endpoint(
|
||||
):
|
||||
"""
|
||||
获取项目信息
|
||||
|
||||
|
||||
- **network**: 管网名称(或项目代码)
|
||||
"""
|
||||
project_detail = await metadata_repo.get_project_detail_by_code(network)
|
||||
if not project_detail:
|
||||
raise HTTPException(status_code=404, detail=f"Project {network} not found")
|
||||
|
||||
return ProjectMetaResponse(
|
||||
project_id=project_detail.project_id,
|
||||
name=project_detail.name,
|
||||
@@ -67,7 +66,7 @@ async def get_project_info_endpoint(
|
||||
gs_workspace=project_detail.gs_workspace,
|
||||
map_extent=project_detail.map_extent,
|
||||
status=project_detail.status,
|
||||
project_role="viewer",
|
||||
project_role="viewer", # Default role for public access
|
||||
)
|
||||
|
||||
@router.get("/project-codes", summary="获取项目列表", description="获取服务器上所有可用的供水管网项目名称列表。")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, List, Optional
|
||||
from typing import Any, List, Literal, Optional
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
import threading
|
||||
@@ -302,12 +302,20 @@ async def valve_isolation_endpoint(
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/flushing-analyses", response_class=PlainTextResponse, summary="冲洗分析(高级)", description="高级版本的冲洗分析,支持同时开启多个阀门进行冲洗,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。")
|
||||
@router.post("/flushing-analyses", response_class=PlainTextResponse, summary="冲洗分析(高级)", description="高级版本的冲洗分析,支持按状态和设置值控制多个可选阀门,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。")
|
||||
async def fastapi_flushing_analysis(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
start_time: str = Query(..., description="冲洗开始时间(ISO 8601格式)"),
|
||||
valves: List[str] = Query(..., description="要开启的阀门ID列表"),
|
||||
valves_k: List[float] = Query(..., description="对应各阀门的开度列表(0-1)"),
|
||||
valves: List[str] | None = Query(None, description="参与控制的阀门ID列表(可选)"),
|
||||
valves_k: List[float] | None = Query(
|
||||
None, description="对应各阀门的开度列表(0-1,可选,与valves同时提供)"
|
||||
),
|
||||
valve_statuses: List[Literal["OPEN", "CLOSED", "ACTIVE"]] | None = Query(
|
||||
None, description="对应各阀门的开关状态列表(OPEN、CLOSED或ACTIVE,可选)"
|
||||
),
|
||||
valve_settings: List[str] | None = Query(
|
||||
None, description="对应各阀门的设置值列表(ACTIVE状态下必填)"
|
||||
),
|
||||
drainage_node_ID: str = Query(..., description="排污节点ID"),
|
||||
flush_flow: float = Query(0, description="冲洗流量(L/s),0表示自动计算"),
|
||||
duration: int | None = Query(None, description="模拟持续时间(秒),默认900秒"),
|
||||
@@ -319,8 +327,10 @@ async def fastapi_flushing_analysis(
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
- **start_time**: 冲洗开始时间
|
||||
- **valves**: 要开启的阀门ID列表
|
||||
- **valves_k**: 各阀门的开度列表(0-1,与valves对应)
|
||||
- **valves**: 参与控制的阀门ID列表(可选)
|
||||
- **valves_k**: 各阀门的开度列表(0-1,可选,与valves同时提供)
|
||||
- **valve_statuses**: 各阀门的开关状态列表(OPEN、CLOSED或ACTIVE,可选)
|
||||
- **valve_settings**: 各阀门的设置值列表(ACTIVE状态下必填)
|
||||
- **drainage_node_ID**: 排污节点ID
|
||||
- **flush_flow**: 冲洗流量(L/s)
|
||||
- **duration**: 模拟持续时间(秒,可选,默认900)
|
||||
@@ -328,14 +338,75 @@ async def fastapi_flushing_analysis(
|
||||
|
||||
支持多阀联合冲洗操作。
|
||||
"""
|
||||
valve_opening = {
|
||||
valve_id: float(valves_k[idx]) for idx, valve_id in enumerate(valves)
|
||||
}
|
||||
valve_opening = None
|
||||
valve_control = None
|
||||
if valve_statuses is not None and valves_k is not None:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="valve_statuses 和 valves_k 不能同时提供",
|
||||
)
|
||||
if valve_settings is not None and valve_statuses is None:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="valve_settings 必须与 valve_statuses 同时提供",
|
||||
)
|
||||
if valves is None:
|
||||
if (
|
||||
valves_k is not None
|
||||
or valve_statuses is not None
|
||||
or valve_settings is not None
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="阀门控制参数必须与 valves 同时提供",
|
||||
)
|
||||
elif valve_statuses is not None:
|
||||
if len(valves) != len(valve_statuses):
|
||||
raise HTTPException(
|
||||
status_code=422, detail="valves 和 valve_statuses 的数量必须一致"
|
||||
)
|
||||
if valve_settings is not None and len(valves) != len(valve_settings):
|
||||
raise HTTPException(
|
||||
status_code=422, detail="valves 和 valve_settings 的数量必须一致"
|
||||
)
|
||||
|
||||
settings = valve_settings or [""] * len(valves)
|
||||
valve_control = {}
|
||||
for valve_id, raw_status, raw_setting in zip(
|
||||
valves, valve_statuses, settings
|
||||
):
|
||||
status = raw_status
|
||||
setting = raw_setting.strip()
|
||||
if status == "ACTIVE" and not setting:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=f"ACTIVE 状态的阀门 {valve_id} 必须提供设置值",
|
||||
)
|
||||
|
||||
control: dict[str, str] = {"status": status}
|
||||
if status == "ACTIVE":
|
||||
control["setting"] = setting
|
||||
valve_control[valve_id] = control
|
||||
elif valves_k is not None:
|
||||
if len(valves) != len(valves_k):
|
||||
raise HTTPException(
|
||||
status_code=422, detail="valves 和 valves_k 的数量必须一致"
|
||||
)
|
||||
valve_opening = {
|
||||
valve_id: float(valve_k)
|
||||
for valve_id, valve_k in zip(valves, valves_k)
|
||||
}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="提供 valves 时必须同时提供 valve_statuses 或 valves_k",
|
||||
)
|
||||
result = flushing_analysis(
|
||||
name=network,
|
||||
modify_pattern_start_time=start_time,
|
||||
modify_total_duration=duration or 900,
|
||||
modify_valve_opening=valve_opening,
|
||||
valve_control=valve_control,
|
||||
drainage_node_ID=drainage_node_ID,
|
||||
flushing_flow=flush_flow,
|
||||
scheme_name=scheme_name,
|
||||
|
||||
@@ -14,9 +14,19 @@ from pydantic import BaseModel, JsonValue, create_model
|
||||
from starlette.responses import Response
|
||||
|
||||
from app.api.problem_details import ProblemDetails
|
||||
from app.api.pagination import PaginatedList
|
||||
from app.api.v1.router import api_router as handler_api_router
|
||||
from app.auth.metadata_dependencies import get_current_metadata_user
|
||||
from app.auth.project_dependencies import ProjectContext, get_project_context
|
||||
from app.auth.project_dependencies import (
|
||||
ProjectContext,
|
||||
get_project_business_routing,
|
||||
get_project_context,
|
||||
get_project_simulation_routing,
|
||||
)
|
||||
from app.infra.db.project_routing import (
|
||||
ActiveProjectRouting,
|
||||
activate_project_routing,
|
||||
)
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -41,8 +51,15 @@ _PUBLIC_PARAMETER_RENAMES = {
|
||||
"burst_ID": "burst_id",
|
||||
"drainage_node_ID": "drainage_node_id",
|
||||
}
|
||||
_MODEL_NAME_IS_NETWORK = {"RunSimulationManuallyByDate"}
|
||||
_MODEL_USERNAME_FROM_AUTH: set[str] = set()
|
||||
_MODEL_NAME_IS_NETWORK = {"RunSimulationManuallyByDate", "PressureSensorPlacement"}
|
||||
_MODEL_USERNAME_FROM_AUTH = {"PressureSensorPlacement"}
|
||||
_TIMESCALE_ROUTED_ENDPOINT_MODULES = {
|
||||
"app.api.v1.endpoints.burst_detection",
|
||||
"app.api.v1.endpoints.burst_location",
|
||||
"app.api.v1.endpoints.leakage",
|
||||
"app.api.v1.endpoints.simulation",
|
||||
}
|
||||
_TIMESCALE_ROUTED_ENDPOINT_NAMES = {"open_project_endpoint"}
|
||||
|
||||
|
||||
def _clean_name(name: str) -> str:
|
||||
@@ -127,10 +144,14 @@ def _with_header_project_context(endpoint, route_name: str):
|
||||
None,
|
||||
)
|
||||
injected_context_name = existing_context_parameter or "_rest_project_context"
|
||||
injected_routing_name = "_rest_project_routing"
|
||||
injected_user_name = "_rest_current_user"
|
||||
|
||||
@wraps(endpoint)
|
||||
async def wrapper(*args, **kwargs):
|
||||
project_routing = kwargs.pop(injected_routing_name, None)
|
||||
if not isinstance(project_routing, ActiveProjectRouting):
|
||||
raise RuntimeError("REST project database routing was not resolved")
|
||||
project_context = kwargs.get(injected_context_name)
|
||||
if not isinstance(project_context, ProjectContext):
|
||||
raise RuntimeError("REST project context was not resolved")
|
||||
@@ -161,10 +182,11 @@ def _with_header_project_context(endpoint, route_name: str):
|
||||
kwargs[parameter_name] = original_model.model_validate(data)
|
||||
if model_has_username:
|
||||
kwargs.pop(injected_user_name, None)
|
||||
result = endpoint(*args, **kwargs)
|
||||
if inspect.isawaitable(result):
|
||||
return await result
|
||||
return result
|
||||
with activate_project_routing(project_routing):
|
||||
result = endpoint(*args, **kwargs)
|
||||
if inspect.isawaitable(result):
|
||||
return await result
|
||||
return result
|
||||
|
||||
parameters = []
|
||||
for name, parameter in signature.parameters.items():
|
||||
@@ -180,6 +202,14 @@ def _with_header_project_context(endpoint, route_name: str):
|
||||
if name in body_models:
|
||||
parameter = parameter.replace(annotation=body_models[name][1])
|
||||
parameters.append(parameter)
|
||||
routing_dependency = (
|
||||
get_project_simulation_routing
|
||||
if (
|
||||
endpoint.__module__ in _TIMESCALE_ROUTED_ENDPOINT_MODULES
|
||||
or endpoint.__name__ in _TIMESCALE_ROUTED_ENDPOINT_NAMES
|
||||
)
|
||||
else get_project_business_routing
|
||||
)
|
||||
if not existing_context_parameter:
|
||||
parameters.append(
|
||||
inspect.Parameter(
|
||||
@@ -189,6 +219,14 @@ def _with_header_project_context(endpoint, route_name: str):
|
||||
default=Depends(get_project_context),
|
||||
)
|
||||
)
|
||||
parameters.append(
|
||||
inspect.Parameter(
|
||||
injected_routing_name,
|
||||
kind=inspect.Parameter.KEYWORD_ONLY,
|
||||
annotation=ActiveProjectRouting,
|
||||
default=Depends(routing_dependency),
|
||||
)
|
||||
)
|
||||
if username_parameter or model_has_username:
|
||||
parameters.append(
|
||||
inspect.Parameter(
|
||||
@@ -230,9 +268,14 @@ def _with_pagination(endpoint):
|
||||
if not isinstance(result, list):
|
||||
return result
|
||||
if handler_handles_pagination:
|
||||
if not isinstance(result, PaginatedList):
|
||||
raise RuntimeError(
|
||||
f"Paginated handler {endpoint.__name__!r} must return "
|
||||
"PaginatedList with the real total"
|
||||
)
|
||||
return Page(
|
||||
items=result,
|
||||
total=offset + len(result),
|
||||
total=result.total,
|
||||
limit=limit or len(result),
|
||||
offset=offset,
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ from app.api.v1.endpoints import (
|
||||
audit,
|
||||
burst_detection,
|
||||
burst_location,
|
||||
cache,
|
||||
extension,
|
||||
geocoding,
|
||||
leakage,
|
||||
@@ -166,11 +165,6 @@ api_router.include_router(
|
||||
tags=["Risk"],
|
||||
dependencies=[risk_run_access],
|
||||
)
|
||||
api_router.include_router(
|
||||
cache.router,
|
||||
tags=["Cache"],
|
||||
dependencies=[simulation_run_access],
|
||||
)
|
||||
api_router.include_router(
|
||||
web_search.router,
|
||||
tags=["Web Search"],
|
||||
|
||||
@@ -16,6 +16,7 @@ from app.infra.db.metadb.repositories.metadata_repository import (
|
||||
MetadataRepository,
|
||||
ProjectDbRouting,
|
||||
)
|
||||
from app.infra.db.project_routing import ActiveProjectRouting
|
||||
|
||||
DB_ROLE_BIZ_DATA = "biz_data"
|
||||
DB_ROLE_IOT_DATA = "iot_data"
|
||||
@@ -99,6 +100,62 @@ async def get_project_context(
|
||||
return await resolve_project_context(x_project_id, current_user, metadata_repo)
|
||||
|
||||
|
||||
async def get_project_business_routing(
|
||||
ctx: ProjectContext = Depends(get_project_context),
|
||||
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
||||
) -> ActiveProjectRouting:
|
||||
return await resolve_project_business_routing(ctx, metadata_repo)
|
||||
|
||||
|
||||
async def resolve_project_business_routing(
|
||||
ctx: ProjectContext,
|
||||
metadata_repo: MetadataRepository,
|
||||
) -> ActiveProjectRouting:
|
||||
business = await _get_project_routing(
|
||||
metadata_repo,
|
||||
ctx.project_id,
|
||||
DB_ROLE_BIZ_DATA,
|
||||
DB_TYPE_POSTGRES,
|
||||
"PostgreSQL",
|
||||
)
|
||||
return ActiveProjectRouting(
|
||||
project_code=ctx.project_code,
|
||||
business_dsn=business.dsn,
|
||||
)
|
||||
|
||||
|
||||
async def get_project_simulation_routing(
|
||||
ctx: ProjectContext = Depends(get_project_context),
|
||||
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
||||
) -> ActiveProjectRouting:
|
||||
return await resolve_project_simulation_routing(ctx, metadata_repo)
|
||||
|
||||
|
||||
async def resolve_project_simulation_routing(
|
||||
ctx: ProjectContext,
|
||||
metadata_repo: MetadataRepository,
|
||||
) -> ActiveProjectRouting:
|
||||
business = await _get_project_routing(
|
||||
metadata_repo,
|
||||
ctx.project_id,
|
||||
DB_ROLE_BIZ_DATA,
|
||||
DB_TYPE_POSTGRES,
|
||||
"PostgreSQL",
|
||||
)
|
||||
timescale = await _get_project_routing(
|
||||
metadata_repo,
|
||||
ctx.project_id,
|
||||
DB_ROLE_IOT_DATA,
|
||||
DB_TYPE_TIMESCALE,
|
||||
"TimescaleDB",
|
||||
)
|
||||
return ActiveProjectRouting(
|
||||
project_code=ctx.project_code,
|
||||
business_dsn=business.dsn,
|
||||
timescale_dsn=timescale.dsn,
|
||||
)
|
||||
|
||||
|
||||
async def _get_project_routing(
|
||||
metadata_repo: MetadataRepository,
|
||||
project_id: UUID,
|
||||
|
||||
@@ -130,6 +130,7 @@ def sanitize_sensitive_data(data: dict) -> dict:
|
||||
"token",
|
||||
"api_key",
|
||||
"apikey",
|
||||
"dsn",
|
||||
"credit_card",
|
||||
"ssn",
|
||||
"social_security",
|
||||
|
||||
@@ -26,7 +26,6 @@ class Settings(BaseSettings):
|
||||
TIMESCALEDB_DB_PORT: str = "5433"
|
||||
TIMESCALEDB_DB_USER: str = "postgres"
|
||||
TIMESCALEDB_DB_PASSWORD: str = "password"
|
||||
|
||||
# Metadata Database Config (PostgreSQL)
|
||||
METADATA_DB_NAME: str = "system_hub"
|
||||
METADATA_DB_HOST: str = "localhost"
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""
|
||||
This module is reserved for future implementation of advanced cryptographic operations.
|
||||
|
||||
Current Fernet encryption helpers are implemented in `app.core.encryption`.
|
||||
Login credentials are owned by Keycloak; this backend does not hash or store
|
||||
local passwords.
|
||||
Current basic encryption (Fernet) and password hashing are implemented in `app.core.encryption` and `app.core.security`.
|
||||
Future expansion may include:
|
||||
- Asymmetric encryption (RSA/ECC) for secure communication
|
||||
- Key management and rotation services
|
||||
|
||||
Vendored
-19
@@ -1,19 +0,0 @@
|
||||
import redis
|
||||
import msgpack
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
# Initialize Redis connection
|
||||
redis_client = redis.Redis(host="127.0.0.1", port=6379, db=0)
|
||||
|
||||
def encode_datetime(obj: Any) -> Any:
|
||||
"""Serialize datetime objects to dictionary format."""
|
||||
if isinstance(obj, datetime):
|
||||
return {"__datetime__": True, "as_str": obj.strftime("%Y%m%dT%H:%M:%S.%f")}
|
||||
return obj
|
||||
|
||||
def decode_datetime(obj: Any) -> Any:
|
||||
"""Deserialize dictionary format to datetime objects."""
|
||||
if "__datetime__" in obj:
|
||||
return datetime.strptime(obj["as_str"], "%Y%m%dT%H:%M:%S.%f")
|
||||
return obj
|
||||
@@ -20,14 +20,17 @@ def _normalize_postgres_dsn(dsn: str) -> str:
|
||||
scheme, rest = dsn.split("://", 1)
|
||||
if scheme not in ("postgresql", "postgres", "postgresql+psycopg"):
|
||||
return dsn
|
||||
if scheme == "postgresql+psycopg":
|
||||
scheme = "postgresql"
|
||||
normalized_dsn = f"{scheme}://{rest}"
|
||||
if "@" not in rest:
|
||||
return dsn
|
||||
return normalized_dsn
|
||||
userinfo, hostinfo = rest.rsplit("@", 1)
|
||||
if ":" not in userinfo:
|
||||
return dsn
|
||||
return normalized_dsn
|
||||
username, password = userinfo.split(":", 1)
|
||||
if "@" not in password:
|
||||
return dsn
|
||||
return normalized_dsn
|
||||
password = password.replace("@", "%40")
|
||||
return f"{scheme}://{username}:{password}@{hostinfo}"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from contextlib import asynccontextmanager
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
import psycopg_pool
|
||||
from psycopg.rows import dict_row
|
||||
import app.core.config as postgresql_info
|
||||
from app.infra.db.project_routing import get_project_pgconn_string
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -13,6 +13,7 @@ class Database:
|
||||
def __init__(self, db_name=None):
|
||||
self.pool = None
|
||||
self.db_name = db_name
|
||||
self.conninfo = None
|
||||
|
||||
def init_pool(self, db_name=None):
|
||||
"""Initialize the connection pool."""
|
||||
@@ -21,9 +22,10 @@ class Database:
|
||||
|
||||
# Get connection string, handling default case where target_db_name might be None
|
||||
if target_db_name:
|
||||
conn_string = postgresql_info.get_pgconn_string(db_name=target_db_name)
|
||||
conn_string = get_project_pgconn_string(db_name=target_db_name)
|
||||
else:
|
||||
conn_string = postgresql_info.get_pgconn_string()
|
||||
conn_string = get_project_pgconn_string()
|
||||
self.conninfo = conn_string
|
||||
|
||||
try:
|
||||
self.pool = psycopg_pool.AsyncConnectionPool(
|
||||
@@ -75,6 +77,12 @@ async def get_database_instance(db_name: Optional[str] = None) -> Database:
|
||||
if not db_name:
|
||||
return db # 返回默认数据库实例
|
||||
|
||||
expected_conninfo = get_project_pgconn_string(db_name=db_name)
|
||||
existing = _database_instances.get(db_name)
|
||||
if existing is not None and existing.conninfo != expected_conninfo:
|
||||
await existing.close()
|
||||
del _database_instances[db_name]
|
||||
|
||||
if db_name not in _database_instances:
|
||||
# 创建新的数据库实例
|
||||
instance = create_database_instance(db_name)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from contextvars import ContextVar, Token
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterator
|
||||
|
||||
from psycopg.conninfo import make_conninfo
|
||||
|
||||
from app.core.config import get_pgconn_string, get_timescaledb_pgconn_string
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ActiveProjectRouting:
|
||||
project_code: str
|
||||
business_dsn: str
|
||||
timescale_dsn: str | None = None
|
||||
|
||||
|
||||
_active_project_routing: ContextVar[ActiveProjectRouting | None] = ContextVar(
|
||||
"active_project_routing",
|
||||
default=None,
|
||||
)
|
||||
|
||||
|
||||
def get_active_project_routing() -> ActiveProjectRouting | None:
|
||||
return _active_project_routing.get()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def activate_project_routing(
|
||||
routing: ActiveProjectRouting,
|
||||
) -> Iterator[ActiveProjectRouting]:
|
||||
token: Token[ActiveProjectRouting | None] = _active_project_routing.set(routing)
|
||||
try:
|
||||
yield routing
|
||||
finally:
|
||||
_active_project_routing.reset(token)
|
||||
|
||||
|
||||
def _dsn_for_database(dsn: str, database_name: str) -> str:
|
||||
return make_conninfo(dsn, dbname=database_name)
|
||||
|
||||
|
||||
def get_project_pgconn_string(db_name: str | None = None) -> str:
|
||||
routing = get_active_project_routing()
|
||||
if routing is None:
|
||||
return get_pgconn_string(db_name=db_name)
|
||||
if db_name is None or db_name == routing.project_code:
|
||||
return routing.business_dsn
|
||||
return _dsn_for_database(routing.business_dsn, db_name)
|
||||
|
||||
|
||||
def get_project_timescale_pgconn_string(db_name: str | None = None) -> str:
|
||||
routing = get_active_project_routing()
|
||||
if routing is None:
|
||||
return get_timescaledb_pgconn_string(db_name=db_name)
|
||||
if routing.timescale_dsn is None:
|
||||
raise RuntimeError(
|
||||
f"TimescaleDB routing is not configured for project {routing.project_code}"
|
||||
)
|
||||
# Legacy simulation code used to derive the Timescale database name from
|
||||
# the project code. Project-scoped requests must instead use the complete
|
||||
# iot_data DSN selected by metadata routing.
|
||||
return routing.timescale_dsn
|
||||
@@ -3,7 +3,7 @@ from contextlib import asynccontextmanager
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
import psycopg_pool
|
||||
from psycopg.rows import dict_row
|
||||
from app.core.config import get_timescaledb_pgconn_string
|
||||
from app.infra.db.project_routing import get_project_timescale_pgconn_string
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -13,6 +13,7 @@ class Database:
|
||||
def __init__(self, db_name=None):
|
||||
self.pool = None
|
||||
self.db_name = db_name
|
||||
self.conninfo = None
|
||||
|
||||
def init_pool(self, db_name=None):
|
||||
"""Initialize the connection pool."""
|
||||
@@ -21,9 +22,10 @@ class Database:
|
||||
|
||||
# Get connection string, handling default case where target_db_name might be None
|
||||
if target_db_name:
|
||||
conn_string = get_timescaledb_pgconn_string(db_name=target_db_name)
|
||||
conn_string = get_project_timescale_pgconn_string(db_name=target_db_name)
|
||||
else:
|
||||
conn_string = get_timescaledb_pgconn_string()
|
||||
conn_string = get_project_timescale_pgconn_string()
|
||||
self.conninfo = conn_string
|
||||
|
||||
try:
|
||||
self.pool = psycopg_pool.AsyncConnectionPool(
|
||||
@@ -54,8 +56,8 @@ class Database:
|
||||
"""Get the TimescaleDB connection string."""
|
||||
target_db_name = db_name or self.db_name
|
||||
if target_db_name:
|
||||
return get_timescaledb_pgconn_string(db_name=target_db_name)
|
||||
return get_timescaledb_pgconn_string()
|
||||
return get_project_timescale_pgconn_string(db_name=target_db_name)
|
||||
return get_project_timescale_pgconn_string()
|
||||
|
||||
@asynccontextmanager
|
||||
async def get_connection(self) -> AsyncGenerator:
|
||||
@@ -84,6 +86,12 @@ async def get_database_instance(db_name: Optional[str] = None) -> Database:
|
||||
if not db_name:
|
||||
return db # 返回默认数据库实例
|
||||
|
||||
expected_conninfo = get_project_timescale_pgconn_string(db_name=db_name)
|
||||
existing = _database_instances.get(db_name)
|
||||
if existing is not None and existing.conninfo != expected_conninfo:
|
||||
await existing.close()
|
||||
del _database_instances[db_name]
|
||||
|
||||
if db_name not in _database_instances:
|
||||
# 创建新的数据库实例
|
||||
instance = create_database_instance(db_name)
|
||||
|
||||
@@ -6,7 +6,7 @@ import psycopg
|
||||
from psycopg import sql
|
||||
from psycopg.rows import dict_row
|
||||
import time
|
||||
from app.core.config import get_timescaledb_pgconn_string
|
||||
from app.infra.db.project_routing import get_project_timescale_pgconn_string
|
||||
from app.infra.db.timescaledb.repositories.scheme import SchemeRepository
|
||||
from app.infra.db.timescaledb.repositories.realtime import RealtimeRepository
|
||||
from app.infra.db.timescaledb.repositories.scada import ScadaRepository
|
||||
@@ -26,9 +26,9 @@ class InternalStorage:
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
conn_string = (
|
||||
get_timescaledb_pgconn_string(db_name=db_name)
|
||||
get_project_timescale_pgconn_string(db_name=db_name)
|
||||
if db_name
|
||||
else get_timescaledb_pgconn_string()
|
||||
else get_project_timescale_pgconn_string()
|
||||
)
|
||||
with psycopg.Connection.connect(conn_string) as conn:
|
||||
RealtimeRepository.store_realtime_simulation_result_sync(
|
||||
@@ -58,9 +58,9 @@ class InternalStorage:
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
conn_string = (
|
||||
get_timescaledb_pgconn_string(db_name=db_name)
|
||||
get_project_timescale_pgconn_string(db_name=db_name)
|
||||
if db_name
|
||||
else get_timescaledb_pgconn_string()
|
||||
else get_project_timescale_pgconn_string()
|
||||
)
|
||||
with psycopg.Connection.connect(conn_string) as conn:
|
||||
SchemeRepository.store_scheme_simulation_result_sync(
|
||||
@@ -99,9 +99,9 @@ class InternalQueries:
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
conn_string = (
|
||||
get_timescaledb_pgconn_string(db_name=db_name)
|
||||
get_project_timescale_pgconn_string(db_name=db_name)
|
||||
if db_name
|
||||
else get_timescaledb_pgconn_string()
|
||||
else get_project_timescale_pgconn_string()
|
||||
)
|
||||
with psycopg.Connection.connect(conn_string) as conn:
|
||||
rows = ScadaRepository.get_scada_by_ids_time_range_sync(
|
||||
@@ -140,9 +140,9 @@ class InternalQueries:
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
conn_string = (
|
||||
get_timescaledb_pgconn_string(db_name=db_name)
|
||||
get_project_timescale_pgconn_string(db_name=db_name)
|
||||
if db_name
|
||||
else get_timescaledb_pgconn_string()
|
||||
else get_project_timescale_pgconn_string()
|
||||
)
|
||||
with psycopg.Connection.connect(conn_string) as conn:
|
||||
rows = ScadaRepository.get_scada_by_ids_time_range_sync(
|
||||
@@ -185,9 +185,9 @@ class InternalQueries:
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
conn_string = (
|
||||
get_timescaledb_pgconn_string(db_name=db_name)
|
||||
get_project_timescale_pgconn_string(db_name=db_name)
|
||||
if db_name
|
||||
else get_timescaledb_pgconn_string()
|
||||
else get_project_timescale_pgconn_string()
|
||||
)
|
||||
with psycopg.Connection.connect(conn_string) as conn:
|
||||
return ScadaRepository.get_latest_scada_time_sync(
|
||||
@@ -286,9 +286,9 @@ class InternalQueries:
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
conn_string = (
|
||||
get_timescaledb_pgconn_string(db_name=db_name)
|
||||
get_project_timescale_pgconn_string(db_name=db_name)
|
||||
if db_name
|
||||
else get_timescaledb_pgconn_string()
|
||||
else get_project_timescale_pgconn_string()
|
||||
)
|
||||
with psycopg.Connection.connect(conn_string) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
|
||||
@@ -4,9 +4,10 @@ from threading import RLock
|
||||
|
||||
import psycopg as pg
|
||||
|
||||
from app.core.config import get_pgconn_string
|
||||
from app.infra.db.project_routing import get_project_pgconn_string
|
||||
|
||||
g_conn_dict: dict[str, pg.Connection] = {}
|
||||
g_conninfo_dict: dict[str, str] = {}
|
||||
_registry_lock = RLock()
|
||||
_project_locks: dict[str, RLock] = {}
|
||||
|
||||
@@ -42,14 +43,18 @@ def _get_project_lock(name: str) -> RLock:
|
||||
|
||||
def open_connection(name: str) -> pg.Connection:
|
||||
with _get_project_lock(name):
|
||||
conninfo = get_project_pgconn_string(db_name=name)
|
||||
connection = g_conn_dict.get(name)
|
||||
if connection is None or not _is_healthy(connection):
|
||||
if (
|
||||
connection is None
|
||||
or g_conninfo_dict.get(name) != conninfo
|
||||
or not _is_healthy(connection)
|
||||
):
|
||||
if connection is not None:
|
||||
_close_connection(connection)
|
||||
connection = pg.connect(
|
||||
conninfo=get_pgconn_string(db_name=name), autocommit=True
|
||||
)
|
||||
connection = pg.connect(conninfo=conninfo, autocommit=True)
|
||||
g_conn_dict[name] = connection
|
||||
g_conninfo_dict[name] = conninfo
|
||||
return connection
|
||||
|
||||
|
||||
@@ -60,6 +65,12 @@ def is_connection_open(name: str) -> bool:
|
||||
return False
|
||||
if not _is_healthy(connection):
|
||||
del g_conn_dict[name]
|
||||
g_conninfo_dict.pop(name, None)
|
||||
_close_connection(connection)
|
||||
return False
|
||||
if g_conninfo_dict.get(name) != get_project_pgconn_string(db_name=name):
|
||||
del g_conn_dict[name]
|
||||
g_conninfo_dict.pop(name, None)
|
||||
_close_connection(connection)
|
||||
return False
|
||||
return True
|
||||
@@ -68,6 +79,7 @@ def is_connection_open(name: str) -> bool:
|
||||
def close_connection(name: str) -> None:
|
||||
with _get_project_lock(name):
|
||||
connection = g_conn_dict.pop(name, None)
|
||||
g_conninfo_dict.pop(name, None)
|
||||
if connection is not None:
|
||||
_close_connection(connection)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any
|
||||
from psycopg.rows import dict_row, Row
|
||||
from .connection import project_connection
|
||||
@@ -82,27 +83,38 @@ class DbChangeSet:
|
||||
return DbChangeSet(redo_sql, undo_sql, redo_cs_s, undo_cs_s)
|
||||
|
||||
|
||||
def read(name: str, sql: str) -> Row:
|
||||
QueryParams = Sequence[Any] | Mapping[str, Any]
|
||||
|
||||
|
||||
def _execute(cur, sql: str, params: QueryParams | None = None):
|
||||
return cur.execute(sql, params) if params is not None else cur.execute(sql)
|
||||
|
||||
|
||||
def read(name: str, sql: str, params: QueryParams | None = None) -> Row:
|
||||
with project_connection(name) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(sql)
|
||||
_execute(cur, sql, params)
|
||||
row = cur.fetchone()
|
||||
if row == None:
|
||||
raise Exception(sql)
|
||||
return row
|
||||
|
||||
|
||||
def read_all(name: str, sql: str) -> list[Row]:
|
||||
def read_all(
|
||||
name: str, sql: str, params: QueryParams | None = None
|
||||
) -> list[Row]:
|
||||
with project_connection(name) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(sql)
|
||||
_execute(cur, sql, params)
|
||||
return cur.fetchall()
|
||||
|
||||
|
||||
def try_read(name: str, sql: str) -> Row | None:
|
||||
def try_read(
|
||||
name: str, sql: str, params: QueryParams | None = None
|
||||
) -> Row | None:
|
||||
with project_connection(name) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(sql)
|
||||
_execute(cur, sql, params)
|
||||
return cur.fetchone()
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ from .connection import (
|
||||
is_connection_open,
|
||||
open_connection,
|
||||
)
|
||||
from app.core.config import get_pgconn_string, get_pg_config, get_pg_password
|
||||
from app.core.config import get_pg_config, get_pg_password
|
||||
from app.infra.db.project_routing import get_project_pgconn_string
|
||||
|
||||
# no undo/redo
|
||||
|
||||
@@ -16,7 +17,7 @@ _server_databases = ["template0", "template1", "postgres", "project"]
|
||||
|
||||
def list_project() -> list[str]:
|
||||
ps = []
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with pg.connect(conninfo=get_project_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
for p in cur.execute(
|
||||
f"select datname from pg_database where datname <> 'postgres' and datname <> 'template0' and datname <> 'template1' and datname <> 'project'"
|
||||
@@ -27,7 +28,7 @@ def list_project() -> list[str]:
|
||||
|
||||
def have_project(name: str) -> bool:
|
||||
with pg.connect(
|
||||
conninfo=get_pgconn_string(db_name="postgres"), autocommit=True
|
||||
conninfo=get_project_pgconn_string(db_name="postgres"), autocommit=True
|
||||
) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("select 1 from pg_database where datname = %s", (name,))
|
||||
@@ -38,7 +39,7 @@ def copy_project(source: str, new: str) -> None:
|
||||
close_connection(source)
|
||||
|
||||
with pg.connect(
|
||||
conninfo=get_pgconn_string(db_name="postgres"), autocommit=True
|
||||
conninfo=get_project_pgconn_string(db_name="postgres"), autocommit=True
|
||||
) as admin_conn:
|
||||
with admin_conn.cursor() as cur:
|
||||
cur.execute(
|
||||
@@ -131,7 +132,9 @@ class CopyProjectEx:
|
||||
connection.commit()
|
||||
|
||||
def __call__(self, source: str, new_db: str, excluded_tables: [str] = None) -> None:
|
||||
source_connection = pg.connect(conninfo=get_pgconn_string(), autocommit=True)
|
||||
source_connection = pg.connect(
|
||||
conninfo=get_project_pgconn_string(), autocommit=True
|
||||
)
|
||||
|
||||
self.create_database(source_connection, new_db)
|
||||
|
||||
@@ -140,7 +143,7 @@ class CopyProjectEx:
|
||||
source_connection.close()
|
||||
|
||||
new_db_connection = pg.connect(
|
||||
conninfo=get_pgconn_string(db_name=new_db), autocommit=True
|
||||
conninfo=get_project_pgconn_string(db_name=new_db), autocommit=True
|
||||
)
|
||||
self.init_operation_table(new_db_connection, excluded_tables)
|
||||
new_db_connection.close()
|
||||
@@ -151,7 +154,7 @@ def create_project(name: str) -> None:
|
||||
|
||||
|
||||
def delete_project(name: str) -> None:
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with pg.connect(conninfo=get_project_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
f"select pg_terminate_backend(pid) from pg_stat_activity where datname = '{name}'"
|
||||
@@ -161,7 +164,7 @@ def delete_project(name: str) -> None:
|
||||
|
||||
def clean_project(excluded: list[str] = []) -> None:
|
||||
projects = list_project()
|
||||
with pg.connect(conninfo=get_pgconn_string(), autocommit=True) as conn:
|
||||
with pg.connect(conninfo=get_project_pgconn_string(), autocommit=True) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
row = cur.execute(f"select current_database()").fetchone()
|
||||
if row != None:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from psycopg import sql
|
||||
from psycopg.rows import dict_row, Row
|
||||
from .connection import project_connection
|
||||
from .database import read
|
||||
@@ -49,7 +50,12 @@ ELEMENT_TYPES : dict[str, int] = {
|
||||
def _get_from(name: str, id: str, base_type: str) -> Row | None:
|
||||
with project_connection(name) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(f"select * from {base_type} where id = '{id}'")
|
||||
cur.execute(
|
||||
sql.SQL("select * from {} where id = %s").format(
|
||||
sql.Identifier(base_type)
|
||||
),
|
||||
(id,),
|
||||
)
|
||||
return cur.fetchone()
|
||||
|
||||
|
||||
@@ -243,11 +249,17 @@ def get_node_links(name: str, id: str) -> list[str]:
|
||||
with project_connection(name) as conn:
|
||||
with conn.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():
|
||||
for p in cur.execute(
|
||||
"select id from pipes where node1 = %s or node2 = %s", (id, id)
|
||||
).fetchall():
|
||||
links.append(p['id'])
|
||||
for p in cur.execute(f"select id from pumps where node1 = '{id}' or node2 = '{id}'").fetchall():
|
||||
for p in cur.execute(
|
||||
"select id from pumps where node1 = %s or node2 = %s", (id, id)
|
||||
).fetchall():
|
||||
links.append(p['id'])
|
||||
for p in cur.execute(f"select id from valves where node1 = '{id}' or node2 = '{id}'").fetchall():
|
||||
for p in cur.execute(
|
||||
"select id from valves where node1 = %s or node2 = %s", (id, id)
|
||||
).fetchall():
|
||||
links.append(p['id'])
|
||||
return links
|
||||
|
||||
@@ -255,16 +267,15 @@ def get_node_links(name: str, id: str) -> list[str]:
|
||||
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}'")
|
||||
row = read(name, "select node1, node2 from pipes where id = %s", (id,))
|
||||
elif is_pump(name, id):
|
||||
row = read(name, f"select node1, node2 from pumps where id = '{id}'")
|
||||
row = read(name, "select node1, node2 from pumps where id = %s", (id,))
|
||||
elif is_valve(name, id):
|
||||
row = read(name, f"select node1, node2 from valves where id = '{id}'")
|
||||
row = read(name, "select node1, node2 from valves where id = %s", (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}'")
|
||||
type = read(name, "select type from _region where id = %s", (id,))
|
||||
return type
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,11 @@ def from_postgis_point(coord: str) -> dict[str, float]:
|
||||
|
||||
|
||||
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}'")
|
||||
row = try_read(
|
||||
name,
|
||||
"select st_astext(coord) as coord_geom from coordinates where node = %s",
|
||||
(node,),
|
||||
)
|
||||
if row == None:
|
||||
write(name, sql_insert_coord(node, 0.0, 0.0))
|
||||
return {'x': 0.0, 'y': 0.0}
|
||||
@@ -66,7 +70,9 @@ def get_links_in_extent(name: str, x1: float, y1: float, x2: float, y2: float) -
|
||||
|
||||
|
||||
def node_has_coord(name: str, node: str) -> bool:
|
||||
return try_read(name, f"select node from coordinates where node = '{node}'") != None
|
||||
return try_read(
|
||||
name, "select node from coordinates where node = %s", (node,)
|
||||
) != None
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
|
||||
@@ -12,7 +12,7 @@ def get_junction_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
|
||||
|
||||
def get_junction(name: str, id: str) -> dict[str, Any]:
|
||||
j = try_read(name, f"select * from junctions where id = '{id}'")
|
||||
j = try_read(name, "select * from junctions where id = %s", (id,))
|
||||
if j == None:
|
||||
return {}
|
||||
xy = get_node_coord(name, id)
|
||||
|
||||
@@ -23,8 +23,7 @@ non_realtime_region_patterns = {} # 基于source_outflow_region进行区分
|
||||
realtime_region_pipe_flow_and_demand_id = {} # 基于source_outflow_region搜索该分区中的实时pipe_flow和demand的api_query_id,后续用region的流量 - 实时流量计的流量
|
||||
realtime_region_pipe_flow_and_demand_patterns = {} # 基于source_outflow_region搜索该分区中的实时pipe_flow和demand的associated_pattern,后续用region的流量 - 实时流量计的流量
|
||||
# ---------------------------------------------------------
|
||||
# 历史数据访问相关全局变量
|
||||
# 全局变量,用于存储不同类型的 realtime api_query_id
|
||||
# 全局变量,用于存储不同类型的realtime api_query_id
|
||||
reservoir_liquid_level_realtime_ids = []
|
||||
tank_liquid_level_realtime_ids = []
|
||||
fixed_pump_realtime_ids = []
|
||||
|
||||
@@ -5,8 +5,7 @@ import chardet
|
||||
import psycopg
|
||||
from psycopg import sql
|
||||
|
||||
import app.services.project_info as project_info
|
||||
from app.core.config import get_pgconn_string
|
||||
from app.infra.db.project_routing import get_project_pgconn_string
|
||||
from app.services.tjnetwork import read_inp
|
||||
|
||||
|
||||
@@ -15,13 +14,14 @@ from app.services.tjnetwork import read_inp
|
||||
############################################################
|
||||
|
||||
|
||||
def network_update(file_path: str) -> None:
|
||||
def network_update(file_path: str, project_code: str) -> None:
|
||||
"""
|
||||
更新pg数据库中的inp文件
|
||||
:param file_path: inp文件
|
||||
:param project_code: 元数据项目代码
|
||||
:return:
|
||||
"""
|
||||
read_inp("szh", file_path)
|
||||
read_inp(project_code, file_path)
|
||||
|
||||
csv_path = "./history_pattern_flow.csv"
|
||||
|
||||
@@ -51,8 +51,7 @@ def network_update(file_path: str) -> None:
|
||||
if os.path.exists(csv_path):
|
||||
print(f"history_patterns_flows文件存在,开始处理...")
|
||||
|
||||
# 连接到 PostgreSQL 数据库(这里是数据库 "bb")
|
||||
with psycopg.connect(f"dbname={project_info.name} host=127.0.0.1") as conn:
|
||||
with psycopg.connect(get_project_pgconn_string(project_code)) as conn:
|
||||
with conn.cursor() as cur:
|
||||
with open(csv_path, newline="", encoding="utf-8-sig") as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
@@ -92,7 +91,7 @@ def submit_scada_info(name: str, coord_id: str) -> None:
|
||||
print(f"检测到的文件编码:{file_encoding}")
|
||||
try:
|
||||
# 动态替换数据库名称
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
|
||||
# 连接到 PostgreSQL 数据库(这里是数据库 "bb")
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
|
||||
@@ -7,7 +7,7 @@ import pandas as pd
|
||||
import psycopg
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from app.core.config import get_pgconn_string
|
||||
from app.infra.db.project_routing import get_project_pgconn_string
|
||||
from app.services.time_api import parse_utc_time
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ def scheme_name_exists(name: str, scheme_name: str) -> bool:
|
||||
:return: 如果存在返回 True,否则返回 False
|
||||
"""
|
||||
try:
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
@@ -57,7 +57,7 @@ def store_scheme_info(
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
sql = """
|
||||
@@ -93,7 +93,7 @@ def delete_scheme_info(name: str, scheme_name: str) -> None:
|
||||
:param scheme_name: 要删除的方案名称
|
||||
"""
|
||||
try:
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
# 使用参数化查询删除方案记录
|
||||
@@ -121,7 +121,7 @@ def query_scheme_list(
|
||||
"""
|
||||
try:
|
||||
# 动态替换数据库名称
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
# 连接到 PostgreSQL 数据库(这里是数据库 "bb")
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -203,7 +203,7 @@ def query_scheme_detail(
|
||||
scheme_type,
|
||||
)
|
||||
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
if scheme_type:
|
||||
@@ -255,7 +255,7 @@ def store_leakage_identify_result(
|
||||
run_status: str = "completed",
|
||||
error_message: str | None = None,
|
||||
) -> None:
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
@@ -299,7 +299,7 @@ def query_leakage_identify_schemes(
|
||||
scheme_type: str = "dma_leak_identification",
|
||||
query_date: date | None = None,
|
||||
) -> list[dict]:
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
if query_date is None:
|
||||
@@ -343,7 +343,7 @@ def query_leakage_identify_schemes(
|
||||
|
||||
|
||||
def query_leakage_identify_scheme_detail(name: str, scheme_name: str) -> dict:
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
@@ -400,7 +400,7 @@ def query_burst_location_schemes(
|
||||
scheme_type: str = "burst_location",
|
||||
query_date: date | None = None,
|
||||
) -> list[dict]:
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
if query_date is None:
|
||||
@@ -444,7 +444,7 @@ def query_burst_location_schemes(
|
||||
|
||||
|
||||
def query_burst_location_scheme_detail(name: str, scheme_name: str) -> dict:
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
@@ -479,7 +479,7 @@ def query_burst_detection_schemes(
|
||||
scheme_type: str = "burst_detection",
|
||||
query_date: date | None = None,
|
||||
) -> list[dict]:
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
if query_date is None:
|
||||
@@ -523,7 +523,7 @@ def query_burst_detection_schemes(
|
||||
|
||||
|
||||
def query_burst_detection_scheme_detail(name: str, scheme_name: str) -> dict:
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
@@ -564,7 +564,7 @@ def upload_shp_to_pg(name: str, table_name: str, role: str, shp_file_path: str):
|
||||
"""
|
||||
try:
|
||||
# 动态连接到指定的数据库
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
# 读取 Shapefile 文件
|
||||
gdf = gpd.read_file(shp_file_path)
|
||||
@@ -604,7 +604,7 @@ def submit_risk_probability_result(name: str, result_file_path: str) -> None:
|
||||
|
||||
try:
|
||||
# 动态替换数据库名称
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
|
||||
# 连接到 PostgreSQL 数据库
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
|
||||
+39
-12
@@ -34,7 +34,7 @@ import logging
|
||||
import app.services.globals as globals
|
||||
import app.services.project_info as project_info
|
||||
from app.services.time_api import parse_beijing_time, parse_clock_duration_seconds
|
||||
from app.core.config import get_pgconn_string
|
||||
from app.infra.db.project_routing import get_project_pgconn_string
|
||||
from app.infra.db.timescaledb.internal_queries import (
|
||||
InternalQueries as TimescaleInternalQueries,
|
||||
)
|
||||
@@ -54,7 +54,7 @@ def query_corresponding_element_id_and_query_id(name: str) -> None:
|
||||
:return:
|
||||
"""
|
||||
# 连接数据库
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
try:
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -99,7 +99,7 @@ def query_corresponding_pattern_id_and_query_id(name: str) -> None:
|
||||
:return:
|
||||
"""
|
||||
# 连接数据库
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
try:
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -139,7 +139,7 @@ def query_non_realtime_region(name: str) -> dict:
|
||||
"""
|
||||
source_outflow_regions = [] # 用于存储所有 region(包含重复的)
|
||||
# 构建连接字符串
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
try:
|
||||
# 连接到数据库
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
@@ -216,7 +216,7 @@ def query_non_realtime_region_patterns(
|
||||
region_tuple_to_key = {
|
||||
frozenset(ids): region for region, ids in globals.source_outflow_region.items()
|
||||
}
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
try:
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -304,7 +304,7 @@ def query_realtime_region_pipe_flow_and_demand_id(
|
||||
region_tuple_to_key = {
|
||||
frozenset(ids): region for region, ids in globals.source_outflow_region.items()
|
||||
}
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
try:
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -376,7 +376,7 @@ def query_pipe_flow_region_patterns(
|
||||
:param column_prefix: 需要提取的列的前缀
|
||||
:return: pipe_flow_region_patterns 字典
|
||||
"""
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
try:
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -439,7 +439,7 @@ def query_SCADA_ID_corresponding_info(name: str, SCADA_ID: str) -> dict:
|
||||
:param SCADA_ID: SCADA设备的ID
|
||||
:return: 包含associated_element_id和api_query_id的字典
|
||||
"""
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
try:
|
||||
# 使用 psycopg.connect 创建连接
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
@@ -495,7 +495,7 @@ def get_source_outflow_region_id(
|
||||
"No associated_source_outflow_id found in source_outflow_region."
|
||||
)
|
||||
return globals.source_outflow_region_id
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
try:
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -552,7 +552,7 @@ def get_realtime_region_patterns(
|
||||
globals.realtime_region_pipe_flow_and_demand_patterns = {
|
||||
region: [] for region in globals.realtime_region_pipe_flow_and_demand_id.keys()
|
||||
}
|
||||
conn_string = get_pgconn_string(db_name=name)
|
||||
conn_string = get_project_pgconn_string(db_name=name)
|
||||
try:
|
||||
with psycopg.connect(conn_string) as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -685,6 +685,28 @@ def get_history_pattern_info(project_name, pattern_name):
|
||||
return flow_list, factor_list
|
||||
|
||||
|
||||
def _apply_valve_control(
|
||||
project_name: str, valve_control: dict[str, dict]
|
||||
) -> None:
|
||||
"""Apply explicit valve status, setting, and opening controls."""
|
||||
for valve_name, control in valve_control.items():
|
||||
valve_status = get_status(project_name, valve_name)
|
||||
if "status" in control:
|
||||
valve_status["status"] = control["status"]
|
||||
if "setting" in control:
|
||||
valve_status["setting"] = control["setting"]
|
||||
if "k" in control:
|
||||
valve_k = control["k"]
|
||||
if valve_k == 0:
|
||||
valve_status["status"] = "CLOSED"
|
||||
else:
|
||||
valve_status["setting"] = 0.1036 * pow(valve_k, -3.105)
|
||||
|
||||
cs = ChangeSet()
|
||||
cs.append(valve_status)
|
||||
set_status(project_name, cs)
|
||||
|
||||
|
||||
# 2025/01/11
|
||||
def run_simulation(
|
||||
name: str,
|
||||
@@ -700,6 +722,7 @@ def run_simulation(
|
||||
modify_valve_opening: dict[str, float] = None,
|
||||
scheme_type: str = None,
|
||||
scheme_name: str = None,
|
||||
valve_control: dict[str, dict] = None,
|
||||
) -> None:
|
||||
"""
|
||||
传入需要修改的参数,改变数据库中对应位置的值,然后计算,返回结果
|
||||
@@ -714,6 +737,7 @@ def run_simulation(
|
||||
:param modify_fixed_pump_pattern: dict中包含多个水泵模式,str为工频水泵的id,list为修改后的pattern
|
||||
:param modify_variable_pump_pattern: dict中包含多个水泵模式,str为变频水泵的id,list为修改后的pattern
|
||||
:param modify_valve_opening: dict中包含多个阀门开启度,str为阀门的id,float为修改后的阀门开启度
|
||||
:param valve_control: dict中可分别指定阀门的status、setting和k;存在时优先于modify_valve_opening
|
||||
:param scheme_type: 模拟方案类型
|
||||
:param scheme_name:模拟方案名称
|
||||
:return:
|
||||
@@ -1199,8 +1223,11 @@ def run_simulation(
|
||||
cs = ChangeSet()
|
||||
cs.append(pump_pattern)
|
||||
set_pattern(name_c, cs)
|
||||
# 修改阀门(valve)的状态setting和status
|
||||
if modify_valve_opening:
|
||||
# 显式阀门控制沿用 run_simulation_ex 的处理顺序和覆盖规则。
|
||||
if valve_control is not None:
|
||||
_apply_valve_control(name_c, valve_control)
|
||||
# 保留原开度参数逻辑,兼容现有方案调用。
|
||||
elif modify_valve_opening:
|
||||
for valve_name in modify_valve_opening.keys():
|
||||
if not np.isnan(modify_valve_opening[valve_name]):
|
||||
valve_status = get_status(name_c, valve_name)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"contracts": {
|
||||
"server": {
|
||||
"file": "server-v1.openapi.json",
|
||||
"sha256": "b003a57c9b8c1a041b644363ef58afb8bfcede1fe076ce48a190d2a1ac915e3f"
|
||||
"sha256": "ac9b6fac185dfd999f1791cba51eb482df17a427b361963250aafa5fb1a276b4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1859,7 +1859,7 @@
|
||||
"title": "PressureRegulationRest",
|
||||
"type": "object"
|
||||
},
|
||||
"PressureSensorPlacement": {
|
||||
"PressureSensorPlacementRest": {
|
||||
"properties": {
|
||||
"min_diameter": {
|
||||
"default": 0,
|
||||
@@ -1867,11 +1867,6 @@
|
||||
"title": "Min Diameter",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "管网名称(或数据库名称)",
|
||||
"title": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
"scheme_name": {
|
||||
"description": "方案名称",
|
||||
"title": "Scheme Name",
|
||||
@@ -1881,20 +1876,13 @@
|
||||
"description": "传感器数量",
|
||||
"title": "Sensor Number",
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"description": "用户名",
|
||||
"title": "Username",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"scheme_name",
|
||||
"sensor_number",
|
||||
"username"
|
||||
"sensor_number"
|
||||
],
|
||||
"title": "PressureSensorPlacement",
|
||||
"title": "PressureSensorPlacementRest",
|
||||
"type": "object"
|
||||
},
|
||||
"ProblemDetails": {
|
||||
@@ -5239,97 +5227,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/all-redis": {
|
||||
"delete": {
|
||||
"description": "清空整个Redis数据库的所有缓存",
|
||||
"operationId": "delete_all_redis",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "X-Project-Id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"title": "X-Project-Id",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Authentication required"
|
||||
},
|
||||
"403": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Insufficient permission"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Resource not found"
|
||||
},
|
||||
"409": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Resource conflict"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation error"
|
||||
},
|
||||
"503": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Dependency unavailable"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
],
|
||||
"summary": "清除所有缓存",
|
||||
"tags": [
|
||||
"Cache"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/all-scada-properties": {
|
||||
"get": {
|
||||
"description": "获取指定水网中所有SCADA点的属性信息",
|
||||
@@ -11038,7 +10935,7 @@
|
||||
},
|
||||
"/api/v1/flushing-analyses": {
|
||||
"post": {
|
||||
"description": "高级版本的冲洗分析,支持同时开启多个阀门进行冲洗,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。",
|
||||
"description": "高级版本的冲洗分析,支持按状态和设置值控制多个可选阀门,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。",
|
||||
"operationId": "post_flushing_analyses",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -11053,31 +10950,92 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "要开启的阀门ID列表",
|
||||
"description": "参与控制的阀门ID列表(可选)",
|
||||
"in": "query",
|
||||
"name": "valves",
|
||||
"required": true,
|
||||
"required": false,
|
||||
"schema": {
|
||||
"description": "要开启的阀门ID列表",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "Valves",
|
||||
"type": "array"
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "参与控制的阀门ID列表(可选)",
|
||||
"title": "Valves"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "对应各阀门的开度列表(0-1)",
|
||||
"description": "对应各阀门的开度列表(0-1,可选,与valves同时提供)",
|
||||
"in": "query",
|
||||
"name": "valves_k",
|
||||
"required": true,
|
||||
"required": false,
|
||||
"schema": {
|
||||
"description": "对应各阀门的开度列表(0-1)",
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"title": "Valves K",
|
||||
"type": "array"
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "对应各阀门的开度列表(0-1,可选,与valves同时提供)",
|
||||
"title": "Valves K"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "对应各阀门的开关状态列表(OPEN、CLOSED或ACTIVE,可选)",
|
||||
"in": "query",
|
||||
"name": "valve_statuses",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"enum": [
|
||||
"OPEN",
|
||||
"CLOSED",
|
||||
"ACTIVE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "对应各阀门的开关状态列表(OPEN、CLOSED或ACTIVE,可选)",
|
||||
"title": "Valve Statuses"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "对应各阀门的设置值列表(ACTIVE状态下必填)",
|
||||
"in": "query",
|
||||
"name": "valve_settings",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "对应各阀门的设置值列表(ACTIVE状态下必填)",
|
||||
"title": "Valve Settings"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -24833,7 +24791,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PressureSensorPlacement",
|
||||
"$ref": "#/components/schemas/PressureSensorPlacementRest",
|
||||
"description": "传感器放置分析参数"
|
||||
}
|
||||
}
|
||||
@@ -25073,7 +25031,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PressureSensorPlacement",
|
||||
"$ref": "#/components/schemas/PressureSensorPlacementRest",
|
||||
"description": "传感器放置分析参数"
|
||||
}
|
||||
}
|
||||
@@ -29403,308 +29361,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/redis": {
|
||||
"get": {
|
||||
"description": "获取Redis中所有的缓存键",
|
||||
"operationId": "get_redis",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "X-Project-Id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"title": "X-Project-Id",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/JsonValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Authentication required"
|
||||
},
|
||||
"403": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Insufficient permission"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Resource not found"
|
||||
},
|
||||
"409": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Resource conflict"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation error"
|
||||
},
|
||||
"503": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Dependency unavailable"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
],
|
||||
"summary": "查询缓存键列表",
|
||||
"tags": [
|
||||
"Cache"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/redis-keys": {
|
||||
"delete": {
|
||||
"description": "根据模式清除匹配的Redis缓存键",
|
||||
"operationId": "delete_redis_keys",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "缓存键模式(支持通配符)",
|
||||
"in": "query",
|
||||
"name": "keys",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "缓存键模式(支持通配符)",
|
||||
"title": "Keys",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "X-Project-Id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"title": "X-Project-Id",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Authentication required"
|
||||
},
|
||||
"403": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Insufficient permission"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Resource not found"
|
||||
},
|
||||
"409": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Resource conflict"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation error"
|
||||
},
|
||||
"503": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Dependency unavailable"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
],
|
||||
"summary": "清除匹配的缓存键",
|
||||
"tags": [
|
||||
"Cache"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/redis-keys/detail": {
|
||||
"delete": {
|
||||
"description": "根据键名清除单个Redis缓存",
|
||||
"operationId": "delete_redis_keys_detail",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "缓存键名",
|
||||
"in": "query",
|
||||
"name": "key",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "缓存键名",
|
||||
"title": "Key",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "X-Project-Id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"title": "X-Project-Id",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Authentication required"
|
||||
},
|
||||
"403": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Insufficient permission"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Resource not found"
|
||||
},
|
||||
"409": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Resource conflict"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation error"
|
||||
},
|
||||
"503": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Dependency unavailable"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
],
|
||||
"summary": "清除单个缓存键",
|
||||
"tags": [
|
||||
"Cache"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/redos": {
|
||||
"post": {
|
||||
"description": "重做网络上被撤销的操作",
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
# 监测点灵敏度算法优化说明
|
||||
|
||||
本文记录压力监测点布置算法的改造方案、数学含义、复杂度变化和本地验证结果。实现位于 `app/algorithms/sensor/sensitivity.py`,对外兼容入口仍为 `get_ID(name, sensor_num, min_diameter)`。
|
||||
|
||||
## 改造目标
|
||||
|
||||
原算法根据节点压力对管道粗糙度变化的灵敏度,以及节点到全网的水力距离,给每个候选节点评分。空间聚类用于限制监测点过度集中。业务评分可以写成:
|
||||
|
||||
$$
|
||||
score_i = sensitivity_i \times \sum_j distance(i, j)
|
||||
$$
|
||||
|
||||
本次改造保留这套评分语义和空间覆盖原则,主要处理以下问题:
|
||||
|
||||
- 稠密的节点与节点、节点与管道、管道与管道矩阵占用大量内存。
|
||||
- 显式计算行列式、逆矩阵和伪逆,计算量随节点数快速增长。
|
||||
- 从每个节点执行最短路径并保存全节点距离矩阵,时间和内存均为平方级。
|
||||
- 一次任务重复运行 EPANET,并保留算法没有使用的扩展时序结果。
|
||||
- 聚类没有固定随机状态,相同输入可能返回不同结果。
|
||||
- KMeans 按候选节点密度最小化平方距离,密集管网会系统性获得更多监测点,不能保证地图或管网路径覆盖均匀。
|
||||
- WNTR 管径使用米,接口参数使用毫米,旧候选过滤没有统一单位。
|
||||
|
||||
## 稀疏表示替代稠密矩阵
|
||||
|
||||
管网的节点通常只连接少量管道。新实现使用 SciPy CSR 或 CSC 矩阵保存节点与管道关联矩阵、水力有向图和水力雅可比矩阵,只记录真实存在的连接。
|
||||
|
||||
准备阶段生成一个只读的 `_PreparedNetwork`,其中包括:
|
||||
|
||||
- 参与分析的节点及其在完整模型中的索引。
|
||||
- 符合安装条件的候选节点索引。
|
||||
- 归一化前的二维坐标。
|
||||
- 稀疏节点与管道关联矩阵。
|
||||
- 管道导通系数和粗糙度响应系数。
|
||||
- 按实际流向构建的稀疏水力距离图。
|
||||
- 按初始运行状态构建的无向物理覆盖图。
|
||||
|
||||
水库、水箱、泵和阀门的边界节点继续沿用原算法的排除规则。与水源直接相连的管道不参加扰动计算。平行有向边只保留权重最小的边,以符合最短路径语义。
|
||||
|
||||
主体存储由平方级降到接近 `O(n + m)`。稀疏 LU 分解可能产生填充,其内存仍取决于管网拓扑,但实现不会再主动分配完整的 `n × n` 或 `n × m` 数组。
|
||||
|
||||
## 用 Cauchy 投影估计压力灵敏度
|
||||
|
||||
旧算法先显式计算压力对管道粗糙度的响应矩阵:
|
||||
|
||||
$$
|
||||
J = X^{-1} A S
|
||||
$$
|
||||
|
||||
其中,`X` 是节点水力雅可比矩阵,`A` 是节点与管道关联矩阵,`S` 是管道粗糙度响应矩阵。节点灵敏度是 `J` 对应行的 L1 范数。完整的 `J` 为 `节点数 × 管道数`,大模型无法保存。
|
||||
|
||||
新算法使用 Cauchy 分布的 1-stable 特性估计每一行的 L1 范数。设 `R` 为 Cauchy 随机投影矩阵,只需求解:
|
||||
|
||||
$$
|
||||
Y = X^{-1} A S R
|
||||
$$
|
||||
|
||||
对节点 `i` 而言,`Y` 中每个投影值服从以 `||J_i||_1` 为尺度的 Cauchy 分布。实现使用投影绝对值的对数几何均值估计 `log(||J_i||_1)`,不保存完整灵敏度矩阵。
|
||||
|
||||
当前固定参数如下:
|
||||
|
||||
| 参数 | 数值 | 用途 |
|
||||
|---|---:|---|
|
||||
| 随机种子 | 42 | 保证结果可复现 |
|
||||
| Cauchy 投影数 | 256 | 控制灵敏度估计精度 |
|
||||
| 投影批大小 | 16 | 限制投影中间矩阵内存 |
|
||||
| 雅可比正则化 | `max(abs(diag(X))) × sqrt(eps)` | 改善接近奇异矩阵的稳定性 |
|
||||
| 稀疏排序 | `MMD_AT_PLUS_A` | 减少 LU 分解填充 |
|
||||
|
||||
水力雅可比矩阵只进行一次稀疏 LU 分解,256 次投影复用该分解结果。批处理期间最多保留 16 列投影数据。
|
||||
|
||||
## 用空间代表点估计水力距离总和
|
||||
|
||||
旧算法从每个节点执行一次最短路径,并保存 `n × n` 的水力距离矩阵。新算法根据节点坐标构建最多 256 个空间代表点:
|
||||
|
||||
1. 使用固定随机状态的 `MiniBatchKMeans` 对节点坐标分组。
|
||||
2. 每组选择最靠近聚类中心的节点作为代表点。
|
||||
3. 代表点权重等于该组包含的节点数。
|
||||
4. 在反向水力图上从代表点执行有向 Dijkstra,得到原图中各节点到代表点的距离。
|
||||
|
||||
节点 `i` 的全网水力距离总和估计为:
|
||||
|
||||
$$
|
||||
distance\_sum_i \approx \sum_{l \in landmarks} weight_l \times distance(i, l)
|
||||
$$
|
||||
|
||||
Dijkstra 每批处理 16 个代表点,内存中只保留当前距离块。不可达距离按原算法约定计为 0,避免断开分支得到无穷评分。
|
||||
|
||||
## 评分约束下的混合覆盖选点
|
||||
|
||||
最终排序使用对数形式:
|
||||
|
||||
$$
|
||||
log\_score_i = log\_sensitivity_i + \log(distance\_sum_i)
|
||||
$$
|
||||
|
||||
对数是单调函数,因此该排序等价于比较 `sensitivity_i × distance_sum_i`,同时可以避免大数乘法溢出。
|
||||
|
||||
最终选点不再对全部候选节点执行 KMeans。KMeans 的目标函数按候选节点数计权,节点密集区域即使地理范围较小,也会获得更多聚类中心。本次改为评分约束下的最远空白区优先策略。
|
||||
|
||||
候选坐标使用同一个尺度因子归一化,保留管网原始长宽比。初始状态下开启的管道按物理长度构建无向覆盖图,开启的泵和阀门作为点连接;关闭连接会形成独立区域。监测点名额首先按各连通区域的有效管道长度分配:名额足够时每个区域至少一个,其余名额按最大余数法分配,并受该区域候选数量限制。
|
||||
|
||||
选点按区域名额从多到少处理,使主干管网先形成覆盖骨架。第一个区域的首点取综合评分最高的候选;后续区域的首点也必须考虑此前所有区域的已选点,先进入全局地图空白度前 70% 的候选集,再比较综合评分。这一约束避免两个拓扑断开但地图上重叠或相邻的区域各自选择一个近邻高分点。
|
||||
|
||||
设全局已选集合为 `S`,其余候选的最近地图距离和本连通区域内的最近管网路径距离分别为:
|
||||
|
||||
$$
|
||||
g_i = \min_{s \in S} ||x_i-x_s||, \qquad
|
||||
t_i = \min_{s \in S} shortest\_path(i, s)
|
||||
$$
|
||||
|
||||
两类距离分别除以当前未选候选中的最大值,混合空白度取两者较大值:
|
||||
|
||||
$$
|
||||
coverage_i = \max\left(\frac{g_i}{\max g}, \frac{t_i}{\max t}\right)
|
||||
$$
|
||||
|
||||
每轮只保留空白度达到当前最大值 70% 的候选,再从中选择综合评分最高的节点。地图距离始终相对全部已选区域更新,管网路径距离在当前连通区域内更新。这样地图或管网路径中任一维度仍有明显空白时,该区域都不会被高密度节点误判为已经覆盖。综合评分相同时按节点 ID 排序,结果数量、唯一性和可复现性保持不变。
|
||||
|
||||
实现只维护候选节点的最近距离数组。每新增一个监测点,执行一次单源 Dijkstra 并增量更新,不构造候选节点两两距离矩阵。
|
||||
|
||||
所有模型使用同一套算法和相同参数,不根据节点数量切换实现。
|
||||
|
||||
## EPANET 只计算初始状态
|
||||
|
||||
后续计算只读取水力结果的第一个时刻。新实现会临时将 `wn.options.time.duration` 设置为 0,只运行一次 EPANET 初始状态模拟,并在结束或异常后恢复原始时长。
|
||||
|
||||
EPANET 中间文件写入独立的 `TemporaryDirectory`,任务结束后自动清理。并发请求不再共用工作目录下的 `temp.*` 文件。
|
||||
|
||||
旧调用链最多重复运行约四次 EPANET,新调用链只运行一次,也不会分配没有使用的完整时序结果。
|
||||
|
||||
## 最小管径规则
|
||||
|
||||
`min_diameter` 的接口单位是毫米,WNTR 中的管径单位是米。准备阶段使用以下换算:
|
||||
|
||||
$$
|
||||
diameter\_mm = diameter\_m \times 1000
|
||||
$$
|
||||
|
||||
节点连接的管道中,只要至少一根达到最小管径,该节点就可以作为安装候选。小管径管道仍参加全网水力和灵敏度计算,管径条件只限制监测点安装位置。
|
||||
|
||||
当候选节点少于请求的监测点数量时,算法会返回包含候选数量和请求数量的明确错误。
|
||||
|
||||
## 复杂度变化
|
||||
|
||||
下表中的 `n` 为参与分析的节点数,`m` 为参与分析的管道数,`k=256` 为灵敏度投影数,`l≤256` 为水力距离代表点数,`b=16` 为批大小。
|
||||
|
||||
| 环节 | 原实现 | 新实现 |
|
||||
|---|---|---|
|
||||
| 节点与管道关系 | 稠密 `n × m` | CSR 稀疏矩阵,约 `O(n + m)` |
|
||||
| 节点关系与距离 | 多个稠密 `n × n` 矩阵 | 稀疏有向图和流式距离块 |
|
||||
| 灵敏度 | 稠密行列式、逆矩阵或伪逆,时间接近 `O(n³)` | 一次稀疏 LU 分解和 `k` 次稀疏求解 |
|
||||
| 灵敏度结果 | 保存完整 `n × m` 响应矩阵 | 保存 `n` 个对数灵敏度和当前投影批 |
|
||||
| 水力距离 | 从全部节点执行最短路径并保存 `n × n` 结果 | 从至多 `l` 个代表点执行 Dijkstra,每批 `b` 个 |
|
||||
| 最终选点 | 按节点密度分配的完整 KMeans | 连通区域配额和 70% 混合覆盖,线性距离数组 |
|
||||
| 水力模拟 | 调用链中重复执行,并可能保存完整时序 | 一次初始状态模拟 |
|
||||
|
||||
稀疏 LU 的时间和内存不能简单视为线性,其填充程度受网络拓扑影响。当前压力测试覆盖到 20.3 万原始节点,不能据此保证任意更大或连接更稠密的模型都保持相同比例。
|
||||
|
||||
## 精度取舍
|
||||
|
||||
新实现不逐元素生成旧算法的完整精确矩阵,而是估计最终排序需要的两个统计量:
|
||||
|
||||
- Cauchy 投影估计压力响应矩阵每一行的 L1 范数。
|
||||
- 加权空间代表点估计节点到全网的水力距离总和。
|
||||
|
||||
单元测试使用小型模型构造完整稠密参考结果,要求近似方案选点的精确参考目标值不低于精确方案的 95%。本地模型对比结果如下:
|
||||
|
||||
| 模型 | 近似选点的精确参考目标比 |
|
||||
|---|---:|
|
||||
| `fengxian.inp` | 98.80% |
|
||||
| MD 模型 | 99.71% |
|
||||
|
||||
固定随机种子和固定采样数保证同一模型、监测点数量和最小管径得到相同结果。近似排序仍可能与完整稠密算法不同,特别是多个候选节点得分接近时。
|
||||
|
||||
混合覆盖会主动放弃部分集中在同一区域的高分节点。单点综合评分之和因此不是唯一质量指标,还需要同时检查未覆盖半径、最小点间距和入选节点的评分百分位。
|
||||
|
||||
## 资源压力测试记录
|
||||
|
||||
以下结果来自 2026-08-03 的本地验证。环境为 Linux、Python 3.12、Conda `server` 环境,主机物理内存约 30 GiB。测试参数统一为 20 个监测点、最小管径 0。
|
||||
|
||||
外部看门狗使用以下停止条件:
|
||||
|
||||
- 算法进程树 RSS 达到 7.5 GiB。
|
||||
- 系统可用内存低于 6 GiB。
|
||||
- 单模型运行超过 600 秒。
|
||||
- 子进程虚拟地址空间硬限制为 8 GiB。
|
||||
|
||||
任一条件满足时,看门狗会终止整个进程组。三次压力测试均未触发停止条件。
|
||||
|
||||
| 模型 | 原始节点 | 实际分析节点 | 实际分析管道 | 算法流水线耗时 | 峰值 RSS |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| `temp/leakage/temp_3698123.inp` | 31,143 | 28,723 | 29,974 | 2.77 秒 | 467 MiB |
|
||||
| `inp/jbh.inp` | 94,049 | 69,949 | 82,369 | 8.92 秒 | 980 MiB |
|
||||
| `inp/Todo/v-16常熟模型.inp` | 203,569 | 145,948 | 176,512 | 20.00 秒 | 1.84 GiB |
|
||||
|
||||
实际分析节点少于原始节点,是因为水库、水箱、泵和阀门边界节点按算法规则排除。20.3 万节点模型原文件使用非标准的 `REPORTING TIMESTEP`,并且缺少 `[END]`。压力测试只在隔离临时副本中将其规范化,没有修改原文件。
|
||||
|
||||
20.3 万节点模型各阶段耗时如下:
|
||||
|
||||
| 阶段 | 耗时 |
|
||||
|---|---:|
|
||||
| 模型加载 | 6.63 秒 |
|
||||
| EPANET 初始状态模拟 | 8.30 秒 |
|
||||
| 稀疏数据准备 | 3.00 秒 |
|
||||
| 压力灵敏度估计 | 0.76 秒 |
|
||||
| 水力距离估计 | 0.94 秒 |
|
||||
| 监测点选择 | 0.37 秒 |
|
||||
|
||||
### `tjwater` 分布优化前后对比
|
||||
|
||||
2026-08-03 使用 `db_inp/tjwater.db.inp`、20 个监测点、最小管径 0 进行同机对比。进程通过 systemd scope 限制在 2.5 GiB 内存,并设置 90 秒超时;两次运行均未触发保护。覆盖距离使用保持长宽比后的模型坐标,按管网最大轴跨度归一化。
|
||||
|
||||
| 指标 | KMeans 选点 | 70% 混合覆盖 | 变化 |
|
||||
|---|---:|---:|---:|
|
||||
| 实际分析/候选节点 | 87,877 | 87,877 | 不变 |
|
||||
| 最大地图覆盖半径 | 0.176786 | 0.173941 | -1.61% |
|
||||
| P95 地图覆盖半径 | 0.122093 | 0.090686 | -25.72% |
|
||||
| 最小监测点间距 | 0.000559 | 0.040887 | 约 73.2 倍 |
|
||||
| 综合评分中位百分位 | 98.76% | 95.41% | -3.35 个百分点 |
|
||||
| 端到端耗时 | 12.34 秒 | 11.71 秒 | -5.11% |
|
||||
| 峰值 RSS | 913 MiB | 932 MiB | +19 MiB |
|
||||
|
||||
结果达到预定验收条件:P95 覆盖半径下降超过 15%,评分中位百分位下降少于 10 个百分点,运行时间没有增加,峰值内存增加远低于 256 MiB。
|
||||
|
||||
### 2026-08-03:跨连通区域共享全局间距
|
||||
|
||||
首次混合覆盖结果中,节点 `121302` 与 `110874` 分属两个不可达连通区域,但地图距离只有约 550.47 个模型单位。旧的分区独立首点规则分别选中了两个区域的最高分节点,形成视觉近邻。加入跨区域全局地图间距后,`121302` 被替换,最小归一化点间距由 0.013685 进一步提高到 0.040887。
|
||||
|
||||
本次调整保留连通区域名额,改变区域之间互不感知的选点方式。区域按名额从多到少处理,主干管网先形成覆盖骨架。第一个区域仍从综合评分最高的候选开始;后续区域选择首点时,先计算该区域所有候选到全局已选点的最近地图距离,只保留达到本区域最大空白距离 70% 的候选,再比较灵敏度综合评分。区域内部后续选点继续使用地图距离与管网路径距离的混合空白度。
|
||||
|
||||
这项约束只影响跨区域首点选择,不改变灵敏度估计、水力距离估计、区域名额、最小管径规则和公开 API。合成回归模型会构造两个地图上重叠但拓扑断开的区域,确保算法不会再次分别选择两个相邻高分点。
|
||||
|
||||
## 测试与回归验证
|
||||
|
||||
新增测试位于 `tests/unit/test_sensor_sensitivity.py`,覆盖以下行为:
|
||||
|
||||
- 相同输入返回相同节点,并且每次调用只运行一次 EPANET。
|
||||
- EPANET 只保留初始状态,模型原始模拟时长能够恢复。
|
||||
- 关联矩阵和距离图保持 CSR 稀疏格式。
|
||||
- 最小管径按毫米过滤安装候选。
|
||||
- 近似方案在稠密参考目标上的结果不低于 95%。
|
||||
- 高密西部、低密东部的合成模型不再按候选节点密度分配名额。
|
||||
- 地理位置相邻但拓扑断开的区域在名额允许时分别获得监测点。
|
||||
- 地图上重叠的独立区域共享全局地理间距,不能各自选择相邻的首个高分点。
|
||||
- 名额不足时优先覆盖有效管道长度更大的连通区域。
|
||||
- 重复坐标依靠管网路径距离继续选点,并保持数量和确定性。
|
||||
- 非法监测点数量和最小管径返回明确错误。
|
||||
- 模拟结束后不残留共享 `temp.*` 文件。
|
||||
|
||||
回归命令:
|
||||
|
||||
```bash
|
||||
conda run -n server python -m pytest \
|
||||
tests/unit \
|
||||
tests/auth \
|
||||
tests/api/test_sensor_placement_endpoints.py \
|
||||
tests/api/test_simulation_endpoints.py \
|
||||
-q
|
||||
```
|
||||
|
||||
2026-08-03 的执行结果为 `142 passed, 2 skipped, 7 warnings`。`git diff --check` 同时通过。
|
||||
|
||||
## 实现边界
|
||||
|
||||
- 256 次投影和 256 个代表点是当前质量与性能验证后的固定参数。调整参数需要重新运行稠密参考质量测试和大模型压力测试。
|
||||
- 稀疏 LU 对高连接度或拓扑特殊的模型可能产生更多填充,应继续用进程级资源保护运行未知大模型。
|
||||
- 算法使用单个初始水力状态。如果业务目标改为覆盖全天多个工况,需要先定义多工况评分和结果合并规则,不能直接恢复长时段模拟后沿用当前评分。
|
||||
- 节点坐标用于代表点构建和地图覆盖,假定其能表达一致的平面相对距离;缺少有效二维坐标的模型会返回错误。
|
||||
- 物理覆盖图使用初始水力状态。全天工况中频繁开闭的阀门或泵需要在多工况方案中重新定义连通区域合并规则。
|
||||
- 当前压力测试验证到 203,569 个原始节点,没有验证 30 万节点模型。
|
||||
@@ -16,12 +16,8 @@ services:
|
||||
- ../../resources:/app/resources
|
||||
environment:
|
||||
- PYTHONPATH=/app
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=${REDIS_PORT}
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
# Add other DB connections here as needed by your app
|
||||
depends_on:
|
||||
- redis
|
||||
- timescaledb
|
||||
- postgis
|
||||
|
||||
@@ -29,17 +25,6 @@ services:
|
||||
# Infrastructure Services
|
||||
# ==========================================
|
||||
|
||||
# --- Redis ---
|
||||
redis:
|
||||
image: redis:latest
|
||||
container_name: redis
|
||||
restart: always
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
ports:
|
||||
- "${REDIS_PORT}:6379"
|
||||
volumes:
|
||||
- ./redis/data:/data
|
||||
|
||||
# --- Keycloak ---
|
||||
keycloakDB:
|
||||
image: postgis/postgis:14-3.5
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# Keycloak 登录主题
|
||||
|
||||
`themes/tjwater` 是“供水管网水力模型及人工智能系统”的 Keycloak 登录主题。
|
||||
主题继承 `keycloak.v2`,只覆盖样式、消息和本地 SVG 资源,不修改认证模板或
|
||||
认证流程。
|
||||
`themes/tjwater` 是 TJWater 智慧水务平台的 Keycloak 登录主题。主题继承
|
||||
`keycloak.v2`,只覆盖样式、消息和本地 SVG 资源,不修改认证模板或认证流程。
|
||||
|
||||
## 在管理控制台切换登录主题
|
||||
|
||||
@@ -80,13 +79,13 @@ bash infra/docker/keycloak/configure-theme.sh apply
|
||||
```
|
||||
|
||||
脚本默认配置 `tjwater` realm、简体中文默认语言、中英文切换和
|
||||
`供水管网水力模型及人工智能系统` 品牌名,并清除 `tjwater` client 对登录
|
||||
主题的覆盖,使其继承 realm 主题。其他客户环境可临时覆盖:
|
||||
`TJWater 智慧水务平台` 品牌名,并清除 `tjwater` client 对登录主题的覆盖,
|
||||
使其继承 realm 主题。其他环境可临时覆盖:
|
||||
|
||||
```bash
|
||||
TJWATER_KEYCLOAK_REALM=example \
|
||||
TJWATER_KEYCLOAK_CLIENT_ID=example-web \
|
||||
TJWATER_KEYCLOAK_DISPLAY_NAME="客户供水智能系统" \
|
||||
TJWATER_KEYCLOAK_DISPLAY_NAME="示例智慧水务平台" \
|
||||
bash infra/docker/keycloak/configure-theme.sh apply
|
||||
```
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ repo_root="$(cd -- "${script_dir}/../../.." && pwd)"
|
||||
compose_file="${repo_root}/infra/docker/docker-compose.yml"
|
||||
realm="${TJWATER_KEYCLOAK_REALM:-tjwater}"
|
||||
client_id="${TJWATER_KEYCLOAK_CLIENT_ID:-tjwater}"
|
||||
display_name="${TJWATER_KEYCLOAK_DISPLAY_NAME:-供水管网水力模型及人工智能系统}"
|
||||
display_name="${TJWATER_KEYCLOAK_DISPLAY_NAME:-TJWater 智慧水务平台}"
|
||||
|
||||
case "${action}" in
|
||||
apply|verify|rollback) ;;
|
||||
|
||||
+105205
File diff suppressed because it is too large
Load Diff
+26498
File diff suppressed because it is too large
Load Diff
+30973
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+111322
File diff suppressed because it is too large
Load Diff
+328198
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,213 @@
|
||||
[TITLE]
|
||||
Hanoi example by Fujiwara and Khang, Water Resources Research, 1990
|
||||
|
||||
[JUNCTIONS]
|
||||
;ID Elev Demand Pattern
|
||||
2 0 890 ;
|
||||
3 0 850 ;
|
||||
4 0 130 ;
|
||||
5 0 725 ;
|
||||
6 0 1005 ;
|
||||
7 0 1350 ;
|
||||
8 0 550 ;
|
||||
9 0 525 ;
|
||||
10 0 525 ;
|
||||
11 0 500 ;
|
||||
12 0 560 ;
|
||||
13 0 940 ;
|
||||
14 0 615 ;
|
||||
15 0 280 ;
|
||||
16 0 310 ;
|
||||
17 0 865 ;
|
||||
18 0 1345 ;
|
||||
19 0 60 ;
|
||||
20 0 1275 ;
|
||||
21 0 930 ;
|
||||
22 0 485 ;
|
||||
23 0 1045 ;
|
||||
24 0 820 ;
|
||||
25 0 170 ;
|
||||
26 0 900 ;
|
||||
27 0 370 ;
|
||||
28 0 290 ;
|
||||
29 0 360 ;
|
||||
30 0 360 ;
|
||||
31 0 105 ;
|
||||
32 0 805 ;
|
||||
|
||||
[RESERVOIRS]
|
||||
;ID Head Pattern
|
||||
1 100.0 ;
|
||||
|
||||
[TANKS]
|
||||
;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve
|
||||
|
||||
[PIPES]
|
||||
;ID Node1 Node2 Length Diameter Roughness MinorLoss Status
|
||||
1 1 2 100 0.0001 130 0 open ;
|
||||
2 2 3 1350 0.0001 130 0 open ;
|
||||
3 3 4 900 0.0001 130 0 open ;
|
||||
4 4 5 1150 0.0001 130 0 open ;
|
||||
5 5 6 1450 0.0001 130 0 open ;
|
||||
6 6 7 450 0.0001 130 0 open ;
|
||||
7 7 8 850 0.0001 130 0 open ;
|
||||
8 8 9 850 0.0001 130 0 open ;
|
||||
9 9 10 800 0.0001 130 0 open ;
|
||||
10 10 11 950 0.0001 130 0 open ;
|
||||
11 11 12 1200 0.0001 130 0 open ;
|
||||
12 12 13 3500 0.0001 130 0 open ;
|
||||
13 10 14 800 0.0001 130 0 open ;
|
||||
14 14 15 500 0.0001 130 0 open ;
|
||||
15 15 16 550 0.0001 130 0 open ;
|
||||
16 17 16 2730 0.0001 130 0 open ;
|
||||
17 18 17 1750 0.0001 130 0 open ;
|
||||
18 19 18 800 0.0001 130 0 open ;
|
||||
19 3 19 400 0.0001 130 0 open ;
|
||||
20 3 20 2200 0.0001 130 0 open ;
|
||||
21 20 21 1500 0.0001 130 0 open ;
|
||||
22 21 22 500 0.0001 130 0 open ;
|
||||
23 20 23 2650 0.0001 130 0 open ;
|
||||
24 23 24 1230 0.0001 130 0 open ;
|
||||
25 24 25 1300 0.0001 130 0 open ;
|
||||
26 26 25 850 0.0001 130 0 open ;
|
||||
27 27 26 300 0.0001 130 0 open ;
|
||||
28 16 27 750 0.0001 130 0 open ;
|
||||
29 23 28 1500 0.0001 130 0 open ;
|
||||
30 28 29 2000 0.0001 130 0 open ;
|
||||
31 29 30 1600 0.0001 130 0 open ;
|
||||
32 30 31 150 0.0001 130 0 open ;
|
||||
33 32 31 860 0.0001 130 0 open ;
|
||||
34 25 32 950 0.0001 130 0 open ;
|
||||
|
||||
[PUMPS]
|
||||
;ID Node1 Node2 Parameters
|
||||
|
||||
[VALVES]
|
||||
;ID Node1 Node2 Diameter Type Setting MinorLoss
|
||||
|
||||
[TAGS]
|
||||
|
||||
[DEMANDS]
|
||||
;Junction Demand Pattern Category
|
||||
|
||||
[STATUS]
|
||||
;ID Status/Setting
|
||||
|
||||
[PATTERNS]
|
||||
;ID Multipliers
|
||||
|
||||
[CURVES]
|
||||
;ID X-Value Y-Value
|
||||
|
||||
[CONTROLS]
|
||||
|
||||
[RULES]
|
||||
|
||||
[ENERGY]
|
||||
Global Efficiency 75
|
||||
Global Price 0
|
||||
Demand Charge 0
|
||||
|
||||
[EMITTERS]
|
||||
;Junction Coefficient
|
||||
|
||||
[QUALITY]
|
||||
;Node InitQual
|
||||
|
||||
[SOURCES]
|
||||
;Node Type Quality Pattern
|
||||
|
||||
[REACTIONS]
|
||||
;Type Pipe/Tank Coefficient
|
||||
|
||||
|
||||
[REACTIONS]
|
||||
Order Bulk 1
|
||||
Order Wall 1
|
||||
Global Bulk 0
|
||||
Global Wall 0
|
||||
Limiting Potential 0
|
||||
Roughness Correlation 0
|
||||
|
||||
[MIXING]
|
||||
;Tank Model
|
||||
|
||||
[TIMES]
|
||||
Duration 0
|
||||
Hydraulic Timestep 1:00
|
||||
Quality Timestep 0:05
|
||||
Pattern Timestep 1:00
|
||||
Pattern Start 0:00
|
||||
Report Timestep 1:00
|
||||
Report Start 0:00
|
||||
Start ClockTime 12 am
|
||||
Statistic None
|
||||
|
||||
[REPORT]
|
||||
Status No
|
||||
Summary No
|
||||
Page 0
|
||||
|
||||
[OPTIONS]
|
||||
Units CMH
|
||||
Headloss H-W
|
||||
Specific Gravity 1
|
||||
Viscosity 1
|
||||
Trials 40
|
||||
Accuracy 0.001
|
||||
Unbalanced Continue 10
|
||||
Pattern 1
|
||||
Demand Multiplier 1.0
|
||||
Emitter Exponent 0.5
|
||||
Quality NONE mg/L
|
||||
Diffusivity 1
|
||||
Tolerance 0.01
|
||||
|
||||
[COORDINATES]
|
||||
;Node X-Coord Y-Coord
|
||||
2 5021.20 1582.17
|
||||
3 5025.20 2585.42
|
||||
4 5874.22 2588.30
|
||||
5 6873.11 2588.30
|
||||
6 8103.51 2585.42
|
||||
7 8103.51 3234.67
|
||||
8 8106.66 4179.28
|
||||
9 8106.66 5133.78
|
||||
10 7318.64 5133.78
|
||||
11 7319.94 5831.65
|
||||
12 7319.94 6671.19
|
||||
13 5636.76 6676.24
|
||||
14 6530.63 5133.78
|
||||
15 5676.02 5133.78
|
||||
16 5021.20 5133.78
|
||||
17 5021.20 4412.36
|
||||
18 5021.20 3868.52
|
||||
19 5021.20 3191.49
|
||||
20 3587.87 2588.30
|
||||
21 3587.87 1300.84
|
||||
22 3587.87 901.29
|
||||
23 1978.55 2588.30
|
||||
24 1975.58 4084.35
|
||||
25 1980.46 5137.63
|
||||
26 3077.46 5137.63
|
||||
27 3933.52 5133.78
|
||||
28 846.04 2588.20
|
||||
29 -552.41 2588.20
|
||||
30 -552.38 4369.06
|
||||
31 -549.36 5137.63
|
||||
32 536.45 5137.63
|
||||
1 5360.71 1354.05
|
||||
|
||||
[VERTICES]
|
||||
;Link X-Coord Y-Coord
|
||||
|
||||
[LABELS]
|
||||
;X-Coord Y-Coord Label & Anchor Node
|
||||
|
||||
[BACKDROP]
|
||||
DIMENSIONS -985.92 612.54 8551.27 6964.99
|
||||
UNITS None
|
||||
FILE
|
||||
OFFSET 0.00 0.00
|
||||
|
||||
[END]
|
||||
@@ -0,0 +1,132 @@
|
||||
[TITLE]
|
||||
|
||||
|
||||
[JUNCTIONS]
|
||||
;ID Elev Demand Pattern
|
||||
1 0 0 ;
|
||||
2 0 0 ;
|
||||
3 0 0 ;
|
||||
4 0 0 ;
|
||||
5 0 0 ;
|
||||
|
||||
[RESERVOIRS]
|
||||
;ID Head Pattern
|
||||
|
||||
[TANKS]
|
||||
;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve
|
||||
|
||||
[PIPES]
|
||||
;ID Node1 Node2 Length Diameter Roughness MinorLoss Status
|
||||
1 3 2 1000 12 100 0 Open ;
|
||||
|
||||
[PUMPS]
|
||||
;ID Node1 Node2 Parameters
|
||||
|
||||
[VALVES]
|
||||
;ID Node1 Node2 Diameter Type Setting MinorLoss
|
||||
|
||||
[TAGS]
|
||||
|
||||
[DEMANDS]
|
||||
;Junction Demand Pattern Category
|
||||
|
||||
[STATUS]
|
||||
;ID Status/Setting
|
||||
|
||||
[PATTERNS]
|
||||
;ID Multipliers
|
||||
|
||||
[CURVES]
|
||||
;ID X-Value Y-Value
|
||||
|
||||
[CONTROLS]
|
||||
|
||||
|
||||
[RULES]
|
||||
|
||||
|
||||
[ENERGY]
|
||||
Global Efficiency 75
|
||||
Global Price 0
|
||||
Demand Charge 0
|
||||
|
||||
[EMITTERS]
|
||||
;Junction Coefficient
|
||||
|
||||
[QUALITY]
|
||||
;Node InitQual
|
||||
|
||||
[SOURCES]
|
||||
;Node Type Quality Pattern
|
||||
|
||||
[REACTIONS]
|
||||
;Type Pipe/Tank Coefficient
|
||||
|
||||
|
||||
[REACTIONS]
|
||||
Order Bulk 1
|
||||
Order Tank 1
|
||||
Order Wall 1
|
||||
Global Bulk 0
|
||||
Global Wall 0
|
||||
Limiting Potential 0
|
||||
Roughness Correlation 0
|
||||
|
||||
[MIXING]
|
||||
;Tank Model
|
||||
|
||||
[TIMES]
|
||||
Duration 0:00
|
||||
Hydraulic Timestep 1:00
|
||||
Quality Timestep 0:05
|
||||
Pattern Timestep 1:00
|
||||
Pattern Start 0:00
|
||||
Report Timestep 1:00
|
||||
Report Start 0:00
|
||||
Start ClockTime 12 am
|
||||
Statistic NONE
|
||||
|
||||
[REPORT]
|
||||
Status No
|
||||
Summary No
|
||||
Page 0
|
||||
|
||||
[OPTIONS]
|
||||
Units GPM
|
||||
Headloss H-W
|
||||
Specific Gravity 1
|
||||
Viscosity 1
|
||||
Trials 40
|
||||
Accuracy 0.001
|
||||
CHECKFREQ 2
|
||||
MAXCHECK 10
|
||||
DAMPLIMIT 0
|
||||
Unbalanced Continue 10
|
||||
Pattern 1
|
||||
Demand Multiplier 1.0
|
||||
Emitter Exponent 0.5
|
||||
Quality None mg/L
|
||||
Diffusivity 1
|
||||
Tolerance 0.01
|
||||
|
||||
[COORDINATES]
|
||||
;Node X-Coord Y-Coord
|
||||
1 455.97 6698.11
|
||||
2 6022.01 3616.35
|
||||
3 7374.21 6509.43
|
||||
4 3128.93 5786.16
|
||||
5 2122.64 2358.49
|
||||
|
||||
[VERTICES]
|
||||
;Link X-Coord Y-Coord
|
||||
|
||||
[LABELS]
|
||||
;X-Coord Y-Coord Label & Anchor Node
|
||||
|
||||
[BACKDROP]
|
||||
DIMENSIONS 0.00 0.00 10000.00 10000.00
|
||||
UNITS None
|
||||
FILE
|
||||
OFFSET 0.00 0.00
|
||||
|
||||
[END]
|
||||
@@ -0,0 +1,213 @@
|
||||
[TITLE]
|
||||
|
||||
|
||||
[JUNCTIONS]
|
||||
;ID Elev Demand Pattern
|
||||
N23 0 0 ;
|
||||
N1 18 5 ;
|
||||
N2 18 10 ;
|
||||
N3 14 0 ;
|
||||
N4 12 5 ;
|
||||
N5 14 30 ;
|
||||
N24 0 0 ;
|
||||
N25 0 0 ;
|
||||
N14 20 5 ;
|
||||
N13 23 0 ;
|
||||
N16 10 0 ;
|
||||
N17 7 0 ;
|
||||
N19 10 5 ;
|
||||
N20 7 0 ;
|
||||
N21 10 0 ;
|
||||
N22 15 20 ;
|
||||
N18 8 5 ;
|
||||
N6 15 10 ;
|
||||
N7 14.5 0 ;
|
||||
N8 14 20 ;
|
||||
N9 14 0 ;
|
||||
N10 15 5 ;
|
||||
N11 12 10 ;
|
||||
N12 15 0 ;
|
||||
N15 8 20 ;
|
||||
|
||||
[RESERVOIRS]
|
||||
;ID Head Pattern
|
||||
|
||||
[TANKS]
|
||||
;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve
|
||||
1 0 54.66 54.5 56 1000000 0 ;
|
||||
2 0 54.60 54.5 55.5 1000000 0 ;
|
||||
3 0 54.50 54 55.5 1000000 0 ;
|
||||
|
||||
[PIPES]
|
||||
;ID Node1 Node2 Length Diameter Roughness MinorLoss Status
|
||||
P1 N23 N1 606 457 110 0 Open ;
|
||||
P9 N1 N2 1930 457 110 0 Open ;
|
||||
P10 N2 N3 5150 305 10 0 Open ;
|
||||
P30 N3 N4 326 0.152 100 0 Open ;
|
||||
P31 N4 N5 844 229 110 0 Open ;
|
||||
P35 N5 N22 1408 152 100 0 Open ;
|
||||
P36 N5 N7 500 381 110 0 Open ;
|
||||
P34 N7 N6 615 381 110 0 Open ;
|
||||
P32 N6 N3 1274 152 100 0 Open ;
|
||||
P27 N6 N8 743 381 110 0 Open ;
|
||||
P26 N8 N9 443 229 90 0 Open ;
|
||||
P37 N9 N6 300 229 90 0 Open ;
|
||||
P25 N8 N10 249 305 105 0 Open ;
|
||||
P5 N24 N10 3383 305 100 0 Open ;
|
||||
P2 N23 N24 454 457 110 0 Open ;
|
||||
P3 N24 N14 2782 229 105 0 Open ;
|
||||
P4 N14 N25 304 381 135 0 Open ;
|
||||
P6 N24 N13 1767 475 110 0 Open ;
|
||||
P7 N13 N14 1014 381 135 0 Open ;
|
||||
P23 N10 N11 542 229 90 0 Open ;
|
||||
P22 N11 N12 777 229 90 0 Open ;
|
||||
P24 N8 N12 1600 457 110 0 Open ;
|
||||
P8 N25 N16 1014 381 6 0 Open ;
|
||||
P13 N16 N17 822 305 140 0 Open ;
|
||||
P16 N17 N19 1072 229 90 0 Open ;
|
||||
P17 N19 N20 864 152 90 0 Open ;
|
||||
P18 N20 N21 711 152 90 0 Open ;
|
||||
P14 N17 N18 411 152 100 0 Open ;
|
||||
P15 N20 N18 701 229 110 0 Open ;
|
||||
P20 N15 N22 2334 152 100 0 Open ;
|
||||
P19 N15 N21 832 152 90 0 Open ;
|
||||
P12 N15 N16 914 229 125 0 Open ;
|
||||
0 1 N23 1 1000 110 0 Open ;
|
||||
16 2 N24 1 1000 100 0 Open ;
|
||||
20 3 N25 1 1000 100 0 Open ;
|
||||
P11 N13 N12 762 457 110 0 Open ;
|
||||
P21 N12 N15 1996 0.229 95 0 Open ;
|
||||
P28 N8 N22 931 229 125 0 Open ;
|
||||
P29 N21 N22 2689 152 100 0 Open ;
|
||||
P33 N5 N6 1115 229 90 0 Open ;
|
||||
|
||||
[PUMPS]
|
||||
;ID Node1 Node2 Parameters
|
||||
|
||||
[VALVES]
|
||||
;ID Node1 Node2 Diameter Type Setting MinorLoss
|
||||
|
||||
[TAGS]
|
||||
|
||||
[DEMANDS]
|
||||
;Junction Demand Pattern Category
|
||||
|
||||
[STATUS]
|
||||
;ID Status/Setting
|
||||
|
||||
[PATTERNS]
|
||||
;ID Multipliers
|
||||
|
||||
[CURVES]
|
||||
;ID X-Value Y-Value
|
||||
|
||||
[CONTROLS]
|
||||
|
||||
[RULES]
|
||||
|
||||
[ENERGY]
|
||||
Global Efficiency 75
|
||||
Global Price 0
|
||||
Demand Charge 0
|
||||
|
||||
[EMITTERS]
|
||||
;Junction Coefficient
|
||||
|
||||
[QUALITY]
|
||||
;Node InitQual
|
||||
|
||||
[SOURCES]
|
||||
;Node Type Quality Pattern
|
||||
|
||||
[REACTIONS]
|
||||
;Type Pipe/Tank Coefficient
|
||||
|
||||
|
||||
[REACTIONS]
|
||||
Order Bulk 1
|
||||
Order Tank 1
|
||||
Order Wall 1
|
||||
Global Bulk 0
|
||||
Global Wall 0
|
||||
Limiting Potential 0
|
||||
Roughness Correlation 0
|
||||
|
||||
[MIXING]
|
||||
;Tank Model
|
||||
|
||||
[TIMES]
|
||||
Duration 0
|
||||
Hydraulic Timestep 1:00
|
||||
Quality Timestep 0:05
|
||||
Pattern Timestep 1:00
|
||||
Pattern Start 0:00
|
||||
Report Timestep 1:00
|
||||
Report Start 0:00
|
||||
Start ClockTime 12 am
|
||||
Statistic None
|
||||
|
||||
[REPORT]
|
||||
Status No
|
||||
Summary No
|
||||
Page 0
|
||||
|
||||
[OPTIONS]
|
||||
Units LPS
|
||||
Headloss H-W
|
||||
Specific Gravity 1
|
||||
Viscosity 1
|
||||
Trials 40
|
||||
Accuracy 0.001
|
||||
Unbalanced Continue 10
|
||||
Pattern 1
|
||||
Demand Multiplier 1.0
|
||||
Emitter Exponent 0.5
|
||||
Quality None mg/L
|
||||
Diffusivity 1
|
||||
Tolerance 0.01
|
||||
|
||||
[COORDINATES]
|
||||
;Node X-Coord Y-Coord
|
||||
N23 2146.34 8439.02
|
||||
N1 3268.29 8471.54
|
||||
N2 4585.37 8455.28
|
||||
N3 6081.30 8422.76
|
||||
N4 7121.95 8504.07
|
||||
N5 8260.16 8520.33
|
||||
N24 2048.78 6536.59
|
||||
N25 1918.70 3821.14
|
||||
N14 2048.78 4796.75
|
||||
N13 2845.53 5235.77
|
||||
N16 3154.47 3739.84
|
||||
N17 4455.28 3804.88
|
||||
N19 5560.98 3837.40
|
||||
N20 6601.63 3788.62
|
||||
N21 7804.88 3788.62
|
||||
N22 9203.25 3788.62
|
||||
N18 6162.60 3317.07
|
||||
N6 6845.53 7268.29
|
||||
N7 7918.70 7235.77
|
||||
N8 6357.72 6552.85
|
||||
N9 6975.61 6552.85
|
||||
N10 4065.04 6439.02
|
||||
N11 4048.78 5723.58
|
||||
N12 5056.91 5040.65
|
||||
N15 5073.17 4552.85
|
||||
1 1674.80 8520.33
|
||||
2 1170.73 6536.59
|
||||
3 1170.73 4016.26
|
||||
|
||||
[VERTICES]
|
||||
;Link X-Coord Y-Coord
|
||||
16 1170.73 6308.94
|
||||
|
||||
[LABELS]
|
||||
;X-Coord Y-Coord Label & Anchor Node
|
||||
|
||||
[BACKDROP]
|
||||
DIMENSIONS 0.00 0.00 10000.00 10000.00
|
||||
UNITS None
|
||||
FILE
|
||||
OFFSET 0.00 0.00
|
||||
|
||||
[END]
|
||||
+14197
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,178 @@
|
||||
[TITLE]
|
||||
EPANET Example Network 1
|
||||
A simple example of modeling chlorine decay. Both bulk and
|
||||
wall reactions are included.
|
||||
|
||||
[JUNCTIONS]
|
||||
;ID Elev Demand Pattern
|
||||
10 710 0 ;
|
||||
11 710 150 ;
|
||||
12 700 150 ;
|
||||
13 695 100 ;
|
||||
21 700 150 ;
|
||||
22 695 200 ;
|
||||
23 690 150 ;
|
||||
31 700 100 ;
|
||||
32 710 100 ;
|
||||
|
||||
[RESERVOIRS]
|
||||
;ID Head Pattern
|
||||
9 800 ;
|
||||
|
||||
[TANKS]
|
||||
;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve
|
||||
2 850 120 100 150 50.5 0 ;
|
||||
|
||||
[PIPES]
|
||||
;ID Node1 Node2 Length Diameter Roughness MinorLoss Status
|
||||
10 10 11 10530 18 100 0 Open ;
|
||||
11 11 12 5280 14 100 0 Open ;
|
||||
12 12 13 5280 10 100 0 Open ;
|
||||
21 21 22 5280 10 100 0 Open ;
|
||||
22 22 23 5280 12 100 0 Open ;
|
||||
31 31 32 5280 6 100 0 Open ;
|
||||
110 2 12 200 18 100 0 Open ;
|
||||
111 11 21 5280 10 100 0 Open ;
|
||||
112 12 22 5280 12 100 0 Open ;
|
||||
113 13 23 5280 8 100 0 Open ;
|
||||
121 21 31 5280 8 100 0 Open ;
|
||||
122 22 32 5280 6 100 0 Open ;
|
||||
|
||||
[PUMPS]
|
||||
;ID Node1 Node2 Parameters
|
||||
9 9 10 HEAD 1 ;
|
||||
|
||||
[VALVES]
|
||||
;ID Node1 Node2 Diameter Type Setting MinorLoss
|
||||
|
||||
[TAGS]
|
||||
|
||||
[DEMANDS]
|
||||
;Junction Demand Pattern Category
|
||||
|
||||
[STATUS]
|
||||
;ID Status/Setting
|
||||
|
||||
[PATTERNS]
|
||||
;ID Multipliers
|
||||
;Demand Pattern
|
||||
1 1.0 1.2 1.4 1.6 1.4 1.2
|
||||
1 1.0 0.8 0.6 0.4 0.6 0.8
|
||||
|
||||
[CURVES]
|
||||
;ID X-Value Y-Value
|
||||
;PUMP: Pump Curve for Pump 9
|
||||
1 1500 250
|
||||
|
||||
[CONTROLS]
|
||||
LINK 9 OPEN IF NODE 2 BELOW 110
|
||||
LINK 9 CLOSED IF NODE 2 ABOVE 140
|
||||
|
||||
|
||||
[RULES]
|
||||
|
||||
[ENERGY]
|
||||
Global Efficiency 75
|
||||
Global Price 0.0
|
||||
Demand Charge 0.0
|
||||
|
||||
[EMITTERS]
|
||||
;Junction Coefficient
|
||||
|
||||
[QUALITY]
|
||||
;Node InitQual
|
||||
10 0.5
|
||||
11 0.5
|
||||
12 0.5
|
||||
13 0.5
|
||||
21 0.5
|
||||
22 0.5
|
||||
23 0.5
|
||||
31 0.5
|
||||
32 0.5
|
||||
9 1.0
|
||||
2 1.0
|
||||
|
||||
[SOURCES]
|
||||
;Node Type Quality Pattern
|
||||
|
||||
[REACTIONS]
|
||||
;Type Pipe/Tank Coefficient
|
||||
|
||||
|
||||
[REACTIONS]
|
||||
Order Bulk 1
|
||||
Order Tank 1
|
||||
Order Wall 1
|
||||
Global Bulk -.5
|
||||
Global Wall -1
|
||||
Limiting Potential 0.0
|
||||
Roughness Correlation 0.0
|
||||
|
||||
[MIXING]
|
||||
;Tank Model
|
||||
|
||||
[TIMES]
|
||||
Duration 24:00
|
||||
Hydraulic Timestep 1:00
|
||||
Quality Timestep 0:05
|
||||
Pattern Timestep 2:00
|
||||
Pattern Start 0:00
|
||||
Report Timestep 1:00
|
||||
Report Start 0:00
|
||||
Start ClockTime 12 am
|
||||
Statistic None
|
||||
|
||||
[REPORT]
|
||||
Status Yes
|
||||
Summary No
|
||||
Page 0
|
||||
|
||||
[OPTIONS]
|
||||
Units GPM
|
||||
Headloss H-W
|
||||
Specific Gravity 1.0
|
||||
Viscosity 1.0
|
||||
Trials 40
|
||||
Accuracy 0.001
|
||||
CHECKFREQ 2
|
||||
MAXCHECK 10
|
||||
DAMPLIMIT 0
|
||||
Unbalanced Continue 10
|
||||
Pattern 1
|
||||
Demand Multiplier 1.0
|
||||
Emitter Exponent 0.5
|
||||
Quality Chlorine mg/L
|
||||
Diffusivity 1.0
|
||||
Tolerance 0.01
|
||||
|
||||
[COORDINATES]
|
||||
;Node X-Coord Y-Coord
|
||||
10 20.00 70.00
|
||||
11 30.00 70.00
|
||||
12 50.00 70.00
|
||||
13 70.00 70.00
|
||||
21 30.00 40.00
|
||||
22 50.00 40.00
|
||||
23 70.00 40.00
|
||||
31 30.00 10.00
|
||||
32 50.00 10.00
|
||||
9 10.00 70.00
|
||||
2 50.00 90.00
|
||||
|
||||
[VERTICES]
|
||||
;Link X-Coord Y-Coord
|
||||
|
||||
[LABELS]
|
||||
;X-Coord Y-Coord Label & Anchor Node
|
||||
6.99 73.63 "Source"
|
||||
13.48 68.13 "Pump"
|
||||
43.85 91.21 "Tank"
|
||||
|
||||
[BACKDROP]
|
||||
DIMENSIONS 7.00 6.00 73.00 94.00
|
||||
UNITS None
|
||||
FILE
|
||||
OFFSET 0.00 0.00
|
||||
|
||||
[END]
|
||||
@@ -0,0 +1,309 @@
|
||||
[TITLE]
|
||||
EPANET Example Network 2
|
||||
Example of modeling a 55-hour fluoride tracer study.
|
||||
Measured fluoride data is contained in the file Net2-FL.dat
|
||||
and should be registered with the project to produce a
|
||||
Calibration Report (select Calibration Data from the Project
|
||||
menu).
|
||||
|
||||
[JUNCTIONS]
|
||||
;ID Elev Demand Pattern
|
||||
1 50 -694.4 2 ;
|
||||
2 100 8 ;
|
||||
3 60 14 ;
|
||||
4 60 8 ;
|
||||
5 100 8 ;
|
||||
6 125 5 ;
|
||||
7 160 4 ;
|
||||
8 110 9 ;
|
||||
9 180 14 ;
|
||||
10 130 5 ;
|
||||
11 185 34.78 ;
|
||||
12 210 16 ;
|
||||
13 210 2 ;
|
||||
14 200 2 ;
|
||||
15 190 2 ;
|
||||
16 150 20 ;
|
||||
17 180 20 ;
|
||||
18 100 20 ;
|
||||
19 150 5 ;
|
||||
20 170 19 ;
|
||||
21 150 16 ;
|
||||
22 200 10 ;
|
||||
23 230 8 ;
|
||||
24 190 11 ;
|
||||
25 230 6 ;
|
||||
27 130 8 ;
|
||||
28 110 0 ;
|
||||
29 110 7 ;
|
||||
30 130 3 ;
|
||||
31 190 17 ;
|
||||
32 110 17 ;
|
||||
33 180 1.5 ;
|
||||
34 190 1.5 ;
|
||||
35 110 0 ;
|
||||
36 110 1 ;
|
||||
|
||||
[RESERVOIRS]
|
||||
;ID Head Pattern
|
||||
|
||||
[TANKS]
|
||||
;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve
|
||||
26 235 56.7 50 70 50 0 ;
|
||||
|
||||
[PIPES]
|
||||
;ID Node1 Node2 Length Diameter Roughness MinorLoss Status
|
||||
1 1 2 2400 12 100 0 Open ;
|
||||
2 2 5 800 12 100 0 Open ;
|
||||
3 2 3 1300 8 100 0 Open ;
|
||||
4 3 4 1200 8 100 0 Open ;
|
||||
5 4 5 1000 12 100 0 Open ;
|
||||
6 5 6 1200 12 100 0 Open ;
|
||||
7 6 7 2700 12 100 0 Open ;
|
||||
8 7 8 1200 12 140 0 Open ;
|
||||
9 7 9 400 12 100 0 Open ;
|
||||
10 8 10 1000 8 140 0 Open ;
|
||||
11 9 11 700 12 100 0 Open ;
|
||||
12 11 12 1900 12 100 0 Open ;
|
||||
13 12 13 600 12 100 0 Open ;
|
||||
14 13 14 400 12 100 0 Open ;
|
||||
15 14 15 300 12 100 0 Open ;
|
||||
16 13 16 1500 8 100 0 Open ;
|
||||
17 15 17 1500 8 100 0 Open ;
|
||||
18 16 17 600 8 100 0 Open ;
|
||||
19 17 18 700 12 100 0 Open ;
|
||||
20 18 32 350 12 100 0 Open ;
|
||||
21 16 19 1400 8 100 0 Open ;
|
||||
22 14 20 1100 12 100 0 Open ;
|
||||
23 20 21 1300 8 100 0 Open ;
|
||||
24 21 22 1300 8 100 0 Open ;
|
||||
25 20 22 1300 8 100 0 Open ;
|
||||
26 24 23 600 12 100 0 Open ;
|
||||
27 15 24 250 12 100 0 Open ;
|
||||
28 23 25 300 12 100 0 Open ;
|
||||
29 25 26 200 12 100 0 Open ;
|
||||
30 25 31 600 12 100 0 Open ;
|
||||
31 31 27 400 8 100 0 Open ;
|
||||
32 27 29 400 8 100 0 Open ;
|
||||
34 29 28 700 8 100 0 Open ;
|
||||
35 22 33 1000 8 100 0 Open ;
|
||||
36 33 34 400 8 100 0 Open ;
|
||||
37 32 19 500 8 100 0 Open ;
|
||||
38 29 35 500 8 100 0 Open ;
|
||||
39 35 30 1000 8 100 0 Open ;
|
||||
40 28 35 700 8 100 0 Open ;
|
||||
41 28 36 300 8 100 0 Open ;
|
||||
|
||||
[PUMPS]
|
||||
;ID Node1 Node2 Parameters
|
||||
|
||||
[VALVES]
|
||||
;ID Node1 Node2 Diameter Type Setting MinorLoss
|
||||
|
||||
[TAGS]
|
||||
|
||||
[DEMANDS]
|
||||
;Junction Demand Pattern Category
|
||||
|
||||
[STATUS]
|
||||
;ID Status/Setting
|
||||
|
||||
[PATTERNS]
|
||||
;ID Multipliers
|
||||
;Demand Pattern
|
||||
1 1.26 1.04 .97 .97 .89 1.19
|
||||
1 1.28 .67 .67 1.34 2.46 .97
|
||||
1 .92 .68 1.43 .61 .31 .78
|
||||
1 .37 .67 1.26 1.56 1.19 1.26
|
||||
1 .6 1.1 1.03 .73 .88 1.06
|
||||
1 .99 1.72 1.12 1.34 1.12 .97
|
||||
1 1.04 1.15 .91 .61 .68 .46
|
||||
1 .51 .74 1.12 1.34 1.26 .97
|
||||
1 .82 1.37 1.03 .81 .88 .81
|
||||
1 .81
|
||||
;Pump Station Outflow Pattern
|
||||
2 .96 .96 .96 .96 .96 .96
|
||||
2 .62 0 0 0 0 0
|
||||
2 .8 1 1 1 1 .15
|
||||
2 0 0 0 0 0 0
|
||||
2 .55 .92 .92 .92 .92 .9
|
||||
2 .9 .45 0 0 0 0
|
||||
2 0 .7 1 1 1 1
|
||||
2 .2 0 0 0 0 0
|
||||
2 0 .74 .92 .92 .92 .92
|
||||
2 .92
|
||||
;Pump Station Fluoride Pattern
|
||||
3 .98 1.02 1.05 .99 .64 .46
|
||||
3 .35 .35 .35 .35 .35 .35
|
||||
3 .17 .17 .13 .13 .13 .15
|
||||
3 .15 .15 .15 .15 .15 .15
|
||||
3 .15 .12 .1 .08 .11 .09
|
||||
3 .09 .08 .08 .08 .08 .08
|
||||
3 .08 .09 .07 .07 .09 .09
|
||||
3 .09 .09 .09 .09 .09 .09
|
||||
3 .09 .08 .35 .72 .82 .92
|
||||
3 1
|
||||
|
||||
[CURVES]
|
||||
;ID X-Value Y-Value
|
||||
|
||||
[CONTROLS]
|
||||
|
||||
[RULES]
|
||||
|
||||
[ENERGY]
|
||||
Global Efficiency 75
|
||||
Global Price 0.0
|
||||
Demand Charge 0.0
|
||||
|
||||
[EMITTERS]
|
||||
;Junction Coefficient
|
||||
|
||||
[QUALITY]
|
||||
;Node InitQual
|
||||
1 1.0
|
||||
2 1.0
|
||||
3 1.0
|
||||
4 1.0
|
||||
5 1.0
|
||||
6 1.0
|
||||
7 1.0
|
||||
8 1.0
|
||||
9 1.0
|
||||
10 1.0
|
||||
11 1.0
|
||||
12 1.0
|
||||
13 1.0
|
||||
14 1.0
|
||||
15 1.0
|
||||
16 1.0
|
||||
17 1.0
|
||||
18 1.0
|
||||
19 1.0
|
||||
20 1.0
|
||||
21 1.0
|
||||
22 1.0
|
||||
23 1.0
|
||||
24 1.0
|
||||
25 1.0
|
||||
27 1.0
|
||||
28 1.0
|
||||
29 1.0
|
||||
30 1.0
|
||||
31 1.0
|
||||
32 1.0
|
||||
33 1.0
|
||||
34 1.0
|
||||
35 1.0
|
||||
36 1.0
|
||||
26 1.0
|
||||
|
||||
[SOURCES]
|
||||
;Node Type Quality Pattern
|
||||
1 CONCEN 1.0 3
|
||||
|
||||
[REACTIONS]
|
||||
;Type Pipe/Tank Coefficient
|
||||
|
||||
|
||||
[REACTIONS]
|
||||
Order Bulk 1
|
||||
Order Tank 1
|
||||
Order Wall 1
|
||||
Global Bulk 0.0
|
||||
Global Wall 0.0
|
||||
Limiting Potential 0.0
|
||||
Roughness Correlation 0.0
|
||||
|
||||
[MIXING]
|
||||
;Tank Model
|
||||
|
||||
[TIMES]
|
||||
Duration 55:00
|
||||
Hydraulic Timestep 1:00
|
||||
Quality Timestep 0:05
|
||||
Pattern Timestep 1:00
|
||||
Pattern Start 0:00
|
||||
Report Timestep 1:00
|
||||
Report Start 0:00
|
||||
Start ClockTime 8 am
|
||||
Statistic None
|
||||
|
||||
[REPORT]
|
||||
Status No
|
||||
Summary No
|
||||
Page 0
|
||||
|
||||
[OPTIONS]
|
||||
Units GPM
|
||||
Headloss H-W
|
||||
Specific Gravity 1.0
|
||||
Viscosity 1.0
|
||||
Trials 40
|
||||
Accuracy 0.001
|
||||
CHECKFREQ 2
|
||||
MAXCHECK 10
|
||||
DAMPLIMIT 0
|
||||
Unbalanced Continue 10
|
||||
Pattern 1
|
||||
Demand Multiplier 1.0
|
||||
Emitter Exponent 0.5
|
||||
Quality Fluoride mg/L
|
||||
Diffusivity 1.0
|
||||
Tolerance 0.01
|
||||
|
||||
[COORDINATES]
|
||||
;Node X-Coord Y-Coord
|
||||
1 21.00 4.00
|
||||
2 19.00 20.00
|
||||
3 11.00 21.00
|
||||
4 14.00 28.00
|
||||
5 19.00 25.00
|
||||
6 28.00 23.00
|
||||
7 36.00 39.00
|
||||
8 38.00 30.00
|
||||
9 36.00 42.00
|
||||
10 37.00 23.00
|
||||
11 37.00 49.00
|
||||
12 39.00 60.00
|
||||
13 38.00 64.00
|
||||
14 38.00 66.00
|
||||
15 37.00 69.00
|
||||
16 27.00 65.00
|
||||
17 27.00 69.00
|
||||
18 23.00 68.00
|
||||
19 21.00 59.00
|
||||
20 45.00 68.00
|
||||
21 51.00 62.00
|
||||
22 54.00 69.00
|
||||
23 35.00 74.00
|
||||
24 37.00 71.00
|
||||
25 35.00 76.00
|
||||
27 39.00 87.00
|
||||
28 49.00 85.00
|
||||
29 42.00 86.00
|
||||
30 47.00 80.00
|
||||
31 37.00 80.00
|
||||
32 23.00 64.00
|
||||
33 56.00 73.00
|
||||
34 56.00 77.00
|
||||
35 43.00 81.00
|
||||
36 53.00 87.00
|
||||
26 33.00 76.00
|
||||
|
||||
[VERTICES]
|
||||
;Link X-Coord Y-Coord
|
||||
|
||||
[LABELS]
|
||||
;X-Coord Y-Coord Label & Anchor Node
|
||||
24.00 7.00 "Pump"
|
||||
24.00 4.00 "Station"
|
||||
26.76 77.42 "Tank"
|
||||
|
||||
[BACKDROP]
|
||||
DIMENSIONS 8.75 -0.15 58.25 91.15
|
||||
UNITS None
|
||||
FILE
|
||||
OFFSET 0.00 0.00
|
||||
|
||||
[END]
|
||||
+147940
File diff suppressed because it is too large
Load Diff
+60024
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1304498
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,252 @@
|
||||
[TITLE]
|
||||
|
||||
|
||||
[JUNCTIONS]
|
||||
;ID Elev Demand Pattern
|
||||
2 5.80 19.49 1 ;
|
||||
3 5.80 19.49 1 ;
|
||||
4 5.80 19.50 1 ;
|
||||
5 5.80 19.49 1 ;
|
||||
6 5.80 19.49 1 ;
|
||||
7 5.80 19.50 1 ;
|
||||
8 5.80 19.49 1 ;
|
||||
9 5.80 19.50 1 ;
|
||||
10 5.80 19.49 1 ;
|
||||
11 5.80 19.49 1 ;
|
||||
12 5.80 19.50 1 ;
|
||||
13 5.80 19.49 1 ;
|
||||
15 5.80 19.49 1 ;
|
||||
16 5.80 19.50 1 ;
|
||||
17 5.80 19.49 1 ;
|
||||
18 5.80 19.49 1 ;
|
||||
19 5.80 19.50 1 ;
|
||||
20 5.80 19.49 1 ;
|
||||
21 5.80 19.49 1 ;
|
||||
22 0 136.45 1 ;
|
||||
23 0 0 1 ;
|
||||
24 0 0 1 ;
|
||||
|
||||
[RESERVOIRS]
|
||||
;ID Head Pattern
|
||||
1 3.19 ;
|
||||
14 7.40 ;
|
||||
|
||||
[TANKS]
|
||||
;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve
|
||||
|
||||
[PIPES]
|
||||
;ID Node1 Node2 Length Diameter Roughness MinorLoss Status
|
||||
5 2 6 1976 315 120 0 Open ;
|
||||
6 6 7 1300 250 120 0 Open ;
|
||||
7 7 8 1051 160 120 0 Open ;
|
||||
8 3 9 6210 250 120 0 Open ;
|
||||
9 5 10 2173 200 120 0 Open ;
|
||||
10 4 11 2984 500 120 0 Open ;
|
||||
11 11 12 2100 500 120 0 Open ;
|
||||
12 11 13 2199 250 120 0 Open ;
|
||||
14 15 16 3100 315 120 0 Open ;
|
||||
15 15 17 6485 400 120 0 Open ;
|
||||
16 16 18 2688 200 120 0 Open ;
|
||||
17 17 19 2306 200 120 0 Open ;
|
||||
18 17 20 4161 250 120 0 Open ;
|
||||
19 20 21 2841 200 120 0 Open ;
|
||||
20 12 22 1 1000 120 0 Open ;
|
||||
1 23 2 3024 355 100 0 Open ;
|
||||
2 23 3 1400 400 120 0 Open ;
|
||||
3 23 4 3149 500 120 0 Open ;
|
||||
4 23 5 5400 250 120 0 Open ;
|
||||
13 24 15 3554 400 120 0 Open ;
|
||||
|
||||
[PUMPS]
|
||||
;ID Node1 Node2 Parameters
|
||||
23 1 23 HEAD 1 ;
|
||||
22 1 23 HEAD 1 ;
|
||||
21 1 23 HEAD 1 ;
|
||||
24 14 24 HEAD 1 ;
|
||||
25 14 24 HEAD 1 ;
|
||||
|
||||
[VALVES]
|
||||
;ID Node1 Node2 Diameter Type Setting MinorLoss
|
||||
|
||||
[TAGS]
|
||||
|
||||
[DEMANDS]
|
||||
;Junction Demand Pattern Category
|
||||
|
||||
[STATUS]
|
||||
;ID Status/Setting
|
||||
|
||||
[PATTERNS]
|
||||
;ID Multipliers
|
||||
;
|
||||
1 1
|
||||
|
||||
[CURVES]
|
||||
;ID X-Value Y-Value
|
||||
;Ë®±Ã:
|
||||
1 0 56.99
|
||||
1 33.33 56.74
|
||||
1 50 56.49
|
||||
1 66.67 55.74
|
||||
1 83.33 54.24
|
||||
1 100 51.98
|
||||
1 116.67 48.72
|
||||
1 133.33 43.95
|
||||
1 150 38.44
|
||||
|
||||
[CONTROLS]
|
||||
|
||||
[RULES]
|
||||
|
||||
[ENERGY]
|
||||
Global Efficiency 75
|
||||
Global Price 0
|
||||
Demand Charge 0
|
||||
|
||||
[EMITTERS]
|
||||
;Junction Coefficient
|
||||
|
||||
[QUALITY]
|
||||
;Node InitQual
|
||||
1 0.3
|
||||
14 0.3
|
||||
|
||||
[SOURCES]
|
||||
;Node Type Quality Pattern
|
||||
|
||||
[REACTIONS]
|
||||
;Type Pipe/Tank Coefficient
|
||||
|
||||
|
||||
[REACTIONS]
|
||||
Order Bulk 1
|
||||
Order Tank 1
|
||||
Order Wall 1
|
||||
Global Bulk -1
|
||||
Global Wall -0.5
|
||||
Limiting Potential 0
|
||||
Roughness Correlation 0
|
||||
|
||||
[MIXING]
|
||||
;Tank Model
|
||||
|
||||
[TIMES]
|
||||
Duration 48:00
|
||||
Hydraulic Timestep 0:05
|
||||
Quality Timestep 0:05
|
||||
Pattern Timestep 0:05
|
||||
Pattern Start 0:00
|
||||
Report Timestep 0:05
|
||||
Report Start 0:00
|
||||
Start ClockTime 12 am
|
||||
Statistic None
|
||||
|
||||
[REPORT]
|
||||
Status No
|
||||
Summary No
|
||||
Page 0
|
||||
|
||||
[OPTIONS]
|
||||
Units LPS
|
||||
Headloss H-W
|
||||
Specific Gravity 1
|
||||
Viscosity 1
|
||||
Trials 100
|
||||
Accuracy 0.01
|
||||
CHECKFREQ 2
|
||||
MAXCHECK 10
|
||||
DAMPLIMIT 0
|
||||
Unbalanced Continue 10
|
||||
Pattern 1
|
||||
Demand Multiplier 1.0
|
||||
Emitter Exponent 0.5
|
||||
Quality »¯Ñ§³É·Ö mg/L
|
||||
Diffusivity 1
|
||||
Tolerance 0.01
|
||||
|
||||
[COORDINATES]
|
||||
;Node X-Coord Y-Coord
|
||||
2 2054.14 6695.86
|
||||
3 2249.20 7663.22
|
||||
4 3523.09 7328.82
|
||||
5 3136.94 9211.78
|
||||
6 1409.24 6787.42
|
||||
7 1122.61 6819.27
|
||||
8 800.16 6950.64
|
||||
9 1357.48 8809.71
|
||||
10 3531.05 9996.02
|
||||
11 3654.46 8308.12
|
||||
12 4353.11 8268.31
|
||||
13 3710.19 9060.51
|
||||
15 5467.75 8264.33
|
||||
16 5467.75 9327.23
|
||||
17 6769.51 9243.63
|
||||
18 5220.94 9765.13
|
||||
19 6359.47 9390.92
|
||||
20 8055.33 8853.50
|
||||
21 8970.94 8901.27
|
||||
22 4353.60 8338.97
|
||||
23 2590.07 7540.80
|
||||
24 4405.35 8378.78
|
||||
1 2900.58 7676.15
|
||||
14 4309.81 8420.58
|
||||
|
||||
[VERTICES]
|
||||
;Link X-Coord Y-Coord
|
||||
5 1970.54 6707.80
|
||||
5 1791.40 6719.75
|
||||
6 1373.41 6689.89
|
||||
6 1242.04 6769.51
|
||||
8 1817.28 7750.80
|
||||
8 1875.00 8134.95
|
||||
8 1968.55 8190.68
|
||||
8 1849.12 8529.06
|
||||
8 1833.20 8833.60
|
||||
10 3642.52 7852.31
|
||||
14 5481.69 8612.66
|
||||
15 6072.85 8238.46
|
||||
15 6076.83 8349.92
|
||||
15 6106.69 8481.29
|
||||
15 6156.45 8610.67
|
||||
15 6385.35 8616.64
|
||||
15 6357.48 8865.45
|
||||
15 6666.00 8887.34
|
||||
15 6753.58 9203.82
|
||||
16 5467.75 9460.59
|
||||
16 5254.78 9486.46
|
||||
16 5095.54 9488.46
|
||||
16 5127.39 9767.12
|
||||
17 6803.34 9317.28
|
||||
17 6596.34 9402.87
|
||||
17 6486.86 9464.57
|
||||
17 6445.06 9355.10
|
||||
18 7131.77 9080.41
|
||||
18 7689.09 8875.40
|
||||
19 8292.20 8837.58
|
||||
19 8682.32 8909.24
|
||||
19 8769.90 8895.30
|
||||
2 2502.49 7568.67
|
||||
2 2243.73 7600.52
|
||||
3 2781.15 7461.19
|
||||
3 2880.67 7433.32
|
||||
4 2613.95 7636.35
|
||||
4 2757.27 7839.37
|
||||
4 2785.13 8508.16
|
||||
13 5010.45 8384.75
|
||||
13 5040.31 8289.21
|
||||
13 5229.40 8271.30
|
||||
23 2789.11 7711.98
|
||||
21 2900.58 7564.69
|
||||
24 4345.64 8378.78
|
||||
25 4363.55 8410.63
|
||||
|
||||
[LABELS]
|
||||
;X-Coord Y-Coord Label & Anchor Node
|
||||
|
||||
[BACKDROP]
|
||||
DIMENSIONS 0.00 0.00 10000.00 10000.00
|
||||
UNITS None
|
||||
FILE
|
||||
OFFSET 0.00 0.00
|
||||
|
||||
[END]
|
||||
File diff suppressed because it is too large
Load Diff
+109
@@ -0,0 +1,109 @@
|
||||
[TITLE]
|
||||
|
||||
[JUNCTIONS]
|
||||
|
||||
[RESERVOIRS]
|
||||
|
||||
[TANKS]
|
||||
|
||||
[PIPES]
|
||||
|
||||
[PUMPS]
|
||||
|
||||
[VALVES]
|
||||
|
||||
[DEMANDS]
|
||||
|
||||
[EMITTERS]
|
||||
|
||||
[STATUS]
|
||||
|
||||
[PATTERNS]
|
||||
|
||||
[CURVES]
|
||||
|
||||
[CONTROLS]
|
||||
|
||||
[ENERGY]
|
||||
GLOBAL EFFICIENCY 0.7500
|
||||
GLOBAL PRICE 0.0000
|
||||
DEMAND CHARGE 0.0000
|
||||
|
||||
[QUALITY]
|
||||
|
||||
[SOURCES]
|
||||
|
||||
[MIXING]
|
||||
|
||||
[REACTIONS]
|
||||
ORDER BULK 1.0000
|
||||
ORDER WALL 1.0000
|
||||
ORDER TANK 1.0000
|
||||
GLOBAL BULK 0.0000
|
||||
GLOBAL WALL 0.0000
|
||||
LIMITING POTENTIAL 0.0000
|
||||
ROUGHNESS CORRELATION 0.0000
|
||||
|
||||
[OPTIONS]
|
||||
FLOW_UNITS GPM
|
||||
PRESSURE_UNITS PSI
|
||||
HEADLOSS_MODEL H-W
|
||||
SPECIFIC_GRAVITY 1.0000
|
||||
SPECIFIC_VISCOSITY 1.0000
|
||||
|
||||
MAXIMUM_TRIALS 100
|
||||
HEAD_TOLERANCE 0.0000
|
||||
FLOW_TOLERANCE 0.0000
|
||||
FLOW_CHANGE_LIMIT 0.0000
|
||||
TIME_WEIGHT 0.0000
|
||||
STEP_SIZING FULL
|
||||
IF_UNBALANCED STOP
|
||||
|
||||
|
||||
DEMAND_MODEL FIXED
|
||||
DEMAND_PATTERN
|
||||
DEMAND_MULTIPLIER 1.0000
|
||||
MINIMUM_PRESSURE 0.0000
|
||||
SERVICE_PRESSURE 0.0000
|
||||
PRESSURE_EXPONENT 0.5000
|
||||
|
||||
LEAKAGE_MODEL NONE
|
||||
LEAKAGE_COEFF1 0.0000
|
||||
LEAKAGE_COEFF2 0.0000
|
||||
EMITTER_EXPONENT 0.5000
|
||||
|
||||
QUALITY_MODEL NONE
|
||||
SPECIFIC_DIFFUSIVITY 1.0000
|
||||
QUALITY_TOLERANCE 0.0100
|
||||
|
||||
[TIMES]
|
||||
TOTAL DURATION 0:00:00
|
||||
HYDRAULIC TIMESTEP 1:00:00
|
||||
QUALITY TIMESTEP 0:05:00
|
||||
RULE TIMESTEP 0:05:00
|
||||
PATTERN TIMESTEP 1:00:00
|
||||
PATTERN START 0:00:00
|
||||
REPORT TIMESTEP 1:00:00
|
||||
REPORT START 0:00:00
|
||||
START CLOCKTIME 0:00:00
|
||||
|
||||
[REPORT]
|
||||
SUMMARY YES
|
||||
|
||||
[COORDINATES]
|
||||
|
||||
[VERTICES]
|
||||
|
||||
[REGION]
|
||||
|
||||
[BOUND]
|
||||
|
||||
[DATA_NODE_OF_REGION]
|
||||
|
||||
[DMA]
|
||||
|
||||
[VD]
|
||||
|
||||
[SA]
|
||||
|
||||
[DATA_REGION_OF_NODE]
|
||||
+38821
File diff suppressed because it is too large
Load Diff
+37670
File diff suppressed because it is too large
Load Diff
+40290
File diff suppressed because it is too large
Load Diff
+73400
File diff suppressed because it is too large
Load Diff
+72248
File diff suppressed because it is too large
Load Diff
+40183
File diff suppressed because it is too large
Load Diff
+41673
File diff suppressed because it is too large
Load Diff
+6383
File diff suppressed because it is too large
Load Diff
+1645
File diff suppressed because it is too large
Load Diff
+486141
File diff suppressed because it is too large
Load Diff
+40289
File diff suppressed because it is too large
Load Diff
+30972
File diff suppressed because it is too large
Load Diff
+481
@@ -0,0 +1,481 @@
|
||||
[TITLE]
|
||||
EPANET Example Network 3
|
||||
Example showing how the percent of Lake water in a dual-source
|
||||
system changes over time.
|
||||
|
||||
[JUNCTIONS]
|
||||
;ID Elev Demand Pattern
|
||||
10 147 0 ;
|
||||
15 32 1 3 ;
|
||||
20 129 0 ;
|
||||
35 12.5 1 4 ;
|
||||
40 131.9 0 ;
|
||||
50 116.5 0 ;
|
||||
60 0 0 ;
|
||||
601 0 0 ;
|
||||
61 0 0 ;
|
||||
101 42 189.95 ;
|
||||
103 43 133.2 ;
|
||||
105 28.5 135.37 ;
|
||||
107 22 54.64 ;
|
||||
109 20.3 231.4 ;
|
||||
111 10 141.94 ;
|
||||
113 2 20.01 ;
|
||||
115 14 52.1 ;
|
||||
117 13.6 117.71 ;
|
||||
119 2 176.13 ;
|
||||
120 0 0 ;
|
||||
121 -2 41.63 ;
|
||||
123 11 1 2 ;
|
||||
125 11 45.6 ;
|
||||
127 56 17.66 ;
|
||||
129 51 0 ;
|
||||
131 6 42.75 ;
|
||||
139 31 5.89 ;
|
||||
141 4 9.85 ;
|
||||
143 -4.5 6.2 ;
|
||||
145 1 27.63 ;
|
||||
147 18.5 8.55 ;
|
||||
149 16 27.07 ;
|
||||
151 33.5 144.48 ;
|
||||
153 66.2 44.17 ;
|
||||
157 13.1 51.79 ;
|
||||
159 6 41.32 ;
|
||||
161 4 15.8 ;
|
||||
163 5 9.42 ;
|
||||
164 5 0 ;
|
||||
166 -2 2.6 ;
|
||||
167 -5 14.56 ;
|
||||
169 -5 0 ;
|
||||
171 -4 39.34 ;
|
||||
173 -4 0 ;
|
||||
177 8 58.17 ;
|
||||
179 8 0 ;
|
||||
181 8 0 ;
|
||||
183 11 0 ;
|
||||
184 16 0 ;
|
||||
185 16 25.65 ;
|
||||
187 12.5 0 ;
|
||||
189 4 107.92 ;
|
||||
191 25 81.9 ;
|
||||
193 18 71.31 ;
|
||||
195 15.5 0 ;
|
||||
197 23 17.04 ;
|
||||
199 -2 119.32 ;
|
||||
201 0.1 44.61 ;
|
||||
203 2 1 5 ;
|
||||
204 21 0 ;
|
||||
205 21 65.36 ;
|
||||
206 1 0 ;
|
||||
207 9 69.39 ;
|
||||
208 16 0 ;
|
||||
209 -2 0.87 ;
|
||||
211 7 8.67 ;
|
||||
213 7 13.94 ;
|
||||
215 7 92.19 ;
|
||||
217 6 24.22 ;
|
||||
219 4 41.32 ;
|
||||
225 8 22.8 ;
|
||||
229 10.5 64.18 ;
|
||||
231 5 16.48 ;
|
||||
237 14 15.61 ;
|
||||
239 13 44.61 ;
|
||||
241 13 0 ;
|
||||
243 14 4.34 ;
|
||||
247 18 70.38 ;
|
||||
249 18 0 ;
|
||||
251 30 24.16 ;
|
||||
253 36 54.52 ;
|
||||
255 27 40.39 ;
|
||||
257 17 0 ;
|
||||
259 25 0 ;
|
||||
261 0 0 ;
|
||||
263 0 0 ;
|
||||
265 0 0 ;
|
||||
267 21 0 ;
|
||||
269 0 0 ;
|
||||
271 6 0 ;
|
||||
273 8 0 ;
|
||||
275 10 0 ;
|
||||
|
||||
[RESERVOIRS]
|
||||
;ID Head Pattern
|
||||
River 220.0 ;
|
||||
Lake 167.0 ;
|
||||
|
||||
[TANKS]
|
||||
;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve
|
||||
1 131.9 13.1 .1 32.1 85 0 ;
|
||||
2 116.5 23.5 6.5 40.3 50 0 ;
|
||||
3 129.0 29.0 4.0 35.5 164 0 ;
|
||||
|
||||
[PIPES]
|
||||
;ID Node1 Node2 Length Diameter Roughness MinorLoss Status
|
||||
20 3 20 99 99 199 0 Open ;
|
||||
40 1 40 99 99 199 0 Open ;
|
||||
50 2 50 99 99 199 0 Open ;
|
||||
60 River 60 1231 24 140 0 Open ;
|
||||
101 10 101 14200 18 110 0 Open ;
|
||||
103 101 103 1350 16 130 0 Open ;
|
||||
105 101 105 2540 12 130 0 Open ;
|
||||
107 105 107 1470 12 130 0 Open ;
|
||||
109 103 109 3940 16 130 0 Open ;
|
||||
111 109 111 2000 12 130 0 Open ;
|
||||
112 115 111 1160 12 130 0 Open ;
|
||||
113 111 113 1680 12 130 0 Open ;
|
||||
114 115 113 2000 8 130 0 Open ;
|
||||
115 107 115 1950 8 130 0 Open ;
|
||||
116 113 193 1660 12 130 0 Open ;
|
||||
117 263 105 2725 12 130 0 Open ;
|
||||
119 115 117 2180 12 130 0 Open ;
|
||||
120 119 120 730 12 130 0 Open ;
|
||||
121 120 117 1870 12 130 0 Open ;
|
||||
122 121 120 2050 8 130 0 Open ;
|
||||
123 121 119 2000 30 141 0 Open ;
|
||||
125 123 121 1500 30 141 0 Open ;
|
||||
129 121 125 930 24 130 0 Open ;
|
||||
131 125 127 3240 24 130 0 Open ;
|
||||
133 20 127 785 20 130 0 Open ;
|
||||
135 127 129 900 24 130 0 Open ;
|
||||
137 129 131 6480 16 130 0 Open ;
|
||||
145 129 139 2750 8 130 0 Open ;
|
||||
147 139 141 2050 8 130 0 Open ;
|
||||
149 143 141 1400 8 130 0 Open ;
|
||||
151 15 143 1650 8 130 0 Open ;
|
||||
153 145 141 3510 12 130 0 Open ;
|
||||
155 147 145 2200 12 130 0 Open ;
|
||||
159 147 149 880 12 130 0 Open ;
|
||||
161 149 151 1020 8 130 0 Open ;
|
||||
163 151 153 1170 12 130 0 Open ;
|
||||
169 125 153 4560 8 130 0 Open ;
|
||||
171 119 151 3460 12 130 0 Open ;
|
||||
173 119 157 2080 30 141 0 Open ;
|
||||
175 157 159 2910 30 141 0 Open ;
|
||||
177 159 161 2000 30 141 0 Open ;
|
||||
179 161 163 430 30 141 0 Open ;
|
||||
180 163 164 150 14 130 0 Open ;
|
||||
181 164 166 490 14 130 0 Open ;
|
||||
183 265 169 590 30 141 0 Open ;
|
||||
185 167 169 60 8 130 0 Open ;
|
||||
186 187 204 99.9 8 130 0 Open ;
|
||||
187 169 171 1270 30 141 0 Open ;
|
||||
189 171 173 50 30 141 0 Open ;
|
||||
191 271 171 760 24 130 0 Open ;
|
||||
193 35 181 30 24 130 0 Open ;
|
||||
195 181 177 30 12 130 0 Open ;
|
||||
197 177 179 30 12 130 0 Open ;
|
||||
199 179 183 210 12 130 0 Open ;
|
||||
201 40 179 1190 12 130 0 Open ;
|
||||
202 185 184 99.9 8 130 0 Open ;
|
||||
203 183 185 510 8 130 0 Open ;
|
||||
204 184 205 4530. 12 130 0 Open ;
|
||||
205 204 185 1325. 12 130 0 Open ;
|
||||
207 189 183 1350 12 130 0 Open ;
|
||||
209 189 187 500 8 130 0 Open ;
|
||||
211 169 269 646 12 130 0 Open ;
|
||||
213 191 187 2560 12 130 0 Open ;
|
||||
215 267 189 1230 12 130 0 Open ;
|
||||
217 191 193 520 12 130 0 Open ;
|
||||
219 193 195 360 12 130 0 Open ;
|
||||
221 161 195 2300 8 130 0 Open ;
|
||||
223 197 191 1150 12 130 0 Open ;
|
||||
225 111 197 2790 12 130 0 Open ;
|
||||
229 173 199 4000 24 141 0 Open ;
|
||||
231 199 201 630 24 141 0 Open ;
|
||||
233 201 203 120 24 130 0 Open ;
|
||||
235 199 273 725 12 130 0 Open ;
|
||||
237 205 207 1200 12 130 0 Open ;
|
||||
238 207 206 450 12 130 0 Open ;
|
||||
239 275 207 1430 12 130 0 Open ;
|
||||
240 206 208 510 12 130 0 Open ;
|
||||
241 208 209 885 12 130 0 Open ;
|
||||
243 209 211 1210 16 130 0 Open ;
|
||||
245 211 213 990 16 130 0 Open ;
|
||||
247 213 215 4285 16 130 0 Open ;
|
||||
249 215 217 1660 16 130 0 Open ;
|
||||
251 217 219 2050 14 130 0 Open ;
|
||||
257 217 225 1560 12 130 0 Open ;
|
||||
261 213 229 2200 8 130 0 Open ;
|
||||
263 229 231 1960 12 130 0 Open ;
|
||||
269 211 237 2080 12 130 0 Open ;
|
||||
271 237 229 790 8 130 0 Open ;
|
||||
273 237 239 510 12 130 0 Open ;
|
||||
275 239 241 35 12 130 0 Open ;
|
||||
277 241 243 2200 12 130 0 Open ;
|
||||
281 241 247 445 10 130 0 Open ;
|
||||
283 239 249 430 12 130 0 Open ;
|
||||
285 247 249 10 12 130 0 Open ;
|
||||
287 247 255 1390 10 130 0 Open ;
|
||||
289 50 255 925 10 130 0 Open ;
|
||||
291 255 253 1100 10 130 0 Open ;
|
||||
293 255 251 1100 8 130 0 Open ;
|
||||
295 249 251 1450 12 130 0 Open ;
|
||||
297 120 257 645 8 130 0 Open ;
|
||||
299 257 259 350 8 130 0 Open ;
|
||||
301 259 263 1400 8 130 0 Open ;
|
||||
303 257 261 1400 8 130 0 Open ;
|
||||
305 117 261 645 12 130 0 Open ;
|
||||
307 261 263 350 12 130 0 Open ;
|
||||
309 265 267 1580 8 130 0 Open ;
|
||||
311 193 267 1170 12 130 0 Open ;
|
||||
313 269 189 646 12 130 0 Open ;
|
||||
315 181 271 260 24 130 0 Open ;
|
||||
317 273 275 2230 8 130 0 Open ;
|
||||
319 273 205 645 12 130 0 Open ;
|
||||
321 163 265 1200 30 141 0 Open ;
|
||||
323 201 275 300 12 130 0 Open ;
|
||||
325 269 271 1290 8 130 0 Open ;
|
||||
329 61 123 45500 30 140 0 Open ;
|
||||
330 60 601 1 30 140 0 Closed ;
|
||||
333 601 61 1 30 140 0 Open ;
|
||||
|
||||
[PUMPS]
|
||||
;ID Node1 Node2 Parameters
|
||||
10 Lake 10 HEAD 1 ;
|
||||
335 60 61 HEAD 2 ;
|
||||
|
||||
[VALVES]
|
||||
;ID Node1 Node2 Diameter Type Setting MinorLoss
|
||||
|
||||
[TAGS]
|
||||
|
||||
[DEMANDS]
|
||||
;Junction Demand Pattern Category
|
||||
|
||||
[STATUS]
|
||||
;ID Status/Setting
|
||||
10 Closed
|
||||
|
||||
[PATTERNS]
|
||||
;ID Multipliers
|
||||
;General Default Demand Pattern
|
||||
1 1.34 1.94 1.46 1.44 .76 .92
|
||||
1 .85 1.07 .96 1.1 1.08 1.19
|
||||
1 1.16 1.08 .96 .83 .79 .74
|
||||
1 .64 .64 .85 .96 1.24 1.67
|
||||
;Demand Pattern for Node 123
|
||||
2 0 0 0 0 0 1219
|
||||
2 0 0 0 1866 1836 1818
|
||||
2 1818 1822 1822 1817 1824 1816
|
||||
2 1833 1817 1830 1814 1840 1859
|
||||
;Demand Pattern for Node 15
|
||||
3 620 620 620 620 620 360
|
||||
3 360 0 0 0 0 360
|
||||
3 360 360 360 360 0 0
|
||||
3 0 0 0 0 360 360
|
||||
;Demand Pattern for Node 35
|
||||
4 1637 1706 1719 1719 1791 1819
|
||||
4 1777 1842 1815 1825 1856 1801
|
||||
4 1819 1733 1664 1620 1613 1620
|
||||
4 1616 1647 1627 1627 1671 1668
|
||||
;Demand Pattern for Node 203
|
||||
5 4439 4531 4511 4582 4531 4582
|
||||
5 4572 4613 4643 4643 4592 4613
|
||||
5 4531 4521 4449 4439 4449 4460
|
||||
5 4439 4419 4368 4399 4470 4480
|
||||
|
||||
[CURVES]
|
||||
;ID X-Value Y-Value
|
||||
;PUMP: Pump Curve for Pump 10 (Lake Source)
|
||||
1 0 104.
|
||||
1 2000. 92.
|
||||
1 4000. 63.
|
||||
;PUMP: Pump Curve for Pump 335 (River Source)
|
||||
2 0 200.
|
||||
2 8000. 138.
|
||||
2 14000. 86.
|
||||
|
||||
[CONTROLS]
|
||||
;Lake source operates only part of the day
|
||||
Link 10 OPEN AT TIME 1
|
||||
Link 10 CLOSED AT TIME 15
|
||||
|
||||
;Pump 335 controlled by level in Tank 1
|
||||
;When pump is closed, bypass pipe is opened
|
||||
Link 335 OPEN IF Node 1 BELOW 17.1
|
||||
Link 335 CLOSED IF Node 1 ABOVE 19.1
|
||||
Link 330 CLOSED IF Node 1 BELOW 17.1
|
||||
Link 330 OPEN IF Node 1 ABOVE 19.1
|
||||
|
||||
|
||||
[RULES]
|
||||
|
||||
[ENERGY]
|
||||
Global Efficiency 75
|
||||
Global Price 0.0
|
||||
Demand Charge 0.0
|
||||
|
||||
[EMITTERS]
|
||||
;Junction Coefficient
|
||||
|
||||
[QUALITY]
|
||||
;Node InitQual
|
||||
|
||||
[SOURCES]
|
||||
;Node Type Quality Pattern
|
||||
|
||||
[REACTIONS]
|
||||
;Type Pipe/Tank Coefficient
|
||||
|
||||
|
||||
[REACTIONS]
|
||||
Order Bulk 1
|
||||
Order Tank 1
|
||||
Order Wall 1
|
||||
Global Bulk 0.0
|
||||
Global Wall 0.0
|
||||
Limiting Potential 0.0
|
||||
Roughness Correlation 0.0
|
||||
|
||||
[MIXING]
|
||||
;Tank Model
|
||||
|
||||
[TIMES]
|
||||
Duration 24:00
|
||||
Hydraulic Timestep 1:00
|
||||
Quality Timestep 0:05
|
||||
Pattern Timestep 1:00
|
||||
Pattern Start 0:00
|
||||
Report Timestep 1:00
|
||||
Report Start 0:00
|
||||
Start ClockTime 12 am
|
||||
Statistic None
|
||||
|
||||
[REPORT]
|
||||
Status Yes
|
||||
Summary No
|
||||
Page 0
|
||||
|
||||
[OPTIONS]
|
||||
Units GPM
|
||||
Headloss H-W
|
||||
Specific Gravity 1.0
|
||||
Viscosity 1.0
|
||||
Trials 40
|
||||
Accuracy 0.001
|
||||
CHECKFREQ 2
|
||||
MAXCHECK 10
|
||||
DAMPLIMIT 0
|
||||
Unbalanced Continue 10
|
||||
Pattern 1
|
||||
Demand Multiplier 1.0
|
||||
Emitter Exponent 0.5
|
||||
Quality Trace Lake
|
||||
Diffusivity 1.0
|
||||
Tolerance 0.01
|
||||
|
||||
[COORDINATES]
|
||||
;Node X-Coord Y-Coord
|
||||
10 9.00 27.85
|
||||
15 38.68 23.76
|
||||
20 29.44 26.91
|
||||
35 25.46 10.52
|
||||
40 27.02 9.81
|
||||
50 33.01 3.01
|
||||
60 23.90 29.94
|
||||
601 23.00 29.49
|
||||
61 23.71 29.03
|
||||
101 13.81 22.94
|
||||
103 12.96 21.31
|
||||
105 16.97 21.28
|
||||
107 18.45 20.46
|
||||
109 17.64 18.92
|
||||
111 20.21 17.53
|
||||
113 22.04 16.61
|
||||
115 20.98 19.18
|
||||
117 21.69 21.28
|
||||
119 23.70 22.76
|
||||
120 22.08 23.10
|
||||
121 23.54 25.50
|
||||
123 23.37 27.31
|
||||
125 24.59 25.64
|
||||
127 29.29 26.40
|
||||
129 30.32 26.39
|
||||
131 37.89 29.55
|
||||
139 33.28 24.54
|
||||
141 35.68 23.08
|
||||
143 37.47 21.97
|
||||
145 33.02 19.29
|
||||
147 30.24 20.38
|
||||
149 29.62 20.74
|
||||
151 28.29 21.39
|
||||
153 28.13 22.63
|
||||
157 24.85 20.16
|
||||
159 23.12 17.50
|
||||
161 25.10 15.28
|
||||
163 25.39 14.98
|
||||
164 25.98 15.14
|
||||
166 26.48 15.13
|
||||
167 25.88 12.98
|
||||
169 25.68 12.74
|
||||
171 26.65 11.80
|
||||
173 26.87 11.59
|
||||
177 26.00 10.50
|
||||
179 25.71 10.40
|
||||
181 25.72 10.74
|
||||
183 25.45 10.18
|
||||
184 25.15 9.52
|
||||
185 25.01 9.67
|
||||
187 23.64 11.04
|
||||
189 24.15 11.37
|
||||
191 22.10 14.07
|
||||
193 22.88 14.35
|
||||
195 23.18 14.72
|
||||
197 20.97 15.18
|
||||
199 29.42 8.44
|
||||
201 30.89 8.57
|
||||
203 31.14 8.89
|
||||
204 23.80 10.90
|
||||
205 29.20 6.46
|
||||
206 31.66 6.64
|
||||
207 31.00 6.61
|
||||
208 32.54 6.81
|
||||
209 33.76 6.59
|
||||
211 34.20 5.54
|
||||
213 35.26 6.16
|
||||
215 39.95 8.73
|
||||
217 42.11 8.67
|
||||
219 44.86 9.32
|
||||
225 43.53 7.38
|
||||
229 36.16 3.49
|
||||
231 38.38 2.54
|
||||
237 35.37 3.08
|
||||
239 35.76 2.31
|
||||
241 35.87 2.11
|
||||
243 37.04 0.00
|
||||
247 35.02 2.05
|
||||
249 35.02 1.81
|
||||
251 34.15 1.10
|
||||
253 32.17 1.88
|
||||
255 33.51 2.45
|
||||
257 21.17 23.32
|
||||
259 20.80 23.40
|
||||
261 20.79 21.45
|
||||
263 20.32 21.57
|
||||
265 25.39 13.60
|
||||
267 23.38 12.95
|
||||
269 25.03 12.14
|
||||
271 25.97 11.00
|
||||
273 29.16 7.38
|
||||
275 31.07 8.29
|
||||
River 24.15 31.06
|
||||
Lake 8.00 27.53
|
||||
1 27.46 9.84
|
||||
2 32.99 3.45
|
||||
3 29.41 27.27
|
||||
|
||||
[VERTICES]
|
||||
;Link X-Coord Y-Coord
|
||||
|
||||
[LABELS]
|
||||
;X-Coord Y-Coord Label & Anchor Node
|
||||
8.00 29.42 "LAKE"
|
||||
25.00 31.10 "RIVER"
|
||||
|
||||
[BACKDROP]
|
||||
DIMENSIONS 6.16 -1.55 46.70 32.61
|
||||
UNITS None
|
||||
FILE
|
||||
OFFSET 0.00 0.00
|
||||
|
||||
[END]
|
||||
Binary file not shown.
Binary file not shown.
+77766
File diff suppressed because it is too large
Load Diff
+1
-4
@@ -29,7 +29,6 @@ email-validator==2.3.0
|
||||
esda==2.7.0
|
||||
et_xmlfile==2.0.0
|
||||
exceptiongroup==1.3.1
|
||||
fakeredis==2.33.0
|
||||
fastapi==0.128.0
|
||||
fastmcp==2.9.2
|
||||
fonttools==4.58.0
|
||||
@@ -102,12 +101,10 @@ pyarmor==9.0.7
|
||||
pyarmor.cli.core==7.6.4
|
||||
pybind11-stubgen==2.5.1
|
||||
pycparser==2.23
|
||||
pyclipper==1.4.0
|
||||
pydantic==2.10.6
|
||||
pydantic-settings==2.12.0
|
||||
pydantic_core==2.27.2
|
||||
pydevd-pycharm==243.16718.36
|
||||
pydocket==0.16.6
|
||||
Pygments==2.18.0
|
||||
PyJWT==2.10.1
|
||||
pykalman==0.10.2
|
||||
@@ -127,7 +124,6 @@ pytz==2025.2
|
||||
PyYAML==6.0.3
|
||||
pyzmq==26.2.1
|
||||
reactivex==4.0.4
|
||||
redis==5.2.1
|
||||
referencing==0.36.2
|
||||
requests==2.32.3
|
||||
rich==14.2.0
|
||||
@@ -168,3 +164,4 @@ zmq==0.0.0
|
||||
pymoo==0.6.1.6
|
||||
scikit-learn==1.6.1
|
||||
scipy==1.15.2
|
||||
pyclipper==1.4.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,138 @@
|
||||
id,type,associated_element_id,associated_pattern,associated_pipe_flow_id,associated_source_outflow_id1,associated_source_outflow_id2,associated_source_outflow_id3,associated_source_outflow_id4,associated_source_outflow_id5,API_query_id,transmission_mode,transmission_frequency,reliability,X_coor,Y_coor
|
||||
ZBBDJSCP000002,reservoir_liquid_level,ZBBDJSCP000002,HighPressure,,,,,,,2497,realtime,0:01:00,1,106.414403,29.83252977
|
||||
R00003,reservoir_liquid_level,R00003,LowPressure,,,,,,,2571,realtime,0:01:00,1,106.414403,29.83252977
|
||||
ZBBDTJSC000002,tank_liquid_level,ZBBDTJSC000002,,,,,,,,4780,realtime,0:01:00,1,106.377934,29.80097177
|
||||
ZBBDTJSC000001,tank_liquid_level,ZBBDTJSC000001,,,,,,,,9774,realtime,0:01:00,1,106.414257,29.82031872
|
||||
PU00000,fixed_pump,PU00000,1#,,,,,,,2747,realtime,0:01:00,1,106.414403,29.83252977
|
||||
PU00001,fixed_pump,PU00001,2#,,,,,,,2776,realtime,0:01:00,1,106.414403,29.83252977
|
||||
PU00002,fixed_pump,PU00002,3#,,,,,,,2730,realtime,0:01:00,1,106.414403,29.83252977
|
||||
PU00003,fixed_pump,PU00003,4#,,,,,,,2787,realtime,0:01:00,1,106.414403,29.83252977
|
||||
PU00004,variable_pump,PU00004,5#,,,,,,,2500,realtime,0:01:00,1,106.414403,29.83252977
|
||||
PU00005,variable_pump,PU00005,6#,,,,,,,2502,realtime,0:01:00,1,106.414403,29.83252977
|
||||
PU00006,variable_pump,PU00006,7#,,,,,,,2504,realtime,0:01:00,1,106.414403,29.83252977
|
||||
ZBBGXSZW000377,source_outflow,ZBBGXSZW000377,DN900,,ZBBGXSZW000377,,,,,2498,realtime,0:01:00,1,106.4194993,29.83772834
|
||||
P17021,source_outflow,P17021,DN500,,P17021,,,,,3854,realtime,0:01:00,1,106.4194939,29.83810336
|
||||
P16504,source_outflow,P16504,DN1000,,P16504,,,,,3853,realtime,0:01:00,1,106.4195338,29.83781925
|
||||
ZBBGXSZK001071,pipe_flow,ZBBGXSZK001071,BianDianZhan,,ZBBGXSZW000377,P17021,,,,9521,non_realtime,6:00:00,1,106.4003826,29.81743895
|
||||
ZBBGXSZK001163,pipe_flow,ZBBGXSZK001163,BeiNanDaDao,,ZBBGXSZW000377,P17021,,,,9478,non_realtime,6:00:00,1,106.3994217,29.80947624
|
||||
ZBBGXSZK001105,pipe_flow,ZBBGXSZK001105,TianShengLiJie,,ZBBGXSZW000377,P17021,,,,9506,non_realtime,6:00:00,1,106.4180443,29.81625954
|
||||
P10755,pipe_flow,P10755,XueYuanXiaoQu,,ZBBGXSZW000377,P17021,,,,9497,non_realtime,6:00:00,1,106.4131703,29.81316079
|
||||
ZBBGXSZK001128,pipe_flow,ZBBGXSZK001128,YunHuaLu,,ZBBGXSZW000377,P17021,,,,9480,non_realtime,6:00:00,1,106.3948247,29.80702128
|
||||
P10839,pipe_flow,P10839,GaoJiaQiao,,ZBBGXSZW000377,P17021,,,,9507,non_realtime,6:00:00,1,106.4079379,29.82356299
|
||||
ZBBGXSZK001079,pipe_flow,ZBBGXSZK001079,LuZuoFuLuXiaDuan,,ZBBGXSZW000377,P17021,,,,9487,non_realtime,6:00:00,1,106.3922472,29.80343211
|
||||
ZBBGXSZK001096(L),pipe_flow,ZBBGXSZK001096(L),TianRunCheng,,ZBBGXSZW000377,P17021,,,,9485,non_realtime,6:00:00,1,106.3907546,29.80015062
|
||||
P15959,pipe_flow,P15959,CaoJiaBa,,ZBBGXSZW000377,P17021,,,,9505,non_realtime,6:00:00,1,106.3664514,29.76674381
|
||||
ZBBGXSZK001031,pipe_flow,ZBBGXSZK001031,PuLingChang,,ZBBGXSZW000377,P17021,,,,9481,non_realtime,6:00:00,1,106.3807941,29.75983691
|
||||
P12544,pipe_flow,P12544,QiLongXiaoQu,,ZBBGXSZW000377,P17021,,,,9524,non_realtime,6:00:00,1,106.367557,29.76592301
|
||||
ZBBGXTYK003616,pipe_flow,ZBBGXTYK003616,TuanXiao,,ZBBGXSZW000377,P17021,,,,9498,non_realtime,6:00:00,1,106.3741092,29.77647684
|
||||
ZBBGXTYK003427,pipe_flow,ZBBGXTYK003427,ChengBeiCaiShiKou,,P16504,,,,,9488,non_realtime,6:00:00,1,106.4211904,29.8337277
|
||||
ZBBGXSZK001775,pipe_flow,ZBBGXSZK001775,WenXingShe,,P16504,,,,,9482,non_realtime,6:00:00,1,106.4296384,29.83322552
|
||||
ZBBGXTYK003392,pipe_flow,ZBBGXTYK003392,YueLiangTianBBGJCZ,,P16504,,,,,9510,non_realtime,6:00:00,1,106.4338386,29.82316156
|
||||
ZBBGXSZK001166,pipe_flow,ZBBGXSZK001166,YueLiangTian,,P16504,,,,,9486,non_realtime,6:00:00,1,106.4324165,29.82465186
|
||||
P13961,pipe_flow,P13961,YueLiangTian200,,P16504,,,,,9520,non_realtime,6:00:00,1,106.4256,29.8222
|
||||
ZBBGXTYK003069,pipe_flow,ZBBGXTYK003069,ChengTaoChang,,P16504,,,,,9479,non_realtime,6:00:00,1,106.431,29.8153
|
||||
ZBBGXTYK003355,pipe_flow,ZBBGXTYK003355,HuoCheZhan,,P16504,,,,,9518,non_realtime,6:00:00,1,106.4284178,29.80932951
|
||||
ZBBGXSZW000335,pipe_flow,ZBBGXSZW000335,LiangKu,,P16504,,,,,9512,non_realtime,6:00:00,1,106.4241088,29.80023184
|
||||
ZBBGXSZK001957,pipe_flow,ZBBGXSZK001957,QunXingLu,,P16504,,,,,9489,non_realtime,6:00:00,1,106.4302597,29.81089267
|
||||
P05940,pipe_flow,P05940,TuanShanBaoZhongShiHua,,ZBBGXSZW000377,P17021,,,,9535,non_realtime,6:00:00,1,106.4168586,29.83510228
|
||||
ZBBGXSZK002419,pipe_flow,ZBBGXSZK002419,XieMa,,ZBBGXSZW000377,P17021,,,,7423,non_realtime,6:00:00,1,106.3893687,29.79893615
|
||||
P16512,pipe_flow,P16512,BeiWenQuanJiuHaoErQi,,ZBBGXSZW000377,P17021,,,,,non_realtime,6:00:00,1,106.4077965,29.82305305
|
||||
GSD230117133902A0620C9E4A2F,pipe_flow,GSD230117133902A0620C9E4A2F,LaiYinHuSiQi,,ZBBGXSZW000377,P17021,,,,,non_realtime,6:00:00,1,106.3793446,29.80116239
|
||||
ZBBGXSZK000647,pipe_flow,ZBBGXSZK000647,JiuYuanErTongYiYuan,,P16504,,,,,,non_realtime,6:00:00,1,106.4256984,29.82186294
|
||||
ZBBGXSZK000247,pipe_flow,ZBBGXSZK000247,TangDouHua,,P16504,,,,,,non_realtime,6:00:00,1,106.4351488,29.83505307
|
||||
P16773,pipe_flow,P16773,TaiJiBinJiangErQi(SanJi),,P16504,,,,,9531,non_realtime,6:00:00,1,106.4453773,29.82620736
|
||||
ZBBGXSZK002373,pipe_flow,ZBBGXSZK002373,ZhangDouHua,,P16504,,,,,,non_realtime,6:00:00,1,106.4404323,29.82998935
|
||||
P12942,pipe_flow,P12942,JinYunXiaoQuDN400,,P16504,,,,,,non_realtime,6:00:00,1,106.3865726,29.80903033
|
||||
P16769,pipe_flow,P16769,ShiYanCun,,P16504,,,,,9492,non_realtime,6:00:00,1,106.4307834,29.82932991
|
||||
ZBBDTZDP004690,demand,ZBBDTZDP004690,ChuanYiJiXiao,,ZBBGXSZW000377,P17021,,,,9501,non_realtime,6:00:00,1,106.4092,29.8601
|
||||
ZBBDFQJL000025,demand,ZBBDFQJL000025,BeiQuanHuaYuan,,ZBBGXSZW000377,P17021,,,,9405,non_realtime,6:00:00,1,106.4121,29.8275
|
||||
ZBBDFQJL000030,demand,ZBBDFQJL000030,ZhuangYuanFuDi,,ZBBGXSZW000377,P17021,,,,9509,non_realtime,6:00:00,1,106.4021,29.8021
|
||||
ZBBDFQJL000036,demand,ZBBDFQJL000036,JingNingJiaYuan,,ZBBGXSZW000377,P17021,,,,9494,non_realtime,6:00:00,1,106.3987,29.8095
|
||||
ZBBDFQJL000034,demand,ZBBDFQJL000034,308,,ZBBGXSZW000377,P17021,,,,9504,non_realtime,6:00:00,1,106.4008,29.8166
|
||||
ZBBDFQJL000032,demand,ZBBDFQJL000032,JiaYinYuan,,ZBBGXSZW000377,P17021,,,,9513,non_realtime,6:00:00,1,106.3889,29.8048
|
||||
ZBBDFQJL000031,demand,ZBBDFQJL000031,XinChengGuoJi,,ZBBGXSZW000377,P17021,,,,9517,non_realtime,6:00:00,1,106.389,29.805
|
||||
ZBBDFQJL000033,demand,ZBBDFQJL000033,YiJingBeiChen,,ZBBGXSZW000377,P17021,,,,9519,non_realtime,6:00:00,1,106.3906,29.8041
|
||||
ZBBDFQJL000035,demand,ZBBDFQJL000035,ZhongYangXinDu,,ZBBGXSZW000377,P17021,,,,9522,non_realtime,6:00:00,1,106.3937,29.8044
|
||||
J06186,demand,J06186,XinHaiJiaYuan,,ZBBGXSZW000377,P17021,,,,9525,non_realtime,6:00:00,1,106.4067,29.8278
|
||||
ZBBDFQJL000052,demand,ZBBDFQJL000052,DongFengJie,,ZBBGXSZW000377,P17021,,,,9526,non_realtime,6:00:00,1,106.3678,29.766
|
||||
ZBBDFQJL000049,demand,ZBBDFQJL000049,DingYaXinYu,,ZBBGXSZW000377,P17021,,,,9500,non_realtime,6:00:00,1,106.3674,29.7654
|
||||
GSD2302160925534D50CD17540D_E,demand,GSD2302160925534D50CD17540D_End,ZiYunTai,,ZBBGXSZW000377,P17021,,,,9529,non_realtime,6:00:00,1,106.387,29.7884
|
||||
ZBBDFQJL000050,demand,ZBBDFQJL000050,XieMaGuangChang,,ZBBGXSZW000377,P17021,,,,9477,non_realtime,6:00:00,1,106.3652,29.7657
|
||||
GSD2307192058578BCE265C6EA8,demand,GSD2307192058578BCE265C6EA8,YongJinFu,,ZBBGXSZW000377,P17021,,,,9534,non_realtime,6:00:00,1,106.3831,29.781
|
||||
ZBBDFQJL000028,demand,ZBBDFQJL000028,PanXiMingDu,,P16504,,,,,9514,non_realtime,6:00:00,1,106.4223,29.821
|
||||
GSD230113095617514F4A2066D7_E,demand,GSD230113095617514F4A2066D7_End,WanKeJinYuHuaFuGaoCeng,,P16504,,,,,9528,non_realtime,6:00:00,1,106.4228,29.8113
|
||||
ZBBDFQJL000014,demand,ZBBDFQJL000014,KeJiXiao,,P16504,,,,,9503,non_realtime,6:00:00,1,106.4332,29.8258
|
||||
ZBBDFQJL000016,demand,ZBBDFQJL000016,LuGouQiao,,P16504,,,,,9527,non_realtime,6:00:00,1,106.437,29.8285
|
||||
ZBBDFQJL000057,demand,ZBBDFQJL000057,LongJiangHuaYuan,,P16504,,,,,9495,non_realtime,6:00:00,1,106.4316,29.8309
|
||||
ZBBDFQJL000012,demand,ZBBDFQJL000012,LaoQiZhongDui,,P16504,,,,,9496,non_realtime,6:00:00,1,106.433,29.8239
|
||||
ZBBDFQJL000007,demand,ZBBDFQJL000007,TianQiDaSha,,P16504,,,,,9502,non_realtime,6:00:00,1,106.4346,29.8319
|
||||
ZBBDFQJL000013,demand,ZBBDFQJL000013,TianShengPaiChuSuo,,P16504,,,,,9499,non_realtime,6:00:00,1,106.4253,29.8222
|
||||
ZBBDFQJL000037,demand,ZBBDFQJL000037,TianShengShangPin,,P16504,,,,,9493,non_realtime,6:00:00,1,106.4305,29.8264
|
||||
ZBBDFQJL000009,demand,ZBBDFQJL000009,JiaoTang,,P16504,,,,,9511,non_realtime,6:00:00,1,106.4402,29.826
|
||||
ZBBDFQJL000011,demand,ZBBDFQJL000011,RenMinHuaYuan,,P16504,,,,,9508,non_realtime,6:00:00,1,106.4359,29.8238
|
||||
ZBBDFQJL000005,demand,ZBBDFQJL000005,TaiJiBinJiangYiQi,,P16504,,,,,9484,non_realtime,6:00:00,1,106.4443,29.8245
|
||||
ZBBDFQJL000006,demand,ZBBDFQJL000006,TianQiHuaYuan,,P16504,,,,,9523,non_realtime,6:00:00,1,106.4361,29.8189
|
||||
ZBBDFQJL000004,demand,ZBBDFQJL000004,TaiJiBinJiangErQi,,P16504,,,,,9515,non_realtime,6:00:00,1,106.4443,29.8297
|
||||
ZBBDFQJL000039,demand,ZBBDFQJL000039,122Zhong,,P16504,,,,,9516,non_realtime,6:00:00,1,106.4284,29.8109
|
||||
GSD230112144241F42EF6065148_E,demand,GSD230112144241F42EF6065148_End,WanKeJinYuHuaFuYangFang,,P16504,,,,,9530,non_realtime,6:00:00,1,106.4263,29.8154
|
||||
J06194,pressure,J06194,,,,,,,,2514,realtime,0:01:00,1,106.4195183,29.83782213
|
||||
J06190,pressure,J06190,,,,,,,,2510,realtime,0:01:00,1,106.4194644,29.83781489
|
||||
ZBBDFQJL000025_p,pressure,ZBBDFQJL000025,,,,,,,,9536,non_realtime,6:00:00,1,106.4120554,29.82753087
|
||||
ZBBDFQJL000050_p,pressure,ZBBDFQJL000050,,,,,,,,9537,non_realtime,6:00:00,1,106.3651228,29.76558811
|
||||
ZBBDFQJL000018_p,pressure,ZBBDFQJL000018,,,,,,,,9538,non_realtime,6:00:00,1,106.3994298,29.809484
|
||||
ZBBDFQJL000002_p,pressure,ZBBDFQJL000002,,,,,,,,9539,non_realtime,6:00:00,1,106.4309932,29.81534215
|
||||
ZBBDFQJL000019_p,pressure,ZBBDFQJL000019,,,,,,,,9540,non_realtime,6:00:00,1,106.3948196,29.80700511
|
||||
ZBBDFQJL000047_p,pressure,ZBBDFQJL000047,,,,,,,,9541,non_realtime,6:00:00,1,106.3807979,29.75983831
|
||||
ZBBDFQJL000001_p,pressure,ZBBDFQJL000001,,,,,,,,9542,non_realtime,6:00:00,1,106.4296304,29.83324097
|
||||
ZBBDTZDP006510_p,pressure,ZBBDTZDP006510,,,,,,,,9543,non_realtime,6:00:00,1,106.4143,29.8204
|
||||
ZBBDFQJL000005_p,pressure,ZBBDFQJL000005,,,,,,,,9544,non_realtime,6:00:00,1,106.4442613,29.824473
|
||||
ZBBDFQJL000023_p,pressure,ZBBDFQJL000023,,,,,,,,9545,non_realtime,6:00:00,1,106.3907545,29.80014626
|
||||
ZBBDPQFM000144_p,pressure,ZBBDPQFM000144,,,,,,,,9546,non_realtime,6:00:00,1,106.4324087,29.82466081
|
||||
ZBBDFQJL000022_p,pressure,ZBBDFQJL000022,,,,,,,,9547,non_realtime,6:00:00,1,106.3922466,29.80343116
|
||||
ZBBDFQJL000026_p,pressure,ZBBDFQJL000026,,,,,,,,9548,non_realtime,6:00:00,1,106.4211902,29.83372698
|
||||
ZBBDFQJL000027_p,pressure,ZBBDFQJL000027,,,,,,,,9549,non_realtime,6:00:00,1,106.4302659,29.81087884
|
||||
ZBBDFQJL000020_p,pressure,ZBBDFQJL000020,,,,,,,,9551,non_realtime,6:00:00,1,106.4069939,29.81230584
|
||||
ZBBDFQJL000015_p,pressure,ZBBDFQJL000015,,,,,,,,9552,non_realtime,6:00:00,1,106.4307918,29.82933519
|
||||
ZBBDFQJL000037_p,pressure,ZBBDFQJL000037,,,,,,,,9553,non_realtime,6:00:00,1,106.4305041,29.82636702
|
||||
ZBBDFQJL000036_p,pressure,ZBBDFQJL000036,,,,,,,,9554,non_realtime,6:00:00,1,106.3987322,29.80947672
|
||||
ZBBDFQJL000057_p,pressure,ZBBDFQJL000057,,,,,,,,9555,non_realtime,6:00:00,1,106.4315801,29.83086849
|
||||
ZBBDFQJL000012_p,pressure,ZBBDFQJL000012,,,,,,,,9556,non_realtime,6:00:00,1,106.4330174,29.82386587
|
||||
ZBBDFQJL000021_p,pressure,ZBBDFQJL000021,,,,,,,,9557,non_realtime,6:00:00,1,106.413164,29.81315876
|
||||
ZBBDFQJL000045_p,pressure,ZBBDFQJL000045,,,,,,,,9558,non_realtime,6:00:00,1,106.3741065,29.77646525
|
||||
ZBBDFQJL000013_p,pressure,ZBBDFQJL000013,,,,,,,,9559,non_realtime,6:00:00,1,106.4253422,29.82216074
|
||||
ZBBDFQJL000049_p,pressure,ZBBDFQJL000049,,,,,,,,9560,non_realtime,6:00:00,1,106.3673675,29.76535923
|
||||
ZBBDFQJL000053_p,pressure,ZBBDFQJL000053,,,,,,,,9561,non_realtime,6:00:00,1,106.4091385,29.86001467
|
||||
ZBBDFQJL000007_p,pressure,ZBBDFQJL000007,,,,,,,,9562,non_realtime,6:00:00,1,106.4345765,29.83187159
|
||||
ZBBDFQJL000014_p,pressure,ZBBDFQJL000014,,,,,,,,9563,non_realtime,6:00:00,1,106.4332467,29.82578845
|
||||
ZBBDFQJL000034_p,pressure,ZBBDFQJL000034,,,,,,,,9564,non_realtime,6:00:00,1,106.4008362,29.81662925
|
||||
ZBBDFQJL000051_p,pressure,ZBBDFQJL000051,,,,,,,,9565,non_realtime,6:00:00,1,106.3664647,29.76674002
|
||||
ZBBDFQJL000029_p,pressure,ZBBDFQJL000029,,,,,,,,9566,non_realtime,6:00:00,1,106.4180536,29.81625021
|
||||
ZBBDFQJL000038_p,pressure,ZBBDFQJL000038,,,,,,,,9567,non_realtime,6:00:00,1,106.4079197,29.82356534
|
||||
ZBBDFQJL000011_p,pressure,ZBBDFQJL000011,,,,,,,,9568,non_realtime,6:00:00,1,106.4359412,29.82375869
|
||||
ZBBDFQJL000030_p,pressure,ZBBDFQJL000030,,,,,,,,9569,non_realtime,6:00:00,1,106.4021179,29.80210907
|
||||
ZBBDFQJL000024_p,pressure,ZBBDFQJL000024,,,,,,,,9570,non_realtime,6:00:00,1,106.4338355,29.82316474
|
||||
ZBBDFQJL000009_p,pressure,ZBBDFQJL000009,,,,,,,,9571,non_realtime,6:00:00,1,106.4402264,29.825971
|
||||
ZBBDFQJL000042_p,pressure,ZBBDFQJL000042,,,,,,,,9572,non_realtime,6:00:00,1,106.4241609,29.80026986
|
||||
ZBBDFQJL000032_p,pressure,ZBBDFQJL000032,,,,,,,,9573,non_realtime,6:00:00,1,106.3889269,29.80481683
|
||||
ZBBDFQJL000028_p,pressure,ZBBDFQJL000028,,,,,,,,9574,non_realtime,6:00:00,1,106.4222702,29.82103146
|
||||
ZBBDFQJL000004_p,pressure,ZBBDFQJL000004,,,,,,,,9575,non_realtime,6:00:00,1,106.4442663,29.82966834
|
||||
ZBBDFQJL000039_p,pressure,ZBBDFQJL000039,,,,,,,,9576,non_realtime,6:00:00,1,106.4284664,29.81088051
|
||||
ZBBDFQJL000031_p,pressure,ZBBDFQJL000031,,,,,,,,9577,non_realtime,6:00:00,1,106.3890122,29.80504763
|
||||
ZBBDFQJL000043_p,pressure,ZBBDFQJL000043,,,,,,,,9578,non_realtime,6:00:00,1,106.4284118,29.80929921
|
||||
ZBBDFQJL000033_p,pressure,ZBBDFQJL000033,,,,,,,,9579,non_realtime,6:00:00,1,106.3906102,29.80408746
|
||||
ZBBDFQJL000010_p,pressure,ZBBDFQJL000010,,,,,,,,9580,non_realtime,6:00:00,1,106.4256312,29.82218279
|
||||
ZBBDFQJL000041_p,pressure,ZBBDFQJL000041,,,,,,,,9581,non_realtime,6:00:00,1,106.4003806,29.81743804
|
||||
ZBBDFQJL000035_p,pressure,ZBBDFQJL000035,,,,,,,,9582,non_realtime,6:00:00,1,106.3937231,29.80441954
|
||||
ZBBDFQJL000006_p,pressure,ZBBDFQJL000006,,,,,,,,9583,non_realtime,6:00:00,1,106.4361409,29.81886211
|
||||
ZBBDFQJL000048_p,pressure,ZBBDFQJL000048,,,,,,,,9584,non_realtime,6:00:00,1,106.367557,29.76592299
|
||||
J06186_p,pressure,J06186,,,,,,,,9585,non_realtime,6:00:00,1,106.4069218,29.82794812
|
||||
ZBBDFQJL000052_p,pressure,ZBBDFQJL000052,,,,,,,,9586,non_realtime,6:00:00,1,106.3678407,29.76602511
|
||||
ZBBDFQJL000016_p,pressure,ZBBDFQJL000016,,,,,,,,9587,non_realtime,6:00:00,1,106.4369887,29.82851071
|
||||
GSD230113095617514F4A2066D7_E_p,pressure,GSD230113095617514F4A2066D7_End,,,,,,,,9588,non_realtime,6:00:00,1,106.4227365,29.811344
|
||||
GSD2302160925534D50CD17540D_E_p,pressure,GSD2302160925534D50CD17540D_End,,,,,,,,9589,non_realtime,6:00:00,1,106.3870281,29.78840589
|
||||
GSD230112144241F42EF6065148_E_p,pressure,GSD230112144241F42EF6065148_End,,,,,,,,9590,non_realtime,6:00:00,1,106.4262502,29.81534482
|
||||
J06198_p,pressure,J06198,,,,,,,,9591,non_realtime,6:00:00,1,106.4454034,29.82619719
|
||||
J06197_p,pressure,J06197,,,,,,,,9592,non_realtime,6:00:00,1,106.4380272,29.82741219
|
||||
ZBBDYJGP000127_p,pressure,ZBBDYJGP000127,,,,,,,,9593,non_realtime,6:00:00,1,106.4006775,29.81822706
|
||||
GSD2307192058578BCE265C6EA8_p,pressure,GSD2307192058578BCE265C6EA8,,,,,,,,9594,non_realtime,6:00:00,1,106.383014,29.78097074
|
||||
ZBBDTZDP008185_p,pressure,ZBBDTZDP008185,,,,,,,,9595,non_realtime,6:00:00,1,106.4168547,29.83510138
|
||||
|
@@ -0,0 +1,47 @@
|
||||
-- ============================================
|
||||
-- TJWater Server 时区统一迁移脚本
|
||||
-- 将历史无时区时间列升级为 TIMESTAMP WITH TIME ZONE
|
||||
-- 约定:历史无时区值按 UTC 解释
|
||||
-- ============================================
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'users'
|
||||
AND column_name = 'created_at'
|
||||
AND data_type = 'timestamp without time zone'
|
||||
) THEN
|
||||
EXECUTE 'ALTER TABLE public.users
|
||||
ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING created_at AT TIME ZONE ''UTC''';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'users'
|
||||
AND column_name = 'updated_at'
|
||||
AND data_type = 'timestamp without time zone'
|
||||
) THEN
|
||||
EXECUTE 'ALTER TABLE public.users
|
||||
ALTER COLUMN updated_at TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING updated_at AT TIME ZONE ''UTC''';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'audit_logs'
|
||||
AND column_name = 'timestamp'
|
||||
AND data_type = 'timestamp without time zone'
|
||||
) THEN
|
||||
EXECUTE 'ALTER TABLE public.audit_logs
|
||||
ALTER COLUMN timestamp TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING "timestamp" AT TIME ZONE ''UTC''';
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -0,0 +1,53 @@
|
||||
-- Metadata auth management schema patch.
|
||||
-- Keycloak owns login credentials; TJWater stores only business identity and access.
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
users_id_type text;
|
||||
BEGIN
|
||||
SELECT data_type INTO users_id_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'users'
|
||||
AND column_name = 'id';
|
||||
|
||||
IF users_id_type IS NULL THEN
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
keycloak_id UUID UNIQUE NOT NULL,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
email VARCHAR(100) UNIQUE NOT NULL,
|
||||
role VARCHAR(20) DEFAULT 'user' NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
is_superuser BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
attributes JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
last_login_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
ELSIF users_id_type <> 'uuid' THEN
|
||||
RAISE EXCEPTION
|
||||
'Existing public.users.id is %, not uuid. Export old local users, create Keycloak accounts, then migrate to metadata UUID users before applying this patch.',
|
||||
users_id_type;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS keycloak_id UUID,
|
||||
ADD COLUMN IF NOT EXISTS attributes JSONB,
|
||||
ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE users
|
||||
ALTER COLUMN role SET DEFAULT 'user';
|
||||
|
||||
ALTER TABLE users
|
||||
DROP CONSTRAINT IF EXISTS users_role_check;
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT users_role_check
|
||||
CHECK (role IN ('admin', 'user'));
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_keycloak_id ON users(keycloak_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active);
|
||||
@@ -0,0 +1,68 @@
|
||||
-- Metadata project configuration schema patch.
|
||||
-- Admin APIs write these tables; operators should not hand-edit encrypted values.
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
gs_workspace VARCHAR(100) UNIQUE NOT NULL,
|
||||
map_extent JSONB,
|
||||
status VARCHAR(20) DEFAULT 'active' NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT projects_status_check CHECK (status IN ('active', 'inactive', 'archived'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);
|
||||
|
||||
DROP TRIGGER IF EXISTS update_projects_updated_at ON projects;
|
||||
CREATE TRIGGER update_projects_updated_at
|
||||
BEFORE UPDATE ON projects
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_project_membership (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
project_role VARCHAR(20) DEFAULT 'viewer' NOT NULL,
|
||||
CONSTRAINT user_project_membership_role_check
|
||||
CHECK (project_role IN ('member', 'viewer')),
|
||||
CONSTRAINT user_project_membership_unique UNIQUE (user_id, project_id)
|
||||
);
|
||||
|
||||
-- The unique (user_id, project_id) index already supports user-side lookups.
|
||||
CREATE INDEX IF NOT EXISTS idx_user_project_membership_project_id
|
||||
ON user_project_membership(project_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS project_databases (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
db_role VARCHAR(20) NOT NULL,
|
||||
db_type VARCHAR(20) NOT NULL,
|
||||
dsn_encrypted TEXT NOT NULL,
|
||||
pool_min_size INTEGER DEFAULT 2 NOT NULL,
|
||||
pool_max_size INTEGER DEFAULT 10 NOT NULL,
|
||||
CONSTRAINT project_databases_unique_role UNIQUE (project_id, db_role),
|
||||
CONSTRAINT project_databases_role_check CHECK (db_role IN ('biz_data', 'iot_data')),
|
||||
CONSTRAINT project_databases_type_check CHECK (db_type IN ('postgresql', 'timescaledb')),
|
||||
CONSTRAINT project_databases_pool_check CHECK (
|
||||
pool_min_size >= 1 AND pool_max_size >= pool_min_size
|
||||
)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_project_databases_project_id
|
||||
ON project_databases(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_project_databases_role
|
||||
ON project_databases(db_role);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user