diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml index a68b2eb..b617f26 100644 --- a/.gitea/workflows/docker-build.yml +++ b/.gitea/workflows/docker-build.yml @@ -1,149 +1,168 @@ -name: Docker Build and Push - -# 仅主分支 (master) push 触发部署; 其他分支一律不触发 (见 readme「分支规则」) -# 每次提交: 构建前端 + 生成 OTA 升级包 (签名) → 打进 Docker 镜像 → 自动升级数据库 → 部署 -# OTA 随镜像发布: 容器内 OTA_PACKAGE_DIR=/app/ota-package 指向打包的 ota-package/ -# -# 分支策略 (2026-10 调整): -# - master = 主分支, 唯一部署出口, push 即发布生产 (容器 iboard, 宿主 3001) -# - dev-* = 开发集成分支, 只做 CI 校验 (构建/版本一致性), **不再自动部署**; -# 要上线必须先合并到 master 再推 master -# - tmp-* = 单个问题的临时分支, 合到 dev-* 后删除 -# - android-*= 只用于触发 APK 外壳构建, 不部署; 改动前须与 master 同步 -# -# 历史: 之前 dev-* 与 master 共用同一套生产环境 (推送即发布), 容易把半成品带上线, -# 现已取消 dev-* 的 push 触发。 -on: - push: - branches: - - master - -# 连续 push master 时取消还在跑的旧部署, 避免排队执行过时版本的完整部署 -concurrency: - group: deploy-master - cancel-in-progress: true - -jobs: - build-image: - runs-on: ubuntu-latest - env: - # 关键: 显式固定 Android/OTA 后端地址, 覆盖 runner 环境里可能存在的 - # 其他 VITE_API_BASE 值。否则 OTA 包里的前端可能连错后端, 导致 - # "OTA 更新后登录失败/进不去内部"。与 android-build.yml 保持一致。 - VITE_API_BASE: https://iboard.honor3.com - # OTA bundle 要求的最低原生二进制版本。 - # 与 package.json 的应用版本是**两个概念**: 前端 OTA 不碰原生层, - # 现有 APK 仍是 0.1.0, 所以这里固定 0.1.0 (否则旧 APK 会被判定为不兼容而收不到更新)。 - # 重打 APK 并确认原生层无破坏后, 再提升此值。 - OTA_MIN_BINARY: '0.1.0' - steps: - - uses: https://gitea.com/actions/checkout@v4 - - - name: Setup Node - uses: https://gitea.com/actions/setup-node@v4 - with: - node-version: 22 - cache: 'npm' - - - name: Check version consistency & branch - # 1) package.json / tauri.conf.json / Cargo.toml / Cargo.lock 四处版本必须完全相等且不带 -ota - # (tauri.conf.json 的 version 就是 APK 的 versionName) - # 2) 防止 OTA 版本号与分支错位: OTA 包的 version 直接取自 package.json, - # dev-0.2 分支上必须是 0.2.x, 否则线上 OTA 会报成上一个版本的号 - # (master 分支跳过分支前缀校验, 但四处一致性照样强制) - env: - GITHUB_REF_NAME: ${{ github.ref_name }} - run: node scripts/ci/check-version.mjs - - - name: Install deps - run: npm ci --no-audit --no-fund - - - name: Build frontend - # 全流程唯一一次前端构建: dist/ 既供 OTA 打包, 也通过 build context 进入镜像 - # (docker build 传 PREBUILT_DIST=true 跳过镜像内的重复构建) - run: npm run build - - - name: Install minisign - # 静态二进制直接下载, 免去每次 apt-get update + install - # 注意: release tarball 是 macOS 上打包的, 混有多个平台的 minisign 二进制, - # 逐个试运行挑出本机可执行的, 都不行再回退 apt - run: | - set -euo pipefail - mkdir -p /tmp/ms - curl -sL https://github.com/jedisct1/minisign/releases/download/0.12/minisign-0.12-linux.tar.gz | tar xz -C /tmp/ms - BIN="" - for c in $(find /tmp/ms -type f -name minisign); do - if "$c" -v >/dev/null 2>&1; then BIN="$c"; break; fi - done - if [ -n "$BIN" ]; then - sudo install -m755 "$BIN" /usr/local/bin/minisign - else - echo "未找到可执行的静态二进制, 回退 apt 安装" - sudo apt-get update && sudo apt-get install -y minisign - fi - minisign -v - - - name: Build & sign OTA package - # 每次主分支提交生成 OTA 升级包, sequence 默认时间戳 (单调递增) - # 发布说明 = 触发构建的 commit message (标题+正文; 打包脚本内安全截断 400 字符), - # commit sha 通过 --commit 单独写入 manifest, 前端分行展示 - # 产物 ota-package/ (tar.gz + latest.json + .minisig) 进入 docker build context, 由 Dockerfile COPY 进镜像 - env: - MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }} - run: | - set -euo pipefail - # 不用 head -c 截断: 那会把多字节汉字切成非法 UTF-8, latest.json 解析失败 - MSG="$(git log -1 --pretty=format:%B | sed -e '/^[[:space:]]*$/d')" - echo "notes: ${MSG}" - node scripts/build-ota.mjs --out ota-package --notes "${MSG}" --commit "${GITHUB_SHA}" --min-binary ${OTA_MIN_BINARY} - echo "===== latest.json =====" - cat ota-package/latest.json - echo "===== bundle sha256 =====" - sha256sum ota-package/iboard-ota.tar.gz - - - name: Build Docker image - # DATABASE_URL 仅在 prisma generate 阶段需要, 可用任何合法占位串 - # PREBUILT_DIST=true: 复用上一步构建好的 dist/, 镜像内跳过前端构建 - # DOCKER_BUILDKIT=1: Dockerfile 用了 cache mount 语法 - run: docker build -t iboard:latest --build-arg DATABASE_URL=mysql://build:build@127.0.0.1:3306/build --build-arg PREBUILT_DIST=true . - env: - DOCKER_BUILDKIT: '1' - - - name: List images - run: docker images - - - name: Apply DB migrations - # 数据库自动升级: 按 prisma/migrations/ 下的迁移顺序应用 (生产库由 master 部署时统一升级) - # migrate deploy 幂等, 无新迁移时为 no-op - # 位置关键: 在 "Remove old container" 之前 —— 建表/改表成功后才切换流量, - # 迁移失败则整个 workflow 中断, 不会用新镜像顶掉仍在运行的旧容器 - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - run: | - set -euo pipefail - npx prisma migrate status || true - npx prisma migrate deploy - - - name: Remove old container - run: docker rm -vf iboard || true - - - name: Prune dangling images - # 只清理本项目的悬空镜像 (旧 iboard:latest 被同名新镜像顶掉后变 dangling), - # 不再用 `docker system prune --volumes` —— 那会清光宿主机 build cache, - # 导致每次部署都全量重装 npm 依赖、重跑构建 - run: docker image prune -f - - - name: Run container - # DATABASE_URL 通过 gitea Actions secret 注入, 不进镜像层 - # OTA_PACKAGE_DIR 已在 Dockerfile 设为 /app/ota-package - # 米家凭证 (auth.txt) 不进 git: 运维放宿主机 /app/iboard/cache, bind-mount 进容器 (MIHOME_CACHE_DIR=/app/cache) - run: | - docker run -d --name iboard -p 3001:3001 \ - -e DATABASE_URL="${{ secrets.DATABASE_URL }}" \ - -e JWT_SECRET="${{ secrets.JWT_SECRET }}" \ - -e MIHOME_CACHE_DIR=/app/cache \ - -v /app/iboard/cache:/app/cache \ - iboard:latest - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - JWT_SECRET: ${{ secrets.JWT_SECRET }} +name: Docker Build and Push + +# 仅主分支 (master) push 触发部署; 其他分支一律不触发 (见 readme「分支规则」) +# 每次提交: 构建前端 + 生成 OTA 升级包 (签名) → 打进 Docker 镜像 → 自动升级数据库 → 部署 +# OTA 随镜像发布: 容器内 OTA_PACKAGE_DIR=/app/ota-package 指向打包的 ota-package/ +# +# 分支策略 (2026-10 调整): +# - master = 主分支, 唯一部署出口, push 即发布生产 (容器 iboard, 宿主 3001) +# - dev-* = 开发集成分支, 只做 CI 校验 (构建/版本一致性), **不再自动部署**; +# 要上线必须先合并到 master 再推 master +# - tmp-* = 单个问题的临时分支, 合到 dev-* 后删除 +# - android-*= 只用于触发 APK 外壳构建, 不部署; 改动前须与 master 同步 +# +# 历史: 之前 dev-* 与 master 共用同一套生产环境 (推送即发布), 容易把半成品带上线, +# 现已取消 dev-* 的 push 触发。 +on: + push: + branches: + - master + +# 连续 push master 时取消还在跑的旧部署, 避免排队执行过时版本的完整部署 +concurrency: + group: deploy-master + cancel-in-progress: true + +jobs: + build-image: + runs-on: ubuntu-latest + env: + # 关键: 显式固定 Android/OTA 后端地址, 覆盖 runner 环境里可能存在的 + # 其他 VITE_API_BASE 值。否则 OTA 包里的前端可能连错后端, 导致 + # "OTA 更新后登录失败/进不去内部"。与 android-build.yml 保持一致。 + VITE_API_BASE: https://iboard.honor3.com + # OTA bundle 要求的最低原生二进制版本 (hotswap 据此拒绝不兼容的旧外壳)。 + # 0.2.1 起必须为 0.2.1: 旧外壳 (0.1.0) 的 CSP 白名单编译进了原生二进制, + # 不含 https://iboard.honor3.com, 若让它拉到 0.2.1 前端会因 CSP 拦截而白屏。 + # 旧外壳请整包升级 (Windows 重装 exe / Android 换装 APK), 不要靠 OTA。 + OTA_MIN_BINARY: '0.2.1' + steps: + - uses: https://gitea.com/actions/checkout@v4 + + - name: Setup Node + uses: https://gitea.com/actions/setup-node@v4 + with: + node-version: 22 + cache: 'npm' + + - name: Check version consistency & branch + # 1) package.json / tauri.conf.json / Cargo.toml / Cargo.lock 四处版本必须完全相等且不带 -ota + # (tauri.conf.json 的 version 就是 APK 的 versionName) + # 2) 防止 OTA 版本号与分支错位: OTA 包的 version 直接取自 package.json, + # dev-0.2 分支上必须是 0.2.x, 否则线上 OTA 会报成上一个版本的号 + # (master 分支跳过分支前缀校验, 但四处一致性照样强制) + env: + GITHUB_REF_NAME: ${{ github.ref_name }} + run: node scripts/ci/check-version.mjs + + - name: Install deps + run: npm ci --no-audit --no-fund + + - name: Build frontend + # 全流程唯一一次前端构建: dist/ 既供 OTA 打包, 也通过 build context 进入镜像 + # (docker build 传 PREBUILT_DIST=true 跳过镜像内的重复构建) + run: npm run build + + - name: Install minisign + # 静态二进制直接下载, 免去每次 apt-get update + install + # 注意: release tarball 是 macOS 上打包的, 混有多个平台的 minisign 二进制, + # 逐个试运行挑出本机可执行的, 都不行再回退 apt + run: | + set -euo pipefail + mkdir -p /tmp/ms + curl -sL https://github.com/jedisct1/minisign/releases/download/0.12/minisign-0.12-linux.tar.gz | tar xz -C /tmp/ms + BIN="" + for c in $(find /tmp/ms -type f -name minisign); do + if "$c" -v >/dev/null 2>&1; then BIN="$c"; break; fi + done + if [ -n "$BIN" ]; then + sudo install -m755 "$BIN" /usr/local/bin/minisign + else + echo "未找到可执行的静态二进制, 回退 apt 安装" + sudo apt-get update && sudo apt-get install -y minisign + fi + minisign -v + + - name: Build & sign OTA package + # 每次主分支提交生成 OTA 升级包, sequence 默认时间戳 (单调递增) + # 发布说明 = 触发构建的 commit message (标题+正文; 打包脚本内安全截断 400 字符), + # commit sha 通过 --commit 单独写入 manifest, 前端分行展示 + # 产物 ota-package/ (tar.gz + latest.json + .minisig) 进入 docker build context, 由 Dockerfile COPY 进镜像 + env: + MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }} + run: | + set -euo pipefail + # 不用 head -c 截断: 那会把多字节汉字切成非法 UTF-8, latest.json 解析失败 + MSG="$(git log -1 --pretty=format:%B | sed -e '/^[[:space:]]*$/d')" + echo "notes: ${MSG}" + node scripts/build-ota.mjs --out ota-package --notes "${MSG}" --commit "${GITHUB_SHA}" --min-binary ${OTA_MIN_BINARY} + echo "===== latest.json =====" + cat ota-package/latest.json + echo "===== bundle sha256 =====" + sha256sum ota-package/iboard-ota.tar.gz + + - name: Publish OTA package to server + # CI 跑在容器内, 无法直接写宿主文件系统; 用 docker cp 经bind-mount容器 + # 把 ota-package 落到宿主 /app/iboard-prod/deploy/prod/data/ota + # (该目录被生产 app 容器挂载为 /app/ota-package)。 + # docker 不可用或目录缺失时仅告警, 不影响发布。 + run: | + set -euo pipefail + HOST_DIR=/app/iboard-prod + if ! docker info >/dev/null 2>&1; then + echo "::warning::docker 不可用, 跳过 OTA 包同步"; exit 0 + fi + docker run -d --name ota-bridge -v "${HOST_DIR}/deploy/prod/data/ota:/dst" alpine:3.20 sleep 30 >/dev/null + docker cp ota-package/. ota-bridge:/dst/ || echo "::warning::OTA 包复制失败" + docker rm -f ota-bridge >/dev/null 2>&1 || true + echo "== 宿主 OTA 目录 ==" + docker run --rm -v "${HOST_DIR}/deploy/prod/data/ota:/d" alpine:3.20 sh -c 'ls -la /d | head -6' + docker restart iboard-prod-app-1 >/dev/null 2>&1 || true + + - name: Build Docker image + # DATABASE_URL 仅在 prisma generate 阶段需要, 可用任何合法占位串 + # PREBUILT_DIST=true: 复用上一步构建好的 dist/, 镜像内跳过前端构建 + # DOCKER_BUILDKIT=1: Dockerfile 用了 cache mount 语法 + run: docker build -t iboard:latest --build-arg DATABASE_URL=mysql://build:build@127.0.0.1:3306/build --build-arg PREBUILT_DIST=true . + env: + DOCKER_BUILDKIT: '1' + + - name: List images + run: docker images + + - name: Deploy 0.2.1 production stack + # 0.2.1 起生产为单端口架构: app(3001 对外) + pg(内网) + powersync(内网), + # 客户端同步经 app 的 /sync 反代进入同步服务 (无独立同步端口)。 + # 部署逻辑在服务器的 /app/iboard-prod/scripts/deploy/prod.sh (幂等), 这里只负责: + # 1) 把刚构建好的镜像交给该脚本 + # 2) 未配置 PG 相关 secrets 或脚本缺失时跳过部署 (发布不失败, 仅提示) + env: + PG_PASSWORD: ${{ secrets.PG_PASSWORD }} + PS_STORAGE_PASSWORD: ${{ secrets.PS_STORAGE_PASSWORD }} + JWT_SECRET: ${{ secrets.JWT_SECRET }} + POWERSYNC_JWT_SECRET: ${{ secrets.POWERSYNC_JWT_SECRET }} + POWERSYNC_JWT_KEY_B64: ${{ secrets.POWERSYNC_JWT_KEY_B64 }} + run: | + set -euo pipefail + HOST_DIR=/app/iboard-prod + if [ -z "${PG_PASSWORD:-}" ]; then + echo "::warning::未配置 PG_PASSWORD 等 secrets, 跳过自动部署 (镜像已构建完成)" + echo "手工部署: ssh baihu 'bash $HOST_DIR/scripts/deploy/prod.sh'" + exit 0 + fi + if ! docker run --rm -v "$HOST_DIR:/w" alpine:3.20 test -f /w/scripts/deploy/prod.sh; then + echo "::warning::$HOST_DIR/scripts/deploy/prod.sh 不存在, 跳过部署" + exit 0 + fi + docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v "$HOST_DIR:$HOST_DIR" \ + -w "$HOST_DIR" \ + -e PG_PASSWORD="$PG_PASSWORD" \ + -e PS_STORAGE_PASSWORD="$PS_STORAGE_PASSWORD" \ + -e JWT_SECRET="$JWT_SECRET" \ + -e POWERSYNC_JWT_SECRET="$POWERSYNC_JWT_SECRET" \ + -e POWERSYNC_JWT_KEY_B64="$POWERSYNC_JWT_KEY_B64" \ + -e PUBLIC_URL=https://iboard.honor3.com \ + -e ROOT="$HOST_DIR" \ + docker:27-cli sh -c 'apk add -q bash curl >/dev/null 2>&1; bash /app/iboard-prod/scripts/deploy/prod.sh' diff --git a/.gitignore b/.gitignore index e9a0913..ac6b3ec 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,13 @@ deploy/0.2.1/.env # Windows ´ò°ü²úÎï (±¾µØÉú³É, ²»Èë¿â) /artifacts/ + +# Éú²ú²¿ÊðÃÜԿÓëÊý¾ݾí +/deploy/prod/.env +/deploy/prod/data/ + + +# ±¾µØÁÙʱ½ű¾ (º¬¿ÚÁî, ²»Èë¿â) +scripts/_*.mjs +.dsh-ops/ + diff --git a/README.md b/README.md index ec97cee..58a50b8 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,21 @@ 跨平台个人看板 (Tauri + React + Express + MariaDB)。用于收集和整理经济数据,但是以策略为主体重新组织。 -- 首页主要是放经济文章 -- 数据主要是放各种经济数据和图表 -- 策略是放按照经济理论组织的相关数据和图表的对比 +- 宏观:新闻 / 经济文章(本地),以及外接 ifinance 模块的分类(经济数据 / 策略研究 / 看板,运行时动态挂载为 tab) +- 数据与策略由外接 ifinance 模块承担(静态包,见 `../ifinance/readme.md`) +- 报警并入「监控」;油价模块已下线 -## 首页 +## 宏观 -首页显示新闻模块和报警模块的数据,按照时间最新优先,无限滚动,直到数据库中没有数据为止。 +宏观页固定「新闻 / 经济文章」两个本地 tab,并动态挂载外接 ifinance `info.json` 的 `categories[]` 作为额外 tab: -### 新闻模块 - -增加一个新闻模块,数据来源是每日夜间 3 点,通过妙想 MCP 获取国内外最新经济政治新闻,按影响大小排序 10 条。存入数据库。 - -### 报警模块 - -World Bank 中的数据只用来对比妙想 MCP 拉取的数据正确性。当同一时间的同一项数据不同时,记录一个报警。 +- 新闻:每日夜间 3 点通过妙想 MCP 获取国内外最新经济政治新闻,按影响大小排序 10 条,存入数据库;列表按时间倒序无限滚动。 +- 经济文章:由 `content/articles/*.md` 承载的长文。 +- 经济数据 / 策略研究 / …:交给外接 ifinance 模块,宿主只读取 `info.json` 并用 iframe 打开条目页(纯前端直连,见 `ifinance/readme.md` §9)。iBoard 本地不再保存指标 / 策略数据。 ## 监控 -监控分为服务器监控和服务监控两部分。服务器监控对接 komari,地址: +监控分为主机监控 / 服务监控 / 环境监控 / 报警四个 tab。主机监控对接 komari:;报警 tab 展示跨源数据差异与定时任务异常,按时间倒序。 --- @@ -274,3 +270,47 @@ Web 端不会自动下载包,也不会替换前端代码。 - 由于只有 `master` 会部署,**迁移真正作用到生产库的唯一时机就是 master 发布**; 开发分支上的迁移不会被自动应用,请在合并到 `master` 前确认可回滚。 - 本地改了 `prisma/schema.prisma` 后:写迁移 SQL → `npx prisma migrate deploy` 验证 → 提交迁移目录。 + +--- + +## Windows 本地实时预览(不打包) + +改完前端 UI 想立刻在 **Windows 外壳**里看效果,不必 `tauri build`、不必打 NSIS/便携包、也不必重装。 + +```bash +npm run win:dev # 构建(shell 模式) + 挂载 + 重启外壳 +npm run win:dev -- --no-build # 复用现有 dist, 只挂载+重启 (最快, 约 2 秒) +npm run win:dev -- --no-launch # 只挂载, 自己手动启动外壳 +npm run win:dev -- --exe "D:\path\iBoard.exe" +npm run win:dev -- --clear # 卸载覆盖, 外壳回到内置前端 +``` + +### 原理 + +外壳用 `tauri-plugin-hotswap`,启动时**先看覆盖目录有没有生效的前端包,没有才回退到 exe 内置的 `dist`**。 +这和线上 OTA 走的是同一条路径,所以只要把新构建的 `dist` 写进覆盖目录再重启外壳即可: + +``` +%APPDATA%\com.iboard.app\hotswap\ + current ← 文本文件, 内容为生效的目录名 (如 seq-1793068631298) + seq-1793068631298\ ← 本次前端 (dist 内容) + hotswap-meta.json +``` + +`hotswap-meta.json` 里有两个字段是**硬约束**,改错会导致覆盖包被丢弃或立刻回滚: + +| 字段 | 取值 | 原因 | +| --- | --- | --- | +| `min_binary_version` | **必须等于**外壳版本(当前 `0.2.1`) | 默认策略 `DiscardOnUpgrade` 在 `binary > min` 时丢弃缓存;安全线又要求 `binary >= min`,故只能取等号 | +| `confirmed` | **必须为 `true`** | 默认策略 `SingleLaunch` 对未确认版本会在启动时立即回滚 | + +另外 `sequence` 取 `Date.now()`(大值),让 OTA 检查认为"已是最新",避免线上更新把本地版本顶掉。 + +### 注意 + +- 覆盖目录就是外壳**真实的 OTA 落地目录**,挂载期间"我的"页会显示本地版本(`0.2.1-local`); + 测完建议 `npm run win:dev -- --clear` 复原,避免影响后续正常 OTA。 +- 脚本按 `src-tauri/tauri.conf.json` 的 `identifier` 推导覆盖目录,换 identifier 后无需改脚本。 +- 这只适用于**前端**改动。改了 `src-tauri/`(Rust/原生层、权限、CSP)必须重新打包外壳。 +- 想要带 HMR 的完整开发循环(改代码即时刷新、免手动重启)用 `npm run tauri:dev`, + 代价是首次要编译一次 debug 版 Rust 二进制。 + diff --git a/deploy/prod/.env.example b/deploy/prod/.env.example new file mode 100644 index 0000000..10fd62f --- /dev/null +++ b/deploy/prod/.env.example @@ -0,0 +1,9 @@ +# 生产环境变量 (deploy/prod/.env, 权限 600, 绝不入库) +# POWERSYNC_JWT_KEY_B64 = base64url(POWERSYNC_JWT_SECRET 的 UTF-8 字节) +# printf %s "$POWERSYNC_JWT_SECRET" | openssl base64 -A | tr '+/' '-_' | tr -d '=' +PG_PASSWORD=change-me +PS_STORAGE_PASSWORD=change-me +JWT_SECRET=change-me +POWERSYNC_JWT_SECRET=change-me +POWERSYNC_JWT_KEY_B64=change-me +PUBLIC_URL=https://iboard.honor3.com diff --git a/deploy/prod/README.md b/deploy/prod/README.md new file mode 100644 index 0000000..83fdbe7 --- /dev/null +++ b/deploy/prod/README.md @@ -0,0 +1,59 @@ +# iBoard 0.2.1 生产部署 (单端口, 无独立同步端口) + +``` +pg (内网) ← app (3001 对外, /sync 反代) → powersync (内网) +``` + +对外只有 **3001** 一个端口,域名 `https://iboard.honor3.com` 由 NPM 反代到这里; +同步服务不再单独暴露端口(客户端拿到的 `POWERSYNC_URL` 是域名本身,请求经 app 的 +`/sync`、`/socket` 反代进入内网 powersync)。 + +## 首次部署顺序 (关键: publication 要在建表之后) + +```bash +# 0) 目录: /app/iboard-prod (源码已上传), 镜像 iboard:latest 已构建 +cd /app/iboard-prod/deploy/prod +cp .env.example .env && chmod 600 .env # 填真实密钥 +mkdir -p data/pg data/cache + +# 1) 先只起数据库 +docker compose --env-file .env up -d pg + +# 2) 跑迁移建表 (一次性) +docker run --rm --network iboard-prod_default \ + -e DATABASE_URL="postgresql://iboard:@pg:5432/iboard" \ + iboard:latest npx prisma migrate deploy + +# 3) 建逻辑复制 publication (表已存在) +docker compose --env-file .env exec -T pg \ + psql -U iboard -d iboard -f /dev/stdin < sql/publication.sql + +# 4) 起应用与同步服务 +docker compose --env-file .env up -d app powersync + +# 5) 验证 +curl -s http://127.0.0.1:3001/api/health +docker compose logs --tail 20 powersync # 应看到 replication 激活 +``` + +## 数据迁移 (从现网 MySQL 快照) + +```bash +# 只读源库; 目标必须是空库 (有数据会跳过, ETL_FORCE=1 强制) +docker run --rm --network iboard-prod_default \ + -e DATABASE_URL="postgresql://iboard:@pg:5432/iboard" \ + -e SRC_DATABASE_URL="mysql://:@:3306/iboard" \ + iboard:latest node scripts/etl-mariadb-to-pg.mjs +``` + +迁移后需要重启 powersync 让它重新快照: + +```bash +docker compose --env-file .env restart powersync +``` + +## 版本与回滚 + +- 新版本: 构建新镜像 → `docker compose up -d app` (只重建 app, 数据卷不动) +- 回滚: 用上一次的镜像标签重新 `up -d app` +- 数据: `data/pg` 持久化; 变更前建议 `docker compose exec -T pg pg_dump -U iboard iboard > backup.sql` diff --git a/deploy/prod/docker-compose.yml b/deploy/prod/docker-compose.yml new file mode 100644 index 0000000..08e3e3e --- /dev/null +++ b/deploy/prod/docker-compose.yml @@ -0,0 +1,82 @@ +name: iboard-prod + +# iBoard 0.2.1 生产栈 (单端口对外) +# - app : 3001 对外 (唯一入口), 内部把 /sync 反代到 powersync +# - pg : PostgreSQL 16, 仅容器内网 (逻辑复制供同步服务消费) +# - powersync : 同步服务, 仅容器内网 (不对外暴露端口) +# +# 与旧的生产方式区别: 不再依赖宿主 MySQL, 数据库与同步服务都在栈内; +# 对外只有 3001 一个端口, 域名 https://iboard.honor3.com (NPM 反代) 即可。 +# +# 用法 (baihu:/app/iboard-prod): +# cp deploy/prod/.env.example deploy/prod/.env # 填密钥 +# docker compose --env-file deploy/prod/.env -f deploy/prod/docker-compose.yml up -d + +services: + pg: + image: postgres:16-alpine + restart: unless-stopped + command: ["postgres", "-c", "wal_level=logical"] + environment: + POSTGRES_USER: iboard + POSTGRES_PASSWORD: ${PG_PASSWORD:?PG_PASSWORD missing} + POSTGRES_DB: iboard + PS_STORAGE_PASSWORD: ${PS_STORAGE_PASSWORD:?PS_STORAGE_PASSWORD missing} + volumes: + - ./data/pg:/var/lib/postgresql/data + - ./initdb:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U iboard -d iboard"] + interval: 5s + timeout: 5s + retries: 12 + + app: + image: iboard:latest + build: + context: ../.. + dockerfile: Dockerfile + restart: unless-stopped + depends_on: + pg: + condition: service_healthy + environment: + DATABASE_URL: postgresql://iboard:${PG_PASSWORD}@pg:5432/iboard + PORT: "3001" + JWT_SECRET: ${JWT_SECRET:?JWT_SECRET missing} + POWERSYNC_JWT_SECRET: ${POWERSYNC_JWT_SECRET:?POWERSYNC_JWT_SECRET missing} + # 客户端拿到的同步地址 = 同域 (经由 app 的 /sync 反代) + POWERSYNC_URL: ${PUBLIC_URL:-https://iboard.honor3.com} + # app 内部转发同步请求到内网 powersync + POWERSYNC_UPSTREAM: http://powersync:8080 + OTA_PUBLIC_BASE: ${PUBLIC_URL:-https://iboard.honor3.com} + OTA_PACKAGE_DIR: /app/ota-package + MIHOME_CACHE_DIR: /app/cache + volumes: + - ./data/cache:/app/cache + # OTA 热更包 (CI 构建产物, 由流水线写入宿主 data/ota) + - ./data/ota:/app/ota-package + ports: + - "${HOST_PORT:-3001}:3001" + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3001/api/health || exit 1"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 20s + + powersync: + image: journeyapps/powersync-service:latest + restart: unless-stopped + depends_on: + pg: + condition: service_healthy + command: ["start", "-r", "unified"] + volumes: + - ./powersync/service.yaml:/config/service.yaml:ro + - ./powersync/sync-config.yaml:/config/sync-config.yaml:ro + environment: + POWERSYNC_CONFIG_PATH: /config/service.yaml + PS_SOURCE_URI: postgresql://iboard:${PG_PASSWORD}@pg:5432/iboard?sslmode=disable + PS_STORAGE_URI: postgresql://powersync_storage:${PS_STORAGE_PASSWORD}@pg:5432/iboard?sslmode=disable + PS_JWT_KEY_B64: ${POWERSYNC_JWT_KEY_B64:?POWERSYNC_JWT_KEY_B64 missing} diff --git a/deploy/prod/initdb/01-powersync-storage.sh b/deploy/prod/initdb/01-powersync-storage.sh new file mode 100644 index 0000000..6afb759 --- /dev/null +++ b/deploy/prod/initdb/01-powersync-storage.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# 生产 PG 初始化: PowerSync bucket storage 专用用户 +set -e +: "${PS_STORAGE_PASSWORD:?PS_STORAGE_PASSWORD missing}" + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + DO \$\$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'powersync_storage') THEN + CREATE USER powersync_storage WITH PASSWORD '$PS_STORAGE_PASSWORD'; + END IF; + END + \$\$; + GRANT CREATE ON DATABASE $POSTGRES_DB TO powersync_storage; +EOSQL + +echo '[initdb] powersync_storage ready.' diff --git a/deploy/prod/powersync/service.yaml b/deploy/prod/powersync/service.yaml new file mode 100644 index 0000000..757674d --- /dev/null +++ b/deploy/prod/powersync/service.yaml @@ -0,0 +1,41 @@ +# PowerSync Service 主配置 (0.2.1 验证部署) +# 参考: https://docs.powersync.com/configuration/powersync-service/self-hosted-instances +# 注意: !env 是 YAML 整值标签, 只能整个值替换变量, 不能内嵌在 URI 字符串中; +# 完整连接串由 docker-compose 以 PS_* 环境变量注入。 + +replication: + connections: + - type: postgresql + # 源库: 本栈 pg 容器 (0.2.1 独立实例), 复制走 iboard021 账号 + uri: !env PS_SOURCE_URI + sslmode: disable + +# bucket storage: 同实例 PG (16 >= 14 允许同库不同 schema, service 自建 powersync schema) +storage: + type: postgresql + uri: !env PS_STORAGE_URI + sslmode: disable + +port: 8080 + +sync_config: + path: /config/sync-config.yaml + +# 客户端鉴权: app 后端 /api/powersync/token 签发 HS256 JWT (aud=powersync) +# oct 密钥 k = base64url(POWERSYNC_JWT_SECRET) +client_auth: + jwks: + keys: + - kty: oct + k: !env PS_JWT_KEY_B64 + alg: HS256 + audience: + - powersync + +telemetry: + disable_telemetry_sharing: true + +system: + logging: + level: info + format: text diff --git a/deploy/prod/powersync/sync-config.yaml b/deploy/prod/powersync/sync-config.yaml new file mode 100644 index 0000000..041834e --- /dev/null +++ b/deploy/prod/powersync/sync-config.yaml @@ -0,0 +1,25 @@ +# Sync Streams (edition 3): 任务模块 6 表 + User (展示用) +# Sequential ID Mapping (见 docs/V0.2/离线编辑模块/0.2.1实施方案.md §3): +# - 客户端主键 = uuid 列 (离线新建行同步回填后 id 稳定); int 自增 id 不下发 +# - 外键输出 uuid 镜像列, 别名回 projectId/columnId/parentId/taskId (客户端字段名不变) +# - 引号保留 PG PascalCase 标识; 可见性按 ProjectMember 过滤 + +config: + edition: 3 + +streams: + iboard_users: + auto_subscribe: true + query: SELECT CAST("id" AS TEXT) AS "id", "name", "email" FROM "User" + + iboard_task_data: + auto_subscribe: true + with: + my_projects: SELECT "projectId" FROM "ProjectMember" WHERE CAST("userId" AS TEXT) = auth.user_id() + queries: + - SELECT "uuid" AS "id", "uuid", "code", "name", CAST("ownerId" AS TEXT) AS "ownerId", "isPersonal", "archived", "notes", "taskKinds", "icon", "autoArchive", "createdAt", "updatedAt" FROM "Project" WHERE "id" IN my_projects + - SELECT "uuid" AS "id", "uuid", "projectUuid" AS "projectId", CAST("userId" AS TEXT) AS "userId", "permission", "createdAt" FROM "ProjectMember" WHERE "projectId" IN my_projects + - SELECT "uuid" AS "id", "uuid", "projectUuid" AS "projectId", "name", "sortOrder", "boundStatus", "retention", "createdAt", "updatedAt" FROM "BoardColumn" WHERE "projectId" IN my_projects + - SELECT "uuid" AS "id", "uuid", "projectUuid" AS "projectId", "columnUuid" AS "columnId", "parentUuid" AS "parentId", "title", "description", "kind", "markDate", "status", "doneAt", CAST("assigneeId" AS TEXT) AS "assigneeId", CAST("creatorId" AS TEXT) AS "creatorId", "extras", "sortOrder", "number", "archived", "archivedAt", "archiveSource", "createdAt", "updatedAt" FROM "Task" WHERE "projectId" IN my_projects + - SELECT "uuid" AS "id", "uuid", "taskUuid" AS "taskId", CAST("userId" AS TEXT) AS "userId" FROM "TaskMember" WHERE "taskId" IN (SELECT "id" FROM "Task" WHERE "projectId" IN my_projects) + - SELECT "uuid" AS "id", "uuid", "taskUuid" AS "taskId", "name" FROM "TaskTag" WHERE "taskId" IN (SELECT "id" FROM "Task" WHERE "projectId" IN my_projects) diff --git a/deploy/prod/sql/publication.sql b/deploy/prod/sql/publication.sql new file mode 100644 index 0000000..dcf3127 --- /dev/null +++ b/deploy/prod/sql/publication.sql @@ -0,0 +1,11 @@ +-- 生产 PG: PowerSync 逻辑复制 publication +-- 必须在 prisma migrate deploy 建表之后执行 (initdb 阶段表还不存在, 故不放 initdb 目录)。 +-- 幂等: 已存在则跳过。 +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_publication WHERE pubname = 'powersync') THEN + CREATE PUBLICATION powersync FOR TABLE + "Project", "ProjectMember", "BoardColumn", "Task", "TaskMember", "TaskTag", "User"; + END IF; +END +$$; diff --git a/deploy/prod/sql/uuid-triggers.sql b/deploy/prod/sql/uuid-triggers.sql new file mode 100644 index 0000000..79b3fbe --- /dev/null +++ b/deploy/prod/sql/uuid-triggers.sql @@ -0,0 +1,82 @@ +-- iBoard 0.2.1: 外键 uuid 镜像列触发器 +-- 由 0.2.1 验证库导出; 服务端写入时自动维护 projectUuid/columnUuid/parentUuid/taskUuid, +-- 同步流据此把镜像列以 projectId/columnId/parentId/taskId 的别名下发给客户端。 + +CREATE OR REPLACE FUNCTION public.func_boardcolumn_uuid_map() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +BEGIN + SELECT "uuid" INTO NEW."projectUuid" FROM "Project" WHERE "id" = NEW."projectId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Project % 不存在', NEW."projectId"; END IF; + RETURN NEW; +END; +$function$ + +; + +CREATE OR REPLACE FUNCTION public.func_projectmember_uuid_map() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +BEGIN + SELECT "uuid" INTO NEW."projectUuid" FROM "Project" WHERE "id" = NEW."projectId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Project % 不存在', NEW."projectId"; END IF; + RETURN NEW; +END; +$function$ + +; + +CREATE OR REPLACE FUNCTION public.func_task_uuid_map() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +BEGIN + SELECT "uuid" INTO NEW."projectUuid" FROM "Project" WHERE "id" = NEW."projectId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Project % 不存在', NEW."projectId"; END IF; + SELECT "uuid" INTO NEW."columnUuid" FROM "BoardColumn" WHERE "id" = NEW."columnId"; + IF NOT FOUND THEN RAISE EXCEPTION 'BoardColumn % 不存在', NEW."columnId"; END IF; + IF NEW."parentId" IS NULL THEN + NEW."parentUuid" := NULL; + ELSE + SELECT "uuid" INTO NEW."parentUuid" FROM "Task" WHERE "id" = NEW."parentId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Task % 不存在', NEW."parentId"; END IF; + END IF; + RETURN NEW; +END; +$function$ + +; + +CREATE OR REPLACE FUNCTION public.func_taskmember_uuid_map() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +BEGIN + SELECT "uuid" INTO NEW."taskUuid" FROM "Task" WHERE "id" = NEW."taskId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Task % 不存在', NEW."taskId"; END IF; + RETURN NEW; +END; +$function$ + +; + +CREATE OR REPLACE FUNCTION public.func_tasktag_uuid_map() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +BEGIN + SELECT "uuid" INTO NEW."taskUuid" FROM "Task" WHERE "id" = NEW."taskId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Task % 不存在', NEW."taskId"; END IF; + RETURN NEW; +END; +$function$ + +; + +CREATE TRIGGER trg_boardcolumn_uuid_map BEFORE INSERT OR UPDATE ON public."BoardColumn" FOR EACH ROW EXECUTE FUNCTION func_boardcolumn_uuid_map(); +CREATE TRIGGER trg_projectmember_uuid_map BEFORE INSERT OR UPDATE ON public."ProjectMember" FOR EACH ROW EXECUTE FUNCTION func_projectmember_uuid_map(); +CREATE TRIGGER trg_task_uuid_map BEFORE INSERT OR UPDATE ON public."Task" FOR EACH ROW EXECUTE FUNCTION func_task_uuid_map(); +CREATE TRIGGER trg_taskmember_uuid_map BEFORE INSERT OR UPDATE ON public."TaskMember" FOR EACH ROW EXECUTE FUNCTION func_taskmember_uuid_map(); +CREATE TRIGGER trg_tasktag_uuid_map BEFORE INSERT OR UPDATE ON public."TaskTag" FOR EACH ROW EXECUTE FUNCTION func_tasktag_uuid_map(); diff --git a/docs/021任务.md b/docs/021任务.md new file mode 100644 index 0000000..d35f0d6 --- /dev/null +++ b/docs/021任务.md @@ -0,0 +1,379 @@ +# 0.2.1 发布任务 — 目标与现状 + +> 最后更新:2026-10-27 08:20 +> 分支:`dev-0.2` 与 `master` 均已发布(`a1c267b`) +> 状态:**全部完成** —— 生产已切 0.2.1 单端口架构,master 发布 CI 通过,Android APK 与 Windows 安装包均已产出 + +## 零、执行结果速览(2026-10-27 已完成) + +| 项目 | 结果 | +| --- | --- | +| 生产地址 | `https://iboard.honor3.com` → baihu **3001**(NPM + 有效证书) | +| 生产架构 | `iboard-prod-app-1`(3001 对外) + `iboard-prod-pg-1`(内网) + `iboard-prod-powersync-1`(内网) | +| 端口 | **只剩 3001**;13001 / 13002 / 13443 全部下线 | +| 数据迁移 | MySQL → PG 完成:Project 3、Task 25、BoardColumn 12、User 1、CheckIn 17、News 19、IndicatorPoint 245、Mihome 等全部迁入 | +| 旧生产 | 容器 `iboard` 已 **stop(未删除)**,`docker start iboard` 可秒级回滚 | +| 登录态 | `JWT_SECRET` 沿用 `iboard`,现有用户无需重新登录 | +| 米家凭证 | `/app/iboard/cache` → `deploy/prod/data/cache`(`auth.txt` + `apizero/`) | +| E2E 验证 | 在线:项目卡片 + `ADMIN-16`/`ADMIN-18` 编号;断网:徽标出现、任务页数据完整、无 fail fetch;恢复:徽标消失 | +| master 发布 | 已推 `master`(`a1c267b`);CI `build-image` **10/10 步成功**(版本校验 / 前端 / OTA 签名包 / 镜像 / 部署步按设计跳过) | +| Android | `android-1.0` CI **16/16 步成功**,APK 20MB(versionName 0.2.1)已发布到 Gitea packages | +| Windows | 本地 `npm run build:win` 成功:NSIS 6.9MB + 便携 zip 7.9MB | +| OTA 热更 | **已打通**:`https://iboard.honor3.com/api/ota/check/0` 返回 `0.2.1-ota`、`min_binary_version: 0.2.1`、签名与下载地址齐全 | + +### 迁移过程中踩到并修掉的坑 + +1. `prisma/migrations/` 是 **MySQL 语法**(反引号),PG 无法 `migrate deploy` → 改用 `prisma db push --accept-data-loss` 按 schema 建表。 +2. `db push` 首次因参数不被识别而输出帮助(表结构停在旧 schema,缺 `autoArchive`)→ 去掉错误参数重跑,列已补齐。 +3. 新库缺 **uuid 镜像触发器**(`projectUuid`/`columnUuid`/`parentUuid`/`taskUuid`)→ 从 0.2.1 验证库导出 5 个函数 + 5 个触发器,落库并入库 `deploy/prod/sql/uuid-triggers.sql`。 +4. bash 把 `$$` 展开成 PID 导致 TRUNCATE 脚本报错 → 改用 SQL 文件传入。 +5. baihu 上 `docker build` 缺 buildx → 改用 `docker compose build`。 +6. **OTA 包此前完全没有**:手工构建的镜像内 `/app/ota-package` 只有 README(`ota-package/` 被 `.gitignore` 排除,CI 部署段又跳过),线上 OTA 接口返回空 → 已给 app 容器加 `data/ota` 挂载,并新增 CI 步骤用 `docker cp` 经 bind-mount 容器写入宿主(CI 在容器内无法直接写宿主文件)。 +7. CI 首次写入路径多写了一层 `data/ota`(与 compose 的 `deploy/prod/data/ota` 不一致)→ 路径已对齐。 +8. `OTA_MIN_BINARY` 由 `0.1.0` 提升到 **`0.2.1`**,防止旧外壳拉到新域名前端后被 CSP 拦截白屏。 + +--- + +## 一、任务目标(用户原始要求) + +1. 域名 `iboard.honor3.com` 已指向 3000 端口、HTTPS 证书已配好 → 客户端全面切到该域名。 +2. **Windows 外壳 + 离线编辑功能正式合并到 `dev-0.2`**,完整版本号 **0.2.1**。 +3. 确保 **Android** 功能正常(通过 CI 编译验证,不在本地装 Android 工具链)。 +4. 确保 **CI/CD** 正常。 +5. Windows **本地打包正常**即可。 +6. `dev-0.2` 更新完成后 **通过 `master` 分支发布**。 +7. 追加要求:**不要再有 13002 这类独立端口**,所有版本合并成一套。 + +--- + +## 二、已完成 + +### 2.1 分支合并 + +| 内容 | 结果 | +| --- | --- | +| `dev-0.2.1`(离线编辑 + 项目编号 + PG/PowerSync)→ `dev-0.2` | 已合并 `e689e44` | +| `dev-windows-0.1`(Windows 外壳:托盘/悬浮球/吸附/自启动)→ `dev-0.2` | 已按路径迁入 `eef2969` | +| `dev-0.2` → `android-1.0`(触发 APK 构建) | 已合并 `4958359` | +| 冲突处理 | `projects.js`/`tasks.js`/`api/task.js` 手工合并,**两边功能都保留**(`autoArchive` 归档 + `code` 编号) | +| 归档功能补齐到本地库 | `src/api/task.js` 新增 `archive`/`unarchive`/`archivedList`;`schema.js` 加 `archived/archivedAt/archiveSource/autoArchive`;`sync-config.yaml` 与 upload 处理器同步 | +| Prisma 客户端补齐 | dev-0.2 原来缺任务模块 6 个模型,已重新 generate `fc23919` | + +### 2.2 版本与域名 + +- 版本统一 **0.2.1**:`package.json` / `src-tauri/tauri.conf.json` / `Cargo.toml` / `Cargo.lock`(`scripts/ci/check-version.mjs` 通过) +- 域名切 `https://iboard.honor3.com`: + - `src/api/client.js` 注释、`.env.shell`、`scripts/wsl/build-apk.sh`、`.gitea/workflows/android-build.yml`、`.gitea/workflows/docker-build.yml`、`Dockerfile`(`OTA_PUBLIC_BASE`)、`tauri.conf.json`(OTA endpoint + CSP) +- 旧域名 `bh.vps.honor3.com:3001` 仅保留在 CSP 白名单里做滚动兼容 + +### 2.3 单端口架构(去掉 13002) + +| 改动 | 位置 | +| --- | --- | +| Express 增加 `/sync`、`/socket` 同域反代到内网 powersync | `server/powersync-proxy.js` + `server/index.js`(挂在 `express.json()` 之前,支持长连接) | +| 生产编排:app(3001 对外) + pg(内网) + powersync(内网) | `deploy/prod/docker-compose.yml` | +| 生产部署脚本(幂等:迁移 → publication → 起栈 → 健康检查) | `scripts/deploy/prod.sh` | +| PG 初始化(powersync_storage 用户) | `deploy/prod/initdb/01-powersync-storage.sh` | +| 逻辑复制 publication(**建表后**执行) | `deploy/prod/sql/publication.sql` | +| 部署文档 | `deploy/prod/README.md`、`.env.example` | +| CI 部署段改为调用宿主脚本,未配 PG secrets 时自动跳过 | `.gitea/workflows/docker-build.yml` | + +### 2.4 Windows 本地打包 — 通过 ✅ + +`npm run build:win` 全流程成功(本机需 Node 20.19+;已用便携 Node 20.18.1 之外的 Node 24 验证过 vite 构建): + +| 产物 | 路径 | 大小 | +| --- | --- | --- | +| 安装包 | `artifacts/windows/iBoard_0.2.1_x64-setup.exe` | 6.9 MB | +| 便携版 zip | `artifacts/windows/iBoard_0.2.1_x64-portable.zip` | 7.9 MB | + +外壳能力(继承 dev-windows-0.1):托盘、悬浮球吸附、自动隐藏、单实例、自启动、窗口尺寸单值配置 +(`%APPDATA%\com.iboard.app\shell-config.json` 的 `window_width`,高度按 405:720 锁定推导)。 + +离线能力(继承 0.2.1):GET 响应本地缓存、同步凭证持久化(离线不清库)、左上角「离线」徽标。 + +### 2.5 Android CI — 通过 ✅ + +- 触发:`android-1.0` 分支 push(`4ded8e3`) +- 结果:**16 个步骤全部 success**,`Build release APK` + `Publish APK to Gitea packages` 成功 +- 产物:`app-universal-release.apk`(20 MB),versionName = **0.2.1** + 已发布到 `http://git.honor3.com/api/packages/Fullstack/generic/iboard/1.0-4ded8e30/` +- 首次失败原因:runner 宿主机(baihu)磁盘 **95%** → `No space left on device`; + 清理悬空镜像 + 构建缓存后释放 11.5 GB,重跑通过。**磁盘现已 60%(39 GB 可用)** + +### 2.6 CI/CD 现状 + +| Workflow | 触发 | 状态 | +| --- | --- | --- | +| `docker-build.yml` | `master` push | 改造完成,未在 master 上验证(部署段会因缺 secrets 跳过) | +| `android-build.yml` | `android-*` push | **已验证通过** | +| `ota-build.yml` / `sync-registry.yml` | 参照原逻辑 | 未改动 | + +--- + +## 三、当前卡点(生产切换未执行) + +生产镜像 **已构建成功**:`iboard:latest` = `db6b27b851b8`(924 MB,2026-10-26 22:53) + +baihu 上当前跑着两套: + +| 容器 | 用途 | 端口 | +| --- | --- | --- | +| `iboard`(旧) | **现网生产**,MySQL(宿主 3306) | 3001 | +| `iboard-021-app-1` 等 4 个 | 0.2.1 验证栈 | 13001 / 13002 / 13443 | +| (未启动)`iboard-prod` | **新生产栈**(PG + PowerSync + app) | 3001(计划) | + +`/app/iboard-prod` 已上传源码、`deploy/prod/.env` 已生成(6 个密钥,600 权限)。 + +**剩余步骤(约 4 步)**: + +1. 起新栈(先用 `HOST_PORT=13011` 灰度,不动现网) +2. 数据迁移:现网 MySQL → 新 PG(`scripts/etl-mariadb-to-pg.mjs`,只读源库) +3. 验证:健康检查、登录、任务列表、离线编辑(断网后仍可读)、同步回流 +4. 切换:停 `iboard` 旧容器 → 新栈改绑 3001 → 域名 `iboard.honor3.com` 生效 → 下线验证栈(13001/13002/13443) + +然后 `dev-0.2` → `master` 合并推送,触发 CI 发布。 + +--- + +## 四、需要确认的信息(阻塞项) + +1. **Gitea Actions Secrets 是否要配 PG 密钥?** + 需配:`PG_PASSWORD`、`PS_STORAGE_PASSWORD`、`JWT_SECRET`、`POWERSYNC_JWT_SECRET`、`POWERSYNC_JWT_KEY_B64`。 + 不配的话:master 的 CI 只构建镜像不部署,生产靠手工执行 `scripts/deploy/prod.sh`(当前默认路径)。 + +2. **生产数据迁移的时间窗口**:迁移需短暂停写(现网 MySQL 只读快照)。是否可以挑一个低峰时间、期间 (~10 分钟) 站点只读? + +3. **现网 MySQL 里的米家凭证/缓存**:`/app/iboard/cache`(`auth.txt` 等)是否要一并搬到新栈的 `deploy/prod/data/cache`? + +4. **用户密码**:迁移只搬数据,`JWT_SECRET` 若换新,所有人需重新登录;是否沿用现网 `JWT_SECRET`(`iboard`)以保持登录态?**建议换新**(当前值过弱)。 + +5. **域名端口说明**:`iboard.honor3.com` 实际反代到 baihu **3001**(NPM 配置:`114.134.187.170:3001`,SSL 强制)。 + 你提到"已转向 3000 端口"——请确认指的是 `honor3.com` 根域(→ homepage)而非 `iboard` 子域, + 否则需要调整 NPM 的 forward_port。 + +--- + +## 五、关键路径与命令速查 + +```bash +# 仓库 +C:\Users\Rabbit\Documents\workbetch\iboard # dev-0.2 主工作区 +C:\Users\Rabbit\Documents\workbetch\iboard-dev-0.2.1 # 研究分支 worktree +C:\Users\Rabbit\Documents\workbetch\iboard-dev-windows # Windows 外壳 worktree + +# 服务器 +ssh root@baihu # 生产 + CI runner(baihu 上跑 act_runner,注册名 baishe) +ssh root@baishe # 反代(NPM)、Gitea、homepage + +# 生产目录 +/app/iboard-prod/deploy/prod/{docker-compose.yml,.env,data/,powersync/,sql/,initdb/} + +# 常用 +docker compose -f /app/iboard-prod/deploy/prod/docker-compose.yml --env-file /app/iboard-prod/deploy/prod/.env ps +bash /app/iboard-prod/scripts/deploy/prod.sh +curl -s https://iboard.honor3.com/api/health +``` + +## 五之二、后续动作执行步骤(已确认方案,按序执行) + +### 决策确认(2026-10-27) + +| 项 | 决定 | +| --- | --- | +| PG 密钥 | **不进 Gitea Secrets**,直接写在服务器 `/app/iboard-prod/deploy/prod/.env`(600) | +| 迁移时机 | 立即执行 | +| 米家凭证 | 保留,`/app/iboard/cache` 搬到新栈 `deploy/prod/data/cache` | +| `JWT_SECRET` | 沿用现网值(`iboard`),保持现有登录态 | +| 域名 | `iboard.honor3.com` → baihu **3001**(NPM 已配好,SSL 强制) | + +### 步骤 A:准备(不改动现网) + +```bash +# A1. 沿用现网 JWT_SECRET (其余密钥保持已生成的随机值) +cd /app/iboard-prod/deploy/prod +sed -i 's/^JWT_SECRET=.*/JWT_SECRET=iboard/' .env + +# A2. 搬米家缓存 (auth.txt 等) +mkdir -p data/cache +rsync -a /app/iboard/cache/ data/cache/ + +# A3. 保留旧镜像用于回滚 +docker tag iboard:latest iboard:mysql-final # 需在重新构建前执行; 若已覆盖见"回滚预案" +``` + +### 步骤 B:起灰度栈(不动现网 3001) + +```bash +cd /app/iboard-prod/deploy/prod +HOST_PORT=13011 docker compose --project-name iboard-prod \ + --env-file .env -f docker-compose.yml up -d pg + +# 迁移建表 +docker run --rm --network iboard-prod_default \ + -e DATABASE_URL="postgresql://iboard:$(grep ^PG_PASSWORD .env | cut -d= -f2)@pg:5432/iboard" \ + iboard:latest npx prisma migrate deploy + +# 建 publication (必须在建表之后) +docker compose --project-name iboard-prod --env-file .env -f docker-compose.yml exec -T pg \ + psql -U iboard -d iboard -v ON_ERROR_STOP=1 -f /dev/stdin < sql/publication.sql + +# 起 app + powersync (只有 13011 对外) +HOST_PORT=13011 docker compose --project-name iboard-prod \ + --env-file .env -f docker-compose.yml up -d app powersync + +# 验收 +curl -s http://127.0.0.1:13011/api/health # {"ok":true,...} +ss -tlnp | grep -E ':(13011|5432|8080)\b' # 只应看到 13011 +``` + +### 步骤 C:数据迁移(MySQL → PG) + +```bash +docker run --rm --network iboard-prod_default \ + -e DATABASE_URL="postgresql://iboard:@pg:5432/iboard" \ + -e SRC_DATABASE_URL="mysql://root:@:3306/iboard" \ + iboard:latest node scripts/etl-mariadb-to-pg.mjs + +# 迁移后重启同步服务重建快照 +docker compose --project-name iboard-prod --env-file .env -f docker-compose.yml restart powersync +``` + +**验收**:项目 3 个(ADMIN / IBOARD / P3F921)、任务 ~22 条、用户 1 个(admin@iboard.local); +`docker compose logs powersync` 显示 replication 已激活且无报错。 + +### 步骤 D:功能验证(13011) + +- 登录 `admin@iboard.local` +- 任务页:项目卡片、任务编号(如 `ADMIN-16`) +- **离线验证**:断网后列表仍可读、左上角出现「离线」徽标,恢复网络自动同步回流 +- 监控 / 宏观 / 打卡页有数据 + +### 步骤 E:切换生产 + +```bash +# E1. 停旧容器 (MySQL 版) +docker rm -vf iboard || true + +# E2. 新栈改绑 3001 +cd /app/iboard-prod/deploy/prod +HOST_PORT=3001 docker compose --project-name iboard-prod \ + --env-file .env -f docker-compose.yml up -d app +curl -s https://iboard.honor3.com/api/health # 经 NPM + 证书 + +# E3. 下线 0.2.1 验证栈 (释放 13001/13002/13443) +cd /app/iboard-0.2.1/deploy/0.2.1 && docker compose down +``` + +**验收**:`https://iboard.honor3.com` 可登录、任务正常、离线可编辑; +`ss -tlnp` 只剩 3001(外加仅供本机的 3306)。 + +### 步骤 F:发布 + +```bash +git checkout master && git merge dev-0.2 --no-edit +git push origin master # 触发 CI: 构建镜像 + OTA +``` + +CI 未配 PG secrets → 只构建不部署;生产已由步骤 B~E 手工就绪。 + +### 回滚预案 + +| 场景 | 动作 | +| --- | --- | +| 新栈起不来 | 旧容器未停,现网不受影响,回步骤 B 排查 | +| 迁移数据不对 | 清空 `deploy/prod/data/pg` 后重跑 ETL | +| 切换后严重故障 | **`docker start iboard`**(旧容器仍在,见 6.2 警告:旧镜像已被 prune,不能重建) | + +## 六、遗留清单与清理建议(2026-10-27 实测) + +### 6.1 本机(Windows) + +| 路径 | 占用 | 建议 | 说明 | +| --- | --- | --- | --- | +| `workbetch/iboard`(主 worktree) | 3.81 GB | **保留** | 含 `node_modules` 0.49 GB + `src-tauri/target` 2.56 GB | +| `workbetch/iboard-dev-0.2.1` | 0.53 GB | **可删** | 内容已全部并入 `dev-0.2` | +| `workbetch/iboard-dev-windows` | 8.88 GB | **可删** | 同上,最大占用(含 Rust 编译产物) | +| `iboard/src-tauri/target` | 2.56 GB | 可选 `cargo clean` | 删后下次打包需重编译(约 8 分钟) | +| `iboard/artifacts/windows` | 32 MB | 保留最新安装包 | 0.2.1 exe/zip | +| `%TEMP%\node-portable` | 78 MB | **保留** | 本机无系统 Node/npm,`npm run build:win` 依赖它 | +| `%TEMP%\esbuild-*`(4 个) | 8 MB | 可删 | 构建残留 | +| `%TEMP%\iboard-e2e`、`iboard-shell-offline` | < 1 MB | 可删 | 验证截图 | +| `%TEMP%\edge-e2e-profile` | ~100 MB | 可删 | 无头浏览器测试配置 | + +```bash +# 删除 worktree 与已合并分支(内容已在 dev-0.2) +git worktree remove ../iboard-dev-0.2.1 +git worktree remove ../iboard-dev-windows +git branch -d dev-0.2.1 dev-windows-0.1 +git branch -D tmp-offline-edit +``` + +### 6.2 服务器 baihu + +| 路径/对象 | 占用 | 建议 | 说明 | +| --- | --- | --- | --- | +| `/app/iboard-prod` | 72 MB | **保留** | 当前生产 | +| `/app/iboard` | 24 KB | 暂留 → 可删 | 旧生产只剩 `cache/`(`auth.txt` 已复制到新栈)+ `start.sh` | +| `/app/iboard-0.2.1` | 71 MB | **可删** | 验证栈已 down;其中 `deploy/0.2.1/data` 67 MB 是废弃 PG 数据 | +| `/app/cicd` | 36 KB | **保留** | act_runner 配置 | +| 容器 `iboard`(Exited) | — | **暂留** | 当前唯一回滚手段(见下方警告) | +| 镜像 `iboard-021-app:latest` | 924 MB | **可删** | 验证栈镜像 | +| 镜像 `iboard:latest` | 934 MB | 保留 | CI 构建产物 | +| 悬空镜像 + 构建缓存 | 18.7 GB + 2.4 GB | 可清 | `docker image prune -f && docker builder prune -af` | + +> **⚠ 回滚能力警告(实测更正)** +> 原计划保留的 `iboard:mysql-final` 旧镜像**已被 `docker image prune` 清除** —— 旧镜像失去 tag 后成为悬空镜像被回收。 +> 现在唯一回滚途径是重启仍存在的旧容器 `iboard`(镜像层随容器保留): +> +> ```bash +> docker stop iboard-prod-app-1 +> docker start iboard # 旧 MySQL 版立即恢复 3001 +> ``` +> +> **在再次执行 `docker image prune` 前,务必先确认旧容器仍在**;容器一旦删除,MySQL 版镜像只能从 git 历史重新构建。 + +### 6.3 代码与其他 + +- 临时测试脚本(`scripts/_prod-e2e.mjs`、`_browser-test.mjs`、`_shell-offline-test.mjs`)含真实口令,已加入 `.gitignore`(`scripts/_*.mjs`),**不要入库** +- `0.2.1` 验证库里的测试任务(如 `浏览器验证任务`)已按 MySQL 现状一并迁入 PG,如需清理直接在 PG 删除 +- `prisma/migrations/` 仍是 MySQL 语法,**新环境建表必须用 `prisma db push --accept-data-loss`**(已写入 `deploy/prod/README.md`) +- `JWT_SECRET` 仍为弱值(`iboard`),建议择机换新(换新需全员重新登录) +- `node_modules` 在多个 Node 版本间切换过,遇 esbuild 权限报错时清空重装 + +## 七、外壳升级策略:必须整包升级,不能只靠 OTA + +### 结论 + +| 平台 | 结论 | 关键原因 | +| --- | --- | --- | +| Windows 外壳(0.1.0) | **必须重装 exe** | ① CSP 白名单编译进原生二进制,0.1.0 不含 `https://iboard.honor3.com`;② 旧外壳 OTA 检查地址指向 `13001`,**该端口已下线**,连检查都失败 | +| Android(0.1.0 APK) | **必须换装 APK** | CSP 同样是原生配置;旧 APK 的 OTA 端点(3001)虽然有效,但拉到 0.2.1 前端会被 CSP 拦截 → 白屏 | + +### 为什么 OTA 不够 + +`tauri-plugin-hotswap` 的 OTA 包内容只有 `bundle.tar.gz`(即 `dist/` 前端资源),**不触碰原生层**: + +| 配置项 | 位置 | OTA 能否修改 | +| --- | --- | --- | +| CSP 白名单(`connect-src`) | `tauri.conf.json` → 编译期 `generate_context!` 注入二进制 | ❌ | +| OTA 检查 endpoint | 同上 | ❌ | +| 窗口尺寸/最小尺寸 | 同上(另可被 `shell-config.json` 覆盖) | ❌ | +| 托盘 / 悬浮球 / 自启动 | Rust `src-tauri/src/shell/*` | ❌ | +| 页面 JS/CSS/WASM(含离线编辑、徽标、编号展示) | `dist/` | ✅ | + +因此旧外壳无法通过 OTA 获得访问新域名的能力。 + +### 已采取的保护措施 + +CI 的 `OTA_MIN_BINARY` 已从 `0.1.0` 提升到 **`0.2.1`**: + +- 旧外壳(0.1.0 < 0.2.1)会被 hotswap 判定为不兼容 → **拒绝接收新前端** +- 旧外壳继续运行旧前端,仍访问 `http://bh.vps.honor3.com:3001`(该地址现指向新栈,`/api` 完全兼容) +- 结果:**旧外壳不会白屏**,只是缺少离线编辑、项目编号等 0.2.1 新能力 + +### 升级后 + +- 新外壳版本 0.2.1,CSP 含 `https://iboard.honor3.com`,OTA 恢复正常(后续纯前端改动可继续走 OTA) +- Windows 窗口尺寸通过 `%APPDATA%\com.iboard.app\shell-config.json` 的 `window_width` 单值配置 + diff --git a/docs/V0.2/任务模块/任务模块功能设计.md b/docs/V0.2/任务模块/任务模块功能设计.md index 79f2cd8..5daf786 100644 --- a/docs/V0.2/任务模块/任务模块功能设计.md +++ b/docs/V0.2/任务模块/任务模块功能设计.md @@ -360,7 +360,7 @@ | --- | --- | | 任务 `⋯` 菜单 | 新增「归档」(`Dialog.confirm`,含子任务时提示连带条数);成功后列表/看板乐观移除 | | 项目管理 | 「归档设置」分区:`Selector` 四档(3 天后/一周后/一月后/永不),保存即生效 | -| 项目视图头部 | 项目栏新增「已归档」入口 → 全屏弹层:过滤条(关键词/类型/列/归档时段)+ 分页「加载更多」+ 行内「恢复 / 删除」 | +| 项目视图头部 | 项目栏新增「已归档」入口 → 全屏弹层:过滤条(**关键词 / 看板列 / 归档时段**;时段含「本周 / 本月 / 本季度」快捷项与自定义区间;条件编辑后点「筛选」统一提交,另有「重置」;**不提供类型过滤**)+ 已应用条件标签(可单独撤销)与命中条数 + 分页「加载更多」+ 行内「恢复 / 删除」 | | 主视图 | 列表/看板无需感知归档(数据源已排除);恢复后经 `load()` 回到原位 | ### 15.5 边界 diff --git a/docs/V0.2/任务模块/已归档任务管理-原型.html b/docs/V0.2/任务模块/已归档任务管理-原型.html new file mode 100644 index 0000000..efa103f --- /dev/null +++ b/docs/V0.2/任务模块/已归档任务管理-原型.html @@ -0,0 +1,627 @@ + + + + + +已归档任务管理 · 界面重设计(原型待评审) + + + +
+ +
+

已归档任务管理 · 界面重设计

+

对应需求 T-ARC-04/05/06。本文件是评审用原型,不含业务代码改动;确认后再落地。

+
+ 原型 · 待评审 + 2026-10-28 + 可交互:点预设 / 筛选 / 恢复 + 范围:任务模块 › 项目 › 已归档 +
+
+ +

改动要点 对照你提出的 4 点

+
+
+
01
+

去掉「类型」过滤

+

现版本的「全部类型」下拉整体移除。任务类型是弱维度,归档场景几乎不按类型找东西,反而占掉一横行高。

+

全部类型 ▾

+
+
+
02
+

输入框前加提示

+

关键词输入框前加固定标签「标题」,输入框内用灰字占位「输入关键词」——标签常驻,占位随输入消失,不会出现"不知道搜什么"。

+

搜索任务标题(无标签)

+
+
+
03
+

归档时间快捷选择

+

新增 全部 / 本周 / 本月 / 本季度 四个一键预设,选完自动算出起止日期并显示;需要精确区间时再选「自定义」。

+

开始日期 ~ 结束日期(纯手点)

+
+
+
04
+

补上「筛选」按钮

+

现版本改任何条件都立即查询、没有确认动作。改为:改条件 → 点筛选才生效,按钮上显示生效条件数;另给「重置」。

+

无按钮(自动即时查询)

+
+
+ +

主界面 可交互 · 点一下试试

+
+ +
+
+ +
+ +
+
+ 标题 + + + + + + +
+ +
+ 看板列 + + + 全部 + Plan + Doing + Done + + +
+ +
+ 归档时间 + + + 全部 + 本周 + 本月 + 本季度 + 自定义 + + +
+
+
+ +
+ + +
+
+ + + + + +
+ +
+
+
+
+ 主界面 + 筛选卡固定三行:标题 / 看板列 / 归档时间,外加操作行。改动不再即时生效,点「筛选」统一提交。 +
+
+ +
+
+

1为什么改成"点按钮才筛选"

+
    +
  • 现在每改一个条件就重查一次,连点三个条件就发三次请求
  • +
  • 输入关键词时每敲一个字都可能触发(失焦也查),列表会闪
  • +
  • 改成显式提交后,用户能先把条件填完整,再一次性看结果
  • +
  • 按钮上带数字徽标,一眼知道当前生效了几个条件
  • +
+
+
+

2快捷时间怎么算

+
    +
  • 本周:周一 00:00 ~ 今天(不跨到未来)
  • +
  • 本月:1 号 00:00 ~ 今天
  • +
  • 本季度:当季首月 1 号 00:00 ~ 今天
  • +
  • 选完在下方灰字直接显示实际区间,避免"以为选的是整周"
  • +
  • 「自定义」才展开两个日期选择器,默认收起
  • +
+
+
+

3已应用条件可单独撤销

+
    +
  • 筛选后在列表上方显示条件标签,每个带 × 可单独去掉
  • +
  • 右侧显示命中条数,避免"筛完不知道有没有筛到"
  • +
  • 无结果时给明确空状态,而不是一片留白
  • +
+
+
+

4列表行重排

+
    +
  • 标题独占一行(原来和类型/列/时间挤在下面)
  • +
  • 元信息收成一行:#编号 · 看板列 · 归档于 日期
  • +
  • 「恢复 / 删除」从右侧竖排改到底部右侧横排,减少每行高度
  • +
  • 类型标签保留展示(只是不再作为筛选项)
  • +
+
+
+
+ +

状态变体

+
+
+
+ +
+
+
标题
+
看板列全部PlanDone
+
归档时间 + 全部本周本月本季度自定义 +
2026-10-26 ~ 2026-10-28
+
+
+
+
+ 本周× + Done× + 共 3 条 +
+
+
+
任务详情页滚动优化
#22
+
优化Done归档于 10-27
+
恢复删除
+
+
+
桌面端。
#5
+
需求Done归档于 10-26
+
恢复删除
+
+
+
+
+
已应用条件 + 命中条数条件可逐个撤销;时间预设自动显示实际区间。
+
+ +
+
+ +
+
+
标题✕
+
看板列全部Done
+
归档时间 + 全部本周本月本季度自定义 + 2026-09-01~2026-09-30 +
+
+
+
+ 标题: 日报× + 09-01~09-30× + 共 0 条 +
+
🗂
没有符合条件的归档任务
试试放宽时间范围或清空关键词
+
+
+
自定义区间 + 空结果选「自定义」才展开日期;空结果给可操作的建议文案。
+
+ +
+
+ +
+
+
标题
+
看板列全部PlanDoingDone
+
归档时间全部本周本月本季度自定义
+
+
+
📭
这个项目还没有归档任务
任务完成后可在 ⋯ 菜单里归档
+
+
+
初始空状态一个都没归档时,告诉用户归档入口在哪。
+
+
+ +

与现版本对照

+ + + + + + + + + + + + + +
项现在建议
类型过滤「全部类型」下拉 + 独占一行移除
关键词无标签,占位「搜索任务标题」左侧常驻标签「标题」+ 占位「输入关键词」
归档时间两个日期按钮,无快捷项全部 / 本周 / 本月 / 本季度 / 自定义
查询触发改条件即时查询,无按钮「重置 / 筛选」,按钮带生效条件数
条件可见性条件散在控件里,看不出生效了什么列表上方条件标签 + 命中条数,可单独撤销
列表行标题与元信息挤在一起,右侧竖排两按钮标题独占行;元信息一行;按钮移到底部右侧
空状态一行灰字「没有符合条件的归档任务」区分「还没归档过」与「筛选无结果」,给出下一步建议
+ +

待你确认 确认后我再改代码

+
+
+
    +
  • 「筛选」改为显式提交,是否符合你的使用习惯?(也可以保留"改预设即时生效、只有关键词需要点按钮")
  • +
  • 看板列是否也保留?还是只留 标题 + 归档时间 两个条件更清爽?
  • +
  • 列表行的「恢复 / 删除」放底部右侧可以吗?还是希望点行进入详情/预览?
  • +
  • 快捷时间是否需要再加「近 7 天 / 近 30 天」?
  • +
+
+
+ +
+ + + + diff --git a/docs/V0.2/离线编辑模块/即时同步方案.md b/docs/V0.2/离线编辑模块/即时同步方案.md new file mode 100644 index 0000000..d2dd9bf --- /dev/null +++ b/docs/V0.2/离线编辑模块/即时同步方案.md @@ -0,0 +1,312 @@ +# 任务模块「即时同步」方案 + +> 状态:**方案设计,未实施**(本文只描述做法,不含代码改动) +> 范围:任务模块(Project / BoardColumn / Task / TaskMember / TaskTag) +> 相关文档:[0.2.1 实施方案](./0.2.1实施方案.md)、[PowerSync 可行性研究报告](./PowerSync可行性研究报告.md) +> 编写时间:2026-10-27 + +--- + +## 1. 问题与现状 + +### 1.1 现象 + +多端(Windows 外壳 / Android / Web)同时打开同一项目时: + +- A 端点完成或改状态,**B 端不会自动更新**; +- 必须**切到别的页面再切回来**(组件重新挂载触发重新查询)才能看到; +- 看板分组(按 `columnId`)与详情状态(按 `status`)也会出现一段时间的显示不一致。 + +### 1.2 根因:不是"同步没到",而是"到了没人通知界面" + +PowerSync 的下行链路本身是**推送式**的(服务端 PG 逻辑复制 → sync service → WebSocket → +客户端写入本地 SQLite),延迟通常在亚秒级。问题出在**消费端**: + +| 层 | 现状 | 结论 | +| --- | --- | --- | +| 传输层 | `PowerSyncProvider` 已在 `src/App.jsx` 挂载,连接正常 | ✅ 正常 | +| 数据层 | 变化已写入本地 SQLite | ✅ 正常 | +| **UI 层** | **全部是命令式读取**:`db().getAll(...)`,读完就不再看 | ❌ **断点在这** | + +具体地: + +- `src/api/task.js` 的 `projectApi.list/detail`、`taskApi.list` 都是一次性 `getAll()`, + 且在 JS 里做二次装配(子任务、成员、标签、列),**没有任何订阅**; +- `src/lib/powersync/react.jsx` 虽然 re-export 了 `useQuery/useStatus`, + 但**全项目一次都没用过**(`grep useQuery src` 只命中该文件的导出语句); +- `src/pages/task/TaskPage.jsx`、`ProjectView.jsx` 只在 `useEffect(..., [load])` 里加载一次; +- 唯一的刷新机制是 `src/hooks/useRefreshOnVisible.js`(回前台/窗口聚焦时重查), + 而且**任务模块这两个页面根本没有用它**(只有打卡 `MergedView/ListView` 与宏观各页在用)。 + +所以「切面板能刷新」只是因为它**重新挂载组件 → 重新执行一次 `load()`**。 + +### 1.3 现状刷新机制盘点 + +| 页面 | 首次加载 | 回前台刷新 | 数据变化实时反映 | +| --- | --- | --- | --- | +| `TaskPage` | `useEffect` | ❌ 无 | ❌ | +| `ProjectView` | `useEffect` | ❌ 无 | ❌(仅本地操作后手动 `load()`) | +| `TaskDetail` | 打开时传入快照 | — | ❌(快照可能过期) | +| `MergedView`(打卡) | `useEffect` | ✅ | ❌(且打卡走 REST,非 PowerSync) | + +--- + +## 2. 目标与非目标 + +### 2.1 目标 + +1. **跨端即时**:一端修改,其它端在 **≤1s** 内自动呈现(不含网络中断场景); +2. **不打断操作**:正在输入的详情表单、正在拖拽的卡片**不被远端变更打断**; +3. **离线可用**:断网期间照常读写本地库,恢复后自动补齐并刷新; +4. **改动可控**:优先复用现有装配逻辑(`taskApi` 的 shape),不做大规模重写; +5. **可回滚**:通过开关或逐步替换,出问题能退回"手动刷新"。 + +### 2.2 非目标 + +- 不做**多人协同编辑**(同一字段并发写仍按 LWW 后到覆盖); +- 不解决打卡/清单模块的实时性(它们走 REST,见 §7); +- 不引入 WebSocket 之外的额外实时通道(如 SSE)。 + +--- + +## 3. 方案选型 + +| 方案 | 做法 | 优点 | 缺点 | 结论 | +| --- | --- | --- | --- | --- | +| **A. 轮询** | 定时(如 3s)重跑 `load()` | 改动最小 | 无谓查询、电量/流量、仍不"即时" | ❌ | +| **B. 全量响应式** | 把 `taskApi.list` 整体改成一条大 SQL + `useQuery` | 最"正统",细粒度 | 现有装配(子任务/成员/标签)要在 SQL 里重写;改动面大、回归风险高 | ⏳ 后续 | +| **C. 变更信号 + 重算**(推荐) | 用 `db.watch()` 订阅一个**轻量"信号查询"**,信号变化时重跑现有 `load()` | 复用现有装配逻辑;改动集中且小;天然合并多次变更 | 每次重算一次列表(数据量小,可接受) | ✅ **P0 采用** | +| **D. 表级广播** | 监听"某表有变更"事件触发重查 | 实现最简单 | 无法区分项目/范围,易全页面重查 | 作为 C 的退化实现 | + +**结论**:P0 用方案 C;跑稳后再评估把高频视图(看板)迁到 B 的增量查询。 + +--- + +## 4. 推荐方案详设(方案 C) + +### 4.1 核心思路 + +``` +本地 SQLite ←(PowerSync 下行写入)— 任一端/服务端变更 + │ + └─▶ db.watch(<信号查询>) ── 变化 ──▶ 节流合并 ──▶ 重跑 load() + └─▶ setState 渲染 +``` + +**"信号查询"** 是一段**廉价但能覆盖所有相关表**的 SQL:只要任务域的任一相关行变化, +其结果就会变;它不承担业务语义,只负责触发。 + +### 4.2 订阅范围:用 SDK 的表级过滤(已实施) + +> 初版设计是手写一段"信号 SQL"(count / sum / max(updatedAt) 汇总), +> 实施时发现 `db.watch()` 原生支持 **`tables` 表名过滤** 与 **`throttleMs` 节流**, +> 由 SDK 判断"结果是否真的变了",比自己做汇总更可靠,因此改为表级过滤。 + +`CommonPowerSyncDatabase.watch(sql, params, handler, options)` 的 `options`: + +| 选项 | 用途 | +| --- | --- | +| `tables: string[]` | 只关心这些表的变更,SDK 内部按表失效 | +| `throttleMs` | 两次回调之间的最小间隔,天然合并同一批同步写入 | +| `signal: AbortSignal` | 卸载时取消订阅 | + +订阅内容(`src/lib/powersync/useDbSignal.js`): + +```js +db.watch('SELECT 1 AS s', [], { onResult, onError }, { + tables: ['Task', 'BoardColumn', 'TaskMember', 'TaskTag'], + throttleMs: 250, + signal: ac.signal, +}) +``` + +查询本身(`SELECT 1`)没有业务含义,只借 watch 的变更通知能力。表名集合定义在 +`src/api/task.js`: + +| 常量 | 内容 | 用于 | +| --- | --- | --- | +| `TASK_TABLES` | `Task, BoardColumn, TaskMember, TaskTag` | 项目内任务列表 / 看板 | +| `PROJECT_TABLES` | `Project, ProjectMember` | 项目列表、成员 | + +之所以不需要 `max(updatedAt)`:表级过滤关心的是"行有没有被写过", +而不是"结果集内容是否变化",所以改标题、改状态这类不增删的写入同样会触发。 + +### 4.3 UI 层接入 + +提供两个小工具,避免每个页面各写一套: + +| 工具 | 位置(建议) | 作用 | +| --- | --- | --- | +| `useDbSignal(sql, params, onChange)` | `src/lib/powersync/useDbSignal.js`(新增) | 封装 `db.watch()` + 节流 + 卸载清理 | +| `useTaskListSignal(projectId, onChange)` | `src/api/task.js` 或同目录(新增) | 拼好上述信号 SQL,页面只关心"变了要干嘛" | + +接入示意(`ProjectView`): + +```jsx +// 现状 +useEffect(() => { setTasks(null); load() }, [load]) + +// 改造后(示意) +useEffect(() => { setTasks(null); load() }, [load]) +useDbSignal(TASK_LIST_SIGNAL_SQL, [projectId, projectId, projectId, projectId, projectId, projectId], () => { + if (dragActiveRef.current) return // 见 §4.4 + load() +}) +``` + +`TaskPage` 同理,信号为项目表。 + +### 4.4 变更合并与"不打断"策略 + +这是本方案的关键,否则会出现「打字打到一半表单被重置」「拖到一半卡片跳走」。 + +| 场景 | 策略 | +| --- | --- | +| 连续多次变更(一次同步写多行) | `useDbSignal` 内部 **150~300ms 防抖**,合并成一次 `load()` | +| 看板/列表**拖拽进行中** | 用 `dragActiveRef` 跳过本次刷新;拖拽结束后补一次 `load()` | +| 任务**详情页打开中** | 不自动刷新表单;改为监听该任务是否变化,变化时在详情顶部显示「**该任务已在他端更新,点击刷新**」 | +| 正在编辑标题/描述 | 同上传:不覆盖输入框,只提示 | +| 本地乐观更新进行中(`updateTask` 未完成) | 以本地 patch 为准,`load()` 延后到请求结束(现有 `updateTask` 已有 snapshot 回滚,保留) | + +### 4.5 同步状态指示 + +用已导出的 `useStatus()` 在任务页顶部显示一个极轻量的状态点: + +| 状态 | 展示 | +| --- | --- | +| `connected && dataFlowStatus.connected` | 不显示(无干扰) | +| 正在下载/上传 | 细小的同步图标 | +| 离线 / 连接失败 | 「离线,改动将在联网后同步」 | + +> 说明:当前外壳里 PowerSync 会打 `Multiple tab support is not enabled` 的提示, +> 属预期(已在 0.2.1 关闭多标签以避免 Windows reload 后本地库挂起),不影响本方案。 + +### 4.6 离线与重连 + +- 断网:`db.watch()` 仍监听**本地库**,本地写入照常触发刷新(体验一致); +- 恢复:PowerSync 自动重连并回放,写入本地库后信号触发刷新,**无需额外逻辑**; +- 应用回到前台:保留 `useRefreshOnVisible` 作为**兜底**(WebView 可能挂起渲染线程), + 与 `db.watch()` 并存不冲突(两者都只是调 `load()`)。 + +--- + +## 5. 改造清单(按文件) + +| 文件 | 改动 | 状态 | +| --- | --- | --- | +| `src/lib/powersync/useDbSignal.js` | **新增**:`db.watch` + 表过滤 + 节流 + 卸载清理 | ✅ P0 已完成 | +| `src/api/task.js` | 新增 `TASK_TABLES` / `PROJECT_TABLES` 常量 | ✅ P0 | +| `src/pages/task/ProjectView.jsx` | 订阅任务域;拖拽中延后刷新;回前台兜底 | ✅ P0 | +| `src/pages/task/TaskPage.jsx` | 订阅项目域;回前台兜底 | ✅ P0 | +| `src/pages/task/BoardView.jsx` / `ListView.jsx` | 上报 `onDragActiveChange` | ✅ P0 | +| `src/pages/task/TaskDetail.jsx` | 「已在他端更新」提示(不覆盖表单) | ⏳ P1 | +| 同步状态指示 | 用 `useStatus()` 显示离线/同步中 | ⏳ P1 | + +**不需要改动**:`server/`、`sync-config.yaml`、数据库结构。 + +--- + +## 6. 分期与验收 + +### 6.1 分期 + +| 阶段 | 内容 | 预估 | +| --- | --- | --- | +| **P0** | `useDbSignal` + `ProjectView`/`TaskPage` 接入 + 拖拽保护 | 0.5~1 天 | +| **P1** | 详情页「已在他端更新」提示 + 同步状态指示 | 0.5 天 | +| **P2** | 看板改增量查询(方案 B);打卡/清单的实时性另议 | 按需 | + +### 6.2 验收标准(可测) + +1. **跨端延迟**:A 端改状态/拖列/改标题,B 端自动更新。实测 **约 1~2s**(数据进本地库约 1s,渲染 +0.3s,见 §7.0); +2. **不打断**:B 端正在输入标题时,A 端改同任务描述 → B 端输入框内容**不被清空**,仅出现提示; +3. **拖拽安全**:B 端拖拽中收到远端变更 → 卡片**不跳位**,松手后列表更新; +4. **离线**:断网期间本地操作正常,恢复后自动补齐并刷新; +5. **回滚**:`localStorage['iboard_realtime'] = '0'` 后重启应用,退回"手动刷新",无副作用; +6. **性能**:单项目 100 条任务、连续 20 次远端写入,页面无卡顿、无重复渲染风暴(可用 Performance 面板核对)。 + +### 6.3 度量 + +- 在 `load()` 内打点(可临时 `console.time`),统计重算耗时与触发次数; +- 目标:一次远端变更只触发 **1 次** 重算;100 条任务时单次重算 < 60ms。 + +--- + +## 7. 已知边界与后续 + +### 7.0 实测:端到端约 1~2s(达标) + +P0 落地后在真实 Windows 外壳上实测,服务端直接改库模拟另一端: + +| 项 | 结果 | +| --- | --- | +| 是否还需要切页面 | **不需要** —— 服务端改完,页面在无任何操作的情况下自动更新 ✅ | +| 端到端延迟 | **约 1~2s**(数据进本地库约 1s,渲染再 +0.3s)✅ | + +**测量方法(关键:早期结论曾被测量方式误导两次,务必按此方法复测)** + +测量必须在**页面内**自计时,不能用 CDP 轮询 DOM: + +```js +// 页面内: 50ms 轮询本地库 + MutationObserver 观察 DOM, 各自记录 performance.now() +const P = { startedAt: performance.now(), dbAt: null, domAt: null } +setInterval(async () => { + if (P.dbAt !== null) return + const row = await window.__psdb.get('SELECT title FROM Task WHERE number = ?', [16]) + if (row && row.title.includes(MARK)) P.dbAt = performance.now() - P.startedAt +}, 50) +new MutationObserver(check).observe(document.body, { childList: true, subtree: true }) +``` + +一次干净测量的结果(已重载页面、无残留定时器): + +| 度量 | 数值 | +| --- | --- | +| `psql` 命令总耗时(ssh + docker exec + UPDATE) | 2138ms | +| 数据出现在客户端本地库(相对计时起点) | 2251ms → **≈ 命令返回后 113ms** | +| 界面更新(相对计时起点) | 2580ms | +| **同步数据到达 → 渲染完成** | **329ms** | + +结论:**服务端改库到界面呈现约 1~2s(含 SSH/psql 开销在内的保守上界),其中 UI 渲染只占 ~0.33s**, +`useDbSignal` 的表级 watch 工作正常,无需改 `useQuery`。 + +**两次被推翻的中间结论(留档,避免重复踩坑)** + +| 曾经的判断 | 为何是错的 | +| --- | --- | +| ① "nginx `proxy_buffering` 导致按批下发" | 加了 `proxy_buffering off` 等配置并热重载(`nginx -T` 确认生效),延迟毫无变化。**该配置已回滚**(`advanced_config` 清空、生效 conf 还原、nginx 已重载)—— 保留它反而会让 OTA 大包下载失去缓冲保护 | +| ② "同步数据早在服务端发出,客户端 `db.watch`/渲染滞后 15~30s" | 错在测量:用 CDP 反复读 `document.body.innerText`(长列表页上开销很大)轮询,且把 `ssh + docker exec` 的 ~6s 开销算进了延迟。改为页面内自计时后,实测只有 ~1~2s | + +**服务端侧的旁证**(与上述一致):powersync 日志 `checkpoint_complete` 与改库同一秒完成; +`stream_ms` 达 42 分钟、`close_reason: client closing stream`,长连接稳定,非频繁重连。 + + +| 项 | 说明 | +| --- | --- | +| 打卡 / 清单 | 走 REST(`src/api/checkin.js`),不经过本地库,**本项目方案不覆盖**;如需实时,可复用现有 `useRefreshOnVisible` + 定时轮询,或后续把这些模块也迁到 PowerSync | +| 并发写冲突 | 仍为 LWW(`updatedAt` 后到覆盖);本文不引入人工合并 | +| 排序并发 | `taskApi.reorder` 会整批重写 `sortOrder`,两端同时拖拽可能相互覆盖;建议 P2 再引入"版本号/操作序列" | +| 大列表 | 方案 C 每次全量重算,任务数极大(>1000)时需转方案 B 的增量查询 | +| WebView 后台节流 | 应用在后台时 `watch` 回调可能被延后,回前台由 `useRefreshOnVisible` 兜底 | + +--- + +## 8. 附:关键 API 备忘 + +| API | 来源 | 用途 | +| --- | --- | --- | +| `db.watch(sql, params, options)` | `@powersync/common` `CommonPowerSyncDatabase` | 订阅查询结果,返回 `AsyncIterable` | +| `useQuery(sql, params, options)` | `@powersync/react` | React 版订阅;`options.rowComparator` 可做**增量**更新(P2 用) | +| `useStatus()` | `@powersync/react` | 连接/同步状态 | +| `getPowersyncDb()` | `src/lib/powersync/db.js` | 本地库单例(当前唯一入口) | + +> 注:`src/lib/powersync/react.jsx` 已 re-export `useQuery/useStatus/usePowerSync`, +> 本次改造**无需新增依赖**。 + +--- + +## 9. 结论 + +当前"必须切面板才刷新"**不是同步没到,而是 UI 从未订阅本地库变化**。 +采用「**变更信号 + 复用现有装配重算**」(方案 C)可在改动面很小的情况下获得亚秒级跨端同步, +并把"不打断编辑/拖拽"作为硬性要求一并纳入。P0 约半天到一天即可上线验证。 diff --git a/generated/prisma/browser.ts b/generated/prisma/browser.ts index 6b62810..87c44a9 100644 --- a/generated/prisma/browser.ts +++ b/generated/prisma/browser.ts @@ -27,21 +27,6 @@ export type User = Prisma.UserModel * */ export type Article = Prisma.ArticleModel -/** - * Model Strategy - * - */ -export type Strategy = Prisma.StrategyModel -/** - * Model Indicator - * - */ -export type Indicator = Prisma.IndicatorModel -/** - * Model IndicatorPoint - * - */ -export type IndicatorPoint = Prisma.IndicatorPointModel /** * Model News * @@ -77,11 +62,6 @@ export type Econ_SowInventory = Prisma.Econ_SowInventoryModel * */ export type ScraperCache = Prisma.ScraperCacheModel -/** - * Model OilPriceHistory - * - */ -export type OilPriceHistory = Prisma.OilPriceHistoryModel /** * Model MihomeAccount * diff --git a/generated/prisma/client.ts b/generated/prisma/client.ts index 188bf4b..b931523 100644 --- a/generated/prisma/client.ts +++ b/generated/prisma/client.ts @@ -51,21 +51,6 @@ export type User = Prisma.UserModel * */ export type Article = Prisma.ArticleModel -/** - * Model Strategy - * - */ -export type Strategy = Prisma.StrategyModel -/** - * Model Indicator - * - */ -export type Indicator = Prisma.IndicatorModel -/** - * Model IndicatorPoint - * - */ -export type IndicatorPoint = Prisma.IndicatorPointModel /** * Model News * @@ -101,11 +86,6 @@ export type Econ_SowInventory = Prisma.Econ_SowInventoryModel * */ export type ScraperCache = Prisma.ScraperCacheModel -/** - * Model OilPriceHistory - * - */ -export type OilPriceHistory = Prisma.OilPriceHistoryModel /** * Model MihomeAccount * diff --git a/generated/prisma/commonInputTypes.ts b/generated/prisma/commonInputTypes.ts index 3deecf5..b380b29 100644 --- a/generated/prisma/commonInputTypes.ts +++ b/generated/prisma/commonInputTypes.ts @@ -99,97 +99,6 @@ export type DateTimeWithAggregatesFilter<$PrismaModel = never> = { _max?: Prisma.NestedDateTimeFilter<$PrismaModel> } -export type JsonFilter<$PrismaModel = never> = -| Prisma.PatchUndefined< - Prisma.Either>, Exclude>, 'path'>>, - Required> - > -| Prisma.OptionalFlat>, 'path'>> - -export type JsonFilterBase<$PrismaModel = never> = { - equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter - path?: string[] - mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> - string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> - string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> - string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel> - array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter -} - -export type JsonWithAggregatesFilter<$PrismaModel = never> = -| Prisma.PatchUndefined< - Prisma.Either>, Exclude>, 'path'>>, - Required> - > -| Prisma.OptionalFlat>, 'path'>> - -export type JsonWithAggregatesFilterBase<$PrismaModel = never> = { - equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter - path?: string[] - mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> - string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> - string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> - string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel> - array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter - _count?: Prisma.NestedIntFilter<$PrismaModel> - _min?: Prisma.NestedJsonFilter<$PrismaModel> - _max?: Prisma.NestedJsonFilter<$PrismaModel> -} - -export type BoolFilter<$PrismaModel = never> = { - equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> - not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean -} - -export type BoolWithAggregatesFilter<$PrismaModel = never> = { - equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> - not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean - _count?: Prisma.NestedIntFilter<$PrismaModel> - _min?: Prisma.NestedBoolFilter<$PrismaModel> - _max?: Prisma.NestedBoolFilter<$PrismaModel> -} - -export type FloatFilter<$PrismaModel = never> = { - equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> - in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> - notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> - lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> - lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> - gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> - gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> - not?: Prisma.NestedFloatFilter<$PrismaModel> | number -} - -export type FloatWithAggregatesFilter<$PrismaModel = never> = { - equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> - in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> - notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> - lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> - lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> - gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> - gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> - not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number - _count?: Prisma.NestedIntFilter<$PrismaModel> - _avg?: Prisma.NestedFloatFilter<$PrismaModel> - _sum?: Prisma.NestedFloatFilter<$PrismaModel> - _min?: Prisma.NestedFloatFilter<$PrismaModel> - _max?: Prisma.NestedFloatFilter<$PrismaModel> -} - export type StringNullableFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null @@ -228,6 +137,33 @@ export type StringNullableWithAggregatesFilter<$PrismaModel = never> = { _max?: Prisma.NestedStringNullableFilter<$PrismaModel> } +export type FloatFilter<$PrismaModel = never> = { + equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> + lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + not?: Prisma.NestedFloatFilter<$PrismaModel> | number +} + +export type FloatWithAggregatesFilter<$PrismaModel = never> = { + equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> + lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number + _count?: Prisma.NestedIntFilter<$PrismaModel> + _avg?: Prisma.NestedFloatFilter<$PrismaModel> + _sum?: Prisma.NestedFloatFilter<$PrismaModel> + _min?: Prisma.NestedFloatFilter<$PrismaModel> + _max?: Prisma.NestedFloatFilter<$PrismaModel> +} + export type BigIntFilter<$PrismaModel = never> = { equals?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> in?: bigint[] | number[] | Prisma.ListBigIntFieldRefInput<$PrismaModel> @@ -328,6 +264,11 @@ export type DateTimeNullableFilter<$PrismaModel = never> = { not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null } +export type BoolFilter<$PrismaModel = never> = { + equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> + not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean +} + export type IntNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null @@ -358,6 +299,14 @@ export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = { _max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> } +export type BoolWithAggregatesFilter<$PrismaModel = never> = { + equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> + not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedBoolFilter<$PrismaModel> + _max?: Prisma.NestedBoolFilter<$PrismaModel> +} + export type FloatNullableFilter<$PrismaModel = never> = { equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null @@ -385,6 +334,57 @@ export type FloatNullableWithAggregatesFilter<$PrismaModel = never> = { _max?: Prisma.NestedFloatNullableFilter<$PrismaModel> } +export type JsonFilter<$PrismaModel = never> = +| Prisma.PatchUndefined< + Prisma.Either>, Exclude>, 'path'>>, + Required> + > +| Prisma.OptionalFlat>, 'path'>> + +export type JsonFilterBase<$PrismaModel = never> = { + equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter + path?: string[] + mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> + string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> + string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel> + array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null + array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null + array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null + lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter +} + +export type JsonWithAggregatesFilter<$PrismaModel = never> = +| Prisma.PatchUndefined< + Prisma.Either>, Exclude>, 'path'>>, + Required> + > +| Prisma.OptionalFlat>, 'path'>> + +export type JsonWithAggregatesFilterBase<$PrismaModel = never> = { + equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter + path?: string[] + mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> + string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> + string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel> + array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null + array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null + array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null + lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedJsonFilter<$PrismaModel> + _max?: Prisma.NestedJsonFilter<$PrismaModel> +} + export type NestedIntFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> @@ -479,59 +479,6 @@ export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = { _max?: Prisma.NestedDateTimeFilter<$PrismaModel> } -export type NestedJsonFilter<$PrismaModel = never> = -| Prisma.PatchUndefined< - Prisma.Either>, Exclude>, 'path'>>, - Required> - > -| Prisma.OptionalFlat>, 'path'>> - -export type NestedJsonFilterBase<$PrismaModel = never> = { - equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter - path?: string[] - mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> - string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> - string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> - string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel> - array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> - not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter -} - -export type NestedBoolFilter<$PrismaModel = never> = { - equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> - not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean -} - -export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = { - equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> - not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean - _count?: Prisma.NestedIntFilter<$PrismaModel> - _min?: Prisma.NestedBoolFilter<$PrismaModel> - _max?: Prisma.NestedBoolFilter<$PrismaModel> -} - -export type NestedFloatWithAggregatesFilter<$PrismaModel = never> = { - equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> - in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> - notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> - lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> - lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> - gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> - gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> - not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number - _count?: Prisma.NestedIntFilter<$PrismaModel> - _avg?: Prisma.NestedFloatFilter<$PrismaModel> - _sum?: Prisma.NestedFloatFilter<$PrismaModel> - _min?: Prisma.NestedFloatFilter<$PrismaModel> - _max?: Prisma.NestedFloatFilter<$PrismaModel> -} - export type NestedStringNullableFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null @@ -574,6 +521,22 @@ export type NestedIntNullableFilter<$PrismaModel = never> = { not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null } +export type NestedFloatWithAggregatesFilter<$PrismaModel = never> = { + equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> + lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number + _count?: Prisma.NestedIntFilter<$PrismaModel> + _avg?: Prisma.NestedFloatFilter<$PrismaModel> + _sum?: Prisma.NestedFloatFilter<$PrismaModel> + _min?: Prisma.NestedFloatFilter<$PrismaModel> + _max?: Prisma.NestedFloatFilter<$PrismaModel> +} + export type NestedBigIntFilter<$PrismaModel = never> = { equals?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> in?: bigint[] | number[] | Prisma.ListBigIntFieldRefInput<$PrismaModel> @@ -636,6 +599,11 @@ export type NestedDateTimeNullableFilter<$PrismaModel = never> = { not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null } +export type NestedBoolFilter<$PrismaModel = never> = { + equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> + not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean +} + export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null @@ -677,6 +645,14 @@ export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = { _max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> } +export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = { + equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> + not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedBoolFilter<$PrismaModel> + _max?: Prisma.NestedBoolFilter<$PrismaModel> +} + export type NestedFloatNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null @@ -693,4 +669,28 @@ export type NestedFloatNullableWithAggregatesFilter<$PrismaModel = never> = { _max?: Prisma.NestedFloatNullableFilter<$PrismaModel> } +export type NestedJsonFilter<$PrismaModel = never> = +| Prisma.PatchUndefined< + Prisma.Either>, Exclude>, 'path'>>, + Required> + > +| Prisma.OptionalFlat>, 'path'>> + +export type NestedJsonFilterBase<$PrismaModel = never> = { + equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter + path?: string[] + mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> + string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> + string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel> + array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null + array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null + array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null + lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter +} + diff --git a/generated/prisma/internal/class.ts b/generated/prisma/internal/class.ts index c64d7b8..1acba57 100644 --- a/generated/prisma/internal/class.ts +++ b/generated/prisma/internal/class.ts @@ -20,7 +20,7 @@ const config: runtime.GetPrismaClientConfig = { "clientVersion": "7.7.0", "engineVersion": "75cbdc1eb7150937890ad5465d861175c6624711", "activeProvider": "postgresql", - "inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\n// Get a free hosted Postgres database in seconds: `npx create-db`\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel User {\n id Int @id @default(autoincrement())\n name String\n email String @unique\n password String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // V0.2 打卡 / 清单: 用户删除时级联清理\n habits Habit[]\n checkIns CheckIn[]\n checkLists CheckList[]\n templates CheckListTemplate[]\n\n // V0.2 任务模块\n ownedProjects Project[] @relation(\"ProjectOwner\")\n projectMembers ProjectMember[]\n assignedTasks Task[] @relation(\"TaskAssignee\")\n createdTasks Task[] @relation(\"TaskCreator\")\n taskMembers TaskMember[]\n}\n\nmodel Article {\n id Int @id @default(autoincrement())\n title String\n slug String @unique\n content String\n date String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Strategy {\n id Int @id @default(autoincrement())\n name String\n description String\n data Json\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Indicator {\n id Int @id @default(autoincrement())\n name String @unique\n groupName String\n title String @default(\"\")\n unit String @default(\"\")\n source String @default(\"\")\n frequency String @default(\"month\") // year | quarter | month | day\n hidden Boolean @default(false) // true = 数据/脚本保留, 但不在前端展示\n points IndicatorPoint[]\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel IndicatorPoint {\n id Int @id @default(autoincrement())\n indicatorId Int\n indicator Indicator @relation(fields: [indicatorId], references: [id], onDelete: Cascade)\n period String // \"2024\" | \"2024-Q1\" | \"2024-01\" | \"2024-01-15\"\n value Float\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@unique([indicatorId, period])\n}\n\n// 新闻: 每天 3 点由 mx_finance_search_news 拉取, 按影响大小排序, 累加多日供首页无限滚动\nmodel News {\n id Int @id @default(autoincrement())\n title String\n summary String @default(\"\")\n content String? @db.Text\n source String @default(\"\")\n url String @default(\"\")\n publishedAt DateTime\n impactRank Int\n createdAt DateTime @default(now())\n\n @@unique([title, publishedAt])\n @@index([publishedAt])\n}\n\n// 报警: refresh 写库时比对同 (indicatorName, period) 的跨源数据, 差异超阈值记录\nmodel Alert {\n id Int @id @default(autoincrement())\n indicatorName String\n period String\n valueA Float\n valueB Float\n sourceA String\n sourceB String\n diff Float\n diffRatio Float\n createdAt DateTime @default(now())\n\n @@index([createdAt])\n}\n\n// 服务器监控: 对接 komari 公开 API\nmodel ServerNode {\n id Int @id @default(autoincrement())\n uuid String @unique\n name String\n cpuName String @default(\"\")\n os String @default(\"\")\n region String @default(\"\")\n memTotal BigInt @default(0)\n diskTotal BigInt @default(0)\n cpuCores Int @default(0)\n // 最新实时快照\n cpuUsage Float @default(0)\n ramUsed BigInt @default(0)\n swapUsed BigInt @default(0)\n load1 Float @default(0)\n load5 Float @default(0)\n load15 Float @default(0)\n diskUsed BigInt @default(0)\n netIn BigInt @default(0)\n netOut BigInt @default(0)\n uptime BigInt @default(0)\n process Int @default(0)\n connections Int @default(0)\n status String @default(\"unknown\") // online | offline | unknown\n recordedAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\n// 时序历史: 每节点每分钟一条 (komari records 1 分钟粒度)\nmodel ServerNodeHistory {\n id Int @id @default(autoincrement())\n uuid String\n period DateTime\n cpu Float @default(0)\n ram BigInt @default(0)\n load Float @default(0)\n disk BigInt @default(0)\n netIn BigInt @default(0)\n netOut BigInt @default(0)\n process Int @default(0)\n connections Int @default(0)\n createdAt DateTime @default(now())\n\n @@unique([uuid, period])\n @@index([uuid, period])\n}\n\n// 服务健康检查: 本应用自检 (健康端点 / DB / 第三方)\nmodel ServiceCheck {\n id Int @id @default(autoincrement())\n name String @unique\n url String\n status String @default(\"unknown\") // up | down | unknown\n latencyMs Int @default(0)\n message String @default(\"\")\n lastCheckAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Econ_SowInventory {\n id Int @id @default(autoincrement())\n month String @unique\n inventory Int // 能繁母猪数量(万头)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel ScraperCache {\n id Int @id @default(autoincrement())\n scraperName String @unique // 爬虫名称,如:sow-inventory\n lastDataHash String? // 上次数据指纹\n lastUpdateAt DateTime // 上次更新时间\n metadata Json? // 其他元数据\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\n// 浙江省油价历史 (apizero price 接口每日抓取)\n// 每天 04:00 cron 触发: 检查 schedule 里待定且 <= 昨天的调价日, 抓 price?province=浙江 存本表\n// unique (date, province, fuel) 防重复\nmodel OilPriceHistory {\n id Int @id @default(autoincrement())\n date DateTime // 调价日次日 (即数据实际抓取日)\n province String // '浙江'\n fuel String // '92' | '95' | '0'\n price Float // 油价 (元/升, 4 位小数足够)\n source String @default(\"apizero\")\n createdAt DateTime @default(now())\n\n @@unique([date, province, fuel])\n @@index([province, fuel, date])\n}\n\n// ==================== 环境监控:米家 ====================\n\n// 米家授权状态 (DiskCache 存凭证, 本表只存状态/错误供前端展示)\nmodel MihomeAccount {\n id Int @id @default(autoincrement())\n key String @unique @default(\"default\")\n userId String @default(\"\")\n status String @default(\"unknown\") // ok | pending | expired\n lastError String @default(\"\")\n updatedAt DateTime @updatedAt\n}\n\nmodel MihomeHome {\n id Int @id @default(autoincrement())\n homeId String @unique\n name String\n createdAt DateTime @default(now())\n devices MihomeDevice[]\n}\n\nmodel MihomeDevice {\n id Int @id @default(autoincrement())\n did String @unique\n homeId String\n home MihomeHome @relation(fields: [homeId], references: [homeId])\n model String\n name String\n alias String? // 用户别名, 仅本应用展示\n tempSiid Int\n tempPiid Int\n humSiid Int\n humPiid Int\n battSiid Int // 电池电量属性 (炎米 3.1003 / 青萍 2.3)\n battPiid Int\n battery Int? // 最近一次电量 (每天读一次, 标题展示用)\n batteryAt DateTime? // 电量读取时间 (当天缓存, 不频繁更新)\n enabled Boolean @default(true)\n lastSyncAt DateTime?\n createdAt DateTime @default(now())\n samples MihomeEnvSample[]\n hours MihomeEnvHour[]\n}\n\n// 日档: 月视图(逐日)数据源; unique (deviceId, date) 防重\nmodel MihomeEnvSample {\n id Int @id @default(autoincrement())\n deviceId Int\n device MihomeDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)\n date DateTime // 采样日 (Asia/Shanghai 当日 00:00 存 UTC)\n temperature Float? // 当日最后一次实时值 (或 vendor hour 均值)\n humidity Float?\n temperatureAvg Float? // 当日均值 (由小时档聚合, 或 vendor_hour)\n humidityAvg Float?\n temperatureMin Float? // 当日最低\n temperatureMax Float? // 当日最高\n humidityMin Float?\n humidityMax Float?\n origin String @default(\"live\") // live | vendor_history | backfill\n sampledAt DateTime?\n raw Json?\n createdAt DateTime @default(now())\n\n @@unique([deviceId, date])\n @@index([deviceId, date])\n}\n\n// 小时档: 当天视图(逐小时)数据源; unique (deviceId, hour) 防重\nmodel MihomeEnvHour {\n id Int @id @default(autoincrement())\n deviceId Int\n device MihomeDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)\n hour DateTime // Asia/Shanghai 整点 (存 UTC)\n temperatureAvg Float? // 该小时均值 (live 采样点, 或 vendor_hour)\n humidityAvg Float?\n raw Json?\n createdAt DateTime @default(now())\n\n @@unique([deviceId, hour])\n @@index([deviceId, hour])\n}\n\n// ==================== 打卡 / 日历 / 清单 (V0.2) ====================\n\n// 打卡项: 周期性目标 (每天 / 每周 / 每月 / 每年 n 次)\nmodel Habit {\n id Int @id @default(autoincrement())\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n name String\n period String @default(\"day\") // day | week | month | year\n target Int @default(1) // 周期内目标次数 n (>=1)\n color String @default(\"#1677ff\")\n icon String @default(\"\") // emoji 或图标名, 预留\n note String @default(\"\")\n archived Boolean @default(false) // 归档: 不出现在今日待办, 历史保留\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n records CheckIn[]\n\n @@index([userId, archived, sortOrder])\n}\n\n// 打卡记录: 一次打卡 = 一条; 同日多次 => seq 递增 (1..n, 超额继续累加)\nmodel CheckIn {\n id Int @id @default(autoincrement())\n habitId Int\n habit Habit @relation(fields: [habitId], references: [id], onDelete: Cascade)\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n date String @db.VarChar(10) // 本地日历日 \"YYYY-MM-DD\" (Asia/Shanghai)\n seq Int @default(1) // 当日第几次\n note String @default(\"\")\n source String @default(\"user\") // user | backfill (补打)\n createdAt DateTime @default(now())\n\n @@unique([habitId, date, seq])\n @@index([userId, date])\n @@index([habitId, date])\n}\n\n// 检查清单: 出差 / 回老家 等场景的一次性清单\nmodel CheckList {\n id Int @id @default(autoincrement())\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n title String\n scene String @default(\"\") // 出差 | 回老家 | 自定义\n note String @default(\"\")\n archived Boolean @default(false)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n items CheckListItem[]\n\n @@index([userId, archived])\n}\n\n// 检查项: 勾选即完成\nmodel CheckListItem {\n id Int @id @default(autoincrement())\n listId Int\n list CheckList @relation(fields: [listId], references: [id], onDelete: Cascade)\n title String\n note String @default(\"\")\n done Boolean @default(false)\n doneAt DateTime?\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([listId, sortOrder])\n}\n\n// 清单模板: builtin = 系统预置 (userId 为空); 否则为用户自定义\nmodel CheckListTemplate {\n id Int @id @default(autoincrement())\n userId Int?\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n name String\n scene String @default(\"\")\n items Json // [{ title, note }]\n builtin Boolean @default(false)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId, builtin])\n}\n\n// ================== 任务模块 (V0.2 任务模块需求文档 T-*) ==================\n// 口径见 docs/V0.2/任务模块/任务模块数据库设计文档.md\n// 项目 = 隔离 + 权限单位; 看板列是实体(可增删改排序);\n// 任务状态(todo/doing/done) 与 看板列 是两个正交维度, 由列的 boundStatus 决定是否联动.\n\nmodel Project {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑, 见 docs/V0.2/离线编辑模块)\n code String @unique @db.VarChar(8) // 项目短编号: 用户自拟 2~8 位前缀, 全局唯一, 服务端分配\n name String @db.VarChar(50)\n ownerId Int\n owner User @relation(\"ProjectOwner\", fields: [ownerId], references: [id], onDelete: Cascade)\n isPersonal Boolean @default(false)\n archived Boolean @default(false)\n notes Json @default(\"[]\") // 项目信息块 [{ title, content }] (T-PJ-13)\n taskKinds Json? // 任务类型标签列表 (未配置时服务端回落默认四类)\n icon String? @db.Text // 项目图标 (data URL, 客户端裁剪为 96x96 JPEG)\n autoArchive String @default(\"never\") @db.VarChar(8) // 自动归档档位: 3d/1w/1m/never (T-ARC-02)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n members ProjectMember[]\n columns BoardColumn[]\n tasks Task[]\n\n @@index([ownerId])\n @@index([archived])\n}\n\nmodel ProjectMember {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n projectUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n projectId Int\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n permission String @default(\"member\")\n createdAt DateTime @default(now())\n\n @@unique([projectId, userId])\n @@index([userId])\n}\n\nmodel BoardColumn {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n projectUuid String? @db.VarChar(36) // projectId 的 uuid 镜像 (触发器维护, 库级 NOT NULL; Prisma 侧可空以便触发器填充)\n projectId Int\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n name String @db.VarChar(20)\n sortOrder Int @default(0)\n boundStatus String? @db.VarChar(8)\n retention String @default(\"forever\") @db.VarChar(8)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n tasks Task[]\n\n @@unique([projectId, name])\n @@index([projectId, sortOrder])\n}\n\nmodel Task {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n projectUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n columnUuid String? @db.VarChar(36)\n parentUuid String? @db.VarChar(36)\n projectId Int\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n columnId Int\n column BoardColumn @relation(fields: [columnId], references: [id], onDelete: Restrict)\n parentId Int?\n parent Task? @relation(\"Subtask\", fields: [parentId], references: [id], onDelete: Cascade)\n children Task[] @relation(\"Subtask\")\n\n title String @db.VarChar(200)\n description String @default(\"\") @db.Text\n kind String? @db.VarChar(20) // 任务类型标签 (项目 taskKinds 之一)\n markDate DateTime? // 时间标记 (完整日期存储, 展示为 MM-dd)\n status String @default(\"todo\") @db.VarChar(8)\n doneAt DateTime?\n\n assigneeId Int?\n assignee User? @relation(\"TaskAssignee\", fields: [assigneeId], references: [id], onDelete: SetNull)\n creatorId Int\n creator User @relation(\"TaskCreator\", fields: [creatorId], references: [id], onDelete: Cascade)\n\n extras Json @default(\"[]\")\n sortOrder Int @default(0)\n archived Boolean @default(false)\n archivedAt DateTime?\n archiveSource String? @db.VarChar(8) // manual | auto (T-ARC)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n members TaskMember[]\n tags TaskTag[]\n\n number Int? // 项目内自增编号 (服务端分配, 展示为 `code-number`); 本地未定号的离线新建任务为空\n\n @@unique([projectId, number])\n @@index([projectId, columnId])\n @@index([projectId, status])\n @@index([parentId])\n @@index([projectId, doneAt])\n @@index([projectId, archived, archivedAt])\n @@index([projectId, status, archived])\n}\n\nmodel TaskMember {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n taskUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n taskId Int\n task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([taskId, userId])\n}\n\nmodel TaskTag {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n taskUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n taskId Int\n task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)\n name String @db.VarChar(20)\n\n @@index([taskId])\n}\n", + "inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\n// Get a free hosted Postgres database in seconds: `npx create-db`\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel User {\n id Int @id @default(autoincrement())\n name String\n email String @unique\n password String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // V0.2 打卡 / 清单: 用户删除时级联清理\n habits Habit[]\n checkIns CheckIn[]\n checkLists CheckList[]\n templates CheckListTemplate[]\n\n // V0.2 任务模块\n ownedProjects Project[] @relation(\"ProjectOwner\")\n projectMembers ProjectMember[]\n assignedTasks Task[] @relation(\"TaskAssignee\")\n createdTasks Task[] @relation(\"TaskCreator\")\n taskMembers TaskMember[]\n}\n\nmodel Article {\n id Int @id @default(autoincrement())\n title String\n slug String @unique\n content String\n date String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\n// 新闻: 每天 3 点由 mx_finance_search_news 拉取, 按影响大小排序, 累加多日供首页无限滚动\nmodel News {\n id Int @id @default(autoincrement())\n title String\n summary String @default(\"\")\n content String? @db.Text\n source String @default(\"\")\n url String @default(\"\")\n publishedAt DateTime\n impactRank Int\n createdAt DateTime @default(now())\n\n @@unique([title, publishedAt])\n @@index([publishedAt])\n}\n\n// 报警: 定时任务失败等异常写入 (展示于「监控」报警 tab)\nmodel Alert {\n id Int @id @default(autoincrement())\n indicatorName String\n period String\n valueA Float\n valueB Float\n sourceA String\n sourceB String\n diff Float\n diffRatio Float\n createdAt DateTime @default(now())\n\n @@index([createdAt])\n}\n\n// 服务器监控: 对接 komari 公开 API\nmodel ServerNode {\n id Int @id @default(autoincrement())\n uuid String @unique\n name String\n cpuName String @default(\"\")\n os String @default(\"\")\n region String @default(\"\")\n memTotal BigInt @default(0)\n diskTotal BigInt @default(0)\n cpuCores Int @default(0)\n // 最新实时快照\n cpuUsage Float @default(0)\n ramUsed BigInt @default(0)\n swapUsed BigInt @default(0)\n load1 Float @default(0)\n load5 Float @default(0)\n load15 Float @default(0)\n diskUsed BigInt @default(0)\n netIn BigInt @default(0)\n netOut BigInt @default(0)\n uptime BigInt @default(0)\n process Int @default(0)\n connections Int @default(0)\n status String @default(\"unknown\") // online | offline | unknown\n recordedAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\n// 时序历史: 每节点每分钟一条 (komari records 1 分钟粒度)\nmodel ServerNodeHistory {\n id Int @id @default(autoincrement())\n uuid String\n period DateTime\n cpu Float @default(0)\n ram BigInt @default(0)\n load Float @default(0)\n disk BigInt @default(0)\n netIn BigInt @default(0)\n netOut BigInt @default(0)\n process Int @default(0)\n connections Int @default(0)\n createdAt DateTime @default(now())\n\n @@unique([uuid, period])\n @@index([uuid, period])\n}\n\n// 服务健康检查: 本应用自检 (健康端点 / DB / 第三方)\nmodel ServiceCheck {\n id Int @id @default(autoincrement())\n name String @unique\n url String\n status String @default(\"unknown\") // up | down | unknown\n latencyMs Int @default(0)\n message String @default(\"\")\n lastCheckAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Econ_SowInventory {\n id Int @id @default(autoincrement())\n month String @unique\n inventory Int // 能繁母猪数量(万头)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel ScraperCache {\n id Int @id @default(autoincrement())\n scraperName String @unique // 爬虫名称,如:sow-inventory\n lastDataHash String? // 上次数据指纹\n lastUpdateAt DateTime // 上次更新时间\n metadata Json? // 其他元数据\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\n// ==================== 环境监控:米家 ====================\n\n// 米家授权状态 (DiskCache 存凭证, 本表只存状态/错误供前端展示)\nmodel MihomeAccount {\n id Int @id @default(autoincrement())\n key String @unique @default(\"default\")\n userId String @default(\"\")\n status String @default(\"unknown\") // ok | pending | expired\n lastError String @default(\"\")\n updatedAt DateTime @updatedAt\n}\n\nmodel MihomeHome {\n id Int @id @default(autoincrement())\n homeId String @unique\n name String\n createdAt DateTime @default(now())\n devices MihomeDevice[]\n}\n\nmodel MihomeDevice {\n id Int @id @default(autoincrement())\n did String @unique\n homeId String\n home MihomeHome @relation(fields: [homeId], references: [homeId])\n model String\n name String\n alias String? // 用户别名, 仅本应用展示\n tempSiid Int\n tempPiid Int\n humSiid Int\n humPiid Int\n battSiid Int // 电池电量属性 (炎米 3.1003 / 青萍 2.3)\n battPiid Int\n battery Int? // 最近一次电量 (每天读一次, 标题展示用)\n batteryAt DateTime? // 电量读取时间 (当天缓存, 不频繁更新)\n enabled Boolean @default(true)\n lastSyncAt DateTime?\n createdAt DateTime @default(now())\n samples MihomeEnvSample[]\n hours MihomeEnvHour[]\n}\n\n// 日档: 月视图(逐日)数据源; unique (deviceId, date) 防重\nmodel MihomeEnvSample {\n id Int @id @default(autoincrement())\n deviceId Int\n device MihomeDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)\n date DateTime // 采样日 (Asia/Shanghai 当日 00:00 存 UTC)\n temperature Float? // 当日最后一次实时值 (或 vendor hour 均值)\n humidity Float?\n temperatureAvg Float? // 当日均值 (由小时档聚合, 或 vendor_hour)\n humidityAvg Float?\n temperatureMin Float? // 当日最低\n temperatureMax Float? // 当日最高\n humidityMin Float?\n humidityMax Float?\n origin String @default(\"live\") // live | vendor_history | backfill\n sampledAt DateTime?\n raw Json?\n createdAt DateTime @default(now())\n\n @@unique([deviceId, date])\n @@index([deviceId, date])\n}\n\n// 小时档: 当天视图(逐小时)数据源; unique (deviceId, hour) 防重\nmodel MihomeEnvHour {\n id Int @id @default(autoincrement())\n deviceId Int\n device MihomeDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)\n hour DateTime // Asia/Shanghai 整点 (存 UTC)\n temperatureAvg Float? // 该小时均值 (live 采样点, 或 vendor_hour)\n humidityAvg Float?\n raw Json?\n createdAt DateTime @default(now())\n\n @@unique([deviceId, hour])\n @@index([deviceId, hour])\n}\n\n// ==================== 打卡 / 日历 / 清单 (V0.2) ====================\n\n// 打卡项: 周期性目标 (每天 / 每周 / 每月 / 每年 n 次)\nmodel Habit {\n id Int @id @default(autoincrement())\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n name String\n period String @default(\"day\") // day | week | month | year\n target Int @default(1) // 周期内目标次数 n (>=1)\n color String @default(\"#1677ff\")\n icon String @default(\"\") // emoji 或图标名, 预留\n note String @default(\"\")\n archived Boolean @default(false) // 归档: 不出现在今日待办, 历史保留\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n records CheckIn[]\n\n @@index([userId, archived, sortOrder])\n}\n\n// 打卡记录: 一次打卡 = 一条; 同日多次 => seq 递增 (1..n, 超额继续累加)\nmodel CheckIn {\n id Int @id @default(autoincrement())\n habitId Int\n habit Habit @relation(fields: [habitId], references: [id], onDelete: Cascade)\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n date String @db.VarChar(10) // 本地日历日 \"YYYY-MM-DD\" (Asia/Shanghai)\n seq Int @default(1) // 当日第几次\n note String @default(\"\")\n source String @default(\"user\") // user | backfill (补打)\n createdAt DateTime @default(now())\n\n @@unique([habitId, date, seq])\n @@index([userId, date])\n @@index([habitId, date])\n}\n\n// 检查清单: 出差 / 回老家 等场景的一次性清单\nmodel CheckList {\n id Int @id @default(autoincrement())\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n title String\n scene String @default(\"\") // 出差 | 回老家 | 自定义\n note String @default(\"\")\n archived Boolean @default(false)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n items CheckListItem[]\n\n @@index([userId, archived])\n}\n\n// 检查项: 勾选即完成\nmodel CheckListItem {\n id Int @id @default(autoincrement())\n listId Int\n list CheckList @relation(fields: [listId], references: [id], onDelete: Cascade)\n title String\n note String @default(\"\")\n done Boolean @default(false)\n doneAt DateTime?\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([listId, sortOrder])\n}\n\n// 清单模板: builtin = 系统预置 (userId 为空); 否则为用户自定义\nmodel CheckListTemplate {\n id Int @id @default(autoincrement())\n userId Int?\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n name String\n scene String @default(\"\")\n items Json // [{ title, note }]\n builtin Boolean @default(false)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId, builtin])\n}\n\n// ================== 任务模块 (V0.2 任务模块需求文档 T-*) ==================\n// 口径见 docs/V0.2/任务模块/任务模块数据库设计文档.md\n// 项目 = 隔离 + 权限单位; 看板列是实体(可增删改排序);\n// 任务状态(todo/doing/done) 与 看板列 是两个正交维度, 由列的 boundStatus 决定是否联动.\n\nmodel Project {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑, 见 docs/V0.2/离线编辑模块)\n code String @unique @db.VarChar(8) // 项目短编号: 用户自拟 2~8 位前缀, 全局唯一, 服务端分配\n name String @db.VarChar(50)\n ownerId Int\n owner User @relation(\"ProjectOwner\", fields: [ownerId], references: [id], onDelete: Cascade)\n isPersonal Boolean @default(false)\n archived Boolean @default(false)\n notes Json @default(\"[]\") // 项目信息块 [{ title, content }] (T-PJ-13)\n taskKinds Json? // 任务类型标签列表 (未配置时服务端回落默认四类)\n icon String? @db.Text // 项目图标 (data URL, 客户端裁剪为 96x96 JPEG)\n autoArchive String @default(\"never\") @db.VarChar(8) // 自动归档档位: 3d/1w/1m/never (T-ARC-02)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n members ProjectMember[]\n columns BoardColumn[]\n tasks Task[]\n\n @@index([ownerId])\n @@index([archived])\n}\n\nmodel ProjectMember {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n projectUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n projectId Int\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n permission String @default(\"member\")\n createdAt DateTime @default(now())\n\n @@unique([projectId, userId])\n @@index([userId])\n}\n\nmodel BoardColumn {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n projectUuid String? @db.VarChar(36) // projectId 的 uuid 镜像 (触发器维护, 库级 NOT NULL; Prisma 侧可空以便触发器填充)\n projectId Int\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n name String @db.VarChar(20)\n sortOrder Int @default(0)\n boundStatus String? @db.VarChar(8)\n retention String @default(\"forever\") @db.VarChar(8)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n tasks Task[]\n\n @@unique([projectId, name])\n @@index([projectId, sortOrder])\n}\n\nmodel Task {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n projectUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n columnUuid String? @db.VarChar(36)\n parentUuid String? @db.VarChar(36)\n projectId Int\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n columnId Int\n column BoardColumn @relation(fields: [columnId], references: [id], onDelete: Restrict)\n parentId Int?\n parent Task? @relation(\"Subtask\", fields: [parentId], references: [id], onDelete: Cascade)\n children Task[] @relation(\"Subtask\")\n\n title String @db.VarChar(200)\n description String @default(\"\") @db.Text\n kind String? @db.VarChar(20) // 任务类型标签 (项目 taskKinds 之一)\n markDate DateTime? // 时间标记 (完整日期存储, 展示为 MM-dd)\n status String @default(\"todo\") @db.VarChar(8)\n doneAt DateTime?\n\n assigneeId Int?\n assignee User? @relation(\"TaskAssignee\", fields: [assigneeId], references: [id], onDelete: SetNull)\n creatorId Int\n creator User @relation(\"TaskCreator\", fields: [creatorId], references: [id], onDelete: Cascade)\n\n extras Json @default(\"[]\")\n sortOrder Int @default(0)\n archived Boolean @default(false)\n archivedAt DateTime?\n archiveSource String? @db.VarChar(8) // manual | auto (T-ARC)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n members TaskMember[]\n tags TaskTag[]\n\n number Int? // 项目内自增编号 (服务端分配, 展示为 `code-number`); 本地未定号的离线新建任务为空\n\n @@unique([projectId, number])\n @@index([projectId, columnId])\n @@index([projectId, status])\n @@index([parentId])\n @@index([projectId, doneAt])\n @@index([projectId, archived, archivedAt])\n @@index([projectId, status, archived])\n}\n\nmodel TaskMember {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n taskUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n taskId Int\n task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([taskId, userId])\n}\n\nmodel TaskTag {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n taskUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n taskId Int\n task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)\n name String @db.VarChar(20)\n\n @@index([taskId])\n}\n", "runtimeDataModel": { "models": {}, "enums": {}, @@ -32,10 +32,10 @@ const config: runtime.GetPrismaClientConfig = { } } -config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"habits\",\"kind\":\"object\",\"type\":\"Habit\",\"relationName\":\"HabitToUser\"},{\"name\":\"checkIns\",\"kind\":\"object\",\"type\":\"CheckIn\",\"relationName\":\"CheckInToUser\"},{\"name\":\"checkLists\",\"kind\":\"object\",\"type\":\"CheckList\",\"relationName\":\"CheckListToUser\"},{\"name\":\"templates\",\"kind\":\"object\",\"type\":\"CheckListTemplate\",\"relationName\":\"CheckListTemplateToUser\"},{\"name\":\"ownedProjects\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"ProjectOwner\"},{\"name\":\"projectMembers\",\"kind\":\"object\",\"type\":\"ProjectMember\",\"relationName\":\"ProjectMemberToUser\"},{\"name\":\"assignedTasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskAssignee\"},{\"name\":\"createdTasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskCreator\"},{\"name\":\"taskMembers\",\"kind\":\"object\",\"type\":\"TaskMember\",\"relationName\":\"TaskMemberToUser\"}],\"dbName\":null},\"Article\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"slug\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Strategy\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Indicator\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"groupName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"unit\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"frequency\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"hidden\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"points\",\"kind\":\"object\",\"type\":\"IndicatorPoint\",\"relationName\":\"IndicatorToIndicatorPoint\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"IndicatorPoint\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"indicatorId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"indicator\",\"kind\":\"object\",\"type\":\"Indicator\",\"relationName\":\"IndicatorToIndicatorPoint\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"News\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"summary\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"publishedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"impactRank\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Alert\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"indicatorName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"valueA\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"valueB\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"sourceA\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sourceB\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"diff\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"diffRatio\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServerNode\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"cpuName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"os\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"region\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memTotal\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"diskTotal\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"cpuCores\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"cpuUsage\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"ramUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"swapUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"load1\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"load5\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"load15\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"diskUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netIn\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netOut\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"uptime\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"process\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"connections\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"recordedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServerNodeHistory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"cpu\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"ram\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"load\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"disk\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netIn\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netOut\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"process\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"connections\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServiceCheck\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"latencyMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"message\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastCheckAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Econ_SowInventory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"month\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"inventory\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ScraperCache\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"scraperName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastDataHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastUpdateAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"metadata\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"OilPriceHistory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"province\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fuel\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"price\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeAccount\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastError\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeHome\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"homeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"devices\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeHome\"}],\"dbName\":null},\"MihomeDevice\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"did\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"homeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"home\",\"kind\":\"object\",\"type\":\"MihomeHome\",\"relationName\":\"MihomeDeviceToMihomeHome\"},{\"name\":\"model\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"alias\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"tempSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"tempPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"humSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"humPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battery\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"batteryAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"lastSyncAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"samples\",\"kind\":\"object\",\"type\":\"MihomeEnvSample\",\"relationName\":\"MihomeDeviceToMihomeEnvSample\"},{\"name\":\"hours\",\"kind\":\"object\",\"type\":\"MihomeEnvHour\",\"relationName\":\"MihomeDeviceToMihomeEnvHour\"}],\"dbName\":null},\"MihomeEnvSample\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"device\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeEnvSample\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"temperature\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidity\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureMin\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureMax\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityMin\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityMax\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"origin\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sampledAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"raw\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeEnvHour\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"device\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeEnvHour\"},{\"name\":\"hour\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"temperatureAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"raw\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Habit\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"HabitToUser\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"target\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"color\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"icon\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"records\",\"kind\":\"object\",\"type\":\"CheckIn\",\"relationName\":\"CheckInToHabit\"}],\"dbName\":null},\"CheckIn\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"habitId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"habit\",\"kind\":\"object\",\"type\":\"Habit\",\"relationName\":\"CheckInToHabit\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CheckInToUser\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"seq\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"CheckList\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CheckListToUser\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"scene\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"items\",\"kind\":\"object\",\"type\":\"CheckListItem\",\"relationName\":\"CheckListToCheckListItem\"}],\"dbName\":null},\"CheckListItem\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"listId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"list\",\"kind\":\"object\",\"type\":\"CheckList\",\"relationName\":\"CheckListToCheckListItem\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"done\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"doneAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"CheckListTemplate\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CheckListTemplateToUser\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"scene\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"items\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"builtin\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Project\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"code\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ownerId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ProjectOwner\"},{\"name\":\"isPersonal\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"notes\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"taskKinds\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"icon\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"autoArchive\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"members\",\"kind\":\"object\",\"type\":\"ProjectMember\",\"relationName\":\"ProjectToProjectMember\"},{\"name\":\"columns\",\"kind\":\"object\",\"type\":\"BoardColumn\",\"relationName\":\"BoardColumnToProject\"},{\"name\":\"tasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"ProjectToTask\"}],\"dbName\":null},\"ProjectMember\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"ProjectToProjectMember\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ProjectMemberToUser\"},{\"name\":\"permission\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"BoardColumn\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"BoardColumnToProject\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"boundStatus\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"retention\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"tasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"BoardColumnToTask\"}],\"dbName\":null},\"Task\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"columnUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"ProjectToTask\"},{\"name\":\"columnId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"column\",\"kind\":\"object\",\"type\":\"BoardColumn\",\"relationName\":\"BoardColumnToTask\"},{\"name\":\"parentId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parent\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"Subtask\"},{\"name\":\"children\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"Subtask\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"kind\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"markDate\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"doneAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"assigneeId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"assignee\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TaskAssignee\"},{\"name\":\"creatorId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"creator\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TaskCreator\"},{\"name\":\"extras\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"archivedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"archiveSource\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"members\",\"kind\":\"object\",\"type\":\"TaskMember\",\"relationName\":\"TaskToTaskMember\"},{\"name\":\"tags\",\"kind\":\"object\",\"type\":\"TaskTag\",\"relationName\":\"TaskToTaskTag\"},{\"name\":\"number\",\"kind\":\"scalar\",\"type\":\"Int\"}],\"dbName\":null},\"TaskMember\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"task\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskToTaskMember\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TaskMemberToUser\"}],\"dbName\":null},\"TaskTag\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"task\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskToTaskTag\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"habits\",\"kind\":\"object\",\"type\":\"Habit\",\"relationName\":\"HabitToUser\"},{\"name\":\"checkIns\",\"kind\":\"object\",\"type\":\"CheckIn\",\"relationName\":\"CheckInToUser\"},{\"name\":\"checkLists\",\"kind\":\"object\",\"type\":\"CheckList\",\"relationName\":\"CheckListToUser\"},{\"name\":\"templates\",\"kind\":\"object\",\"type\":\"CheckListTemplate\",\"relationName\":\"CheckListTemplateToUser\"},{\"name\":\"ownedProjects\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"ProjectOwner\"},{\"name\":\"projectMembers\",\"kind\":\"object\",\"type\":\"ProjectMember\",\"relationName\":\"ProjectMemberToUser\"},{\"name\":\"assignedTasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskAssignee\"},{\"name\":\"createdTasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskCreator\"},{\"name\":\"taskMembers\",\"kind\":\"object\",\"type\":\"TaskMember\",\"relationName\":\"TaskMemberToUser\"}],\"dbName\":null},\"Article\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"slug\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"News\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"summary\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"publishedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"impactRank\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Alert\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"indicatorName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"valueA\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"valueB\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"sourceA\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sourceB\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"diff\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"diffRatio\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServerNode\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"cpuName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"os\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"region\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memTotal\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"diskTotal\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"cpuCores\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"cpuUsage\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"ramUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"swapUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"load1\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"load5\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"load15\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"diskUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netIn\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netOut\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"uptime\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"process\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"connections\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"recordedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServerNodeHistory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"cpu\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"ram\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"load\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"disk\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netIn\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netOut\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"process\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"connections\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServiceCheck\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"latencyMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"message\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastCheckAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Econ_SowInventory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"month\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"inventory\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ScraperCache\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"scraperName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastDataHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastUpdateAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"metadata\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeAccount\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastError\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeHome\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"homeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"devices\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeHome\"}],\"dbName\":null},\"MihomeDevice\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"did\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"homeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"home\",\"kind\":\"object\",\"type\":\"MihomeHome\",\"relationName\":\"MihomeDeviceToMihomeHome\"},{\"name\":\"model\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"alias\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"tempSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"tempPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"humSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"humPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battery\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"batteryAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"lastSyncAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"samples\",\"kind\":\"object\",\"type\":\"MihomeEnvSample\",\"relationName\":\"MihomeDeviceToMihomeEnvSample\"},{\"name\":\"hours\",\"kind\":\"object\",\"type\":\"MihomeEnvHour\",\"relationName\":\"MihomeDeviceToMihomeEnvHour\"}],\"dbName\":null},\"MihomeEnvSample\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"device\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeEnvSample\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"temperature\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidity\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureMin\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureMax\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityMin\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityMax\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"origin\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sampledAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"raw\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeEnvHour\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"device\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeEnvHour\"},{\"name\":\"hour\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"temperatureAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"raw\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Habit\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"HabitToUser\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"target\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"color\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"icon\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"records\",\"kind\":\"object\",\"type\":\"CheckIn\",\"relationName\":\"CheckInToHabit\"}],\"dbName\":null},\"CheckIn\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"habitId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"habit\",\"kind\":\"object\",\"type\":\"Habit\",\"relationName\":\"CheckInToHabit\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CheckInToUser\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"seq\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"CheckList\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CheckListToUser\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"scene\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"items\",\"kind\":\"object\",\"type\":\"CheckListItem\",\"relationName\":\"CheckListToCheckListItem\"}],\"dbName\":null},\"CheckListItem\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"listId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"list\",\"kind\":\"object\",\"type\":\"CheckList\",\"relationName\":\"CheckListToCheckListItem\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"done\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"doneAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"CheckListTemplate\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CheckListTemplateToUser\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"scene\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"items\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"builtin\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Project\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"code\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ownerId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ProjectOwner\"},{\"name\":\"isPersonal\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"notes\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"taskKinds\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"icon\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"autoArchive\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"members\",\"kind\":\"object\",\"type\":\"ProjectMember\",\"relationName\":\"ProjectToProjectMember\"},{\"name\":\"columns\",\"kind\":\"object\",\"type\":\"BoardColumn\",\"relationName\":\"BoardColumnToProject\"},{\"name\":\"tasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"ProjectToTask\"}],\"dbName\":null},\"ProjectMember\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"ProjectToProjectMember\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ProjectMemberToUser\"},{\"name\":\"permission\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"BoardColumn\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"BoardColumnToProject\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"boundStatus\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"retention\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"tasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"BoardColumnToTask\"}],\"dbName\":null},\"Task\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"columnUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"ProjectToTask\"},{\"name\":\"columnId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"column\",\"kind\":\"object\",\"type\":\"BoardColumn\",\"relationName\":\"BoardColumnToTask\"},{\"name\":\"parentId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parent\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"Subtask\"},{\"name\":\"children\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"Subtask\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"kind\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"markDate\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"doneAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"assigneeId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"assignee\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TaskAssignee\"},{\"name\":\"creatorId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"creator\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TaskCreator\"},{\"name\":\"extras\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"archivedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"archiveSource\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"members\",\"kind\":\"object\",\"type\":\"TaskMember\",\"relationName\":\"TaskToTaskMember\"},{\"name\":\"tags\",\"kind\":\"object\",\"type\":\"TaskTag\",\"relationName\":\"TaskToTaskTag\"},{\"name\":\"number\",\"kind\":\"scalar\",\"type\":\"Int\"}],\"dbName\":null},\"TaskMember\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"task\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskToTaskMember\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TaskMemberToUser\"}],\"dbName\":null},\"TaskTag\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"task\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskToTaskTag\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") config.parameterizationSchema = { - strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"user\",\"habit\",\"records\",\"_count\",\"habits\",\"checkIns\",\"list\",\"items\",\"checkLists\",\"templates\",\"owner\",\"project\",\"members\",\"column\",\"parent\",\"children\",\"assignee\",\"creator\",\"task\",\"tags\",\"tasks\",\"columns\",\"ownedProjects\",\"projectMembers\",\"assignedTasks\",\"createdTasks\",\"taskMembers\",\"User.findUnique\",\"User.findUniqueOrThrow\",\"User.findFirst\",\"User.findFirstOrThrow\",\"User.findMany\",\"data\",\"User.createOne\",\"User.createMany\",\"User.createManyAndReturn\",\"User.updateOne\",\"User.updateMany\",\"User.updateManyAndReturn\",\"create\",\"update\",\"User.upsertOne\",\"User.deleteOne\",\"User.deleteMany\",\"having\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"User.groupBy\",\"User.aggregate\",\"Article.findUnique\",\"Article.findUniqueOrThrow\",\"Article.findFirst\",\"Article.findFirstOrThrow\",\"Article.findMany\",\"Article.createOne\",\"Article.createMany\",\"Article.createManyAndReturn\",\"Article.updateOne\",\"Article.updateMany\",\"Article.updateManyAndReturn\",\"Article.upsertOne\",\"Article.deleteOne\",\"Article.deleteMany\",\"Article.groupBy\",\"Article.aggregate\",\"Strategy.findUnique\",\"Strategy.findUniqueOrThrow\",\"Strategy.findFirst\",\"Strategy.findFirstOrThrow\",\"Strategy.findMany\",\"Strategy.createOne\",\"Strategy.createMany\",\"Strategy.createManyAndReturn\",\"Strategy.updateOne\",\"Strategy.updateMany\",\"Strategy.updateManyAndReturn\",\"Strategy.upsertOne\",\"Strategy.deleteOne\",\"Strategy.deleteMany\",\"Strategy.groupBy\",\"Strategy.aggregate\",\"indicator\",\"points\",\"Indicator.findUnique\",\"Indicator.findUniqueOrThrow\",\"Indicator.findFirst\",\"Indicator.findFirstOrThrow\",\"Indicator.findMany\",\"Indicator.createOne\",\"Indicator.createMany\",\"Indicator.createManyAndReturn\",\"Indicator.updateOne\",\"Indicator.updateMany\",\"Indicator.updateManyAndReturn\",\"Indicator.upsertOne\",\"Indicator.deleteOne\",\"Indicator.deleteMany\",\"Indicator.groupBy\",\"Indicator.aggregate\",\"IndicatorPoint.findUnique\",\"IndicatorPoint.findUniqueOrThrow\",\"IndicatorPoint.findFirst\",\"IndicatorPoint.findFirstOrThrow\",\"IndicatorPoint.findMany\",\"IndicatorPoint.createOne\",\"IndicatorPoint.createMany\",\"IndicatorPoint.createManyAndReturn\",\"IndicatorPoint.updateOne\",\"IndicatorPoint.updateMany\",\"IndicatorPoint.updateManyAndReturn\",\"IndicatorPoint.upsertOne\",\"IndicatorPoint.deleteOne\",\"IndicatorPoint.deleteMany\",\"IndicatorPoint.groupBy\",\"IndicatorPoint.aggregate\",\"News.findUnique\",\"News.findUniqueOrThrow\",\"News.findFirst\",\"News.findFirstOrThrow\",\"News.findMany\",\"News.createOne\",\"News.createMany\",\"News.createManyAndReturn\",\"News.updateOne\",\"News.updateMany\",\"News.updateManyAndReturn\",\"News.upsertOne\",\"News.deleteOne\",\"News.deleteMany\",\"News.groupBy\",\"News.aggregate\",\"Alert.findUnique\",\"Alert.findUniqueOrThrow\",\"Alert.findFirst\",\"Alert.findFirstOrThrow\",\"Alert.findMany\",\"Alert.createOne\",\"Alert.createMany\",\"Alert.createManyAndReturn\",\"Alert.updateOne\",\"Alert.updateMany\",\"Alert.updateManyAndReturn\",\"Alert.upsertOne\",\"Alert.deleteOne\",\"Alert.deleteMany\",\"Alert.groupBy\",\"Alert.aggregate\",\"ServerNode.findUnique\",\"ServerNode.findUniqueOrThrow\",\"ServerNode.findFirst\",\"ServerNode.findFirstOrThrow\",\"ServerNode.findMany\",\"ServerNode.createOne\",\"ServerNode.createMany\",\"ServerNode.createManyAndReturn\",\"ServerNode.updateOne\",\"ServerNode.updateMany\",\"ServerNode.updateManyAndReturn\",\"ServerNode.upsertOne\",\"ServerNode.deleteOne\",\"ServerNode.deleteMany\",\"ServerNode.groupBy\",\"ServerNode.aggregate\",\"ServerNodeHistory.findUnique\",\"ServerNodeHistory.findUniqueOrThrow\",\"ServerNodeHistory.findFirst\",\"ServerNodeHistory.findFirstOrThrow\",\"ServerNodeHistory.findMany\",\"ServerNodeHistory.createOne\",\"ServerNodeHistory.createMany\",\"ServerNodeHistory.createManyAndReturn\",\"ServerNodeHistory.updateOne\",\"ServerNodeHistory.updateMany\",\"ServerNodeHistory.updateManyAndReturn\",\"ServerNodeHistory.upsertOne\",\"ServerNodeHistory.deleteOne\",\"ServerNodeHistory.deleteMany\",\"ServerNodeHistory.groupBy\",\"ServerNodeHistory.aggregate\",\"ServiceCheck.findUnique\",\"ServiceCheck.findUniqueOrThrow\",\"ServiceCheck.findFirst\",\"ServiceCheck.findFirstOrThrow\",\"ServiceCheck.findMany\",\"ServiceCheck.createOne\",\"ServiceCheck.createMany\",\"ServiceCheck.createManyAndReturn\",\"ServiceCheck.updateOne\",\"ServiceCheck.updateMany\",\"ServiceCheck.updateManyAndReturn\",\"ServiceCheck.upsertOne\",\"ServiceCheck.deleteOne\",\"ServiceCheck.deleteMany\",\"ServiceCheck.groupBy\",\"ServiceCheck.aggregate\",\"Econ_SowInventory.findUnique\",\"Econ_SowInventory.findUniqueOrThrow\",\"Econ_SowInventory.findFirst\",\"Econ_SowInventory.findFirstOrThrow\",\"Econ_SowInventory.findMany\",\"Econ_SowInventory.createOne\",\"Econ_SowInventory.createMany\",\"Econ_SowInventory.createManyAndReturn\",\"Econ_SowInventory.updateOne\",\"Econ_SowInventory.updateMany\",\"Econ_SowInventory.updateManyAndReturn\",\"Econ_SowInventory.upsertOne\",\"Econ_SowInventory.deleteOne\",\"Econ_SowInventory.deleteMany\",\"Econ_SowInventory.groupBy\",\"Econ_SowInventory.aggregate\",\"ScraperCache.findUnique\",\"ScraperCache.findUniqueOrThrow\",\"ScraperCache.findFirst\",\"ScraperCache.findFirstOrThrow\",\"ScraperCache.findMany\",\"ScraperCache.createOne\",\"ScraperCache.createMany\",\"ScraperCache.createManyAndReturn\",\"ScraperCache.updateOne\",\"ScraperCache.updateMany\",\"ScraperCache.updateManyAndReturn\",\"ScraperCache.upsertOne\",\"ScraperCache.deleteOne\",\"ScraperCache.deleteMany\",\"ScraperCache.groupBy\",\"ScraperCache.aggregate\",\"OilPriceHistory.findUnique\",\"OilPriceHistory.findUniqueOrThrow\",\"OilPriceHistory.findFirst\",\"OilPriceHistory.findFirstOrThrow\",\"OilPriceHistory.findMany\",\"OilPriceHistory.createOne\",\"OilPriceHistory.createMany\",\"OilPriceHistory.createManyAndReturn\",\"OilPriceHistory.updateOne\",\"OilPriceHistory.updateMany\",\"OilPriceHistory.updateManyAndReturn\",\"OilPriceHistory.upsertOne\",\"OilPriceHistory.deleteOne\",\"OilPriceHistory.deleteMany\",\"OilPriceHistory.groupBy\",\"OilPriceHistory.aggregate\",\"MihomeAccount.findUnique\",\"MihomeAccount.findUniqueOrThrow\",\"MihomeAccount.findFirst\",\"MihomeAccount.findFirstOrThrow\",\"MihomeAccount.findMany\",\"MihomeAccount.createOne\",\"MihomeAccount.createMany\",\"MihomeAccount.createManyAndReturn\",\"MihomeAccount.updateOne\",\"MihomeAccount.updateMany\",\"MihomeAccount.updateManyAndReturn\",\"MihomeAccount.upsertOne\",\"MihomeAccount.deleteOne\",\"MihomeAccount.deleteMany\",\"MihomeAccount.groupBy\",\"MihomeAccount.aggregate\",\"home\",\"device\",\"samples\",\"hours\",\"devices\",\"MihomeHome.findUnique\",\"MihomeHome.findUniqueOrThrow\",\"MihomeHome.findFirst\",\"MihomeHome.findFirstOrThrow\",\"MihomeHome.findMany\",\"MihomeHome.createOne\",\"MihomeHome.createMany\",\"MihomeHome.createManyAndReturn\",\"MihomeHome.updateOne\",\"MihomeHome.updateMany\",\"MihomeHome.updateManyAndReturn\",\"MihomeHome.upsertOne\",\"MihomeHome.deleteOne\",\"MihomeHome.deleteMany\",\"MihomeHome.groupBy\",\"MihomeHome.aggregate\",\"MihomeDevice.findUnique\",\"MihomeDevice.findUniqueOrThrow\",\"MihomeDevice.findFirst\",\"MihomeDevice.findFirstOrThrow\",\"MihomeDevice.findMany\",\"MihomeDevice.createOne\",\"MihomeDevice.createMany\",\"MihomeDevice.createManyAndReturn\",\"MihomeDevice.updateOne\",\"MihomeDevice.updateMany\",\"MihomeDevice.updateManyAndReturn\",\"MihomeDevice.upsertOne\",\"MihomeDevice.deleteOne\",\"MihomeDevice.deleteMany\",\"MihomeDevice.groupBy\",\"MihomeDevice.aggregate\",\"MihomeEnvSample.findUnique\",\"MihomeEnvSample.findUniqueOrThrow\",\"MihomeEnvSample.findFirst\",\"MihomeEnvSample.findFirstOrThrow\",\"MihomeEnvSample.findMany\",\"MihomeEnvSample.createOne\",\"MihomeEnvSample.createMany\",\"MihomeEnvSample.createManyAndReturn\",\"MihomeEnvSample.updateOne\",\"MihomeEnvSample.updateMany\",\"MihomeEnvSample.updateManyAndReturn\",\"MihomeEnvSample.upsertOne\",\"MihomeEnvSample.deleteOne\",\"MihomeEnvSample.deleteMany\",\"MihomeEnvSample.groupBy\",\"MihomeEnvSample.aggregate\",\"MihomeEnvHour.findUnique\",\"MihomeEnvHour.findUniqueOrThrow\",\"MihomeEnvHour.findFirst\",\"MihomeEnvHour.findFirstOrThrow\",\"MihomeEnvHour.findMany\",\"MihomeEnvHour.createOne\",\"MihomeEnvHour.createMany\",\"MihomeEnvHour.createManyAndReturn\",\"MihomeEnvHour.updateOne\",\"MihomeEnvHour.updateMany\",\"MihomeEnvHour.updateManyAndReturn\",\"MihomeEnvHour.upsertOne\",\"MihomeEnvHour.deleteOne\",\"MihomeEnvHour.deleteMany\",\"MihomeEnvHour.groupBy\",\"MihomeEnvHour.aggregate\",\"Habit.findUnique\",\"Habit.findUniqueOrThrow\",\"Habit.findFirst\",\"Habit.findFirstOrThrow\",\"Habit.findMany\",\"Habit.createOne\",\"Habit.createMany\",\"Habit.createManyAndReturn\",\"Habit.updateOne\",\"Habit.updateMany\",\"Habit.updateManyAndReturn\",\"Habit.upsertOne\",\"Habit.deleteOne\",\"Habit.deleteMany\",\"Habit.groupBy\",\"Habit.aggregate\",\"CheckIn.findUnique\",\"CheckIn.findUniqueOrThrow\",\"CheckIn.findFirst\",\"CheckIn.findFirstOrThrow\",\"CheckIn.findMany\",\"CheckIn.createOne\",\"CheckIn.createMany\",\"CheckIn.createManyAndReturn\",\"CheckIn.updateOne\",\"CheckIn.updateMany\",\"CheckIn.updateManyAndReturn\",\"CheckIn.upsertOne\",\"CheckIn.deleteOne\",\"CheckIn.deleteMany\",\"CheckIn.groupBy\",\"CheckIn.aggregate\",\"CheckList.findUnique\",\"CheckList.findUniqueOrThrow\",\"CheckList.findFirst\",\"CheckList.findFirstOrThrow\",\"CheckList.findMany\",\"CheckList.createOne\",\"CheckList.createMany\",\"CheckList.createManyAndReturn\",\"CheckList.updateOne\",\"CheckList.updateMany\",\"CheckList.updateManyAndReturn\",\"CheckList.upsertOne\",\"CheckList.deleteOne\",\"CheckList.deleteMany\",\"CheckList.groupBy\",\"CheckList.aggregate\",\"CheckListItem.findUnique\",\"CheckListItem.findUniqueOrThrow\",\"CheckListItem.findFirst\",\"CheckListItem.findFirstOrThrow\",\"CheckListItem.findMany\",\"CheckListItem.createOne\",\"CheckListItem.createMany\",\"CheckListItem.createManyAndReturn\",\"CheckListItem.updateOne\",\"CheckListItem.updateMany\",\"CheckListItem.updateManyAndReturn\",\"CheckListItem.upsertOne\",\"CheckListItem.deleteOne\",\"CheckListItem.deleteMany\",\"CheckListItem.groupBy\",\"CheckListItem.aggregate\",\"CheckListTemplate.findUnique\",\"CheckListTemplate.findUniqueOrThrow\",\"CheckListTemplate.findFirst\",\"CheckListTemplate.findFirstOrThrow\",\"CheckListTemplate.findMany\",\"CheckListTemplate.createOne\",\"CheckListTemplate.createMany\",\"CheckListTemplate.createManyAndReturn\",\"CheckListTemplate.updateOne\",\"CheckListTemplate.updateMany\",\"CheckListTemplate.updateManyAndReturn\",\"CheckListTemplate.upsertOne\",\"CheckListTemplate.deleteOne\",\"CheckListTemplate.deleteMany\",\"CheckListTemplate.groupBy\",\"CheckListTemplate.aggregate\",\"Project.findUnique\",\"Project.findUniqueOrThrow\",\"Project.findFirst\",\"Project.findFirstOrThrow\",\"Project.findMany\",\"Project.createOne\",\"Project.createMany\",\"Project.createManyAndReturn\",\"Project.updateOne\",\"Project.updateMany\",\"Project.updateManyAndReturn\",\"Project.upsertOne\",\"Project.deleteOne\",\"Project.deleteMany\",\"Project.groupBy\",\"Project.aggregate\",\"ProjectMember.findUnique\",\"ProjectMember.findUniqueOrThrow\",\"ProjectMember.findFirst\",\"ProjectMember.findFirstOrThrow\",\"ProjectMember.findMany\",\"ProjectMember.createOne\",\"ProjectMember.createMany\",\"ProjectMember.createManyAndReturn\",\"ProjectMember.updateOne\",\"ProjectMember.updateMany\",\"ProjectMember.updateManyAndReturn\",\"ProjectMember.upsertOne\",\"ProjectMember.deleteOne\",\"ProjectMember.deleteMany\",\"ProjectMember.groupBy\",\"ProjectMember.aggregate\",\"BoardColumn.findUnique\",\"BoardColumn.findUniqueOrThrow\",\"BoardColumn.findFirst\",\"BoardColumn.findFirstOrThrow\",\"BoardColumn.findMany\",\"BoardColumn.createOne\",\"BoardColumn.createMany\",\"BoardColumn.createManyAndReturn\",\"BoardColumn.updateOne\",\"BoardColumn.updateMany\",\"BoardColumn.updateManyAndReturn\",\"BoardColumn.upsertOne\",\"BoardColumn.deleteOne\",\"BoardColumn.deleteMany\",\"BoardColumn.groupBy\",\"BoardColumn.aggregate\",\"Task.findUnique\",\"Task.findUniqueOrThrow\",\"Task.findFirst\",\"Task.findFirstOrThrow\",\"Task.findMany\",\"Task.createOne\",\"Task.createMany\",\"Task.createManyAndReturn\",\"Task.updateOne\",\"Task.updateMany\",\"Task.updateManyAndReturn\",\"Task.upsertOne\",\"Task.deleteOne\",\"Task.deleteMany\",\"Task.groupBy\",\"Task.aggregate\",\"TaskMember.findUnique\",\"TaskMember.findUniqueOrThrow\",\"TaskMember.findFirst\",\"TaskMember.findFirstOrThrow\",\"TaskMember.findMany\",\"TaskMember.createOne\",\"TaskMember.createMany\",\"TaskMember.createManyAndReturn\",\"TaskMember.updateOne\",\"TaskMember.updateMany\",\"TaskMember.updateManyAndReturn\",\"TaskMember.upsertOne\",\"TaskMember.deleteOne\",\"TaskMember.deleteMany\",\"TaskMember.groupBy\",\"TaskMember.aggregate\",\"TaskTag.findUnique\",\"TaskTag.findUniqueOrThrow\",\"TaskTag.findFirst\",\"TaskTag.findFirstOrThrow\",\"TaskTag.findMany\",\"TaskTag.createOne\",\"TaskTag.createMany\",\"TaskTag.createManyAndReturn\",\"TaskTag.updateOne\",\"TaskTag.updateMany\",\"TaskTag.updateManyAndReturn\",\"TaskTag.upsertOne\",\"TaskTag.deleteOne\",\"TaskTag.deleteMany\",\"TaskTag.groupBy\",\"TaskTag.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"uuid\",\"taskUuid\",\"taskId\",\"name\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"contains\",\"startsWith\",\"endsWith\",\"not\",\"userId\",\"projectUuid\",\"columnUuid\",\"parentUuid\",\"projectId\",\"columnId\",\"parentId\",\"title\",\"description\",\"kind\",\"markDate\",\"status\",\"doneAt\",\"assigneeId\",\"creatorId\",\"extras\",\"sortOrder\",\"archived\",\"archivedAt\",\"archiveSource\",\"createdAt\",\"updatedAt\",\"number\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"boundStatus\",\"retention\",\"permission\",\"code\",\"ownerId\",\"isPersonal\",\"notes\",\"taskKinds\",\"icon\",\"autoArchive\",\"scene\",\"builtin\",\"listId\",\"note\",\"done\",\"habitId\",\"date\",\"seq\",\"source\",\"period\",\"target\",\"color\",\"deviceId\",\"hour\",\"temperatureAvg\",\"humidityAvg\",\"raw\",\"temperature\",\"humidity\",\"temperatureMin\",\"temperatureMax\",\"humidityMin\",\"humidityMax\",\"origin\",\"sampledAt\",\"did\",\"homeId\",\"model\",\"alias\",\"tempSiid\",\"tempPiid\",\"humSiid\",\"humPiid\",\"battSiid\",\"battPiid\",\"battery\",\"batteryAt\",\"enabled\",\"lastSyncAt\",\"every\",\"some\",\"none\",\"deviceId_hour\",\"deviceId_date\",\"key\",\"lastError\",\"province\",\"fuel\",\"price\",\"date_province_fuel\",\"scraperName\",\"lastDataHash\",\"lastUpdateAt\",\"metadata\",\"month\",\"inventory\",\"url\",\"latencyMs\",\"message\",\"lastCheckAt\",\"cpu\",\"ram\",\"load\",\"disk\",\"netIn\",\"netOut\",\"process\",\"connections\",\"uuid_period\",\"cpuName\",\"os\",\"region\",\"memTotal\",\"diskTotal\",\"cpuCores\",\"cpuUsage\",\"ramUsed\",\"swapUsed\",\"load1\",\"load5\",\"load15\",\"diskUsed\",\"uptime\",\"recordedAt\",\"indicatorName\",\"valueA\",\"valueB\",\"sourceA\",\"sourceB\",\"diff\",\"diffRatio\",\"summary\",\"content\",\"publishedAt\",\"impactRank\",\"title_publishedAt\",\"indicatorId\",\"value\",\"groupName\",\"unit\",\"frequency\",\"hidden\",\"indicatorId_period\",\"slug\",\"email\",\"password\",\"taskId_userId\",\"projectId_number\",\"projectId_name\",\"projectId_userId\",\"habitId_date_seq\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), - graph: "mgysAtADEgcAAIoHACAIAACLBwAgCwAAjAcAIAwAAI0HACAZAACOBwAgGgAAhwcAIBsAAP4GACAcAAD-BgAgHQAAgAcAIP0DAACJBwAw_gMAABoAEP8DAACJBwAwgAQCAAAAAYQEAQC5BgAhpARAALoGACGlBEAAugYAIZ8FAQAAAAGgBQEAuQYAIQEAAAABACARAwAA-AYAIAUAAIsHACD9AwAAlwcAMP4DAAADABD_AwAAlwcAMIAEAgC4BgAhhAQBALkGACGQBAIAuAYAIaAEAgC4BgAhoQQgAMcGACGkBEAAugYAIaUEQAC6BgAhtQQBALkGACG6BAEAuQYAIcAEAQC5BgAhwQQCALgGACHCBAEAuQYAIQIDAACPCwAgBQAAhwsAIBEDAAD4BgAgBQAAiwcAIP0DAACXBwAw_gMAAAMAEP8DAACXBwAwgAQCAAAAAYQEAQC5BgAhkAQCALgGACGgBAIAuAYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbUEAQC5BgAhugQBALkGACHABAEAuQYAIcEEAgC4BgAhwgQBALkGACEDAAAAAwAgAQAABAAwAgAABQAgDQMAAPgGACAEAACWBwAg_QMAAJUHADD-AwAABwAQ_wMAAJUHADCABAIAuAYAIZAEAgC4BgAhpARAALoGACG6BAEAuQYAIbwEAgC4BgAhvQQBALkGACG-BAIAuAYAIb8EAQC5BgAhAgMAAI8LACAEAACWCwAgDgMAAPgGACAEAACWBwAg_QMAAJUHADD-AwAABwAQ_wMAAJUHADCABAIAAAABkAQCALgGACGkBEAAugYAIboEAQC5BgAhvAQCALgGACG9BAEAuQYAIb4EAgC4BgAhvwQBALkGACGlBQAAlAcAIAMAAAAHACABAAAIADACAAAJACABAAAABwAgAwAAAAcAIAEAAAgAMAIAAAkAIA0DAAD4BgAgCgAAkwcAIP0DAACSBwAw_gMAAA0AEP8DAACSBwAwgAQCALgGACGQBAIAuAYAIZcEAQC5BgAhoQQgAMcGACGkBEAAugYAIaUEQAC6BgAhtwQBALkGACG6BAEAuQYAIQIDAACPCwAgCgAAlQsAIA0DAAD4BgAgCgAAkwcAIP0DAACSBwAw_gMAAA0AEP8DAACSBwAwgAQCAAAAAZAEAgC4BgAhlwQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIboEAQC5BgAhAwAAAA0AIAEAAA4AMAIAAA8AIA0JAACRBwAg_QMAAJAHADD-AwAAEQAQ_wMAAJAHADCABAIAuAYAIZcEAQC5BgAhnARAAMMGACGgBAIAuAYAIaQEQAC6BgAhpQRAALoGACG5BAIAuAYAIboEAQC5BgAhuwQgAMcGACECCQAAlAsAIJwEAACYBwAgDQkAAJEHACD9AwAAkAcAMP4DAAARABD_AwAAkAcAMIAEAgAAAAGXBAEAuQYAIZwEQADDBgAhoAQCALgGACGkBEAAugYAIaUEQAC6BgAhuQQCALgGACG6BAEAuQYAIbsEIADHBgAhAwAAABEAIAEAABIAMAIAABMAIAEAAAARACAMAwAA_wYAIAoAAPAGACD9AwAAjwcAMP4DAAAWABD_AwAAjwcAMIAEAgC4BgAhhAQBALkGACGQBAIAxgYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIbgEIADHBgAhAgMAAI8LACCQBAAAmAcAIAwDAAD_BgAgCgAA8AYAIP0DAACPBwAw_gMAABYAEP8DAACPBwAwgAQCAAAAAYQEAQC5BgAhkAQCAMYGACGkBEAAugYAIaUEQAC6BgAhtwQBALkGACG4BCAAxwYAIQMAAAAWACABAAAXADACAAAYACASBwAAigcAIAgAAIsHACALAACMBwAgDAAAjQcAIBkAAI4HACAaAACHBwAgGwAA_gYAIBwAAP4GACAdAACABwAg_QMAAIkHADD-AwAAGgAQ_wMAAIkHADCABAIAuAYAIYQEAQC5BgAhpARAALoGACGlBEAAugYAIZ8FAQC5BgAhoAUBALkGACEBAAAAGgAgFA0AAPgGACAPAACHBwAgFwAA_gYAIBgAAIgHACD9AwAAhgcAMP4DAAAcABD_AwAAhgcAMIAEAgC4BgAhgQQBALkGACGEBAEAuQYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbAEAQC5BgAhsQQCALgGACGyBCAAxwYAIbMEAADwBgAgtAQAAL8GACC1BAEAxQYAIbYEAQC5BgAhBg0AAI8LACAPAACLCwAgFwAAjAsAIBgAAJMLACC0BAAAmAcAILUEAACYBwAgFA0AAPgGACAPAACHBwAgFwAA_gYAIBgAAIgHACD9AwAAhgcAMP4DAAAcABD_AwAAhgcAMIAEAgAAAAGBBAEAAAABhAQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACGwBAEAAAABsQQCALgGACGyBCAAxwYAIbMEAADwBgAgtAQAAL8GACC1BAEAxQYAIbYEAQC5BgAhAwAAABwAIAEAAB0AMAIAAB4AIAwDAAD4BgAgDgAA-wYAIP0DAACFBwAw_gMAACAAEP8DAACFBwAwgAQCALgGACGBBAEAuQYAIZAEAgC4BgAhkQQBAMUGACGUBAIAuAYAIaQEQAC6BgAhrwQBALkGACEDAwAAjwsAIA4AAJALACCRBAAAmAcAIA0DAAD4BgAgDgAA-wYAIP0DAACFBwAw_gMAACAAEP8DAACFBwAwgAQCAAAAAYEEAQAAAAGQBAIAuAYAIZEEAQDFBgAhlAQCALgGACGkBEAAugYAIa8EAQC5BgAhpAUAAIQHACADAAAAIAAgAQAAIQAwAgAAIgAgDw4AAPsGACAXAAD-BgAg_QMAAIMHADD-AwAAJAAQ_wMAAIMHADCABAIAuAYAIYEEAQC5BgAhhAQBALkGACGRBAEAxQYAIZQEAgC4BgAhoAQCALgGACGkBEAAugYAIaUEQAC6BgAhrQQBAMUGACGuBAEAuQYAIQQOAACQCwAgFwAAjAsAIJEEAACYBwAgrQQAAJgHACAQDgAA-wYAIBcAAP4GACD9AwAAgwcAMP4DAAAkABD_AwAAgwcAMIAEAgAAAAGBBAEAAAABhAQBALkGACGRBAEAxQYAIZQEAgC4BgAhoAQCALgGACGkBEAAugYAIaUEQAC6BgAhrQQBAMUGACGuBAEAuQYAIaMFAACCBwAgAwAAACQAIAEAACUAMAIAACYAICMOAAD7BgAgDwAAgAcAIBAAAPwGACARAAD9BgAgEgAA_gYAIBMAAP8GACAUAAD4BgAgFgAAgQcAIP0DAAD6BgAw_gMAACgAEP8DAAD6BgAwgAQCALgGACGBBAEAuQYAIZEEAQDFBgAhkgQBAMUGACGTBAEAxQYAIZQEAgC4BgAhlQQCALgGACGWBAIAxgYAIZcEAQC5BgAhmAQBALkGACGZBAEAxQYAIZoEQADDBgAhmwQBALkGACGcBEAAwwYAIZ0EAgDGBgAhngQCALgGACGfBAAA8AYAIKAEAgC4BgAhoQQgAMcGACGiBEAAwwYAIaMEAQDFBgAhpARAALoGACGlBEAAugYAIaYEAgDGBgAhEw4AAJALACAPAACNCwAgEAAAkQsAIBEAAI4LACASAACMCwAgEwAAjwsAIBQAAI8LACAWAACSCwAgkQQAAJgHACCSBAAAmAcAIJMEAACYBwAglgQAAJgHACCZBAAAmAcAIJoEAACYBwAgnAQAAJgHACCdBAAAmAcAIKIEAACYBwAgowQAAJgHACCmBAAAmAcAICQOAAD7BgAgDwAAgAcAIBAAAPwGACARAAD9BgAgEgAA_gYAIBMAAP8GACAUAAD4BgAgFgAAgQcAIP0DAAD6BgAw_gMAACgAEP8DAAD6BgAwgAQCAAAAAYEEAQAAAAGRBAEAxQYAIZIEAQDFBgAhkwQBAMUGACGUBAIAuAYAIZUEAgC4BgAhlgQCAMYGACGXBAEAuQYAIZgEAQC5BgAhmQQBAMUGACGaBEAAwwYAIZsEAQC5BgAhnARAAMMGACGdBAIAxgYAIZ4EAgC4BgAhnwQAAPAGACCgBAIAuAYAIaEEIADHBgAhogRAAMMGACGjBAEAxQYAIaQEQAC6BgAhpQRAALoGACGmBAIAxgYAIaIFAAD5BgAgAwAAACgAIAEAACkAMAIAACoAIAEAAAAoACADAAAAKAAgAQAAKQAwAgAAKgAgAQAAABoAIAoDAAD4BgAgFQAA9QYAIP0DAAD3BgAw_gMAAC8AEP8DAAD3BgAwgAQCALgGACGBBAEAuQYAIYIEAQDFBgAhgwQCALgGACGQBAIAuAYAIQMDAACPCwAgFQAAjgsAIIIEAACYBwAgCwMAAPgGACAVAAD1BgAg_QMAAPcGADD-AwAALwAQ_wMAAPcGADCABAIAAAABgQQBAAAAAYIEAQDFBgAhgwQCALgGACGQBAIAuAYAIaEFAAD2BgAgAwAAAC8AIAEAADAAMAIAADEAIAkVAAD1BgAg_QMAAPQGADD-AwAAMwAQ_wMAAPQGADCABAIAuAYAIYEEAQC5BgAhggQBAMUGACGDBAIAuAYAIYQEAQC5BgAhAhUAAI4LACCCBAAAmAcAIAkVAAD1BgAg_QMAAPQGADD-AwAAMwAQ_wMAAPQGADCABAIAAAABgQQBAAAAAYIEAQDFBgAhgwQCALgGACGEBAEAuQYAIQMAAAAzACABAAA0ADACAAA1ACABAAAAKAAgAQAAAC8AIAEAAAAzACABAAAAKAAgAwAAACgAIAEAACkAMAIAACoAIAEAAAAgACABAAAAJAAgAQAAACgAIAMAAAAgACABAAAhADACAAAiACADAAAAKAAgAQAAKQAwAgAAKgAgAwAAACgAIAEAACkAMAIAACoAIAMAAAAvACABAAAwADACAAAxACABAAAAAwAgAQAAAAcAIAEAAAANACABAAAAFgAgAQAAABwAIAEAAAAgACABAAAAKAAgAQAAACgAIAEAAAAvACABAAAAAQAgCQcAAIYLACAIAACHCwAgCwAAiAsAIAwAAIkLACAZAACKCwAgGgAAiwsAIBsAAIwLACAcAACMCwAgHQAAjQsAIAMAAAAaACABAABNADACAAABACADAAAAGgAgAQAATQAwAgAAAQAgAwAAABoAIAEAAE0AMAIAAAEAIA8HAAD9CgAgCAAA_goAIAsAAP8KACAMAACACwAgGQAAgQsAIBoAAIILACAbAACDCwAgHAAAhAsAIB0AAIULACCABAIAAAABhAQBAAAAAaQEQAAAAAGlBEAAAAABnwUBAAAAAaAFAQAAAAEBIwAAUQAgBoAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAGfBQEAAAABoAUBAAAAAQEjAABTADABIwAAUwAwDwcAAJcKACAIAACYCgAgCwAAmQoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQIAAAABACAjAABWACAGgAQCAKAHACGEBAEAngcAIaQEQACzBwAhpQRAALMHACGfBQEAngcAIaAFAQCeBwAhAgAAABoAICMAAFgAIAIAAAAaACAjAABYACADAAAAAQAgKgAAUQAgKwAAVgAgAQAAAAEAIAEAAAAaACAFBgAAkgoAIDAAAJMKACAxAACWCgAgMgAAlQoAIDMAAJQKACAJ_QMAAPMGADD-AwAAXwAQ_wMAAPMGADCABAIAjAYAIYQEAQCNBgAhpARAAJ0GACGlBEAAnQYAIZ8FAQCNBgAhoAUBAI0GACEDAAAAGgAgAQAAXgAwLwAAXwAgAwAAABoAIAEAAE0AMAIAAAEAIAr9AwAA8gYAMP4DAABlABD_AwAA8gYAMIAEAgAAAAGXBAEAuQYAIaQEQAC6BgAhpQRAALoGACG9BAEAuQYAIZMFAQC5BgAhngUBAAAAAQEAAABiACABAAAAYgAgCv0DAADyBgAw_gMAAGUAEP8DAADyBgAwgAQCALgGACGXBAEAuQYAIaQEQAC6BgAhpQRAALoGACG9BAEAuQYAIZMFAQC5BgAhngUBALkGACEAAwAAAGUAIAEAAGYAMAIAAGIAIAMAAABlACABAABmADACAABiACADAAAAZQAgAQAAZgAwAgAAYgAgB4AEAgAAAAGXBAEAAAABpARAAAAAAaUEQAAAAAG9BAEAAAABkwUBAAAAAZ4FAQAAAAEBIwAAagAgB4AEAgAAAAGXBAEAAAABpARAAAAAAaUEQAAAAAG9BAEAAAABkwUBAAAAAZ4FAQAAAAEBIwAAbAAwASMAAGwAMAeABAIAoAcAIZcEAQCeBwAhpARAALMHACGlBEAAswcAIb0EAQCeBwAhkwUBAJ4HACGeBQEAngcAIQIAAABiACAjAABvACAHgAQCAKAHACGXBAEAngcAIaQEQACzBwAhpQRAALMHACG9BAEAngcAIZMFAQCeBwAhngUBAJ4HACECAAAAZQAgIwAAcQAgAgAAAGUAICMAAHEAIAMAAABiACAqAABqACArAABvACABAAAAYgAgAQAAAGUAIAUGAACNCgAgMAAAjgoAIDEAAJEKACAyAACQCgAgMwAAjwoAIAr9AwAA8QYAMP4DAAB4ABD_AwAA8QYAMIAEAgCMBgAhlwQBAI0GACGkBEAAnQYAIaUEQACdBgAhvQQBAI0GACGTBQEAjQYAIZ4FAQCNBgAhAwAAAGUAIAEAAHcAMC8AAHgAIAMAAABlACABAABmADACAABiACAJIwAA8AYAIP0DAADvBgAw_gMAAH4AEP8DAADvBgAwgAQCAAAAAYQEAQC5BgAhmAQBALkGACGkBEAAugYAIaUEQAC6BgAhAQAAAHsAIAEAAAB7ACAJIwAA8AYAIP0DAADvBgAw_gMAAH4AEP8DAADvBgAwgAQCALgGACGEBAEAuQYAIZgEAQC5BgAhpARAALoGACGlBEAAugYAIQADAAAAfgAgAQAAfwAwAgAAewAgAwAAAH4AIAEAAH8AMAIAAHsAIAMAAAB-ACABAAB_ADACAAB7ACAGI4AAAAABgAQCAAAAAYQEAQAAAAGYBAEAAAABpARAAAAAAaUEQAAAAAEBIwAAgwEAIAYjgAAAAAGABAIAAAABhAQBAAAAAZgEAQAAAAGkBEAAAAABpQRAAAAAAQEjAACFAQAwASMAAIUBADAGI4AAAAABgAQCAKAHACGEBAEAngcAIZgEAQCeBwAhpARAALMHACGlBEAAswcAIQIAAAB7ACAjAACIAQAgBiOAAAAAAYAEAgCgBwAhhAQBAJ4HACGYBAEAngcAIaQEQACzBwAhpQRAALMHACECAAAAfgAgIwAAigEAIAIAAAB-ACAjAACKAQAgAwAAAHsAICoAAIMBACArAACIAQAgAQAAAHsAIAEAAAB-ACAFBgAAiAoAIDAAAIkKACAxAACMCgAgMgAAiwoAIDMAAIoKACAJIwAAmwYAIP0DAADuBgAw_gMAAJEBABD_AwAA7gYAMIAEAgCMBgAhhAQBAI0GACGYBAEAjQYAIaQEQACdBgAhpQRAAJ0GACEDAAAAfgAgAQAAkAEAMC8AAJEBACADAAAAfgAgAQAAfwAwAgAAewAgDlcAAOoGACD9AwAA6QYAMP4DAACcAQAQ_wMAAOkGADCABAIAAAABhAQBAAAAAZcEAQC5BgAhpARAALoGACGlBEAAugYAIb8EAQC5BgAhmQUBALkGACGaBQEAuQYAIZsFAQC5BgAhnAUgAMcGACEBAAAAlAEAIApWAADtBgAg_QMAAOwGADD-AwAAlgEAEP8DAADsBgAwgAQCALgGACGkBEAAugYAIaUEQAC6BgAhwAQBALkGACGXBQIAuAYAIZgFCADRBgAhAVYAAIcKACALVgAA7QYAIP0DAADsBgAw_gMAAJYBABD_AwAA7AYAMIAEAgAAAAGkBEAAugYAIaUEQAC6BgAhwAQBALkGACGXBQIAuAYAIZgFCADRBgAhnQUAAOsGACADAAAAlgEAIAEAAJcBADACAACYAQAgAQAAAJYBACABAAAAlAEAIA5XAADqBgAg_QMAAOkGADD-AwAAnAEAEP8DAADpBgAwgAQCALgGACGEBAEAuQYAIZcEAQC5BgAhpARAALoGACGlBEAAugYAIb8EAQC5BgAhmQUBALkGACGaBQEAuQYAIZsFAQC5BgAhnAUgAMcGACEBVwAAhgoAIAMAAACcAQAgAQAAnQEAMAIAAJQBACADAAAAnAEAIAEAAJ0BADACAACUAQAgAwAAAJwBACABAACdAQAwAgAAlAEAIAtXAACFCgAggAQCAAAAAYQEAQAAAAGXBAEAAAABpARAAAAAAaUEQAAAAAG_BAEAAAABmQUBAAAAAZoFAQAAAAGbBQEAAAABnAUgAAAAAQEjAAChAQAgCoAEAgAAAAGEBAEAAAABlwQBAAAAAaQEQAAAAAGlBEAAAAABvwQBAAAAAZkFAQAAAAGaBQEAAAABmwUBAAAAAZwFIAAAAAEBIwAAowEAMAEjAACjAQAwC1cAAPgJACCABAIAoAcAIYQEAQCeBwAhlwQBAJ4HACGkBEAAswcAIaUEQACzBwAhvwQBAJ4HACGZBQEAngcAIZoFAQCeBwAhmwUBAJ4HACGcBSAAsgcAIQIAAACUAQAgIwAApgEAIAqABAIAoAcAIYQEAQCeBwAhlwQBAJ4HACGkBEAAswcAIaUEQACzBwAhvwQBAJ4HACGZBQEAngcAIZoFAQCeBwAhmwUBAJ4HACGcBSAAsgcAIQIAAACcAQAgIwAAqAEAIAIAAACcAQAgIwAAqAEAIAMAAACUAQAgKgAAoQEAICsAAKYBACABAAAAlAEAIAEAAACcAQAgBQYAAPMJACAwAAD0CQAgMQAA9wkAIDIAAPYJACAzAAD1CQAgDf0DAADoBgAw_gMAAK8BABD_AwAA6AYAMIAEAgCMBgAhhAQBAI0GACGXBAEAjQYAIaQEQACdBgAhpQRAAJ0GACG_BAEAjQYAIZkFAQCNBgAhmgUBAI0GACGbBQEAjQYAIZwFIACcBgAhAwAAAJwBACABAACuAQAwLwAArwEAIAMAAACcAQAgAQAAnQEAMAIAAJQBACABAAAAmAEAIAEAAACYAQAgAwAAAJYBACABAACXAQAwAgAAmAEAIAMAAACWAQAgAQAAlwEAMAIAAJgBACADAAAAlgEAIAEAAJcBADACAACYAQAgB1YAAPIJACCABAIAAAABpARAAAAAAaUEQAAAAAHABAEAAAABlwUCAAAAAZgFCAAAAAEBIwAAtwEAIAaABAIAAAABpARAAAAAAaUEQAAAAAHABAEAAAABlwUCAAAAAZgFCAAAAAEBIwAAuQEAMAEjAAC5AQAwB1YAAPEJACCABAIAoAcAIaQEQACzBwAhpQRAALMHACHABAEAngcAIZcFAgCgBwAhmAUIAMcJACECAAAAmAEAICMAALwBACAGgAQCAKAHACGkBEAAswcAIaUEQACzBwAhwAQBAJ4HACGXBQIAoAcAIZgFCADHCQAhAgAAAJYBACAjAAC-AQAgAgAAAJYBACAjAAC-AQAgAwAAAJgBACAqAAC3AQAgKwAAvAEAIAEAAACYAQAgAQAAAJYBACAFBgAA7AkAIDAAAO0JACAxAADwCQAgMgAA7wkAIDMAAO4JACAJ_QMAAOcGADD-AwAAxQEAEP8DAADnBgAwgAQCAIwGACGkBEAAnQYAIaUEQACdBgAhwAQBAI0GACGXBQIAjAYAIZgFCADOBgAhAwAAAJYBACABAADEAQAwLwAAxQEAIAMAAACWAQAgAQAAlwEAMAIAAJgBACAN_QMAAOUGADD-AwAAywEAEP8DAADlBgAwgAQCAAAAAZcEAQC5BgAhpARAALoGACG_BAEAuQYAIe8EAQC5BgAhkgUBALkGACGTBQEAxQYAIZQFQAC6BgAhlQUCALgGACGWBQAA5gYAIAEAAADIAQAgAQAAAMgBACAM_QMAAOUGADD-AwAAywEAEP8DAADlBgAwgAQCALgGACGXBAEAuQYAIaQEQAC6BgAhvwQBALkGACHvBAEAuQYAIZIFAQC5BgAhkwUBAMUGACGUBUAAugYAIZUFAgC4BgAhAZMFAACYBwAgAwAAAMsBACABAADMAQAwAgAAyAEAIAMAAADLAQAgAQAAzAEAMAIAAMgBACADAAAAywEAIAEAAMwBADACAADIAQAgCYAEAgAAAAGXBAEAAAABpARAAAAAAb8EAQAAAAHvBAEAAAABkgUBAAAAAZMFAQAAAAGUBUAAAAABlQUCAAAAAQEjAADQAQAgCYAEAgAAAAGXBAEAAAABpARAAAAAAb8EAQAAAAHvBAEAAAABkgUBAAAAAZMFAQAAAAGUBUAAAAABlQUCAAAAAQEjAADSAQAwASMAANIBADAJgAQCAKAHACGXBAEAngcAIaQEQACzBwAhvwQBAJ4HACHvBAEAngcAIZIFAQCeBwAhkwUBAJ8HACGUBUAAswcAIZUFAgCgBwAhAgAAAMgBACAjAADVAQAgCYAEAgCgBwAhlwQBAJ4HACGkBEAAswcAIb8EAQCeBwAh7wQBAJ4HACGSBQEAngcAIZMFAQCfBwAhlAVAALMHACGVBQIAoAcAIQIAAADLAQAgIwAA1wEAIAIAAADLAQAgIwAA1wEAIAMAAADIAQAgKgAA0AEAICsAANUBACABAAAAyAEAIAEAAADLAQAgBgYAAOcJACAwAADoCQAgMQAA6wkAIDIAAOoJACAzAADpCQAgkwUAAJgHACAM_QMAAOQGADD-AwAA3gEAEP8DAADkBgAwgAQCAIwGACGXBAEAjQYAIaQEQACdBgAhvwQBAI0GACHvBAEAjQYAIZIFAQCNBgAhkwUBAI4GACGUBUAAnQYAIZUFAgCMBgAhAwAAAMsBACABAADdAQAwLwAA3gEAIAMAAADLAQAgAQAAzAEAMAIAAMgBACAN_QMAAOMGADD-AwAA5AEAEP8DAADjBgAwgAQCAAAAAaQEQAC6BgAhwAQBALkGACGLBQEAuQYAIYwFCADRBgAhjQUIANEGACGOBQEAuQYAIY8FAQC5BgAhkAUIANEGACGRBQgA0QYAIQEAAADhAQAgAQAAAOEBACAN_QMAAOMGADD-AwAA5AEAEP8DAADjBgAwgAQCALgGACGkBEAAugYAIcAEAQC5BgAhiwUBALkGACGMBQgA0QYAIY0FCADRBgAhjgUBALkGACGPBQEAuQYAIZAFCADRBgAhkQUIANEGACEAAwAAAOQBACABAADlAQAwAgAA4QEAIAMAAADkAQAgAQAA5QEAMAIAAOEBACADAAAA5AEAIAEAAOUBADACAADhAQAgCoAEAgAAAAGkBEAAAAABwAQBAAAAAYsFAQAAAAGMBQgAAAABjQUIAAAAAY4FAQAAAAGPBQEAAAABkAUIAAAAAZEFCAAAAAEBIwAA6QEAIAqABAIAAAABpARAAAAAAcAEAQAAAAGLBQEAAAABjAUIAAAAAY0FCAAAAAGOBQEAAAABjwUBAAAAAZAFCAAAAAGRBQgAAAABASMAAOsBADABIwAA6wEAMAqABAIAoAcAIaQEQACzBwAhwAQBAJ4HACGLBQEAngcAIYwFCADHCQAhjQUIAMcJACGOBQEAngcAIY8FAQCeBwAhkAUIAMcJACGRBQgAxwkAIQIAAADhAQAgIwAA7gEAIAqABAIAoAcAIaQEQACzBwAhwAQBAJ4HACGLBQEAngcAIYwFCADHCQAhjQUIAMcJACGOBQEAngcAIY8FAQCeBwAhkAUIAMcJACGRBQgAxwkAIQIAAADkAQAgIwAA8AEAIAIAAADkAQAgIwAA8AEAIAMAAADhAQAgKgAA6QEAICsAAO4BACABAAAA4QEAIAEAAADkAQAgBQYAAOIJACAwAADjCQAgMQAA5gkAIDIAAOUJACAzAADkCQAgDf0DAADiBgAw_gMAAPcBABD_AwAA4gYAMIAEAgCMBgAhpARAAJ0GACHABAEAjQYAIYsFAQCNBgAhjAUIAM4GACGNBQgAzgYAIY4FAQCNBgAhjwUBAI0GACGQBQgAzgYAIZEFCADOBgAhAwAAAOQBACABAAD2AQAwLwAA9wEAIAMAAADkAQAgAQAA5QEAMAIAAOEBACAc_QMAAOEGADD-AwAA_QEAEP8DAADhBgAwgAQCAAAAAYEEAQAAAAGEBAEAuQYAIZsEAQC5BgAhpARAALoGACGlBEAAugYAIfcEBADeBgAh-AQEAN4GACH5BAIAuAYAIfoEAgC4BgAh_AQBALkGACH9BAEAuQYAIf4EAQC5BgAh_wQEAN4GACGABQQA3gYAIYEFAgC4BgAhggUIANEGACGDBQQA3gYAIYQFBADeBgAhhQUIANEGACGGBQgA0QYAIYcFCADRBgAhiAUEAN4GACGJBQQA3gYAIYoFQAC6BgAhAQAAAPoBACABAAAA-gEAIBz9AwAA4QYAMP4DAAD9AQAQ_wMAAOEGADCABAIAuAYAIYEEAQC5BgAhhAQBALkGACGbBAEAuQYAIaQEQAC6BgAhpQRAALoGACH3BAQA3gYAIfgEBADeBgAh-QQCALgGACH6BAIAuAYAIfwEAQC5BgAh_QQBALkGACH-BAEAuQYAIf8EBADeBgAhgAUEAN4GACGBBQIAuAYAIYIFCADRBgAhgwUEAN4GACGEBQQA3gYAIYUFCADRBgAhhgUIANEGACGHBQgA0QYAIYgFBADeBgAhiQUEAN4GACGKBUAAugYAIQADAAAA_QEAIAEAAP4BADACAAD6AQAgAwAAAP0BACABAAD-AQAwAgAA-gEAIAMAAAD9AQAgAQAA_gEAMAIAAPoBACAZgAQCAAAAAYEEAQAAAAGEBAEAAAABmwQBAAAAAaQEQAAAAAGlBEAAAAAB9wQEAAAAAfgEBAAAAAH5BAIAAAAB-gQCAAAAAfwEAQAAAAH9BAEAAAAB_gQBAAAAAf8EBAAAAAGABQQAAAABgQUCAAAAAYIFCAAAAAGDBQQAAAABhAUEAAAAAYUFCAAAAAGGBQgAAAABhwUIAAAAAYgFBAAAAAGJBQQAAAABigVAAAAAAQEjAACCAgAgGYAEAgAAAAGBBAEAAAABhAQBAAAAAZsEAQAAAAGkBEAAAAABpQRAAAAAAfcEBAAAAAH4BAQAAAAB-QQCAAAAAfoEAgAAAAH8BAEAAAAB_QQBAAAAAf4EAQAAAAH_BAQAAAABgAUEAAAAAYEFAgAAAAGCBQgAAAABgwUEAAAAAYQFBAAAAAGFBQgAAAABhgUIAAAAAYcFCAAAAAGIBQQAAAABiQUEAAAAAYoFQAAAAAEBIwAAhAIAMAEjAACEAgAwGYAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIZsEAQCeBwAhpARAALMHACGlBEAAswcAIfcEBADcCQAh-AQEANwJACH5BAIAoAcAIfoEAgCgBwAh_AQBAJ4HACH9BAEAngcAIf4EAQCeBwAh_wQEANwJACGABQQA3AkAIYEFAgCgBwAhggUIAMcJACGDBQQA3AkAIYQFBADcCQAhhQUIAMcJACGGBQgAxwkAIYcFCADHCQAhiAUEANwJACGJBQQA3AkAIYoFQACzBwAhAgAAAPoBACAjAACHAgAgGYAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIZsEAQCeBwAhpARAALMHACGlBEAAswcAIfcEBADcCQAh-AQEANwJACH5BAIAoAcAIfoEAgCgBwAh_AQBAJ4HACH9BAEAngcAIf4EAQCeBwAh_wQEANwJACGABQQA3AkAIYEFAgCgBwAhggUIAMcJACGDBQQA3AkAIYQFBADcCQAhhQUIAMcJACGGBQgAxwkAIYcFCADHCQAhiAUEANwJACGJBQQA3AkAIYoFQACzBwAhAgAAAP0BACAjAACJAgAgAgAAAP0BACAjAACJAgAgAwAAAPoBACAqAACCAgAgKwAAhwIAIAEAAAD6AQAgAQAAAP0BACAFBgAA3QkAIDAAAN4JACAxAADhCQAgMgAA4AkAIDMAAN8JACAc_QMAAOAGADD-AwAAkAIAEP8DAADgBgAwgAQCAIwGACGBBAEAjQYAIYQEAQCNBgAhmwQBAI0GACGkBEAAnQYAIaUEQACdBgAh9wQEANoGACH4BAQA2gYAIfkEAgCMBgAh-gQCAIwGACH8BAEAjQYAIf0EAQCNBgAh_gQBAI0GACH_BAQA2gYAIYAFBADaBgAhgQUCAIwGACGCBQgAzgYAIYMFBADaBgAhhAUEANoGACGFBQgAzgYAIYYFCADOBgAhhwUIAM4GACGIBQQA2gYAIYkFBADaBgAhigVAAJ0GACEDAAAA_QEAIAEAAI8CADAvAACQAgAgAwAAAP0BACABAAD-AQAwAgAA-gEAIBD9AwAA3QYAMP4DAACWAgAQ_wMAAN0GADCABAIAAAABgQQBALkGACGkBEAAugYAIcAEQAC6BgAh8wQIANEGACH0BAQA3gYAIfUECADRBgAh9gQEAN4GACH3BAQA3gYAIfgEBADeBgAh-QQCALgGACH6BAIAuAYAIfsEAADfBgAgAQAAAJMCACABAAAAkwIAIA_9AwAA3QYAMP4DAACWAgAQ_wMAAN0GADCABAIAuAYAIYEEAQC5BgAhpARAALoGACHABEAAugYAIfMECADRBgAh9AQEAN4GACH1BAgA0QYAIfYEBADeBgAh9wQEAN4GACH4BAQA3gYAIfkEAgC4BgAh-gQCALgGACEAAwAAAJYCACABAACXAgAwAgAAkwIAIAMAAACWAgAgAQAAlwIAMAIAAJMCACADAAAAlgIAIAEAAJcCADACAACTAgAgDIAEAgAAAAGBBAEAAAABpARAAAAAAcAEQAAAAAHzBAgAAAAB9AQEAAAAAfUECAAAAAH2BAQAAAAB9wQEAAAAAfgEBAAAAAH5BAIAAAAB-gQCAAAAAQEjAACbAgAgDIAEAgAAAAGBBAEAAAABpARAAAAAAcAEQAAAAAHzBAgAAAAB9AQEAAAAAfUECAAAAAH2BAQAAAAB9wQEAAAAAfgEBAAAAAH5BAIAAAAB-gQCAAAAAQEjAACdAgAwASMAAJ0CADAMgAQCAKAHACGBBAEAngcAIaQEQACzBwAhwARAALMHACHzBAgAxwkAIfQEBADcCQAh9QQIAMcJACH2BAQA3AkAIfcEBADcCQAh-AQEANwJACH5BAIAoAcAIfoEAgCgBwAhAgAAAJMCACAjAACgAgAgDIAEAgCgBwAhgQQBAJ4HACGkBEAAswcAIcAEQACzBwAh8wQIAMcJACH0BAQA3AkAIfUECADHCQAh9gQEANwJACH3BAQA3AkAIfgEBADcCQAh-QQCAKAHACH6BAIAoAcAIQIAAACWAgAgIwAAogIAIAIAAACWAgAgIwAAogIAIAMAAACTAgAgKgAAmwIAICsAAKACACABAAAAkwIAIAEAAACWAgAgBQYAANcJACAwAADYCQAgMQAA2wkAIDIAANoJACAzAADZCQAgD_0DAADZBgAw_gMAAKkCABD_AwAA2QYAMIAEAgCMBgAhgQQBAI0GACGkBEAAnQYAIcAEQACdBgAh8wQIAM4GACH0BAQA2gYAIfUECADOBgAh9gQEANoGACH3BAQA2gYAIfgEBADaBgAh-QQCAIwGACH6BAIAjAYAIQMAAACWAgAgAQAAqAIAMC8AAKkCACADAAAAlgIAIAEAAJcCADACAACTAgAgDP0DAADYBgAw_gMAAK8CABD_AwAA2AYAMIAEAgAAAAGEBAEAAAABmwQBALkGACGkBEAAugYAIaUEQAC6BgAh7wQBALkGACHwBAIAuAYAIfEEAQC5BgAh8gRAALoGACEBAAAArAIAIAEAAACsAgAgDP0DAADYBgAw_gMAAK8CABD_AwAA2AYAMIAEAgC4BgAhhAQBALkGACGbBAEAuQYAIaQEQAC6BgAhpQRAALoGACHvBAEAuQYAIfAEAgC4BgAh8QQBALkGACHyBEAAugYAIQADAAAArwIAIAEAALACADACAACsAgAgAwAAAK8CACABAACwAgAwAgAArAIAIAMAAACvAgAgAQAAsAIAMAIAAKwCACAJgAQCAAAAAYQEAQAAAAGbBAEAAAABpARAAAAAAaUEQAAAAAHvBAEAAAAB8AQCAAAAAfEEAQAAAAHyBEAAAAABASMAALQCACAJgAQCAAAAAYQEAQAAAAGbBAEAAAABpARAAAAAAaUEQAAAAAHvBAEAAAAB8AQCAAAAAfEEAQAAAAHyBEAAAAABASMAALYCADABIwAAtgIAMAmABAIAoAcAIYQEAQCeBwAhmwQBAJ4HACGkBEAAswcAIaUEQACzBwAh7wQBAJ4HACHwBAIAoAcAIfEEAQCeBwAh8gRAALMHACECAAAArAIAICMAALkCACAJgAQCAKAHACGEBAEAngcAIZsEAQCeBwAhpARAALMHACGlBEAAswcAIe8EAQCeBwAh8AQCAKAHACHxBAEAngcAIfIEQACzBwAhAgAAAK8CACAjAAC7AgAgAgAAAK8CACAjAAC7AgAgAwAAAKwCACAqAAC0AgAgKwAAuQIAIAEAAACsAgAgAQAAAK8CACAFBgAA0gkAIDAAANMJACAxAADWCQAgMgAA1QkAIDMAANQJACAM_QMAANcGADD-AwAAwgIAEP8DAADXBgAwgAQCAIwGACGEBAEAjQYAIZsEAQCNBgAhpARAAJ0GACGlBEAAnQYAIe8EAQCNBgAh8AQCAIwGACHxBAEAjQYAIfIEQACdBgAhAwAAAK8CACABAADBAgAwLwAAwgIAIAMAAACvAgAgAQAAsAIAMAIAAKwCACAI_QMAANYGADD-AwAAyAIAEP8DAADWBgAwgAQCAAAAAaQEQAC6BgAhpQRAALoGACHtBAEAAAAB7gQCALgGACEBAAAAxQIAIAEAAADFAgAgCP0DAADWBgAw_gMAAMgCABD_AwAA1gYAMIAEAgC4BgAhpARAALoGACGlBEAAugYAIe0EAQC5BgAh7gQCALgGACEAAwAAAMgCACABAADJAgAwAgAAxQIAIAMAAADIAgAgAQAAyQIAMAIAAMUCACADAAAAyAIAIAEAAMkCADACAADFAgAgBYAEAgAAAAGkBEAAAAABpQRAAAAAAe0EAQAAAAHuBAIAAAABASMAAM0CACAFgAQCAAAAAaQEQAAAAAGlBEAAAAAB7QQBAAAAAe4EAgAAAAEBIwAAzwIAMAEjAADPAgAwBYAEAgCgBwAhpARAALMHACGlBEAAswcAIe0EAQCeBwAh7gQCAKAHACECAAAAxQIAICMAANICACAFgAQCAKAHACGkBEAAswcAIaUEQACzBwAh7QQBAJ4HACHuBAIAoAcAIQIAAADIAgAgIwAA1AIAIAIAAADIAgAgIwAA1AIAIAMAAADFAgAgKgAAzQIAICsAANICACABAAAAxQIAIAEAAADIAgAgBQYAAM0JACAwAADOCQAgMQAA0QkAIDIAANAJACAzAADPCQAgCP0DAADVBgAw_gMAANsCABD_AwAA1QYAMIAEAgCMBgAhpARAAJ0GACGlBEAAnQYAIe0EAQCNBgAh7gQCAIwGACEDAAAAyAIAIAEAANoCADAvAADbAgAgAwAAAMgCACABAADJAgAwAgAAxQIAIAr9AwAA1AYAMP4DAADhAgAQ_wMAANQGADCABAIAAAABpARAALoGACGlBEAAugYAIekEAQAAAAHqBAEAxQYAIesEQAC6BgAh7AQAAL8GACABAAAA3gIAIAEAAADeAgAgCv0DAADUBgAw_gMAAOECABD_AwAA1AYAMIAEAgC4BgAhpARAALoGACGlBEAAugYAIekEAQC5BgAh6gQBAMUGACHrBEAAugYAIewEAAC_BgAgAuoEAACYBwAg7AQAAJgHACADAAAA4QIAIAEAAOICADACAADeAgAgAwAAAOECACABAADiAgAwAgAA3gIAIAMAAADhAgAgAQAA4gIAMAIAAN4CACAHgAQCAAAAAaQEQAAAAAGlBEAAAAAB6QQBAAAAAeoEAQAAAAHrBEAAAAAB7ASAAAAAAQEjAADmAgAgB4AEAgAAAAGkBEAAAAABpQRAAAAAAekEAQAAAAHqBAEAAAAB6wRAAAAAAewEgAAAAAEBIwAA6AIAMAEjAADoAgAwB4AEAgCgBwAhpARAALMHACGlBEAAswcAIekEAQCeBwAh6gQBAJ8HACHrBEAAswcAIewEgAAAAAECAAAA3gIAICMAAOsCACAHgAQCAKAHACGkBEAAswcAIaUEQACzBwAh6QQBAJ4HACHqBAEAnwcAIesEQACzBwAh7ASAAAAAAQIAAADhAgAgIwAA7QIAIAIAAADhAgAgIwAA7QIAIAMAAADeAgAgKgAA5gIAICsAAOsCACABAAAA3gIAIAEAAADhAgAgBwYAAMgJACAwAADJCQAgMQAAzAkAIDIAAMsJACAzAADKCQAg6gQAAJgHACDsBAAAmAcAIAr9AwAA0wYAMP4DAAD0AgAQ_wMAANMGADCABAIAjAYAIaQEQACdBgAhpQRAAJ0GACHpBAEAjQYAIeoEAQCOBgAh6wRAAJ0GACHsBAAAqgYAIAMAAADhAgAgAQAA8wIAMC8AAPQCACADAAAA4QIAIAEAAOICADACAADeAgAgC_0DAADQBgAw_gMAAPoCABD_AwAA0AYAMIAEAgAAAAGkBEAAugYAIb0EQAC6BgAhvwQBALkGACHlBAEAuQYAIeYEAQC5BgAh5wQIANEGACHoBAAA0gYAIAEAAAD3AgAgAQAAAPcCACAK_QMAANAGADD-AwAA-gIAEP8DAADQBgAwgAQCALgGACGkBEAAugYAIb0EQAC6BgAhvwQBALkGACHlBAEAuQYAIeYEAQC5BgAh5wQIANEGACEAAwAAAPoCACABAAD7AgAwAgAA9wIAIAMAAAD6AgAgAQAA-wIAMAIAAPcCACADAAAA-gIAIAEAAPsCADACAAD3AgAgB4AEAgAAAAGkBEAAAAABvQRAAAAAAb8EAQAAAAHlBAEAAAAB5gQBAAAAAecECAAAAAEBIwAA_wIAIAeABAIAAAABpARAAAAAAb0EQAAAAAG_BAEAAAAB5QQBAAAAAeYEAQAAAAHnBAgAAAABASMAAIEDADABIwAAgQMAMAeABAIAoAcAIaQEQACzBwAhvQRAALMHACG_BAEAngcAIeUEAQCeBwAh5gQBAJ4HACHnBAgAxwkAIQIAAAD3AgAgIwAAhAMAIAeABAIAoAcAIaQEQACzBwAhvQRAALMHACG_BAEAngcAIeUEAQCeBwAh5gQBAJ4HACHnBAgAxwkAIQIAAAD6AgAgIwAAhgMAIAIAAAD6AgAgIwAAhgMAIAMAAAD3AgAgKgAA_wIAICsAAIQDACABAAAA9wIAIAEAAAD6AgAgBQYAAMIJACAwAADDCQAgMQAAxgkAIDIAAMUJACAzAADECQAgCv0DAADNBgAw_gMAAI0DABD_AwAAzQYAMIAEAgCMBgAhpARAAJ0GACG9BEAAnQYAIb8EAQCNBgAh5QQBAI0GACHmBAEAjQYAIecECADOBgAhAwAAAPoCACABAACMAwAwLwAAjQMAIAMAAAD6AgAgAQAA-wIAMAIAAPcCACAJ_QMAAMwGADD-AwAAkwMAEP8DAADMBgAwgAQCAAAAAZAEAQC5BgAhmwQBALkGACGlBEAAugYAIeMEAQAAAAHkBAEAuQYAIQEAAACQAwAgAQAAAJADACAJ_QMAAMwGADD-AwAAkwMAEP8DAADMBgAwgAQCALgGACGQBAEAuQYAIZsEAQC5BgAhpQRAALoGACHjBAEAuQYAIeQEAQC5BgAhAAMAAACTAwAgAQAAlAMAMAIAAJADACADAAAAkwMAIAEAAJQDADACAACQAwAgAwAAAJMDACABAACUAwAwAgAAkAMAIAaABAIAAAABkAQBAAAAAZsEAQAAAAGlBEAAAAAB4wQBAAAAAeQEAQAAAAEBIwAAmAMAIAaABAIAAAABkAQBAAAAAZsEAQAAAAGlBEAAAAAB4wQBAAAAAeQEAQAAAAEBIwAAmgMAMAEjAACaAwAwBoAEAgCgBwAhkAQBAJ4HACGbBAEAngcAIaUEQACzBwAh4wQBAJ4HACHkBAEAngcAIQIAAACQAwAgIwAAnQMAIAaABAIAoAcAIZAEAQCeBwAhmwQBAJ4HACGlBEAAswcAIeMEAQCeBwAh5AQBAJ4HACECAAAAkwMAICMAAJ8DACACAAAAkwMAICMAAJ8DACADAAAAkAMAICoAAJgDACArAACdAwAgAQAAAJADACABAAAAkwMAIAUGAAC9CQAgMAAAvgkAIDEAAMEJACAyAADACQAgMwAAvwkAIAn9AwAAywYAMP4DAACmAwAQ_wMAAMsGADCABAIAjAYAIZAEAQCNBgAhmwQBAI0GACGlBEAAnQYAIeMEAQCNBgAh5AQBAI0GACEDAAAAkwMAIAEAAKUDADAvAACmAwAgAwAAAJMDACABAACUAwAwAgAAkAMAIAiMAgAAuwYAIP0DAAC3BgAw_gMAALsDABD_AwAAtwYAMIAEAgAAAAGEBAEAuQYAIaQEQAC6BgAh0QQBAAAAAQEAAACpAwAgF4gCAADIBgAgigIAAMkGACCLAgAAygYAIP0DAADEBgAw_gMAAKsDABD_AwAAxAYAMIAEAgC4BgAhhAQBALkGACGkBEAAugYAIdAEAQC5BgAh0QQBALkGACHSBAEAuQYAIdMEAQDFBgAh1AQCALgGACHVBAIAuAYAIdYEAgC4BgAh1wQCALgGACHYBAIAuAYAIdkEAgC4BgAh2gQCAMYGACHbBEAAwwYAIdwEIADHBgAh3QRAAMMGACEHiAIAALoJACCKAgAAuwkAIIsCAAC8CQAg0wQAAJgHACDaBAAAmAcAINsEAACYBwAg3QQAAJgHACAXiAIAAMgGACCKAgAAyQYAIIsCAADKBgAg_QMAAMQGADD-AwAAqwMAEP8DAADEBgAwgAQCAAAAAYQEAQC5BgAhpARAALoGACHQBAEAAAAB0QQBALkGACHSBAEAuQYAIdMEAQDFBgAh1AQCALgGACHVBAIAuAYAIdYEAgC4BgAh1wQCALgGACHYBAIAuAYAIdkEAgC4BgAh2gQCAMYGACHbBEAAwwYAIdwEIADHBgAh3QRAAMMGACEDAAAAqwMAIAEAAKwDADACAACtAwAgE4kCAADABgAg_QMAAMIGADD-AwAArwMAEP8DAADCBgAwgAQCALgGACGkBEAAugYAIb0EQAC6BgAhwwQCALgGACHFBAgAvgYAIcYECAC-BgAhxwQAAL8GACDIBAgAvgYAIckECAC-BgAhygQIAL4GACHLBAgAvgYAIcwECAC-BgAhzQQIAL4GACHOBAEAuQYAIc8EQADDBgAhC4kCAAC5CQAgxQQAAJgHACDGBAAAmAcAIMcEAACYBwAgyAQAAJgHACDJBAAAmAcAIMoEAACYBwAgywQAAJgHACDMBAAAmAcAIM0EAACYBwAgzwQAAJgHACAUiQIAAMAGACD9AwAAwgYAMP4DAACvAwAQ_wMAAMIGADCABAIAAAABpARAALoGACG9BEAAugYAIcMEAgC4BgAhxQQIAL4GACHGBAgAvgYAIccEAAC_BgAgyAQIAL4GACHJBAgAvgYAIcoECAC-BgAhywQIAL4GACHMBAgAvgYAIc0ECAC-BgAhzgQBALkGACHPBEAAwwYAIeIEAADBBgAgAwAAAK8DACABAACwAwAwAgAAsQMAIAuJAgAAwAYAIP0DAAC9BgAw_gMAALMDABD_AwAAvQYAMIAEAgC4BgAhpARAALoGACHDBAIAuAYAIcQEQAC6BgAhxQQIAL4GACHGBAgAvgYAIccEAAC_BgAgBIkCAAC5CQAgxQQAAJgHACDGBAAAmAcAIMcEAACYBwAgDIkCAADABgAg_QMAAL0GADD-AwAAswMAEP8DAAC9BgAwgAQCAAAAAaQEQAC6BgAhwwQCALgGACHEBEAAugYAIcUECAC-BgAhxgQIAL4GACHHBAAAvwYAIOEEAAC8BgAgAwAAALMDACABAAC0AwAwAgAAtQMAIAEAAACvAwAgAQAAALMDACABAAAAqwMAIAEAAACpAwAgCIwCAAC7BgAg_QMAALcGADD-AwAAuwMAEP8DAAC3BgAwgAQCALgGACGEBAEAuQYAIaQEQAC6BgAh0QQBALkGACEBjAIAALgJACADAAAAuwMAIAEAALwDADACAACpAwAgAwAAALsDACABAAC8AwAwAgAAqQMAIAMAAAC7AwAgAQAAvAMAMAIAAKkDACAFjAIAALcJACCABAIAAAABhAQBAAAAAaQEQAAAAAHRBAEAAAABASMAAMADACAEgAQCAAAAAYQEAQAAAAGkBEAAAAAB0QQBAAAAAQEjAADCAwAwASMAAMIDADAFjAIAAKoJACCABAIAoAcAIYQEAQCeBwAhpARAALMHACHRBAEAngcAIQIAAACpAwAgIwAAxQMAIASABAIAoAcAIYQEAQCeBwAhpARAALMHACHRBAEAngcAIQIAAAC7AwAgIwAAxwMAIAIAAAC7AwAgIwAAxwMAIAMAAACpAwAgKgAAwAMAICsAAMUDACABAAAAqQMAIAEAAAC7AwAgBQYAAKUJACAwAACmCQAgMQAAqQkAIDIAAKgJACAzAACnCQAgB_0DAAC2BgAw_gMAAM4DABD_AwAAtgYAMIAEAgCMBgAhhAQBAI0GACGkBEAAnQYAIdEEAQCNBgAhAwAAALsDACABAADNAwAwLwAAzgMAIAMAAAC7AwAgAQAAvAMAMAIAAKkDACABAAAArQMAIAEAAACtAwAgAwAAAKsDACABAACsAwAwAgAArQMAIAMAAACrAwAgAQAArAMAMAIAAK0DACADAAAAqwMAIAEAAKwDADACAACtAwAgFIgCAACiCQAgigIAAKMJACCLAgAApAkAIIAEAgAAAAGEBAEAAAABpARAAAAAAdAEAQAAAAHRBAEAAAAB0gQBAAAAAdMEAQAAAAHUBAIAAAAB1QQCAAAAAdYEAgAAAAHXBAIAAAAB2AQCAAAAAdkEAgAAAAHaBAIAAAAB2wRAAAAAAdwEIAAAAAHdBEAAAAABASMAANYDACARgAQCAAAAAYQEAQAAAAGkBEAAAAAB0AQBAAAAAdEEAQAAAAHSBAEAAAAB0wQBAAAAAdQEAgAAAAHVBAIAAAAB1gQCAAAAAdcEAgAAAAHYBAIAAAAB2QQCAAAAAdoEAgAAAAHbBEAAAAAB3AQgAAAAAd0EQAAAAAEBIwAA2AMAMAEjAADYAwAwFIgCAACHCQAgigIAAIgJACCLAgAAiQkAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdAEAQCeBwAh0QQBAJ4HACHSBAEAngcAIdMEAQCfBwAh1AQCAKAHACHVBAIAoAcAIdYEAgCgBwAh1wQCAKAHACHYBAIAoAcAIdkEAgCgBwAh2gQCALQHACHbBEAAsQcAIdwEIACyBwAh3QRAALEHACECAAAArQMAICMAANsDACARgAQCAKAHACGEBAEAngcAIaQEQACzBwAh0AQBAJ4HACHRBAEAngcAIdIEAQCeBwAh0wQBAJ8HACHUBAIAoAcAIdUEAgCgBwAh1gQCAKAHACHXBAIAoAcAIdgEAgCgBwAh2QQCAKAHACHaBAIAtAcAIdsEQACxBwAh3AQgALIHACHdBEAAsQcAIQIAAACrAwAgIwAA3QMAIAIAAACrAwAgIwAA3QMAIAMAAACtAwAgKgAA1gMAICsAANsDACABAAAArQMAIAEAAACrAwAgCQYAAIIJACAwAACDCQAgMQAAhgkAIDIAAIUJACAzAACECQAg0wQAAJgHACDaBAAAmAcAINsEAACYBwAg3QQAAJgHACAU_QMAALUGADD-AwAA5AMAEP8DAAC1BgAwgAQCAIwGACGEBAEAjQYAIaQEQACdBgAh0AQBAI0GACHRBAEAjQYAIdIEAQCNBgAh0wQBAI4GACHUBAIAjAYAIdUEAgCMBgAh1gQCAIwGACHXBAIAjAYAIdgEAgCMBgAh2QQCAIwGACHaBAIAmQYAIdsEQACaBgAh3AQgAJwGACHdBEAAmgYAIQMAAACrAwAgAQAA4wMAMC8AAOQDACADAAAAqwMAIAEAAKwDADACAACtAwAgAQAAALEDACABAAAAsQMAIAMAAACvAwAgAQAAsAMAMAIAALEDACADAAAArwMAIAEAALADADACAACxAwAgAwAAAK8DACABAACwAwAwAgAAsQMAIBCJAgAAgQkAIIAEAgAAAAGkBEAAAAABvQRAAAAAAcMEAgAAAAHFBAgAAAABxgQIAAAAAccEgAAAAAHIBAgAAAAByQQIAAAAAcoECAAAAAHLBAgAAAABzAQIAAAAAc0ECAAAAAHOBAEAAAABzwRAAAAAAQEjAADsAwAgD4AEAgAAAAGkBEAAAAABvQRAAAAAAcMEAgAAAAHFBAgAAAABxgQIAAAAAccEgAAAAAHIBAgAAAAByQQIAAAAAcoECAAAAAHLBAgAAAABzAQIAAAAAc0ECAAAAAHOBAEAAAABzwRAAAAAAQEjAADuAwAwASMAAO4DADAQiQIAAIAJACCABAIAoAcAIaQEQACzBwAhvQRAALMHACHDBAIAoAcAIcUECAD4CAAhxgQIAPgIACHHBIAAAAAByAQIAPgIACHJBAgA-AgAIcoECAD4CAAhywQIAPgIACHMBAgA-AgAIc0ECAD4CAAhzgQBAJ4HACHPBEAAsQcAIQIAAACxAwAgIwAA8QMAIA-ABAIAoAcAIaQEQACzBwAhvQRAALMHACHDBAIAoAcAIcUECAD4CAAhxgQIAPgIACHHBIAAAAAByAQIAPgIACHJBAgA-AgAIcoECAD4CAAhywQIAPgIACHMBAgA-AgAIc0ECAD4CAAhzgQBAJ4HACHPBEAAsQcAIQIAAACvAwAgIwAA8wMAIAIAAACvAwAgIwAA8wMAIAMAAACxAwAgKgAA7AMAICsAAPEDACABAAAAsQMAIAEAAACvAwAgDwYAAPsIACAwAAD8CAAgMQAA_wgAIDIAAP4IACAzAAD9CAAgxQQAAJgHACDGBAAAmAcAIMcEAACYBwAgyAQAAJgHACDJBAAAmAcAIMoEAACYBwAgywQAAJgHACDMBAAAmAcAIM0EAACYBwAgzwQAAJgHACAS_QMAALQGADD-AwAA-gMAEP8DAAC0BgAwgAQCAIwGACGkBEAAnQYAIb0EQACdBgAhwwQCAIwGACHFBAgAsgYAIcYECACyBgAhxwQAAKoGACDIBAgAsgYAIckECACyBgAhygQIALIGACHLBAgAsgYAIcwECACyBgAhzQQIALIGACHOBAEAjQYAIc8EQACaBgAhAwAAAK8DACABAAD5AwAwLwAA-gMAIAMAAACvAwAgAQAAsAMAMAIAALEDACABAAAAtQMAIAEAAAC1AwAgAwAAALMDACABAAC0AwAwAgAAtQMAIAMAAACzAwAgAQAAtAMAMAIAALUDACADAAAAswMAIAEAALQDADACAAC1AwAgCIkCAAD6CAAggAQCAAAAAaQEQAAAAAHDBAIAAAABxARAAAAAAcUECAAAAAHGBAgAAAABxwSAAAAAAQEjAACCBAAgB4AEAgAAAAGkBEAAAAABwwQCAAAAAcQEQAAAAAHFBAgAAAABxgQIAAAAAccEgAAAAAEBIwAAhAQAMAEjAACEBAAwCIkCAAD5CAAggAQCAKAHACGkBEAAswcAIcMEAgCgBwAhxARAALMHACHFBAgA-AgAIcYECAD4CAAhxwSAAAAAAQIAAAC1AwAgIwAAhwQAIAeABAIAoAcAIaQEQACzBwAhwwQCAKAHACHEBEAAswcAIcUECAD4CAAhxgQIAPgIACHHBIAAAAABAgAAALMDACAjAACJBAAgAgAAALMDACAjAACJBAAgAwAAALUDACAqAACCBAAgKwAAhwQAIAEAAAC1AwAgAQAAALMDACAIBgAA8wgAIDAAAPQIACAxAAD3CAAgMgAA9ggAIDMAAPUIACDFBAAAmAcAIMYEAACYBwAgxwQAAJgHACAK_QMAALEGADD-AwAAkAQAEP8DAACxBgAwgAQCAIwGACGkBEAAnQYAIcMEAgCMBgAhxARAAJ0GACHFBAgAsgYAIcYECACyBgAhxwQAAKoGACADAAAAswMAIAEAAI8EADAvAACQBAAgAwAAALMDACABAAC0AwAwAgAAtQMAIAEAAAAFACABAAAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIAMAAAADACABAAAEADACAAAFACADAAAAAwAgAQAABAAwAgAABQAgDgMAAPEIACAFAADyCAAggAQCAAAAAYQEAQAAAAGQBAIAAAABoAQCAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbUEAQAAAAG6BAEAAAABwAQBAAAAAcEEAgAAAAHCBAEAAAABASMAAJgEACAMgAQCAAAAAYQEAQAAAAGQBAIAAAABoAQCAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbUEAQAAAAG6BAEAAAABwAQBAAAAAcEEAgAAAAHCBAEAAAABASMAAJoEADABIwAAmgQAMA4DAADjCAAgBQAA5AgAIIAEAgCgBwAhhAQBAJ4HACGQBAIAoAcAIaAEAgCgBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhtQQBAJ4HACG6BAEAngcAIcAEAQCeBwAhwQQCAKAHACHCBAEAngcAIQIAAAAFACAjAACdBAAgDIAEAgCgBwAhhAQBAJ4HACGQBAIAoAcAIaAEAgCgBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhtQQBAJ4HACG6BAEAngcAIcAEAQCeBwAhwQQCAKAHACHCBAEAngcAIQIAAAADACAjAACfBAAgAgAAAAMAICMAAJ8EACADAAAABQAgKgAAmAQAICsAAJ0EACABAAAABQAgAQAAAAMAIAUGAADeCAAgMAAA3wgAIDEAAOIIACAyAADhCAAgMwAA4AgAIA_9AwAAsAYAMP4DAACmBAAQ_wMAALAGADCABAIAjAYAIYQEAQCNBgAhkAQCAIwGACGgBAIAjAYAIaEEIACcBgAhpARAAJ0GACGlBEAAnQYAIbUEAQCNBgAhugQBAI0GACHABAEAjQYAIcEEAgCMBgAhwgQBAI0GACEDAAAAAwAgAQAApQQAMC8AAKYEACADAAAAAwAgAQAABAAwAgAABQAgAQAAAAkAIAEAAAAJACADAAAABwAgAQAACAAwAgAACQAgAwAAAAcAIAEAAAgAMAIAAAkAIAMAAAAHACABAAAIADACAAAJACAKAwAA3QgAIAQAANwIACCABAIAAAABkAQCAAAAAaQEQAAAAAG6BAEAAAABvAQCAAAAAb0EAQAAAAG-BAIAAAABvwQBAAAAAQEjAACuBAAgCIAEAgAAAAGQBAIAAAABpARAAAAAAboEAQAAAAG8BAIAAAABvQQBAAAAAb4EAgAAAAG_BAEAAAABASMAALAEADABIwAAsAQAMAoDAADbCAAgBAAA2ggAIIAEAgCgBwAhkAQCAKAHACGkBEAAswcAIboEAQCeBwAhvAQCAKAHACG9BAEAngcAIb4EAgCgBwAhvwQBAJ4HACECAAAACQAgIwAAswQAIAiABAIAoAcAIZAEAgCgBwAhpARAALMHACG6BAEAngcAIbwEAgCgBwAhvQQBAJ4HACG-BAIAoAcAIb8EAQCeBwAhAgAAAAcAICMAALUEACACAAAABwAgIwAAtQQAIAMAAAAJACAqAACuBAAgKwAAswQAIAEAAAAJACABAAAABwAgBQYAANUIACAwAADWCAAgMQAA2QgAIDIAANgIACAzAADXCAAgC_0DAACvBgAw_gMAALwEABD_AwAArwYAMIAEAgCMBgAhkAQCAIwGACGkBEAAnQYAIboEAQCNBgAhvAQCAIwGACG9BAEAjQYAIb4EAgCMBgAhvwQBAI0GACEDAAAABwAgAQAAuwQAMC8AALwEACADAAAABwAgAQAACAAwAgAACQAgAQAAAA8AIAEAAAAPACADAAAADQAgAQAADgAwAgAADwAgAwAAAA0AIAEAAA4AMAIAAA8AIAMAAAANACABAAAOADACAAAPACAKAwAA0wgAIAoAANQIACCABAIAAAABkAQCAAAAAZcEAQAAAAGhBCAAAAABpARAAAAAAaUEQAAAAAG3BAEAAAABugQBAAAAAQEjAADEBAAgCIAEAgAAAAGQBAIAAAABlwQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbcEAQAAAAG6BAEAAAABASMAAMYEADABIwAAxgQAMAoDAADFCAAgCgAAxggAIIAEAgCgBwAhkAQCAKAHACGXBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbcEAQCeBwAhugQBAJ4HACECAAAADwAgIwAAyQQAIAiABAIAoAcAIZAEAgCgBwAhlwQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACG3BAEAngcAIboEAQCeBwAhAgAAAA0AICMAAMsEACACAAAADQAgIwAAywQAIAMAAAAPACAqAADEBAAgKwAAyQQAIAEAAAAPACABAAAADQAgBQYAAMAIACAwAADBCAAgMQAAxAgAIDIAAMMIACAzAADCCAAgC_0DAACuBgAw_gMAANIEABD_AwAArgYAMIAEAgCMBgAhkAQCAIwGACGXBAEAjQYAIaEEIACcBgAhpARAAJ0GACGlBEAAnQYAIbcEAQCNBgAhugQBAI0GACEDAAAADQAgAQAA0QQAMC8AANIEACADAAAADQAgAQAADgAwAgAADwAgAQAAABMAIAEAAAATACADAAAAEQAgAQAAEgAwAgAAEwAgAwAAABEAIAEAABIAMAIAABMAIAMAAAARACABAAASADACAAATACAKCQAAvwgAIIAEAgAAAAGXBAEAAAABnARAAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAbkEAgAAAAG6BAEAAAABuwQgAAAAAQEjAADaBAAgCYAEAgAAAAGXBAEAAAABnARAAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAbkEAgAAAAG6BAEAAAABuwQgAAAAAQEjAADcBAAwASMAANwEADAKCQAAvggAIIAEAgCgBwAhlwQBAJ4HACGcBEAAsQcAIaAEAgCgBwAhpARAALMHACGlBEAAswcAIbkEAgCgBwAhugQBAJ4HACG7BCAAsgcAIQIAAAATACAjAADfBAAgCYAEAgCgBwAhlwQBAJ4HACGcBEAAsQcAIaAEAgCgBwAhpARAALMHACGlBEAAswcAIbkEAgCgBwAhugQBAJ4HACG7BCAAsgcAIQIAAAARACAjAADhBAAgAgAAABEAICMAAOEEACADAAAAEwAgKgAA2gQAICsAAN8EACABAAAAEwAgAQAAABEAIAYGAAC5CAAgMAAAuggAIDEAAL0IACAyAAC8CAAgMwAAuwgAIJwEAACYBwAgDP0DAACtBgAw_gMAAOgEABD_AwAArQYAMIAEAgCMBgAhlwQBAI0GACGcBEAAmgYAIaAEAgCMBgAhpARAAJ0GACGlBEAAnQYAIbkEAgCMBgAhugQBAI0GACG7BCAAnAYAIQMAAAARACABAADnBAAwLwAA6AQAIAMAAAARACABAAASADACAAATACABAAAAGAAgAQAAABgAIAMAAAAWACABAAAXADACAAAYACADAAAAFgAgAQAAFwAwAgAAGAAgAwAAABYAIAEAABcAMAIAABgAIAkDAAC4CAAgCoAAAAABgAQCAAAAAYQEAQAAAAGQBAIAAAABpARAAAAAAaUEQAAAAAG3BAEAAAABuAQgAAAAAQEjAADwBAAgCAqAAAAAAYAEAgAAAAGEBAEAAAABkAQCAAAAAaQEQAAAAAGlBEAAAAABtwQBAAAAAbgEIAAAAAEBIwAA8gQAMAEjAADyBAAwAQAAABoAIAkDAAC3CAAgCoAAAAABgAQCAKAHACGEBAEAngcAIZAEAgC0BwAhpARAALMHACGlBEAAswcAIbcEAQCeBwAhuAQgALIHACECAAAAGAAgIwAA9gQAIAgKgAAAAAGABAIAoAcAIYQEAQCeBwAhkAQCALQHACGkBEAAswcAIaUEQACzBwAhtwQBAJ4HACG4BCAAsgcAIQIAAAAWACAjAAD4BAAgAgAAABYAICMAAPgEACABAAAAGgAgAwAAABgAICoAAPAEACArAAD2BAAgAQAAABgAIAEAAAAWACAGBgAAsggAIDAAALMIACAxAAC2CAAgMgAAtQgAIDMAALQIACCQBAAAmAcAIAsKAACbBgAg_QMAAKwGADD-AwAAgAUAEP8DAACsBgAwgAQCAIwGACGEBAEAjQYAIZAEAgCZBgAhpARAAJ0GACGlBEAAnQYAIbcEAQCNBgAhuAQgAJwGACEDAAAAFgAgAQAA_wQAMC8AAIAFACADAAAAFgAgAQAAFwAwAgAAGAAgAQAAAB4AIAEAAAAeACADAAAAHAAgAQAAHQAwAgAAHgAgAwAAABwAIAEAAB0AMAIAAB4AIAMAAAAcACABAAAdADACAAAeACARDQAArggAIA8AAK8IACAXAACxCAAgGAAAsAgAIIAEAgAAAAGBBAEAAAABhAQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbAEAQAAAAGxBAIAAAABsgQgAAAAAbMEgAAAAAG0BIAAAAABtQQBAAAAAbYEAQAAAAEBIwAAiAUAIA2ABAIAAAABgQQBAAAAAYQEAQAAAAGhBCAAAAABpARAAAAAAaUEQAAAAAGwBAEAAAABsQQCAAAAAbIEIAAAAAGzBIAAAAABtASAAAAAAbUEAQAAAAG2BAEAAAABASMAAIoFADABIwAAigUAMBENAACJCAAgDwAAiggAIBcAAIwIACAYAACLCAAggAQCAKAHACGBBAEAngcAIYQEAQCeBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhsAQBAJ4HACGxBAIAoAcAIbIEIACyBwAhswSAAAAAAbQEgAAAAAG1BAEAnwcAIbYEAQCeBwAhAgAAAB4AICMAAI0FACANgAQCAKAHACGBBAEAngcAIYQEAQCeBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhsAQBAJ4HACGxBAIAoAcAIbIEIACyBwAhswSAAAAAAbQEgAAAAAG1BAEAnwcAIbYEAQCeBwAhAgAAABwAICMAAI8FACACAAAAHAAgIwAAjwUAIAMAAAAeACAqAACIBQAgKwAAjQUAIAEAAAAeACABAAAAHAAgBwYAAIQIACAwAACFCAAgMQAAiAgAIDIAAIcIACAzAACGCAAgtAQAAJgHACC1BAAAmAcAIBD9AwAAqQYAMP4DAACWBQAQ_wMAAKkGADCABAIAjAYAIYEEAQCNBgAhhAQBAI0GACGhBCAAnAYAIaQEQACdBgAhpQRAAJ0GACGwBAEAjQYAIbEEAgCMBgAhsgQgAJwGACGzBAAAmwYAILQEAACqBgAgtQQBAI4GACG2BAEAjQYAIQMAAAAcACABAACVBQAwLwAAlgUAIAMAAAAcACABAAAdADACAAAeACABAAAAIgAgAQAAACIAIAMAAAAgACABAAAhADACAAAiACADAAAAIAAgAQAAIQAwAgAAIgAgAwAAACAAIAEAACEAMAIAACIAIAkDAACDCAAgDgAAgggAIIAEAgAAAAGBBAEAAAABkAQCAAAAAZEEAQAAAAGUBAIAAAABpARAAAAAAa8EAQAAAAEBIwAAngUAIAeABAIAAAABgQQBAAAAAZAEAgAAAAGRBAEAAAABlAQCAAAAAaQEQAAAAAGvBAEAAAABASMAAKAFADABIwAAoAUAMAkDAACBCAAgDgAAgAgAIIAEAgCgBwAhgQQBAJ4HACGQBAIAoAcAIZEEAQCfBwAhlAQCAKAHACGkBEAAswcAIa8EAQCeBwAhAgAAACIAICMAAKMFACAHgAQCAKAHACGBBAEAngcAIZAEAgCgBwAhkQQBAJ8HACGUBAIAoAcAIaQEQACzBwAhrwQBAJ4HACECAAAAIAAgIwAApQUAIAIAAAAgACAjAAClBQAgAwAAACIAICoAAJ4FACArAACjBQAgAQAAACIAIAEAAAAgACAGBgAA-wcAIDAAAPwHACAxAAD_BwAgMgAA_gcAIDMAAP0HACCRBAAAmAcAIAr9AwAAqAYAMP4DAACsBQAQ_wMAAKgGADCABAIAjAYAIYEEAQCNBgAhkAQCAIwGACGRBAEAjgYAIZQEAgCMBgAhpARAAJ0GACGvBAEAjQYAIQMAAAAgACABAACrBQAwLwAArAUAIAMAAAAgACABAAAhADACAAAiACABAAAAJgAgAQAAACYAIAMAAAAkACABAAAlADACAAAmACADAAAAJAAgAQAAJQAwAgAAJgAgAwAAACQAIAEAACUAMAIAACYAIAwOAAD5BwAgFwAA-gcAIIAEAgAAAAGBBAEAAAABhAQBAAAAAZEEAQAAAAGUBAIAAAABoAQCAAAAAaQEQAAAAAGlBEAAAAABrQQBAAAAAa4EAQAAAAEBIwAAtAUAIAqABAIAAAABgQQBAAAAAYQEAQAAAAGRBAEAAAABlAQCAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAa0EAQAAAAGuBAEAAAABASMAALYFADABIwAAtgUAMAwOAADuBwAgFwAA7wcAIIAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIZEEAQCfBwAhlAQCAKAHACGgBAIAoAcAIaQEQACzBwAhpQRAALMHACGtBAEAnwcAIa4EAQCeBwAhAgAAACYAICMAALkFACAKgAQCAKAHACGBBAEAngcAIYQEAQCeBwAhkQQBAJ8HACGUBAIAoAcAIaAEAgCgBwAhpARAALMHACGlBEAAswcAIa0EAQCfBwAhrgQBAJ4HACECAAAAJAAgIwAAuwUAIAIAAAAkACAjAAC7BQAgAwAAACYAICoAALQFACArAAC5BQAgAQAAACYAIAEAAAAkACAHBgAA6QcAIDAAAOoHACAxAADtBwAgMgAA7AcAIDMAAOsHACCRBAAAmAcAIK0EAACYBwAgDf0DAACnBgAw_gMAAMIFABD_AwAApwYAMIAEAgCMBgAhgQQBAI0GACGEBAEAjQYAIZEEAQCOBgAhlAQCAIwGACGgBAIAjAYAIaQEQACdBgAhpQRAAJ0GACGtBAEAjgYAIa4EAQCNBgAhAwAAACQAIAEAAMEFADAvAADCBQAgAwAAACQAIAEAACUAMAIAACYAIAEAAAAqACABAAAAKgAgAwAAACgAIAEAACkAMAIAACoAIAMAAAAoACABAAApADACAAAqACADAAAAKAAgAQAAKQAwAgAAKgAgIA4AAOEHACAPAADmBwAgEAAA4gcAIBEAAOgHACASAADjBwAgEwAA5AcAIBQAAOUHACAWAADnBwAggAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABASMAAMoFACAYgAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABASMAAMwFADABIwAAzAUAMAEAAAAoACABAAAAGgAgIA4AALUHACAPAAC7BwAgEAAAtgcAIBEAALcHACASAAC4BwAgEwAAuQcAIBQAALoHACAWAAC8BwAggAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlQQCAKAHACGWBAIAtAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACECAAAAKgAgIwAA0QUAIBiABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhnQQCALQHACGeBAIAoAcAIZ8EgAAAAAGgBAIAoAcAIaEEIACyBwAhogRAALEHACGjBAEAnwcAIaQEQACzBwAhpQRAALMHACGmBAIAtAcAIQIAAAAoACAjAADTBQAgAgAAACgAICMAANMFACABAAAAKAAgAQAAABoAIAMAAAAqACAqAADKBQAgKwAA0QUAIAEAAAAqACABAAAAKAAgEAYAAKwHACAwAACtBwAgMQAAsAcAIDIAAK8HACAzAACuBwAgkQQAAJgHACCSBAAAmAcAIJMEAACYBwAglgQAAJgHACCZBAAAmAcAIJoEAACYBwAgnAQAAJgHACCdBAAAmAcAIKIEAACYBwAgowQAAJgHACCmBAAAmAcAIBv9AwAAmAYAMP4DAADcBQAQ_wMAAJgGADCABAIAjAYAIYEEAQCNBgAhkQQBAI4GACGSBAEAjgYAIZMEAQCOBgAhlAQCAIwGACGVBAIAjAYAIZYEAgCZBgAhlwQBAI0GACGYBAEAjQYAIZkEAQCOBgAhmgRAAJoGACGbBAEAjQYAIZwEQACaBgAhnQQCAJkGACGeBAIAjAYAIZ8EAACbBgAgoAQCAIwGACGhBCAAnAYAIaIEQACaBgAhowQBAI4GACGkBEAAnQYAIaUEQACdBgAhpgQCAJkGACEDAAAAKAAgAQAA2wUAMC8AANwFACADAAAAKAAgAQAAKQAwAgAAKgAgAQAAADEAIAEAAAAxACADAAAALwAgAQAAMAAwAgAAMQAgAwAAAC8AIAEAADAAMAIAADEAIAMAAAAvACABAAAwADACAAAxACAHAwAAqwcAIBUAAKoHACCABAIAAAABgQQBAAAAAYIEAQAAAAGDBAIAAAABkAQCAAAAAQEjAADkBQAgBYAEAgAAAAGBBAEAAAABggQBAAAAAYMEAgAAAAGQBAIAAAABASMAAOYFADABIwAA5gUAMAcDAACpBwAgFQAAqAcAIIAEAgCgBwAhgQQBAJ4HACGCBAEAnwcAIYMEAgCgBwAhkAQCAKAHACECAAAAMQAgIwAA6QUAIAWABAIAoAcAIYEEAQCeBwAhggQBAJ8HACGDBAIAoAcAIZAEAgCgBwAhAgAAAC8AICMAAOsFACACAAAALwAgIwAA6wUAIAMAAAAxACAqAADkBQAgKwAA6QUAIAEAAAAxACABAAAALwAgBgYAAKMHACAwAACkBwAgMQAApwcAIDIAAKYHACAzAAClBwAgggQAAJgHACAI_QMAAJcGADD-AwAA8gUAEP8DAACXBgAwgAQCAIwGACGBBAEAjQYAIYIEAQCOBgAhgwQCAIwGACGQBAIAjAYAIQMAAAAvACABAADxBQAwLwAA8gUAIAMAAAAvACABAAAwADACAAAxACABAAAANQAgAQAAADUAIAMAAAAzACABAAA0ADACAAA1ACADAAAAMwAgAQAANAAwAgAANQAgAwAAADMAIAEAADQAMAIAADUAIAYVAACiBwAggAQCAAAAAYEEAQAAAAGCBAEAAAABgwQCAAAAAYQEAQAAAAEBIwAA-gUAIAWABAIAAAABgQQBAAAAAYIEAQAAAAGDBAIAAAABhAQBAAAAAQEjAAD8BQAwASMAAPwFADAGFQAAoQcAIIAEAgCgBwAhgQQBAJ4HACGCBAEAnwcAIYMEAgCgBwAhhAQBAJ4HACECAAAANQAgIwAA_wUAIAWABAIAoAcAIYEEAQCeBwAhggQBAJ8HACGDBAIAoAcAIYQEAQCeBwAhAgAAADMAICMAAIEGACACAAAAMwAgIwAAgQYAIAMAAAA1ACAqAAD6BQAgKwAA_wUAIAEAAAA1ACABAAAAMwAgBgYAAJkHACAwAACaBwAgMQAAnQcAIDIAAJwHACAzAACbBwAgggQAAJgHACAI_QMAAIsGADD-AwAAiAYAEP8DAACLBgAwgAQCAIwGACGBBAEAjQYAIYIEAQCOBgAhgwQCAIwGACGEBAEAjQYAIQMAAAAzACABAACHBgAwLwAAiAYAIAMAAAAzACABAAA0ADACAAA1ACAI_QMAAIsGADD-AwAAiAYAEP8DAACLBgAwgAQCAIwGACGBBAEAjQYAIYIEAQCOBgAhgwQCAIwGACGEBAEAjQYAIQ0GAACTBgAgMAAAlgYAIDEAAJMGACAyAACTBgAgMwAAkwYAIIUEAgAAAAGGBAIAAAAEhwQCAAAABIgEAgAAAAGJBAIAAAABigQCAAAAAYsEAgAAAAGPBAIAlQYAIQ4GAACTBgAgMgAAlAYAIDMAAJQGACCFBAEAAAABhgQBAAAABIcEAQAAAASIBAEAAAABiQQBAAAAAYoEAQAAAAGLBAEAAAABjAQBAAAAAY0EAQAAAAGOBAEAAAABjwQBAJIGACEOBgAAkAYAIDIAAJEGACAzAACRBgAghQQBAAAAAYYEAQAAAAWHBAEAAAAFiAQBAAAAAYkEAQAAAAGKBAEAAAABiwQBAAAAAYwEAQAAAAGNBAEAAAABjgQBAAAAAY8EAQCPBgAhDgYAAJAGACAyAACRBgAgMwAAkQYAIIUEAQAAAAGGBAEAAAAFhwQBAAAABYgEAQAAAAGJBAEAAAABigQBAAAAAYsEAQAAAAGMBAEAAAABjQQBAAAAAY4EAQAAAAGPBAEAjwYAIQiFBAIAAAABhgQCAAAABYcEAgAAAAWIBAIAAAABiQQCAAAAAYoEAgAAAAGLBAIAAAABjwQCAJAGACELhQQBAAAAAYYEAQAAAAWHBAEAAAAFiAQBAAAAAYkEAQAAAAGKBAEAAAABiwQBAAAAAYwEAQAAAAGNBAEAAAABjgQBAAAAAY8EAQCRBgAhDgYAAJMGACAyAACUBgAgMwAAlAYAIIUEAQAAAAGGBAEAAAAEhwQBAAAABIgEAQAAAAGJBAEAAAABigQBAAAAAYsEAQAAAAGMBAEAAAABjQQBAAAAAY4EAQAAAAGPBAEAkgYAIQiFBAIAAAABhgQCAAAABIcEAgAAAASIBAIAAAABiQQCAAAAAYoEAgAAAAGLBAIAAAABjwQCAJMGACELhQQBAAAAAYYEAQAAAASHBAEAAAAEiAQBAAAAAYkEAQAAAAGKBAEAAAABiwQBAAAAAYwEAQAAAAGNBAEAAAABjgQBAAAAAY8EAQCUBgAhDQYAAJMGACAwAACWBgAgMQAAkwYAIDIAAJMGACAzAACTBgAghQQCAAAAAYYEAgAAAASHBAIAAAAEiAQCAAAAAYkEAgAAAAGKBAIAAAABiwQCAAAAAY8EAgCVBgAhCIUECAAAAAGGBAgAAAAEhwQIAAAABIgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgAlgYAIQj9AwAAlwYAMP4DAADyBQAQ_wMAAJcGADCABAIAjAYAIYEEAQCNBgAhggQBAI4GACGDBAIAjAYAIZAEAgCMBgAhG_0DAACYBgAw_gMAANwFABD_AwAAmAYAMIAEAgCMBgAhgQQBAI0GACGRBAEAjgYAIZIEAQCOBgAhkwQBAI4GACGUBAIAjAYAIZUEAgCMBgAhlgQCAJkGACGXBAEAjQYAIZgEAQCNBgAhmQQBAI4GACGaBEAAmgYAIZsEAQCNBgAhnARAAJoGACGdBAIAmQYAIZ4EAgCMBgAhnwQAAJsGACCgBAIAjAYAIaEEIACcBgAhogRAAJoGACGjBAEAjgYAIaQEQACdBgAhpQRAAJ0GACGmBAIAmQYAIQ0GAACQBgAgMAAApgYAIDEAAJAGACAyAACQBgAgMwAAkAYAIIUEAgAAAAGGBAIAAAAFhwQCAAAABYgEAgAAAAGJBAIAAAABigQCAAAAAYsEAgAAAAGPBAIApQYAIQsGAACQBgAgMgAApAYAIDMAAKQGACCFBEAAAAABhgRAAAAABYcEQAAAAAWIBEAAAAABiQRAAAAAAYoEQAAAAAGLBEAAAAABjwRAAKMGACEPBgAAkwYAIDIAAKIGACAzAACiBgAghQSAAAAAAYgEgAAAAAGJBIAAAAABigSAAAAAAYsEgAAAAAGPBIAAAAABpwQBAAAAAagEAQAAAAGpBAEAAAABqgSAAAAAAasEgAAAAAGsBIAAAAABBQYAAJMGACAyAAChBgAgMwAAoQYAIIUEIAAAAAGPBCAAoAYAIQsGAACTBgAgMgAAnwYAIDMAAJ8GACCFBEAAAAABhgRAAAAABIcEQAAAAASIBEAAAAABiQRAAAAAAYoEQAAAAAGLBEAAAAABjwRAAJ4GACELBgAAkwYAIDIAAJ8GACAzAACfBgAghQRAAAAAAYYEQAAAAASHBEAAAAAEiARAAAAAAYkEQAAAAAGKBEAAAAABiwRAAAAAAY8EQACeBgAhCIUEQAAAAAGGBEAAAAAEhwRAAAAABIgEQAAAAAGJBEAAAAABigRAAAAAAYsEQAAAAAGPBEAAnwYAIQUGAACTBgAgMgAAoQYAIDMAAKEGACCFBCAAAAABjwQgAKAGACEChQQgAAAAAY8EIAChBgAhDIUEgAAAAAGIBIAAAAABiQSAAAAAAYoEgAAAAAGLBIAAAAABjwSAAAAAAacEAQAAAAGoBAEAAAABqQQBAAAAAaoEgAAAAAGrBIAAAAABrASAAAAAAQsGAACQBgAgMgAApAYAIDMAAKQGACCFBEAAAAABhgRAAAAABYcEQAAAAAWIBEAAAAABiQRAAAAAAYoEQAAAAAGLBEAAAAABjwRAAKMGACEIhQRAAAAAAYYEQAAAAAWHBEAAAAAFiARAAAAAAYkEQAAAAAGKBEAAAAABiwRAAAAAAY8EQACkBgAhDQYAAJAGACAwAACmBgAgMQAAkAYAIDIAAJAGACAzAACQBgAghQQCAAAAAYYEAgAAAAWHBAIAAAAFiAQCAAAAAYkEAgAAAAGKBAIAAAABiwQCAAAAAY8EAgClBgAhCIUECAAAAAGGBAgAAAAFhwQIAAAABYgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgApgYAIQ39AwAApwYAMP4DAADCBQAQ_wMAAKcGADCABAIAjAYAIYEEAQCNBgAhhAQBAI0GACGRBAEAjgYAIZQEAgCMBgAhoAQCAIwGACGkBEAAnQYAIaUEQACdBgAhrQQBAI4GACGuBAEAjQYAIQr9AwAAqAYAMP4DAACsBQAQ_wMAAKgGADCABAIAjAYAIYEEAQCNBgAhkAQCAIwGACGRBAEAjgYAIZQEAgCMBgAhpARAAJ0GACGvBAEAjQYAIRD9AwAAqQYAMP4DAACWBQAQ_wMAAKkGADCABAIAjAYAIYEEAQCNBgAhhAQBAI0GACGhBCAAnAYAIaQEQACdBgAhpQRAAJ0GACGwBAEAjQYAIbEEAgCMBgAhsgQgAJwGACGzBAAAmwYAILQEAACqBgAgtQQBAI4GACG2BAEAjQYAIQ8GAACQBgAgMgAAqwYAIDMAAKsGACCFBIAAAAABiASAAAAAAYkEgAAAAAGKBIAAAAABiwSAAAAAAY8EgAAAAAGnBAEAAAABqAQBAAAAAakEAQAAAAGqBIAAAAABqwSAAAAAAawEgAAAAAEMhQSAAAAAAYgEgAAAAAGJBIAAAAABigSAAAAAAYsEgAAAAAGPBIAAAAABpwQBAAAAAagEAQAAAAGpBAEAAAABqgSAAAAAAasEgAAAAAGsBIAAAAABCwoAAJsGACD9AwAArAYAMP4DAACABQAQ_wMAAKwGADCABAIAjAYAIYQEAQCNBgAhkAQCAJkGACGkBEAAnQYAIaUEQACdBgAhtwQBAI0GACG4BCAAnAYAIQz9AwAArQYAMP4DAADoBAAQ_wMAAK0GADCABAIAjAYAIZcEAQCNBgAhnARAAJoGACGgBAIAjAYAIaQEQACdBgAhpQRAAJ0GACG5BAIAjAYAIboEAQCNBgAhuwQgAJwGACEL_QMAAK4GADD-AwAA0gQAEP8DAACuBgAwgAQCAIwGACGQBAIAjAYAIZcEAQCNBgAhoQQgAJwGACGkBEAAnQYAIaUEQACdBgAhtwQBAI0GACG6BAEAjQYAIQv9AwAArwYAMP4DAAC8BAAQ_wMAAK8GADCABAIAjAYAIZAEAgCMBgAhpARAAJ0GACG6BAEAjQYAIbwEAgCMBgAhvQQBAI0GACG-BAIAjAYAIb8EAQCNBgAhD_0DAACwBgAw_gMAAKYEABD_AwAAsAYAMIAEAgCMBgAhhAQBAI0GACGQBAIAjAYAIaAEAgCMBgAhoQQgAJwGACGkBEAAnQYAIaUEQACdBgAhtQQBAI0GACG6BAEAjQYAIcAEAQCNBgAhwQQCAIwGACHCBAEAjQYAIQr9AwAAsQYAMP4DAACQBAAQ_wMAALEGADCABAIAjAYAIaQEQACdBgAhwwQCAIwGACHEBEAAnQYAIcUECACyBgAhxgQIALIGACHHBAAAqgYAIA0GAACQBgAgMAAApgYAIDEAAKYGACAyAACmBgAgMwAApgYAIIUECAAAAAGGBAgAAAAFhwQIAAAABYgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgAswYAIQ0GAACQBgAgMAAApgYAIDEAAKYGACAyAACmBgAgMwAApgYAIIUECAAAAAGGBAgAAAAFhwQIAAAABYgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgAswYAIRL9AwAAtAYAMP4DAAD6AwAQ_wMAALQGADCABAIAjAYAIaQEQACdBgAhvQRAAJ0GACHDBAIAjAYAIcUECACyBgAhxgQIALIGACHHBAAAqgYAIMgECACyBgAhyQQIALIGACHKBAgAsgYAIcsECACyBgAhzAQIALIGACHNBAgAsgYAIc4EAQCNBgAhzwRAAJoGACEU_QMAALUGADD-AwAA5AMAEP8DAAC1BgAwgAQCAIwGACGEBAEAjQYAIaQEQACdBgAh0AQBAI0GACHRBAEAjQYAIdIEAQCNBgAh0wQBAI4GACHUBAIAjAYAIdUEAgCMBgAh1gQCAIwGACHXBAIAjAYAIdgEAgCMBgAh2QQCAIwGACHaBAIAmQYAIdsEQACaBgAh3AQgAJwGACHdBEAAmgYAIQf9AwAAtgYAMP4DAADOAwAQ_wMAALYGADCABAIAjAYAIYQEAQCNBgAhpARAAJ0GACHRBAEAjQYAIQiMAgAAuwYAIP0DAAC3BgAw_gMAALsDABD_AwAAtwYAMIAEAgC4BgAhhAQBALkGACGkBEAAugYAIdEEAQC5BgAhCIUEAgAAAAGGBAIAAAAEhwQCAAAABIgEAgAAAAGJBAIAAAABigQCAAAAAYsEAgAAAAGPBAIAkwYAIQuFBAEAAAABhgQBAAAABIcEAQAAAASIBAEAAAABiQQBAAAAAYoEAQAAAAGLBAEAAAABjAQBAAAAAY0EAQAAAAGOBAEAAAABjwQBAJQGACEIhQRAAAAAAYYEQAAAAASHBEAAAAAEiARAAAAAAYkEQAAAAAGKBEAAAAABiwRAAAAAAY8EQACfBgAhA94EAACrAwAg3wQAAKsDACDgBAAAqwMAIALDBAIAAAABxARAAAAAAQuJAgAAwAYAIP0DAAC9BgAw_gMAALMDABD_AwAAvQYAMIAEAgC4BgAhpARAALoGACHDBAIAuAYAIcQEQAC6BgAhxQQIAL4GACHGBAgAvgYAIccEAAC_BgAgCIUECAAAAAGGBAgAAAAFhwQIAAAABYgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgApgYAIQyFBIAAAAABiASAAAAAAYkEgAAAAAGKBIAAAAABiwSAAAAAAY8EgAAAAAGnBAEAAAABqAQBAAAAAakEAQAAAAGqBIAAAAABqwSAAAAAAawEgAAAAAEZiAIAAMgGACCKAgAAyQYAIIsCAADKBgAg_QMAAMQGADD-AwAAqwMAEP8DAADEBgAwgAQCALgGACGEBAEAuQYAIaQEQAC6BgAh0AQBALkGACHRBAEAuQYAIdIEAQC5BgAh0wQBAMUGACHUBAIAuAYAIdUEAgC4BgAh1gQCALgGACHXBAIAuAYAIdgEAgC4BgAh2QQCALgGACHaBAIAxgYAIdsEQADDBgAh3AQgAMcGACHdBEAAwwYAIaYFAACrAwAgpwUAAKsDACACvQRAAAAAAcMEAgAAAAETiQIAAMAGACD9AwAAwgYAMP4DAACvAwAQ_wMAAMIGADCABAIAuAYAIaQEQAC6BgAhvQRAALoGACHDBAIAuAYAIcUECAC-BgAhxgQIAL4GACHHBAAAvwYAIMgECAC-BgAhyQQIAL4GACHKBAgAvgYAIcsECAC-BgAhzAQIAL4GACHNBAgAvgYAIc4EAQC5BgAhzwRAAMMGACEIhQRAAAAAAYYEQAAAAAWHBEAAAAAFiARAAAAAAYkEQAAAAAGKBEAAAAABiwRAAAAAAY8EQACkBgAhF4gCAADIBgAgigIAAMkGACCLAgAAygYAIP0DAADEBgAw_gMAAKsDABD_AwAAxAYAMIAEAgC4BgAhhAQBALkGACGkBEAAugYAIdAEAQC5BgAh0QQBALkGACHSBAEAuQYAIdMEAQDFBgAh1AQCALgGACHVBAIAuAYAIdYEAgC4BgAh1wQCALgGACHYBAIAuAYAIdkEAgC4BgAh2gQCAMYGACHbBEAAwwYAIdwEIADHBgAh3QRAAMMGACELhQQBAAAAAYYEAQAAAAWHBAEAAAAFiAQBAAAAAYkEAQAAAAGKBAEAAAABiwQBAAAAAYwEAQAAAAGNBAEAAAABjgQBAAAAAY8EAQCRBgAhCIUEAgAAAAGGBAIAAAAFhwQCAAAABYgEAgAAAAGJBAIAAAABigQCAAAAAYsEAgAAAAGPBAIAkAYAIQKFBCAAAAABjwQgAKEGACEKjAIAALsGACD9AwAAtwYAMP4DAAC7AwAQ_wMAALcGADCABAIAuAYAIYQEAQC5BgAhpARAALoGACHRBAEAuQYAIaYFAAC7AwAgpwUAALsDACAD3gQAAK8DACDfBAAArwMAIOAEAACvAwAgA94EAACzAwAg3wQAALMDACDgBAAAswMAIAn9AwAAywYAMP4DAACmAwAQ_wMAAMsGADCABAIAjAYAIZAEAQCNBgAhmwQBAI0GACGlBEAAnQYAIeMEAQCNBgAh5AQBAI0GACEJ_QMAAMwGADD-AwAAkwMAEP8DAADMBgAwgAQCALgGACGQBAEAuQYAIZsEAQC5BgAhpQRAALoGACHjBAEAuQYAIeQEAQC5BgAhCv0DAADNBgAw_gMAAI0DABD_AwAAzQYAMIAEAgCMBgAhpARAAJ0GACG9BEAAnQYAIb8EAQCNBgAh5QQBAI0GACHmBAEAjQYAIecECADOBgAhDQYAAJMGACAwAACWBgAgMQAAlgYAIDIAAJYGACAzAACWBgAghQQIAAAAAYYECAAAAASHBAgAAAAEiAQIAAAAAYkECAAAAAGKBAgAAAABiwQIAAAAAY8ECADPBgAhDQYAAJMGACAwAACWBgAgMQAAlgYAIDIAAJYGACAzAACWBgAghQQIAAAAAYYECAAAAASHBAgAAAAEiAQIAAAAAYkECAAAAAGKBAgAAAABiwQIAAAAAY8ECADPBgAhCv0DAADQBgAw_gMAAPoCABD_AwAA0AYAMIAEAgC4BgAhpARAALoGACG9BEAAugYAIb8EAQC5BgAh5QQBALkGACHmBAEAuQYAIecECADRBgAhCIUECAAAAAGGBAgAAAAEhwQIAAAABIgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgAlgYAIQO9BEAAAAAB5QQBAAAAAeYEAQAAAAEK_QMAANMGADD-AwAA9AIAEP8DAADTBgAwgAQCAIwGACGkBEAAnQYAIaUEQACdBgAh6QQBAI0GACHqBAEAjgYAIesEQACdBgAh7AQAAKoGACAK_QMAANQGADD-AwAA4QIAEP8DAADUBgAwgAQCALgGACGkBEAAugYAIaUEQAC6BgAh6QQBALkGACHqBAEAxQYAIesEQAC6BgAh7AQAAL8GACAI_QMAANUGADD-AwAA2wIAEP8DAADVBgAwgAQCAIwGACGkBEAAnQYAIaUEQACdBgAh7QQBAI0GACHuBAIAjAYAIQj9AwAA1gYAMP4DAADIAgAQ_wMAANYGADCABAIAuAYAIaQEQAC6BgAhpQRAALoGACHtBAEAuQYAIe4EAgC4BgAhDP0DAADXBgAw_gMAAMICABD_AwAA1wYAMIAEAgCMBgAhhAQBAI0GACGbBAEAjQYAIaQEQACdBgAhpQRAAJ0GACHvBAEAjQYAIfAEAgCMBgAh8QQBAI0GACHyBEAAnQYAIQz9AwAA2AYAMP4DAACvAgAQ_wMAANgGADCABAIAuAYAIYQEAQC5BgAhmwQBALkGACGkBEAAugYAIaUEQAC6BgAh7wQBALkGACHwBAIAuAYAIfEEAQC5BgAh8gRAALoGACEP_QMAANkGADD-AwAAqQIAEP8DAADZBgAwgAQCAIwGACGBBAEAjQYAIaQEQACdBgAhwARAAJ0GACHzBAgAzgYAIfQEBADaBgAh9QQIAM4GACH2BAQA2gYAIfcEBADaBgAh-AQEANoGACH5BAIAjAYAIfoEAgCMBgAhDQYAAJMGACAwAACWBgAgMQAA3AYAIDIAANwGACAzAADcBgAghQQEAAAAAYYEBAAAAASHBAQAAAAEiAQEAAAAAYkEBAAAAAGKBAQAAAABiwQEAAAAAY8EBADbBgAhDQYAAJMGACAwAACWBgAgMQAA3AYAIDIAANwGACAzAADcBgAghQQEAAAAAYYEBAAAAASHBAQAAAAEiAQEAAAAAYkEBAAAAAGKBAQAAAABiwQEAAAAAY8EBADbBgAhCIUEBAAAAAGGBAQAAAAEhwQEAAAABIgEBAAAAAGJBAQAAAABigQEAAAAAYsEBAAAAAGPBAQA3AYAIQ_9AwAA3QYAMP4DAACWAgAQ_wMAAN0GADCABAIAuAYAIYEEAQC5BgAhpARAALoGACHABEAAugYAIfMECADRBgAh9AQEAN4GACH1BAgA0QYAIfYEBADeBgAh9wQEAN4GACH4BAQA3gYAIfkEAgC4BgAh-gQCALgGACEIhQQEAAAAAYYEBAAAAASHBAQAAAAEiAQEAAAAAYkEBAAAAAGKBAQAAAABiwQEAAAAAY8EBADcBgAhAoEEAQAAAAHABEAAAAABHP0DAADgBgAw_gMAAJACABD_AwAA4AYAMIAEAgCMBgAhgQQBAI0GACGEBAEAjQYAIZsEAQCNBgAhpARAAJ0GACGlBEAAnQYAIfcEBADaBgAh-AQEANoGACH5BAIAjAYAIfoEAgCMBgAh_AQBAI0GACH9BAEAjQYAIf4EAQCNBgAh_wQEANoGACGABQQA2gYAIYEFAgCMBgAhggUIAM4GACGDBQQA2gYAIYQFBADaBgAhhQUIAM4GACGGBQgAzgYAIYcFCADOBgAhiAUEANoGACGJBQQA2gYAIYoFQACdBgAhHP0DAADhBgAw_gMAAP0BABD_AwAA4QYAMIAEAgC4BgAhgQQBALkGACGEBAEAuQYAIZsEAQC5BgAhpARAALoGACGlBEAAugYAIfcEBADeBgAh-AQEAN4GACH5BAIAuAYAIfoEAgC4BgAh_AQBALkGACH9BAEAuQYAIf4EAQC5BgAh_wQEAN4GACGABQQA3gYAIYEFAgC4BgAhggUIANEGACGDBQQA3gYAIYQFBADeBgAhhQUIANEGACGGBQgA0QYAIYcFCADRBgAhiAUEAN4GACGJBQQA3gYAIYoFQAC6BgAhDf0DAADiBgAw_gMAAPcBABD_AwAA4gYAMIAEAgCMBgAhpARAAJ0GACHABAEAjQYAIYsFAQCNBgAhjAUIAM4GACGNBQgAzgYAIY4FAQCNBgAhjwUBAI0GACGQBQgAzgYAIZEFCADOBgAhDf0DAADjBgAw_gMAAOQBABD_AwAA4wYAMIAEAgC4BgAhpARAALoGACHABAEAuQYAIYsFAQC5BgAhjAUIANEGACGNBQgA0QYAIY4FAQC5BgAhjwUBALkGACGQBQgA0QYAIZEFCADRBgAhDP0DAADkBgAw_gMAAN4BABD_AwAA5AYAMIAEAgCMBgAhlwQBAI0GACGkBEAAnQYAIb8EAQCNBgAh7wQBAI0GACGSBQEAjQYAIZMFAQCOBgAhlAVAAJ0GACGVBQIAjAYAIQz9AwAA5QYAMP4DAADLAQAQ_wMAAOUGADCABAIAuAYAIZcEAQC5BgAhpARAALoGACG_BAEAuQYAIe8EAQC5BgAhkgUBALkGACGTBQEAxQYAIZQFQAC6BgAhlQUCALgGACEClwQBAAAAAZQFQAAAAAEJ_QMAAOcGADD-AwAAxQEAEP8DAADnBgAwgAQCAIwGACGkBEAAnQYAIaUEQACdBgAhwAQBAI0GACGXBQIAjAYAIZgFCADOBgAhDf0DAADoBgAw_gMAAK8BABD_AwAA6AYAMIAEAgCMBgAhhAQBAI0GACGXBAEAjQYAIaQEQACdBgAhpQRAAJ0GACG_BAEAjQYAIZkFAQCNBgAhmgUBAI0GACGbBQEAjQYAIZwFIACcBgAhDlcAAOoGACD9AwAA6QYAMP4DAACcAQAQ_wMAAOkGADCABAIAuAYAIYQEAQC5BgAhlwQBALkGACGkBEAAugYAIaUEQAC6BgAhvwQBALkGACGZBQEAuQYAIZoFAQC5BgAhmwUBALkGACGcBSAAxwYAIQPeBAAAlgEAIN8EAACWAQAg4AQAAJYBACACwAQBAAAAAZcFAgAAAAEKVgAA7QYAIP0DAADsBgAw_gMAAJYBABD_AwAA7AYAMIAEAgC4BgAhpARAALoGACGlBEAAugYAIcAEAQC5BgAhlwUCALgGACGYBQgA0QYAIRBXAADqBgAg_QMAAOkGADD-AwAAnAEAEP8DAADpBgAwgAQCALgGACGEBAEAuQYAIZcEAQC5BgAhpARAALoGACGlBEAAugYAIb8EAQC5BgAhmQUBALkGACGaBQEAuQYAIZsFAQC5BgAhnAUgAMcGACGmBQAAnAEAIKcFAACcAQAgCSMAAJsGACD9AwAA7gYAMP4DAACRAQAQ_wMAAO4GADCABAIAjAYAIYQEAQCNBgAhmAQBAI0GACGkBEAAnQYAIaUEQACdBgAhCSMAAPAGACD9AwAA7wYAMP4DAAB-ABD_AwAA7wYAMIAEAgC4BgAhhAQBALkGACGYBAEAuQYAIaQEQAC6BgAhpQRAALoGACEMhQSAAAAAAYgEgAAAAAGJBIAAAAABigSAAAAAAYsEgAAAAAGPBIAAAAABpwQBAAAAAagEAQAAAAGpBAEAAAABqgSAAAAAAasEgAAAAAGsBIAAAAABCv0DAADxBgAw_gMAAHgAEP8DAADxBgAwgAQCAIwGACGXBAEAjQYAIaQEQACdBgAhpQRAAJ0GACG9BAEAjQYAIZMFAQCNBgAhngUBAI0GACEK_QMAAPIGADD-AwAAZQAQ_wMAAPIGADCABAIAuAYAIZcEAQC5BgAhpARAALoGACGlBEAAugYAIb0EAQC5BgAhkwUBALkGACGeBQEAuQYAIQn9AwAA8wYAMP4DAABfABD_AwAA8wYAMIAEAgCMBgAhhAQBAI0GACGkBEAAnQYAIaUEQACdBgAhnwUBAI0GACGgBQEAjQYAIQkVAAD1BgAg_QMAAPQGADD-AwAAMwAQ_wMAAPQGADCABAIAuAYAIYEEAQC5BgAhggQBAMUGACGDBAIAuAYAIYQEAQC5BgAhJQ4AAPsGACAPAACABwAgEAAA_AYAIBEAAP0GACASAAD-BgAgEwAA_wYAIBQAAPgGACAWAACBBwAg_QMAAPoGADD-AwAAKAAQ_wMAAPoGADCABAIAuAYAIYEEAQC5BgAhkQQBAMUGACGSBAEAxQYAIZMEAQDFBgAhlAQCALgGACGVBAIAuAYAIZYEAgDGBgAhlwQBALkGACGYBAEAuQYAIZkEAQDFBgAhmgRAAMMGACGbBAEAuQYAIZwEQADDBgAhnQQCAMYGACGeBAIAuAYAIZ8EAADwBgAgoAQCALgGACGhBCAAxwYAIaIEQADDBgAhowQBAMUGACGkBEAAugYAIaUEQAC6BgAhpgQCAMYGACGmBQAAKAAgpwUAACgAIAKDBAIAAAABkAQCAAAAAQoDAAD4BgAgFQAA9QYAIP0DAAD3BgAw_gMAAC8AEP8DAAD3BgAwgAQCALgGACGBBAEAuQYAIYIEAQDFBgAhgwQCALgGACGQBAIAuAYAIRQHAACKBwAgCAAAiwcAIAsAAIwHACAMAACNBwAgGQAAjgcAIBoAAIcHACAbAAD-BgAgHAAA_gYAIB0AAIAHACD9AwAAiQcAMP4DAAAaABD_AwAAiQcAMIAEAgC4BgAhhAQBALkGACGkBEAAugYAIaUEQAC6BgAhnwUBALkGACGgBQEAuQYAIaYFAAAaACCnBQAAGgAgApQEAgAAAAGmBAIAAAABIw4AAPsGACAPAACABwAgEAAA_AYAIBEAAP0GACASAAD-BgAgEwAA_wYAIBQAAPgGACAWAACBBwAg_QMAAPoGADD-AwAAKAAQ_wMAAPoGADCABAIAuAYAIYEEAQC5BgAhkQQBAMUGACGSBAEAxQYAIZMEAQDFBgAhlAQCALgGACGVBAIAuAYAIZYEAgDGBgAhlwQBALkGACGYBAEAuQYAIZkEAQDFBgAhmgRAAMMGACGbBAEAuQYAIZwEQADDBgAhnQQCAMYGACGeBAIAuAYAIZ8EAADwBgAgoAQCALgGACGhBCAAxwYAIaIEQADDBgAhowQBAMUGACGkBEAAugYAIaUEQAC6BgAhpgQCAMYGACEWDQAA-AYAIA8AAIcHACAXAAD-BgAgGAAAiAcAIP0DAACGBwAw_gMAABwAEP8DAACGBwAwgAQCALgGACGBBAEAuQYAIYQEAQC5BgAhoQQgAMcGACGkBEAAugYAIaUEQAC6BgAhsAQBALkGACGxBAIAuAYAIbIEIADHBgAhswQAAPAGACC0BAAAvwYAILUEAQDFBgAhtgQBALkGACGmBQAAHAAgpwUAABwAIBEOAAD7BgAgFwAA_gYAIP0DAACDBwAw_gMAACQAEP8DAACDBwAwgAQCALgGACGBBAEAuQYAIYQEAQC5BgAhkQQBAMUGACGUBAIAuAYAIaAEAgC4BgAhpARAALoGACGlBEAAugYAIa0EAQDFBgAhrgQBALkGACGmBQAAJAAgpwUAACQAICUOAAD7BgAgDwAAgAcAIBAAAPwGACARAAD9BgAgEgAA_gYAIBMAAP8GACAUAAD4BgAgFgAAgQcAIP0DAAD6BgAw_gMAACgAEP8DAAD6BgAwgAQCALgGACGBBAEAuQYAIZEEAQDFBgAhkgQBAMUGACGTBAEAxQYAIZQEAgC4BgAhlQQCALgGACGWBAIAxgYAIZcEAQC5BgAhmAQBALkGACGZBAEAxQYAIZoEQADDBgAhmwQBALkGACGcBEAAwwYAIZ0EAgDGBgAhngQCALgGACGfBAAA8AYAIKAEAgC4BgAhoQQgAMcGACGiBEAAwwYAIaMEAQDFBgAhpARAALoGACGlBEAAugYAIaYEAgDGBgAhpgUAACgAIKcFAAAoACAD3gQAACgAIN8EAAAoACDgBAAAKAAgFAcAAIoHACAIAACLBwAgCwAAjAcAIAwAAI0HACAZAACOBwAgGgAAhwcAIBsAAP4GACAcAAD-BgAgHQAAgAcAIP0DAACJBwAw_gMAABoAEP8DAACJBwAwgAQCALgGACGEBAEAuQYAIaQEQAC6BgAhpQRAALoGACGfBQEAuQYAIaAFAQC5BgAhpgUAABoAIKcFAAAaACAD3gQAAC8AIN8EAAAvACDgBAAALwAgA94EAAAzACDfBAAAMwAg4AQAADMAIAKEBAEAAAABlAQCAAAAAQ8OAAD7BgAgFwAA_gYAIP0DAACDBwAw_gMAACQAEP8DAACDBwAwgAQCALgGACGBBAEAuQYAIYQEAQC5BgAhkQQBAMUGACGUBAIAuAYAIaAEAgC4BgAhpARAALoGACGlBEAAugYAIa0EAQDFBgAhrgQBALkGACECkAQCAAAAAZQEAgAAAAEMAwAA-AYAIA4AAPsGACD9AwAAhQcAMP4DAAAgABD_AwAAhQcAMIAEAgC4BgAhgQQBALkGACGQBAIAuAYAIZEEAQDFBgAhlAQCALgGACGkBEAAugYAIa8EAQC5BgAhFA0AAPgGACAPAACHBwAgFwAA_gYAIBgAAIgHACD9AwAAhgcAMP4DAAAcABD_AwAAhgcAMIAEAgC4BgAhgQQBALkGACGEBAEAuQYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbAEAQC5BgAhsQQCALgGACGyBCAAxwYAIbMEAADwBgAgtAQAAL8GACC1BAEAxQYAIbYEAQC5BgAhA94EAAAgACDfBAAAIAAg4AQAACAAIAPeBAAAJAAg3wQAACQAIOAEAAAkACASBwAAigcAIAgAAIsHACALAACMBwAgDAAAjQcAIBkAAI4HACAaAACHBwAgGwAA_gYAIBwAAP4GACAdAACABwAg_QMAAIkHADD-AwAAGgAQ_wMAAIkHADCABAIAuAYAIYQEAQC5BgAhpARAALoGACGlBEAAugYAIZ8FAQC5BgAhoAUBALkGACED3gQAAAMAIN8EAAADACDgBAAAAwAgA94EAAAHACDfBAAABwAg4AQAAAcAIAPeBAAADQAg3wQAAA0AIOAEAAANACAD3gQAABYAIN8EAAAWACDgBAAAFgAgA94EAAAcACDfBAAAHAAg4AQAABwAIAwDAAD_BgAgCgAA8AYAIP0DAACPBwAw_gMAABYAEP8DAACPBwAwgAQCALgGACGEBAEAuQYAIZAEAgDGBgAhpARAALoGACGlBEAAugYAIbcEAQC5BgAhuAQgAMcGACENCQAAkQcAIP0DAACQBwAw_gMAABEAEP8DAACQBwAwgAQCALgGACGXBAEAuQYAIZwEQADDBgAhoAQCALgGACGkBEAAugYAIaUEQAC6BgAhuQQCALgGACG6BAEAuQYAIbsEIADHBgAhDwMAAPgGACAKAACTBwAg_QMAAJIHADD-AwAADQAQ_wMAAJIHADCABAIAuAYAIZAEAgC4BgAhlwQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIboEAQC5BgAhpgUAAA0AIKcFAAANACANAwAA-AYAIAoAAJMHACD9AwAAkgcAMP4DAAANABD_AwAAkgcAMIAEAgC4BgAhkAQCALgGACGXBAEAuQYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbcEAQC5BgAhugQBALkGACED3gQAABEAIN8EAAARACDgBAAAEQAgA7wEAgAAAAG9BAEAAAABvgQCAAAAAQ0DAAD4BgAgBAAAlgcAIP0DAACVBwAw_gMAAAcAEP8DAACVBwAwgAQCALgGACGQBAIAuAYAIaQEQAC6BgAhugQBALkGACG8BAIAuAYAIb0EAQC5BgAhvgQCALgGACG_BAEAuQYAIRMDAAD4BgAgBQAAiwcAIP0DAACXBwAw_gMAAAMAEP8DAACXBwAwgAQCALgGACGEBAEAuQYAIZAEAgC4BgAhoAQCALgGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG1BAEAuQYAIboEAQC5BgAhwAQBALkGACHBBAIAuAYAIcIEAQC5BgAhpgUAAAMAIKcFAAADACARAwAA-AYAIAUAAIsHACD9AwAAlwcAMP4DAAADABD_AwAAlwcAMIAEAgC4BgAhhAQBALkGACGQBAIAuAYAIaAEAgC4BgAhoQQgAMcGACGkBEAAugYAIaUEQAC6BgAhtQQBALkGACG6BAEAuQYAIcAEAQC5BgAhwQQCALgGACHCBAEAuQYAIQAAAAAAAAGrBQEAAAABAasFAQAAAAEFqwUCAAAAAbEFAgAAAAGyBQIAAAABswUCAAAAAbQFAgAAAAEFKgAAlgwAICsAAJkMACCoBQAAlwwAIKkFAACYDAAgrgUAACoAIAMqAACWDAAgqAUAAJcMACCuBQAAKgAgAAAAAAAFKgAAjgwAICsAAJQMACCoBQAAjwwAIKkFAACTDAAgrgUAACoAIAUqAACMDAAgKwAAkQwAIKgFAACNDAAgqQUAAJAMACCuBQAAAQAgAyoAAI4MACCoBQAAjwwAIK4FAAAqACADKgAAjAwAIKgFAACNDAAgrgUAAAEAIAAAAAAAAasFQAAAAAEBqwUgAAAAAQGrBUAAAAABBasFAgAAAAGxBQIAAAABsgUCAAAAAbMFAgAAAAG0BQIAAAABBSoAAPgLACArAACKDAAgqAUAAPkLACCpBQAAiQwAIK4FAAAeACAFKgAA9gsAICsAAIcMACCoBQAA9wsAIKkFAACGDAAgrgUAACYAIAcqAADwCwAgKwAAhAwAIKgFAADxCwAgqQUAAIMMACCsBQAAKAAgrQUAACgAIK4FAAAqACALKgAA1QcAMCsAANoHADCoBQAA1gcAMKkFAADXBwAwqgUAANgHACCrBQAA2QcAMKwFAADZBwAwrQUAANkHADCuBQAA2QcAMK8FAADbBwAwsAUAANwHADAHKgAA9AsAICsAAIEMACCoBQAA9QsAIKkFAACADAAgrAUAABoAIK0FAAAaACCuBQAAAQAgBSoAAPILACArAAD-CwAgqAUAAPMLACCpBQAA_QsAIK4FAAABACALKgAAyQcAMCsAAM4HADCoBQAAygcAMKkFAADLBwAwqgUAAMwHACCrBQAAzQcAMKwFAADNBwAwrQUAAM0HADCuBQAAzQcAMK8FAADPBwAwsAUAANAHADALKgAAvQcAMCsAAMIHADCoBQAAvgcAMKkFAAC_BwAwqgUAAMAHACCrBQAAwQcAMKwFAADBBwAwrQUAAMEHADCuBQAAwQcAMK8FAADDBwAwsAUAAMQHADAEgAQCAAAAAYEEAQAAAAGCBAEAAAABhAQBAAAAAQIAAAA1ACAqAADIBwAgAwAAADUAICoAAMgHACArAADHBwAgASMAAPwLADAJFQAA9QYAIP0DAAD0BgAw_gMAADMAEP8DAAD0BgAwgAQCAAAAAYEEAQAAAAGCBAEAxQYAIYMEAgC4BgAhhAQBALkGACECAAAANQAgIwAAxwcAIAIAAADFBwAgIwAAxgcAIAj9AwAAxAcAMP4DAADFBwAQ_wMAAMQHADCABAIAuAYAIYEEAQC5BgAhggQBAMUGACGDBAIAuAYAIYQEAQC5BgAhCP0DAADEBwAw_gMAAMUHABD_AwAAxAcAMIAEAgC4BgAhgQQBALkGACGCBAEAxQYAIYMEAgC4BgAhhAQBALkGACEEgAQCAKAHACGBBAEAngcAIYIEAQCfBwAhhAQBAJ4HACEEgAQCAKAHACGBBAEAngcAIYIEAQCfBwAhhAQBAJ4HACEEgAQCAAAAAYEEAQAAAAGCBAEAAAABhAQBAAAAAQUDAACrBwAggAQCAAAAAYEEAQAAAAGCBAEAAAABkAQCAAAAAQIAAAAxACAqAADUBwAgAwAAADEAICoAANQHACArAADTBwAgASMAAPsLADALAwAA-AYAIBUAAPUGACD9AwAA9wYAMP4DAAAvABD_AwAA9wYAMIAEAgAAAAGBBAEAAAABggQBAMUGACGDBAIAuAYAIZAEAgC4BgAhoQUAAPYGACACAAAAMQAgIwAA0wcAIAIAAADRBwAgIwAA0gcAIAj9AwAA0AcAMP4DAADRBwAQ_wMAANAHADCABAIAuAYAIYEEAQC5BgAhggQBAMUGACGDBAIAuAYAIZAEAgC4BgAhCP0DAADQBwAw_gMAANEHABD_AwAA0AcAMIAEAgC4BgAhgQQBALkGACGCBAEAxQYAIYMEAgC4BgAhkAQCALgGACEEgAQCAKAHACGBBAEAngcAIYIEAQCfBwAhkAQCAKAHACEFAwAAqQcAIIAEAgCgBwAhgQQBAJ4HACGCBAEAnwcAIZAEAgCgBwAhBQMAAKsHACCABAIAAAABgQQBAAAAAYIEAQAAAAGQBAIAAAABHg4AAOEHACAPAADmBwAgEAAA4gcAIBIAAOMHACATAADkBwAgFAAA5QcAIBYAAOcHACCABAIAAAABgQQBAAAAAZEEAQAAAAGSBAEAAAABkwQBAAAAAZQEAgAAAAGVBAIAAAABlwQBAAAAAZgEAQAAAAGZBAEAAAABmgRAAAAAAZsEAQAAAAGcBEAAAAABnQQCAAAAAZ4EAgAAAAGfBIAAAAABoAQCAAAAAaEEIAAAAAGiBEAAAAABowQBAAAAAaQEQAAAAAGlBEAAAAABpgQCAAAAAQIAAAAqACAqAADgBwAgAwAAACoAICoAAOAHACArAADfBwAgASMAAPoLADAkDgAA-wYAIA8AAIAHACAQAAD8BgAgEQAA_QYAIBIAAP4GACATAAD_BgAgFAAA-AYAIBYAAIEHACD9AwAA-gYAMP4DAAAoABD_AwAA-gYAMIAEAgAAAAGBBAEAAAABkQQBAMUGACGSBAEAxQYAIZMEAQDFBgAhlAQCALgGACGVBAIAuAYAIZYEAgDGBgAhlwQBALkGACGYBAEAuQYAIZkEAQDFBgAhmgRAAMMGACGbBAEAuQYAIZwEQADDBgAhnQQCAMYGACGeBAIAuAYAIZ8EAADwBgAgoAQCALgGACGhBCAAxwYAIaIEQADDBgAhowQBAMUGACGkBEAAugYAIaUEQAC6BgAhpgQCAMYGACGiBQAA-QYAIAIAAAAqACAjAADfBwAgAgAAAN0HACAjAADeBwAgG_0DAADcBwAw_gMAAN0HABD_AwAA3AcAMIAEAgC4BgAhgQQBALkGACGRBAEAxQYAIZIEAQDFBgAhkwQBAMUGACGUBAIAuAYAIZUEAgC4BgAhlgQCAMYGACGXBAEAuQYAIZgEAQC5BgAhmQQBAMUGACGaBEAAwwYAIZsEAQC5BgAhnARAAMMGACGdBAIAxgYAIZ4EAgC4BgAhnwQAAPAGACCgBAIAuAYAIaEEIADHBgAhogRAAMMGACGjBAEAxQYAIaQEQAC6BgAhpQRAALoGACGmBAIAxgYAIRv9AwAA3AcAMP4DAADdBwAQ_wMAANwHADCABAIAuAYAIYEEAQC5BgAhkQQBAMUGACGSBAEAxQYAIZMEAQDFBgAhlAQCALgGACGVBAIAuAYAIZYEAgDGBgAhlwQBALkGACGYBAEAuQYAIZkEAQDFBgAhmgRAAMMGACGbBAEAuQYAIZwEQADDBgAhnQQCAMYGACGeBAIAuAYAIZ8EAADwBgAgoAQCALgGACGhBCAAxwYAIaIEQADDBgAhowQBAMUGACGkBEAAugYAIaUEQAC6BgAhpgQCAMYGACEXgAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlQQCAKAHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGdBAIAtAcAIZ4EAgCgBwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHg4AALUHACAPAAC7BwAgEAAAtgcAIBIAALgHACATAAC5BwAgFAAAugcAIBYAALwHACCABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEeDgAA4QcAIA8AAOYHACAQAADiBwAgEgAA4wcAIBMAAOQHACAUAADlBwAgFgAA5wcAIIAEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZUEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABAyoAAPgLACCoBQAA-QsAIK4FAAAeACADKgAA9gsAIKgFAAD3CwAgrgUAACYAIAQqAADVBwAwqAUAANYHADCqBQAA2AcAIK4FAADZBwAwAyoAAPQLACCoBQAA9QsAIK4FAAABACADKgAA8gsAIKgFAADzCwAgrgUAAAEAIAQqAADJBwAwqAUAAMoHADCqBQAAzAcAIK4FAADNBwAwBCoAAL0HADCoBQAAvgcAMKoFAADABwAgrgUAAMEHADADKgAA8AsAIKgFAADxCwAgrgUAACoAIAAAAAAABSoAAOoLACArAADuCwAgqAUAAOsLACCpBQAA7QsAIK4FAAAeACALKgAA8AcAMCsAAPQHADCoBQAA8QcAMKkFAADyBwAwqgUAAPMHACCrBQAA2QcAMKwFAADZBwAwrQUAANkHADCuBQAA2QcAMK8FAAD1BwAwsAUAANwHADAeDgAA4QcAIA8AAOYHACARAADoBwAgEgAA4wcAIBMAAOQHACAUAADlBwAgFgAA5wcAIIAEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABAgAAACoAICoAAPgHACADAAAAKgAgKgAA-AcAICsAAPcHACABIwAA7AsAMAIAAAAqACAjAAD3BwAgAgAAAN0HACAjAAD2BwAgF4AEAgCgBwAhgQQBAJ4HACGRBAEAnwcAIZIEAQCfBwAhkwQBAJ8HACGUBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhnQQCALQHACGeBAIAoAcAIZ8EgAAAAAGgBAIAoAcAIaEEIACyBwAhogRAALEHACGjBAEAnwcAIaQEQACzBwAhpQRAALMHACGmBAIAtAcAIR4OAAC1BwAgDwAAuwcAIBEAALcHACASAAC4BwAgEwAAuQcAIBQAALoHACAWAAC8BwAggAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlgQCALQHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGdBAIAtAcAIZ4EAgCgBwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHg4AAOEHACAPAADmBwAgEQAA6AcAIBIAAOMHACATAADkBwAgFAAA5QcAIBYAAOcHACCABAIAAAABgQQBAAAAAZEEAQAAAAGSBAEAAAABkwQBAAAAAZQEAgAAAAGWBAIAAAABlwQBAAAAAZgEAQAAAAGZBAEAAAABmgRAAAAAAZsEAQAAAAGcBEAAAAABnQQCAAAAAZ4EAgAAAAGfBIAAAAABoAQCAAAAAaEEIAAAAAGiBEAAAAABowQBAAAAAaQEQAAAAAGlBEAAAAABpgQCAAAAAQMqAADqCwAgqAUAAOsLACCuBQAAHgAgBCoAAPAHADCoBQAA8QcAMKoFAADzBwAgrgUAANkHADAAAAAAAAUqAADiCwAgKwAA6AsAIKgFAADjCwAgqQUAAOcLACCuBQAAHgAgBSoAAOALACArAADlCwAgqAUAAOELACCpBQAA5AsAIK4FAAABACADKgAA4gsAIKgFAADjCwAgrgUAAB4AIAMqAADgCwAgqAUAAOELACCuBQAAAQAgAAAAAAAFKgAA2AsAICsAAN4LACCoBQAA2QsAIKkFAADdCwAgrgUAAAEAIAsqAACiCAAwKwAApwgAMKgFAACjCAAwqQUAAKQIADCqBQAApQgAIKsFAACmCAAwrAUAAKYIADCtBQAApggAMK4FAACmCAAwrwUAAKgIADCwBQAAqQgAMAsqAACWCAAwKwAAmwgAMKgFAACXCAAwqQUAAJgIADCqBQAAmQgAIKsFAACaCAAwrAUAAJoIADCtBQAAmggAMK4FAACaCAAwrwUAAJwIADCwBQAAnQgAMAsqAACNCAAwKwAAkQgAMKgFAACOCAAwqQUAAI8IADCqBQAAkAgAIKsFAADZBwAwrAUAANkHADCtBQAA2QcAMK4FAADZBwAwrwUAAJIIADCwBQAA3AcAMB4PAADmBwAgEAAA4gcAIBEAAOgHACASAADjBwAgEwAA5AcAIBQAAOUHACAWAADnBwAggAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGVBAIAAAABlgQCAAAAAZcEAQAAAAGYBAEAAAABmQQBAAAAAZoEQAAAAAGbBAEAAAABnARAAAAAAZ0EAgAAAAGeBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAECAAAAKgAgKgAAlQgAIAMAAAAqACAqAACVCAAgKwAAlAgAIAEjAADcCwAwAgAAACoAICMAAJQIACACAAAA3QcAICMAAJMIACAXgAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZUEAgCgBwAhlgQCALQHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGdBAIAtAcAIZ4EAgCgBwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHg8AALsHACAQAAC2BwAgEQAAtwcAIBIAALgHACATAAC5BwAgFAAAugcAIBYAALwHACCABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlQQCAKAHACGWBAIAtAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEeDwAA5gcAIBAAAOIHACARAADoBwAgEgAA4wcAIBMAAOQHACAUAADlBwAgFgAA5wcAIIAEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABChcAAPoHACCABAIAAAABgQQBAAAAAYQEAQAAAAGRBAEAAAABoAQCAAAAAaQEQAAAAAGlBEAAAAABrQQBAAAAAa4EAQAAAAECAAAAJgAgKgAAoQgAIAMAAAAmACAqAAChCAAgKwAAoAgAIAEjAADbCwAwEA4AAPsGACAXAAD-BgAg_QMAAIMHADD-AwAAJAAQ_wMAAIMHADCABAIAAAABgQQBAAAAAYQEAQC5BgAhkQQBAMUGACGUBAIAuAYAIaAEAgC4BgAhpARAALoGACGlBEAAugYAIa0EAQDFBgAhrgQBALkGACGjBQAAggcAIAIAAAAmACAjAACgCAAgAgAAAJ4IACAjAACfCAAgDf0DAACdCAAw_gMAAJ4IABD_AwAAnQgAMIAEAgC4BgAhgQQBALkGACGEBAEAuQYAIZEEAQDFBgAhlAQCALgGACGgBAIAuAYAIaQEQAC6BgAhpQRAALoGACGtBAEAxQYAIa4EAQC5BgAhDf0DAACdCAAw_gMAAJ4IABD_AwAAnQgAMIAEAgC4BgAhgQQBALkGACGEBAEAuQYAIZEEAQDFBgAhlAQCALgGACGgBAIAuAYAIaQEQAC6BgAhpQRAALoGACGtBAEAxQYAIa4EAQC5BgAhCYAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIZEEAQCfBwAhoAQCAKAHACGkBEAAswcAIaUEQACzBwAhrQQBAJ8HACGuBAEAngcAIQoXAADvBwAggAQCAKAHACGBBAEAngcAIYQEAQCeBwAhkQQBAJ8HACGgBAIAoAcAIaQEQACzBwAhpQRAALMHACGtBAEAnwcAIa4EAQCeBwAhChcAAPoHACCABAIAAAABgQQBAAAAAYQEAQAAAAGRBAEAAAABoAQCAAAAAaQEQAAAAAGlBEAAAAABrQQBAAAAAa4EAQAAAAEHAwAAgwgAIIAEAgAAAAGBBAEAAAABkAQCAAAAAZEEAQAAAAGkBEAAAAABrwQBAAAAAQIAAAAiACAqAACtCAAgAwAAACIAICoAAK0IACArAACsCAAgASMAANoLADANAwAA-AYAIA4AAPsGACD9AwAAhQcAMP4DAAAgABD_AwAAhQcAMIAEAgAAAAGBBAEAAAABkAQCALgGACGRBAEAxQYAIZQEAgC4BgAhpARAALoGACGvBAEAuQYAIaQFAACEBwAgAgAAACIAICMAAKwIACACAAAAqggAICMAAKsIACAK_QMAAKkIADD-AwAAqggAEP8DAACpCAAwgAQCALgGACGBBAEAuQYAIZAEAgC4BgAhkQQBAMUGACGUBAIAuAYAIaQEQAC6BgAhrwQBALkGACEK_QMAAKkIADD-AwAAqggAEP8DAACpCAAwgAQCALgGACGBBAEAuQYAIZAEAgC4BgAhkQQBAMUGACGUBAIAuAYAIaQEQAC6BgAhrwQBALkGACEGgAQCAKAHACGBBAEAngcAIZAEAgCgBwAhkQQBAJ8HACGkBEAAswcAIa8EAQCeBwAhBwMAAIEIACCABAIAoAcAIYEEAQCeBwAhkAQCAKAHACGRBAEAnwcAIaQEQACzBwAhrwQBAJ4HACEHAwAAgwgAIIAEAgAAAAGBBAEAAAABkAQCAAAAAZEEAQAAAAGkBEAAAAABrwQBAAAAAQMqAADYCwAgqAUAANkLACCuBQAAAQAgBCoAAKIIADCoBQAAowgAMKoFAAClCAAgrgUAAKYIADAEKgAAlggAMKgFAACXCAAwqgUAAJkIACCuBQAAmggAMAQqAACNCAAwqAUAAI4IADCqBQAAkAgAIK4FAADZBwAwAAAAAAAHKgAA0wsAICsAANYLACCoBQAA1AsAIKkFAADVCwAgrAUAABoAIK0FAAAaACCuBQAAAQAgAyoAANMLACCoBQAA1AsAIK4FAAABACAAAAAAAAUqAADOCwAgKwAA0QsAIKgFAADPCwAgqQUAANALACCuBQAADwAgAyoAAM4LACCoBQAAzwsAIK4FAAAPACAAAAAAAAUqAADICwAgKwAAzAsAIKgFAADJCwAgqQUAAMsLACCuBQAAAQAgCyoAAMcIADArAADMCAAwqAUAAMgIADCpBQAAyQgAMKoFAADKCAAgqwUAAMsIADCsBQAAywgAMK0FAADLCAAwrgUAAMsIADCvBQAAzQgAMLAFAADOCAAwCIAEAgAAAAGXBAEAAAABnARAAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAboEAQAAAAG7BCAAAAABAgAAABMAICoAANIIACADAAAAEwAgKgAA0ggAICsAANEIACABIwAAygsAMA0JAACRBwAg_QMAAJAHADD-AwAAEQAQ_wMAAJAHADCABAIAAAABlwQBALkGACGcBEAAwwYAIaAEAgC4BgAhpARAALoGACGlBEAAugYAIbkEAgC4BgAhugQBALkGACG7BCAAxwYAIQIAAAATACAjAADRCAAgAgAAAM8IACAjAADQCAAgDP0DAADOCAAw_gMAAM8IABD_AwAAzggAMIAEAgC4BgAhlwQBALkGACGcBEAAwwYAIaAEAgC4BgAhpARAALoGACGlBEAAugYAIbkEAgC4BgAhugQBALkGACG7BCAAxwYAIQz9AwAAzggAMP4DAADPCAAQ_wMAAM4IADCABAIAuAYAIZcEAQC5BgAhnARAAMMGACGgBAIAuAYAIaQEQAC6BgAhpQRAALoGACG5BAIAuAYAIboEAQC5BgAhuwQgAMcGACEIgAQCAKAHACGXBAEAngcAIZwEQACxBwAhoAQCAKAHACGkBEAAswcAIaUEQACzBwAhugQBAJ4HACG7BCAAsgcAIQiABAIAoAcAIZcEAQCeBwAhnARAALEHACGgBAIAoAcAIaQEQACzBwAhpQRAALMHACG6BAEAngcAIbsEIACyBwAhCIAEAgAAAAGXBAEAAAABnARAAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAboEAQAAAAG7BCAAAAABAyoAAMgLACCoBQAAyQsAIK4FAAABACAEKgAAxwgAMKgFAADICAAwqgUAAMoIACCuBQAAywgAMAAAAAAABSoAAMALACArAADGCwAgqAUAAMELACCpBQAAxQsAIK4FAAAFACAFKgAAvgsAICsAAMMLACCoBQAAvwsAIKkFAADCCwAgrgUAAAEAIAMqAADACwAgqAUAAMELACCuBQAABQAgAyoAAL4LACCoBQAAvwsAIK4FAAABACAAAAAAAAUqAAC4CwAgKwAAvAsAIKgFAAC5CwAgqQUAALsLACCuBQAAAQAgCyoAAOUIADArAADqCAAwqAUAAOYIADCpBQAA5wgAMKoFAADoCAAgqwUAAOkIADCsBQAA6QgAMK0FAADpCAAwrgUAAOkIADCvBQAA6wgAMLAFAADsCAAwCAMAAN0IACCABAIAAAABkAQCAAAAAaQEQAAAAAG6BAEAAAABvQQBAAAAAb4EAgAAAAG_BAEAAAABAgAAAAkAICoAAPAIACADAAAACQAgKgAA8AgAICsAAO8IACABIwAAugsAMA4DAAD4BgAgBAAAlgcAIP0DAACVBwAw_gMAAAcAEP8DAACVBwAwgAQCAAAAAZAEAgC4BgAhpARAALoGACG6BAEAuQYAIbwEAgC4BgAhvQQBALkGACG-BAIAuAYAIb8EAQC5BgAhpQUAAJQHACACAAAACQAgIwAA7wgAIAIAAADtCAAgIwAA7ggAIAv9AwAA7AgAMP4DAADtCAAQ_wMAAOwIADCABAIAuAYAIZAEAgC4BgAhpARAALoGACG6BAEAuQYAIbwEAgC4BgAhvQQBALkGACG-BAIAuAYAIb8EAQC5BgAhC_0DAADsCAAw_gMAAO0IABD_AwAA7AgAMIAEAgC4BgAhkAQCALgGACGkBEAAugYAIboEAQC5BgAhvAQCALgGACG9BAEAuQYAIb4EAgC4BgAhvwQBALkGACEHgAQCAKAHACGQBAIAoAcAIaQEQACzBwAhugQBAJ4HACG9BAEAngcAIb4EAgCgBwAhvwQBAJ4HACEIAwAA2wgAIIAEAgCgBwAhkAQCAKAHACGkBEAAswcAIboEAQCeBwAhvQQBAJ4HACG-BAIAoAcAIb8EAQCeBwAhCAMAAN0IACCABAIAAAABkAQCAAAAAaQEQAAAAAG6BAEAAAABvQQBAAAAAb4EAgAAAAG_BAEAAAABAyoAALgLACCoBQAAuQsAIK4FAAABACAEKgAA5QgAMKgFAADmCAAwqgUAAOgIACCuBQAA6QgAMAAAAAAABasFCAAAAAGxBQgAAAABsgUIAAAAAbMFCAAAAAG0BQgAAAABBSoAALMLACArAAC2CwAgqAUAALQLACCpBQAAtQsAIK4FAACtAwAgAyoAALMLACCoBQAAtAsAIK4FAACtAwAgAAAAAAAFKgAArgsAICsAALELACCoBQAArwsAIKkFAACwCwAgrgUAAK0DACADKgAArgsAIKgFAACvCwAgrgUAAK0DACAAAAAAAAUqAACnCwAgKwAArAsAIKgFAACoCwAgqQUAAKsLACCuBQAAqQMAIAsqAACWCQAwKwAAmwkAMKgFAACXCQAwqQUAAJgJADCqBQAAmQkAIKsFAACaCQAwrAUAAJoJADCtBQAAmgkAMK4FAACaCQAwrwUAAJwJADCwBQAAnQkAMAsqAACKCQAwKwAAjwkAMKgFAACLCQAwqQUAAIwJADCqBQAAjQkAIKsFAACOCQAwrAUAAI4JADCtBQAAjgkAMK4FAACOCQAwrwUAAJAJADCwBQAAkQkAMAaABAIAAAABpARAAAAAAcQEQAAAAAHFBAgAAAABxgQIAAAAAccEgAAAAAECAAAAtQMAICoAAJUJACADAAAAtQMAICoAAJUJACArAACUCQAgASMAAKoLADAMiQIAAMAGACD9AwAAvQYAMP4DAACzAwAQ_wMAAL0GADCABAIAAAABpARAALoGACHDBAIAuAYAIcQEQAC6BgAhxQQIAL4GACHGBAgAvgYAIccEAAC_BgAg4QQAALwGACACAAAAtQMAICMAAJQJACACAAAAkgkAICMAAJMJACAK_QMAAJEJADD-AwAAkgkAEP8DAACRCQAwgAQCALgGACGkBEAAugYAIcMEAgC4BgAhxARAALoGACHFBAgAvgYAIcYECAC-BgAhxwQAAL8GACAK_QMAAJEJADD-AwAAkgkAEP8DAACRCQAwgAQCALgGACGkBEAAugYAIcMEAgC4BgAhxARAALoGACHFBAgAvgYAIcYECAC-BgAhxwQAAL8GACAGgAQCAKAHACGkBEAAswcAIcQEQACzBwAhxQQIAPgIACHGBAgA-AgAIccEgAAAAAEGgAQCAKAHACGkBEAAswcAIcQEQACzBwAhxQQIAPgIACHGBAgA-AgAIccEgAAAAAEGgAQCAAAAAaQEQAAAAAHEBEAAAAABxQQIAAAAAcYECAAAAAHHBIAAAAABDoAEAgAAAAGkBEAAAAABvQRAAAAAAcUECAAAAAHGBAgAAAABxwSAAAAAAcgECAAAAAHJBAgAAAABygQIAAAAAcsECAAAAAHMBAgAAAABzQQIAAAAAc4EAQAAAAHPBEAAAAABAgAAALEDACAqAAChCQAgAwAAALEDACAqAAChCQAgKwAAoAkAIAEjAACpCwAwFIkCAADABgAg_QMAAMIGADD-AwAArwMAEP8DAADCBgAwgAQCAAAAAaQEQAC6BgAhvQRAALoGACHDBAIAuAYAIcUECAC-BgAhxgQIAL4GACHHBAAAvwYAIMgECAC-BgAhyQQIAL4GACHKBAgAvgYAIcsECAC-BgAhzAQIAL4GACHNBAgAvgYAIc4EAQC5BgAhzwRAAMMGACHiBAAAwQYAIAIAAACxAwAgIwAAoAkAIAIAAACeCQAgIwAAnwkAIBL9AwAAnQkAMP4DAACeCQAQ_wMAAJ0JADCABAIAuAYAIaQEQAC6BgAhvQRAALoGACHDBAIAuAYAIcUECAC-BgAhxgQIAL4GACHHBAAAvwYAIMgECAC-BgAhyQQIAL4GACHKBAgAvgYAIcsECAC-BgAhzAQIAL4GACHNBAgAvgYAIc4EAQC5BgAhzwRAAMMGACES_QMAAJ0JADD-AwAAngkAEP8DAACdCQAwgAQCALgGACGkBEAAugYAIb0EQAC6BgAhwwQCALgGACHFBAgAvgYAIcYECAC-BgAhxwQAAL8GACDIBAgAvgYAIckECAC-BgAhygQIAL4GACHLBAgAvgYAIcwECAC-BgAhzQQIAL4GACHOBAEAuQYAIc8EQADDBgAhDoAEAgCgBwAhpARAALMHACG9BEAAswcAIcUECAD4CAAhxgQIAPgIACHHBIAAAAAByAQIAPgIACHJBAgA-AgAIcoECAD4CAAhywQIAPgIACHMBAgA-AgAIc0ECAD4CAAhzgQBAJ4HACHPBEAAsQcAIQ6ABAIAoAcAIaQEQACzBwAhvQRAALMHACHFBAgA-AgAIcYECAD4CAAhxwSAAAAAAcgECAD4CAAhyQQIAPgIACHKBAgA-AgAIcsECAD4CAAhzAQIAPgIACHNBAgA-AgAIc4EAQCeBwAhzwRAALEHACEOgAQCAAAAAaQEQAAAAAG9BEAAAAABxQQIAAAAAcYECAAAAAHHBIAAAAAByAQIAAAAAckECAAAAAHKBAgAAAABywQIAAAAAcwECAAAAAHNBAgAAAABzgQBAAAAAc8EQAAAAAEDKgAApwsAIKgFAACoCwAgrgUAAKkDACAEKgAAlgkAMKgFAACXCQAwqgUAAJkJACCuBQAAmgkAMAQqAACKCQAwqAUAAIsJADCqBQAAjQkAIK4FAACOCQAwAAAAAAALKgAAqwkAMCsAALAJADCoBQAArAkAMKkFAACtCQAwqgUAAK4JACCrBQAArwkAMKwFAACvCQAwrQUAAK8JADCuBQAArwkAMK8FAACxCQAwsAUAALIJADASigIAAKMJACCLAgAApAkAIIAEAgAAAAGEBAEAAAABpARAAAAAAdAEAQAAAAHSBAEAAAAB0wQBAAAAAdQEAgAAAAHVBAIAAAAB1gQCAAAAAdcEAgAAAAHYBAIAAAAB2QQCAAAAAdoEAgAAAAHbBEAAAAAB3AQgAAAAAd0EQAAAAAECAAAArQMAICoAALYJACADAAAArQMAICoAALYJACArAAC1CQAgASMAAKYLADAXiAIAAMgGACCKAgAAyQYAIIsCAADKBgAg_QMAAMQGADD-AwAAqwMAEP8DAADEBgAwgAQCAAAAAYQEAQC5BgAhpARAALoGACHQBAEAAAAB0QQBALkGACHSBAEAuQYAIdMEAQDFBgAh1AQCALgGACHVBAIAuAYAIdYEAgC4BgAh1wQCALgGACHYBAIAuAYAIdkEAgC4BgAh2gQCAMYGACHbBEAAwwYAIdwEIADHBgAh3QRAAMMGACECAAAArQMAICMAALUJACACAAAAswkAICMAALQJACAU_QMAALIJADD-AwAAswkAEP8DAACyCQAwgAQCALgGACGEBAEAuQYAIaQEQAC6BgAh0AQBALkGACHRBAEAuQYAIdIEAQC5BgAh0wQBAMUGACHUBAIAuAYAIdUEAgC4BgAh1gQCALgGACHXBAIAuAYAIdgEAgC4BgAh2QQCALgGACHaBAIAxgYAIdsEQADDBgAh3AQgAMcGACHdBEAAwwYAIRT9AwAAsgkAMP4DAACzCQAQ_wMAALIJADCABAIAuAYAIYQEAQC5BgAhpARAALoGACHQBAEAuQYAIdEEAQC5BgAh0gQBALkGACHTBAEAxQYAIdQEAgC4BgAh1QQCALgGACHWBAIAuAYAIdcEAgC4BgAh2AQCALgGACHZBAIAuAYAIdoEAgDGBgAh2wRAAMMGACHcBCAAxwYAId0EQADDBgAhEIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdAEAQCeBwAh0gQBAJ4HACHTBAEAnwcAIdQEAgCgBwAh1QQCAKAHACHWBAIAoAcAIdcEAgCgBwAh2AQCAKAHACHZBAIAoAcAIdoEAgC0BwAh2wRAALEHACHcBCAAsgcAId0EQACxBwAhEooCAACICQAgiwIAAIkJACCABAIAoAcAIYQEAQCeBwAhpARAALMHACHQBAEAngcAIdIEAQCeBwAh0wQBAJ8HACHUBAIAoAcAIdUEAgCgBwAh1gQCAKAHACHXBAIAoAcAIdgEAgCgBwAh2QQCAKAHACHaBAIAtAcAIdsEQACxBwAh3AQgALIHACHdBEAAsQcAIRKKAgAAowkAIIsCAACkCQAggAQCAAAAAYQEAQAAAAGkBEAAAAAB0AQBAAAAAdIEAQAAAAHTBAEAAAAB1AQCAAAAAdUEAgAAAAHWBAIAAAAB1wQCAAAAAdgEAgAAAAHZBAIAAAAB2gQCAAAAAdsEQAAAAAHcBCAAAAAB3QRAAAAAAQQqAACrCQAwqAUAAKwJADCqBQAArgkAIK4FAACvCQAwAAeIAgAAugkAIIoCAAC7CQAgiwIAALwJACDTBAAAmAcAINoEAACYBwAg2wQAAJgHACDdBAAAmAcAIAGMAgAAuAkAIAAAAAAAAAAAAAAAAAWrBQgAAAABsQUIAAAAAbIFCAAAAAGzBQgAAAABtAUIAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAABasFBAAAAAGxBQQAAAABsgUEAAAAAbMFBAAAAAG0BQQAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAFKgAAoQsAICsAAKQLACCoBQAAogsAIKkFAACjCwAgrgUAAJQBACADKgAAoQsAIKgFAACiCwAgrgUAAJQBACAAAAAAAAsqAAD5CQAwKwAA_gkAMKgFAAD6CQAwqQUAAPsJADCqBQAA_AkAIKsFAAD9CQAwrAUAAP0JADCtBQAA_QkAMK4FAAD9CQAwrwUAAP8JADCwBQAAgAoAMAWABAIAAAABpARAAAAAAaUEQAAAAAHABAEAAAABmAUIAAAAAQIAAACYAQAgKgAAhAoAIAMAAACYAQAgKgAAhAoAICsAAIMKACABIwAAoAsAMAtWAADtBgAg_QMAAOwGADD-AwAAlgEAEP8DAADsBgAwgAQCAAAAAaQEQAC6BgAhpQRAALoGACHABAEAuQYAIZcFAgC4BgAhmAUIANEGACGdBQAA6wYAIAIAAACYAQAgIwAAgwoAIAIAAACBCgAgIwAAggoAIAn9AwAAgAoAMP4DAACBCgAQ_wMAAIAKADCABAIAuAYAIaQEQAC6BgAhpQRAALoGACHABAEAuQYAIZcFAgC4BgAhmAUIANEGACEJ_QMAAIAKADD-AwAAgQoAEP8DAACACgAwgAQCALgGACGkBEAAugYAIaUEQAC6BgAhwAQBALkGACGXBQIAuAYAIZgFCADRBgAhBYAEAgCgBwAhpARAALMHACGlBEAAswcAIcAEAQCeBwAhmAUIAMcJACEFgAQCAKAHACGkBEAAswcAIaUEQACzBwAhwAQBAJ4HACGYBQgAxwkAIQWABAIAAAABpARAAAAAAaUEQAAAAAHABAEAAAABmAUIAAAAAQQqAAD5CQAwqAUAAPoJADCqBQAA_AkAIK4FAAD9CQAwAAFXAACGCgAgAAAAAAAAAAAAAAAAAAAACyoAAPEKADArAAD2CgAwqAUAAPIKADCpBQAA8woAMKoFAAD0CgAgqwUAAPUKADCsBQAA9QoAMK0FAAD1CgAwrgUAAPUKADCvBQAA9woAMLAFAAD4CgAwCyoAAOgKADArAADsCgAwqAUAAOkKADCpBQAA6goAMKoFAADrCgAgqwUAAOkIADCsBQAA6QgAMK0FAADpCAAwrgUAAOkIADCvBQAA7QoAMLAFAADsCAAwCyoAANwKADArAADhCgAwqAUAAN0KADCpBQAA3goAMKoFAADfCgAgqwUAAOAKADCsBQAA4AoAMK0FAADgCgAwrgUAAOAKADCvBQAA4goAMLAFAADjCgAwCyoAANAKADArAADVCgAwqAUAANEKADCpBQAA0goAMKoFAADTCgAgqwUAANQKADCsBQAA1AoAMK0FAADUCgAwrgUAANQKADCvBQAA1goAMLAFAADXCgAwCyoAAMQKADArAADJCgAwqAUAAMUKADCpBQAAxgoAMKoFAADHCgAgqwUAAMgKADCsBQAAyAoAMK0FAADICgAwrgUAAMgKADCvBQAAygoAMLAFAADLCgAwCyoAALsKADArAAC_CgAwqAUAALwKADCpBQAAvQoAMKoFAAC-CgAgqwUAAKYIADCsBQAApggAMK0FAACmCAAwrgUAAKYIADCvBQAAwAoAMLAFAACpCAAwCyoAALIKADArAAC2CgAwqAUAALMKADCpBQAAtAoAMKoFAAC1CgAgqwUAANkHADCsBQAA2QcAMK0FAADZBwAwrgUAANkHADCvBQAAtwoAMLAFAADcBwAwCyoAAKkKADArAACtCgAwqAUAAKoKADCpBQAAqwoAMKoFAACsCgAgqwUAANkHADCsBQAA2QcAMK0FAADZBwAwrgUAANkHADCvBQAArgoAMLAFAADcBwAwCyoAAKAKADArAACkCgAwqAUAAKEKADCpBQAAogoAMKoFAACjCgAgqwUAAM0HADCsBQAAzQcAMK0FAADNBwAwrgUAAM0HADCvBQAApQoAMLAFAADQBwAwBRUAAKoHACCABAIAAAABgQQBAAAAAYIEAQAAAAGDBAIAAAABAgAAADEAICoAAKgKACADAAAAMQAgKgAAqAoAICsAAKcKACABIwAAnwsAMAIAAAAxACAjAACnCgAgAgAAANEHACAjAACmCgAgBIAEAgCgBwAhgQQBAJ4HACGCBAEAnwcAIYMEAgCgBwAhBRUAAKgHACCABAIAoAcAIYEEAQCeBwAhggQBAJ8HACGDBAIAoAcAIQUVAACqBwAggAQCAAAAAYEEAQAAAAGCBAEAAAABgwQCAAAAAR4OAADhBwAgDwAA5gcAIBAAAOIHACARAADoBwAgEgAA4wcAIBMAAOQHACAWAADnBwAggAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAECAAAAKgAgKgAAsQoAIAMAAAAqACAqAACxCgAgKwAAsAoAIAEjAACeCwAwAgAAACoAICMAALAKACACAAAA3QcAICMAAK8KACAXgAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlQQCAKAHACGWBAIAtAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHg4AALUHACAPAAC7BwAgEAAAtgcAIBEAALcHACASAAC4BwAgEwAAuQcAIBYAALwHACCABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhnQQCALQHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEeDgAA4QcAIA8AAOYHACAQAADiBwAgEQAA6AcAIBIAAOMHACATAADkBwAgFgAA5wcAIIAEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZUEAgAAAAGWBAIAAAABlwQBAAAAAZgEAQAAAAGZBAEAAAABmgRAAAAAAZsEAQAAAAGcBEAAAAABnQQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABHg4AAOEHACAPAADmBwAgEAAA4gcAIBEAAOgHACASAADjBwAgFAAA5QcAIBYAAOcHACCABAIAAAABgQQBAAAAAZEEAQAAAAGSBAEAAAABkwQBAAAAAZQEAgAAAAGVBAIAAAABlgQCAAAAAZcEAQAAAAGYBAEAAAABmQQBAAAAAZoEQAAAAAGbBAEAAAABnARAAAAAAZ4EAgAAAAGfBIAAAAABoAQCAAAAAaEEIAAAAAGiBEAAAAABowQBAAAAAaQEQAAAAAGlBEAAAAABpgQCAAAAAQIAAAAqACAqAAC6CgAgAwAAACoAICoAALoKACArAAC5CgAgASMAAJ0LADACAAAAKgAgIwAAuQoAIAIAAADdBwAgIwAAuAoAIBeABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEeDgAAtQcAIA8AALsHACAQAAC2BwAgEQAAtwcAIBIAALgHACAUAAC6BwAgFgAAvAcAIIAEAgCgBwAhgQQBAJ4HACGRBAEAnwcAIZIEAQCfBwAhkwQBAJ8HACGUBAIAoAcAIZUEAgCgBwAhlgQCALQHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGeBAIAoAcAIZ8EgAAAAAGgBAIAoAcAIaEEIACyBwAhogRAALEHACGjBAEAnwcAIaQEQACzBwAhpQRAALMHACGmBAIAtAcAIR4OAADhBwAgDwAA5gcAIBAAAOIHACARAADoBwAgEgAA4wcAIBQAAOUHACAWAADnBwAggAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGeBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAEHDgAAgggAIIAEAgAAAAGBBAEAAAABkQQBAAAAAZQEAgAAAAGkBEAAAAABrwQBAAAAAQIAAAAiACAqAADDCgAgAwAAACIAICoAAMMKACArAADCCgAgASMAAJwLADACAAAAIgAgIwAAwgoAIAIAAACqCAAgIwAAwQoAIAaABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGUBAIAoAcAIaQEQACzBwAhrwQBAJ4HACEHDgAAgAgAIIAEAgCgBwAhgQQBAJ4HACGRBAEAnwcAIZQEAgCgBwAhpARAALMHACGvBAEAngcAIQcOAACCCAAggAQCAAAAAYEEAQAAAAGRBAEAAAABlAQCAAAAAaQEQAAAAAGvBAEAAAABDw8AAK8IACAXAACxCAAgGAAAsAgAIIAEAgAAAAGBBAEAAAABhAQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbAEAQAAAAGyBCAAAAABswSAAAAAAbQEgAAAAAG1BAEAAAABtgQBAAAAAQIAAAAeACAqAADPCgAgAwAAAB4AICoAAM8KACArAADOCgAgASMAAJsLADAUDQAA-AYAIA8AAIcHACAXAAD-BgAgGAAAiAcAIP0DAACGBwAw_gMAABwAEP8DAACGBwAwgAQCAAAAAYEEAQAAAAGEBAEAuQYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbAEAQAAAAGxBAIAuAYAIbIEIADHBgAhswQAAPAGACC0BAAAvwYAILUEAQDFBgAhtgQBALkGACECAAAAHgAgIwAAzgoAIAIAAADMCgAgIwAAzQoAIBD9AwAAywoAMP4DAADMCgAQ_wMAAMsKADCABAIAuAYAIYEEAQC5BgAhhAQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACGwBAEAuQYAIbEEAgC4BgAhsgQgAMcGACGzBAAA8AYAILQEAAC_BgAgtQQBAMUGACG2BAEAuQYAIRD9AwAAywoAMP4DAADMCgAQ_wMAAMsKADCABAIAuAYAIYEEAQC5BgAhhAQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACGwBAEAuQYAIbEEAgC4BgAhsgQgAMcGACGzBAAA8AYAILQEAAC_BgAgtQQBAMUGACG2BAEAuQYAIQyABAIAoAcAIYEEAQCeBwAhhAQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACGwBAEAngcAIbIEIACyBwAhswSAAAAAAbQEgAAAAAG1BAEAnwcAIbYEAQCeBwAhDw8AAIoIACAXAACMCAAgGAAAiwgAIIAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbAEAQCeBwAhsgQgALIHACGzBIAAAAABtASAAAAAAbUEAQCfBwAhtgQBAJ4HACEPDwAArwgAIBcAALEIACAYAACwCAAggAQCAAAAAYEEAQAAAAGEBAEAAAABoQQgAAAAAaQEQAAAAAGlBEAAAAABsAQBAAAAAbIEIAAAAAGzBIAAAAABtASAAAAAAbUEAQAAAAG2BAEAAAABBwqAAAAAAYAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAG3BAEAAAABuAQgAAAAAQIAAAAYACAqAADbCgAgAwAAABgAICoAANsKACArAADaCgAgASMAAJoLADAMAwAA_wYAIAoAAPAGACD9AwAAjwcAMP4DAAAWABD_AwAAjwcAMIAEAgAAAAGEBAEAuQYAIZAEAgDGBgAhpARAALoGACGlBEAAugYAIbcEAQC5BgAhuAQgAMcGACECAAAAGAAgIwAA2goAIAIAAADYCgAgIwAA2QoAIAsKAADwBgAg_QMAANcKADD-AwAA2AoAEP8DAADXCgAwgAQCALgGACGEBAEAuQYAIZAEAgDGBgAhpARAALoGACGlBEAAugYAIbcEAQC5BgAhuAQgAMcGACELCgAA8AYAIP0DAADXCgAw_gMAANgKABD_AwAA1woAMIAEAgC4BgAhhAQBALkGACGQBAIAxgYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIbgEIADHBgAhBwqAAAAAAYAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhtwQBAJ4HACG4BCAAsgcAIQcKgAAAAAGABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIbcEAQCeBwAhuAQgALIHACEHCoAAAAABgAQCAAAAAYQEAQAAAAGkBEAAAAABpQRAAAAAAbcEAQAAAAG4BCAAAAABCAoAANQIACCABAIAAAABlwQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbcEAQAAAAG6BAEAAAABAgAAAA8AICoAAOcKACADAAAADwAgKgAA5woAICsAAOYKACABIwAAmQsAMA0DAAD4BgAgCgAAkwcAIP0DAACSBwAw_gMAAA0AEP8DAACSBwAwgAQCAAAAAZAEAgC4BgAhlwQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIboEAQC5BgAhAgAAAA8AICMAAOYKACACAAAA5AoAICMAAOUKACAL_QMAAOMKADD-AwAA5AoAEP8DAADjCgAwgAQCALgGACGQBAIAuAYAIZcEAQC5BgAhoQQgAMcGACGkBEAAugYAIaUEQAC6BgAhtwQBALkGACG6BAEAuQYAIQv9AwAA4woAMP4DAADkCgAQ_wMAAOMKADCABAIAuAYAIZAEAgC4BgAhlwQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIboEAQC5BgAhB4AEAgCgBwAhlwQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACG3BAEAngcAIboEAQCeBwAhCAoAAMYIACCABAIAoAcAIZcEAQCeBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhtwQBAJ4HACG6BAEAngcAIQgKAADUCAAggAQCAAAAAZcEAQAAAAGhBCAAAAABpARAAAAAAaUEQAAAAAG3BAEAAAABugQBAAAAAQgEAADcCAAggAQCAAAAAaQEQAAAAAG6BAEAAAABvAQCAAAAAb0EAQAAAAG-BAIAAAABvwQBAAAAAQIAAAAJACAqAADwCgAgAwAAAAkAICoAAPAKACArAADvCgAgASMAAJgLADACAAAACQAgIwAA7woAIAIAAADtCAAgIwAA7goAIAeABAIAoAcAIaQEQACzBwAhugQBAJ4HACG8BAIAoAcAIb0EAQCeBwAhvgQCAKAHACG_BAEAngcAIQgEAADaCAAggAQCAKAHACGkBEAAswcAIboEAQCeBwAhvAQCAKAHACG9BAEAngcAIb4EAgCgBwAhvwQBAJ4HACEIBAAA3AgAIIAEAgAAAAGkBEAAAAABugQBAAAAAbwEAgAAAAG9BAEAAAABvgQCAAAAAb8EAQAAAAEMBQAA8ggAIIAEAgAAAAGEBAEAAAABoAQCAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbUEAQAAAAG6BAEAAAABwAQBAAAAAcEEAgAAAAHCBAEAAAABAgAAAAUAICoAAPwKACADAAAABQAgKgAA_AoAICsAAPsKACABIwAAlwsAMBEDAAD4BgAgBQAAiwcAIP0DAACXBwAw_gMAAAMAEP8DAACXBwAwgAQCAAAAAYQEAQC5BgAhkAQCALgGACGgBAIAuAYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbUEAQC5BgAhugQBALkGACHABAEAuQYAIcEEAgC4BgAhwgQBALkGACECAAAABQAgIwAA-woAIAIAAAD5CgAgIwAA-goAIA_9AwAA-AoAMP4DAAD5CgAQ_wMAAPgKADCABAIAuAYAIYQEAQC5BgAhkAQCALgGACGgBAIAuAYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbUEAQC5BgAhugQBALkGACHABAEAuQYAIcEEAgC4BgAhwgQBALkGACEP_QMAAPgKADD-AwAA-QoAEP8DAAD4CgAwgAQCALgGACGEBAEAuQYAIZAEAgC4BgAhoAQCALgGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG1BAEAuQYAIboEAQC5BgAhwAQBALkGACHBBAIAuAYAIcIEAQC5BgAhC4AEAgCgBwAhhAQBAJ4HACGgBAIAoAcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbUEAQCeBwAhugQBAJ4HACHABAEAngcAIcEEAgCgBwAhwgQBAJ4HACEMBQAA5AgAIIAEAgCgBwAhhAQBAJ4HACGgBAIAoAcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbUEAQCeBwAhugQBAJ4HACHABAEAngcAIcEEAgCgBwAhwgQBAJ4HACEMBQAA8ggAIIAEAgAAAAGEBAEAAAABoAQCAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbUEAQAAAAG6BAEAAAABwAQBAAAAAcEEAgAAAAHCBAEAAAABBCoAAPEKADCoBQAA8goAMKoFAAD0CgAgrgUAAPUKADAEKgAA6AoAMKgFAADpCgAwqgUAAOsKACCuBQAA6QgAMAQqAADcCgAwqAUAAN0KADCqBQAA3woAIK4FAADgCgAwBCoAANAKADCoBQAA0QoAMKoFAADTCgAgrgUAANQKADAEKgAAxAoAMKgFAADFCgAwqgUAAMcKACCuBQAAyAoAMAQqAAC7CgAwqAUAALwKADCqBQAAvgoAIK4FAACmCAAwBCoAALIKADCoBQAAswoAMKoFAAC1CgAgrgUAANkHADAEKgAAqQoAMKgFAACqCgAwqgUAAKwKACCuBQAA2QcAMAQqAACgCgAwqAUAAKEKADCqBQAAowoAIK4FAADNBwAwAAAAAAAAAAATDgAAkAsAIA8AAI0LACAQAACRCwAgEQAAjgsAIBIAAIwLACATAACPCwAgFAAAjwsAIBYAAJILACCRBAAAmAcAIJIEAACYBwAgkwQAAJgHACCWBAAAmAcAIJkEAACYBwAgmgQAAJgHACCcBAAAmAcAIJ0EAACYBwAgogQAAJgHACCjBAAAmAcAIKYEAACYBwAgCQcAAIYLACAIAACHCwAgCwAAiAsAIAwAAIkLACAZAACKCwAgGgAAiwsAIBsAAIwLACAcAACMCwAgHQAAjQsAIAYNAACPCwAgDwAAiwsAIBcAAIwLACAYAACTCwAgtAQAAJgHACC1BAAAmAcAIAQOAACQCwAgFwAAjAsAIJEEAACYBwAgrQQAAJgHACAAAAIDAACPCwAgCgAAlQsAIAACAwAAjwsAIAUAAIcLACALgAQCAAAAAYQEAQAAAAGgBAIAAAABoQQgAAAAAaQEQAAAAAGlBEAAAAABtQQBAAAAAboEAQAAAAHABAEAAAABwQQCAAAAAcIEAQAAAAEHgAQCAAAAAaQEQAAAAAG6BAEAAAABvAQCAAAAAb0EAQAAAAG-BAIAAAABvwQBAAAAAQeABAIAAAABlwQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbcEAQAAAAG6BAEAAAABBwqAAAAAAYAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAG3BAEAAAABuAQgAAAAAQyABAIAAAABgQQBAAAAAYQEAQAAAAGhBCAAAAABpARAAAAAAaUEQAAAAAGwBAEAAAABsgQgAAAAAbMEgAAAAAG0BIAAAAABtQQBAAAAAbYEAQAAAAEGgAQCAAAAAYEEAQAAAAGRBAEAAAABlAQCAAAAAaQEQAAAAAGvBAEAAAABF4AEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZUEAgAAAAGWBAIAAAABlwQBAAAAAZgEAQAAAAGZBAEAAAABmgRAAAAAAZsEAQAAAAGcBEAAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABF4AEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZUEAgAAAAGWBAIAAAABlwQBAAAAAZgEAQAAAAGZBAEAAAABmgRAAAAAAZsEAQAAAAGcBEAAAAABnQQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABBIAEAgAAAAGBBAEAAAABggQBAAAAAYMEAgAAAAEFgAQCAAAAAaQEQAAAAAGlBEAAAAABwAQBAAAAAZgFCAAAAAEKgAQCAAAAAYQEAQAAAAGXBAEAAAABpARAAAAAAaUEQAAAAAG_BAEAAAABmQUBAAAAAZoFAQAAAAGbBQEAAAABnAUgAAAAAQIAAACUAQAgKgAAoQsAIAMAAACcAQAgKgAAoQsAICsAAKULACAMAAAAnAEAICMAAKULACCABAIAoAcAIYQEAQCeBwAhlwQBAJ4HACGkBEAAswcAIaUEQACzBwAhvwQBAJ4HACGZBQEAngcAIZoFAQCeBwAhmwUBAJ4HACGcBSAAsgcAIQqABAIAoAcAIYQEAQCeBwAhlwQBAJ4HACGkBEAAswcAIaUEQACzBwAhvwQBAJ4HACGZBQEAngcAIZoFAQCeBwAhmwUBAJ4HACGcBSAAsgcAIRCABAIAAAABhAQBAAAAAaQEQAAAAAHQBAEAAAAB0gQBAAAAAdMEAQAAAAHUBAIAAAAB1QQCAAAAAdYEAgAAAAHXBAIAAAAB2AQCAAAAAdkEAgAAAAHaBAIAAAAB2wRAAAAAAdwEIAAAAAHdBEAAAAABBIAEAgAAAAGEBAEAAAABpARAAAAAAdEEAQAAAAECAAAAqQMAICoAAKcLACAOgAQCAAAAAaQEQAAAAAG9BEAAAAABxQQIAAAAAcYECAAAAAHHBIAAAAAByAQIAAAAAckECAAAAAHKBAgAAAABywQIAAAAAcwECAAAAAHNBAgAAAABzgQBAAAAAc8EQAAAAAEGgAQCAAAAAaQEQAAAAAHEBEAAAAABxQQIAAAAAcYECAAAAAHHBIAAAAABAwAAALsDACAqAACnCwAgKwAArQsAIAYAAAC7AwAgIwAArQsAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdEEAQCeBwAhBIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdEEAQCeBwAhE4gCAACiCQAgiwIAAKQJACCABAIAAAABhAQBAAAAAaQEQAAAAAHQBAEAAAAB0QQBAAAAAdIEAQAAAAHTBAEAAAAB1AQCAAAAAdUEAgAAAAHWBAIAAAAB1wQCAAAAAdgEAgAAAAHZBAIAAAAB2gQCAAAAAdsEQAAAAAHcBCAAAAAB3QRAAAAAAQIAAACtAwAgKgAArgsAIAMAAACrAwAgKgAArgsAICsAALILACAVAAAAqwMAICMAALILACCIAgAAhwkAIIsCAACJCQAggAQCAKAHACGEBAEAngcAIaQEQACzBwAh0AQBAJ4HACHRBAEAngcAIdIEAQCeBwAh0wQBAJ8HACHUBAIAoAcAIdUEAgCgBwAh1gQCAKAHACHXBAIAoAcAIdgEAgCgBwAh2QQCAKAHACHaBAIAtAcAIdsEQACxBwAh3AQgALIHACHdBEAAsQcAIROIAgAAhwkAIIsCAACJCQAggAQCAKAHACGEBAEAngcAIaQEQACzBwAh0AQBAJ4HACHRBAEAngcAIdIEAQCeBwAh0wQBAJ8HACHUBAIAoAcAIdUEAgCgBwAh1gQCAKAHACHXBAIAoAcAIdgEAgCgBwAh2QQCAKAHACHaBAIAtAcAIdsEQACxBwAh3AQgALIHACHdBEAAsQcAIROIAgAAogkAIIoCAACjCQAggAQCAAAAAYQEAQAAAAGkBEAAAAAB0AQBAAAAAdEEAQAAAAHSBAEAAAAB0wQBAAAAAdQEAgAAAAHVBAIAAAAB1gQCAAAAAdcEAgAAAAHYBAIAAAAB2QQCAAAAAdoEAgAAAAHbBEAAAAAB3AQgAAAAAd0EQAAAAAECAAAArQMAICoAALMLACADAAAAqwMAICoAALMLACArAAC3CwAgFQAAAKsDACAjAAC3CwAgiAIAAIcJACCKAgAAiAkAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdAEAQCeBwAh0QQBAJ4HACHSBAEAngcAIdMEAQCfBwAh1AQCAKAHACHVBAIAoAcAIdYEAgCgBwAh1wQCAKAHACHYBAIAoAcAIdkEAgCgBwAh2gQCALQHACHbBEAAsQcAIdwEIACyBwAh3QRAALEHACETiAIAAIcJACCKAgAAiAkAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdAEAQCeBwAh0QQBAJ4HACHSBAEAngcAIdMEAQCfBwAh1AQCAKAHACHVBAIAoAcAIdYEAgCgBwAh1wQCAKAHACHYBAIAoAcAIdkEAgCgBwAh2gQCALQHACHbBEAAsQcAIdwEIACyBwAh3QRAALEHACEOCAAA_goAIAsAAP8KACAMAACACwAgGQAAgQsAIBoAAIILACAbAACDCwAgHAAAhAsAIB0AAIULACCABAIAAAABhAQBAAAAAaQEQAAAAAGlBEAAAAABnwUBAAAAAaAFAQAAAAECAAAAAQAgKgAAuAsAIAeABAIAAAABkAQCAAAAAaQEQAAAAAG6BAEAAAABvQQBAAAAAb4EAgAAAAG_BAEAAAABAwAAABoAICoAALgLACArAAC9CwAgEAAAABoAIAgAAJgKACALAACZCgAgDAAAmgoAIBkAAJsKACAaAACcCgAgGwAAnQoAIBwAAJ4KACAdAACfCgAgIwAAvQsAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4IAACYCgAgCwAAmQoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4HAAD9CgAgCwAA_woAIAwAAIALACAZAACBCwAgGgAAggsAIBsAAIMLACAcAACECwAgHQAAhQsAIIAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAGfBQEAAAABoAUBAAAAAQIAAAABACAqAAC-CwAgDQMAAPEIACCABAIAAAABhAQBAAAAAZAEAgAAAAGgBAIAAAABoQQgAAAAAaQEQAAAAAGlBEAAAAABtQQBAAAAAboEAQAAAAHABAEAAAABwQQCAAAAAcIEAQAAAAECAAAABQAgKgAAwAsAIAMAAAAaACAqAAC-CwAgKwAAxAsAIBAAAAAaACAHAACXCgAgCwAAmQoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAICMAAMQLACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEOBwAAlwoAIAsAAJkKACAMAACaCgAgGQAAmwoAIBoAAJwKACAbAACdCgAgHAAAngoAIB0AAJ8KACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEDAAAAAwAgKgAAwAsAICsAAMcLACAPAAAAAwAgAwAA4wgAICMAAMcLACCABAIAoAcAIYQEAQCeBwAhkAQCAKAHACGgBAIAoAcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbUEAQCeBwAhugQBAJ4HACHABAEAngcAIcEEAgCgBwAhwgQBAJ4HACENAwAA4wgAIIAEAgCgBwAhhAQBAJ4HACGQBAIAoAcAIaAEAgCgBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhtQQBAJ4HACG6BAEAngcAIcAEAQCeBwAhwQQCAKAHACHCBAEAngcAIQ4HAAD9CgAgCAAA_goAIAwAAIALACAZAACBCwAgGgAAggsAIBsAAIMLACAcAACECwAgHQAAhQsAIIAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAGfBQEAAAABoAUBAAAAAQIAAAABACAqAADICwAgCIAEAgAAAAGXBAEAAAABnARAAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAboEAQAAAAG7BCAAAAABAwAAABoAICoAAMgLACArAADNCwAgEAAAABoAIAcAAJcKACAIAACYCgAgDAAAmgoAIBkAAJsKACAaAACcCgAgGwAAnQoAIBwAAJ4KACAdAACfCgAgIwAAzQsAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4HAACXCgAgCAAAmAoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQkDAADTCAAggAQCAAAAAZAEAgAAAAGXBAEAAAABoQQgAAAAAaQEQAAAAAGlBEAAAAABtwQBAAAAAboEAQAAAAECAAAADwAgKgAAzgsAIAMAAAANACAqAADOCwAgKwAA0gsAIAsAAAANACADAADFCAAgIwAA0gsAIIAEAgCgBwAhkAQCAKAHACGXBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbcEAQCeBwAhugQBAJ4HACEJAwAAxQgAIIAEAgCgBwAhkAQCAKAHACGXBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbcEAQCeBwAhugQBAJ4HACEOBwAA_QoAIAgAAP4KACALAAD_CgAgGQAAgQsAIBoAAIILACAbAACDCwAgHAAAhAsAIB0AAIULACCABAIAAAABhAQBAAAAAaQEQAAAAAGlBEAAAAABnwUBAAAAAaAFAQAAAAECAAAAAQAgKgAA0wsAIAMAAAAaACAqAADTCwAgKwAA1wsAIBAAAAAaACAHAACXCgAgCAAAmAoAIAsAAJkKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAICMAANcLACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEOBwAAlwoAIAgAAJgKACALAACZCgAgGQAAmwoAIBoAAJwKACAbAACdCgAgHAAAngoAIB0AAJ8KACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEOBwAA_QoAIAgAAP4KACALAAD_CgAgDAAAgAsAIBoAAIILACAbAACDCwAgHAAAhAsAIB0AAIULACCABAIAAAABhAQBAAAAAaQEQAAAAAGlBEAAAAABnwUBAAAAAaAFAQAAAAECAAAAAQAgKgAA2AsAIAaABAIAAAABgQQBAAAAAZAEAgAAAAGRBAEAAAABpARAAAAAAa8EAQAAAAEJgAQCAAAAAYEEAQAAAAGEBAEAAAABkQQBAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAa0EAQAAAAGuBAEAAAABF4AEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABAwAAABoAICoAANgLACArAADfCwAgEAAAABoAIAcAAJcKACAIAACYCgAgCwAAmQoAIAwAAJoKACAaAACcCgAgGwAAnQoAIBwAAJ4KACAdAACfCgAgIwAA3wsAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4HAACXCgAgCAAAmAoAIAsAAJkKACAMAACaCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4HAAD9CgAgCAAA_goAIAsAAP8KACAMAACACwAgGQAAgQsAIBsAAIMLACAcAACECwAgHQAAhQsAIIAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAGfBQEAAAABoAUBAAAAAQIAAAABACAqAADgCwAgEA0AAK4IACAXAACxCAAgGAAAsAgAIIAEAgAAAAGBBAEAAAABhAQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbAEAQAAAAGxBAIAAAABsgQgAAAAAbMEgAAAAAG0BIAAAAABtQQBAAAAAbYEAQAAAAECAAAAHgAgKgAA4gsAIAMAAAAaACAqAADgCwAgKwAA5gsAIBAAAAAaACAHAACXCgAgCAAAmAoAIAsAAJkKACAMAACaCgAgGQAAmwoAIBsAAJ0KACAcAACeCgAgHQAAnwoAICMAAOYLACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEOBwAAlwoAIAgAAJgKACALAACZCgAgDAAAmgoAIBkAAJsKACAbAACdCgAgHAAAngoAIB0AAJ8KACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEDAAAAHAAgKgAA4gsAICsAAOkLACASAAAAHAAgDQAAiQgAIBcAAIwIACAYAACLCAAgIwAA6QsAIIAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbAEAQCeBwAhsQQCAKAHACGyBCAAsgcAIbMEgAAAAAG0BIAAAAABtQQBAJ8HACG2BAEAngcAIRANAACJCAAgFwAAjAgAIBgAAIsIACCABAIAoAcAIYEEAQCeBwAhhAQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACGwBAEAngcAIbEEAgCgBwAhsgQgALIHACGzBIAAAAABtASAAAAAAbUEAQCfBwAhtgQBAJ4HACEQDQAArggAIA8AAK8IACAXAACxCAAggAQCAAAAAYEEAQAAAAGEBAEAAAABoQQgAAAAAaQEQAAAAAGlBEAAAAABsAQBAAAAAbEEAgAAAAGyBCAAAAABswSAAAAAAbQEgAAAAAG1BAEAAAABtgQBAAAAAQIAAAAeACAqAADqCwAgF4AEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABAwAAABwAICoAAOoLACArAADvCwAgEgAAABwAIA0AAIkIACAPAACKCAAgFwAAjAgAICMAAO8LACCABAIAoAcAIYEEAQCeBwAhhAQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACGwBAEAngcAIbEEAgCgBwAhsgQgALIHACGzBIAAAAABtASAAAAAAbUEAQCfBwAhtgQBAJ4HACEQDQAAiQgAIA8AAIoIACAXAACMCAAggAQCAKAHACGBBAEAngcAIYQEAQCeBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhsAQBAJ4HACGxBAIAoAcAIbIEIACyBwAhswSAAAAAAbQEgAAAAAG1BAEAnwcAIbYEAQCeBwAhHw4AAOEHACAPAADmBwAgEAAA4gcAIBEAAOgHACATAADkBwAgFAAA5QcAIBYAAOcHACCABAIAAAABgQQBAAAAAZEEAQAAAAGSBAEAAAABkwQBAAAAAZQEAgAAAAGVBAIAAAABlgQCAAAAAZcEAQAAAAGYBAEAAAABmQQBAAAAAZoEQAAAAAGbBAEAAAABnARAAAAAAZ0EAgAAAAGeBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAECAAAAKgAgKgAA8AsAIA4HAAD9CgAgCAAA_goAIAsAAP8KACAMAACACwAgGQAAgQsAIBoAAIILACAbAACDCwAgHQAAhQsAIIAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAGfBQEAAAABoAUBAAAAAQIAAAABACAqAADyCwAgDgcAAP0KACAIAAD-CgAgCwAA_woAIAwAAIALACAZAACBCwAgGgAAggsAIBwAAIQLACAdAACFCwAggAQCAAAAAYQEAQAAAAGkBEAAAAABpQRAAAAAAZ8FAQAAAAGgBQEAAAABAgAAAAEAICoAAPQLACALDgAA-QcAIIAEAgAAAAGBBAEAAAABhAQBAAAAAZEEAQAAAAGUBAIAAAABoAQCAAAAAaQEQAAAAAGlBEAAAAABrQQBAAAAAa4EAQAAAAECAAAAJgAgKgAA9gsAIBANAACuCAAgDwAArwgAIBgAALAIACCABAIAAAABgQQBAAAAAYQEAQAAAAGhBCAAAAABpARAAAAAAaUEQAAAAAGwBAEAAAABsQQCAAAAAbIEIAAAAAGzBIAAAAABtASAAAAAAbUEAQAAAAG2BAEAAAABAgAAAB4AICoAAPgLACAXgAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZcEAQAAAAGYBAEAAAABmQQBAAAAAZoEQAAAAAGbBAEAAAABnARAAAAAAZ0EAgAAAAGeBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAEEgAQCAAAAAYEEAQAAAAGCBAEAAAABkAQCAAAAAQSABAIAAAABgQQBAAAAAYIEAQAAAAGEBAEAAAABAwAAABoAICoAAPILACArAAD_CwAgEAAAABoAIAcAAJcKACAIAACYCgAgCwAAmQoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAdAACfCgAgIwAA_wsAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4HAACXCgAgCAAAmAoAIAsAAJkKACAMAACaCgAgGQAAmwoAIBoAAJwKACAbAACdCgAgHQAAnwoAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQMAAAAaACAqAAD0CwAgKwAAggwAIBAAAAAaACAHAACXCgAgCAAAmAoAIAsAAJkKACAMAACaCgAgGQAAmwoAIBoAAJwKACAcAACeCgAgHQAAnwoAICMAAIIMACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEOBwAAlwoAIAgAAJgKACALAACZCgAgDAAAmgoAIBkAAJsKACAaAACcCgAgHAAAngoAIB0AAJ8KACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEDAAAAKAAgKgAA8AsAICsAAIUMACAhAAAAKAAgDgAAtQcAIA8AALsHACAQAAC2BwAgEQAAtwcAIBMAALkHACAUAAC6BwAgFgAAvAcAICMAAIUMACCABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhnQQCALQHACGeBAIAoAcAIZ8EgAAAAAGgBAIAoAcAIaEEIACyBwAhogRAALEHACGjBAEAnwcAIaQEQACzBwAhpQRAALMHACGmBAIAtAcAIR8OAAC1BwAgDwAAuwcAIBAAALYHACARAAC3BwAgEwAAuQcAIBQAALoHACAWAAC8BwAggAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlQQCAKAHACGWBAIAtAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEDAAAAJAAgKgAA9gsAICsAAIgMACANAAAAJAAgDgAA7gcAICMAAIgMACCABAIAoAcAIYEEAQCeBwAhhAQBAJ4HACGRBAEAnwcAIZQEAgCgBwAhoAQCAKAHACGkBEAAswcAIaUEQACzBwAhrQQBAJ8HACGuBAEAngcAIQsOAADuBwAggAQCAKAHACGBBAEAngcAIYQEAQCeBwAhkQQBAJ8HACGUBAIAoAcAIaAEAgCgBwAhpARAALMHACGlBEAAswcAIa0EAQCfBwAhrgQBAJ4HACEDAAAAHAAgKgAA-AsAICsAAIsMACASAAAAHAAgDQAAiQgAIA8AAIoIACAYAACLCAAgIwAAiwwAIIAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbAEAQCeBwAhsQQCAKAHACGyBCAAsgcAIbMEgAAAAAG0BIAAAAABtQQBAJ8HACG2BAEAngcAIRANAACJCAAgDwAAiggAIBgAAIsIACCABAIAoAcAIYEEAQCeBwAhhAQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACGwBAEAngcAIbEEAgCgBwAhsgQgALIHACGzBIAAAAABtASAAAAAAbUEAQCfBwAhtgQBAJ4HACEOBwAA_QoAIAgAAP4KACALAAD_CgAgDAAAgAsAIBkAAIELACAaAACCCwAgGwAAgwsAIBwAAIQLACCABAIAAAABhAQBAAAAAaQEQAAAAAGlBEAAAAABnwUBAAAAAaAFAQAAAAECAAAAAQAgKgAAjAwAIB8OAADhBwAgEAAA4gcAIBEAAOgHACASAADjBwAgEwAA5AcAIBQAAOUHACAWAADnBwAggAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABAgAAACoAICoAAI4MACADAAAAGgAgKgAAjAwAICsAAJIMACAQAAAAGgAgBwAAlwoAIAgAAJgKACALAACZCgAgDAAAmgoAIBkAAJsKACAaAACcCgAgGwAAnQoAIBwAAJ4KACAjAACSDAAggAQCAKAHACGEBAEAngcAIaQEQACzBwAhpQRAALMHACGfBQEAngcAIaAFAQCeBwAhDgcAAJcKACAIAACYCgAgCwAAmQoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAggAQCAKAHACGEBAEAngcAIaQEQACzBwAhpQRAALMHACGfBQEAngcAIaAFAQCeBwAhAwAAACgAICoAAI4MACArAACVDAAgIQAAACgAIA4AALUHACAQAAC2BwAgEQAAtwcAIBIAALgHACATAAC5BwAgFAAAugcAIBYAALwHACAjAACVDAAggAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlQQCAKAHACGWBAIAtAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEfDgAAtQcAIBAAALYHACARAAC3BwAgEgAAuAcAIBMAALkHACAUAAC6BwAgFgAAvAcAIIAEAgCgBwAhgQQBAJ4HACGRBAEAnwcAIZIEAQCfBwAhkwQBAJ8HACGUBAIAoAcAIZUEAgCgBwAhlgQCALQHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGdBAIAtAcAIZ4EAgCgBwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHw4AAOEHACAPAADmBwAgEAAA4gcAIBEAAOgHACASAADjBwAgEwAA5AcAIBQAAOUHACCABAIAAAABgQQBAAAAAZEEAQAAAAGSBAEAAAABkwQBAAAAAZQEAgAAAAGVBAIAAAABlgQCAAAAAZcEAQAAAAGYBAEAAAABmQQBAAAAAZoEQAAAAAGbBAEAAAABnARAAAAAAZ0EAgAAAAGeBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAECAAAAKgAgKgAAlgwAIAMAAAAoACAqAACWDAAgKwAAmgwAICEAAAAoACAOAAC1BwAgDwAAuwcAIBAAALYHACARAAC3BwAgEgAAuAcAIBMAALkHACAUAAC6BwAgIwAAmgwAIIAEAgCgBwAhgQQBAJ4HACGRBAEAnwcAIZIEAQCfBwAhkwQBAJ8HACGUBAIAoAcAIZUEAgCgBwAhlgQCALQHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGdBAIAtAcAIZ4EAgCgBwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHw4AALUHACAPAAC7BwAgEAAAtgcAIBEAALcHACASAAC4BwAgEwAAuQcAIBQAALoHACCABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhnQQCALQHACGeBAIAoAcAIZ8EgAAAAAGgBAIAoAcAIaEEIACyBwAhogRAALEHACGjBAEAnwcAIaQEQACzBwAhpQRAALMHACGmBAIAtAcAIQoGABIHBgIIDAMLEAUMGQgZHwkaPwobQAwcQQwdQg0DAwABBQoDBgAEAgMAAQQAAgEFCwADAwABBgAHChQGAQkABQEKFQABAxsBBQYAEQ0AAQ8jChc7DBgnCwIDAAEOAAkDBgAQDgAJFysMCQYADw4ACQ8yDRAACxEsDBItDBMuARQAARY2DgIDAAEVAAwBFQAMAw84ABI3ABY5AAEXOgADDzwAFz4AGD0ACQdDAAhEAAtFAAxGABlHABpIABtJABxKAB1LAAAAAAUGABcwABgxABkyABozABsAAAAAAAUGABcwABgxABkyABozABsAAAAFBgAhMAAiMQAjMgAkMwAlAAAAAAAFBgAhMAAiMQAjMgAkMwAlAAAABQYAKzAALDEALTIALjMALwAAAAAABQYAKzAALDEALTIALjMALwIGADNXmQEyAVYAMQFXmgEAAAAFBgA3MAA4MQA5MgA6MwA7AAAAAAAFBgA3MAA4MQA5MgA6MwA7AVYAMQFWADEFBgBAMABBMQBCMgBDMwBEAAAAAAAFBgBAMABBMQBCMgBDMwBEAAAABQYASjAASzEATDIATTMATgAAAAAABQYASjAASzEATDIATTMATgAAAAUGAFQwAFUxAFYyAFczAFgAAAAAAAUGAFQwAFUxAFYyAFczAFgAAAAFBgBeMABfMQBgMgBhMwBiAAAAAAAFBgBeMABfMQBgMgBhMwBiAAAABQYAaDAAaTEAajIAazMAbAAAAAAABQYAaDAAaTEAajIAazMAbAAAAAUGAHIwAHMxAHQyAHUzAHYAAAAAAAUGAHIwAHMxAHQyAHUzAHYAAAAFBgB8MAB9MQB-MgB_MwCAAQAAAAAABQYAfDAAfTEAfjIAfzMAgAEAAAAFBgCGATAAhwExAIgBMgCJATMAigEAAAAAAAUGAIYBMACHATEAiAEyAIkBMwCKAQAAAAUGAJABMACRATEAkgEyAJMBMwCUAQAAAAAABQYAkAEwAJEBMQCSATIAkwEzAJQBAAAABQYAmgEwAJsBMQCcATIAnQEzAJ4BAAAAAAAFBgCaATAAmwExAJwBMgCdATMAngECBgClAYwCrgOhAQQGAKQBiAIAoAGKArIDogGLArYDowEBiQIAoQEBiQIAoQECigK3AwCLArgDAAGMArkDAAAABQYAqQEwAKoBMQCrATIArAEzAK0BAAAAAAAFBgCpATAAqgExAKsBMgCsATMArQEBiAIAoAEBiAIAoAEFBgCyATAAswExALQBMgC1ATMAtgEAAAAAAAUGALIBMACzATEAtAEyALUBMwC2AQGJAgChAQGJAgChAQUGALsBMAC8ATEAvQEyAL4BMwC_AQAAAAAABQYAuwEwALwBMQC9ATIAvgEzAL8BAYkCAKEBAYkCAKEBBQYAxAEwAMUBMQDGATIAxwEzAMgBAAAAAAAFBgDEATAAxQExAMYBMgDHATMAyAEBAwABAQMAAQUGAM0BMADOATEAzwEyANABMwDRAQAAAAAABQYAzQEwAM4BMQDPATIA0AEzANEBAgMAAQQAAgIDAAEEAAIFBgDWATAA1wExANgBMgDZATMA2gEAAAAAAAUGANYBMADXATEA2AEyANkBMwDaAQEDAAEBAwABBQYA3wEwAOABMQDhATIA4gEzAOMBAAAAAAAFBgDfATAA4AExAOEBMgDiATMA4wEBCQAFAQkABQUGAOgBMADpATEA6gEyAOsBMwDsAQAAAAAABQYA6AEwAOkBMQDqATIA6wEzAOwBAQP1BAEBA_sEAQUGAPEBMADyATEA8wEyAPQBMwD1AQAAAAAABQYA8QEwAPIBMQDzATIA9AEzAPUBAQ0AAQENAAEFBgD6ATAA-wExAPwBMgD9ATMA_gEAAAAAAAUGAPoBMAD7ATEA_AEyAP0BMwD-AQIDAAEOAAkCAwABDgAJBQYAgwIwAIQCMQCFAjIAhgIzAIcCAAAAAAAFBgCDAjAAhAIxAIUCMgCGAjMAhwIBDgAJAQ4ACQUGAIwCMACNAjEAjgIyAI8CMwCQAgAAAAAABQYAjAIwAI0CMQCOAjIAjwIzAJACBQ4ACRAACxHPBQwT0AUBFAABBQ4ACRAACxHWBQwT1wUBFAABBQYAlQIwAJYCMQCXAjIAmAIzAJkCAAAAAAAFBgCVAjAAlgIxAJcCMgCYAjMAmQICAwABFQAMAgMAARUADAUGAJ4CMACfAjEAoAIyAKECMwCiAgAAAAAABQYAngIwAJ8CMQCgAjIAoQIzAKICARUADAEVAAwFBgCnAjAAqAIxAKkCMgCqAjMAqwIAAAAAAAUGAKcCMACoAjEAqQIyAKoCMwCrAh4CAR9MASBOASFPASJQASRSASVUEyZVFCdXAShZEylaFSxbAS1cAS5dEzRgFjVhHDZjHTdkHThnHTloHTppHTtrHTxtEz1uHj5wHT9yE0BzH0F0HUJ1HUN2E0R5IEV6JkZ8J0d9J0iAASdJgQEnSoIBJ0uEASdMhgETTYcBKE6JASdPiwETUIwBKVGNASdSjgEnU48BE1SSASpVkwEwWJUBMVmbATFangExW58BMVygATFdogExXqQBE1-lATRgpwExYakBE2KqATVjqwExZKwBMWWtARNmsAE2Z7EBPGiyATJpswEyarQBMmu1ATJstgEybbgBMm66ARNvuwE9cL0BMnG_ARNywAE-c8EBMnTCATJ1wwETdsYBP3fHAUV4yQFGecoBRnrNAUZ7zgFGfM8BRn3RAUZ-0wETf9QBR4AB1gFGgQHYAROCAdkBSIMB2gFGhAHbAUaFAdwBE4YB3wFJhwHgAU-IAeIBUIkB4wFQigHmAVCLAecBUIwB6AFQjQHqAVCOAewBE48B7QFRkAHvAVCRAfEBE5IB8gFSkwHzAVCUAfQBUJUB9QETlgH4AVOXAfkBWZgB-wFamQH8AVqaAf8BWpsBgAJanAGBAlqdAYMCWp4BhQITnwGGAlugAYgCWqEBigITogGLAlyjAYwCWqQBjQJapQGOAhOmAZECXacBkgJjqAGUAmSpAZUCZKoBmAJkqwGZAmSsAZoCZK0BnAJkrgGeAhOvAZ8CZbABoQJksQGjAhOyAaQCZrMBpQJktAGmAmS1AacCE7YBqgJntwGrAm24Aa0CbrkBrgJuugGxAm67AbICbrwBswJuvQG1Am6-AbcCE78BuAJvwAG6Am7BAbwCE8IBvQJwwwG-Am7EAb8CbsUBwAITxgHDAnHHAcQCd8gBxgJ4yQHHAnjKAcoCeMsBywJ4zAHMAnjNAc4CeM4B0AITzwHRAnnQAdMCeNEB1QIT0gHWAnrTAdcCeNQB2AJ41QHZAhPWAdwCe9cB3QKBAdgB3wKCAdkB4AKCAdoB4wKCAdsB5AKCAdwB5QKCAd0B5wKCAd4B6QIT3wHqAoMB4AHsAoIB4QHuAhPiAe8ChAHjAfACggHkAfECggHlAfICE-YB9QKFAecB9gKLAegB-AKMAekB-QKMAeoB_AKMAesB_QKMAewB_gKMAe0BgAOMAe4BggMT7wGDA40B8AGFA4wB8QGHAxPyAYgDjgHzAYkDjAH0AYoDjAH1AYsDE_YBjgOPAfcBjwOVAfgBkQOWAfkBkgOWAfoBlQOWAfsBlgOWAfwBlwOWAf0BmQOWAf4BmwMT_wGcA5cBgAKeA5YBgQKgAxOCAqEDmAGDAqIDlgGEAqMDlgGFAqQDE4YCpwOZAYcCqAOfAY0CqgOgAY4CugOgAY8CvQOgAZACvgOgAZECvwOgAZICwQOgAZMCwwMTlALEA6YBlQLGA6ABlgLIAxOXAskDpwGYAsoDoAGZAssDoAGaAswDE5sCzwOoAZwC0AOuAZ0C0QOhAZ4C0gOhAZ8C0wOhAaAC1AOhAaEC1QOhAaIC1wOhAaMC2QMTpALaA68BpQLcA6EBpgLeAxOnAt8DsAGoAuADoQGpAuEDoQGqAuIDE6sC5QOxAawC5gO3Aa0C5wOiAa4C6AOiAa8C6QOiAbAC6gOiAbEC6wOiAbIC7QOiAbMC7wMTtALwA7gBtQLyA6IBtgL0AxO3AvUDuQG4AvYDogG5AvcDogG6AvgDE7sC-wO6AbwC_APAAb0C_QOjAb4C_gOjAb8C_wOjAcACgASjAcECgQSjAcICgwSjAcMChQQTxAKGBMEBxQKIBKMBxgKKBBPHAosEwgHIAowEowHJAo0EowHKAo4EE8sCkQTDAcwCkgTJAc0CkwQCzgKUBALPApUEAtAClgQC0QKXBALSApkEAtMCmwQT1AKcBMoB1QKeBALWAqAEE9cCoQTLAdgCogQC2QKjBALaAqQEE9sCpwTMAdwCqATSAd0CqQQD3gKqBAPfAqsEA-ACrAQD4QKtBAPiAq8EA-MCsQQT5AKyBNMB5QK0BAPmArYEE-cCtwTUAegCuAQD6QK5BAPqAroEE-sCvQTVAewCvgTbAe0CvwQF7gLABAXvAsEEBfACwgQF8QLDBAXyAsUEBfMCxwQT9ALIBNwB9QLKBAX2AswEE_cCzQTdAfgCzgQF-QLPBAX6AtAEE_sC0wTeAfwC1ATkAf0C1QQG_gLWBAb_AtcEBoAD2AQGgQPZBAaCA9sEBoMD3QQThAPeBOUBhQPgBAaGA-IEE4cD4wTmAYgD5AQGiQPlBAaKA-YEE4sD6QTnAYwD6gTtAY0D6wQIjgPsBAiPA-0ECJAD7gQIkQPvBAiSA_EECJMD8wQTlAP0BO4BlQP3BAiWA_kEE5cD-gTvAZgD_AQImQP9BAiaA_4EE5sDgQXwAZwDggX2AZ0DgwUJngOEBQmfA4UFCaADhgUJoQOHBQmiA4kFCaMDiwUTpAOMBfcBpQOOBQmmA5AFE6cDkQX4AagDkgUJqQOTBQmqA5QFE6sDlwX5AawDmAX_Aa0DmQUKrgOaBQqvA5sFCrADnAUKsQOdBQqyA58FCrMDoQUTtAOiBYACtQOkBQq2A6YFE7cDpwWBArgDqAUKuQOpBQq6A6oFE7sDrQWCArwDrgWIAr0DrwULvgOwBQu_A7EFC8ADsgULwQOzBQvCA7UFC8MDtwUTxAO4BYkCxQO6BQvGA7wFE8cDvQWKAsgDvgULyQO_BQvKA8AFE8sDwwWLAswDxAWRAs0DxQUMzgPGBQzPA8cFDNADyAUM0QPJBQzSA8sFDNMDzQUT1APOBZIC1QPSBQzWA9QFE9cD1QWTAtgD2AUM2QPZBQzaA9oFE9sD3QWUAtwD3gWaAt0D3wUN3gPgBQ3fA-EFDeAD4gUN4QPjBQ3iA-UFDeMD5wUT5APoBZsC5QPqBQ3mA-wFE-cD7QWcAugD7gUN6QPvBQ3qA_AFE-sD8wWdAuwD9AWjAu0D9QUO7gP2BQ7vA_cFDvAD-AUO8QP5BQ7yA_sFDvMD_QUT9AP-BaQC9QOABg72A4IGE_cDgwalAvgDhAYO-QOFBg76A4YGE_sDiQamAvwDigasAg" + strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"user\",\"habit\",\"records\",\"_count\",\"habits\",\"checkIns\",\"list\",\"items\",\"checkLists\",\"templates\",\"owner\",\"project\",\"members\",\"column\",\"parent\",\"children\",\"assignee\",\"creator\",\"task\",\"tags\",\"tasks\",\"columns\",\"ownedProjects\",\"projectMembers\",\"assignedTasks\",\"createdTasks\",\"taskMembers\",\"User.findUnique\",\"User.findUniqueOrThrow\",\"User.findFirst\",\"User.findFirstOrThrow\",\"User.findMany\",\"data\",\"User.createOne\",\"User.createMany\",\"User.createManyAndReturn\",\"User.updateOne\",\"User.updateMany\",\"User.updateManyAndReturn\",\"create\",\"update\",\"User.upsertOne\",\"User.deleteOne\",\"User.deleteMany\",\"having\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"User.groupBy\",\"User.aggregate\",\"Article.findUnique\",\"Article.findUniqueOrThrow\",\"Article.findFirst\",\"Article.findFirstOrThrow\",\"Article.findMany\",\"Article.createOne\",\"Article.createMany\",\"Article.createManyAndReturn\",\"Article.updateOne\",\"Article.updateMany\",\"Article.updateManyAndReturn\",\"Article.upsertOne\",\"Article.deleteOne\",\"Article.deleteMany\",\"Article.groupBy\",\"Article.aggregate\",\"News.findUnique\",\"News.findUniqueOrThrow\",\"News.findFirst\",\"News.findFirstOrThrow\",\"News.findMany\",\"News.createOne\",\"News.createMany\",\"News.createManyAndReturn\",\"News.updateOne\",\"News.updateMany\",\"News.updateManyAndReturn\",\"News.upsertOne\",\"News.deleteOne\",\"News.deleteMany\",\"News.groupBy\",\"News.aggregate\",\"Alert.findUnique\",\"Alert.findUniqueOrThrow\",\"Alert.findFirst\",\"Alert.findFirstOrThrow\",\"Alert.findMany\",\"Alert.createOne\",\"Alert.createMany\",\"Alert.createManyAndReturn\",\"Alert.updateOne\",\"Alert.updateMany\",\"Alert.updateManyAndReturn\",\"Alert.upsertOne\",\"Alert.deleteOne\",\"Alert.deleteMany\",\"Alert.groupBy\",\"Alert.aggregate\",\"ServerNode.findUnique\",\"ServerNode.findUniqueOrThrow\",\"ServerNode.findFirst\",\"ServerNode.findFirstOrThrow\",\"ServerNode.findMany\",\"ServerNode.createOne\",\"ServerNode.createMany\",\"ServerNode.createManyAndReturn\",\"ServerNode.updateOne\",\"ServerNode.updateMany\",\"ServerNode.updateManyAndReturn\",\"ServerNode.upsertOne\",\"ServerNode.deleteOne\",\"ServerNode.deleteMany\",\"ServerNode.groupBy\",\"ServerNode.aggregate\",\"ServerNodeHistory.findUnique\",\"ServerNodeHistory.findUniqueOrThrow\",\"ServerNodeHistory.findFirst\",\"ServerNodeHistory.findFirstOrThrow\",\"ServerNodeHistory.findMany\",\"ServerNodeHistory.createOne\",\"ServerNodeHistory.createMany\",\"ServerNodeHistory.createManyAndReturn\",\"ServerNodeHistory.updateOne\",\"ServerNodeHistory.updateMany\",\"ServerNodeHistory.updateManyAndReturn\",\"ServerNodeHistory.upsertOne\",\"ServerNodeHistory.deleteOne\",\"ServerNodeHistory.deleteMany\",\"ServerNodeHistory.groupBy\",\"ServerNodeHistory.aggregate\",\"ServiceCheck.findUnique\",\"ServiceCheck.findUniqueOrThrow\",\"ServiceCheck.findFirst\",\"ServiceCheck.findFirstOrThrow\",\"ServiceCheck.findMany\",\"ServiceCheck.createOne\",\"ServiceCheck.createMany\",\"ServiceCheck.createManyAndReturn\",\"ServiceCheck.updateOne\",\"ServiceCheck.updateMany\",\"ServiceCheck.updateManyAndReturn\",\"ServiceCheck.upsertOne\",\"ServiceCheck.deleteOne\",\"ServiceCheck.deleteMany\",\"ServiceCheck.groupBy\",\"ServiceCheck.aggregate\",\"Econ_SowInventory.findUnique\",\"Econ_SowInventory.findUniqueOrThrow\",\"Econ_SowInventory.findFirst\",\"Econ_SowInventory.findFirstOrThrow\",\"Econ_SowInventory.findMany\",\"Econ_SowInventory.createOne\",\"Econ_SowInventory.createMany\",\"Econ_SowInventory.createManyAndReturn\",\"Econ_SowInventory.updateOne\",\"Econ_SowInventory.updateMany\",\"Econ_SowInventory.updateManyAndReturn\",\"Econ_SowInventory.upsertOne\",\"Econ_SowInventory.deleteOne\",\"Econ_SowInventory.deleteMany\",\"Econ_SowInventory.groupBy\",\"Econ_SowInventory.aggregate\",\"ScraperCache.findUnique\",\"ScraperCache.findUniqueOrThrow\",\"ScraperCache.findFirst\",\"ScraperCache.findFirstOrThrow\",\"ScraperCache.findMany\",\"ScraperCache.createOne\",\"ScraperCache.createMany\",\"ScraperCache.createManyAndReturn\",\"ScraperCache.updateOne\",\"ScraperCache.updateMany\",\"ScraperCache.updateManyAndReturn\",\"ScraperCache.upsertOne\",\"ScraperCache.deleteOne\",\"ScraperCache.deleteMany\",\"ScraperCache.groupBy\",\"ScraperCache.aggregate\",\"MihomeAccount.findUnique\",\"MihomeAccount.findUniqueOrThrow\",\"MihomeAccount.findFirst\",\"MihomeAccount.findFirstOrThrow\",\"MihomeAccount.findMany\",\"MihomeAccount.createOne\",\"MihomeAccount.createMany\",\"MihomeAccount.createManyAndReturn\",\"MihomeAccount.updateOne\",\"MihomeAccount.updateMany\",\"MihomeAccount.updateManyAndReturn\",\"MihomeAccount.upsertOne\",\"MihomeAccount.deleteOne\",\"MihomeAccount.deleteMany\",\"MihomeAccount.groupBy\",\"MihomeAccount.aggregate\",\"home\",\"device\",\"samples\",\"hours\",\"devices\",\"MihomeHome.findUnique\",\"MihomeHome.findUniqueOrThrow\",\"MihomeHome.findFirst\",\"MihomeHome.findFirstOrThrow\",\"MihomeHome.findMany\",\"MihomeHome.createOne\",\"MihomeHome.createMany\",\"MihomeHome.createManyAndReturn\",\"MihomeHome.updateOne\",\"MihomeHome.updateMany\",\"MihomeHome.updateManyAndReturn\",\"MihomeHome.upsertOne\",\"MihomeHome.deleteOne\",\"MihomeHome.deleteMany\",\"MihomeHome.groupBy\",\"MihomeHome.aggregate\",\"MihomeDevice.findUnique\",\"MihomeDevice.findUniqueOrThrow\",\"MihomeDevice.findFirst\",\"MihomeDevice.findFirstOrThrow\",\"MihomeDevice.findMany\",\"MihomeDevice.createOne\",\"MihomeDevice.createMany\",\"MihomeDevice.createManyAndReturn\",\"MihomeDevice.updateOne\",\"MihomeDevice.updateMany\",\"MihomeDevice.updateManyAndReturn\",\"MihomeDevice.upsertOne\",\"MihomeDevice.deleteOne\",\"MihomeDevice.deleteMany\",\"MihomeDevice.groupBy\",\"MihomeDevice.aggregate\",\"MihomeEnvSample.findUnique\",\"MihomeEnvSample.findUniqueOrThrow\",\"MihomeEnvSample.findFirst\",\"MihomeEnvSample.findFirstOrThrow\",\"MihomeEnvSample.findMany\",\"MihomeEnvSample.createOne\",\"MihomeEnvSample.createMany\",\"MihomeEnvSample.createManyAndReturn\",\"MihomeEnvSample.updateOne\",\"MihomeEnvSample.updateMany\",\"MihomeEnvSample.updateManyAndReturn\",\"MihomeEnvSample.upsertOne\",\"MihomeEnvSample.deleteOne\",\"MihomeEnvSample.deleteMany\",\"MihomeEnvSample.groupBy\",\"MihomeEnvSample.aggregate\",\"MihomeEnvHour.findUnique\",\"MihomeEnvHour.findUniqueOrThrow\",\"MihomeEnvHour.findFirst\",\"MihomeEnvHour.findFirstOrThrow\",\"MihomeEnvHour.findMany\",\"MihomeEnvHour.createOne\",\"MihomeEnvHour.createMany\",\"MihomeEnvHour.createManyAndReturn\",\"MihomeEnvHour.updateOne\",\"MihomeEnvHour.updateMany\",\"MihomeEnvHour.updateManyAndReturn\",\"MihomeEnvHour.upsertOne\",\"MihomeEnvHour.deleteOne\",\"MihomeEnvHour.deleteMany\",\"MihomeEnvHour.groupBy\",\"MihomeEnvHour.aggregate\",\"Habit.findUnique\",\"Habit.findUniqueOrThrow\",\"Habit.findFirst\",\"Habit.findFirstOrThrow\",\"Habit.findMany\",\"Habit.createOne\",\"Habit.createMany\",\"Habit.createManyAndReturn\",\"Habit.updateOne\",\"Habit.updateMany\",\"Habit.updateManyAndReturn\",\"Habit.upsertOne\",\"Habit.deleteOne\",\"Habit.deleteMany\",\"Habit.groupBy\",\"Habit.aggregate\",\"CheckIn.findUnique\",\"CheckIn.findUniqueOrThrow\",\"CheckIn.findFirst\",\"CheckIn.findFirstOrThrow\",\"CheckIn.findMany\",\"CheckIn.createOne\",\"CheckIn.createMany\",\"CheckIn.createManyAndReturn\",\"CheckIn.updateOne\",\"CheckIn.updateMany\",\"CheckIn.updateManyAndReturn\",\"CheckIn.upsertOne\",\"CheckIn.deleteOne\",\"CheckIn.deleteMany\",\"CheckIn.groupBy\",\"CheckIn.aggregate\",\"CheckList.findUnique\",\"CheckList.findUniqueOrThrow\",\"CheckList.findFirst\",\"CheckList.findFirstOrThrow\",\"CheckList.findMany\",\"CheckList.createOne\",\"CheckList.createMany\",\"CheckList.createManyAndReturn\",\"CheckList.updateOne\",\"CheckList.updateMany\",\"CheckList.updateManyAndReturn\",\"CheckList.upsertOne\",\"CheckList.deleteOne\",\"CheckList.deleteMany\",\"CheckList.groupBy\",\"CheckList.aggregate\",\"CheckListItem.findUnique\",\"CheckListItem.findUniqueOrThrow\",\"CheckListItem.findFirst\",\"CheckListItem.findFirstOrThrow\",\"CheckListItem.findMany\",\"CheckListItem.createOne\",\"CheckListItem.createMany\",\"CheckListItem.createManyAndReturn\",\"CheckListItem.updateOne\",\"CheckListItem.updateMany\",\"CheckListItem.updateManyAndReturn\",\"CheckListItem.upsertOne\",\"CheckListItem.deleteOne\",\"CheckListItem.deleteMany\",\"CheckListItem.groupBy\",\"CheckListItem.aggregate\",\"CheckListTemplate.findUnique\",\"CheckListTemplate.findUniqueOrThrow\",\"CheckListTemplate.findFirst\",\"CheckListTemplate.findFirstOrThrow\",\"CheckListTemplate.findMany\",\"CheckListTemplate.createOne\",\"CheckListTemplate.createMany\",\"CheckListTemplate.createManyAndReturn\",\"CheckListTemplate.updateOne\",\"CheckListTemplate.updateMany\",\"CheckListTemplate.updateManyAndReturn\",\"CheckListTemplate.upsertOne\",\"CheckListTemplate.deleteOne\",\"CheckListTemplate.deleteMany\",\"CheckListTemplate.groupBy\",\"CheckListTemplate.aggregate\",\"Project.findUnique\",\"Project.findUniqueOrThrow\",\"Project.findFirst\",\"Project.findFirstOrThrow\",\"Project.findMany\",\"Project.createOne\",\"Project.createMany\",\"Project.createManyAndReturn\",\"Project.updateOne\",\"Project.updateMany\",\"Project.updateManyAndReturn\",\"Project.upsertOne\",\"Project.deleteOne\",\"Project.deleteMany\",\"Project.groupBy\",\"Project.aggregate\",\"ProjectMember.findUnique\",\"ProjectMember.findUniqueOrThrow\",\"ProjectMember.findFirst\",\"ProjectMember.findFirstOrThrow\",\"ProjectMember.findMany\",\"ProjectMember.createOne\",\"ProjectMember.createMany\",\"ProjectMember.createManyAndReturn\",\"ProjectMember.updateOne\",\"ProjectMember.updateMany\",\"ProjectMember.updateManyAndReturn\",\"ProjectMember.upsertOne\",\"ProjectMember.deleteOne\",\"ProjectMember.deleteMany\",\"ProjectMember.groupBy\",\"ProjectMember.aggregate\",\"BoardColumn.findUnique\",\"BoardColumn.findUniqueOrThrow\",\"BoardColumn.findFirst\",\"BoardColumn.findFirstOrThrow\",\"BoardColumn.findMany\",\"BoardColumn.createOne\",\"BoardColumn.createMany\",\"BoardColumn.createManyAndReturn\",\"BoardColumn.updateOne\",\"BoardColumn.updateMany\",\"BoardColumn.updateManyAndReturn\",\"BoardColumn.upsertOne\",\"BoardColumn.deleteOne\",\"BoardColumn.deleteMany\",\"BoardColumn.groupBy\",\"BoardColumn.aggregate\",\"Task.findUnique\",\"Task.findUniqueOrThrow\",\"Task.findFirst\",\"Task.findFirstOrThrow\",\"Task.findMany\",\"Task.createOne\",\"Task.createMany\",\"Task.createManyAndReturn\",\"Task.updateOne\",\"Task.updateMany\",\"Task.updateManyAndReturn\",\"Task.upsertOne\",\"Task.deleteOne\",\"Task.deleteMany\",\"Task.groupBy\",\"Task.aggregate\",\"TaskMember.findUnique\",\"TaskMember.findUniqueOrThrow\",\"TaskMember.findFirst\",\"TaskMember.findFirstOrThrow\",\"TaskMember.findMany\",\"TaskMember.createOne\",\"TaskMember.createMany\",\"TaskMember.createManyAndReturn\",\"TaskMember.updateOne\",\"TaskMember.updateMany\",\"TaskMember.updateManyAndReturn\",\"TaskMember.upsertOne\",\"TaskMember.deleteOne\",\"TaskMember.deleteMany\",\"TaskMember.groupBy\",\"TaskMember.aggregate\",\"TaskTag.findUnique\",\"TaskTag.findUniqueOrThrow\",\"TaskTag.findFirst\",\"TaskTag.findFirstOrThrow\",\"TaskTag.findMany\",\"TaskTag.createOne\",\"TaskTag.createMany\",\"TaskTag.createManyAndReturn\",\"TaskTag.updateOne\",\"TaskTag.updateMany\",\"TaskTag.updateManyAndReturn\",\"TaskTag.upsertOne\",\"TaskTag.deleteOne\",\"TaskTag.deleteMany\",\"TaskTag.groupBy\",\"TaskTag.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"uuid\",\"taskUuid\",\"taskId\",\"name\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"contains\",\"startsWith\",\"endsWith\",\"not\",\"userId\",\"projectUuid\",\"columnUuid\",\"parentUuid\",\"projectId\",\"columnId\",\"parentId\",\"title\",\"description\",\"kind\",\"markDate\",\"status\",\"doneAt\",\"assigneeId\",\"creatorId\",\"extras\",\"sortOrder\",\"archived\",\"archivedAt\",\"archiveSource\",\"createdAt\",\"updatedAt\",\"number\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"boundStatus\",\"retention\",\"permission\",\"code\",\"ownerId\",\"isPersonal\",\"notes\",\"taskKinds\",\"icon\",\"autoArchive\",\"scene\",\"builtin\",\"listId\",\"note\",\"done\",\"habitId\",\"date\",\"seq\",\"source\",\"period\",\"target\",\"color\",\"deviceId\",\"hour\",\"temperatureAvg\",\"humidityAvg\",\"raw\",\"temperature\",\"humidity\",\"temperatureMin\",\"temperatureMax\",\"humidityMin\",\"humidityMax\",\"origin\",\"sampledAt\",\"did\",\"homeId\",\"model\",\"alias\",\"tempSiid\",\"tempPiid\",\"humSiid\",\"humPiid\",\"battSiid\",\"battPiid\",\"battery\",\"batteryAt\",\"enabled\",\"lastSyncAt\",\"every\",\"some\",\"none\",\"deviceId_hour\",\"deviceId_date\",\"key\",\"lastError\",\"scraperName\",\"lastDataHash\",\"lastUpdateAt\",\"metadata\",\"month\",\"inventory\",\"url\",\"latencyMs\",\"message\",\"lastCheckAt\",\"cpu\",\"ram\",\"load\",\"disk\",\"netIn\",\"netOut\",\"process\",\"connections\",\"uuid_period\",\"cpuName\",\"os\",\"region\",\"memTotal\",\"diskTotal\",\"cpuCores\",\"cpuUsage\",\"ramUsed\",\"swapUsed\",\"load1\",\"load5\",\"load15\",\"diskUsed\",\"uptime\",\"recordedAt\",\"indicatorName\",\"valueA\",\"valueB\",\"sourceA\",\"sourceB\",\"diff\",\"diffRatio\",\"summary\",\"content\",\"publishedAt\",\"impactRank\",\"title_publishedAt\",\"slug\",\"email\",\"password\",\"taskId_userId\",\"projectId_number\",\"projectId_name\",\"projectId_userId\",\"habitId_date_seq\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), + graph: "_AqDApADEgcAAJgGACAIAACZBgAgCwAAmgYAIAwAAJsGACAZAACcBgAgGgAAlQYAIBsAAIwGACAcAACMBgAgHQAAjgYAILsDAACXBgAwvAMAABoAEL0DAACXBgAwvgMCAAAAAcIDAQDTBQAh4gNAANQFACHjA0AA1AUAIdIEAQAAAAHTBAEA0wUAIQEAAAABACARAwAAhQYAIAUAAJkGACC7AwAApQYAMLwDAAADABC9AwAApQYAML4DAgDSBQAhwgMBANMFACHOAwIA0gUAId4DAgDSBQAh3wMgAOEFACHiA0AA1AUAIeMDQADUBQAh8wMBANMFACH4AwEA0wUAIf4DAQDTBQAh_wMCANIFACGABAEA0wUAIQIDAAD3CQAgBQAA7wkAIBEDAACFBgAgBQAAmQYAILsDAAClBgAwvAMAAAMAEL0DAAClBgAwvgMCAAAAAcIDAQDTBQAhzgMCANIFACHeAwIA0gUAId8DIADhBQAh4gNAANQFACHjA0AA1AUAIfMDAQDTBQAh-AMBANMFACH-AwEA0wUAIf8DAgDSBQAhgAQBANMFACEDAAAAAwAgAQAABAAwAgAABQAgDQMAAIUGACAEAACkBgAguwMAAKMGADC8AwAABwAQvQMAAKMGADC-AwIA0gUAIc4DAgDSBQAh4gNAANQFACH4AwEA0wUAIfoDAgDSBQAh-wMBANMFACH8AwIA0gUAIf0DAQDTBQAhAgMAAPcJACAEAAD-CQAgDgMAAIUGACAEAACkBgAguwMAAKMGADC8AwAABwAQvQMAAKMGADC-AwIAAAABzgMCANIFACHiA0AA1AUAIfgDAQDTBQAh-gMCANIFACH7AwEA0wUAIfwDAgDSBQAh_QMBANMFACHYBAAAogYAIAMAAAAHACABAAAIADACAAAJACABAAAABwAgAwAAAAcAIAEAAAgAMAIAAAkAIA0DAACFBgAgCgAAoQYAILsDAACgBgAwvAMAAA0AEL0DAACgBgAwvgMCANIFACHOAwIA0gUAIdUDAQDTBQAh3wMgAOEFACHiA0AA1AUAIeMDQADUBQAh9QMBANMFACH4AwEA0wUAIQIDAAD3CQAgCgAA_QkAIA0DAACFBgAgCgAAoQYAILsDAACgBgAwvAMAAA0AEL0DAACgBgAwvgMCAAAAAc4DAgDSBQAh1QMBANMFACHfAyAA4QUAIeIDQADUBQAh4wNAANQFACH1AwEA0wUAIfgDAQDTBQAhAwAAAA0AIAEAAA4AMAIAAA8AIA0JAACfBgAguwMAAJ4GADC8AwAAEQAQvQMAAJ4GADC-AwIA0gUAIdUDAQDTBQAh2gNAAN0FACHeAwIA0gUAIeIDQADUBQAh4wNAANQFACH3AwIA0gUAIfgDAQDTBQAh-QMgAOEFACECCQAA_AkAINoDAACmBgAgDQkAAJ8GACC7AwAAngYAMLwDAAARABC9AwAAngYAML4DAgAAAAHVAwEA0wUAIdoDQADdBQAh3gMCANIFACHiA0AA1AUAIeMDQADUBQAh9wMCANIFACH4AwEA0wUAIfkDIADhBQAhAwAAABEAIAEAABIAMAIAABMAIAEAAAARACAMAwAAjQYAIAoAAIgGACC7AwAAnQYAMLwDAAAWABC9AwAAnQYAML4DAgDSBQAhwgMBANMFACHOAwIA4AUAIeIDQADUBQAh4wNAANQFACH1AwEA0wUAIfYDIADhBQAhAgMAAPcJACDOAwAApgYAIAwDAACNBgAgCgAAiAYAILsDAACdBgAwvAMAABYAEL0DAACdBgAwvgMCAAAAAcIDAQDTBQAhzgMCAOAFACHiA0AA1AUAIeMDQADUBQAh9QMBANMFACH2AyAA4QUAIQMAAAAWACABAAAXADACAAAYACASBwAAmAYAIAgAAJkGACALAACaBgAgDAAAmwYAIBkAAJwGACAaAACVBgAgGwAAjAYAIBwAAIwGACAdAACOBgAguwMAAJcGADC8AwAAGgAQvQMAAJcGADC-AwIA0gUAIcIDAQDTBQAh4gNAANQFACHjA0AA1AUAIdIEAQDTBQAh0wQBANMFACEBAAAAGgAgFA0AAIUGACAPAACVBgAgFwAAjAYAIBgAAJYGACC7AwAAlAYAMLwDAAAcABC9AwAAlAYAML4DAgDSBQAhvwMBANMFACHCAwEA0wUAId8DIADhBQAh4gNAANQFACHjA0AA1AUAIe4DAQDTBQAh7wMCANIFACHwAyAA4QUAIfEDAACIBgAg8gMAANkFACDzAwEA3wUAIfQDAQDTBQAhBg0AAPcJACAPAADzCQAgFwAA9AkAIBgAAPsJACDyAwAApgYAIPMDAACmBgAgFA0AAIUGACAPAACVBgAgFwAAjAYAIBgAAJYGACC7AwAAlAYAMLwDAAAcABC9AwAAlAYAML4DAgAAAAG_AwEAAAABwgMBANMFACHfAyAA4QUAIeIDQADUBQAh4wNAANQFACHuAwEAAAAB7wMCANIFACHwAyAA4QUAIfEDAACIBgAg8gMAANkFACDzAwEA3wUAIfQDAQDTBQAhAwAAABwAIAEAAB0AMAIAAB4AIAwDAACFBgAgDgAAiQYAILsDAACTBgAwvAMAACAAEL0DAACTBgAwvgMCANIFACG_AwEA0wUAIc4DAgDSBQAhzwMBAN8FACHSAwIA0gUAIeIDQADUBQAh7QMBANMFACEDAwAA9wkAIA4AAPgJACDPAwAApgYAIA0DAACFBgAgDgAAiQYAILsDAACTBgAwvAMAACAAEL0DAACTBgAwvgMCAAAAAb8DAQAAAAHOAwIA0gUAIc8DAQDfBQAh0gMCANIFACHiA0AA1AUAIe0DAQDTBQAh1wQAAJIGACADAAAAIAAgAQAAIQAwAgAAIgAgDw4AAIkGACAXAACMBgAguwMAAJEGADC8AwAAJAAQvQMAAJEGADC-AwIA0gUAIb8DAQDTBQAhwgMBANMFACHPAwEA3wUAIdIDAgDSBQAh3gMCANIFACHiA0AA1AUAIeMDQADUBQAh6wMBAN8FACHsAwEA0wUAIQQOAAD4CQAgFwAA9AkAIM8DAACmBgAg6wMAAKYGACAQDgAAiQYAIBcAAIwGACC7AwAAkQYAMLwDAAAkABC9AwAAkQYAML4DAgAAAAG_AwEAAAABwgMBANMFACHPAwEA3wUAIdIDAgDSBQAh3gMCANIFACHiA0AA1AUAIeMDQADUBQAh6wMBAN8FACHsAwEA0wUAIdYEAACQBgAgAwAAACQAIAEAACUAMAIAACYAICMOAACJBgAgDwAAjgYAIBAAAIoGACARAACLBgAgEgAAjAYAIBMAAI0GACAUAACFBgAgFgAAjwYAILsDAACHBgAwvAMAACgAEL0DAACHBgAwvgMCANIFACG_AwEA0wUAIc8DAQDfBQAh0AMBAN8FACHRAwEA3wUAIdIDAgDSBQAh0wMCANIFACHUAwIA4AUAIdUDAQDTBQAh1gMBANMFACHXAwEA3wUAIdgDQADdBQAh2QMBANMFACHaA0AA3QUAIdsDAgDgBQAh3AMCANIFACHdAwAAiAYAIN4DAgDSBQAh3wMgAOEFACHgA0AA3QUAIeEDAQDfBQAh4gNAANQFACHjA0AA1AUAIeQDAgDgBQAhEw4AAPgJACAPAAD1CQAgEAAA-QkAIBEAAPYJACASAAD0CQAgEwAA9wkAIBQAAPcJACAWAAD6CQAgzwMAAKYGACDQAwAApgYAINEDAACmBgAg1AMAAKYGACDXAwAApgYAINgDAACmBgAg2gMAAKYGACDbAwAApgYAIOADAACmBgAg4QMAAKYGACDkAwAApgYAICQOAACJBgAgDwAAjgYAIBAAAIoGACARAACLBgAgEgAAjAYAIBMAAI0GACAUAACFBgAgFgAAjwYAILsDAACHBgAwvAMAACgAEL0DAACHBgAwvgMCAAAAAb8DAQAAAAHPAwEA3wUAIdADAQDfBQAh0QMBAN8FACHSAwIA0gUAIdMDAgDSBQAh1AMCAOAFACHVAwEA0wUAIdYDAQDTBQAh1wMBAN8FACHYA0AA3QUAIdkDAQDTBQAh2gNAAN0FACHbAwIA4AUAIdwDAgDSBQAh3QMAAIgGACDeAwIA0gUAId8DIADhBQAh4ANAAN0FACHhAwEA3wUAIeIDQADUBQAh4wNAANQFACHkAwIA4AUAIdUEAACGBgAgAwAAACgAIAEAACkAMAIAACoAIAEAAAAoACADAAAAKAAgAQAAKQAwAgAAKgAgAQAAABoAIAoDAACFBgAgFQAAggYAILsDAACEBgAwvAMAAC8AEL0DAACEBgAwvgMCANIFACG_AwEA0wUAIcADAQDfBQAhwQMCANIFACHOAwIA0gUAIQMDAAD3CQAgFQAA9gkAIMADAACmBgAgCwMAAIUGACAVAACCBgAguwMAAIQGADC8AwAALwAQvQMAAIQGADC-AwIAAAABvwMBAAAAAcADAQDfBQAhwQMCANIFACHOAwIA0gUAIdQEAACDBgAgAwAAAC8AIAEAADAAMAIAADEAIAkVAACCBgAguwMAAIEGADC8AwAAMwAQvQMAAIEGADC-AwIA0gUAIb8DAQDTBQAhwAMBAN8FACHBAwIA0gUAIcIDAQDTBQAhAhUAAPYJACDAAwAApgYAIAkVAACCBgAguwMAAIEGADC8AwAAMwAQvQMAAIEGADC-AwIAAAABvwMBAAAAAcADAQDfBQAhwQMCANIFACHCAwEA0wUAIQMAAAAzACABAAA0ADACAAA1ACABAAAAKAAgAQAAAC8AIAEAAAAzACABAAAAKAAgAwAAACgAIAEAACkAMAIAACoAIAEAAAAgACABAAAAJAAgAQAAACgAIAMAAAAgACABAAAhADACAAAiACADAAAAKAAgAQAAKQAwAgAAKgAgAwAAACgAIAEAACkAMAIAACoAIAMAAAAvACABAAAwADACAAAxACABAAAAAwAgAQAAAAcAIAEAAAANACABAAAAFgAgAQAAABwAIAEAAAAgACABAAAAKAAgAQAAACgAIAEAAAAvACABAAAAAQAgCQcAAO4JACAIAADvCQAgCwAA8AkAIAwAAPEJACAZAADyCQAgGgAA8wkAIBsAAPQJACAcAAD0CQAgHQAA9QkAIAMAAAAaACABAABNADACAAABACADAAAAGgAgAQAATQAwAgAAAQAgAwAAABoAIAEAAE0AMAIAAAEAIA8HAADlCQAgCAAA5gkAIAsAAOcJACAMAADoCQAgGQAA6QkAIBoAAOoJACAbAADrCQAgHAAA7AkAIB0AAO0JACC-AwIAAAABwgMBAAAAAeIDQAAAAAHjA0AAAAAB0gQBAAAAAdMEAQAAAAEBIwAAUQAgBr4DAgAAAAHCAwEAAAAB4gNAAAAAAeMDQAAAAAHSBAEAAAAB0wQBAAAAAQEjAABTADABIwAAUwAwDwcAAP8IACAIAACACQAgCwAAgQkAIAwAAIIJACAZAACDCQAgGgAAhAkAIBsAAIUJACAcAACGCQAgHQAAhwkAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIeMDQADBBgAh0gQBAKwGACHTBAEArAYAIQIAAAABACAjAABWACAGvgMCAK4GACHCAwEArAYAIeIDQADBBgAh4wNAAMEGACHSBAEArAYAIdMEAQCsBgAhAgAAABoAICMAAFgAIAIAAAAaACAjAABYACADAAAAAQAgKgAAUQAgKwAAVgAgAQAAAAEAIAEAAAAaACAFBgAA-ggAIDAAAPsIACAxAAD-CAAgMgAA_QgAIDMAAPwIACAJuwMAAIAGADC8AwAAXwAQvQMAAIAGADC-AwIApgUAIcIDAQCnBQAh4gNAALcFACHjA0AAtwUAIdIEAQCnBQAh0wQBAKcFACEDAAAAGgAgAQAAXgAwLwAAXwAgAwAAABoAIAEAAE0AMAIAAAEAIAq7AwAA_wUAMLwDAABlABC9AwAA_wUAML4DAgAAAAHVAwEA0wUAIeIDQADUBQAh4wNAANQFACH7AwEA0wUAIc0EAQDTBQAh0QQBAAAAAQEAAABiACABAAAAYgAgCrsDAAD_BQAwvAMAAGUAEL0DAAD_BQAwvgMCANIFACHVAwEA0wUAIeIDQADUBQAh4wNAANQFACH7AwEA0wUAIc0EAQDTBQAh0QQBANMFACEAAwAAAGUAIAEAAGYAMAIAAGIAIAMAAABlACABAABmADACAABiACADAAAAZQAgAQAAZgAwAgAAYgAgB74DAgAAAAHVAwEAAAAB4gNAAAAAAeMDQAAAAAH7AwEAAAABzQQBAAAAAdEEAQAAAAEBIwAAagAgB74DAgAAAAHVAwEAAAAB4gNAAAAAAeMDQAAAAAH7AwEAAAABzQQBAAAAAdEEAQAAAAEBIwAAbAAwASMAAGwAMAe-AwIArgYAIdUDAQCsBgAh4gNAAMEGACHjA0AAwQYAIfsDAQCsBgAhzQQBAKwGACHRBAEArAYAIQIAAABiACAjAABvACAHvgMCAK4GACHVAwEArAYAIeIDQADBBgAh4wNAAMEGACH7AwEArAYAIc0EAQCsBgAh0QQBAKwGACECAAAAZQAgIwAAcQAgAgAAAGUAICMAAHEAIAMAAABiACAqAABqACArAABvACABAAAAYgAgAQAAAGUAIAUGAAD1CAAgMAAA9ggAIDEAAPkIACAyAAD4CAAgMwAA9wgAIAq7AwAA_gUAMLwDAAB4ABC9AwAA_gUAML4DAgCmBQAh1QMBAKcFACHiA0AAtwUAIeMDQAC3BQAh-wMBAKcFACHNBAEApwUAIdEEAQCnBQAhAwAAAGUAIAEAAHcAMC8AAHgAIAMAAABlACABAABmADACAABiACANuwMAAPwFADC8AwAAfgAQvQMAAPwFADC-AwIAAAAB1QMBANMFACHiA0AA1AUAIf0DAQDTBQAhqQQBANMFACHMBAEA0wUAIc0EAQDfBQAhzgRAANQFACHPBAIA0gUAIdAEAAD9BQAgAQAAAHsAIAEAAAB7ACAMuwMAAPwFADC8AwAAfgAQvQMAAPwFADC-AwIA0gUAIdUDAQDTBQAh4gNAANQFACH9AwEA0wUAIakEAQDTBQAhzAQBANMFACHNBAEA3wUAIc4EQADUBQAhzwQCANIFACEBzQQAAKYGACADAAAAfgAgAQAAfwAwAgAAewAgAwAAAH4AIAEAAH8AMAIAAHsAIAMAAAB-ACABAAB_ADACAAB7ACAJvgMCAAAAAdUDAQAAAAHiA0AAAAAB_QMBAAAAAakEAQAAAAHMBAEAAAABzQQBAAAAAc4EQAAAAAHPBAIAAAABASMAAIMBACAJvgMCAAAAAdUDAQAAAAHiA0AAAAAB_QMBAAAAAakEAQAAAAHMBAEAAAABzQQBAAAAAc4EQAAAAAHPBAIAAAABASMAAIUBADABIwAAhQEAMAm-AwIArgYAIdUDAQCsBgAh4gNAAMEGACH9AwEArAYAIakEAQCsBgAhzAQBAKwGACHNBAEArQYAIc4EQADBBgAhzwQCAK4GACECAAAAewAgIwAAiAEAIAm-AwIArgYAIdUDAQCsBgAh4gNAAMEGACH9AwEArAYAIakEAQCsBgAhzAQBAKwGACHNBAEArQYAIc4EQADBBgAhzwQCAK4GACECAAAAfgAgIwAAigEAIAIAAAB-ACAjAACKAQAgAwAAAHsAICoAAIMBACArAACIAQAgAQAAAHsAIAEAAAB-ACAGBgAA8AgAIDAAAPEIACAxAAD0CAAgMgAA8wgAIDMAAPIIACDNBAAApgYAIAy7AwAA-wUAMLwDAACRAQAQvQMAAPsFADC-AwIApgUAIdUDAQCnBQAh4gNAALcFACH9AwEApwUAIakEAQCnBQAhzAQBAKcFACHNBAEAqAUAIc4EQAC3BQAhzwQCAKYFACEDAAAAfgAgAQAAkAEAMC8AAJEBACADAAAAfgAgAQAAfwAwAgAAewAgDbsDAAD6BQAwvAMAAJcBABC9AwAA-gUAML4DAgAAAAHiA0AA1AUAIf4DAQDTBQAhxQQBANMFACHGBAgA9AUAIccECAD0BQAhyAQBANMFACHJBAEA0wUAIcoECAD0BQAhywQIAPQFACEBAAAAlAEAIAEAAACUAQAgDbsDAAD6BQAwvAMAAJcBABC9AwAA-gUAML4DAgDSBQAh4gNAANQFACH-AwEA0wUAIcUEAQDTBQAhxgQIAPQFACHHBAgA9AUAIcgEAQDTBQAhyQQBANMFACHKBAgA9AUAIcsECAD0BQAhAAMAAACXAQAgAQAAmAEAMAIAAJQBACADAAAAlwEAIAEAAJgBADACAACUAQAgAwAAAJcBACABAACYAQAwAgAAlAEAIAq-AwIAAAAB4gNAAAAAAf4DAQAAAAHFBAEAAAABxgQIAAAAAccECAAAAAHIBAEAAAAByQQBAAAAAcoECAAAAAHLBAgAAAABASMAAJwBACAKvgMCAAAAAeIDQAAAAAH-AwEAAAABxQQBAAAAAcYECAAAAAHHBAgAAAAByAQBAAAAAckEAQAAAAHKBAgAAAABywQIAAAAAQEjAACeAQAwASMAAJ4BADAKvgMCAK4GACHiA0AAwQYAIf4DAQCsBgAhxQQBAKwGACHGBAgA5AgAIccECADkCAAhyAQBAKwGACHJBAEArAYAIcoECADkCAAhywQIAOQIACECAAAAlAEAICMAAKEBACAKvgMCAK4GACHiA0AAwQYAIf4DAQCsBgAhxQQBAKwGACHGBAgA5AgAIccECADkCAAhyAQBAKwGACHJBAEArAYAIcoECADkCAAhywQIAOQIACECAAAAlwEAICMAAKMBACACAAAAlwEAICMAAKMBACADAAAAlAEAICoAAJwBACArAAChAQAgAQAAAJQBACABAAAAlwEAIAUGAADrCAAgMAAA7AgAIDEAAO8IACAyAADuCAAgMwAA7QgAIA27AwAA-QUAMLwDAACqAQAQvQMAAPkFADC-AwIApgUAIeIDQAC3BQAh_gMBAKcFACHFBAEApwUAIcYECADuBQAhxwQIAO4FACHIBAEApwUAIckEAQCnBQAhygQIAO4FACHLBAgA7gUAIQMAAACXAQAgAQAAqQEAMC8AAKoBACADAAAAlwEAIAEAAJgBADACAACUAQAgHLsDAAD4BQAwvAMAALABABC9AwAA-AUAML4DAgAAAAG_AwEAAAABwgMBANMFACHZAwEA0wUAIeIDQADUBQAh4wNAANQFACGxBAQA9QUAIbIEBAD1BQAhswQCANIFACG0BAIA0gUAIbYEAQDTBQAhtwQBANMFACG4BAEA0wUAIbkEBAD1BQAhugQEAPUFACG7BAIA0gUAIbwECAD0BQAhvQQEAPUFACG-BAQA9QUAIb8ECAD0BQAhwAQIAPQFACHBBAgA9AUAIcIEBAD1BQAhwwQEAPUFACHEBEAA1AUAIQEAAACtAQAgAQAAAK0BACAcuwMAAPgFADC8AwAAsAEAEL0DAAD4BQAwvgMCANIFACG_AwEA0wUAIcIDAQDTBQAh2QMBANMFACHiA0AA1AUAIeMDQADUBQAhsQQEAPUFACGyBAQA9QUAIbMEAgDSBQAhtAQCANIFACG2BAEA0wUAIbcEAQDTBQAhuAQBANMFACG5BAQA9QUAIboEBAD1BQAhuwQCANIFACG8BAgA9AUAIb0EBAD1BQAhvgQEAPUFACG_BAgA9AUAIcAECAD0BQAhwQQIAPQFACHCBAQA9QUAIcMEBAD1BQAhxARAANQFACEAAwAAALABACABAACxAQAwAgAArQEAIAMAAACwAQAgAQAAsQEAMAIAAK0BACADAAAAsAEAIAEAALEBADACAACtAQAgGb4DAgAAAAG_AwEAAAABwgMBAAAAAdkDAQAAAAHiA0AAAAAB4wNAAAAAAbEEBAAAAAGyBAQAAAABswQCAAAAAbQEAgAAAAG2BAEAAAABtwQBAAAAAbgEAQAAAAG5BAQAAAABugQEAAAAAbsEAgAAAAG8BAgAAAABvQQEAAAAAb4EBAAAAAG_BAgAAAABwAQIAAAAAcEECAAAAAHCBAQAAAABwwQEAAAAAcQEQAAAAAEBIwAAtQEAIBm-AwIAAAABvwMBAAAAAcIDAQAAAAHZAwEAAAAB4gNAAAAAAeMDQAAAAAGxBAQAAAABsgQEAAAAAbMEAgAAAAG0BAIAAAABtgQBAAAAAbcEAQAAAAG4BAEAAAABuQQEAAAAAboEBAAAAAG7BAIAAAABvAQIAAAAAb0EBAAAAAG-BAQAAAABvwQIAAAAAcAECAAAAAHBBAgAAAABwgQEAAAAAcMEBAAAAAHEBEAAAAABASMAALcBADABIwAAtwEAMBm-AwIArgYAIb8DAQCsBgAhwgMBAKwGACHZAwEArAYAIeIDQADBBgAh4wNAAMEGACGxBAQA5QgAIbIEBADlCAAhswQCAK4GACG0BAIArgYAIbYEAQCsBgAhtwQBAKwGACG4BAEArAYAIbkEBADlCAAhugQEAOUIACG7BAIArgYAIbwECADkCAAhvQQEAOUIACG-BAQA5QgAIb8ECADkCAAhwAQIAOQIACHBBAgA5AgAIcIEBADlCAAhwwQEAOUIACHEBEAAwQYAIQIAAACtAQAgIwAAugEAIBm-AwIArgYAIb8DAQCsBgAhwgMBAKwGACHZAwEArAYAIeIDQADBBgAh4wNAAMEGACGxBAQA5QgAIbIEBADlCAAhswQCAK4GACG0BAIArgYAIbYEAQCsBgAhtwQBAKwGACG4BAEArAYAIbkEBADlCAAhugQEAOUIACG7BAIArgYAIbwECADkCAAhvQQEAOUIACG-BAQA5QgAIb8ECADkCAAhwAQIAOQIACHBBAgA5AgAIcIEBADlCAAhwwQEAOUIACHEBEAAwQYAIQIAAACwAQAgIwAAvAEAIAIAAACwAQAgIwAAvAEAIAMAAACtAQAgKgAAtQEAICsAALoBACABAAAArQEAIAEAAACwAQAgBQYAAOYIACAwAADnCAAgMQAA6ggAIDIAAOkIACAzAADoCAAgHLsDAAD3BQAwvAMAAMMBABC9AwAA9wUAML4DAgCmBQAhvwMBAKcFACHCAwEApwUAIdkDAQCnBQAh4gNAALcFACHjA0AAtwUAIbEEBADvBQAhsgQEAO8FACGzBAIApgUAIbQEAgCmBQAhtgQBAKcFACG3BAEApwUAIbgEAQCnBQAhuQQEAO8FACG6BAQA7wUAIbsEAgCmBQAhvAQIAO4FACG9BAQA7wUAIb4EBADvBQAhvwQIAO4FACHABAgA7gUAIcEECADuBQAhwgQEAO8FACHDBAQA7wUAIcQEQAC3BQAhAwAAALABACABAADCAQAwLwAAwwEAIAMAAACwAQAgAQAAsQEAMAIAAK0BACAQuwMAAPMFADC8AwAAyQEAEL0DAADzBQAwvgMCAAAAAb8DAQDTBQAh4gNAANQFACH-A0AA1AUAIa0ECAD0BQAhrgQEAPUFACGvBAgA9AUAIbAEBAD1BQAhsQQEAPUFACGyBAQA9QUAIbMEAgDSBQAhtAQCANIFACG1BAAA9gUAIAEAAADGAQAgAQAAAMYBACAPuwMAAPMFADC8AwAAyQEAEL0DAADzBQAwvgMCANIFACG_AwEA0wUAIeIDQADUBQAh_gNAANQFACGtBAgA9AUAIa4EBAD1BQAhrwQIAPQFACGwBAQA9QUAIbEEBAD1BQAhsgQEAPUFACGzBAIA0gUAIbQEAgDSBQAhAAMAAADJAQAgAQAAygEAMAIAAMYBACADAAAAyQEAIAEAAMoBADACAADGAQAgAwAAAMkBACABAADKAQAwAgAAxgEAIAy-AwIAAAABvwMBAAAAAeIDQAAAAAH-A0AAAAABrQQIAAAAAa4EBAAAAAGvBAgAAAABsAQEAAAAAbEEBAAAAAGyBAQAAAABswQCAAAAAbQEAgAAAAEBIwAAzgEAIAy-AwIAAAABvwMBAAAAAeIDQAAAAAH-A0AAAAABrQQIAAAAAa4EBAAAAAGvBAgAAAABsAQEAAAAAbEEBAAAAAGyBAQAAAABswQCAAAAAbQEAgAAAAEBIwAA0AEAMAEjAADQAQAwDL4DAgCuBgAhvwMBAKwGACHiA0AAwQYAIf4DQADBBgAhrQQIAOQIACGuBAQA5QgAIa8ECADkCAAhsAQEAOUIACGxBAQA5QgAIbIEBADlCAAhswQCAK4GACG0BAIArgYAIQIAAADGAQAgIwAA0wEAIAy-AwIArgYAIb8DAQCsBgAh4gNAAMEGACH-A0AAwQYAIa0ECADkCAAhrgQEAOUIACGvBAgA5AgAIbAEBADlCAAhsQQEAOUIACGyBAQA5QgAIbMEAgCuBgAhtAQCAK4GACECAAAAyQEAICMAANUBACACAAAAyQEAICMAANUBACADAAAAxgEAICoAAM4BACArAADTAQAgAQAAAMYBACABAAAAyQEAIAUGAADfCAAgMAAA4AgAIDEAAOMIACAyAADiCAAgMwAA4QgAIA-7AwAA7QUAMLwDAADcAQAQvQMAAO0FADC-AwIApgUAIb8DAQCnBQAh4gNAALcFACH-A0AAtwUAIa0ECADuBQAhrgQEAO8FACGvBAgA7gUAIbAEBADvBQAhsQQEAO8FACGyBAQA7wUAIbMEAgCmBQAhtAQCAKYFACEDAAAAyQEAIAEAANsBADAvAADcAQAgAwAAAMkBACABAADKAQAwAgAAxgEAIAy7AwAA7AUAMLwDAADiAQAQvQMAAOwFADC-AwIAAAABwgMBAAAAAdkDAQDTBQAh4gNAANQFACHjA0AA1AUAIakEAQDTBQAhqgQCANIFACGrBAEA0wUAIawEQADUBQAhAQAAAN8BACABAAAA3wEAIAy7AwAA7AUAMLwDAADiAQAQvQMAAOwFADC-AwIA0gUAIcIDAQDTBQAh2QMBANMFACHiA0AA1AUAIeMDQADUBQAhqQQBANMFACGqBAIA0gUAIasEAQDTBQAhrARAANQFACEAAwAAAOIBACABAADjAQAwAgAA3wEAIAMAAADiAQAgAQAA4wEAMAIAAN8BACADAAAA4gEAIAEAAOMBADACAADfAQAgCb4DAgAAAAHCAwEAAAAB2QMBAAAAAeIDQAAAAAHjA0AAAAABqQQBAAAAAaoEAgAAAAGrBAEAAAABrARAAAAAAQEjAADnAQAgCb4DAgAAAAHCAwEAAAAB2QMBAAAAAeIDQAAAAAHjA0AAAAABqQQBAAAAAaoEAgAAAAGrBAEAAAABrARAAAAAAQEjAADpAQAwASMAAOkBADAJvgMCAK4GACHCAwEArAYAIdkDAQCsBgAh4gNAAMEGACHjA0AAwQYAIakEAQCsBgAhqgQCAK4GACGrBAEArAYAIawEQADBBgAhAgAAAN8BACAjAADsAQAgCb4DAgCuBgAhwgMBAKwGACHZAwEArAYAIeIDQADBBgAh4wNAAMEGACGpBAEArAYAIaoEAgCuBgAhqwQBAKwGACGsBEAAwQYAIQIAAADiAQAgIwAA7gEAIAIAAADiAQAgIwAA7gEAIAMAAADfAQAgKgAA5wEAICsAAOwBACABAAAA3wEAIAEAAADiAQAgBQYAANoIACAwAADbCAAgMQAA3ggAIDIAAN0IACAzAADcCAAgDLsDAADrBQAwvAMAAPUBABC9AwAA6wUAML4DAgCmBQAhwgMBAKcFACHZAwEApwUAIeIDQAC3BQAh4wNAALcFACGpBAEApwUAIaoEAgCmBQAhqwQBAKcFACGsBEAAtwUAIQMAAADiAQAgAQAA9AEAMC8AAPUBACADAAAA4gEAIAEAAOMBADACAADfAQAgCLsDAADqBQAwvAMAAPsBABC9AwAA6gUAML4DAgAAAAHiA0AA1AUAIeMDQADUBQAhpwQBAAAAAagEAgDSBQAhAQAAAPgBACABAAAA-AEAIAi7AwAA6gUAMLwDAAD7AQAQvQMAAOoFADC-AwIA0gUAIeIDQADUBQAh4wNAANQFACGnBAEA0wUAIagEAgDSBQAhAAMAAAD7AQAgAQAA_AEAMAIAAPgBACADAAAA-wEAIAEAAPwBADACAAD4AQAgAwAAAPsBACABAAD8AQAwAgAA-AEAIAW-AwIAAAAB4gNAAAAAAeMDQAAAAAGnBAEAAAABqAQCAAAAAQEjAACAAgAgBb4DAgAAAAHiA0AAAAAB4wNAAAAAAacEAQAAAAGoBAIAAAABASMAAIICADABIwAAggIAMAW-AwIArgYAIeIDQADBBgAh4wNAAMEGACGnBAEArAYAIagEAgCuBgAhAgAAAPgBACAjAACFAgAgBb4DAgCuBgAh4gNAAMEGACHjA0AAwQYAIacEAQCsBgAhqAQCAK4GACECAAAA-wEAICMAAIcCACACAAAA-wEAICMAAIcCACADAAAA-AEAICoAAIACACArAACFAgAgAQAAAPgBACABAAAA-wEAIAUGAADVCAAgMAAA1ggAIDEAANkIACAyAADYCAAgMwAA1wgAIAi7AwAA6QUAMLwDAACOAgAQvQMAAOkFADC-AwIApgUAIeIDQAC3BQAh4wNAALcFACGnBAEApwUAIagEAgCmBQAhAwAAAPsBACABAACNAgAwLwAAjgIAIAMAAAD7AQAgAQAA_AEAMAIAAPgBACAKuwMAAOgFADC8AwAAlAIAEL0DAADoBQAwvgMCAAAAAeIDQADUBQAh4wNAANQFACGjBAEAAAABpAQBAN8FACGlBEAA1AUAIaYEAADZBQAgAQAAAJECACABAAAAkQIAIAq7AwAA6AUAMLwDAACUAgAQvQMAAOgFADC-AwIA0gUAIeIDQADUBQAh4wNAANQFACGjBAEA0wUAIaQEAQDfBQAhpQRAANQFACGmBAAA2QUAIAKkBAAApgYAIKYEAACmBgAgAwAAAJQCACABAACVAgAwAgAAkQIAIAMAAACUAgAgAQAAlQIAMAIAAJECACADAAAAlAIAIAEAAJUCADACAACRAgAgB74DAgAAAAHiA0AAAAAB4wNAAAAAAaMEAQAAAAGkBAEAAAABpQRAAAAAAaYEgAAAAAEBIwAAmQIAIAe-AwIAAAAB4gNAAAAAAeMDQAAAAAGjBAEAAAABpAQBAAAAAaUEQAAAAAGmBIAAAAABASMAAJsCADABIwAAmwIAMAe-AwIArgYAIeIDQADBBgAh4wNAAMEGACGjBAEArAYAIaQEAQCtBgAhpQRAAMEGACGmBIAAAAABAgAAAJECACAjAACeAgAgB74DAgCuBgAh4gNAAMEGACHjA0AAwQYAIaMEAQCsBgAhpAQBAK0GACGlBEAAwQYAIaYEgAAAAAECAAAAlAIAICMAAKACACACAAAAlAIAICMAAKACACADAAAAkQIAICoAAJkCACArAACeAgAgAQAAAJECACABAAAAlAIAIAcGAADQCAAgMAAA0QgAIDEAANQIACAyAADTCAAgMwAA0ggAIKQEAACmBgAgpgQAAKYGACAKuwMAAOcFADC8AwAApwIAEL0DAADnBQAwvgMCAKYFACHiA0AAtwUAIeMDQAC3BQAhowQBAKcFACGkBAEAqAUAIaUEQAC3BQAhpgQAAMQFACADAAAAlAIAIAEAAKYCADAvAACnAgAgAwAAAJQCACABAACVAgAwAgAAkQIAIAm7AwAA5gUAMLwDAACtAgAQvQMAAOYFADC-AwIAAAABzgMBANMFACHZAwEA0wUAIeMDQADUBQAhoQQBAAAAAaIEAQDTBQAhAQAAAKoCACABAAAAqgIAIAm7AwAA5gUAMLwDAACtAgAQvQMAAOYFADC-AwIA0gUAIc4DAQDTBQAh2QMBANMFACHjA0AA1AUAIaEEAQDTBQAhogQBANMFACEAAwAAAK0CACABAACuAgAwAgAAqgIAIAMAAACtAgAgAQAArgIAMAIAAKoCACADAAAArQIAIAEAAK4CADACAACqAgAgBr4DAgAAAAHOAwEAAAAB2QMBAAAAAeMDQAAAAAGhBAEAAAABogQBAAAAAQEjAACyAgAgBr4DAgAAAAHOAwEAAAAB2QMBAAAAAeMDQAAAAAGhBAEAAAABogQBAAAAAQEjAAC0AgAwASMAALQCADAGvgMCAK4GACHOAwEArAYAIdkDAQCsBgAh4wNAAMEGACGhBAEArAYAIaIEAQCsBgAhAgAAAKoCACAjAAC3AgAgBr4DAgCuBgAhzgMBAKwGACHZAwEArAYAIeMDQADBBgAhoQQBAKwGACGiBAEArAYAIQIAAACtAgAgIwAAuQIAIAIAAACtAgAgIwAAuQIAIAMAAACqAgAgKgAAsgIAICsAALcCACABAAAAqgIAIAEAAACtAgAgBQYAAMsIACAwAADMCAAgMQAAzwgAIDIAAM4IACAzAADNCAAgCbsDAADlBQAwvAMAAMACABC9AwAA5QUAML4DAgCmBQAhzgMBAKcFACHZAwEApwUAIeMDQAC3BQAhoQQBAKcFACGiBAEApwUAIQMAAACtAgAgAQAAvwIAMC8AAMACACADAAAArQIAIAEAAK4CADACAACqAgAgCMoBAADVBQAguwMAANEFADC8AwAA1QIAEL0DAADRBQAwvgMCAAAAAcIDAQDTBQAh4gNAANQFACGPBAEAAAABAQAAAMMCACAXxgEAAOIFACDIAQAA4wUAIMkBAADkBQAguwMAAN4FADC8AwAAxQIAEL0DAADeBQAwvgMCANIFACHCAwEA0wUAIeIDQADUBQAhjgQBANMFACGPBAEA0wUAIZAEAQDTBQAhkQQBAN8FACGSBAIA0gUAIZMEAgDSBQAhlAQCANIFACGVBAIA0gUAIZYEAgDSBQAhlwQCANIFACGYBAIA4AUAIZkEQADdBQAhmgQgAOEFACGbBEAA3QUAIQfGAQAAyAgAIMgBAADJCAAgyQEAAMoIACCRBAAApgYAIJgEAACmBgAgmQQAAKYGACCbBAAApgYAIBfGAQAA4gUAIMgBAADjBQAgyQEAAOQFACC7AwAA3gUAMLwDAADFAgAQvQMAAN4FADC-AwIAAAABwgMBANMFACHiA0AA1AUAIY4EAQAAAAGPBAEA0wUAIZAEAQDTBQAhkQQBAN8FACGSBAIA0gUAIZMEAgDSBQAhlAQCANIFACGVBAIA0gUAIZYEAgDSBQAhlwQCANIFACGYBAIA4AUAIZkEQADdBQAhmgQgAOEFACGbBEAA3QUAIQMAAADFAgAgAQAAxgIAMAIAAMcCACATxwEAANoFACC7AwAA3AUAMLwDAADJAgAQvQMAANwFADC-AwIA0gUAIeIDQADUBQAh-wNAANQFACGBBAIA0gUAIYMECADYBQAhhAQIANgFACGFBAAA2QUAIIYECADYBQAhhwQIANgFACGIBAgA2AUAIYkECADYBQAhigQIANgFACGLBAgA2AUAIYwEAQDTBQAhjQRAAN0FACELxwEAAMcIACCDBAAApgYAIIQEAACmBgAghQQAAKYGACCGBAAApgYAIIcEAACmBgAgiAQAAKYGACCJBAAApgYAIIoEAACmBgAgiwQAAKYGACCNBAAApgYAIBTHAQAA2gUAILsDAADcBQAwvAMAAMkCABC9AwAA3AUAML4DAgAAAAHiA0AA1AUAIfsDQADUBQAhgQQCANIFACGDBAgA2AUAIYQECADYBQAhhQQAANkFACCGBAgA2AUAIYcECADYBQAhiAQIANgFACGJBAgA2AUAIYoECADYBQAhiwQIANgFACGMBAEA0wUAIY0EQADdBQAhoAQAANsFACADAAAAyQIAIAEAAMoCADACAADLAgAgC8cBAADaBQAguwMAANcFADC8AwAAzQIAEL0DAADXBQAwvgMCANIFACHiA0AA1AUAIYEEAgDSBQAhggRAANQFACGDBAgA2AUAIYQECADYBQAhhQQAANkFACAExwEAAMcIACCDBAAApgYAIIQEAACmBgAghQQAAKYGACAMxwEAANoFACC7AwAA1wUAMLwDAADNAgAQvQMAANcFADC-AwIAAAAB4gNAANQFACGBBAIA0gUAIYIEQADUBQAhgwQIANgFACGEBAgA2AUAIYUEAADZBQAgnwQAANYFACADAAAAzQIAIAEAAM4CADACAADPAgAgAQAAAMkCACABAAAAzQIAIAEAAADFAgAgAQAAAMMCACAIygEAANUFACC7AwAA0QUAMLwDAADVAgAQvQMAANEFADC-AwIA0gUAIcIDAQDTBQAh4gNAANQFACGPBAEA0wUAIQHKAQAAxggAIAMAAADVAgAgAQAA1gIAMAIAAMMCACADAAAA1QIAIAEAANYCADACAADDAgAgAwAAANUCACABAADWAgAwAgAAwwIAIAXKAQAAxQgAIL4DAgAAAAHCAwEAAAAB4gNAAAAAAY8EAQAAAAEBIwAA2gIAIAS-AwIAAAABwgMBAAAAAeIDQAAAAAGPBAEAAAABASMAANwCADABIwAA3AIAMAXKAQAAuAgAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIY8EAQCsBgAhAgAAAMMCACAjAADfAgAgBL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIY8EAQCsBgAhAgAAANUCACAjAADhAgAgAgAAANUCACAjAADhAgAgAwAAAMMCACAqAADaAgAgKwAA3wIAIAEAAADDAgAgAQAAANUCACAFBgAAswgAIDAAALQIACAxAAC3CAAgMgAAtggAIDMAALUIACAHuwMAANAFADC8AwAA6AIAEL0DAADQBQAwvgMCAKYFACHCAwEApwUAIeIDQAC3BQAhjwQBAKcFACEDAAAA1QIAIAEAAOcCADAvAADoAgAgAwAAANUCACABAADWAgAwAgAAwwIAIAEAAADHAgAgAQAAAMcCACADAAAAxQIAIAEAAMYCADACAADHAgAgAwAAAMUCACABAADGAgAwAgAAxwIAIAMAAADFAgAgAQAAxgIAMAIAAMcCACAUxgEAALAIACDIAQAAsQgAIMkBAACyCAAgvgMCAAAAAcIDAQAAAAHiA0AAAAABjgQBAAAAAY8EAQAAAAGQBAEAAAABkQQBAAAAAZIEAgAAAAGTBAIAAAABlAQCAAAAAZUEAgAAAAGWBAIAAAABlwQCAAAAAZgEAgAAAAGZBEAAAAABmgQgAAAAAZsEQAAAAAEBIwAA8AIAIBG-AwIAAAABwgMBAAAAAeIDQAAAAAGOBAEAAAABjwQBAAAAAZAEAQAAAAGRBAEAAAABkgQCAAAAAZMEAgAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAIAAAABmAQCAAAAAZkEQAAAAAGaBCAAAAABmwRAAAAAAQEjAADyAgAwASMAAPICADAUxgEAAJUIACDIAQAAlggAIMkBAACXCAAgvgMCAK4GACHCAwEArAYAIeIDQADBBgAhjgQBAKwGACGPBAEArAYAIZAEAQCsBgAhkQQBAK0GACGSBAIArgYAIZMEAgCuBgAhlAQCAK4GACGVBAIArgYAIZYEAgCuBgAhlwQCAK4GACGYBAIAwgYAIZkEQAC_BgAhmgQgAMAGACGbBEAAvwYAIQIAAADHAgAgIwAA9QIAIBG-AwIArgYAIcIDAQCsBgAh4gNAAMEGACGOBAEArAYAIY8EAQCsBgAhkAQBAKwGACGRBAEArQYAIZIEAgCuBgAhkwQCAK4GACGUBAIArgYAIZUEAgCuBgAhlgQCAK4GACGXBAIArgYAIZgEAgDCBgAhmQRAAL8GACGaBCAAwAYAIZsEQAC_BgAhAgAAAMUCACAjAAD3AgAgAgAAAMUCACAjAAD3AgAgAwAAAMcCACAqAADwAgAgKwAA9QIAIAEAAADHAgAgAQAAAMUCACAJBgAAkAgAIDAAAJEIACAxAACUCAAgMgAAkwgAIDMAAJIIACCRBAAApgYAIJgEAACmBgAgmQQAAKYGACCbBAAApgYAIBS7AwAAzwUAMLwDAAD-AgAQvQMAAM8FADC-AwIApgUAIcIDAQCnBQAh4gNAALcFACGOBAEApwUAIY8EAQCnBQAhkAQBAKcFACGRBAEAqAUAIZIEAgCmBQAhkwQCAKYFACGUBAIApgUAIZUEAgCmBQAhlgQCAKYFACGXBAIApgUAIZgEAgCzBQAhmQRAALQFACGaBCAAtgUAIZsEQAC0BQAhAwAAAMUCACABAAD9AgAwLwAA_gIAIAMAAADFAgAgAQAAxgIAMAIAAMcCACABAAAAywIAIAEAAADLAgAgAwAAAMkCACABAADKAgAwAgAAywIAIAMAAADJAgAgAQAAygIAMAIAAMsCACADAAAAyQIAIAEAAMoCADACAADLAgAgEMcBAACPCAAgvgMCAAAAAeIDQAAAAAH7A0AAAAABgQQCAAAAAYMECAAAAAGEBAgAAAABhQSAAAAAAYYECAAAAAGHBAgAAAABiAQIAAAAAYkECAAAAAGKBAgAAAABiwQIAAAAAYwEAQAAAAGNBEAAAAABASMAAIYDACAPvgMCAAAAAeIDQAAAAAH7A0AAAAABgQQCAAAAAYMECAAAAAGEBAgAAAABhQSAAAAAAYYECAAAAAGHBAgAAAABiAQIAAAAAYkECAAAAAGKBAgAAAABiwQIAAAAAYwEAQAAAAGNBEAAAAABASMAAIgDADABIwAAiAMAMBDHAQAAjggAIL4DAgCuBgAh4gNAAMEGACH7A0AAwQYAIYEEAgCuBgAhgwQIAIYIACGEBAgAhggAIYUEgAAAAAGGBAgAhggAIYcECACGCAAhiAQIAIYIACGJBAgAhggAIYoECACGCAAhiwQIAIYIACGMBAEArAYAIY0EQAC_BgAhAgAAAMsCACAjAACLAwAgD74DAgCuBgAh4gNAAMEGACH7A0AAwQYAIYEEAgCuBgAhgwQIAIYIACGEBAgAhggAIYUEgAAAAAGGBAgAhggAIYcECACGCAAhiAQIAIYIACGJBAgAhggAIYoECACGCAAhiwQIAIYIACGMBAEArAYAIY0EQAC_BgAhAgAAAMkCACAjAACNAwAgAgAAAMkCACAjAACNAwAgAwAAAMsCACAqAACGAwAgKwAAiwMAIAEAAADLAgAgAQAAAMkCACAPBgAAiQgAIDAAAIoIACAxAACNCAAgMgAAjAgAIDMAAIsIACCDBAAApgYAIIQEAACmBgAghQQAAKYGACCGBAAApgYAIIcEAACmBgAgiAQAAKYGACCJBAAApgYAIIoEAACmBgAgiwQAAKYGACCNBAAApgYAIBK7AwAAzgUAMLwDAACUAwAQvQMAAM4FADC-AwIApgUAIeIDQAC3BQAh-wNAALcFACGBBAIApgUAIYMECADMBQAhhAQIAMwFACGFBAAAxAUAIIYECADMBQAhhwQIAMwFACGIBAgAzAUAIYkECADMBQAhigQIAMwFACGLBAgAzAUAIYwEAQCnBQAhjQRAALQFACEDAAAAyQIAIAEAAJMDADAvAACUAwAgAwAAAMkCACABAADKAgAwAgAAywIAIAEAAADPAgAgAQAAAM8CACADAAAAzQIAIAEAAM4CADACAADPAgAgAwAAAM0CACABAADOAgAwAgAAzwIAIAMAAADNAgAgAQAAzgIAMAIAAM8CACAIxwEAAIgIACC-AwIAAAAB4gNAAAAAAYEEAgAAAAGCBEAAAAABgwQIAAAAAYQECAAAAAGFBIAAAAABASMAAJwDACAHvgMCAAAAAeIDQAAAAAGBBAIAAAABggRAAAAAAYMECAAAAAGEBAgAAAABhQSAAAAAAQEjAACeAwAwASMAAJ4DADAIxwEAAIcIACC-AwIArgYAIeIDQADBBgAhgQQCAK4GACGCBEAAwQYAIYMECACGCAAhhAQIAIYIACGFBIAAAAABAgAAAM8CACAjAAChAwAgB74DAgCuBgAh4gNAAMEGACGBBAIArgYAIYIEQADBBgAhgwQIAIYIACGEBAgAhggAIYUEgAAAAAECAAAAzQIAICMAAKMDACACAAAAzQIAICMAAKMDACADAAAAzwIAICoAAJwDACArAAChAwAgAQAAAM8CACABAAAAzQIAIAgGAACBCAAgMAAAgggAIDEAAIUIACAyAACECAAgMwAAgwgAIIMEAACmBgAghAQAAKYGACCFBAAApgYAIAq7AwAAywUAMLwDAACqAwAQvQMAAMsFADC-AwIApgUAIeIDQAC3BQAhgQQCAKYFACGCBEAAtwUAIYMECADMBQAhhAQIAMwFACGFBAAAxAUAIAMAAADNAgAgAQAAqQMAMC8AAKoDACADAAAAzQIAIAEAAM4CADACAADPAgAgAQAAAAUAIAEAAAAFACADAAAAAwAgAQAABAAwAgAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIAMAAAADACABAAAEADACAAAFACAOAwAA_wcAIAUAAIAIACC-AwIAAAABwgMBAAAAAc4DAgAAAAHeAwIAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB8wMBAAAAAfgDAQAAAAH-AwEAAAAB_wMCAAAAAYAEAQAAAAEBIwAAsgMAIAy-AwIAAAABwgMBAAAAAc4DAgAAAAHeAwIAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB8wMBAAAAAfgDAQAAAAH-AwEAAAAB_wMCAAAAAYAEAQAAAAEBIwAAtAMAMAEjAAC0AwAwDgMAAPEHACAFAADyBwAgvgMCAK4GACHCAwEArAYAIc4DAgCuBgAh3gMCAK4GACHfAyAAwAYAIeIDQADBBgAh4wNAAMEGACHzAwEArAYAIfgDAQCsBgAh_gMBAKwGACH_AwIArgYAIYAEAQCsBgAhAgAAAAUAICMAALcDACAMvgMCAK4GACHCAwEArAYAIc4DAgCuBgAh3gMCAK4GACHfAyAAwAYAIeIDQADBBgAh4wNAAMEGACHzAwEArAYAIfgDAQCsBgAh_gMBAKwGACH_AwIArgYAIYAEAQCsBgAhAgAAAAMAICMAALkDACACAAAAAwAgIwAAuQMAIAMAAAAFACAqAACyAwAgKwAAtwMAIAEAAAAFACABAAAAAwAgBQYAAOwHACAwAADtBwAgMQAA8AcAIDIAAO8HACAzAADuBwAgD7sDAADKBQAwvAMAAMADABC9AwAAygUAML4DAgCmBQAhwgMBAKcFACHOAwIApgUAId4DAgCmBQAh3wMgALYFACHiA0AAtwUAIeMDQAC3BQAh8wMBAKcFACH4AwEApwUAIf4DAQCnBQAh_wMCAKYFACGABAEApwUAIQMAAAADACABAAC_AwAwLwAAwAMAIAMAAAADACABAAAEADACAAAFACABAAAACQAgAQAAAAkAIAMAAAAHACABAAAIADACAAAJACADAAAABwAgAQAACAAwAgAACQAgAwAAAAcAIAEAAAgAMAIAAAkAIAoDAADrBwAgBAAA6gcAIL4DAgAAAAHOAwIAAAAB4gNAAAAAAfgDAQAAAAH6AwIAAAAB-wMBAAAAAfwDAgAAAAH9AwEAAAABASMAAMgDACAIvgMCAAAAAc4DAgAAAAHiA0AAAAAB-AMBAAAAAfoDAgAAAAH7AwEAAAAB_AMCAAAAAf0DAQAAAAEBIwAAygMAMAEjAADKAwAwCgMAAOkHACAEAADoBwAgvgMCAK4GACHOAwIArgYAIeIDQADBBgAh-AMBAKwGACH6AwIArgYAIfsDAQCsBgAh_AMCAK4GACH9AwEArAYAIQIAAAAJACAjAADNAwAgCL4DAgCuBgAhzgMCAK4GACHiA0AAwQYAIfgDAQCsBgAh-gMCAK4GACH7AwEArAYAIfwDAgCuBgAh_QMBAKwGACECAAAABwAgIwAAzwMAIAIAAAAHACAjAADPAwAgAwAAAAkAICoAAMgDACArAADNAwAgAQAAAAkAIAEAAAAHACAFBgAA4wcAIDAAAOQHACAxAADnBwAgMgAA5gcAIDMAAOUHACALuwMAAMkFADC8AwAA1gMAEL0DAADJBQAwvgMCAKYFACHOAwIApgUAIeIDQAC3BQAh-AMBAKcFACH6AwIApgUAIfsDAQCnBQAh_AMCAKYFACH9AwEApwUAIQMAAAAHACABAADVAwAwLwAA1gMAIAMAAAAHACABAAAIADACAAAJACABAAAADwAgAQAAAA8AIAMAAAANACABAAAOADACAAAPACADAAAADQAgAQAADgAwAgAADwAgAwAAAA0AIAEAAA4AMAIAAA8AIAoDAADhBwAgCgAA4gcAIL4DAgAAAAHOAwIAAAAB1QMBAAAAAd8DIAAAAAHiA0AAAAAB4wNAAAAAAfUDAQAAAAH4AwEAAAABASMAAN4DACAIvgMCAAAAAc4DAgAAAAHVAwEAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB9QMBAAAAAfgDAQAAAAEBIwAA4AMAMAEjAADgAwAwCgMAANMHACAKAADUBwAgvgMCAK4GACHOAwIArgYAIdUDAQCsBgAh3wMgAMAGACHiA0AAwQYAIeMDQADBBgAh9QMBAKwGACH4AwEArAYAIQIAAAAPACAjAADjAwAgCL4DAgCuBgAhzgMCAK4GACHVAwEArAYAId8DIADABgAh4gNAAMEGACHjA0AAwQYAIfUDAQCsBgAh-AMBAKwGACECAAAADQAgIwAA5QMAIAIAAAANACAjAADlAwAgAwAAAA8AICoAAN4DACArAADjAwAgAQAAAA8AIAEAAAANACAFBgAAzgcAIDAAAM8HACAxAADSBwAgMgAA0QcAIDMAANAHACALuwMAAMgFADC8AwAA7AMAEL0DAADIBQAwvgMCAKYFACHOAwIApgUAIdUDAQCnBQAh3wMgALYFACHiA0AAtwUAIeMDQAC3BQAh9QMBAKcFACH4AwEApwUAIQMAAAANACABAADrAwAwLwAA7AMAIAMAAAANACABAAAOADACAAAPACABAAAAEwAgAQAAABMAIAMAAAARACABAAASADACAAATACADAAAAEQAgAQAAEgAwAgAAEwAgAwAAABEAIAEAABIAMAIAABMAIAoJAADNBwAgvgMCAAAAAdUDAQAAAAHaA0AAAAAB3gMCAAAAAeIDQAAAAAHjA0AAAAAB9wMCAAAAAfgDAQAAAAH5AyAAAAABASMAAPQDACAJvgMCAAAAAdUDAQAAAAHaA0AAAAAB3gMCAAAAAeIDQAAAAAHjA0AAAAAB9wMCAAAAAfgDAQAAAAH5AyAAAAABASMAAPYDADABIwAA9gMAMAoJAADMBwAgvgMCAK4GACHVAwEArAYAIdoDQAC_BgAh3gMCAK4GACHiA0AAwQYAIeMDQADBBgAh9wMCAK4GACH4AwEArAYAIfkDIADABgAhAgAAABMAICMAAPkDACAJvgMCAK4GACHVAwEArAYAIdoDQAC_BgAh3gMCAK4GACHiA0AAwQYAIeMDQADBBgAh9wMCAK4GACH4AwEArAYAIfkDIADABgAhAgAAABEAICMAAPsDACACAAAAEQAgIwAA-wMAIAMAAAATACAqAAD0AwAgKwAA-QMAIAEAAAATACABAAAAEQAgBgYAAMcHACAwAADIBwAgMQAAywcAIDIAAMoHACAzAADJBwAg2gMAAKYGACAMuwMAAMcFADC8AwAAggQAEL0DAADHBQAwvgMCAKYFACHVAwEApwUAIdoDQAC0BQAh3gMCAKYFACHiA0AAtwUAIeMDQAC3BQAh9wMCAKYFACH4AwEApwUAIfkDIAC2BQAhAwAAABEAIAEAAIEEADAvAACCBAAgAwAAABEAIAEAABIAMAIAABMAIAEAAAAYACABAAAAGAAgAwAAABYAIAEAABcAMAIAABgAIAMAAAAWACABAAAXADACAAAYACADAAAAFgAgAQAAFwAwAgAAGAAgCQMAAMYHACAKgAAAAAG-AwIAAAABwgMBAAAAAc4DAgAAAAHiA0AAAAAB4wNAAAAAAfUDAQAAAAH2AyAAAAABASMAAIoEACAICoAAAAABvgMCAAAAAcIDAQAAAAHOAwIAAAAB4gNAAAAAAeMDQAAAAAH1AwEAAAAB9gMgAAAAAQEjAACMBAAwASMAAIwEADABAAAAGgAgCQMAAMUHACAKgAAAAAG-AwIArgYAIcIDAQCsBgAhzgMCAMIGACHiA0AAwQYAIeMDQADBBgAh9QMBAKwGACH2AyAAwAYAIQIAAAAYACAjAACQBAAgCAqAAAAAAb4DAgCuBgAhwgMBAKwGACHOAwIAwgYAIeIDQADBBgAh4wNAAMEGACH1AwEArAYAIfYDIADABgAhAgAAABYAICMAAJIEACACAAAAFgAgIwAAkgQAIAEAAAAaACADAAAAGAAgKgAAigQAICsAAJAEACABAAAAGAAgAQAAABYAIAYGAADABwAgMAAAwQcAIDEAAMQHACAyAADDBwAgMwAAwgcAIM4DAACmBgAgCwoAALUFACC7AwAAxgUAMLwDAACaBAAQvQMAAMYFADC-AwIApgUAIcIDAQCnBQAhzgMCALMFACHiA0AAtwUAIeMDQAC3BQAh9QMBAKcFACH2AyAAtgUAIQMAAAAWACABAACZBAAwLwAAmgQAIAMAAAAWACABAAAXADACAAAYACABAAAAHgAgAQAAAB4AIAMAAAAcACABAAAdADACAAAeACADAAAAHAAgAQAAHQAwAgAAHgAgAwAAABwAIAEAAB0AMAIAAB4AIBENAAC8BwAgDwAAvQcAIBcAAL8HACAYAAC-BwAgvgMCAAAAAb8DAQAAAAHCAwEAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB7gMBAAAAAe8DAgAAAAHwAyAAAAAB8QOAAAAAAfIDgAAAAAHzAwEAAAAB9AMBAAAAAQEjAACiBAAgDb4DAgAAAAG_AwEAAAABwgMBAAAAAd8DIAAAAAHiA0AAAAAB4wNAAAAAAe4DAQAAAAHvAwIAAAAB8AMgAAAAAfEDgAAAAAHyA4AAAAAB8wMBAAAAAfQDAQAAAAEBIwAApAQAMAEjAACkBAAwEQ0AAJcHACAPAACYBwAgFwAAmgcAIBgAAJkHACC-AwIArgYAIb8DAQCsBgAhwgMBAKwGACHfAyAAwAYAIeIDQADBBgAh4wNAAMEGACHuAwEArAYAIe8DAgCuBgAh8AMgAMAGACHxA4AAAAAB8gOAAAAAAfMDAQCtBgAh9AMBAKwGACECAAAAHgAgIwAApwQAIA2-AwIArgYAIb8DAQCsBgAhwgMBAKwGACHfAyAAwAYAIeIDQADBBgAh4wNAAMEGACHuAwEArAYAIe8DAgCuBgAh8AMgAMAGACHxA4AAAAAB8gOAAAAAAfMDAQCtBgAh9AMBAKwGACECAAAAHAAgIwAAqQQAIAIAAAAcACAjAACpBAAgAwAAAB4AICoAAKIEACArAACnBAAgAQAAAB4AIAEAAAAcACAHBgAAkgcAIDAAAJMHACAxAACWBwAgMgAAlQcAIDMAAJQHACDyAwAApgYAIPMDAACmBgAgELsDAADDBQAwvAMAALAEABC9AwAAwwUAML4DAgCmBQAhvwMBAKcFACHCAwEApwUAId8DIAC2BQAh4gNAALcFACHjA0AAtwUAIe4DAQCnBQAh7wMCAKYFACHwAyAAtgUAIfEDAAC1BQAg8gMAAMQFACDzAwEAqAUAIfQDAQCnBQAhAwAAABwAIAEAAK8EADAvAACwBAAgAwAAABwAIAEAAB0AMAIAAB4AIAEAAAAiACABAAAAIgAgAwAAACAAIAEAACEAMAIAACIAIAMAAAAgACABAAAhADACAAAiACADAAAAIAAgAQAAIQAwAgAAIgAgCQMAAJEHACAOAACQBwAgvgMCAAAAAb8DAQAAAAHOAwIAAAABzwMBAAAAAdIDAgAAAAHiA0AAAAAB7QMBAAAAAQEjAAC4BAAgB74DAgAAAAG_AwEAAAABzgMCAAAAAc8DAQAAAAHSAwIAAAAB4gNAAAAAAe0DAQAAAAEBIwAAugQAMAEjAAC6BAAwCQMAAI8HACAOAACOBwAgvgMCAK4GACG_AwEArAYAIc4DAgCuBgAhzwMBAK0GACHSAwIArgYAIeIDQADBBgAh7QMBAKwGACECAAAAIgAgIwAAvQQAIAe-AwIArgYAIb8DAQCsBgAhzgMCAK4GACHPAwEArQYAIdIDAgCuBgAh4gNAAMEGACHtAwEArAYAIQIAAAAgACAjAAC_BAAgAgAAACAAICMAAL8EACADAAAAIgAgKgAAuAQAICsAAL0EACABAAAAIgAgAQAAACAAIAYGAACJBwAgMAAAigcAIDEAAI0HACAyAACMBwAgMwAAiwcAIM8DAACmBgAgCrsDAADCBQAwvAMAAMYEABC9AwAAwgUAML4DAgCmBQAhvwMBAKcFACHOAwIApgUAIc8DAQCoBQAh0gMCAKYFACHiA0AAtwUAIe0DAQCnBQAhAwAAACAAIAEAAMUEADAvAADGBAAgAwAAACAAIAEAACEAMAIAACIAIAEAAAAmACABAAAAJgAgAwAAACQAIAEAACUAMAIAACYAIAMAAAAkACABAAAlADACAAAmACADAAAAJAAgAQAAJQAwAgAAJgAgDA4AAIcHACAXAACIBwAgvgMCAAAAAb8DAQAAAAHCAwEAAAABzwMBAAAAAdIDAgAAAAHeAwIAAAAB4gNAAAAAAeMDQAAAAAHrAwEAAAAB7AMBAAAAAQEjAADOBAAgCr4DAgAAAAG_AwEAAAABwgMBAAAAAc8DAQAAAAHSAwIAAAAB3gMCAAAAAeIDQAAAAAHjA0AAAAAB6wMBAAAAAewDAQAAAAEBIwAA0AQAMAEjAADQBAAwDA4AAPwGACAXAAD9BgAgvgMCAK4GACG_AwEArAYAIcIDAQCsBgAhzwMBAK0GACHSAwIArgYAId4DAgCuBgAh4gNAAMEGACHjA0AAwQYAIesDAQCtBgAh7AMBAKwGACECAAAAJgAgIwAA0wQAIAq-AwIArgYAIb8DAQCsBgAhwgMBAKwGACHPAwEArQYAIdIDAgCuBgAh3gMCAK4GACHiA0AAwQYAIeMDQADBBgAh6wMBAK0GACHsAwEArAYAIQIAAAAkACAjAADVBAAgAgAAACQAICMAANUEACADAAAAJgAgKgAAzgQAICsAANMEACABAAAAJgAgAQAAACQAIAcGAAD3BgAgMAAA-AYAIDEAAPsGACAyAAD6BgAgMwAA-QYAIM8DAACmBgAg6wMAAKYGACANuwMAAMEFADC8AwAA3AQAEL0DAADBBQAwvgMCAKYFACG_AwEApwUAIcIDAQCnBQAhzwMBAKgFACHSAwIApgUAId4DAgCmBQAh4gNAALcFACHjA0AAtwUAIesDAQCoBQAh7AMBAKcFACEDAAAAJAAgAQAA2wQAMC8AANwEACADAAAAJAAgAQAAJQAwAgAAJgAgAQAAACoAIAEAAAAqACADAAAAKAAgAQAAKQAwAgAAKgAgAwAAACgAIAEAACkAMAIAACoAIAMAAAAoACABAAApADACAAAqACAgDgAA7wYAIA8AAPQGACAQAADwBgAgEQAA9gYAIBIAAPEGACATAADyBgAgFAAA8wYAIBYAAPUGACC-AwIAAAABvwMBAAAAAc8DAQAAAAHQAwEAAAAB0QMBAAAAAdIDAgAAAAHTAwIAAAAB1AMCAAAAAdUDAQAAAAHWAwEAAAAB1wMBAAAAAdgDQAAAAAHZAwEAAAAB2gNAAAAAAdsDAgAAAAHcAwIAAAAB3QOAAAAAAd4DAgAAAAHfAyAAAAAB4ANAAAAAAeEDAQAAAAHiA0AAAAAB4wNAAAAAAeQDAgAAAAEBIwAA5AQAIBi-AwIAAAABvwMBAAAAAc8DAQAAAAHQAwEAAAAB0QMBAAAAAdIDAgAAAAHTAwIAAAAB1AMCAAAAAdUDAQAAAAHWAwEAAAAB1wMBAAAAAdgDQAAAAAHZAwEAAAAB2gNAAAAAAdsDAgAAAAHcAwIAAAAB3QOAAAAAAd4DAgAAAAHfAyAAAAAB4ANAAAAAAeEDAQAAAAHiA0AAAAAB4wNAAAAAAeQDAgAAAAEBIwAA5gQAMAEjAADmBAAwAQAAACgAIAEAAAAaACAgDgAAwwYAIA8AAMkGACAQAADEBgAgEQAAxQYAIBIAAMYGACATAADHBgAgFAAAyAYAIBYAAMoGACC-AwIArgYAIb8DAQCsBgAhzwMBAK0GACHQAwEArQYAIdEDAQCtBgAh0gMCAK4GACHTAwIArgYAIdQDAgDCBgAh1QMBAKwGACHWAwEArAYAIdcDAQCtBgAh2ANAAL8GACHZAwEArAYAIdoDQAC_BgAh2wMCAMIGACHcAwIArgYAId0DgAAAAAHeAwIArgYAId8DIADABgAh4ANAAL8GACHhAwEArQYAIeIDQADBBgAh4wNAAMEGACHkAwIAwgYAIQIAAAAqACAjAADrBAAgGL4DAgCuBgAhvwMBAKwGACHPAwEArQYAIdADAQCtBgAh0QMBAK0GACHSAwIArgYAIdMDAgCuBgAh1AMCAMIGACHVAwEArAYAIdYDAQCsBgAh1wMBAK0GACHYA0AAvwYAIdkDAQCsBgAh2gNAAL8GACHbAwIAwgYAIdwDAgCuBgAh3QOAAAAAAd4DAgCuBgAh3wMgAMAGACHgA0AAvwYAIeEDAQCtBgAh4gNAAMEGACHjA0AAwQYAIeQDAgDCBgAhAgAAACgAICMAAO0EACACAAAAKAAgIwAA7QQAIAEAAAAoACABAAAAGgAgAwAAACoAICoAAOQEACArAADrBAAgAQAAACoAIAEAAAAoACAQBgAAugYAIDAAALsGACAxAAC-BgAgMgAAvQYAIDMAALwGACDPAwAApgYAINADAACmBgAg0QMAAKYGACDUAwAApgYAINcDAACmBgAg2AMAAKYGACDaAwAApgYAINsDAACmBgAg4AMAAKYGACDhAwAApgYAIOQDAACmBgAgG7sDAACyBQAwvAMAAPYEABC9AwAAsgUAML4DAgCmBQAhvwMBAKcFACHPAwEAqAUAIdADAQCoBQAh0QMBAKgFACHSAwIApgUAIdMDAgCmBQAh1AMCALMFACHVAwEApwUAIdYDAQCnBQAh1wMBAKgFACHYA0AAtAUAIdkDAQCnBQAh2gNAALQFACHbAwIAswUAIdwDAgCmBQAh3QMAALUFACDeAwIApgUAId8DIAC2BQAh4ANAALQFACHhAwEAqAUAIeIDQAC3BQAh4wNAALcFACHkAwIAswUAIQMAAAAoACABAAD1BAAwLwAA9gQAIAMAAAAoACABAAApADACAAAqACABAAAAMQAgAQAAADEAIAMAAAAvACABAAAwADACAAAxACADAAAALwAgAQAAMAAwAgAAMQAgAwAAAC8AIAEAADAAMAIAADEAIAcDAAC5BgAgFQAAuAYAIL4DAgAAAAG_AwEAAAABwAMBAAAAAcEDAgAAAAHOAwIAAAABASMAAP4EACAFvgMCAAAAAb8DAQAAAAHAAwEAAAABwQMCAAAAAc4DAgAAAAEBIwAAgAUAMAEjAACABQAwBwMAALcGACAVAAC2BgAgvgMCAK4GACG_AwEArAYAIcADAQCtBgAhwQMCAK4GACHOAwIArgYAIQIAAAAxACAjAACDBQAgBb4DAgCuBgAhvwMBAKwGACHAAwEArQYAIcEDAgCuBgAhzgMCAK4GACECAAAALwAgIwAAhQUAIAIAAAAvACAjAACFBQAgAwAAADEAICoAAP4EACArAACDBQAgAQAAADEAIAEAAAAvACAGBgAAsQYAIDAAALIGACAxAAC1BgAgMgAAtAYAIDMAALMGACDAAwAApgYAIAi7AwAAsQUAMLwDAACMBQAQvQMAALEFADC-AwIApgUAIb8DAQCnBQAhwAMBAKgFACHBAwIApgUAIc4DAgCmBQAhAwAAAC8AIAEAAIsFADAvAACMBQAgAwAAAC8AIAEAADAAMAIAADEAIAEAAAA1ACABAAAANQAgAwAAADMAIAEAADQAMAIAADUAIAMAAAAzACABAAA0ADACAAA1ACADAAAAMwAgAQAANAAwAgAANQAgBhUAALAGACC-AwIAAAABvwMBAAAAAcADAQAAAAHBAwIAAAABwgMBAAAAAQEjAACUBQAgBb4DAgAAAAG_AwEAAAABwAMBAAAAAcEDAgAAAAHCAwEAAAABASMAAJYFADABIwAAlgUAMAYVAACvBgAgvgMCAK4GACG_AwEArAYAIcADAQCtBgAhwQMCAK4GACHCAwEArAYAIQIAAAA1ACAjAACZBQAgBb4DAgCuBgAhvwMBAKwGACHAAwEArQYAIcEDAgCuBgAhwgMBAKwGACECAAAAMwAgIwAAmwUAIAIAAAAzACAjAACbBQAgAwAAADUAICoAAJQFACArAACZBQAgAQAAADUAIAEAAAAzACAGBgAApwYAIDAAAKgGACAxAACrBgAgMgAAqgYAIDMAAKkGACDAAwAApgYAIAi7AwAApQUAMLwDAACiBQAQvQMAAKUFADC-AwIApgUAIb8DAQCnBQAhwAMBAKgFACHBAwIApgUAIcIDAQCnBQAhAwAAADMAIAEAAKEFADAvAACiBQAgAwAAADMAIAEAADQAMAIAADUAIAi7AwAApQUAMLwDAACiBQAQvQMAAKUFADC-AwIApgUAIb8DAQCnBQAhwAMBAKgFACHBAwIApgUAIcIDAQCnBQAhDQYAAK0FACAwAACwBQAgMQAArQUAIDIAAK0FACAzAACtBQAgwwMCAAAAAcQDAgAAAATFAwIAAAAExgMCAAAAAccDAgAAAAHIAwIAAAAByQMCAAAAAc0DAgCvBQAhDgYAAK0FACAyAACuBQAgMwAArgUAIMMDAQAAAAHEAwEAAAAExQMBAAAABMYDAQAAAAHHAwEAAAAByAMBAAAAAckDAQAAAAHKAwEAAAABywMBAAAAAcwDAQAAAAHNAwEArAUAIQ4GAACqBQAgMgAAqwUAIDMAAKsFACDDAwEAAAABxAMBAAAABcUDAQAAAAXGAwEAAAABxwMBAAAAAcgDAQAAAAHJAwEAAAABygMBAAAAAcsDAQAAAAHMAwEAAAABzQMBAKkFACEOBgAAqgUAIDIAAKsFACAzAACrBQAgwwMBAAAAAcQDAQAAAAXFAwEAAAAFxgMBAAAAAccDAQAAAAHIAwEAAAAByQMBAAAAAcoDAQAAAAHLAwEAAAABzAMBAAAAAc0DAQCpBQAhCMMDAgAAAAHEAwIAAAAFxQMCAAAABcYDAgAAAAHHAwIAAAAByAMCAAAAAckDAgAAAAHNAwIAqgUAIQvDAwEAAAABxAMBAAAABcUDAQAAAAXGAwEAAAABxwMBAAAAAcgDAQAAAAHJAwEAAAABygMBAAAAAcsDAQAAAAHMAwEAAAABzQMBAKsFACEOBgAArQUAIDIAAK4FACAzAACuBQAgwwMBAAAAAcQDAQAAAATFAwEAAAAExgMBAAAAAccDAQAAAAHIAwEAAAAByQMBAAAAAcoDAQAAAAHLAwEAAAABzAMBAAAAAc0DAQCsBQAhCMMDAgAAAAHEAwIAAAAExQMCAAAABMYDAgAAAAHHAwIAAAAByAMCAAAAAckDAgAAAAHNAwIArQUAIQvDAwEAAAABxAMBAAAABMUDAQAAAATGAwEAAAABxwMBAAAAAcgDAQAAAAHJAwEAAAABygMBAAAAAcsDAQAAAAHMAwEAAAABzQMBAK4FACENBgAArQUAIDAAALAFACAxAACtBQAgMgAArQUAIDMAAK0FACDDAwIAAAABxAMCAAAABMUDAgAAAATGAwIAAAABxwMCAAAAAcgDAgAAAAHJAwIAAAABzQMCAK8FACEIwwMIAAAAAcQDCAAAAATFAwgAAAAExgMIAAAAAccDCAAAAAHIAwgAAAAByQMIAAAAAc0DCACwBQAhCLsDAACxBQAwvAMAAIwFABC9AwAAsQUAML4DAgCmBQAhvwMBAKcFACHAAwEAqAUAIcEDAgCmBQAhzgMCAKYFACEbuwMAALIFADC8AwAA9gQAEL0DAACyBQAwvgMCAKYFACG_AwEApwUAIc8DAQCoBQAh0AMBAKgFACHRAwEAqAUAIdIDAgCmBQAh0wMCAKYFACHUAwIAswUAIdUDAQCnBQAh1gMBAKcFACHXAwEAqAUAIdgDQAC0BQAh2QMBAKcFACHaA0AAtAUAIdsDAgCzBQAh3AMCAKYFACHdAwAAtQUAIN4DAgCmBQAh3wMgALYFACHgA0AAtAUAIeEDAQCoBQAh4gNAALcFACHjA0AAtwUAIeQDAgCzBQAhDQYAAKoFACAwAADABQAgMQAAqgUAIDIAAKoFACAzAACqBQAgwwMCAAAAAcQDAgAAAAXFAwIAAAAFxgMCAAAAAccDAgAAAAHIAwIAAAAByQMCAAAAAc0DAgC_BQAhCwYAAKoFACAyAAC-BQAgMwAAvgUAIMMDQAAAAAHEA0AAAAAFxQNAAAAABcYDQAAAAAHHA0AAAAAByANAAAAAAckDQAAAAAHNA0AAvQUAIQ8GAACtBQAgMgAAvAUAIDMAALwFACDDA4AAAAABxgOAAAAAAccDgAAAAAHIA4AAAAAByQOAAAAAAc0DgAAAAAHlAwEAAAAB5gMBAAAAAecDAQAAAAHoA4AAAAAB6QOAAAAAAeoDgAAAAAEFBgAArQUAIDIAALsFACAzAAC7BQAgwwMgAAAAAc0DIAC6BQAhCwYAAK0FACAyAAC5BQAgMwAAuQUAIMMDQAAAAAHEA0AAAAAExQNAAAAABMYDQAAAAAHHA0AAAAAByANAAAAAAckDQAAAAAHNA0AAuAUAIQsGAACtBQAgMgAAuQUAIDMAALkFACDDA0AAAAABxANAAAAABMUDQAAAAATGA0AAAAABxwNAAAAAAcgDQAAAAAHJA0AAAAABzQNAALgFACEIwwNAAAAAAcQDQAAAAATFA0AAAAAExgNAAAAAAccDQAAAAAHIA0AAAAAByQNAAAAAAc0DQAC5BQAhBQYAAK0FACAyAAC7BQAgMwAAuwUAIMMDIAAAAAHNAyAAugUAIQLDAyAAAAABzQMgALsFACEMwwOAAAAAAcYDgAAAAAHHA4AAAAAByAOAAAAAAckDgAAAAAHNA4AAAAAB5QMBAAAAAeYDAQAAAAHnAwEAAAAB6AOAAAAAAekDgAAAAAHqA4AAAAABCwYAAKoFACAyAAC-BQAgMwAAvgUAIMMDQAAAAAHEA0AAAAAFxQNAAAAABcYDQAAAAAHHA0AAAAAByANAAAAAAckDQAAAAAHNA0AAvQUAIQjDA0AAAAABxANAAAAABcUDQAAAAAXGA0AAAAABxwNAAAAAAcgDQAAAAAHJA0AAAAABzQNAAL4FACENBgAAqgUAIDAAAMAFACAxAACqBQAgMgAAqgUAIDMAAKoFACDDAwIAAAABxAMCAAAABcUDAgAAAAXGAwIAAAABxwMCAAAAAcgDAgAAAAHJAwIAAAABzQMCAL8FACEIwwMIAAAAAcQDCAAAAAXFAwgAAAAFxgMIAAAAAccDCAAAAAHIAwgAAAAByQMIAAAAAc0DCADABQAhDbsDAADBBQAwvAMAANwEABC9AwAAwQUAML4DAgCmBQAhvwMBAKcFACHCAwEApwUAIc8DAQCoBQAh0gMCAKYFACHeAwIApgUAIeIDQAC3BQAh4wNAALcFACHrAwEAqAUAIewDAQCnBQAhCrsDAADCBQAwvAMAAMYEABC9AwAAwgUAML4DAgCmBQAhvwMBAKcFACHOAwIApgUAIc8DAQCoBQAh0gMCAKYFACHiA0AAtwUAIe0DAQCnBQAhELsDAADDBQAwvAMAALAEABC9AwAAwwUAML4DAgCmBQAhvwMBAKcFACHCAwEApwUAId8DIAC2BQAh4gNAALcFACHjA0AAtwUAIe4DAQCnBQAh7wMCAKYFACHwAyAAtgUAIfEDAAC1BQAg8gMAAMQFACDzAwEAqAUAIfQDAQCnBQAhDwYAAKoFACAyAADFBQAgMwAAxQUAIMMDgAAAAAHGA4AAAAABxwOAAAAAAcgDgAAAAAHJA4AAAAABzQOAAAAAAeUDAQAAAAHmAwEAAAAB5wMBAAAAAegDgAAAAAHpA4AAAAAB6gOAAAAAAQzDA4AAAAABxgOAAAAAAccDgAAAAAHIA4AAAAAByQOAAAAAAc0DgAAAAAHlAwEAAAAB5gMBAAAAAecDAQAAAAHoA4AAAAAB6QOAAAAAAeoDgAAAAAELCgAAtQUAILsDAADGBQAwvAMAAJoEABC9AwAAxgUAML4DAgCmBQAhwgMBAKcFACHOAwIAswUAIeIDQAC3BQAh4wNAALcFACH1AwEApwUAIfYDIAC2BQAhDLsDAADHBQAwvAMAAIIEABC9AwAAxwUAML4DAgCmBQAh1QMBAKcFACHaA0AAtAUAId4DAgCmBQAh4gNAALcFACHjA0AAtwUAIfcDAgCmBQAh-AMBAKcFACH5AyAAtgUAIQu7AwAAyAUAMLwDAADsAwAQvQMAAMgFADC-AwIApgUAIc4DAgCmBQAh1QMBAKcFACHfAyAAtgUAIeIDQAC3BQAh4wNAALcFACH1AwEApwUAIfgDAQCnBQAhC7sDAADJBQAwvAMAANYDABC9AwAAyQUAML4DAgCmBQAhzgMCAKYFACHiA0AAtwUAIfgDAQCnBQAh-gMCAKYFACH7AwEApwUAIfwDAgCmBQAh_QMBAKcFACEPuwMAAMoFADC8AwAAwAMAEL0DAADKBQAwvgMCAKYFACHCAwEApwUAIc4DAgCmBQAh3gMCAKYFACHfAyAAtgUAIeIDQAC3BQAh4wNAALcFACHzAwEApwUAIfgDAQCnBQAh_gMBAKcFACH_AwIApgUAIYAEAQCnBQAhCrsDAADLBQAwvAMAAKoDABC9AwAAywUAML4DAgCmBQAh4gNAALcFACGBBAIApgUAIYIEQAC3BQAhgwQIAMwFACGEBAgAzAUAIYUEAADEBQAgDQYAAKoFACAwAADABQAgMQAAwAUAIDIAAMAFACAzAADABQAgwwMIAAAAAcQDCAAAAAXFAwgAAAAFxgMIAAAAAccDCAAAAAHIAwgAAAAByQMIAAAAAc0DCADNBQAhDQYAAKoFACAwAADABQAgMQAAwAUAIDIAAMAFACAzAADABQAgwwMIAAAAAcQDCAAAAAXFAwgAAAAFxgMIAAAAAccDCAAAAAHIAwgAAAAByQMIAAAAAc0DCADNBQAhErsDAADOBQAwvAMAAJQDABC9AwAAzgUAML4DAgCmBQAh4gNAALcFACH7A0AAtwUAIYEEAgCmBQAhgwQIAMwFACGEBAgAzAUAIYUEAADEBQAghgQIAMwFACGHBAgAzAUAIYgECADMBQAhiQQIAMwFACGKBAgAzAUAIYsECADMBQAhjAQBAKcFACGNBEAAtAUAIRS7AwAAzwUAMLwDAAD-AgAQvQMAAM8FADC-AwIApgUAIcIDAQCnBQAh4gNAALcFACGOBAEApwUAIY8EAQCnBQAhkAQBAKcFACGRBAEAqAUAIZIEAgCmBQAhkwQCAKYFACGUBAIApgUAIZUEAgCmBQAhlgQCAKYFACGXBAIApgUAIZgEAgCzBQAhmQRAALQFACGaBCAAtgUAIZsEQAC0BQAhB7sDAADQBQAwvAMAAOgCABC9AwAA0AUAML4DAgCmBQAhwgMBAKcFACHiA0AAtwUAIY8EAQCnBQAhCMoBAADVBQAguwMAANEFADC8AwAA1QIAEL0DAADRBQAwvgMCANIFACHCAwEA0wUAIeIDQADUBQAhjwQBANMFACEIwwMCAAAAAcQDAgAAAATFAwIAAAAExgMCAAAAAccDAgAAAAHIAwIAAAAByQMCAAAAAc0DAgCtBQAhC8MDAQAAAAHEAwEAAAAExQMBAAAABMYDAQAAAAHHAwEAAAAByAMBAAAAAckDAQAAAAHKAwEAAAABywMBAAAAAcwDAQAAAAHNAwEArgUAIQjDA0AAAAABxANAAAAABMUDQAAAAATGA0AAAAABxwNAAAAAAcgDQAAAAAHJA0AAAAABzQNAALkFACEDnAQAAMUCACCdBAAAxQIAIJ4EAADFAgAgAoEEAgAAAAGCBEAAAAABC8cBAADaBQAguwMAANcFADC8AwAAzQIAEL0DAADXBQAwvgMCANIFACHiA0AA1AUAIYEEAgDSBQAhggRAANQFACGDBAgA2AUAIYQECADYBQAhhQQAANkFACAIwwMIAAAAAcQDCAAAAAXFAwgAAAAFxgMIAAAAAccDCAAAAAHIAwgAAAAByQMIAAAAAc0DCADABQAhDMMDgAAAAAHGA4AAAAABxwOAAAAAAcgDgAAAAAHJA4AAAAABzQOAAAAAAeUDAQAAAAHmAwEAAAAB5wMBAAAAAegDgAAAAAHpA4AAAAAB6gOAAAAAARnGAQAA4gUAIMgBAADjBQAgyQEAAOQFACC7AwAA3gUAMLwDAADFAgAQvQMAAN4FADC-AwIA0gUAIcIDAQDTBQAh4gNAANQFACGOBAEA0wUAIY8EAQDTBQAhkAQBANMFACGRBAEA3wUAIZIEAgDSBQAhkwQCANIFACGUBAIA0gUAIZUEAgDSBQAhlgQCANIFACGXBAIA0gUAIZgEAgDgBQAhmQRAAN0FACGaBCAA4QUAIZsEQADdBQAh2QQAAMUCACDaBAAAxQIAIAL7A0AAAAABgQQCAAAAARPHAQAA2gUAILsDAADcBQAwvAMAAMkCABC9AwAA3AUAML4DAgDSBQAh4gNAANQFACH7A0AA1AUAIYEEAgDSBQAhgwQIANgFACGEBAgA2AUAIYUEAADZBQAghgQIANgFACGHBAgA2AUAIYgECADYBQAhiQQIANgFACGKBAgA2AUAIYsECADYBQAhjAQBANMFACGNBEAA3QUAIQjDA0AAAAABxANAAAAABcUDQAAAAAXGA0AAAAABxwNAAAAAAcgDQAAAAAHJA0AAAAABzQNAAL4FACEXxgEAAOIFACDIAQAA4wUAIMkBAADkBQAguwMAAN4FADC8AwAAxQIAEL0DAADeBQAwvgMCANIFACHCAwEA0wUAIeIDQADUBQAhjgQBANMFACGPBAEA0wUAIZAEAQDTBQAhkQQBAN8FACGSBAIA0gUAIZMEAgDSBQAhlAQCANIFACGVBAIA0gUAIZYEAgDSBQAhlwQCANIFACGYBAIA4AUAIZkEQADdBQAhmgQgAOEFACGbBEAA3QUAIQvDAwEAAAABxAMBAAAABcUDAQAAAAXGAwEAAAABxwMBAAAAAcgDAQAAAAHJAwEAAAABygMBAAAAAcsDAQAAAAHMAwEAAAABzQMBAKsFACEIwwMCAAAAAcQDAgAAAAXFAwIAAAAFxgMCAAAAAccDAgAAAAHIAwIAAAAByQMCAAAAAc0DAgCqBQAhAsMDIAAAAAHNAyAAuwUAIQrKAQAA1QUAILsDAADRBQAwvAMAANUCABC9AwAA0QUAML4DAgDSBQAhwgMBANMFACHiA0AA1AUAIY8EAQDTBQAh2QQAANUCACDaBAAA1QIAIAOcBAAAyQIAIJ0EAADJAgAgngQAAMkCACADnAQAAM0CACCdBAAAzQIAIJ4EAADNAgAgCbsDAADlBQAwvAMAAMACABC9AwAA5QUAML4DAgCmBQAhzgMBAKcFACHZAwEApwUAIeMDQAC3BQAhoQQBAKcFACGiBAEApwUAIQm7AwAA5gUAMLwDAACtAgAQvQMAAOYFADC-AwIA0gUAIc4DAQDTBQAh2QMBANMFACHjA0AA1AUAIaEEAQDTBQAhogQBANMFACEKuwMAAOcFADC8AwAApwIAEL0DAADnBQAwvgMCAKYFACHiA0AAtwUAIeMDQAC3BQAhowQBAKcFACGkBAEAqAUAIaUEQAC3BQAhpgQAAMQFACAKuwMAAOgFADC8AwAAlAIAEL0DAADoBQAwvgMCANIFACHiA0AA1AUAIeMDQADUBQAhowQBANMFACGkBAEA3wUAIaUEQADUBQAhpgQAANkFACAIuwMAAOkFADC8AwAAjgIAEL0DAADpBQAwvgMCAKYFACHiA0AAtwUAIeMDQAC3BQAhpwQBAKcFACGoBAIApgUAIQi7AwAA6gUAMLwDAAD7AQAQvQMAAOoFADC-AwIA0gUAIeIDQADUBQAh4wNAANQFACGnBAEA0wUAIagEAgDSBQAhDLsDAADrBQAwvAMAAPUBABC9AwAA6wUAML4DAgCmBQAhwgMBAKcFACHZAwEApwUAIeIDQAC3BQAh4wNAALcFACGpBAEApwUAIaoEAgCmBQAhqwQBAKcFACGsBEAAtwUAIQy7AwAA7AUAMLwDAADiAQAQvQMAAOwFADC-AwIA0gUAIcIDAQDTBQAh2QMBANMFACHiA0AA1AUAIeMDQADUBQAhqQQBANMFACGqBAIA0gUAIasEAQDTBQAhrARAANQFACEPuwMAAO0FADC8AwAA3AEAEL0DAADtBQAwvgMCAKYFACG_AwEApwUAIeIDQAC3BQAh_gNAALcFACGtBAgA7gUAIa4EBADvBQAhrwQIAO4FACGwBAQA7wUAIbEEBADvBQAhsgQEAO8FACGzBAIApgUAIbQEAgCmBQAhDQYAAK0FACAwAACwBQAgMQAAsAUAIDIAALAFACAzAACwBQAgwwMIAAAAAcQDCAAAAATFAwgAAAAExgMIAAAAAccDCAAAAAHIAwgAAAAByQMIAAAAAc0DCADyBQAhDQYAAK0FACAwAACwBQAgMQAA8QUAIDIAAPEFACAzAADxBQAgwwMEAAAAAcQDBAAAAATFAwQAAAAExgMEAAAAAccDBAAAAAHIAwQAAAAByQMEAAAAAc0DBADwBQAhDQYAAK0FACAwAACwBQAgMQAA8QUAIDIAAPEFACAzAADxBQAgwwMEAAAAAcQDBAAAAATFAwQAAAAExgMEAAAAAccDBAAAAAHIAwQAAAAByQMEAAAAAc0DBADwBQAhCMMDBAAAAAHEAwQAAAAExQMEAAAABMYDBAAAAAHHAwQAAAAByAMEAAAAAckDBAAAAAHNAwQA8QUAIQ0GAACtBQAgMAAAsAUAIDEAALAFACAyAACwBQAgMwAAsAUAIMMDCAAAAAHEAwgAAAAExQMIAAAABMYDCAAAAAHHAwgAAAAByAMIAAAAAckDCAAAAAHNAwgA8gUAIQ-7AwAA8wUAMLwDAADJAQAQvQMAAPMFADC-AwIA0gUAIb8DAQDTBQAh4gNAANQFACH-A0AA1AUAIa0ECAD0BQAhrgQEAPUFACGvBAgA9AUAIbAEBAD1BQAhsQQEAPUFACGyBAQA9QUAIbMEAgDSBQAhtAQCANIFACEIwwMIAAAAAcQDCAAAAATFAwgAAAAExgMIAAAAAccDCAAAAAHIAwgAAAAByQMIAAAAAc0DCACwBQAhCMMDBAAAAAHEAwQAAAAExQMEAAAABMYDBAAAAAHHAwQAAAAByAMEAAAAAckDBAAAAAHNAwQA8QUAIQK_AwEAAAAB_gNAAAAAARy7AwAA9wUAMLwDAADDAQAQvQMAAPcFADC-AwIApgUAIb8DAQCnBQAhwgMBAKcFACHZAwEApwUAIeIDQAC3BQAh4wNAALcFACGxBAQA7wUAIbIEBADvBQAhswQCAKYFACG0BAIApgUAIbYEAQCnBQAhtwQBAKcFACG4BAEApwUAIbkEBADvBQAhugQEAO8FACG7BAIApgUAIbwECADuBQAhvQQEAO8FACG-BAQA7wUAIb8ECADuBQAhwAQIAO4FACHBBAgA7gUAIcIEBADvBQAhwwQEAO8FACHEBEAAtwUAIRy7AwAA-AUAMLwDAACwAQAQvQMAAPgFADC-AwIA0gUAIb8DAQDTBQAhwgMBANMFACHZAwEA0wUAIeIDQADUBQAh4wNAANQFACGxBAQA9QUAIbIEBAD1BQAhswQCANIFACG0BAIA0gUAIbYEAQDTBQAhtwQBANMFACG4BAEA0wUAIbkEBAD1BQAhugQEAPUFACG7BAIA0gUAIbwECAD0BQAhvQQEAPUFACG-BAQA9QUAIb8ECAD0BQAhwAQIAPQFACHBBAgA9AUAIcIEBAD1BQAhwwQEAPUFACHEBEAA1AUAIQ27AwAA-QUAMLwDAACqAQAQvQMAAPkFADC-AwIApgUAIeIDQAC3BQAh_gMBAKcFACHFBAEApwUAIcYECADuBQAhxwQIAO4FACHIBAEApwUAIckEAQCnBQAhygQIAO4FACHLBAgA7gUAIQ27AwAA-gUAMLwDAACXAQAQvQMAAPoFADC-AwIA0gUAIeIDQADUBQAh_gMBANMFACHFBAEA0wUAIcYECAD0BQAhxwQIAPQFACHIBAEA0wUAIckEAQDTBQAhygQIAPQFACHLBAgA9AUAIQy7AwAA-wUAMLwDAACRAQAQvQMAAPsFADC-AwIApgUAIdUDAQCnBQAh4gNAALcFACH9AwEApwUAIakEAQCnBQAhzAQBAKcFACHNBAEAqAUAIc4EQAC3BQAhzwQCAKYFACEMuwMAAPwFADC8AwAAfgAQvQMAAPwFADC-AwIA0gUAIdUDAQDTBQAh4gNAANQFACH9AwEA0wUAIakEAQDTBQAhzAQBANMFACHNBAEA3wUAIc4EQADUBQAhzwQCANIFACEC1QMBAAAAAc4EQAAAAAEKuwMAAP4FADC8AwAAeAAQvQMAAP4FADC-AwIApgUAIdUDAQCnBQAh4gNAALcFACHjA0AAtwUAIfsDAQCnBQAhzQQBAKcFACHRBAEApwUAIQq7AwAA_wUAMLwDAABlABC9AwAA_wUAML4DAgDSBQAh1QMBANMFACHiA0AA1AUAIeMDQADUBQAh-wMBANMFACHNBAEA0wUAIdEEAQDTBQAhCbsDAACABgAwvAMAAF8AEL0DAACABgAwvgMCAKYFACHCAwEApwUAIeIDQAC3BQAh4wNAALcFACHSBAEApwUAIdMEAQCnBQAhCRUAAIIGACC7AwAAgQYAMLwDAAAzABC9AwAAgQYAML4DAgDSBQAhvwMBANMFACHAAwEA3wUAIcEDAgDSBQAhwgMBANMFACElDgAAiQYAIA8AAI4GACAQAACKBgAgEQAAiwYAIBIAAIwGACATAACNBgAgFAAAhQYAIBYAAI8GACC7AwAAhwYAMLwDAAAoABC9AwAAhwYAML4DAgDSBQAhvwMBANMFACHPAwEA3wUAIdADAQDfBQAh0QMBAN8FACHSAwIA0gUAIdMDAgDSBQAh1AMCAOAFACHVAwEA0wUAIdYDAQDTBQAh1wMBAN8FACHYA0AA3QUAIdkDAQDTBQAh2gNAAN0FACHbAwIA4AUAIdwDAgDSBQAh3QMAAIgGACDeAwIA0gUAId8DIADhBQAh4ANAAN0FACHhAwEA3wUAIeIDQADUBQAh4wNAANQFACHkAwIA4AUAIdkEAAAoACDaBAAAKAAgAsEDAgAAAAHOAwIAAAABCgMAAIUGACAVAACCBgAguwMAAIQGADC8AwAALwAQvQMAAIQGADC-AwIA0gUAIb8DAQDTBQAhwAMBAN8FACHBAwIA0gUAIc4DAgDSBQAhFAcAAJgGACAIAACZBgAgCwAAmgYAIAwAAJsGACAZAACcBgAgGgAAlQYAIBsAAIwGACAcAACMBgAgHQAAjgYAILsDAACXBgAwvAMAABoAEL0DAACXBgAwvgMCANIFACHCAwEA0wUAIeIDQADUBQAh4wNAANQFACHSBAEA0wUAIdMEAQDTBQAh2QQAABoAINoEAAAaACAC0gMCAAAAAeQDAgAAAAEjDgAAiQYAIA8AAI4GACAQAACKBgAgEQAAiwYAIBIAAIwGACATAACNBgAgFAAAhQYAIBYAAI8GACC7AwAAhwYAMLwDAAAoABC9AwAAhwYAML4DAgDSBQAhvwMBANMFACHPAwEA3wUAIdADAQDfBQAh0QMBAN8FACHSAwIA0gUAIdMDAgDSBQAh1AMCAOAFACHVAwEA0wUAIdYDAQDTBQAh1wMBAN8FACHYA0AA3QUAIdkDAQDTBQAh2gNAAN0FACHbAwIA4AUAIdwDAgDSBQAh3QMAAIgGACDeAwIA0gUAId8DIADhBQAh4ANAAN0FACHhAwEA3wUAIeIDQADUBQAh4wNAANQFACHkAwIA4AUAIQzDA4AAAAABxgOAAAAAAccDgAAAAAHIA4AAAAAByQOAAAAAAc0DgAAAAAHlAwEAAAAB5gMBAAAAAecDAQAAAAHoA4AAAAAB6QOAAAAAAeoDgAAAAAEWDQAAhQYAIA8AAJUGACAXAACMBgAgGAAAlgYAILsDAACUBgAwvAMAABwAEL0DAACUBgAwvgMCANIFACG_AwEA0wUAIcIDAQDTBQAh3wMgAOEFACHiA0AA1AUAIeMDQADUBQAh7gMBANMFACHvAwIA0gUAIfADIADhBQAh8QMAAIgGACDyAwAA2QUAIPMDAQDfBQAh9AMBANMFACHZBAAAHAAg2gQAABwAIBEOAACJBgAgFwAAjAYAILsDAACRBgAwvAMAACQAEL0DAACRBgAwvgMCANIFACG_AwEA0wUAIcIDAQDTBQAhzwMBAN8FACHSAwIA0gUAId4DAgDSBQAh4gNAANQFACHjA0AA1AUAIesDAQDfBQAh7AMBANMFACHZBAAAJAAg2gQAACQAICUOAACJBgAgDwAAjgYAIBAAAIoGACARAACLBgAgEgAAjAYAIBMAAI0GACAUAACFBgAgFgAAjwYAILsDAACHBgAwvAMAACgAEL0DAACHBgAwvgMCANIFACG_AwEA0wUAIc8DAQDfBQAh0AMBAN8FACHRAwEA3wUAIdIDAgDSBQAh0wMCANIFACHUAwIA4AUAIdUDAQDTBQAh1gMBANMFACHXAwEA3wUAIdgDQADdBQAh2QMBANMFACHaA0AA3QUAIdsDAgDgBQAh3AMCANIFACHdAwAAiAYAIN4DAgDSBQAh3wMgAOEFACHgA0AA3QUAIeEDAQDfBQAh4gNAANQFACHjA0AA1AUAIeQDAgDgBQAh2QQAACgAINoEAAAoACADnAQAACgAIJ0EAAAoACCeBAAAKAAgFAcAAJgGACAIAACZBgAgCwAAmgYAIAwAAJsGACAZAACcBgAgGgAAlQYAIBsAAIwGACAcAACMBgAgHQAAjgYAILsDAACXBgAwvAMAABoAEL0DAACXBgAwvgMCANIFACHCAwEA0wUAIeIDQADUBQAh4wNAANQFACHSBAEA0wUAIdMEAQDTBQAh2QQAABoAINoEAAAaACADnAQAAC8AIJ0EAAAvACCeBAAALwAgA5wEAAAzACCdBAAAMwAgngQAADMAIALCAwEAAAAB0gMCAAAAAQ8OAACJBgAgFwAAjAYAILsDAACRBgAwvAMAACQAEL0DAACRBgAwvgMCANIFACG_AwEA0wUAIcIDAQDTBQAhzwMBAN8FACHSAwIA0gUAId4DAgDSBQAh4gNAANQFACHjA0AA1AUAIesDAQDfBQAh7AMBANMFACECzgMCAAAAAdIDAgAAAAEMAwAAhQYAIA4AAIkGACC7AwAAkwYAMLwDAAAgABC9AwAAkwYAML4DAgDSBQAhvwMBANMFACHOAwIA0gUAIc8DAQDfBQAh0gMCANIFACHiA0AA1AUAIe0DAQDTBQAhFA0AAIUGACAPAACVBgAgFwAAjAYAIBgAAJYGACC7AwAAlAYAMLwDAAAcABC9AwAAlAYAML4DAgDSBQAhvwMBANMFACHCAwEA0wUAId8DIADhBQAh4gNAANQFACHjA0AA1AUAIe4DAQDTBQAh7wMCANIFACHwAyAA4QUAIfEDAACIBgAg8gMAANkFACDzAwEA3wUAIfQDAQDTBQAhA5wEAAAgACCdBAAAIAAgngQAACAAIAOcBAAAJAAgnQQAACQAIJ4EAAAkACASBwAAmAYAIAgAAJkGACALAACaBgAgDAAAmwYAIBkAAJwGACAaAACVBgAgGwAAjAYAIBwAAIwGACAdAACOBgAguwMAAJcGADC8AwAAGgAQvQMAAJcGADC-AwIA0gUAIcIDAQDTBQAh4gNAANQFACHjA0AA1AUAIdIEAQDTBQAh0wQBANMFACEDnAQAAAMAIJ0EAAADACCeBAAAAwAgA5wEAAAHACCdBAAABwAgngQAAAcAIAOcBAAADQAgnQQAAA0AIJ4EAAANACADnAQAABYAIJ0EAAAWACCeBAAAFgAgA5wEAAAcACCdBAAAHAAgngQAABwAIAwDAACNBgAgCgAAiAYAILsDAACdBgAwvAMAABYAEL0DAACdBgAwvgMCANIFACHCAwEA0wUAIc4DAgDgBQAh4gNAANQFACHjA0AA1AUAIfUDAQDTBQAh9gMgAOEFACENCQAAnwYAILsDAACeBgAwvAMAABEAEL0DAACeBgAwvgMCANIFACHVAwEA0wUAIdoDQADdBQAh3gMCANIFACHiA0AA1AUAIeMDQADUBQAh9wMCANIFACH4AwEA0wUAIfkDIADhBQAhDwMAAIUGACAKAAChBgAguwMAAKAGADC8AwAADQAQvQMAAKAGADC-AwIA0gUAIc4DAgDSBQAh1QMBANMFACHfAyAA4QUAIeIDQADUBQAh4wNAANQFACH1AwEA0wUAIfgDAQDTBQAh2QQAAA0AINoEAAANACANAwAAhQYAIAoAAKEGACC7AwAAoAYAMLwDAAANABC9AwAAoAYAML4DAgDSBQAhzgMCANIFACHVAwEA0wUAId8DIADhBQAh4gNAANQFACHjA0AA1AUAIfUDAQDTBQAh-AMBANMFACEDnAQAABEAIJ0EAAARACCeBAAAEQAgA_oDAgAAAAH7AwEAAAAB_AMCAAAAAQ0DAACFBgAgBAAApAYAILsDAACjBgAwvAMAAAcAEL0DAACjBgAwvgMCANIFACHOAwIA0gUAIeIDQADUBQAh-AMBANMFACH6AwIA0gUAIfsDAQDTBQAh_AMCANIFACH9AwEA0wUAIRMDAACFBgAgBQAAmQYAILsDAAClBgAwvAMAAAMAEL0DAAClBgAwvgMCANIFACHCAwEA0wUAIc4DAgDSBQAh3gMCANIFACHfAyAA4QUAIeIDQADUBQAh4wNAANQFACHzAwEA0wUAIfgDAQDTBQAh_gMBANMFACH_AwIA0gUAIYAEAQDTBQAh2QQAAAMAINoEAAADACARAwAAhQYAIAUAAJkGACC7AwAApQYAMLwDAAADABC9AwAApQYAML4DAgDSBQAhwgMBANMFACHOAwIA0gUAId4DAgDSBQAh3wMgAOEFACHiA0AA1AUAIeMDQADUBQAh8wMBANMFACH4AwEA0wUAIf4DAQDTBQAh_wMCANIFACGABAEA0wUAIQAAAAAAAAHeBAEAAAABAd4EAQAAAAEF3gQCAAAAAeQEAgAAAAHlBAIAAAAB5gQCAAAAAecEAgAAAAEFKgAA-AoAICsAAPsKACDbBAAA-QoAINwEAAD6CgAg4QQAACoAIAMqAAD4CgAg2wQAAPkKACDhBAAAKgAgAAAAAAAFKgAA8AoAICsAAPYKACDbBAAA8QoAINwEAAD1CgAg4QQAACoAIAUqAADuCgAgKwAA8woAINsEAADvCgAg3AQAAPIKACDhBAAAAQAgAyoAAPAKACDbBAAA8QoAIOEEAAAqACADKgAA7goAINsEAADvCgAg4QQAAAEAIAAAAAAAAd4EQAAAAAEB3gQgAAAAAQHeBEAAAAABBd4EAgAAAAHkBAIAAAAB5QQCAAAAAeYEAgAAAAHnBAIAAAABBSoAANoKACArAADsCgAg2wQAANsKACDcBAAA6woAIOEEAAAeACAFKgAA2AoAICsAAOkKACDbBAAA2QoAINwEAADoCgAg4QQAACYAIAcqAADSCgAgKwAA5goAINsEAADTCgAg3AQAAOUKACDfBAAAKAAg4AQAACgAIOEEAAAqACALKgAA4wYAMCsAAOgGADDbBAAA5AYAMNwEAADlBgAw3QQAAOYGACDeBAAA5wYAMN8EAADnBgAw4AQAAOcGADDhBAAA5wYAMOIEAADpBgAw4wQAAOoGADAHKgAA1goAICsAAOMKACDbBAAA1woAINwEAADiCgAg3wQAABoAIOAEAAAaACDhBAAAAQAgBSoAANQKACArAADgCgAg2wQAANUKACDcBAAA3woAIOEEAAABACALKgAA1wYAMCsAANwGADDbBAAA2AYAMNwEAADZBgAw3QQAANoGACDeBAAA2wYAMN8EAADbBgAw4AQAANsGADDhBAAA2wYAMOIEAADdBgAw4wQAAN4GADALKgAAywYAMCsAANAGADDbBAAAzAYAMNwEAADNBgAw3QQAAM4GACDeBAAAzwYAMN8EAADPBgAw4AQAAM8GADDhBAAAzwYAMOIEAADRBgAw4wQAANIGADAEvgMCAAAAAb8DAQAAAAHAAwEAAAABwgMBAAAAAQIAAAA1ACAqAADWBgAgAwAAADUAICoAANYGACArAADVBgAgASMAAN4KADAJFQAAggYAILsDAACBBgAwvAMAADMAEL0DAACBBgAwvgMCAAAAAb8DAQAAAAHAAwEA3wUAIcEDAgDSBQAhwgMBANMFACECAAAANQAgIwAA1QYAIAIAAADTBgAgIwAA1AYAIAi7AwAA0gYAMLwDAADTBgAQvQMAANIGADC-AwIA0gUAIb8DAQDTBQAhwAMBAN8FACHBAwIA0gUAIcIDAQDTBQAhCLsDAADSBgAwvAMAANMGABC9AwAA0gYAML4DAgDSBQAhvwMBANMFACHAAwEA3wUAIcEDAgDSBQAhwgMBANMFACEEvgMCAK4GACG_AwEArAYAIcADAQCtBgAhwgMBAKwGACEEvgMCAK4GACG_AwEArAYAIcADAQCtBgAhwgMBAKwGACEEvgMCAAAAAb8DAQAAAAHAAwEAAAABwgMBAAAAAQUDAAC5BgAgvgMCAAAAAb8DAQAAAAHAAwEAAAABzgMCAAAAAQIAAAAxACAqAADiBgAgAwAAADEAICoAAOIGACArAADhBgAgASMAAN0KADALAwAAhQYAIBUAAIIGACC7AwAAhAYAMLwDAAAvABC9AwAAhAYAML4DAgAAAAG_AwEAAAABwAMBAN8FACHBAwIA0gUAIc4DAgDSBQAh1AQAAIMGACACAAAAMQAgIwAA4QYAIAIAAADfBgAgIwAA4AYAIAi7AwAA3gYAMLwDAADfBgAQvQMAAN4GADC-AwIA0gUAIb8DAQDTBQAhwAMBAN8FACHBAwIA0gUAIc4DAgDSBQAhCLsDAADeBgAwvAMAAN8GABC9AwAA3gYAML4DAgDSBQAhvwMBANMFACHAAwEA3wUAIcEDAgDSBQAhzgMCANIFACEEvgMCAK4GACG_AwEArAYAIcADAQCtBgAhzgMCAK4GACEFAwAAtwYAIL4DAgCuBgAhvwMBAKwGACHAAwEArQYAIc4DAgCuBgAhBQMAALkGACC-AwIAAAABvwMBAAAAAcADAQAAAAHOAwIAAAABHg4AAO8GACAPAAD0BgAgEAAA8AYAIBIAAPEGACATAADyBgAgFAAA8wYAIBYAAPUGACC-AwIAAAABvwMBAAAAAc8DAQAAAAHQAwEAAAAB0QMBAAAAAdIDAgAAAAHTAwIAAAAB1QMBAAAAAdYDAQAAAAHXAwEAAAAB2ANAAAAAAdkDAQAAAAHaA0AAAAAB2wMCAAAAAdwDAgAAAAHdA4AAAAAB3gMCAAAAAd8DIAAAAAHgA0AAAAAB4QMBAAAAAeIDQAAAAAHjA0AAAAAB5AMCAAAAAQIAAAAqACAqAADuBgAgAwAAACoAICoAAO4GACArAADtBgAgASMAANwKADAkDgAAiQYAIA8AAI4GACAQAACKBgAgEQAAiwYAIBIAAIwGACATAACNBgAgFAAAhQYAIBYAAI8GACC7AwAAhwYAMLwDAAAoABC9AwAAhwYAML4DAgAAAAG_AwEAAAABzwMBAN8FACHQAwEA3wUAIdEDAQDfBQAh0gMCANIFACHTAwIA0gUAIdQDAgDgBQAh1QMBANMFACHWAwEA0wUAIdcDAQDfBQAh2ANAAN0FACHZAwEA0wUAIdoDQADdBQAh2wMCAOAFACHcAwIA0gUAId0DAACIBgAg3gMCANIFACHfAyAA4QUAIeADQADdBQAh4QMBAN8FACHiA0AA1AUAIeMDQADUBQAh5AMCAOAFACHVBAAAhgYAIAIAAAAqACAjAADtBgAgAgAAAOsGACAjAADsBgAgG7sDAADqBgAwvAMAAOsGABC9AwAA6gYAML4DAgDSBQAhvwMBANMFACHPAwEA3wUAIdADAQDfBQAh0QMBAN8FACHSAwIA0gUAIdMDAgDSBQAh1AMCAOAFACHVAwEA0wUAIdYDAQDTBQAh1wMBAN8FACHYA0AA3QUAIdkDAQDTBQAh2gNAAN0FACHbAwIA4AUAIdwDAgDSBQAh3QMAAIgGACDeAwIA0gUAId8DIADhBQAh4ANAAN0FACHhAwEA3wUAIeIDQADUBQAh4wNAANQFACHkAwIA4AUAIRu7AwAA6gYAMLwDAADrBgAQvQMAAOoGADC-AwIA0gUAIb8DAQDTBQAhzwMBAN8FACHQAwEA3wUAIdEDAQDfBQAh0gMCANIFACHTAwIA0gUAIdQDAgDgBQAh1QMBANMFACHWAwEA0wUAIdcDAQDfBQAh2ANAAN0FACHZAwEA0wUAIdoDQADdBQAh2wMCAOAFACHcAwIA0gUAId0DAACIBgAg3gMCANIFACHfAyAA4QUAIeADQADdBQAh4QMBAN8FACHiA0AA1AUAIeMDQADUBQAh5AMCAOAFACEXvgMCAK4GACG_AwEArAYAIc8DAQCtBgAh0AMBAK0GACHRAwEArQYAIdIDAgCuBgAh0wMCAK4GACHVAwEArAYAIdYDAQCsBgAh1wMBAK0GACHYA0AAvwYAIdkDAQCsBgAh2gNAAL8GACHbAwIAwgYAIdwDAgCuBgAh3QOAAAAAAd4DAgCuBgAh3wMgAMAGACHgA0AAvwYAIeEDAQCtBgAh4gNAAMEGACHjA0AAwQYAIeQDAgDCBgAhHg4AAMMGACAPAADJBgAgEAAAxAYAIBIAAMYGACATAADHBgAgFAAAyAYAIBYAAMoGACC-AwIArgYAIb8DAQCsBgAhzwMBAK0GACHQAwEArQYAIdEDAQCtBgAh0gMCAK4GACHTAwIArgYAIdUDAQCsBgAh1gMBAKwGACHXAwEArQYAIdgDQAC_BgAh2QMBAKwGACHaA0AAvwYAIdsDAgDCBgAh3AMCAK4GACHdA4AAAAAB3gMCAK4GACHfAyAAwAYAIeADQAC_BgAh4QMBAK0GACHiA0AAwQYAIeMDQADBBgAh5AMCAMIGACEeDgAA7wYAIA8AAPQGACAQAADwBgAgEgAA8QYAIBMAAPIGACAUAADzBgAgFgAA9QYAIL4DAgAAAAG_AwEAAAABzwMBAAAAAdADAQAAAAHRAwEAAAAB0gMCAAAAAdMDAgAAAAHVAwEAAAAB1gMBAAAAAdcDAQAAAAHYA0AAAAAB2QMBAAAAAdoDQAAAAAHbAwIAAAAB3AMCAAAAAd0DgAAAAAHeAwIAAAAB3wMgAAAAAeADQAAAAAHhAwEAAAAB4gNAAAAAAeMDQAAAAAHkAwIAAAABAyoAANoKACDbBAAA2woAIOEEAAAeACADKgAA2AoAINsEAADZCgAg4QQAACYAIAQqAADjBgAw2wQAAOQGADDdBAAA5gYAIOEEAADnBgAwAyoAANYKACDbBAAA1woAIOEEAAABACADKgAA1AoAINsEAADVCgAg4QQAAAEAIAQqAADXBgAw2wQAANgGADDdBAAA2gYAIOEEAADbBgAwBCoAAMsGADDbBAAAzAYAMN0EAADOBgAg4QQAAM8GADADKgAA0goAINsEAADTCgAg4QQAACoAIAAAAAAABSoAAMwKACArAADQCgAg2wQAAM0KACDcBAAAzwoAIOEEAAAeACALKgAA_gYAMCsAAIIHADDbBAAA_wYAMNwEAACABwAw3QQAAIEHACDeBAAA5wYAMN8EAADnBgAw4AQAAOcGADDhBAAA5wYAMOIEAACDBwAw4wQAAOoGADAeDgAA7wYAIA8AAPQGACARAAD2BgAgEgAA8QYAIBMAAPIGACAUAADzBgAgFgAA9QYAIL4DAgAAAAG_AwEAAAABzwMBAAAAAdADAQAAAAHRAwEAAAAB0gMCAAAAAdQDAgAAAAHVAwEAAAAB1gMBAAAAAdcDAQAAAAHYA0AAAAAB2QMBAAAAAdoDQAAAAAHbAwIAAAAB3AMCAAAAAd0DgAAAAAHeAwIAAAAB3wMgAAAAAeADQAAAAAHhAwEAAAAB4gNAAAAAAeMDQAAAAAHkAwIAAAABAgAAACoAICoAAIYHACADAAAAKgAgKgAAhgcAICsAAIUHACABIwAAzgoAMAIAAAAqACAjAACFBwAgAgAAAOsGACAjAACEBwAgF74DAgCuBgAhvwMBAKwGACHPAwEArQYAIdADAQCtBgAh0QMBAK0GACHSAwIArgYAIdQDAgDCBgAh1QMBAKwGACHWAwEArAYAIdcDAQCtBgAh2ANAAL8GACHZAwEArAYAIdoDQAC_BgAh2wMCAMIGACHcAwIArgYAId0DgAAAAAHeAwIArgYAId8DIADABgAh4ANAAL8GACHhAwEArQYAIeIDQADBBgAh4wNAAMEGACHkAwIAwgYAIR4OAADDBgAgDwAAyQYAIBEAAMUGACASAADGBgAgEwAAxwYAIBQAAMgGACAWAADKBgAgvgMCAK4GACG_AwEArAYAIc8DAQCtBgAh0AMBAK0GACHRAwEArQYAIdIDAgCuBgAh1AMCAMIGACHVAwEArAYAIdYDAQCsBgAh1wMBAK0GACHYA0AAvwYAIdkDAQCsBgAh2gNAAL8GACHbAwIAwgYAIdwDAgCuBgAh3QOAAAAAAd4DAgCuBgAh3wMgAMAGACHgA0AAvwYAIeEDAQCtBgAh4gNAAMEGACHjA0AAwQYAIeQDAgDCBgAhHg4AAO8GACAPAAD0BgAgEQAA9gYAIBIAAPEGACATAADyBgAgFAAA8wYAIBYAAPUGACC-AwIAAAABvwMBAAAAAc8DAQAAAAHQAwEAAAAB0QMBAAAAAdIDAgAAAAHUAwIAAAAB1QMBAAAAAdYDAQAAAAHXAwEAAAAB2ANAAAAAAdkDAQAAAAHaA0AAAAAB2wMCAAAAAdwDAgAAAAHdA4AAAAAB3gMCAAAAAd8DIAAAAAHgA0AAAAAB4QMBAAAAAeIDQAAAAAHjA0AAAAAB5AMCAAAAAQMqAADMCgAg2wQAAM0KACDhBAAAHgAgBCoAAP4GADDbBAAA_wYAMN0EAACBBwAg4QQAAOcGADAAAAAAAAUqAADECgAgKwAAygoAINsEAADFCgAg3AQAAMkKACDhBAAAHgAgBSoAAMIKACArAADHCgAg2wQAAMMKACDcBAAAxgoAIOEEAAABACADKgAAxAoAINsEAADFCgAg4QQAAB4AIAMqAADCCgAg2wQAAMMKACDhBAAAAQAgAAAAAAAFKgAAugoAICsAAMAKACDbBAAAuwoAINwEAAC_CgAg4QQAAAEAIAsqAACwBwAwKwAAtQcAMNsEAACxBwAw3AQAALIHADDdBAAAswcAIN4EAAC0BwAw3wQAALQHADDgBAAAtAcAMOEEAAC0BwAw4gQAALYHADDjBAAAtwcAMAsqAACkBwAwKwAAqQcAMNsEAAClBwAw3AQAAKYHADDdBAAApwcAIN4EAACoBwAw3wQAAKgHADDgBAAAqAcAMOEEAACoBwAw4gQAAKoHADDjBAAAqwcAMAsqAACbBwAwKwAAnwcAMNsEAACcBwAw3AQAAJ0HADDdBAAAngcAIN4EAADnBgAw3wQAAOcGADDgBAAA5wYAMOEEAADnBgAw4gQAAKAHADDjBAAA6gYAMB4PAAD0BgAgEAAA8AYAIBEAAPYGACASAADxBgAgEwAA8gYAIBQAAPMGACAWAAD1BgAgvgMCAAAAAb8DAQAAAAHPAwEAAAAB0AMBAAAAAdEDAQAAAAHTAwIAAAAB1AMCAAAAAdUDAQAAAAHWAwEAAAAB1wMBAAAAAdgDQAAAAAHZAwEAAAAB2gNAAAAAAdsDAgAAAAHcAwIAAAAB3QOAAAAAAd4DAgAAAAHfAyAAAAAB4ANAAAAAAeEDAQAAAAHiA0AAAAAB4wNAAAAAAeQDAgAAAAECAAAAKgAgKgAAowcAIAMAAAAqACAqAACjBwAgKwAAogcAIAEjAAC-CgAwAgAAACoAICMAAKIHACACAAAA6wYAICMAAKEHACAXvgMCAK4GACG_AwEArAYAIc8DAQCtBgAh0AMBAK0GACHRAwEArQYAIdMDAgCuBgAh1AMCAMIGACHVAwEArAYAIdYDAQCsBgAh1wMBAK0GACHYA0AAvwYAIdkDAQCsBgAh2gNAAL8GACHbAwIAwgYAIdwDAgCuBgAh3QOAAAAAAd4DAgCuBgAh3wMgAMAGACHgA0AAvwYAIeEDAQCtBgAh4gNAAMEGACHjA0AAwQYAIeQDAgDCBgAhHg8AAMkGACAQAADEBgAgEQAAxQYAIBIAAMYGACATAADHBgAgFAAAyAYAIBYAAMoGACC-AwIArgYAIb8DAQCsBgAhzwMBAK0GACHQAwEArQYAIdEDAQCtBgAh0wMCAK4GACHUAwIAwgYAIdUDAQCsBgAh1gMBAKwGACHXAwEArQYAIdgDQAC_BgAh2QMBAKwGACHaA0AAvwYAIdsDAgDCBgAh3AMCAK4GACHdA4AAAAAB3gMCAK4GACHfAyAAwAYAIeADQAC_BgAh4QMBAK0GACHiA0AAwQYAIeMDQADBBgAh5AMCAMIGACEeDwAA9AYAIBAAAPAGACARAAD2BgAgEgAA8QYAIBMAAPIGACAUAADzBgAgFgAA9QYAIL4DAgAAAAG_AwEAAAABzwMBAAAAAdADAQAAAAHRAwEAAAAB0wMCAAAAAdQDAgAAAAHVAwEAAAAB1gMBAAAAAdcDAQAAAAHYA0AAAAAB2QMBAAAAAdoDQAAAAAHbAwIAAAAB3AMCAAAAAd0DgAAAAAHeAwIAAAAB3wMgAAAAAeADQAAAAAHhAwEAAAAB4gNAAAAAAeMDQAAAAAHkAwIAAAABChcAAIgHACC-AwIAAAABvwMBAAAAAcIDAQAAAAHPAwEAAAAB3gMCAAAAAeIDQAAAAAHjA0AAAAAB6wMBAAAAAewDAQAAAAECAAAAJgAgKgAArwcAIAMAAAAmACAqAACvBwAgKwAArgcAIAEjAAC9CgAwEA4AAIkGACAXAACMBgAguwMAAJEGADC8AwAAJAAQvQMAAJEGADC-AwIAAAABvwMBAAAAAcIDAQDTBQAhzwMBAN8FACHSAwIA0gUAId4DAgDSBQAh4gNAANQFACHjA0AA1AUAIesDAQDfBQAh7AMBANMFACHWBAAAkAYAIAIAAAAmACAjAACuBwAgAgAAAKwHACAjAACtBwAgDbsDAACrBwAwvAMAAKwHABC9AwAAqwcAML4DAgDSBQAhvwMBANMFACHCAwEA0wUAIc8DAQDfBQAh0gMCANIFACHeAwIA0gUAIeIDQADUBQAh4wNAANQFACHrAwEA3wUAIewDAQDTBQAhDbsDAACrBwAwvAMAAKwHABC9AwAAqwcAML4DAgDSBQAhvwMBANMFACHCAwEA0wUAIc8DAQDfBQAh0gMCANIFACHeAwIA0gUAIeIDQADUBQAh4wNAANQFACHrAwEA3wUAIewDAQDTBQAhCb4DAgCuBgAhvwMBAKwGACHCAwEArAYAIc8DAQCtBgAh3gMCAK4GACHiA0AAwQYAIeMDQADBBgAh6wMBAK0GACHsAwEArAYAIQoXAAD9BgAgvgMCAK4GACG_AwEArAYAIcIDAQCsBgAhzwMBAK0GACHeAwIArgYAIeIDQADBBgAh4wNAAMEGACHrAwEArQYAIewDAQCsBgAhChcAAIgHACC-AwIAAAABvwMBAAAAAcIDAQAAAAHPAwEAAAAB3gMCAAAAAeIDQAAAAAHjA0AAAAAB6wMBAAAAAewDAQAAAAEHAwAAkQcAIL4DAgAAAAG_AwEAAAABzgMCAAAAAc8DAQAAAAHiA0AAAAAB7QMBAAAAAQIAAAAiACAqAAC7BwAgAwAAACIAICoAALsHACArAAC6BwAgASMAALwKADANAwAAhQYAIA4AAIkGACC7AwAAkwYAMLwDAAAgABC9AwAAkwYAML4DAgAAAAG_AwEAAAABzgMCANIFACHPAwEA3wUAIdIDAgDSBQAh4gNAANQFACHtAwEA0wUAIdcEAACSBgAgAgAAACIAICMAALoHACACAAAAuAcAICMAALkHACAKuwMAALcHADC8AwAAuAcAEL0DAAC3BwAwvgMCANIFACG_AwEA0wUAIc4DAgDSBQAhzwMBAN8FACHSAwIA0gUAIeIDQADUBQAh7QMBANMFACEKuwMAALcHADC8AwAAuAcAEL0DAAC3BwAwvgMCANIFACG_AwEA0wUAIc4DAgDSBQAhzwMBAN8FACHSAwIA0gUAIeIDQADUBQAh7QMBANMFACEGvgMCAK4GACG_AwEArAYAIc4DAgCuBgAhzwMBAK0GACHiA0AAwQYAIe0DAQCsBgAhBwMAAI8HACC-AwIArgYAIb8DAQCsBgAhzgMCAK4GACHPAwEArQYAIeIDQADBBgAh7QMBAKwGACEHAwAAkQcAIL4DAgAAAAG_AwEAAAABzgMCAAAAAc8DAQAAAAHiA0AAAAAB7QMBAAAAAQMqAAC6CgAg2wQAALsKACDhBAAAAQAgBCoAALAHADDbBAAAsQcAMN0EAACzBwAg4QQAALQHADAEKgAApAcAMNsEAAClBwAw3QQAAKcHACDhBAAAqAcAMAQqAACbBwAw2wQAAJwHADDdBAAAngcAIOEEAADnBgAwAAAAAAAHKgAAtQoAICsAALgKACDbBAAAtgoAINwEAAC3CgAg3wQAABoAIOAEAAAaACDhBAAAAQAgAyoAALUKACDbBAAAtgoAIOEEAAABACAAAAAAAAUqAACwCgAgKwAAswoAINsEAACxCgAg3AQAALIKACDhBAAADwAgAyoAALAKACDbBAAAsQoAIOEEAAAPACAAAAAAAAUqAACqCgAgKwAArgoAINsEAACrCgAg3AQAAK0KACDhBAAAAQAgCyoAANUHADArAADaBwAw2wQAANYHADDcBAAA1wcAMN0EAADYBwAg3gQAANkHADDfBAAA2QcAMOAEAADZBwAw4QQAANkHADDiBAAA2wcAMOMEAADcBwAwCL4DAgAAAAHVAwEAAAAB2gNAAAAAAd4DAgAAAAHiA0AAAAAB4wNAAAAAAfgDAQAAAAH5AyAAAAABAgAAABMAICoAAOAHACADAAAAEwAgKgAA4AcAICsAAN8HACABIwAArAoAMA0JAACfBgAguwMAAJ4GADC8AwAAEQAQvQMAAJ4GADC-AwIAAAAB1QMBANMFACHaA0AA3QUAId4DAgDSBQAh4gNAANQFACHjA0AA1AUAIfcDAgDSBQAh-AMBANMFACH5AyAA4QUAIQIAAAATACAjAADfBwAgAgAAAN0HACAjAADeBwAgDLsDAADcBwAwvAMAAN0HABC9AwAA3AcAML4DAgDSBQAh1QMBANMFACHaA0AA3QUAId4DAgDSBQAh4gNAANQFACHjA0AA1AUAIfcDAgDSBQAh-AMBANMFACH5AyAA4QUAIQy7AwAA3AcAMLwDAADdBwAQvQMAANwHADC-AwIA0gUAIdUDAQDTBQAh2gNAAN0FACHeAwIA0gUAIeIDQADUBQAh4wNAANQFACH3AwIA0gUAIfgDAQDTBQAh-QMgAOEFACEIvgMCAK4GACHVAwEArAYAIdoDQAC_BgAh3gMCAK4GACHiA0AAwQYAIeMDQADBBgAh-AMBAKwGACH5AyAAwAYAIQi-AwIArgYAIdUDAQCsBgAh2gNAAL8GACHeAwIArgYAIeIDQADBBgAh4wNAAMEGACH4AwEArAYAIfkDIADABgAhCL4DAgAAAAHVAwEAAAAB2gNAAAAAAd4DAgAAAAHiA0AAAAAB4wNAAAAAAfgDAQAAAAH5AyAAAAABAyoAAKoKACDbBAAAqwoAIOEEAAABACAEKgAA1QcAMNsEAADWBwAw3QQAANgHACDhBAAA2QcAMAAAAAAABSoAAKIKACArAACoCgAg2wQAAKMKACDcBAAApwoAIOEEAAAFACAFKgAAoAoAICsAAKUKACDbBAAAoQoAINwEAACkCgAg4QQAAAEAIAMqAACiCgAg2wQAAKMKACDhBAAABQAgAyoAAKAKACDbBAAAoQoAIOEEAAABACAAAAAAAAUqAACaCgAgKwAAngoAINsEAACbCgAg3AQAAJ0KACDhBAAAAQAgCyoAAPMHADArAAD4BwAw2wQAAPQHADDcBAAA9QcAMN0EAAD2BwAg3gQAAPcHADDfBAAA9wcAMOAEAAD3BwAw4QQAAPcHADDiBAAA-QcAMOMEAAD6BwAwCAMAAOsHACC-AwIAAAABzgMCAAAAAeIDQAAAAAH4AwEAAAAB-wMBAAAAAfwDAgAAAAH9AwEAAAABAgAAAAkAICoAAP4HACADAAAACQAgKgAA_gcAICsAAP0HACABIwAAnAoAMA4DAACFBgAgBAAApAYAILsDAACjBgAwvAMAAAcAEL0DAACjBgAwvgMCAAAAAc4DAgDSBQAh4gNAANQFACH4AwEA0wUAIfoDAgDSBQAh-wMBANMFACH8AwIA0gUAIf0DAQDTBQAh2AQAAKIGACACAAAACQAgIwAA_QcAIAIAAAD7BwAgIwAA_AcAIAu7AwAA-gcAMLwDAAD7BwAQvQMAAPoHADC-AwIA0gUAIc4DAgDSBQAh4gNAANQFACH4AwEA0wUAIfoDAgDSBQAh-wMBANMFACH8AwIA0gUAIf0DAQDTBQAhC7sDAAD6BwAwvAMAAPsHABC9AwAA-gcAML4DAgDSBQAhzgMCANIFACHiA0AA1AUAIfgDAQDTBQAh-gMCANIFACH7AwEA0wUAIfwDAgDSBQAh_QMBANMFACEHvgMCAK4GACHOAwIArgYAIeIDQADBBgAh-AMBAKwGACH7AwEArAYAIfwDAgCuBgAh_QMBAKwGACEIAwAA6QcAIL4DAgCuBgAhzgMCAK4GACHiA0AAwQYAIfgDAQCsBgAh-wMBAKwGACH8AwIArgYAIf0DAQCsBgAhCAMAAOsHACC-AwIAAAABzgMCAAAAAeIDQAAAAAH4AwEAAAAB-wMBAAAAAfwDAgAAAAH9AwEAAAABAyoAAJoKACDbBAAAmwoAIOEEAAABACAEKgAA8wcAMNsEAAD0BwAw3QQAAPYHACDhBAAA9wcAMAAAAAAABd4ECAAAAAHkBAgAAAAB5QQIAAAAAeYECAAAAAHnBAgAAAABBSoAAJUKACArAACYCgAg2wQAAJYKACDcBAAAlwoAIOEEAADHAgAgAyoAAJUKACDbBAAAlgoAIOEEAADHAgAgAAAAAAAFKgAAkAoAICsAAJMKACDbBAAAkQoAINwEAACSCgAg4QQAAMcCACADKgAAkAoAINsEAACRCgAg4QQAAMcCACAAAAAAAAUqAACJCgAgKwAAjgoAINsEAACKCgAg3AQAAI0KACDhBAAAwwIAIAsqAACkCAAwKwAAqQgAMNsEAAClCAAw3AQAAKYIADDdBAAApwgAIN4EAACoCAAw3wQAAKgIADDgBAAAqAgAMOEEAACoCAAw4gQAAKoIADDjBAAAqwgAMAsqAACYCAAwKwAAnQgAMNsEAACZCAAw3AQAAJoIADDdBAAAmwgAIN4EAACcCAAw3wQAAJwIADDgBAAAnAgAMOEEAACcCAAw4gQAAJ4IADDjBAAAnwgAMAa-AwIAAAAB4gNAAAAAAYIEQAAAAAGDBAgAAAABhAQIAAAAAYUEgAAAAAECAAAAzwIAICoAAKMIACADAAAAzwIAICoAAKMIACArAACiCAAgASMAAIwKADAMxwEAANoFACC7AwAA1wUAMLwDAADNAgAQvQMAANcFADC-AwIAAAAB4gNAANQFACGBBAIA0gUAIYIEQADUBQAhgwQIANgFACGEBAgA2AUAIYUEAADZBQAgnwQAANYFACACAAAAzwIAICMAAKIIACACAAAAoAgAICMAAKEIACAKuwMAAJ8IADC8AwAAoAgAEL0DAACfCAAwvgMCANIFACHiA0AA1AUAIYEEAgDSBQAhggRAANQFACGDBAgA2AUAIYQECADYBQAhhQQAANkFACAKuwMAAJ8IADC8AwAAoAgAEL0DAACfCAAwvgMCANIFACHiA0AA1AUAIYEEAgDSBQAhggRAANQFACGDBAgA2AUAIYQECADYBQAhhQQAANkFACAGvgMCAK4GACHiA0AAwQYAIYIEQADBBgAhgwQIAIYIACGEBAgAhggAIYUEgAAAAAEGvgMCAK4GACHiA0AAwQYAIYIEQADBBgAhgwQIAIYIACGEBAgAhggAIYUEgAAAAAEGvgMCAAAAAeIDQAAAAAGCBEAAAAABgwQIAAAAAYQECAAAAAGFBIAAAAABDr4DAgAAAAHiA0AAAAAB-wNAAAAAAYMECAAAAAGEBAgAAAABhQSAAAAAAYYECAAAAAGHBAgAAAABiAQIAAAAAYkECAAAAAGKBAgAAAABiwQIAAAAAYwEAQAAAAGNBEAAAAABAgAAAMsCACAqAACvCAAgAwAAAMsCACAqAACvCAAgKwAArggAIAEjAACLCgAwFMcBAADaBQAguwMAANwFADC8AwAAyQIAEL0DAADcBQAwvgMCAAAAAeIDQADUBQAh-wNAANQFACGBBAIA0gUAIYMECADYBQAhhAQIANgFACGFBAAA2QUAIIYECADYBQAhhwQIANgFACGIBAgA2AUAIYkECADYBQAhigQIANgFACGLBAgA2AUAIYwEAQDTBQAhjQRAAN0FACGgBAAA2wUAIAIAAADLAgAgIwAArggAIAIAAACsCAAgIwAArQgAIBK7AwAAqwgAMLwDAACsCAAQvQMAAKsIADC-AwIA0gUAIeIDQADUBQAh-wNAANQFACGBBAIA0gUAIYMECADYBQAhhAQIANgFACGFBAAA2QUAIIYECADYBQAhhwQIANgFACGIBAgA2AUAIYkECADYBQAhigQIANgFACGLBAgA2AUAIYwEAQDTBQAhjQRAAN0FACESuwMAAKsIADC8AwAArAgAEL0DAACrCAAwvgMCANIFACHiA0AA1AUAIfsDQADUBQAhgQQCANIFACGDBAgA2AUAIYQECADYBQAhhQQAANkFACCGBAgA2AUAIYcECADYBQAhiAQIANgFACGJBAgA2AUAIYoECADYBQAhiwQIANgFACGMBAEA0wUAIY0EQADdBQAhDr4DAgCuBgAh4gNAAMEGACH7A0AAwQYAIYMECACGCAAhhAQIAIYIACGFBIAAAAABhgQIAIYIACGHBAgAhggAIYgECACGCAAhiQQIAIYIACGKBAgAhggAIYsECACGCAAhjAQBAKwGACGNBEAAvwYAIQ6-AwIArgYAIeIDQADBBgAh-wNAAMEGACGDBAgAhggAIYQECACGCAAhhQSAAAAAAYYECACGCAAhhwQIAIYIACGIBAgAhggAIYkECACGCAAhigQIAIYIACGLBAgAhggAIYwEAQCsBgAhjQRAAL8GACEOvgMCAAAAAeIDQAAAAAH7A0AAAAABgwQIAAAAAYQECAAAAAGFBIAAAAABhgQIAAAAAYcECAAAAAGIBAgAAAABiQQIAAAAAYoECAAAAAGLBAgAAAABjAQBAAAAAY0EQAAAAAEDKgAAiQoAINsEAACKCgAg4QQAAMMCACAEKgAApAgAMNsEAAClCAAw3QQAAKcIACDhBAAAqAgAMAQqAACYCAAw2wQAAJkIADDdBAAAmwgAIOEEAACcCAAwAAAAAAALKgAAuQgAMCsAAL4IADDbBAAAuggAMNwEAAC7CAAw3QQAALwIACDeBAAAvQgAMN8EAAC9CAAw4AQAAL0IADDhBAAAvQgAMOIEAAC_CAAw4wQAAMAIADASyAEAALEIACDJAQAAsggAIL4DAgAAAAHCAwEAAAAB4gNAAAAAAY4EAQAAAAGQBAEAAAABkQQBAAAAAZIEAgAAAAGTBAIAAAABlAQCAAAAAZUEAgAAAAGWBAIAAAABlwQCAAAAAZgEAgAAAAGZBEAAAAABmgQgAAAAAZsEQAAAAAECAAAAxwIAICoAAMQIACADAAAAxwIAICoAAMQIACArAADDCAAgASMAAIgKADAXxgEAAOIFACDIAQAA4wUAIMkBAADkBQAguwMAAN4FADC8AwAAxQIAEL0DAADeBQAwvgMCAAAAAcIDAQDTBQAh4gNAANQFACGOBAEAAAABjwQBANMFACGQBAEA0wUAIZEEAQDfBQAhkgQCANIFACGTBAIA0gUAIZQEAgDSBQAhlQQCANIFACGWBAIA0gUAIZcEAgDSBQAhmAQCAOAFACGZBEAA3QUAIZoEIADhBQAhmwRAAN0FACECAAAAxwIAICMAAMMIACACAAAAwQgAICMAAMIIACAUuwMAAMAIADC8AwAAwQgAEL0DAADACAAwvgMCANIFACHCAwEA0wUAIeIDQADUBQAhjgQBANMFACGPBAEA0wUAIZAEAQDTBQAhkQQBAN8FACGSBAIA0gUAIZMEAgDSBQAhlAQCANIFACGVBAIA0gUAIZYEAgDSBQAhlwQCANIFACGYBAIA4AUAIZkEQADdBQAhmgQgAOEFACGbBEAA3QUAIRS7AwAAwAgAMLwDAADBCAAQvQMAAMAIADC-AwIA0gUAIcIDAQDTBQAh4gNAANQFACGOBAEA0wUAIY8EAQDTBQAhkAQBANMFACGRBAEA3wUAIZIEAgDSBQAhkwQCANIFACGUBAIA0gUAIZUEAgDSBQAhlgQCANIFACGXBAIA0gUAIZgEAgDgBQAhmQRAAN0FACGaBCAA4QUAIZsEQADdBQAhEL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIY4EAQCsBgAhkAQBAKwGACGRBAEArQYAIZIEAgCuBgAhkwQCAK4GACGUBAIArgYAIZUEAgCuBgAhlgQCAK4GACGXBAIArgYAIZgEAgDCBgAhmQRAAL8GACGaBCAAwAYAIZsEQAC_BgAhEsgBAACWCAAgyQEAAJcIACC-AwIArgYAIcIDAQCsBgAh4gNAAMEGACGOBAEArAYAIZAEAQCsBgAhkQQBAK0GACGSBAIArgYAIZMEAgCuBgAhlAQCAK4GACGVBAIArgYAIZYEAgCuBgAhlwQCAK4GACGYBAIAwgYAIZkEQAC_BgAhmgQgAMAGACGbBEAAvwYAIRLIAQAAsQgAIMkBAACyCAAgvgMCAAAAAcIDAQAAAAHiA0AAAAABjgQBAAAAAZAEAQAAAAGRBAEAAAABkgQCAAAAAZMEAgAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAIAAAABmAQCAAAAAZkEQAAAAAGaBCAAAAABmwRAAAAAAQQqAAC5CAAw2wQAALoIADDdBAAAvAgAIOEEAAC9CAAwAAfGAQAAyAgAIMgBAADJCAAgyQEAAMoIACCRBAAApgYAIJgEAACmBgAgmQQAAKYGACCbBAAApgYAIAHKAQAAxggAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXeBAgAAAAB5AQIAAAAAeUECAAAAAHmBAgAAAAB5wQIAAAAAQXeBAQAAAAB5AQEAAAAAeUEBAAAAAHmBAQAAAAB5wQEAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALKgAA2QkAMCsAAN4JADDbBAAA2gkAMNwEAADbCQAw3QQAANwJACDeBAAA3QkAMN8EAADdCQAw4AQAAN0JADDhBAAA3QkAMOIEAADfCQAw4wQAAOAJADALKgAA0AkAMCsAANQJADDbBAAA0QkAMNwEAADSCQAw3QQAANMJACDeBAAA9wcAMN8EAAD3BwAw4AQAAPcHADDhBAAA9wcAMOIEAADVCQAw4wQAAPoHADALKgAAxAkAMCsAAMkJADDbBAAAxQkAMNwEAADGCQAw3QQAAMcJACDeBAAAyAkAMN8EAADICQAw4AQAAMgJADDhBAAAyAkAMOIEAADKCQAw4wQAAMsJADALKgAAuAkAMCsAAL0JADDbBAAAuQkAMNwEAAC6CQAw3QQAALsJACDeBAAAvAkAMN8EAAC8CQAw4AQAALwJADDhBAAAvAkAMOIEAAC-CQAw4wQAAL8JADALKgAArAkAMCsAALEJADDbBAAArQkAMNwEAACuCQAw3QQAAK8JACDeBAAAsAkAMN8EAACwCQAw4AQAALAJADDhBAAAsAkAMOIEAACyCQAw4wQAALMJADALKgAAowkAMCsAAKcJADDbBAAApAkAMNwEAAClCQAw3QQAAKYJACDeBAAAtAcAMN8EAAC0BwAw4AQAALQHADDhBAAAtAcAMOIEAACoCQAw4wQAALcHADALKgAAmgkAMCsAAJ4JADDbBAAAmwkAMNwEAACcCQAw3QQAAJ0JACDeBAAA5wYAMN8EAADnBgAw4AQAAOcGADDhBAAA5wYAMOIEAACfCQAw4wQAAOoGADALKgAAkQkAMCsAAJUJADDbBAAAkgkAMNwEAACTCQAw3QQAAJQJACDeBAAA5wYAMN8EAADnBgAw4AQAAOcGADDhBAAA5wYAMOIEAACWCQAw4wQAAOoGADALKgAAiAkAMCsAAIwJADDbBAAAiQkAMNwEAACKCQAw3QQAAIsJACDeBAAA2wYAMN8EAADbBgAw4AQAANsGADDhBAAA2wYAMOIEAACNCQAw4wQAAN4GADAFFQAAuAYAIL4DAgAAAAG_AwEAAAABwAMBAAAAAcEDAgAAAAECAAAAMQAgKgAAkAkAIAMAAAAxACAqAACQCQAgKwAAjwkAIAEjAACHCgAwAgAAADEAICMAAI8JACACAAAA3wYAICMAAI4JACAEvgMCAK4GACG_AwEArAYAIcADAQCtBgAhwQMCAK4GACEFFQAAtgYAIL4DAgCuBgAhvwMBAKwGACHAAwEArQYAIcEDAgCuBgAhBRUAALgGACC-AwIAAAABvwMBAAAAAcADAQAAAAHBAwIAAAABHg4AAO8GACAPAAD0BgAgEAAA8AYAIBEAAPYGACASAADxBgAgEwAA8gYAIBYAAPUGACC-AwIAAAABvwMBAAAAAc8DAQAAAAHQAwEAAAAB0QMBAAAAAdIDAgAAAAHTAwIAAAAB1AMCAAAAAdUDAQAAAAHWAwEAAAAB1wMBAAAAAdgDQAAAAAHZAwEAAAAB2gNAAAAAAdsDAgAAAAHdA4AAAAAB3gMCAAAAAd8DIAAAAAHgA0AAAAAB4QMBAAAAAeIDQAAAAAHjA0AAAAAB5AMCAAAAAQIAAAAqACAqAACZCQAgAwAAACoAICoAAJkJACArAACYCQAgASMAAIYKADACAAAAKgAgIwAAmAkAIAIAAADrBgAgIwAAlwkAIBe-AwIArgYAIb8DAQCsBgAhzwMBAK0GACHQAwEArQYAIdEDAQCtBgAh0gMCAK4GACHTAwIArgYAIdQDAgDCBgAh1QMBAKwGACHWAwEArAYAIdcDAQCtBgAh2ANAAL8GACHZAwEArAYAIdoDQAC_BgAh2wMCAMIGACHdA4AAAAAB3gMCAK4GACHfAyAAwAYAIeADQAC_BgAh4QMBAK0GACHiA0AAwQYAIeMDQADBBgAh5AMCAMIGACEeDgAAwwYAIA8AAMkGACAQAADEBgAgEQAAxQYAIBIAAMYGACATAADHBgAgFgAAygYAIL4DAgCuBgAhvwMBAKwGACHPAwEArQYAIdADAQCtBgAh0QMBAK0GACHSAwIArgYAIdMDAgCuBgAh1AMCAMIGACHVAwEArAYAIdYDAQCsBgAh1wMBAK0GACHYA0AAvwYAIdkDAQCsBgAh2gNAAL8GACHbAwIAwgYAId0DgAAAAAHeAwIArgYAId8DIADABgAh4ANAAL8GACHhAwEArQYAIeIDQADBBgAh4wNAAMEGACHkAwIAwgYAIR4OAADvBgAgDwAA9AYAIBAAAPAGACARAAD2BgAgEgAA8QYAIBMAAPIGACAWAAD1BgAgvgMCAAAAAb8DAQAAAAHPAwEAAAAB0AMBAAAAAdEDAQAAAAHSAwIAAAAB0wMCAAAAAdQDAgAAAAHVAwEAAAAB1gMBAAAAAdcDAQAAAAHYA0AAAAAB2QMBAAAAAdoDQAAAAAHbAwIAAAAB3QOAAAAAAd4DAgAAAAHfAyAAAAAB4ANAAAAAAeEDAQAAAAHiA0AAAAAB4wNAAAAAAeQDAgAAAAEeDgAA7wYAIA8AAPQGACAQAADwBgAgEQAA9gYAIBIAAPEGACAUAADzBgAgFgAA9QYAIL4DAgAAAAG_AwEAAAABzwMBAAAAAdADAQAAAAHRAwEAAAAB0gMCAAAAAdMDAgAAAAHUAwIAAAAB1QMBAAAAAdYDAQAAAAHXAwEAAAAB2ANAAAAAAdkDAQAAAAHaA0AAAAAB3AMCAAAAAd0DgAAAAAHeAwIAAAAB3wMgAAAAAeADQAAAAAHhAwEAAAAB4gNAAAAAAeMDQAAAAAHkAwIAAAABAgAAACoAICoAAKIJACADAAAAKgAgKgAAogkAICsAAKEJACABIwAAhQoAMAIAAAAqACAjAAChCQAgAgAAAOsGACAjAACgCQAgF74DAgCuBgAhvwMBAKwGACHPAwEArQYAIdADAQCtBgAh0QMBAK0GACHSAwIArgYAIdMDAgCuBgAh1AMCAMIGACHVAwEArAYAIdYDAQCsBgAh1wMBAK0GACHYA0AAvwYAIdkDAQCsBgAh2gNAAL8GACHcAwIArgYAId0DgAAAAAHeAwIArgYAId8DIADABgAh4ANAAL8GACHhAwEArQYAIeIDQADBBgAh4wNAAMEGACHkAwIAwgYAIR4OAADDBgAgDwAAyQYAIBAAAMQGACARAADFBgAgEgAAxgYAIBQAAMgGACAWAADKBgAgvgMCAK4GACG_AwEArAYAIc8DAQCtBgAh0AMBAK0GACHRAwEArQYAIdIDAgCuBgAh0wMCAK4GACHUAwIAwgYAIdUDAQCsBgAh1gMBAKwGACHXAwEArQYAIdgDQAC_BgAh2QMBAKwGACHaA0AAvwYAIdwDAgCuBgAh3QOAAAAAAd4DAgCuBgAh3wMgAMAGACHgA0AAvwYAIeEDAQCtBgAh4gNAAMEGACHjA0AAwQYAIeQDAgDCBgAhHg4AAO8GACAPAAD0BgAgEAAA8AYAIBEAAPYGACASAADxBgAgFAAA8wYAIBYAAPUGACC-AwIAAAABvwMBAAAAAc8DAQAAAAHQAwEAAAAB0QMBAAAAAdIDAgAAAAHTAwIAAAAB1AMCAAAAAdUDAQAAAAHWAwEAAAAB1wMBAAAAAdgDQAAAAAHZAwEAAAAB2gNAAAAAAdwDAgAAAAHdA4AAAAAB3gMCAAAAAd8DIAAAAAHgA0AAAAAB4QMBAAAAAeIDQAAAAAHjA0AAAAAB5AMCAAAAAQcOAACQBwAgvgMCAAAAAb8DAQAAAAHPAwEAAAAB0gMCAAAAAeIDQAAAAAHtAwEAAAABAgAAACIAICoAAKsJACADAAAAIgAgKgAAqwkAICsAAKoJACABIwAAhAoAMAIAAAAiACAjAACqCQAgAgAAALgHACAjAACpCQAgBr4DAgCuBgAhvwMBAKwGACHPAwEArQYAIdIDAgCuBgAh4gNAAMEGACHtAwEArAYAIQcOAACOBwAgvgMCAK4GACG_AwEArAYAIc8DAQCtBgAh0gMCAK4GACHiA0AAwQYAIe0DAQCsBgAhBw4AAJAHACC-AwIAAAABvwMBAAAAAc8DAQAAAAHSAwIAAAAB4gNAAAAAAe0DAQAAAAEPDwAAvQcAIBcAAL8HACAYAAC-BwAgvgMCAAAAAb8DAQAAAAHCAwEAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB7gMBAAAAAfADIAAAAAHxA4AAAAAB8gOAAAAAAfMDAQAAAAH0AwEAAAABAgAAAB4AICoAALcJACADAAAAHgAgKgAAtwkAICsAALYJACABIwAAgwoAMBQNAACFBgAgDwAAlQYAIBcAAIwGACAYAACWBgAguwMAAJQGADC8AwAAHAAQvQMAAJQGADC-AwIAAAABvwMBAAAAAcIDAQDTBQAh3wMgAOEFACHiA0AA1AUAIeMDQADUBQAh7gMBAAAAAe8DAgDSBQAh8AMgAOEFACHxAwAAiAYAIPIDAADZBQAg8wMBAN8FACH0AwEA0wUAIQIAAAAeACAjAAC2CQAgAgAAALQJACAjAAC1CQAgELsDAACzCQAwvAMAALQJABC9AwAAswkAML4DAgDSBQAhvwMBANMFACHCAwEA0wUAId8DIADhBQAh4gNAANQFACHjA0AA1AUAIe4DAQDTBQAh7wMCANIFACHwAyAA4QUAIfEDAACIBgAg8gMAANkFACDzAwEA3wUAIfQDAQDTBQAhELsDAACzCQAwvAMAALQJABC9AwAAswkAML4DAgDSBQAhvwMBANMFACHCAwEA0wUAId8DIADhBQAh4gNAANQFACHjA0AA1AUAIe4DAQDTBQAh7wMCANIFACHwAyAA4QUAIfEDAACIBgAg8gMAANkFACDzAwEA3wUAIfQDAQDTBQAhDL4DAgCuBgAhvwMBAKwGACHCAwEArAYAId8DIADABgAh4gNAAMEGACHjA0AAwQYAIe4DAQCsBgAh8AMgAMAGACHxA4AAAAAB8gOAAAAAAfMDAQCtBgAh9AMBAKwGACEPDwAAmAcAIBcAAJoHACAYAACZBwAgvgMCAK4GACG_AwEArAYAIcIDAQCsBgAh3wMgAMAGACHiA0AAwQYAIeMDQADBBgAh7gMBAKwGACHwAyAAwAYAIfEDgAAAAAHyA4AAAAAB8wMBAK0GACH0AwEArAYAIQ8PAAC9BwAgFwAAvwcAIBgAAL4HACC-AwIAAAABvwMBAAAAAcIDAQAAAAHfAyAAAAAB4gNAAAAAAeMDQAAAAAHuAwEAAAAB8AMgAAAAAfEDgAAAAAHyA4AAAAAB8wMBAAAAAfQDAQAAAAEHCoAAAAABvgMCAAAAAcIDAQAAAAHiA0AAAAAB4wNAAAAAAfUDAQAAAAH2AyAAAAABAgAAABgAICoAAMMJACADAAAAGAAgKgAAwwkAICsAAMIJACABIwAAggoAMAwDAACNBgAgCgAAiAYAILsDAACdBgAwvAMAABYAEL0DAACdBgAwvgMCAAAAAcIDAQDTBQAhzgMCAOAFACHiA0AA1AUAIeMDQADUBQAh9QMBANMFACH2AyAA4QUAIQIAAAAYACAjAADCCQAgAgAAAMAJACAjAADBCQAgCwoAAIgGACC7AwAAvwkAMLwDAADACQAQvQMAAL8JADC-AwIA0gUAIcIDAQDTBQAhzgMCAOAFACHiA0AA1AUAIeMDQADUBQAh9QMBANMFACH2AyAA4QUAIQsKAACIBgAguwMAAL8JADC8AwAAwAkAEL0DAAC_CQAwvgMCANIFACHCAwEA0wUAIc4DAgDgBQAh4gNAANQFACHjA0AA1AUAIfUDAQDTBQAh9gMgAOEFACEHCoAAAAABvgMCAK4GACHCAwEArAYAIeIDQADBBgAh4wNAAMEGACH1AwEArAYAIfYDIADABgAhBwqAAAAAAb4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIeMDQADBBgAh9QMBAKwGACH2AyAAwAYAIQcKgAAAAAG-AwIAAAABwgMBAAAAAeIDQAAAAAHjA0AAAAAB9QMBAAAAAfYDIAAAAAEICgAA4gcAIL4DAgAAAAHVAwEAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB9QMBAAAAAfgDAQAAAAECAAAADwAgKgAAzwkAIAMAAAAPACAqAADPCQAgKwAAzgkAIAEjAACBCgAwDQMAAIUGACAKAAChBgAguwMAAKAGADC8AwAADQAQvQMAAKAGADC-AwIAAAABzgMCANIFACHVAwEA0wUAId8DIADhBQAh4gNAANQFACHjA0AA1AUAIfUDAQDTBQAh-AMBANMFACECAAAADwAgIwAAzgkAIAIAAADMCQAgIwAAzQkAIAu7AwAAywkAMLwDAADMCQAQvQMAAMsJADC-AwIA0gUAIc4DAgDSBQAh1QMBANMFACHfAyAA4QUAIeIDQADUBQAh4wNAANQFACH1AwEA0wUAIfgDAQDTBQAhC7sDAADLCQAwvAMAAMwJABC9AwAAywkAML4DAgDSBQAhzgMCANIFACHVAwEA0wUAId8DIADhBQAh4gNAANQFACHjA0AA1AUAIfUDAQDTBQAh-AMBANMFACEHvgMCAK4GACHVAwEArAYAId8DIADABgAh4gNAAMEGACHjA0AAwQYAIfUDAQCsBgAh-AMBAKwGACEICgAA1AcAIL4DAgCuBgAh1QMBAKwGACHfAyAAwAYAIeIDQADBBgAh4wNAAMEGACH1AwEArAYAIfgDAQCsBgAhCAoAAOIHACC-AwIAAAAB1QMBAAAAAd8DIAAAAAHiA0AAAAAB4wNAAAAAAfUDAQAAAAH4AwEAAAABCAQAAOoHACC-AwIAAAAB4gNAAAAAAfgDAQAAAAH6AwIAAAAB-wMBAAAAAfwDAgAAAAH9AwEAAAABAgAAAAkAICoAANgJACADAAAACQAgKgAA2AkAICsAANcJACABIwAAgAoAMAIAAAAJACAjAADXCQAgAgAAAPsHACAjAADWCQAgB74DAgCuBgAh4gNAAMEGACH4AwEArAYAIfoDAgCuBgAh-wMBAKwGACH8AwIArgYAIf0DAQCsBgAhCAQAAOgHACC-AwIArgYAIeIDQADBBgAh-AMBAKwGACH6AwIArgYAIfsDAQCsBgAh_AMCAK4GACH9AwEArAYAIQgEAADqBwAgvgMCAAAAAeIDQAAAAAH4AwEAAAAB-gMCAAAAAfsDAQAAAAH8AwIAAAAB_QMBAAAAAQwFAACACAAgvgMCAAAAAcIDAQAAAAHeAwIAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB8wMBAAAAAfgDAQAAAAH-AwEAAAAB_wMCAAAAAYAEAQAAAAECAAAABQAgKgAA5AkAIAMAAAAFACAqAADkCQAgKwAA4wkAIAEjAAD_CQAwEQMAAIUGACAFAACZBgAguwMAAKUGADC8AwAAAwAQvQMAAKUGADC-AwIAAAABwgMBANMFACHOAwIA0gUAId4DAgDSBQAh3wMgAOEFACHiA0AA1AUAIeMDQADUBQAh8wMBANMFACH4AwEA0wUAIf4DAQDTBQAh_wMCANIFACGABAEA0wUAIQIAAAAFACAjAADjCQAgAgAAAOEJACAjAADiCQAgD7sDAADgCQAwvAMAAOEJABC9AwAA4AkAML4DAgDSBQAhwgMBANMFACHOAwIA0gUAId4DAgDSBQAh3wMgAOEFACHiA0AA1AUAIeMDQADUBQAh8wMBANMFACH4AwEA0wUAIf4DAQDTBQAh_wMCANIFACGABAEA0wUAIQ-7AwAA4AkAMLwDAADhCQAQvQMAAOAJADC-AwIA0gUAIcIDAQDTBQAhzgMCANIFACHeAwIA0gUAId8DIADhBQAh4gNAANQFACHjA0AA1AUAIfMDAQDTBQAh-AMBANMFACH-AwEA0wUAIf8DAgDSBQAhgAQBANMFACELvgMCAK4GACHCAwEArAYAId4DAgCuBgAh3wMgAMAGACHiA0AAwQYAIeMDQADBBgAh8wMBAKwGACH4AwEArAYAIf4DAQCsBgAh_wMCAK4GACGABAEArAYAIQwFAADyBwAgvgMCAK4GACHCAwEArAYAId4DAgCuBgAh3wMgAMAGACHiA0AAwQYAIeMDQADBBgAh8wMBAKwGACH4AwEArAYAIf4DAQCsBgAh_wMCAK4GACGABAEArAYAIQwFAACACAAgvgMCAAAAAcIDAQAAAAHeAwIAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB8wMBAAAAAfgDAQAAAAH-AwEAAAAB_wMCAAAAAYAEAQAAAAEEKgAA2QkAMNsEAADaCQAw3QQAANwJACDhBAAA3QkAMAQqAADQCQAw2wQAANEJADDdBAAA0wkAIOEEAAD3BwAwBCoAAMQJADDbBAAAxQkAMN0EAADHCQAg4QQAAMgJADAEKgAAuAkAMNsEAAC5CQAw3QQAALsJACDhBAAAvAkAMAQqAACsCQAw2wQAAK0JADDdBAAArwkAIOEEAACwCQAwBCoAAKMJADDbBAAApAkAMN0EAACmCQAg4QQAALQHADAEKgAAmgkAMNsEAACbCQAw3QQAAJ0JACDhBAAA5wYAMAQqAACRCQAw2wQAAJIJADDdBAAAlAkAIOEEAADnBgAwBCoAAIgJADDbBAAAiQkAMN0EAACLCQAg4QQAANsGADAAAAAAAAAAABMOAAD4CQAgDwAA9QkAIBAAAPkJACARAAD2CQAgEgAA9AkAIBMAAPcJACAUAAD3CQAgFgAA-gkAIM8DAACmBgAg0AMAAKYGACDRAwAApgYAINQDAACmBgAg1wMAAKYGACDYAwAApgYAINoDAACmBgAg2wMAAKYGACDgAwAApgYAIOEDAACmBgAg5AMAAKYGACAJBwAA7gkAIAgAAO8JACALAADwCQAgDAAA8QkAIBkAAPIJACAaAADzCQAgGwAA9AkAIBwAAPQJACAdAAD1CQAgBg0AAPcJACAPAADzCQAgFwAA9AkAIBgAAPsJACDyAwAApgYAIPMDAACmBgAgBA4AAPgJACAXAAD0CQAgzwMAAKYGACDrAwAApgYAIAAAAgMAAPcJACAKAAD9CQAgAAIDAAD3CQAgBQAA7wkAIAu-AwIAAAABwgMBAAAAAd4DAgAAAAHfAyAAAAAB4gNAAAAAAeMDQAAAAAHzAwEAAAAB-AMBAAAAAf4DAQAAAAH_AwIAAAABgAQBAAAAAQe-AwIAAAAB4gNAAAAAAfgDAQAAAAH6AwIAAAAB-wMBAAAAAfwDAgAAAAH9AwEAAAABB74DAgAAAAHVAwEAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB9QMBAAAAAfgDAQAAAAEHCoAAAAABvgMCAAAAAcIDAQAAAAHiA0AAAAAB4wNAAAAAAfUDAQAAAAH2AyAAAAABDL4DAgAAAAG_AwEAAAABwgMBAAAAAd8DIAAAAAHiA0AAAAAB4wNAAAAAAe4DAQAAAAHwAyAAAAAB8QOAAAAAAfIDgAAAAAHzAwEAAAAB9AMBAAAAAQa-AwIAAAABvwMBAAAAAc8DAQAAAAHSAwIAAAAB4gNAAAAAAe0DAQAAAAEXvgMCAAAAAb8DAQAAAAHPAwEAAAAB0AMBAAAAAdEDAQAAAAHSAwIAAAAB0wMCAAAAAdQDAgAAAAHVAwEAAAAB1gMBAAAAAdcDAQAAAAHYA0AAAAAB2QMBAAAAAdoDQAAAAAHcAwIAAAAB3QOAAAAAAd4DAgAAAAHfAyAAAAAB4ANAAAAAAeEDAQAAAAHiA0AAAAAB4wNAAAAAAeQDAgAAAAEXvgMCAAAAAb8DAQAAAAHPAwEAAAAB0AMBAAAAAdEDAQAAAAHSAwIAAAAB0wMCAAAAAdQDAgAAAAHVAwEAAAAB1gMBAAAAAdcDAQAAAAHYA0AAAAAB2QMBAAAAAdoDQAAAAAHbAwIAAAAB3QOAAAAAAd4DAgAAAAHfAyAAAAAB4ANAAAAAAeEDAQAAAAHiA0AAAAAB4wNAAAAAAeQDAgAAAAEEvgMCAAAAAb8DAQAAAAHAAwEAAAABwQMCAAAAARC-AwIAAAABwgMBAAAAAeIDQAAAAAGOBAEAAAABkAQBAAAAAZEEAQAAAAGSBAIAAAABkwQCAAAAAZQEAgAAAAGVBAIAAAABlgQCAAAAAZcEAgAAAAGYBAIAAAABmQRAAAAAAZoEIAAAAAGbBEAAAAABBL4DAgAAAAHCAwEAAAAB4gNAAAAAAY8EAQAAAAECAAAAwwIAICoAAIkKACAOvgMCAAAAAeIDQAAAAAH7A0AAAAABgwQIAAAAAYQECAAAAAGFBIAAAAABhgQIAAAAAYcECAAAAAGIBAgAAAABiQQIAAAAAYoECAAAAAGLBAgAAAABjAQBAAAAAY0EQAAAAAEGvgMCAAAAAeIDQAAAAAGCBEAAAAABgwQIAAAAAYQECAAAAAGFBIAAAAABAwAAANUCACAqAACJCgAgKwAAjwoAIAYAAADVAgAgIwAAjwoAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIY8EAQCsBgAhBL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIY8EAQCsBgAhE8YBAACwCAAgyQEAALIIACC-AwIAAAABwgMBAAAAAeIDQAAAAAGOBAEAAAABjwQBAAAAAZAEAQAAAAGRBAEAAAABkgQCAAAAAZMEAgAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAIAAAABmAQCAAAAAZkEQAAAAAGaBCAAAAABmwRAAAAAAQIAAADHAgAgKgAAkAoAIAMAAADFAgAgKgAAkAoAICsAAJQKACAVAAAAxQIAICMAAJQKACDGAQAAlQgAIMkBAACXCAAgvgMCAK4GACHCAwEArAYAIeIDQADBBgAhjgQBAKwGACGPBAEArAYAIZAEAQCsBgAhkQQBAK0GACGSBAIArgYAIZMEAgCuBgAhlAQCAK4GACGVBAIArgYAIZYEAgCuBgAhlwQCAK4GACGYBAIAwgYAIZkEQAC_BgAhmgQgAMAGACGbBEAAvwYAIRPGAQAAlQgAIMkBAACXCAAgvgMCAK4GACHCAwEArAYAIeIDQADBBgAhjgQBAKwGACGPBAEArAYAIZAEAQCsBgAhkQQBAK0GACGSBAIArgYAIZMEAgCuBgAhlAQCAK4GACGVBAIArgYAIZYEAgCuBgAhlwQCAK4GACGYBAIAwgYAIZkEQAC_BgAhmgQgAMAGACGbBEAAvwYAIRPGAQAAsAgAIMgBAACxCAAgvgMCAAAAAcIDAQAAAAHiA0AAAAABjgQBAAAAAY8EAQAAAAGQBAEAAAABkQQBAAAAAZIEAgAAAAGTBAIAAAABlAQCAAAAAZUEAgAAAAGWBAIAAAABlwQCAAAAAZgEAgAAAAGZBEAAAAABmgQgAAAAAZsEQAAAAAECAAAAxwIAICoAAJUKACADAAAAxQIAICoAAJUKACArAACZCgAgFQAAAMUCACAjAACZCgAgxgEAAJUIACDIAQAAlggAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIY4EAQCsBgAhjwQBAKwGACGQBAEArAYAIZEEAQCtBgAhkgQCAK4GACGTBAIArgYAIZQEAgCuBgAhlQQCAK4GACGWBAIArgYAIZcEAgCuBgAhmAQCAMIGACGZBEAAvwYAIZoEIADABgAhmwRAAL8GACETxgEAAJUIACDIAQAAlggAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIY4EAQCsBgAhjwQBAKwGACGQBAEArAYAIZEEAQCtBgAhkgQCAK4GACGTBAIArgYAIZQEAgCuBgAhlQQCAK4GACGWBAIArgYAIZcEAgCuBgAhmAQCAMIGACGZBEAAvwYAIZoEIADABgAhmwRAAL8GACEOCAAA5gkAIAsAAOcJACAMAADoCQAgGQAA6QkAIBoAAOoJACAbAADrCQAgHAAA7AkAIB0AAO0JACC-AwIAAAABwgMBAAAAAeIDQAAAAAHjA0AAAAAB0gQBAAAAAdMEAQAAAAECAAAAAQAgKgAAmgoAIAe-AwIAAAABzgMCAAAAAeIDQAAAAAH4AwEAAAAB-wMBAAAAAfwDAgAAAAH9AwEAAAABAwAAABoAICoAAJoKACArAACfCgAgEAAAABoAIAgAAIAJACALAACBCQAgDAAAggkAIBkAAIMJACAaAACECQAgGwAAhQkAIBwAAIYJACAdAACHCQAgIwAAnwoAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIeMDQADBBgAh0gQBAKwGACHTBAEArAYAIQ4IAACACQAgCwAAgQkAIAwAAIIJACAZAACDCQAgGgAAhAkAIBsAAIUJACAcAACGCQAgHQAAhwkAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIeMDQADBBgAh0gQBAKwGACHTBAEArAYAIQ4HAADlCQAgCwAA5wkAIAwAAOgJACAZAADpCQAgGgAA6gkAIBsAAOsJACAcAADsCQAgHQAA7QkAIL4DAgAAAAHCAwEAAAAB4gNAAAAAAeMDQAAAAAHSBAEAAAAB0wQBAAAAAQIAAAABACAqAACgCgAgDQMAAP8HACC-AwIAAAABwgMBAAAAAc4DAgAAAAHeAwIAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB8wMBAAAAAfgDAQAAAAH-AwEAAAAB_wMCAAAAAYAEAQAAAAECAAAABQAgKgAAogoAIAMAAAAaACAqAACgCgAgKwAApgoAIBAAAAAaACAHAAD_CAAgCwAAgQkAIAwAAIIJACAZAACDCQAgGgAAhAkAIBsAAIUJACAcAACGCQAgHQAAhwkAICMAAKYKACC-AwIArgYAIcIDAQCsBgAh4gNAAMEGACHjA0AAwQYAIdIEAQCsBgAh0wQBAKwGACEOBwAA_wgAIAsAAIEJACAMAACCCQAgGQAAgwkAIBoAAIQJACAbAACFCQAgHAAAhgkAIB0AAIcJACC-AwIArgYAIcIDAQCsBgAh4gNAAMEGACHjA0AAwQYAIdIEAQCsBgAh0wQBAKwGACEDAAAAAwAgKgAAogoAICsAAKkKACAPAAAAAwAgAwAA8QcAICMAAKkKACC-AwIArgYAIcIDAQCsBgAhzgMCAK4GACHeAwIArgYAId8DIADABgAh4gNAAMEGACHjA0AAwQYAIfMDAQCsBgAh-AMBAKwGACH-AwEArAYAIf8DAgCuBgAhgAQBAKwGACENAwAA8QcAIL4DAgCuBgAhwgMBAKwGACHOAwIArgYAId4DAgCuBgAh3wMgAMAGACHiA0AAwQYAIeMDQADBBgAh8wMBAKwGACH4AwEArAYAIf4DAQCsBgAh_wMCAK4GACGABAEArAYAIQ4HAADlCQAgCAAA5gkAIAwAAOgJACAZAADpCQAgGgAA6gkAIBsAAOsJACAcAADsCQAgHQAA7QkAIL4DAgAAAAHCAwEAAAAB4gNAAAAAAeMDQAAAAAHSBAEAAAAB0wQBAAAAAQIAAAABACAqAACqCgAgCL4DAgAAAAHVAwEAAAAB2gNAAAAAAd4DAgAAAAHiA0AAAAAB4wNAAAAAAfgDAQAAAAH5AyAAAAABAwAAABoAICoAAKoKACArAACvCgAgEAAAABoAIAcAAP8IACAIAACACQAgDAAAggkAIBkAAIMJACAaAACECQAgGwAAhQkAIBwAAIYJACAdAACHCQAgIwAArwoAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIeMDQADBBgAh0gQBAKwGACHTBAEArAYAIQ4HAAD_CAAgCAAAgAkAIAwAAIIJACAZAACDCQAgGgAAhAkAIBsAAIUJACAcAACGCQAgHQAAhwkAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIeMDQADBBgAh0gQBAKwGACHTBAEArAYAIQkDAADhBwAgvgMCAAAAAc4DAgAAAAHVAwEAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB9QMBAAAAAfgDAQAAAAECAAAADwAgKgAAsAoAIAMAAAANACAqAACwCgAgKwAAtAoAIAsAAAANACADAADTBwAgIwAAtAoAIL4DAgCuBgAhzgMCAK4GACHVAwEArAYAId8DIADABgAh4gNAAMEGACHjA0AAwQYAIfUDAQCsBgAh-AMBAKwGACEJAwAA0wcAIL4DAgCuBgAhzgMCAK4GACHVAwEArAYAId8DIADABgAh4gNAAMEGACHjA0AAwQYAIfUDAQCsBgAh-AMBAKwGACEOBwAA5QkAIAgAAOYJACALAADnCQAgGQAA6QkAIBoAAOoJACAbAADrCQAgHAAA7AkAIB0AAO0JACC-AwIAAAABwgMBAAAAAeIDQAAAAAHjA0AAAAAB0gQBAAAAAdMEAQAAAAECAAAAAQAgKgAAtQoAIAMAAAAaACAqAAC1CgAgKwAAuQoAIBAAAAAaACAHAAD_CAAgCAAAgAkAIAsAAIEJACAZAACDCQAgGgAAhAkAIBsAAIUJACAcAACGCQAgHQAAhwkAICMAALkKACC-AwIArgYAIcIDAQCsBgAh4gNAAMEGACHjA0AAwQYAIdIEAQCsBgAh0wQBAKwGACEOBwAA_wgAIAgAAIAJACALAACBCQAgGQAAgwkAIBoAAIQJACAbAACFCQAgHAAAhgkAIB0AAIcJACC-AwIArgYAIcIDAQCsBgAh4gNAAMEGACHjA0AAwQYAIdIEAQCsBgAh0wQBAKwGACEOBwAA5QkAIAgAAOYJACALAADnCQAgDAAA6AkAIBoAAOoJACAbAADrCQAgHAAA7AkAIB0AAO0JACC-AwIAAAABwgMBAAAAAeIDQAAAAAHjA0AAAAAB0gQBAAAAAdMEAQAAAAECAAAAAQAgKgAAugoAIAa-AwIAAAABvwMBAAAAAc4DAgAAAAHPAwEAAAAB4gNAAAAAAe0DAQAAAAEJvgMCAAAAAb8DAQAAAAHCAwEAAAABzwMBAAAAAd4DAgAAAAHiA0AAAAAB4wNAAAAAAesDAQAAAAHsAwEAAAABF74DAgAAAAG_AwEAAAABzwMBAAAAAdADAQAAAAHRAwEAAAAB0wMCAAAAAdQDAgAAAAHVAwEAAAAB1gMBAAAAAdcDAQAAAAHYA0AAAAAB2QMBAAAAAdoDQAAAAAHbAwIAAAAB3AMCAAAAAd0DgAAAAAHeAwIAAAAB3wMgAAAAAeADQAAAAAHhAwEAAAAB4gNAAAAAAeMDQAAAAAHkAwIAAAABAwAAABoAICoAALoKACArAADBCgAgEAAAABoAIAcAAP8IACAIAACACQAgCwAAgQkAIAwAAIIJACAaAACECQAgGwAAhQkAIBwAAIYJACAdAACHCQAgIwAAwQoAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIeMDQADBBgAh0gQBAKwGACHTBAEArAYAIQ4HAAD_CAAgCAAAgAkAIAsAAIEJACAMAACCCQAgGgAAhAkAIBsAAIUJACAcAACGCQAgHQAAhwkAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIeMDQADBBgAh0gQBAKwGACHTBAEArAYAIQ4HAADlCQAgCAAA5gkAIAsAAOcJACAMAADoCQAgGQAA6QkAIBsAAOsJACAcAADsCQAgHQAA7QkAIL4DAgAAAAHCAwEAAAAB4gNAAAAAAeMDQAAAAAHSBAEAAAAB0wQBAAAAAQIAAAABACAqAADCCgAgEA0AALwHACAXAAC_BwAgGAAAvgcAIL4DAgAAAAG_AwEAAAABwgMBAAAAAd8DIAAAAAHiA0AAAAAB4wNAAAAAAe4DAQAAAAHvAwIAAAAB8AMgAAAAAfEDgAAAAAHyA4AAAAAB8wMBAAAAAfQDAQAAAAECAAAAHgAgKgAAxAoAIAMAAAAaACAqAADCCgAgKwAAyAoAIBAAAAAaACAHAAD_CAAgCAAAgAkAIAsAAIEJACAMAACCCQAgGQAAgwkAIBsAAIUJACAcAACGCQAgHQAAhwkAICMAAMgKACC-AwIArgYAIcIDAQCsBgAh4gNAAMEGACHjA0AAwQYAIdIEAQCsBgAh0wQBAKwGACEOBwAA_wgAIAgAAIAJACALAACBCQAgDAAAggkAIBkAAIMJACAbAACFCQAgHAAAhgkAIB0AAIcJACC-AwIArgYAIcIDAQCsBgAh4gNAAMEGACHjA0AAwQYAIdIEAQCsBgAh0wQBAKwGACEDAAAAHAAgKgAAxAoAICsAAMsKACASAAAAHAAgDQAAlwcAIBcAAJoHACAYAACZBwAgIwAAywoAIL4DAgCuBgAhvwMBAKwGACHCAwEArAYAId8DIADABgAh4gNAAMEGACHjA0AAwQYAIe4DAQCsBgAh7wMCAK4GACHwAyAAwAYAIfEDgAAAAAHyA4AAAAAB8wMBAK0GACH0AwEArAYAIRANAACXBwAgFwAAmgcAIBgAAJkHACC-AwIArgYAIb8DAQCsBgAhwgMBAKwGACHfAyAAwAYAIeIDQADBBgAh4wNAAMEGACHuAwEArAYAIe8DAgCuBgAh8AMgAMAGACHxA4AAAAAB8gOAAAAAAfMDAQCtBgAh9AMBAKwGACEQDQAAvAcAIA8AAL0HACAXAAC_BwAgvgMCAAAAAb8DAQAAAAHCAwEAAAAB3wMgAAAAAeIDQAAAAAHjA0AAAAAB7gMBAAAAAe8DAgAAAAHwAyAAAAAB8QOAAAAAAfIDgAAAAAHzAwEAAAAB9AMBAAAAAQIAAAAeACAqAADMCgAgF74DAgAAAAG_AwEAAAABzwMBAAAAAdADAQAAAAHRAwEAAAAB0gMCAAAAAdQDAgAAAAHVAwEAAAAB1gMBAAAAAdcDAQAAAAHYA0AAAAAB2QMBAAAAAdoDQAAAAAHbAwIAAAAB3AMCAAAAAd0DgAAAAAHeAwIAAAAB3wMgAAAAAeADQAAAAAHhAwEAAAAB4gNAAAAAAeMDQAAAAAHkAwIAAAABAwAAABwAICoAAMwKACArAADRCgAgEgAAABwAIA0AAJcHACAPAACYBwAgFwAAmgcAICMAANEKACC-AwIArgYAIb8DAQCsBgAhwgMBAKwGACHfAyAAwAYAIeIDQADBBgAh4wNAAMEGACHuAwEArAYAIe8DAgCuBgAh8AMgAMAGACHxA4AAAAAB8gOAAAAAAfMDAQCtBgAh9AMBAKwGACEQDQAAlwcAIA8AAJgHACAXAACaBwAgvgMCAK4GACG_AwEArAYAIcIDAQCsBgAh3wMgAMAGACHiA0AAwQYAIeMDQADBBgAh7gMBAKwGACHvAwIArgYAIfADIADABgAh8QOAAAAAAfIDgAAAAAHzAwEArQYAIfQDAQCsBgAhHw4AAO8GACAPAAD0BgAgEAAA8AYAIBEAAPYGACATAADyBgAgFAAA8wYAIBYAAPUGACC-AwIAAAABvwMBAAAAAc8DAQAAAAHQAwEAAAAB0QMBAAAAAdIDAgAAAAHTAwIAAAAB1AMCAAAAAdUDAQAAAAHWAwEAAAAB1wMBAAAAAdgDQAAAAAHZAwEAAAAB2gNAAAAAAdsDAgAAAAHcAwIAAAAB3QOAAAAAAd4DAgAAAAHfAyAAAAAB4ANAAAAAAeEDAQAAAAHiA0AAAAAB4wNAAAAAAeQDAgAAAAECAAAAKgAgKgAA0goAIA4HAADlCQAgCAAA5gkAIAsAAOcJACAMAADoCQAgGQAA6QkAIBoAAOoJACAbAADrCQAgHQAA7QkAIL4DAgAAAAHCAwEAAAAB4gNAAAAAAeMDQAAAAAHSBAEAAAAB0wQBAAAAAQIAAAABACAqAADUCgAgDgcAAOUJACAIAADmCQAgCwAA5wkAIAwAAOgJACAZAADpCQAgGgAA6gkAIBwAAOwJACAdAADtCQAgvgMCAAAAAcIDAQAAAAHiA0AAAAAB4wNAAAAAAdIEAQAAAAHTBAEAAAABAgAAAAEAICoAANYKACALDgAAhwcAIL4DAgAAAAG_AwEAAAABwgMBAAAAAc8DAQAAAAHSAwIAAAAB3gMCAAAAAeIDQAAAAAHjA0AAAAAB6wMBAAAAAewDAQAAAAECAAAAJgAgKgAA2AoAIBANAAC8BwAgDwAAvQcAIBgAAL4HACC-AwIAAAABvwMBAAAAAcIDAQAAAAHfAyAAAAAB4gNAAAAAAeMDQAAAAAHuAwEAAAAB7wMCAAAAAfADIAAAAAHxA4AAAAAB8gOAAAAAAfMDAQAAAAH0AwEAAAABAgAAAB4AICoAANoKACAXvgMCAAAAAb8DAQAAAAHPAwEAAAAB0AMBAAAAAdEDAQAAAAHSAwIAAAAB0wMCAAAAAdUDAQAAAAHWAwEAAAAB1wMBAAAAAdgDQAAAAAHZAwEAAAAB2gNAAAAAAdsDAgAAAAHcAwIAAAAB3QOAAAAAAd4DAgAAAAHfAyAAAAAB4ANAAAAAAeEDAQAAAAHiA0AAAAAB4wNAAAAAAeQDAgAAAAEEvgMCAAAAAb8DAQAAAAHAAwEAAAABzgMCAAAAAQS-AwIAAAABvwMBAAAAAcADAQAAAAHCAwEAAAABAwAAABoAICoAANQKACArAADhCgAgEAAAABoAIAcAAP8IACAIAACACQAgCwAAgQkAIAwAAIIJACAZAACDCQAgGgAAhAkAIBsAAIUJACAdAACHCQAgIwAA4QoAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIeMDQADBBgAh0gQBAKwGACHTBAEArAYAIQ4HAAD_CAAgCAAAgAkAIAsAAIEJACAMAACCCQAgGQAAgwkAIBoAAIQJACAbAACFCQAgHQAAhwkAIL4DAgCuBgAhwgMBAKwGACHiA0AAwQYAIeMDQADBBgAh0gQBAKwGACHTBAEArAYAIQMAAAAaACAqAADWCgAgKwAA5AoAIBAAAAAaACAHAAD_CAAgCAAAgAkAIAsAAIEJACAMAACCCQAgGQAAgwkAIBoAAIQJACAcAACGCQAgHQAAhwkAICMAAOQKACC-AwIArgYAIcIDAQCsBgAh4gNAAMEGACHjA0AAwQYAIdIEAQCsBgAh0wQBAKwGACEOBwAA_wgAIAgAAIAJACALAACBCQAgDAAAggkAIBkAAIMJACAaAACECQAgHAAAhgkAIB0AAIcJACC-AwIArgYAIcIDAQCsBgAh4gNAAMEGACHjA0AAwQYAIdIEAQCsBgAh0wQBAKwGACEDAAAAKAAgKgAA0goAICsAAOcKACAhAAAAKAAgDgAAwwYAIA8AAMkGACAQAADEBgAgEQAAxQYAIBMAAMcGACAUAADIBgAgFgAAygYAICMAAOcKACC-AwIArgYAIb8DAQCsBgAhzwMBAK0GACHQAwEArQYAIdEDAQCtBgAh0gMCAK4GACHTAwIArgYAIdQDAgDCBgAh1QMBAKwGACHWAwEArAYAIdcDAQCtBgAh2ANAAL8GACHZAwEArAYAIdoDQAC_BgAh2wMCAMIGACHcAwIArgYAId0DgAAAAAHeAwIArgYAId8DIADABgAh4ANAAL8GACHhAwEArQYAIeIDQADBBgAh4wNAAMEGACHkAwIAwgYAIR8OAADDBgAgDwAAyQYAIBAAAMQGACARAADFBgAgEwAAxwYAIBQAAMgGACAWAADKBgAgvgMCAK4GACG_AwEArAYAIc8DAQCtBgAh0AMBAK0GACHRAwEArQYAIdIDAgCuBgAh0wMCAK4GACHUAwIAwgYAIdUDAQCsBgAh1gMBAKwGACHXAwEArQYAIdgDQAC_BgAh2QMBAKwGACHaA0AAvwYAIdsDAgDCBgAh3AMCAK4GACHdA4AAAAAB3gMCAK4GACHfAyAAwAYAIeADQAC_BgAh4QMBAK0GACHiA0AAwQYAIeMDQADBBgAh5AMCAMIGACEDAAAAJAAgKgAA2AoAICsAAOoKACANAAAAJAAgDgAA_AYAICMAAOoKACC-AwIArgYAIb8DAQCsBgAhwgMBAKwGACHPAwEArQYAIdIDAgCuBgAh3gMCAK4GACHiA0AAwQYAIeMDQADBBgAh6wMBAK0GACHsAwEArAYAIQsOAAD8BgAgvgMCAK4GACG_AwEArAYAIcIDAQCsBgAhzwMBAK0GACHSAwIArgYAId4DAgCuBgAh4gNAAMEGACHjA0AAwQYAIesDAQCtBgAh7AMBAKwGACEDAAAAHAAgKgAA2goAICsAAO0KACASAAAAHAAgDQAAlwcAIA8AAJgHACAYAACZBwAgIwAA7QoAIL4DAgCuBgAhvwMBAKwGACHCAwEArAYAId8DIADABgAh4gNAAMEGACHjA0AAwQYAIe4DAQCsBgAh7wMCAK4GACHwAyAAwAYAIfEDgAAAAAHyA4AAAAAB8wMBAK0GACH0AwEArAYAIRANAACXBwAgDwAAmAcAIBgAAJkHACC-AwIArgYAIb8DAQCsBgAhwgMBAKwGACHfAyAAwAYAIeIDQADBBgAh4wNAAMEGACHuAwEArAYAIe8DAgCuBgAh8AMgAMAGACHxA4AAAAAB8gOAAAAAAfMDAQCtBgAh9AMBAKwGACEOBwAA5QkAIAgAAOYJACALAADnCQAgDAAA6AkAIBkAAOkJACAaAADqCQAgGwAA6wkAIBwAAOwJACC-AwIAAAABwgMBAAAAAeIDQAAAAAHjA0AAAAAB0gQBAAAAAdMEAQAAAAECAAAAAQAgKgAA7goAIB8OAADvBgAgEAAA8AYAIBEAAPYGACASAADxBgAgEwAA8gYAIBQAAPMGACAWAAD1BgAgvgMCAAAAAb8DAQAAAAHPAwEAAAAB0AMBAAAAAdEDAQAAAAHSAwIAAAAB0wMCAAAAAdQDAgAAAAHVAwEAAAAB1gMBAAAAAdcDAQAAAAHYA0AAAAAB2QMBAAAAAdoDQAAAAAHbAwIAAAAB3AMCAAAAAd0DgAAAAAHeAwIAAAAB3wMgAAAAAeADQAAAAAHhAwEAAAAB4gNAAAAAAeMDQAAAAAHkAwIAAAABAgAAACoAICoAAPAKACADAAAAGgAgKgAA7goAICsAAPQKACAQAAAAGgAgBwAA_wgAIAgAAIAJACALAACBCQAgDAAAggkAIBkAAIMJACAaAACECQAgGwAAhQkAIBwAAIYJACAjAAD0CgAgvgMCAK4GACHCAwEArAYAIeIDQADBBgAh4wNAAMEGACHSBAEArAYAIdMEAQCsBgAhDgcAAP8IACAIAACACQAgCwAAgQkAIAwAAIIJACAZAACDCQAgGgAAhAkAIBsAAIUJACAcAACGCQAgvgMCAK4GACHCAwEArAYAIeIDQADBBgAh4wNAAMEGACHSBAEArAYAIdMEAQCsBgAhAwAAACgAICoAAPAKACArAAD3CgAgIQAAACgAIA4AAMMGACAQAADEBgAgEQAAxQYAIBIAAMYGACATAADHBgAgFAAAyAYAIBYAAMoGACAjAAD3CgAgvgMCAK4GACG_AwEArAYAIc8DAQCtBgAh0AMBAK0GACHRAwEArQYAIdIDAgCuBgAh0wMCAK4GACHUAwIAwgYAIdUDAQCsBgAh1gMBAKwGACHXAwEArQYAIdgDQAC_BgAh2QMBAKwGACHaA0AAvwYAIdsDAgDCBgAh3AMCAK4GACHdA4AAAAAB3gMCAK4GACHfAyAAwAYAIeADQAC_BgAh4QMBAK0GACHiA0AAwQYAIeMDQADBBgAh5AMCAMIGACEfDgAAwwYAIBAAAMQGACARAADFBgAgEgAAxgYAIBMAAMcGACAUAADIBgAgFgAAygYAIL4DAgCuBgAhvwMBAKwGACHPAwEArQYAIdADAQCtBgAh0QMBAK0GACHSAwIArgYAIdMDAgCuBgAh1AMCAMIGACHVAwEArAYAIdYDAQCsBgAh1wMBAK0GACHYA0AAvwYAIdkDAQCsBgAh2gNAAL8GACHbAwIAwgYAIdwDAgCuBgAh3QOAAAAAAd4DAgCuBgAh3wMgAMAGACHgA0AAvwYAIeEDAQCtBgAh4gNAAMEGACHjA0AAwQYAIeQDAgDCBgAhHw4AAO8GACAPAAD0BgAgEAAA8AYAIBEAAPYGACASAADxBgAgEwAA8gYAIBQAAPMGACC-AwIAAAABvwMBAAAAAc8DAQAAAAHQAwEAAAAB0QMBAAAAAdIDAgAAAAHTAwIAAAAB1AMCAAAAAdUDAQAAAAHWAwEAAAAB1wMBAAAAAdgDQAAAAAHZAwEAAAAB2gNAAAAAAdsDAgAAAAHcAwIAAAAB3QOAAAAAAd4DAgAAAAHfAyAAAAAB4ANAAAAAAeEDAQAAAAHiA0AAAAAB4wNAAAAAAeQDAgAAAAECAAAAKgAgKgAA-AoAIAMAAAAoACAqAAD4CgAgKwAA_AoAICEAAAAoACAOAADDBgAgDwAAyQYAIBAAAMQGACARAADFBgAgEgAAxgYAIBMAAMcGACAUAADIBgAgIwAA_AoAIL4DAgCuBgAhvwMBAKwGACHPAwEArQYAIdADAQCtBgAh0QMBAK0GACHSAwIArgYAIdMDAgCuBgAh1AMCAMIGACHVAwEArAYAIdYDAQCsBgAh1wMBAK0GACHYA0AAvwYAIdkDAQCsBgAh2gNAAL8GACHbAwIAwgYAIdwDAgCuBgAh3QOAAAAAAd4DAgCuBgAh3wMgAMAGACHgA0AAvwYAIeEDAQCtBgAh4gNAAMEGACHjA0AAwQYAIeQDAgDCBgAhHw4AAMMGACAPAADJBgAgEAAAxAYAIBEAAMUGACASAADGBgAgEwAAxwYAIBQAAMgGACC-AwIArgYAIb8DAQCsBgAhzwMBAK0GACHQAwEArQYAIdEDAQCtBgAh0gMCAK4GACHTAwIArgYAIdQDAgDCBgAh1QMBAKwGACHWAwEArAYAIdcDAQCtBgAh2ANAAL8GACHZAwEArAYAIdoDQAC_BgAh2wMCAMIGACHcAwIArgYAId0DgAAAAAHeAwIArgYAId8DIADABgAh4ANAAL8GACHhAwEArQYAIeIDQADBBgAh4wNAAMEGACHkAwIAwgYAIQoGABIHBgIIDAMLEAUMGQgZHwkaPwobQAwcQQwdQg0DAwABBQoDBgAEAgMAAQQAAgEFCwADAwABBgAHChQGAQkABQEKFQABAxsBBQYAEQ0AAQ8jChc7DBgnCwIDAAEOAAkDBgAQDgAJFysMCQYADw4ACQ8yDRAACxEsDBItDBMuARQAARY2DgIDAAEVAAwBFQAMAw84ABI3ABY5AAEXOgADDzwAFz4AGD0ACQdDAAhEAAtFAAxGABlHABpIABtJABxKAB1LAAAAAAUGABcwABgxABkyABozABsAAAAAAAUGABcwABgxABkyABozABsAAAAFBgAhMAAiMQAjMgAkMwAlAAAAAAAFBgAhMAAiMQAjMgAkMwAlAAAABQYAKzAALDEALTIALjMALwAAAAAABQYAKzAALDEALTIALjMALwAAAAUGADUwADYxADcyADgzADkAAAAAAAUGADUwADYxADcyADgzADkAAAAFBgA_MABAMQBBMgBCMwBDAAAAAAAFBgA_MABAMQBBMgBCMwBDAAAABQYASTAASjEASzIATDMATQAAAAAABQYASTAASjEASzIATDMATQAAAAUGAFMwAFQxAFUyAFYzAFcAAAAAAAUGAFMwAFQxAFUyAFYzAFcAAAAFBgBdMABeMQBfMgBgMwBhAAAAAAAFBgBdMABeMQBfMgBgMwBhAAAABQYAZzAAaDEAaTIAajMAawAAAAAABQYAZzAAaDEAaTIAajMAawAAAAUGAHEwAHIxAHMyAHQzAHUAAAAAAAUGAHEwAHIxAHMyAHQzAHUCBgB8ygHIAngEBgB7xgEAd8gBzAJ5yQHQAnoBxwEAeAHHAQB4AsgB0QIAyQHSAgABygHTAgAAAAUGAIABMACBATEAggEyAIMBMwCEAQAAAAAABQYAgAEwAIEBMQCCATIAgwEzAIQBAcYBAHcBxgEAdwUGAIkBMACKATEAiwEyAIwBMwCNAQAAAAAABQYAiQEwAIoBMQCLATIAjAEzAI0BAccBAHgBxwEAeAUGAJIBMACTATEAlAEyAJUBMwCWAQAAAAAABQYAkgEwAJMBMQCUATIAlQEzAJYBAccBAHgBxwEAeAUGAJsBMACcATEAnQEyAJ4BMwCfAQAAAAAABQYAmwEwAJwBMQCdATIAngEzAJ8BAQMAAQEDAAEFBgCkATAApQExAKYBMgCnATMAqAEAAAAAAAUGAKQBMAClATEApgEyAKcBMwCoAQIDAAEEAAICAwABBAACBQYArQEwAK4BMQCvATIAsAEzALEBAAAAAAAFBgCtATAArgExAK8BMgCwATMAsQEBAwABAQMAAQUGALYBMAC3ATEAuAEyALkBMwC6AQAAAAAABQYAtgEwALcBMQC4ATIAuQEzALoBAQkABQEJAAUFBgC_ATAAwAExAMEBMgDCATMAwwEAAAAAAAUGAL8BMADAATEAwQEyAMIBMwDDAQEDjwQBAQOVBAEFBgDIATAAyQExAMoBMgDLATMAzAEAAAAAAAUGAMgBMADJATEAygEyAMsBMwDMAQENAAEBDQABBQYA0QEwANIBMQDTATIA1AEzANUBAAAAAAAFBgDRATAA0gExANMBMgDUATMA1QECAwABDgAJAgMAAQ4ACQUGANoBMADbATEA3AEyAN0BMwDeAQAAAAAABQYA2gEwANsBMQDcATIA3QEzAN4BAQ4ACQEOAAkFBgDjATAA5AExAOUBMgDmATMA5wEAAAAAAAUGAOMBMADkATEA5QEyAOYBMwDnAQUOAAkQAAsR6QQME-oEARQAAQUOAAkQAAsR8AQME_EEARQAAQUGAOwBMADtATEA7gEyAO8BMwDwAQAAAAAABQYA7AEwAO0BMQDuATIA7wEzAPABAgMAARUADAIDAAEVAAwFBgD1ATAA9gExAPcBMgD4ATMA-QEAAAAAAAUGAPUBMAD2ATEA9wEyAPgBMwD5AQEVAAwBFQAMBQYA_gEwAP8BMQCAAjIAgQIzAIICAAAAAAAFBgD-ATAA_wExAIACMgCBAjMAggIeAgEfTAEgTgEhTwEiUAEkUgElVBMmVRQnVwEoWRMpWhUsWwEtXAEuXRM0YBY1YRw2Yx03ZB04Zx05aB06aR07ax08bRM9bh4-cB0_chNAcx9BdB1CdR1DdhNEeSBFeiZGfCdHfSdIgAEnSYEBJ0qCASdLhAEnTIYBE02HAShOiQEnT4sBE1CMASlRjQEnUo4BJ1OPARNUkgEqVZMBMFaVATFXlgExWJkBMVmaATFamwExW50BMVyfARNdoAEyXqIBMV-kARNgpQEzYaYBMWKnATFjqAETZKsBNGWsATpmrgE7Z68BO2iyATtpswE7arQBO2u2ATtsuAETbbkBPG67ATtvvQETcL4BPXG_ATtywAE7c8EBE3TEAT51xQFEdscBRXfIAUV4ywFFecwBRXrNAUV7zwFFfNEBE33SAUZ-1AFFf9YBE4AB1wFHgQHYAUWCAdkBRYMB2gEThAHdAUiFAd4BToYB4AFPhwHhAU-IAeQBT4kB5QFPigHmAU-LAegBT4wB6gETjQHrAVCOAe0BT48B7wETkAHwAVGRAfEBT5IB8gFPkwHzAROUAfYBUpUB9wFYlgH5AVmXAfoBWZgB_QFZmQH-AVmaAf8BWZsBgQJZnAGDAhOdAYQCWp4BhgJZnwGIAhOgAYkCW6EBigJZogGLAlmjAYwCE6QBjwJcpQGQAmKmAZICY6cBkwJjqAGWAmOpAZcCY6oBmAJjqwGaAmOsAZwCE60BnQJkrgGfAmOvAaECE7ABogJlsQGjAmOyAaQCY7MBpQITtAGoAma1AakCbLYBqwJttwGsAm24Aa8CbbkBsAJtugGxAm27AbMCbbwBtQITvQG2Am6-AbgCbb8BugITwAG7Am_BAbwCbcIBvQJtwwG-AhPEAcECcMUBwgJ2ywHEAnfMAdQCd80B1wJ3zgHYAnfPAdkCd9AB2wJ30QHdAhPSAd4CfdMB4AJ31AHiAhPVAeMCftYB5AJ31wHlAnfYAeYCE9kB6QJ_2gHqAoUB2wHrAnjcAewCeN0B7QJ43gHuAnjfAe8CeOAB8QJ44QHzAhPiAfQChgHjAfYCeOQB-AIT5QH5AocB5gH6AnjnAfsCeOgB_AIT6QH_AogB6gGAA44B6wGBA3nsAYIDee0BgwN57gGEA3nvAYUDefABhwN58QGJAxPyAYoDjwHzAYwDefQBjgMT9QGPA5AB9gGQA3n3AZEDefgBkgMT-QGVA5EB-gGWA5cB-wGXA3r8AZgDev0BmQN6_gGaA3r_AZsDeoACnQN6gQKfAxOCAqADmAGDAqIDeoQCpAMThQKlA5kBhgKmA3qHAqcDeogCqAMTiQKrA5oBigKsA6ABiwKtAwKMAq4DAo0CrwMCjgKwAwKPArEDApACswMCkQK1AxOSArYDoQGTArgDApQCugMTlQK7A6IBlgK8AwKXAr0DApgCvgMTmQLBA6MBmgLCA6kBmwLDAwOcAsQDA50CxQMDngLGAwOfAscDA6ACyQMDoQLLAxOiAswDqgGjAs4DA6QC0AMTpQLRA6sBpgLSAwOnAtMDA6gC1AMTqQLXA6wBqgLYA7IBqwLZAwWsAtoDBa0C2wMFrgLcAwWvAt0DBbAC3wMFsQLhAxOyAuIDswGzAuQDBbQC5gMTtQLnA7QBtgLoAwW3AukDBbgC6gMTuQLtA7UBugLuA7sBuwLvAwa8AvADBr0C8QMGvgLyAwa_AvMDBsAC9QMGwQL3AxPCAvgDvAHDAvoDBsQC_AMTxQL9A70BxgL-AwbHAv8DBsgCgAQTyQKDBL4BygKEBMQBywKFBAjMAoYECM0ChwQIzgKIBAjPAokECNACiwQI0QKNBBPSAo4ExQHTApEECNQCkwQT1QKUBMYB1gKWBAjXApcECNgCmAQT2QKbBMcB2gKcBM0B2wKdBAncAp4ECd0CnwQJ3gKgBAnfAqEECeACowQJ4QKlBBPiAqYEzgHjAqgECeQCqgQT5QKrBM8B5gKsBAnnAq0ECegCrgQT6QKxBNAB6gKyBNYB6wKzBArsArQECu0CtQQK7gK2BArvArcECvACuQQK8QK7BBPyArwE1wHzAr4ECvQCwAQT9QLBBNgB9gLCBAr3AsMECvgCxAQT-QLHBNkB-gLIBN8B-wLJBAv8AsoEC_0CywQL_gLMBAv_As0EC4ADzwQLgQPRBBOCA9IE4AGDA9QEC4QD1gQThQPXBOEBhgPYBAuHA9kEC4gD2gQTiQPdBOIBigPeBOgBiwPfBAyMA-AEDI0D4QQMjgPiBAyPA-MEDJAD5QQMkQPnBBOSA-gE6QGTA-wEDJQD7gQTlQPvBOoBlgPyBAyXA_MEDJgD9AQTmQP3BOsBmgP4BPEBmwP5BA2cA_oEDZ0D-wQNngP8BA2fA_0EDaAD_wQNoQOBBROiA4IF8gGjA4QFDaQDhgUTpQOHBfMBpgOIBQ2nA4kFDagDigUTqQONBfQBqgOOBfoBqwOPBQ6sA5AFDq0DkQUOrgOSBQ6vA5MFDrADlQUOsQOXBROyA5gF-wGzA5oFDrQDnAUTtQOdBfwBtgOeBQ63A58FDrgDoAUTuQOjBf0BugOkBYMC" } async function decodeBase64AsWasm(wasmBase64: string): Promise { @@ -208,36 +208,6 @@ export interface PrismaClient< */ get article(): Prisma.ArticleDelegate; - /** - * `prisma.strategy`: Exposes CRUD operations for the **Strategy** model. - * Example usage: - * ```ts - * // Fetch zero or more Strategies - * const strategies = await prisma.strategy.findMany() - * ``` - */ - get strategy(): Prisma.StrategyDelegate; - - /** - * `prisma.indicator`: Exposes CRUD operations for the **Indicator** model. - * Example usage: - * ```ts - * // Fetch zero or more Indicators - * const indicators = await prisma.indicator.findMany() - * ``` - */ - get indicator(): Prisma.IndicatorDelegate; - - /** - * `prisma.indicatorPoint`: Exposes CRUD operations for the **IndicatorPoint** model. - * Example usage: - * ```ts - * // Fetch zero or more IndicatorPoints - * const indicatorPoints = await prisma.indicatorPoint.findMany() - * ``` - */ - get indicatorPoint(): Prisma.IndicatorPointDelegate; - /** * `prisma.news`: Exposes CRUD operations for the **News** model. * Example usage: @@ -308,16 +278,6 @@ export interface PrismaClient< */ get scraperCache(): Prisma.ScraperCacheDelegate; - /** - * `prisma.oilPriceHistory`: Exposes CRUD operations for the **OilPriceHistory** model. - * Example usage: - * ```ts - * // Fetch zero or more OilPriceHistories - * const oilPriceHistories = await prisma.oilPriceHistory.findMany() - * ``` - */ - get oilPriceHistory(): Prisma.OilPriceHistoryDelegate; - /** * `prisma.mihomeAccount`: Exposes CRUD operations for the **MihomeAccount** model. * Example usage: diff --git a/generated/prisma/internal/prismaNamespace.ts b/generated/prisma/internal/prismaNamespace.ts index ab66c1f..dfcef84 100644 --- a/generated/prisma/internal/prismaNamespace.ts +++ b/generated/prisma/internal/prismaNamespace.ts @@ -386,9 +386,6 @@ type FieldRefInputType = Model extends never ? never : FieldRe export const ModelName = { User: 'User', Article: 'Article', - Strategy: 'Strategy', - Indicator: 'Indicator', - IndicatorPoint: 'IndicatorPoint', News: 'News', Alert: 'Alert', ServerNode: 'ServerNode', @@ -396,7 +393,6 @@ export const ModelName = { ServiceCheck: 'ServiceCheck', Econ_SowInventory: 'Econ_SowInventory', ScraperCache: 'ScraperCache', - OilPriceHistory: 'OilPriceHistory', MihomeAccount: 'MihomeAccount', MihomeHome: 'MihomeHome', MihomeDevice: 'MihomeDevice', @@ -428,7 +424,7 @@ export type TypeMap - fields: Prisma.StrategyFieldRefs - operations: { - findUnique: { - args: Prisma.StrategyFindUniqueArgs - result: runtime.Types.Utils.PayloadToResult | null - } - findUniqueOrThrow: { - args: Prisma.StrategyFindUniqueOrThrowArgs - result: runtime.Types.Utils.PayloadToResult - } - findFirst: { - args: Prisma.StrategyFindFirstArgs - result: runtime.Types.Utils.PayloadToResult | null - } - findFirstOrThrow: { - args: Prisma.StrategyFindFirstOrThrowArgs - result: runtime.Types.Utils.PayloadToResult - } - findMany: { - args: Prisma.StrategyFindManyArgs - result: runtime.Types.Utils.PayloadToResult[] - } - create: { - args: Prisma.StrategyCreateArgs - result: runtime.Types.Utils.PayloadToResult - } - createMany: { - args: Prisma.StrategyCreateManyArgs - result: BatchPayload - } - createManyAndReturn: { - args: Prisma.StrategyCreateManyAndReturnArgs - result: runtime.Types.Utils.PayloadToResult[] - } - delete: { - args: Prisma.StrategyDeleteArgs - result: runtime.Types.Utils.PayloadToResult - } - update: { - args: Prisma.StrategyUpdateArgs - result: runtime.Types.Utils.PayloadToResult - } - deleteMany: { - args: Prisma.StrategyDeleteManyArgs - result: BatchPayload - } - updateMany: { - args: Prisma.StrategyUpdateManyArgs - result: BatchPayload - } - updateManyAndReturn: { - args: Prisma.StrategyUpdateManyAndReturnArgs - result: runtime.Types.Utils.PayloadToResult[] - } - upsert: { - args: Prisma.StrategyUpsertArgs - result: runtime.Types.Utils.PayloadToResult - } - aggregate: { - args: Prisma.StrategyAggregateArgs - result: runtime.Types.Utils.Optional - } - groupBy: { - args: Prisma.StrategyGroupByArgs - result: runtime.Types.Utils.Optional[] - } - count: { - args: Prisma.StrategyCountArgs - result: runtime.Types.Utils.Optional | number - } - } - } - Indicator: { - payload: Prisma.$IndicatorPayload - fields: Prisma.IndicatorFieldRefs - operations: { - findUnique: { - args: Prisma.IndicatorFindUniqueArgs - result: runtime.Types.Utils.PayloadToResult | null - } - findUniqueOrThrow: { - args: Prisma.IndicatorFindUniqueOrThrowArgs - result: runtime.Types.Utils.PayloadToResult - } - findFirst: { - args: Prisma.IndicatorFindFirstArgs - result: runtime.Types.Utils.PayloadToResult | null - } - findFirstOrThrow: { - args: Prisma.IndicatorFindFirstOrThrowArgs - result: runtime.Types.Utils.PayloadToResult - } - findMany: { - args: Prisma.IndicatorFindManyArgs - result: runtime.Types.Utils.PayloadToResult[] - } - create: { - args: Prisma.IndicatorCreateArgs - result: runtime.Types.Utils.PayloadToResult - } - createMany: { - args: Prisma.IndicatorCreateManyArgs - result: BatchPayload - } - createManyAndReturn: { - args: Prisma.IndicatorCreateManyAndReturnArgs - result: runtime.Types.Utils.PayloadToResult[] - } - delete: { - args: Prisma.IndicatorDeleteArgs - result: runtime.Types.Utils.PayloadToResult - } - update: { - args: Prisma.IndicatorUpdateArgs - result: runtime.Types.Utils.PayloadToResult - } - deleteMany: { - args: Prisma.IndicatorDeleteManyArgs - result: BatchPayload - } - updateMany: { - args: Prisma.IndicatorUpdateManyArgs - result: BatchPayload - } - updateManyAndReturn: { - args: Prisma.IndicatorUpdateManyAndReturnArgs - result: runtime.Types.Utils.PayloadToResult[] - } - upsert: { - args: Prisma.IndicatorUpsertArgs - result: runtime.Types.Utils.PayloadToResult - } - aggregate: { - args: Prisma.IndicatorAggregateArgs - result: runtime.Types.Utils.Optional - } - groupBy: { - args: Prisma.IndicatorGroupByArgs - result: runtime.Types.Utils.Optional[] - } - count: { - args: Prisma.IndicatorCountArgs - result: runtime.Types.Utils.Optional | number - } - } - } - IndicatorPoint: { - payload: Prisma.$IndicatorPointPayload - fields: Prisma.IndicatorPointFieldRefs - operations: { - findUnique: { - args: Prisma.IndicatorPointFindUniqueArgs - result: runtime.Types.Utils.PayloadToResult | null - } - findUniqueOrThrow: { - args: Prisma.IndicatorPointFindUniqueOrThrowArgs - result: runtime.Types.Utils.PayloadToResult - } - findFirst: { - args: Prisma.IndicatorPointFindFirstArgs - result: runtime.Types.Utils.PayloadToResult | null - } - findFirstOrThrow: { - args: Prisma.IndicatorPointFindFirstOrThrowArgs - result: runtime.Types.Utils.PayloadToResult - } - findMany: { - args: Prisma.IndicatorPointFindManyArgs - result: runtime.Types.Utils.PayloadToResult[] - } - create: { - args: Prisma.IndicatorPointCreateArgs - result: runtime.Types.Utils.PayloadToResult - } - createMany: { - args: Prisma.IndicatorPointCreateManyArgs - result: BatchPayload - } - createManyAndReturn: { - args: Prisma.IndicatorPointCreateManyAndReturnArgs - result: runtime.Types.Utils.PayloadToResult[] - } - delete: { - args: Prisma.IndicatorPointDeleteArgs - result: runtime.Types.Utils.PayloadToResult - } - update: { - args: Prisma.IndicatorPointUpdateArgs - result: runtime.Types.Utils.PayloadToResult - } - deleteMany: { - args: Prisma.IndicatorPointDeleteManyArgs - result: BatchPayload - } - updateMany: { - args: Prisma.IndicatorPointUpdateManyArgs - result: BatchPayload - } - updateManyAndReturn: { - args: Prisma.IndicatorPointUpdateManyAndReturnArgs - result: runtime.Types.Utils.PayloadToResult[] - } - upsert: { - args: Prisma.IndicatorPointUpsertArgs - result: runtime.Types.Utils.PayloadToResult - } - aggregate: { - args: Prisma.IndicatorPointAggregateArgs - result: runtime.Types.Utils.Optional - } - groupBy: { - args: Prisma.IndicatorPointGroupByArgs - result: runtime.Types.Utils.Optional[] - } - count: { - args: Prisma.IndicatorPointCountArgs - result: runtime.Types.Utils.Optional | number - } - } - } News: { payload: Prisma.$NewsPayload fields: Prisma.NewsFieldRefs @@ -1320,80 +1094,6 @@ export type TypeMap - fields: Prisma.OilPriceHistoryFieldRefs - operations: { - findUnique: { - args: Prisma.OilPriceHistoryFindUniqueArgs - result: runtime.Types.Utils.PayloadToResult | null - } - findUniqueOrThrow: { - args: Prisma.OilPriceHistoryFindUniqueOrThrowArgs - result: runtime.Types.Utils.PayloadToResult - } - findFirst: { - args: Prisma.OilPriceHistoryFindFirstArgs - result: runtime.Types.Utils.PayloadToResult | null - } - findFirstOrThrow: { - args: Prisma.OilPriceHistoryFindFirstOrThrowArgs - result: runtime.Types.Utils.PayloadToResult - } - findMany: { - args: Prisma.OilPriceHistoryFindManyArgs - result: runtime.Types.Utils.PayloadToResult[] - } - create: { - args: Prisma.OilPriceHistoryCreateArgs - result: runtime.Types.Utils.PayloadToResult - } - createMany: { - args: Prisma.OilPriceHistoryCreateManyArgs - result: BatchPayload - } - createManyAndReturn: { - args: Prisma.OilPriceHistoryCreateManyAndReturnArgs - result: runtime.Types.Utils.PayloadToResult[] - } - delete: { - args: Prisma.OilPriceHistoryDeleteArgs - result: runtime.Types.Utils.PayloadToResult - } - update: { - args: Prisma.OilPriceHistoryUpdateArgs - result: runtime.Types.Utils.PayloadToResult - } - deleteMany: { - args: Prisma.OilPriceHistoryDeleteManyArgs - result: BatchPayload - } - updateMany: { - args: Prisma.OilPriceHistoryUpdateManyArgs - result: BatchPayload - } - updateManyAndReturn: { - args: Prisma.OilPriceHistoryUpdateManyAndReturnArgs - result: runtime.Types.Utils.PayloadToResult[] - } - upsert: { - args: Prisma.OilPriceHistoryUpsertArgs - result: runtime.Types.Utils.PayloadToResult - } - aggregate: { - args: Prisma.OilPriceHistoryAggregateArgs - result: runtime.Types.Utils.Optional - } - groupBy: { - args: Prisma.OilPriceHistoryGroupByArgs - result: runtime.Types.Utils.Optional[] - } - count: { - args: Prisma.OilPriceHistoryCountArgs - result: runtime.Types.Utils.Optional | number - } - } - } MihomeAccount: { payload: Prisma.$MihomeAccountPayload fields: Prisma.MihomeAccountFieldRefs @@ -2642,46 +2342,6 @@ export const ArticleScalarFieldEnum = { export type ArticleScalarFieldEnum = (typeof ArticleScalarFieldEnum)[keyof typeof ArticleScalarFieldEnum] -export const StrategyScalarFieldEnum = { - id: 'id', - name: 'name', - description: 'description', - data: 'data', - createdAt: 'createdAt', - updatedAt: 'updatedAt' -} as const - -export type StrategyScalarFieldEnum = (typeof StrategyScalarFieldEnum)[keyof typeof StrategyScalarFieldEnum] - - -export const IndicatorScalarFieldEnum = { - id: 'id', - name: 'name', - groupName: 'groupName', - title: 'title', - unit: 'unit', - source: 'source', - frequency: 'frequency', - hidden: 'hidden', - createdAt: 'createdAt', - updatedAt: 'updatedAt' -} as const - -export type IndicatorScalarFieldEnum = (typeof IndicatorScalarFieldEnum)[keyof typeof IndicatorScalarFieldEnum] - - -export const IndicatorPointScalarFieldEnum = { - id: 'id', - indicatorId: 'indicatorId', - period: 'period', - value: 'value', - createdAt: 'createdAt', - updatedAt: 'updatedAt' -} as const - -export type IndicatorPointScalarFieldEnum = (typeof IndicatorPointScalarFieldEnum)[keyof typeof IndicatorPointScalarFieldEnum] - - export const NewsScalarFieldEnum = { id: 'id', title: 'title', @@ -2801,19 +2461,6 @@ export const ScraperCacheScalarFieldEnum = { export type ScraperCacheScalarFieldEnum = (typeof ScraperCacheScalarFieldEnum)[keyof typeof ScraperCacheScalarFieldEnum] -export const OilPriceHistoryScalarFieldEnum = { - id: 'id', - date: 'date', - province: 'province', - fuel: 'fuel', - price: 'price', - source: 'source', - createdAt: 'createdAt' -} as const - -export type OilPriceHistoryScalarFieldEnum = (typeof OilPriceHistoryScalarFieldEnum)[keyof typeof OilPriceHistoryScalarFieldEnum] - - export const MihomeAccountScalarFieldEnum = { id: 'id', key: 'key', @@ -3076,13 +2723,6 @@ export const SortOrder = { export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder] -export const JsonNullValueInput = { - JsonNull: JsonNull -} as const - -export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput] - - export const NullableJsonNullValueInput = { DbNull: DbNull, JsonNull: JsonNull @@ -3091,6 +2731,13 @@ export const NullableJsonNullValueInput = { export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput] +export const JsonNullValueInput = { + JsonNull: JsonNull +} as const + +export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput] + + export const QueryMode = { default: 'default', insensitive: 'insensitive' @@ -3099,6 +2746,14 @@ export const QueryMode = { export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode] +export const NullsOrder = { + first: 'first', + last: 'last' +} as const + +export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder] + + export const JsonNullValueFilter = { DbNull: DbNull, JsonNull: JsonNull, @@ -3108,14 +2763,6 @@ export const JsonNullValueFilter = { export type JsonNullValueFilter = (typeof JsonNullValueFilter)[keyof typeof JsonNullValueFilter] -export const NullsOrder = { - first: 'first', - last: 'last' -} as const - -export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder] - - /** * Field references @@ -3164,27 +2811,6 @@ export type ListDateTimeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaM -/** - * Reference to a field of type 'Json' - */ -export type JsonFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Json'> - - - -/** - * Reference to a field of type 'QueryMode' - */ -export type EnumQueryModeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'QueryMode'> - - - -/** - * Reference to a field of type 'Boolean' - */ -export type BooleanFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Boolean'> - - - /** * Reference to a field of type 'Float' */ @@ -3212,6 +2838,27 @@ export type BigIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, export type ListBigIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'BigInt[]'> + +/** + * Reference to a field of type 'Json' + */ +export type JsonFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Json'> + + + +/** + * Reference to a field of type 'QueryMode' + */ +export type EnumQueryModeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'QueryMode'> + + + +/** + * Reference to a field of type 'Boolean' + */ +export type BooleanFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Boolean'> + + /** * Batch Payload for updateMany & deleteMany & createMany */ @@ -3309,9 +2956,6 @@ export type PrismaClientOptions = ({ export type GlobalOmitConfig = { user?: Prisma.UserOmit article?: Prisma.ArticleOmit - strategy?: Prisma.StrategyOmit - indicator?: Prisma.IndicatorOmit - indicatorPoint?: Prisma.IndicatorPointOmit news?: Prisma.NewsOmit alert?: Prisma.AlertOmit serverNode?: Prisma.ServerNodeOmit @@ -3319,7 +2963,6 @@ export type GlobalOmitConfig = { serviceCheck?: Prisma.ServiceCheckOmit econ_SowInventory?: Prisma.Econ_SowInventoryOmit scraperCache?: Prisma.ScraperCacheOmit - oilPriceHistory?: Prisma.OilPriceHistoryOmit mihomeAccount?: Prisma.MihomeAccountOmit mihomeHome?: Prisma.MihomeHomeOmit mihomeDevice?: Prisma.MihomeDeviceOmit diff --git a/generated/prisma/internal/prismaNamespaceBrowser.ts b/generated/prisma/internal/prismaNamespaceBrowser.ts index 251860c..b38b9d7 100644 --- a/generated/prisma/internal/prismaNamespaceBrowser.ts +++ b/generated/prisma/internal/prismaNamespaceBrowser.ts @@ -53,9 +53,6 @@ export const AnyNull = runtime.AnyNull export const ModelName = { User: 'User', Article: 'Article', - Strategy: 'Strategy', - Indicator: 'Indicator', - IndicatorPoint: 'IndicatorPoint', News: 'News', Alert: 'Alert', ServerNode: 'ServerNode', @@ -63,7 +60,6 @@ export const ModelName = { ServiceCheck: 'ServiceCheck', Econ_SowInventory: 'Econ_SowInventory', ScraperCache: 'ScraperCache', - OilPriceHistory: 'OilPriceHistory', MihomeAccount: 'MihomeAccount', MihomeHome: 'MihomeHome', MihomeDevice: 'MihomeDevice', @@ -123,46 +119,6 @@ export const ArticleScalarFieldEnum = { export type ArticleScalarFieldEnum = (typeof ArticleScalarFieldEnum)[keyof typeof ArticleScalarFieldEnum] -export const StrategyScalarFieldEnum = { - id: 'id', - name: 'name', - description: 'description', - data: 'data', - createdAt: 'createdAt', - updatedAt: 'updatedAt' -} as const - -export type StrategyScalarFieldEnum = (typeof StrategyScalarFieldEnum)[keyof typeof StrategyScalarFieldEnum] - - -export const IndicatorScalarFieldEnum = { - id: 'id', - name: 'name', - groupName: 'groupName', - title: 'title', - unit: 'unit', - source: 'source', - frequency: 'frequency', - hidden: 'hidden', - createdAt: 'createdAt', - updatedAt: 'updatedAt' -} as const - -export type IndicatorScalarFieldEnum = (typeof IndicatorScalarFieldEnum)[keyof typeof IndicatorScalarFieldEnum] - - -export const IndicatorPointScalarFieldEnum = { - id: 'id', - indicatorId: 'indicatorId', - period: 'period', - value: 'value', - createdAt: 'createdAt', - updatedAt: 'updatedAt' -} as const - -export type IndicatorPointScalarFieldEnum = (typeof IndicatorPointScalarFieldEnum)[keyof typeof IndicatorPointScalarFieldEnum] - - export const NewsScalarFieldEnum = { id: 'id', title: 'title', @@ -282,19 +238,6 @@ export const ScraperCacheScalarFieldEnum = { export type ScraperCacheScalarFieldEnum = (typeof ScraperCacheScalarFieldEnum)[keyof typeof ScraperCacheScalarFieldEnum] -export const OilPriceHistoryScalarFieldEnum = { - id: 'id', - date: 'date', - province: 'province', - fuel: 'fuel', - price: 'price', - source: 'source', - createdAt: 'createdAt' -} as const - -export type OilPriceHistoryScalarFieldEnum = (typeof OilPriceHistoryScalarFieldEnum)[keyof typeof OilPriceHistoryScalarFieldEnum] - - export const MihomeAccountScalarFieldEnum = { id: 'id', key: 'key', @@ -557,13 +500,6 @@ export const SortOrder = { export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder] -export const JsonNullValueInput = { - JsonNull: JsonNull -} as const - -export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput] - - export const NullableJsonNullValueInput = { DbNull: DbNull, JsonNull: JsonNull @@ -572,6 +508,13 @@ export const NullableJsonNullValueInput = { export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput] +export const JsonNullValueInput = { + JsonNull: JsonNull +} as const + +export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput] + + export const QueryMode = { default: 'default', insensitive: 'insensitive' @@ -580,6 +523,14 @@ export const QueryMode = { export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode] +export const NullsOrder = { + first: 'first', + last: 'last' +} as const + +export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder] + + export const JsonNullValueFilter = { DbNull: DbNull, JsonNull: JsonNull, @@ -588,11 +539,3 @@ export const JsonNullValueFilter = { export type JsonNullValueFilter = (typeof JsonNullValueFilter)[keyof typeof JsonNullValueFilter] - -export const NullsOrder = { - first: 'first', - last: 'last' -} as const - -export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder] - diff --git a/generated/prisma/models.ts b/generated/prisma/models.ts index cd74c6b..0bfc851 100644 --- a/generated/prisma/models.ts +++ b/generated/prisma/models.ts @@ -10,9 +10,6 @@ */ export type * from './models/User.ts' export type * from './models/Article.ts' -export type * from './models/Strategy.ts' -export type * from './models/Indicator.ts' -export type * from './models/IndicatorPoint.ts' export type * from './models/News.ts' export type * from './models/Alert.ts' export type * from './models/ServerNode.ts' @@ -20,7 +17,6 @@ export type * from './models/ServerNodeHistory.ts' export type * from './models/ServiceCheck.ts' export type * from './models/Econ_SowInventory.ts' export type * from './models/ScraperCache.ts' -export type * from './models/OilPriceHistory.ts' export type * from './models/MihomeAccount.ts' export type * from './models/MihomeHome.ts' export type * from './models/MihomeDevice.ts' diff --git a/generated/prisma/models/Alert.ts b/generated/prisma/models/Alert.ts index 1d44094..d9271eb 100644 --- a/generated/prisma/models/Alert.ts +++ b/generated/prisma/models/Alert.ts @@ -480,6 +480,14 @@ export type AlertSumOrderByAggregateInput = { diffRatio?: Prisma.SortOrder } +export type FloatFieldUpdateOperationsInput = { + set?: number + increment?: number + decrement?: number + multiply?: number + divide?: number +} + export type AlertSelect = runtime.Types.Extensions.GetSelect<{ diff --git a/generated/prisma/models/Indicator.ts b/generated/prisma/models/Indicator.ts deleted file mode 100644 index 55d4a94..0000000 --- a/generated/prisma/models/Indicator.ts +++ /dev/null @@ -1,1530 +0,0 @@ - -/* !!! This is code generated by Prisma. Do not edit directly. !!! */ -/* eslint-disable */ -// biome-ignore-all lint: generated file -// @ts-nocheck -/* - * This file exports the `Indicator` model and its related types. - * - * 🟢 You can import this file directly. - */ -import type * as runtime from "@prisma/client/runtime/client" -import type * as $Enums from "../enums.ts" -import type * as Prisma from "../internal/prismaNamespace.ts" - -/** - * Model Indicator - * - */ -export type IndicatorModel = runtime.Types.Result.DefaultSelection - -export type AggregateIndicator = { - _count: IndicatorCountAggregateOutputType | null - _avg: IndicatorAvgAggregateOutputType | null - _sum: IndicatorSumAggregateOutputType | null - _min: IndicatorMinAggregateOutputType | null - _max: IndicatorMaxAggregateOutputType | null -} - -export type IndicatorAvgAggregateOutputType = { - id: number | null -} - -export type IndicatorSumAggregateOutputType = { - id: number | null -} - -export type IndicatorMinAggregateOutputType = { - id: number | null - name: string | null - groupName: string | null - title: string | null - unit: string | null - source: string | null - frequency: string | null - hidden: boolean | null - createdAt: Date | null - updatedAt: Date | null -} - -export type IndicatorMaxAggregateOutputType = { - id: number | null - name: string | null - groupName: string | null - title: string | null - unit: string | null - source: string | null - frequency: string | null - hidden: boolean | null - createdAt: Date | null - updatedAt: Date | null -} - -export type IndicatorCountAggregateOutputType = { - id: number - name: number - groupName: number - title: number - unit: number - source: number - frequency: number - hidden: number - createdAt: number - updatedAt: number - _all: number -} - - -export type IndicatorAvgAggregateInputType = { - id?: true -} - -export type IndicatorSumAggregateInputType = { - id?: true -} - -export type IndicatorMinAggregateInputType = { - id?: true - name?: true - groupName?: true - title?: true - unit?: true - source?: true - frequency?: true - hidden?: true - createdAt?: true - updatedAt?: true -} - -export type IndicatorMaxAggregateInputType = { - id?: true - name?: true - groupName?: true - title?: true - unit?: true - source?: true - frequency?: true - hidden?: true - createdAt?: true - updatedAt?: true -} - -export type IndicatorCountAggregateInputType = { - id?: true - name?: true - groupName?: true - title?: true - unit?: true - source?: true - frequency?: true - hidden?: true - createdAt?: true - updatedAt?: true - _all?: true -} - -export type IndicatorAggregateArgs = { - /** - * Filter which Indicator to aggregate. - */ - where?: Prisma.IndicatorWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of Indicators to fetch. - */ - orderBy?: Prisma.IndicatorOrderByWithRelationInput | Prisma.IndicatorOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the start position - */ - cursor?: Prisma.IndicatorWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` Indicators from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` Indicators. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Count returned Indicators - **/ - _count?: true | IndicatorCountAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to average - **/ - _avg?: IndicatorAvgAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to sum - **/ - _sum?: IndicatorSumAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to find the minimum value - **/ - _min?: IndicatorMinAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to find the maximum value - **/ - _max?: IndicatorMaxAggregateInputType -} - -export type GetIndicatorAggregateType = { - [P in keyof T & keyof AggregateIndicator]: P extends '_count' | 'count' - ? T[P] extends true - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType -} - - - - -export type IndicatorGroupByArgs = { - where?: Prisma.IndicatorWhereInput - orderBy?: Prisma.IndicatorOrderByWithAggregationInput | Prisma.IndicatorOrderByWithAggregationInput[] - by: Prisma.IndicatorScalarFieldEnum[] | Prisma.IndicatorScalarFieldEnum - having?: Prisma.IndicatorScalarWhereWithAggregatesInput - take?: number - skip?: number - _count?: IndicatorCountAggregateInputType | true - _avg?: IndicatorAvgAggregateInputType - _sum?: IndicatorSumAggregateInputType - _min?: IndicatorMinAggregateInputType - _max?: IndicatorMaxAggregateInputType -} - -export type IndicatorGroupByOutputType = { - id: number - name: string - groupName: string - title: string - unit: string - source: string - frequency: string - hidden: boolean - createdAt: Date - updatedAt: Date - _count: IndicatorCountAggregateOutputType | null - _avg: IndicatorAvgAggregateOutputType | null - _sum: IndicatorSumAggregateOutputType | null - _min: IndicatorMinAggregateOutputType | null - _max: IndicatorMaxAggregateOutputType | null -} - -export type GetIndicatorGroupByPayload = Prisma.PrismaPromise< - Array< - Prisma.PickEnumerable & - { - [P in ((keyof T) & (keyof IndicatorGroupByOutputType))]: P extends '_count' - ? T[P] extends boolean - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType - } - > - > - - - -export type IndicatorWhereInput = { - AND?: Prisma.IndicatorWhereInput | Prisma.IndicatorWhereInput[] - OR?: Prisma.IndicatorWhereInput[] - NOT?: Prisma.IndicatorWhereInput | Prisma.IndicatorWhereInput[] - id?: Prisma.IntFilter<"Indicator"> | number - name?: Prisma.StringFilter<"Indicator"> | string - groupName?: Prisma.StringFilter<"Indicator"> | string - title?: Prisma.StringFilter<"Indicator"> | string - unit?: Prisma.StringFilter<"Indicator"> | string - source?: Prisma.StringFilter<"Indicator"> | string - frequency?: Prisma.StringFilter<"Indicator"> | string - hidden?: Prisma.BoolFilter<"Indicator"> | boolean - createdAt?: Prisma.DateTimeFilter<"Indicator"> | Date | string - updatedAt?: Prisma.DateTimeFilter<"Indicator"> | Date | string - points?: Prisma.IndicatorPointListRelationFilter -} - -export type IndicatorOrderByWithRelationInput = { - id?: Prisma.SortOrder - name?: Prisma.SortOrder - groupName?: Prisma.SortOrder - title?: Prisma.SortOrder - unit?: Prisma.SortOrder - source?: Prisma.SortOrder - frequency?: Prisma.SortOrder - hidden?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder - points?: Prisma.IndicatorPointOrderByRelationAggregateInput -} - -export type IndicatorWhereUniqueInput = Prisma.AtLeast<{ - id?: number - name?: string - AND?: Prisma.IndicatorWhereInput | Prisma.IndicatorWhereInput[] - OR?: Prisma.IndicatorWhereInput[] - NOT?: Prisma.IndicatorWhereInput | Prisma.IndicatorWhereInput[] - groupName?: Prisma.StringFilter<"Indicator"> | string - title?: Prisma.StringFilter<"Indicator"> | string - unit?: Prisma.StringFilter<"Indicator"> | string - source?: Prisma.StringFilter<"Indicator"> | string - frequency?: Prisma.StringFilter<"Indicator"> | string - hidden?: Prisma.BoolFilter<"Indicator"> | boolean - createdAt?: Prisma.DateTimeFilter<"Indicator"> | Date | string - updatedAt?: Prisma.DateTimeFilter<"Indicator"> | Date | string - points?: Prisma.IndicatorPointListRelationFilter -}, "id" | "name"> - -export type IndicatorOrderByWithAggregationInput = { - id?: Prisma.SortOrder - name?: Prisma.SortOrder - groupName?: Prisma.SortOrder - title?: Prisma.SortOrder - unit?: Prisma.SortOrder - source?: Prisma.SortOrder - frequency?: Prisma.SortOrder - hidden?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder - _count?: Prisma.IndicatorCountOrderByAggregateInput - _avg?: Prisma.IndicatorAvgOrderByAggregateInput - _max?: Prisma.IndicatorMaxOrderByAggregateInput - _min?: Prisma.IndicatorMinOrderByAggregateInput - _sum?: Prisma.IndicatorSumOrderByAggregateInput -} - -export type IndicatorScalarWhereWithAggregatesInput = { - AND?: Prisma.IndicatorScalarWhereWithAggregatesInput | Prisma.IndicatorScalarWhereWithAggregatesInput[] - OR?: Prisma.IndicatorScalarWhereWithAggregatesInput[] - NOT?: Prisma.IndicatorScalarWhereWithAggregatesInput | Prisma.IndicatorScalarWhereWithAggregatesInput[] - id?: Prisma.IntWithAggregatesFilter<"Indicator"> | number - name?: Prisma.StringWithAggregatesFilter<"Indicator"> | string - groupName?: Prisma.StringWithAggregatesFilter<"Indicator"> | string - title?: Prisma.StringWithAggregatesFilter<"Indicator"> | string - unit?: Prisma.StringWithAggregatesFilter<"Indicator"> | string - source?: Prisma.StringWithAggregatesFilter<"Indicator"> | string - frequency?: Prisma.StringWithAggregatesFilter<"Indicator"> | string - hidden?: Prisma.BoolWithAggregatesFilter<"Indicator"> | boolean - createdAt?: Prisma.DateTimeWithAggregatesFilter<"Indicator"> | Date | string - updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Indicator"> | Date | string -} - -export type IndicatorCreateInput = { - name: string - groupName: string - title?: string - unit?: string - source?: string - frequency?: string - hidden?: boolean - createdAt?: Date | string - updatedAt?: Date | string - points?: Prisma.IndicatorPointCreateNestedManyWithoutIndicatorInput -} - -export type IndicatorUncheckedCreateInput = { - id?: number - name: string - groupName: string - title?: string - unit?: string - source?: string - frequency?: string - hidden?: boolean - createdAt?: Date | string - updatedAt?: Date | string - points?: Prisma.IndicatorPointUncheckedCreateNestedManyWithoutIndicatorInput -} - -export type IndicatorUpdateInput = { - name?: Prisma.StringFieldUpdateOperationsInput | string - groupName?: Prisma.StringFieldUpdateOperationsInput | string - title?: Prisma.StringFieldUpdateOperationsInput | string - unit?: Prisma.StringFieldUpdateOperationsInput | string - source?: Prisma.StringFieldUpdateOperationsInput | string - frequency?: Prisma.StringFieldUpdateOperationsInput | string - hidden?: Prisma.BoolFieldUpdateOperationsInput | boolean - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - points?: Prisma.IndicatorPointUpdateManyWithoutIndicatorNestedInput -} - -export type IndicatorUncheckedUpdateInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - name?: Prisma.StringFieldUpdateOperationsInput | string - groupName?: Prisma.StringFieldUpdateOperationsInput | string - title?: Prisma.StringFieldUpdateOperationsInput | string - unit?: Prisma.StringFieldUpdateOperationsInput | string - source?: Prisma.StringFieldUpdateOperationsInput | string - frequency?: Prisma.StringFieldUpdateOperationsInput | string - hidden?: Prisma.BoolFieldUpdateOperationsInput | boolean - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - points?: Prisma.IndicatorPointUncheckedUpdateManyWithoutIndicatorNestedInput -} - -export type IndicatorCreateManyInput = { - id?: number - name: string - groupName: string - title?: string - unit?: string - source?: string - frequency?: string - hidden?: boolean - createdAt?: Date | string - updatedAt?: Date | string -} - -export type IndicatorUpdateManyMutationInput = { - name?: Prisma.StringFieldUpdateOperationsInput | string - groupName?: Prisma.StringFieldUpdateOperationsInput | string - title?: Prisma.StringFieldUpdateOperationsInput | string - unit?: Prisma.StringFieldUpdateOperationsInput | string - source?: Prisma.StringFieldUpdateOperationsInput | string - frequency?: Prisma.StringFieldUpdateOperationsInput | string - hidden?: Prisma.BoolFieldUpdateOperationsInput | boolean - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type IndicatorUncheckedUpdateManyInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - name?: Prisma.StringFieldUpdateOperationsInput | string - groupName?: Prisma.StringFieldUpdateOperationsInput | string - title?: Prisma.StringFieldUpdateOperationsInput | string - unit?: Prisma.StringFieldUpdateOperationsInput | string - source?: Prisma.StringFieldUpdateOperationsInput | string - frequency?: Prisma.StringFieldUpdateOperationsInput | string - hidden?: Prisma.BoolFieldUpdateOperationsInput | boolean - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type IndicatorCountOrderByAggregateInput = { - id?: Prisma.SortOrder - name?: Prisma.SortOrder - groupName?: Prisma.SortOrder - title?: Prisma.SortOrder - unit?: Prisma.SortOrder - source?: Prisma.SortOrder - frequency?: Prisma.SortOrder - hidden?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder -} - -export type IndicatorAvgOrderByAggregateInput = { - id?: Prisma.SortOrder -} - -export type IndicatorMaxOrderByAggregateInput = { - id?: Prisma.SortOrder - name?: Prisma.SortOrder - groupName?: Prisma.SortOrder - title?: Prisma.SortOrder - unit?: Prisma.SortOrder - source?: Prisma.SortOrder - frequency?: Prisma.SortOrder - hidden?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder -} - -export type IndicatorMinOrderByAggregateInput = { - id?: Prisma.SortOrder - name?: Prisma.SortOrder - groupName?: Prisma.SortOrder - title?: Prisma.SortOrder - unit?: Prisma.SortOrder - source?: Prisma.SortOrder - frequency?: Prisma.SortOrder - hidden?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder -} - -export type IndicatorSumOrderByAggregateInput = { - id?: Prisma.SortOrder -} - -export type IndicatorScalarRelationFilter = { - is?: Prisma.IndicatorWhereInput - isNot?: Prisma.IndicatorWhereInput -} - -export type BoolFieldUpdateOperationsInput = { - set?: boolean -} - -export type IndicatorCreateNestedOneWithoutPointsInput = { - create?: Prisma.XOR - connectOrCreate?: Prisma.IndicatorCreateOrConnectWithoutPointsInput - connect?: Prisma.IndicatorWhereUniqueInput -} - -export type IndicatorUpdateOneRequiredWithoutPointsNestedInput = { - create?: Prisma.XOR - connectOrCreate?: Prisma.IndicatorCreateOrConnectWithoutPointsInput - upsert?: Prisma.IndicatorUpsertWithoutPointsInput - connect?: Prisma.IndicatorWhereUniqueInput - update?: Prisma.XOR, Prisma.IndicatorUncheckedUpdateWithoutPointsInput> -} - -export type IndicatorCreateWithoutPointsInput = { - name: string - groupName: string - title?: string - unit?: string - source?: string - frequency?: string - hidden?: boolean - createdAt?: Date | string - updatedAt?: Date | string -} - -export type IndicatorUncheckedCreateWithoutPointsInput = { - id?: number - name: string - groupName: string - title?: string - unit?: string - source?: string - frequency?: string - hidden?: boolean - createdAt?: Date | string - updatedAt?: Date | string -} - -export type IndicatorCreateOrConnectWithoutPointsInput = { - where: Prisma.IndicatorWhereUniqueInput - create: Prisma.XOR -} - -export type IndicatorUpsertWithoutPointsInput = { - update: Prisma.XOR - create: Prisma.XOR - where?: Prisma.IndicatorWhereInput -} - -export type IndicatorUpdateToOneWithWhereWithoutPointsInput = { - where?: Prisma.IndicatorWhereInput - data: Prisma.XOR -} - -export type IndicatorUpdateWithoutPointsInput = { - name?: Prisma.StringFieldUpdateOperationsInput | string - groupName?: Prisma.StringFieldUpdateOperationsInput | string - title?: Prisma.StringFieldUpdateOperationsInput | string - unit?: Prisma.StringFieldUpdateOperationsInput | string - source?: Prisma.StringFieldUpdateOperationsInput | string - frequency?: Prisma.StringFieldUpdateOperationsInput | string - hidden?: Prisma.BoolFieldUpdateOperationsInput | boolean - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type IndicatorUncheckedUpdateWithoutPointsInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - name?: Prisma.StringFieldUpdateOperationsInput | string - groupName?: Prisma.StringFieldUpdateOperationsInput | string - title?: Prisma.StringFieldUpdateOperationsInput | string - unit?: Prisma.StringFieldUpdateOperationsInput | string - source?: Prisma.StringFieldUpdateOperationsInput | string - frequency?: Prisma.StringFieldUpdateOperationsInput | string - hidden?: Prisma.BoolFieldUpdateOperationsInput | boolean - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - - -/** - * Count Type IndicatorCountOutputType - */ - -export type IndicatorCountOutputType = { - points: number -} - -export type IndicatorCountOutputTypeSelect = { - points?: boolean | IndicatorCountOutputTypeCountPointsArgs -} - -/** - * IndicatorCountOutputType without action - */ -export type IndicatorCountOutputTypeDefaultArgs = { - /** - * Select specific fields to fetch from the IndicatorCountOutputType - */ - select?: Prisma.IndicatorCountOutputTypeSelect | null -} - -/** - * IndicatorCountOutputType without action - */ -export type IndicatorCountOutputTypeCountPointsArgs = { - where?: Prisma.IndicatorPointWhereInput -} - - -export type IndicatorSelect = runtime.Types.Extensions.GetSelect<{ - id?: boolean - name?: boolean - groupName?: boolean - title?: boolean - unit?: boolean - source?: boolean - frequency?: boolean - hidden?: boolean - createdAt?: boolean - updatedAt?: boolean - points?: boolean | Prisma.Indicator$pointsArgs - _count?: boolean | Prisma.IndicatorCountOutputTypeDefaultArgs -}, ExtArgs["result"]["indicator"]> - -export type IndicatorSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ - id?: boolean - name?: boolean - groupName?: boolean - title?: boolean - unit?: boolean - source?: boolean - frequency?: boolean - hidden?: boolean - createdAt?: boolean - updatedAt?: boolean -}, ExtArgs["result"]["indicator"]> - -export type IndicatorSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ - id?: boolean - name?: boolean - groupName?: boolean - title?: boolean - unit?: boolean - source?: boolean - frequency?: boolean - hidden?: boolean - createdAt?: boolean - updatedAt?: boolean -}, ExtArgs["result"]["indicator"]> - -export type IndicatorSelectScalar = { - id?: boolean - name?: boolean - groupName?: boolean - title?: boolean - unit?: boolean - source?: boolean - frequency?: boolean - hidden?: boolean - createdAt?: boolean - updatedAt?: boolean -} - -export type IndicatorOmit = runtime.Types.Extensions.GetOmit<"id" | "name" | "groupName" | "title" | "unit" | "source" | "frequency" | "hidden" | "createdAt" | "updatedAt", ExtArgs["result"]["indicator"]> -export type IndicatorInclude = { - points?: boolean | Prisma.Indicator$pointsArgs - _count?: boolean | Prisma.IndicatorCountOutputTypeDefaultArgs -} -export type IndicatorIncludeCreateManyAndReturn = {} -export type IndicatorIncludeUpdateManyAndReturn = {} - -export type $IndicatorPayload = { - name: "Indicator" - objects: { - points: Prisma.$IndicatorPointPayload[] - } - scalars: runtime.Types.Extensions.GetPayloadResult<{ - id: number - name: string - groupName: string - title: string - unit: string - source: string - frequency: string - hidden: boolean - createdAt: Date - updatedAt: Date - }, ExtArgs["result"]["indicator"]> - composites: {} -} - -export type IndicatorGetPayload = runtime.Types.Result.GetResult - -export type IndicatorCountArgs = - Omit & { - select?: IndicatorCountAggregateInputType | true - } - -export interface IndicatorDelegate { - [K: symbol]: { types: Prisma.TypeMap['model']['Indicator'], meta: { name: 'Indicator' } } - /** - * Find zero or one Indicator that matches the filter. - * @param {IndicatorFindUniqueArgs} args - Arguments to find a Indicator - * @example - * // Get one Indicator - * const indicator = await prisma.indicator.findUnique({ - * where: { - * // ... provide filter here - * } - * }) - */ - findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> - - /** - * Find one Indicator that matches the filter or throw an error with `error.code='P2025'` - * if no matches were found. - * @param {IndicatorFindUniqueOrThrowArgs} args - Arguments to find a Indicator - * @example - * // Get one Indicator - * const indicator = await prisma.indicator.findUniqueOrThrow({ - * where: { - * // ... provide filter here - * } - * }) - */ - findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Find the first Indicator that matches the filter. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorFindFirstArgs} args - Arguments to find a Indicator - * @example - * // Get one Indicator - * const indicator = await prisma.indicator.findFirst({ - * where: { - * // ... provide filter here - * } - * }) - */ - findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__IndicatorClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> - - /** - * Find the first Indicator that matches the filter or - * throw `PrismaKnownClientError` with `P2025` code if no matches were found. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorFindFirstOrThrowArgs} args - Arguments to find a Indicator - * @example - * // Get one Indicator - * const indicator = await prisma.indicator.findFirstOrThrow({ - * where: { - * // ... provide filter here - * } - * }) - */ - findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__IndicatorClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Find zero or more Indicators that matches the filter. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorFindManyArgs} args - Arguments to filter and select certain fields only. - * @example - * // Get all Indicators - * const indicators = await prisma.indicator.findMany() - * - * // Get first 10 Indicators - * const indicators = await prisma.indicator.findMany({ take: 10 }) - * - * // Only select the `id` - * const indicatorWithIdOnly = await prisma.indicator.findMany({ select: { id: true } }) - * - */ - findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> - - /** - * Create a Indicator. - * @param {IndicatorCreateArgs} args - Arguments to create a Indicator. - * @example - * // Create one Indicator - * const Indicator = await prisma.indicator.create({ - * data: { - * // ... data to create a Indicator - * } - * }) - * - */ - create(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Create many Indicators. - * @param {IndicatorCreateManyArgs} args - Arguments to create many Indicators. - * @example - * // Create many Indicators - * const indicator = await prisma.indicator.createMany({ - * data: [ - * // ... provide data here - * ] - * }) - * - */ - createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Create many Indicators and returns the data saved in the database. - * @param {IndicatorCreateManyAndReturnArgs} args - Arguments to create many Indicators. - * @example - * // Create many Indicators - * const indicator = await prisma.indicator.createManyAndReturn({ - * data: [ - * // ... provide data here - * ] - * }) - * - * // Create many Indicators and only return the `id` - * const indicatorWithIdOnly = await prisma.indicator.createManyAndReturn({ - * select: { id: true }, - * data: [ - * // ... provide data here - * ] - * }) - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * - */ - createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> - - /** - * Delete a Indicator. - * @param {IndicatorDeleteArgs} args - Arguments to delete one Indicator. - * @example - * // Delete one Indicator - * const Indicator = await prisma.indicator.delete({ - * where: { - * // ... filter to delete one Indicator - * } - * }) - * - */ - delete(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Update one Indicator. - * @param {IndicatorUpdateArgs} args - Arguments to update one Indicator. - * @example - * // Update one Indicator - * const indicator = await prisma.indicator.update({ - * where: { - * // ... provide filter here - * }, - * data: { - * // ... provide data here - * } - * }) - * - */ - update(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Delete zero or more Indicators. - * @param {IndicatorDeleteManyArgs} args - Arguments to filter Indicators to delete. - * @example - * // Delete a few Indicators - * const { count } = await prisma.indicator.deleteMany({ - * where: { - * // ... provide filter here - * } - * }) - * - */ - deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Update zero or more Indicators. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorUpdateManyArgs} args - Arguments to update one or more rows. - * @example - * // Update many Indicators - * const indicator = await prisma.indicator.updateMany({ - * where: { - * // ... provide filter here - * }, - * data: { - * // ... provide data here - * } - * }) - * - */ - updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Update zero or more Indicators and returns the data updated in the database. - * @param {IndicatorUpdateManyAndReturnArgs} args - Arguments to update many Indicators. - * @example - * // Update many Indicators - * const indicator = await prisma.indicator.updateManyAndReturn({ - * where: { - * // ... provide filter here - * }, - * data: [ - * // ... provide data here - * ] - * }) - * - * // Update zero or more Indicators and only return the `id` - * const indicatorWithIdOnly = await prisma.indicator.updateManyAndReturn({ - * select: { id: true }, - * where: { - * // ... provide filter here - * }, - * data: [ - * // ... provide data here - * ] - * }) - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * - */ - updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> - - /** - * Create or update one Indicator. - * @param {IndicatorUpsertArgs} args - Arguments to update or create a Indicator. - * @example - * // Update or create a Indicator - * const indicator = await prisma.indicator.upsert({ - * create: { - * // ... data to create a Indicator - * }, - * update: { - * // ... in case it already exists, update - * }, - * where: { - * // ... the filter for the Indicator we want to update - * } - * }) - */ - upsert(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - - /** - * Count the number of Indicators. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorCountArgs} args - Arguments to filter Indicators to count. - * @example - * // Count the number of Indicators - * const count = await prisma.indicator.count({ - * where: { - * // ... the filter for the Indicators we want to count - * } - * }) - **/ - count( - args?: Prisma.Subset, - ): Prisma.PrismaPromise< - T extends runtime.Types.Utils.Record<'select', any> - ? T['select'] extends true - ? number - : Prisma.GetScalarType - : number - > - - /** - * Allows you to perform aggregations operations on a Indicator. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorAggregateArgs} args - Select which aggregations you would like to apply and on what fields. - * @example - * // Ordered by age ascending - * // Where email contains prisma.io - * // Limited to the 10 users - * const aggregations = await prisma.user.aggregate({ - * _avg: { - * age: true, - * }, - * where: { - * email: { - * contains: "prisma.io", - * }, - * }, - * orderBy: { - * age: "asc", - * }, - * take: 10, - * }) - **/ - aggregate(args: Prisma.Subset): Prisma.PrismaPromise> - - /** - * Group by Indicator. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorGroupByArgs} args - Group by arguments. - * @example - * // Group by city, order by createdAt, get count - * const result = await prisma.user.groupBy({ - * by: ['city', 'createdAt'], - * orderBy: { - * createdAt: true - * }, - * _count: { - * _all: true - * }, - * }) - * - **/ - groupBy< - T extends IndicatorGroupByArgs, - HasSelectOrTake extends Prisma.Or< - Prisma.Extends<'skip', Prisma.Keys>, - Prisma.Extends<'take', Prisma.Keys> - >, - OrderByArg extends Prisma.True extends HasSelectOrTake - ? { orderBy: IndicatorGroupByArgs['orderBy'] } - : { orderBy?: IndicatorGroupByArgs['orderBy'] }, - OrderFields extends Prisma.ExcludeUnderscoreKeys>>, - ByFields extends Prisma.MaybeTupleToUnion, - ByValid extends Prisma.Has, - HavingFields extends Prisma.GetHavingFields, - HavingValid extends Prisma.Has, - ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, - InputErrors extends ByEmpty extends Prisma.True - ? `Error: "by" must not be empty.` - : HavingValid extends Prisma.False - ? { - [P in HavingFields]: P extends ByFields - ? never - : P extends string - ? `Error: Field "${P}" used in "having" needs to be provided in "by".` - : [ - Error, - 'Field ', - P, - ` in "having" needs to be provided in "by"`, - ] - }[HavingFields] - : 'take' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - : 'Error: If you provide "take", you also need to provide "orderBy"' - : 'skip' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - : 'Error: If you provide "skip", you also need to provide "orderBy"' - : ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetIndicatorGroupByPayload : Prisma.PrismaPromise -/** - * Fields of the Indicator model - */ -readonly fields: IndicatorFieldRefs; -} - -/** - * The delegate class that acts as a "Promise-like" for Indicator. - * Why is this prefixed with `Prisma__`? - * Because we want to prevent naming conflicts as mentioned in - * https://github.com/prisma/prisma-client-js/issues/707 - */ -export interface Prisma__IndicatorClient extends Prisma.PrismaPromise { - readonly [Symbol.toStringTag]: "PrismaPromise" - points = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise - /** - * Attaches a callback for only the rejection of the Promise. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of the callback. - */ - catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise - /** - * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The - * resolved value cannot be modified from the callback. - * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). - * @returns A Promise for the completion of the callback. - */ - finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise -} - - - - -/** - * Fields of the Indicator model - */ -export interface IndicatorFieldRefs { - readonly id: Prisma.FieldRef<"Indicator", 'Int'> - readonly name: Prisma.FieldRef<"Indicator", 'String'> - readonly groupName: Prisma.FieldRef<"Indicator", 'String'> - readonly title: Prisma.FieldRef<"Indicator", 'String'> - readonly unit: Prisma.FieldRef<"Indicator", 'String'> - readonly source: Prisma.FieldRef<"Indicator", 'String'> - readonly frequency: Prisma.FieldRef<"Indicator", 'String'> - readonly hidden: Prisma.FieldRef<"Indicator", 'Boolean'> - readonly createdAt: Prisma.FieldRef<"Indicator", 'DateTime'> - readonly updatedAt: Prisma.FieldRef<"Indicator", 'DateTime'> -} - - -// Custom InputTypes -/** - * Indicator findUnique - */ -export type IndicatorFindUniqueArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelect | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorInclude | null - /** - * Filter, which Indicator to fetch. - */ - where: Prisma.IndicatorWhereUniqueInput -} - -/** - * Indicator findUniqueOrThrow - */ -export type IndicatorFindUniqueOrThrowArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelect | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorInclude | null - /** - * Filter, which Indicator to fetch. - */ - where: Prisma.IndicatorWhereUniqueInput -} - -/** - * Indicator findFirst - */ -export type IndicatorFindFirstArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelect | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorInclude | null - /** - * Filter, which Indicator to fetch. - */ - where?: Prisma.IndicatorWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of Indicators to fetch. - */ - orderBy?: Prisma.IndicatorOrderByWithRelationInput | Prisma.IndicatorOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for searching for Indicators. - */ - cursor?: Prisma.IndicatorWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` Indicators from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` Indicators. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of Indicators. - */ - distinct?: Prisma.IndicatorScalarFieldEnum | Prisma.IndicatorScalarFieldEnum[] -} - -/** - * Indicator findFirstOrThrow - */ -export type IndicatorFindFirstOrThrowArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelect | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorInclude | null - /** - * Filter, which Indicator to fetch. - */ - where?: Prisma.IndicatorWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of Indicators to fetch. - */ - orderBy?: Prisma.IndicatorOrderByWithRelationInput | Prisma.IndicatorOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for searching for Indicators. - */ - cursor?: Prisma.IndicatorWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` Indicators from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` Indicators. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of Indicators. - */ - distinct?: Prisma.IndicatorScalarFieldEnum | Prisma.IndicatorScalarFieldEnum[] -} - -/** - * Indicator findMany - */ -export type IndicatorFindManyArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelect | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorInclude | null - /** - * Filter, which Indicators to fetch. - */ - where?: Prisma.IndicatorWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of Indicators to fetch. - */ - orderBy?: Prisma.IndicatorOrderByWithRelationInput | Prisma.IndicatorOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for listing Indicators. - */ - cursor?: Prisma.IndicatorWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` Indicators from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` Indicators. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of Indicators. - */ - distinct?: Prisma.IndicatorScalarFieldEnum | Prisma.IndicatorScalarFieldEnum[] -} - -/** - * Indicator create - */ -export type IndicatorCreateArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelect | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorInclude | null - /** - * The data needed to create a Indicator. - */ - data: Prisma.XOR -} - -/** - * Indicator createMany - */ -export type IndicatorCreateManyArgs = { - /** - * The data used to create many Indicators. - */ - data: Prisma.IndicatorCreateManyInput | Prisma.IndicatorCreateManyInput[] - skipDuplicates?: boolean -} - -/** - * Indicator createManyAndReturn - */ -export type IndicatorCreateManyAndReturnArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelectCreateManyAndReturn | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * The data used to create many Indicators. - */ - data: Prisma.IndicatorCreateManyInput | Prisma.IndicatorCreateManyInput[] - skipDuplicates?: boolean -} - -/** - * Indicator update - */ -export type IndicatorUpdateArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelect | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorInclude | null - /** - * The data needed to update a Indicator. - */ - data: Prisma.XOR - /** - * Choose, which Indicator to update. - */ - where: Prisma.IndicatorWhereUniqueInput -} - -/** - * Indicator updateMany - */ -export type IndicatorUpdateManyArgs = { - /** - * The data used to update Indicators. - */ - data: Prisma.XOR - /** - * Filter which Indicators to update - */ - where?: Prisma.IndicatorWhereInput - /** - * Limit how many Indicators to update. - */ - limit?: number -} - -/** - * Indicator updateManyAndReturn - */ -export type IndicatorUpdateManyAndReturnArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelectUpdateManyAndReturn | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * The data used to update Indicators. - */ - data: Prisma.XOR - /** - * Filter which Indicators to update - */ - where?: Prisma.IndicatorWhereInput - /** - * Limit how many Indicators to update. - */ - limit?: number -} - -/** - * Indicator upsert - */ -export type IndicatorUpsertArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelect | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorInclude | null - /** - * The filter to search for the Indicator to update in case it exists. - */ - where: Prisma.IndicatorWhereUniqueInput - /** - * In case the Indicator found by the `where` argument doesn't exist, create a new Indicator with this data. - */ - create: Prisma.XOR - /** - * In case the Indicator was found with the provided `where` argument, update it with this data. - */ - update: Prisma.XOR -} - -/** - * Indicator delete - */ -export type IndicatorDeleteArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelect | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorInclude | null - /** - * Filter which Indicator to delete. - */ - where: Prisma.IndicatorWhereUniqueInput -} - -/** - * Indicator deleteMany - */ -export type IndicatorDeleteManyArgs = { - /** - * Filter which Indicators to delete - */ - where?: Prisma.IndicatorWhereInput - /** - * Limit how many Indicators to delete. - */ - limit?: number -} - -/** - * Indicator.points - */ -export type Indicator$pointsArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null - where?: Prisma.IndicatorPointWhereInput - orderBy?: Prisma.IndicatorPointOrderByWithRelationInput | Prisma.IndicatorPointOrderByWithRelationInput[] - cursor?: Prisma.IndicatorPointWhereUniqueInput - take?: number - skip?: number - distinct?: Prisma.IndicatorPointScalarFieldEnum | Prisma.IndicatorPointScalarFieldEnum[] -} - -/** - * Indicator without action - */ -export type IndicatorDefaultArgs = { - /** - * Select specific fields to fetch from the Indicator - */ - select?: Prisma.IndicatorSelect | null - /** - * Omit specific fields from the Indicator - */ - omit?: Prisma.IndicatorOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorInclude | null -} diff --git a/generated/prisma/models/IndicatorPoint.ts b/generated/prisma/models/IndicatorPoint.ts deleted file mode 100644 index c2eb452..0000000 --- a/generated/prisma/models/IndicatorPoint.ts +++ /dev/null @@ -1,1445 +0,0 @@ - -/* !!! This is code generated by Prisma. Do not edit directly. !!! */ -/* eslint-disable */ -// biome-ignore-all lint: generated file -// @ts-nocheck -/* - * This file exports the `IndicatorPoint` model and its related types. - * - * 🟢 You can import this file directly. - */ -import type * as runtime from "@prisma/client/runtime/client" -import type * as $Enums from "../enums.ts" -import type * as Prisma from "../internal/prismaNamespace.ts" - -/** - * Model IndicatorPoint - * - */ -export type IndicatorPointModel = runtime.Types.Result.DefaultSelection - -export type AggregateIndicatorPoint = { - _count: IndicatorPointCountAggregateOutputType | null - _avg: IndicatorPointAvgAggregateOutputType | null - _sum: IndicatorPointSumAggregateOutputType | null - _min: IndicatorPointMinAggregateOutputType | null - _max: IndicatorPointMaxAggregateOutputType | null -} - -export type IndicatorPointAvgAggregateOutputType = { - id: number | null - indicatorId: number | null - value: number | null -} - -export type IndicatorPointSumAggregateOutputType = { - id: number | null - indicatorId: number | null - value: number | null -} - -export type IndicatorPointMinAggregateOutputType = { - id: number | null - indicatorId: number | null - period: string | null - value: number | null - createdAt: Date | null - updatedAt: Date | null -} - -export type IndicatorPointMaxAggregateOutputType = { - id: number | null - indicatorId: number | null - period: string | null - value: number | null - createdAt: Date | null - updatedAt: Date | null -} - -export type IndicatorPointCountAggregateOutputType = { - id: number - indicatorId: number - period: number - value: number - createdAt: number - updatedAt: number - _all: number -} - - -export type IndicatorPointAvgAggregateInputType = { - id?: true - indicatorId?: true - value?: true -} - -export type IndicatorPointSumAggregateInputType = { - id?: true - indicatorId?: true - value?: true -} - -export type IndicatorPointMinAggregateInputType = { - id?: true - indicatorId?: true - period?: true - value?: true - createdAt?: true - updatedAt?: true -} - -export type IndicatorPointMaxAggregateInputType = { - id?: true - indicatorId?: true - period?: true - value?: true - createdAt?: true - updatedAt?: true -} - -export type IndicatorPointCountAggregateInputType = { - id?: true - indicatorId?: true - period?: true - value?: true - createdAt?: true - updatedAt?: true - _all?: true -} - -export type IndicatorPointAggregateArgs = { - /** - * Filter which IndicatorPoint to aggregate. - */ - where?: Prisma.IndicatorPointWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of IndicatorPoints to fetch. - */ - orderBy?: Prisma.IndicatorPointOrderByWithRelationInput | Prisma.IndicatorPointOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the start position - */ - cursor?: Prisma.IndicatorPointWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` IndicatorPoints from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` IndicatorPoints. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Count returned IndicatorPoints - **/ - _count?: true | IndicatorPointCountAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to average - **/ - _avg?: IndicatorPointAvgAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to sum - **/ - _sum?: IndicatorPointSumAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to find the minimum value - **/ - _min?: IndicatorPointMinAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to find the maximum value - **/ - _max?: IndicatorPointMaxAggregateInputType -} - -export type GetIndicatorPointAggregateType = { - [P in keyof T & keyof AggregateIndicatorPoint]: P extends '_count' | 'count' - ? T[P] extends true - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType -} - - - - -export type IndicatorPointGroupByArgs = { - where?: Prisma.IndicatorPointWhereInput - orderBy?: Prisma.IndicatorPointOrderByWithAggregationInput | Prisma.IndicatorPointOrderByWithAggregationInput[] - by: Prisma.IndicatorPointScalarFieldEnum[] | Prisma.IndicatorPointScalarFieldEnum - having?: Prisma.IndicatorPointScalarWhereWithAggregatesInput - take?: number - skip?: number - _count?: IndicatorPointCountAggregateInputType | true - _avg?: IndicatorPointAvgAggregateInputType - _sum?: IndicatorPointSumAggregateInputType - _min?: IndicatorPointMinAggregateInputType - _max?: IndicatorPointMaxAggregateInputType -} - -export type IndicatorPointGroupByOutputType = { - id: number - indicatorId: number - period: string - value: number - createdAt: Date - updatedAt: Date - _count: IndicatorPointCountAggregateOutputType | null - _avg: IndicatorPointAvgAggregateOutputType | null - _sum: IndicatorPointSumAggregateOutputType | null - _min: IndicatorPointMinAggregateOutputType | null - _max: IndicatorPointMaxAggregateOutputType | null -} - -export type GetIndicatorPointGroupByPayload = Prisma.PrismaPromise< - Array< - Prisma.PickEnumerable & - { - [P in ((keyof T) & (keyof IndicatorPointGroupByOutputType))]: P extends '_count' - ? T[P] extends boolean - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType - } - > - > - - - -export type IndicatorPointWhereInput = { - AND?: Prisma.IndicatorPointWhereInput | Prisma.IndicatorPointWhereInput[] - OR?: Prisma.IndicatorPointWhereInput[] - NOT?: Prisma.IndicatorPointWhereInput | Prisma.IndicatorPointWhereInput[] - id?: Prisma.IntFilter<"IndicatorPoint"> | number - indicatorId?: Prisma.IntFilter<"IndicatorPoint"> | number - period?: Prisma.StringFilter<"IndicatorPoint"> | string - value?: Prisma.FloatFilter<"IndicatorPoint"> | number - createdAt?: Prisma.DateTimeFilter<"IndicatorPoint"> | Date | string - updatedAt?: Prisma.DateTimeFilter<"IndicatorPoint"> | Date | string - indicator?: Prisma.XOR -} - -export type IndicatorPointOrderByWithRelationInput = { - id?: Prisma.SortOrder - indicatorId?: Prisma.SortOrder - period?: Prisma.SortOrder - value?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder - indicator?: Prisma.IndicatorOrderByWithRelationInput -} - -export type IndicatorPointWhereUniqueInput = Prisma.AtLeast<{ - id?: number - indicatorId_period?: Prisma.IndicatorPointIndicatorIdPeriodCompoundUniqueInput - AND?: Prisma.IndicatorPointWhereInput | Prisma.IndicatorPointWhereInput[] - OR?: Prisma.IndicatorPointWhereInput[] - NOT?: Prisma.IndicatorPointWhereInput | Prisma.IndicatorPointWhereInput[] - indicatorId?: Prisma.IntFilter<"IndicatorPoint"> | number - period?: Prisma.StringFilter<"IndicatorPoint"> | string - value?: Prisma.FloatFilter<"IndicatorPoint"> | number - createdAt?: Prisma.DateTimeFilter<"IndicatorPoint"> | Date | string - updatedAt?: Prisma.DateTimeFilter<"IndicatorPoint"> | Date | string - indicator?: Prisma.XOR -}, "id" | "indicatorId_period"> - -export type IndicatorPointOrderByWithAggregationInput = { - id?: Prisma.SortOrder - indicatorId?: Prisma.SortOrder - period?: Prisma.SortOrder - value?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder - _count?: Prisma.IndicatorPointCountOrderByAggregateInput - _avg?: Prisma.IndicatorPointAvgOrderByAggregateInput - _max?: Prisma.IndicatorPointMaxOrderByAggregateInput - _min?: Prisma.IndicatorPointMinOrderByAggregateInput - _sum?: Prisma.IndicatorPointSumOrderByAggregateInput -} - -export type IndicatorPointScalarWhereWithAggregatesInput = { - AND?: Prisma.IndicatorPointScalarWhereWithAggregatesInput | Prisma.IndicatorPointScalarWhereWithAggregatesInput[] - OR?: Prisma.IndicatorPointScalarWhereWithAggregatesInput[] - NOT?: Prisma.IndicatorPointScalarWhereWithAggregatesInput | Prisma.IndicatorPointScalarWhereWithAggregatesInput[] - id?: Prisma.IntWithAggregatesFilter<"IndicatorPoint"> | number - indicatorId?: Prisma.IntWithAggregatesFilter<"IndicatorPoint"> | number - period?: Prisma.StringWithAggregatesFilter<"IndicatorPoint"> | string - value?: Prisma.FloatWithAggregatesFilter<"IndicatorPoint"> | number - createdAt?: Prisma.DateTimeWithAggregatesFilter<"IndicatorPoint"> | Date | string - updatedAt?: Prisma.DateTimeWithAggregatesFilter<"IndicatorPoint"> | Date | string -} - -export type IndicatorPointCreateInput = { - period: string - value: number - createdAt?: Date | string - updatedAt?: Date | string - indicator: Prisma.IndicatorCreateNestedOneWithoutPointsInput -} - -export type IndicatorPointUncheckedCreateInput = { - id?: number - indicatorId: number - period: string - value: number - createdAt?: Date | string - updatedAt?: Date | string -} - -export type IndicatorPointUpdateInput = { - period?: Prisma.StringFieldUpdateOperationsInput | string - value?: Prisma.FloatFieldUpdateOperationsInput | number - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - indicator?: Prisma.IndicatorUpdateOneRequiredWithoutPointsNestedInput -} - -export type IndicatorPointUncheckedUpdateInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - indicatorId?: Prisma.IntFieldUpdateOperationsInput | number - period?: Prisma.StringFieldUpdateOperationsInput | string - value?: Prisma.FloatFieldUpdateOperationsInput | number - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type IndicatorPointCreateManyInput = { - id?: number - indicatorId: number - period: string - value: number - createdAt?: Date | string - updatedAt?: Date | string -} - -export type IndicatorPointUpdateManyMutationInput = { - period?: Prisma.StringFieldUpdateOperationsInput | string - value?: Prisma.FloatFieldUpdateOperationsInput | number - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type IndicatorPointUncheckedUpdateManyInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - indicatorId?: Prisma.IntFieldUpdateOperationsInput | number - period?: Prisma.StringFieldUpdateOperationsInput | string - value?: Prisma.FloatFieldUpdateOperationsInput | number - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type IndicatorPointListRelationFilter = { - every?: Prisma.IndicatorPointWhereInput - some?: Prisma.IndicatorPointWhereInput - none?: Prisma.IndicatorPointWhereInput -} - -export type IndicatorPointOrderByRelationAggregateInput = { - _count?: Prisma.SortOrder -} - -export type IndicatorPointIndicatorIdPeriodCompoundUniqueInput = { - indicatorId: number - period: string -} - -export type IndicatorPointCountOrderByAggregateInput = { - id?: Prisma.SortOrder - indicatorId?: Prisma.SortOrder - period?: Prisma.SortOrder - value?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder -} - -export type IndicatorPointAvgOrderByAggregateInput = { - id?: Prisma.SortOrder - indicatorId?: Prisma.SortOrder - value?: Prisma.SortOrder -} - -export type IndicatorPointMaxOrderByAggregateInput = { - id?: Prisma.SortOrder - indicatorId?: Prisma.SortOrder - period?: Prisma.SortOrder - value?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder -} - -export type IndicatorPointMinOrderByAggregateInput = { - id?: Prisma.SortOrder - indicatorId?: Prisma.SortOrder - period?: Prisma.SortOrder - value?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder -} - -export type IndicatorPointSumOrderByAggregateInput = { - id?: Prisma.SortOrder - indicatorId?: Prisma.SortOrder - value?: Prisma.SortOrder -} - -export type IndicatorPointCreateNestedManyWithoutIndicatorInput = { - create?: Prisma.XOR | Prisma.IndicatorPointCreateWithoutIndicatorInput[] | Prisma.IndicatorPointUncheckedCreateWithoutIndicatorInput[] - connectOrCreate?: Prisma.IndicatorPointCreateOrConnectWithoutIndicatorInput | Prisma.IndicatorPointCreateOrConnectWithoutIndicatorInput[] - createMany?: Prisma.IndicatorPointCreateManyIndicatorInputEnvelope - connect?: Prisma.IndicatorPointWhereUniqueInput | Prisma.IndicatorPointWhereUniqueInput[] -} - -export type IndicatorPointUncheckedCreateNestedManyWithoutIndicatorInput = { - create?: Prisma.XOR | Prisma.IndicatorPointCreateWithoutIndicatorInput[] | Prisma.IndicatorPointUncheckedCreateWithoutIndicatorInput[] - connectOrCreate?: Prisma.IndicatorPointCreateOrConnectWithoutIndicatorInput | Prisma.IndicatorPointCreateOrConnectWithoutIndicatorInput[] - createMany?: Prisma.IndicatorPointCreateManyIndicatorInputEnvelope - connect?: Prisma.IndicatorPointWhereUniqueInput | Prisma.IndicatorPointWhereUniqueInput[] -} - -export type IndicatorPointUpdateManyWithoutIndicatorNestedInput = { - create?: Prisma.XOR | Prisma.IndicatorPointCreateWithoutIndicatorInput[] | Prisma.IndicatorPointUncheckedCreateWithoutIndicatorInput[] - connectOrCreate?: Prisma.IndicatorPointCreateOrConnectWithoutIndicatorInput | Prisma.IndicatorPointCreateOrConnectWithoutIndicatorInput[] - upsert?: Prisma.IndicatorPointUpsertWithWhereUniqueWithoutIndicatorInput | Prisma.IndicatorPointUpsertWithWhereUniqueWithoutIndicatorInput[] - createMany?: Prisma.IndicatorPointCreateManyIndicatorInputEnvelope - set?: Prisma.IndicatorPointWhereUniqueInput | Prisma.IndicatorPointWhereUniqueInput[] - disconnect?: Prisma.IndicatorPointWhereUniqueInput | Prisma.IndicatorPointWhereUniqueInput[] - delete?: Prisma.IndicatorPointWhereUniqueInput | Prisma.IndicatorPointWhereUniqueInput[] - connect?: Prisma.IndicatorPointWhereUniqueInput | Prisma.IndicatorPointWhereUniqueInput[] - update?: Prisma.IndicatorPointUpdateWithWhereUniqueWithoutIndicatorInput | Prisma.IndicatorPointUpdateWithWhereUniqueWithoutIndicatorInput[] - updateMany?: Prisma.IndicatorPointUpdateManyWithWhereWithoutIndicatorInput | Prisma.IndicatorPointUpdateManyWithWhereWithoutIndicatorInput[] - deleteMany?: Prisma.IndicatorPointScalarWhereInput | Prisma.IndicatorPointScalarWhereInput[] -} - -export type IndicatorPointUncheckedUpdateManyWithoutIndicatorNestedInput = { - create?: Prisma.XOR | Prisma.IndicatorPointCreateWithoutIndicatorInput[] | Prisma.IndicatorPointUncheckedCreateWithoutIndicatorInput[] - connectOrCreate?: Prisma.IndicatorPointCreateOrConnectWithoutIndicatorInput | Prisma.IndicatorPointCreateOrConnectWithoutIndicatorInput[] - upsert?: Prisma.IndicatorPointUpsertWithWhereUniqueWithoutIndicatorInput | Prisma.IndicatorPointUpsertWithWhereUniqueWithoutIndicatorInput[] - createMany?: Prisma.IndicatorPointCreateManyIndicatorInputEnvelope - set?: Prisma.IndicatorPointWhereUniqueInput | Prisma.IndicatorPointWhereUniqueInput[] - disconnect?: Prisma.IndicatorPointWhereUniqueInput | Prisma.IndicatorPointWhereUniqueInput[] - delete?: Prisma.IndicatorPointWhereUniqueInput | Prisma.IndicatorPointWhereUniqueInput[] - connect?: Prisma.IndicatorPointWhereUniqueInput | Prisma.IndicatorPointWhereUniqueInput[] - update?: Prisma.IndicatorPointUpdateWithWhereUniqueWithoutIndicatorInput | Prisma.IndicatorPointUpdateWithWhereUniqueWithoutIndicatorInput[] - updateMany?: Prisma.IndicatorPointUpdateManyWithWhereWithoutIndicatorInput | Prisma.IndicatorPointUpdateManyWithWhereWithoutIndicatorInput[] - deleteMany?: Prisma.IndicatorPointScalarWhereInput | Prisma.IndicatorPointScalarWhereInput[] -} - -export type FloatFieldUpdateOperationsInput = { - set?: number - increment?: number - decrement?: number - multiply?: number - divide?: number -} - -export type IndicatorPointCreateWithoutIndicatorInput = { - period: string - value: number - createdAt?: Date | string - updatedAt?: Date | string -} - -export type IndicatorPointUncheckedCreateWithoutIndicatorInput = { - id?: number - period: string - value: number - createdAt?: Date | string - updatedAt?: Date | string -} - -export type IndicatorPointCreateOrConnectWithoutIndicatorInput = { - where: Prisma.IndicatorPointWhereUniqueInput - create: Prisma.XOR -} - -export type IndicatorPointCreateManyIndicatorInputEnvelope = { - data: Prisma.IndicatorPointCreateManyIndicatorInput | Prisma.IndicatorPointCreateManyIndicatorInput[] - skipDuplicates?: boolean -} - -export type IndicatorPointUpsertWithWhereUniqueWithoutIndicatorInput = { - where: Prisma.IndicatorPointWhereUniqueInput - update: Prisma.XOR - create: Prisma.XOR -} - -export type IndicatorPointUpdateWithWhereUniqueWithoutIndicatorInput = { - where: Prisma.IndicatorPointWhereUniqueInput - data: Prisma.XOR -} - -export type IndicatorPointUpdateManyWithWhereWithoutIndicatorInput = { - where: Prisma.IndicatorPointScalarWhereInput - data: Prisma.XOR -} - -export type IndicatorPointScalarWhereInput = { - AND?: Prisma.IndicatorPointScalarWhereInput | Prisma.IndicatorPointScalarWhereInput[] - OR?: Prisma.IndicatorPointScalarWhereInput[] - NOT?: Prisma.IndicatorPointScalarWhereInput | Prisma.IndicatorPointScalarWhereInput[] - id?: Prisma.IntFilter<"IndicatorPoint"> | number - indicatorId?: Prisma.IntFilter<"IndicatorPoint"> | number - period?: Prisma.StringFilter<"IndicatorPoint"> | string - value?: Prisma.FloatFilter<"IndicatorPoint"> | number - createdAt?: Prisma.DateTimeFilter<"IndicatorPoint"> | Date | string - updatedAt?: Prisma.DateTimeFilter<"IndicatorPoint"> | Date | string -} - -export type IndicatorPointCreateManyIndicatorInput = { - id?: number - period: string - value: number - createdAt?: Date | string - updatedAt?: Date | string -} - -export type IndicatorPointUpdateWithoutIndicatorInput = { - period?: Prisma.StringFieldUpdateOperationsInput | string - value?: Prisma.FloatFieldUpdateOperationsInput | number - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type IndicatorPointUncheckedUpdateWithoutIndicatorInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - period?: Prisma.StringFieldUpdateOperationsInput | string - value?: Prisma.FloatFieldUpdateOperationsInput | number - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type IndicatorPointUncheckedUpdateManyWithoutIndicatorInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - period?: Prisma.StringFieldUpdateOperationsInput | string - value?: Prisma.FloatFieldUpdateOperationsInput | number - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - - - -export type IndicatorPointSelect = runtime.Types.Extensions.GetSelect<{ - id?: boolean - indicatorId?: boolean - period?: boolean - value?: boolean - createdAt?: boolean - updatedAt?: boolean - indicator?: boolean | Prisma.IndicatorDefaultArgs -}, ExtArgs["result"]["indicatorPoint"]> - -export type IndicatorPointSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ - id?: boolean - indicatorId?: boolean - period?: boolean - value?: boolean - createdAt?: boolean - updatedAt?: boolean - indicator?: boolean | Prisma.IndicatorDefaultArgs -}, ExtArgs["result"]["indicatorPoint"]> - -export type IndicatorPointSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ - id?: boolean - indicatorId?: boolean - period?: boolean - value?: boolean - createdAt?: boolean - updatedAt?: boolean - indicator?: boolean | Prisma.IndicatorDefaultArgs -}, ExtArgs["result"]["indicatorPoint"]> - -export type IndicatorPointSelectScalar = { - id?: boolean - indicatorId?: boolean - period?: boolean - value?: boolean - createdAt?: boolean - updatedAt?: boolean -} - -export type IndicatorPointOmit = runtime.Types.Extensions.GetOmit<"id" | "indicatorId" | "period" | "value" | "createdAt" | "updatedAt", ExtArgs["result"]["indicatorPoint"]> -export type IndicatorPointInclude = { - indicator?: boolean | Prisma.IndicatorDefaultArgs -} -export type IndicatorPointIncludeCreateManyAndReturn = { - indicator?: boolean | Prisma.IndicatorDefaultArgs -} -export type IndicatorPointIncludeUpdateManyAndReturn = { - indicator?: boolean | Prisma.IndicatorDefaultArgs -} - -export type $IndicatorPointPayload = { - name: "IndicatorPoint" - objects: { - indicator: Prisma.$IndicatorPayload - } - scalars: runtime.Types.Extensions.GetPayloadResult<{ - id: number - indicatorId: number - period: string - value: number - createdAt: Date - updatedAt: Date - }, ExtArgs["result"]["indicatorPoint"]> - composites: {} -} - -export type IndicatorPointGetPayload = runtime.Types.Result.GetResult - -export type IndicatorPointCountArgs = - Omit & { - select?: IndicatorPointCountAggregateInputType | true - } - -export interface IndicatorPointDelegate { - [K: symbol]: { types: Prisma.TypeMap['model']['IndicatorPoint'], meta: { name: 'IndicatorPoint' } } - /** - * Find zero or one IndicatorPoint that matches the filter. - * @param {IndicatorPointFindUniqueArgs} args - Arguments to find a IndicatorPoint - * @example - * // Get one IndicatorPoint - * const indicatorPoint = await prisma.indicatorPoint.findUnique({ - * where: { - * // ... provide filter here - * } - * }) - */ - findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorPointClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> - - /** - * Find one IndicatorPoint that matches the filter or throw an error with `error.code='P2025'` - * if no matches were found. - * @param {IndicatorPointFindUniqueOrThrowArgs} args - Arguments to find a IndicatorPoint - * @example - * // Get one IndicatorPoint - * const indicatorPoint = await prisma.indicatorPoint.findUniqueOrThrow({ - * where: { - * // ... provide filter here - * } - * }) - */ - findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorPointClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Find the first IndicatorPoint that matches the filter. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorPointFindFirstArgs} args - Arguments to find a IndicatorPoint - * @example - * // Get one IndicatorPoint - * const indicatorPoint = await prisma.indicatorPoint.findFirst({ - * where: { - * // ... provide filter here - * } - * }) - */ - findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__IndicatorPointClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> - - /** - * Find the first IndicatorPoint that matches the filter or - * throw `PrismaKnownClientError` with `P2025` code if no matches were found. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorPointFindFirstOrThrowArgs} args - Arguments to find a IndicatorPoint - * @example - * // Get one IndicatorPoint - * const indicatorPoint = await prisma.indicatorPoint.findFirstOrThrow({ - * where: { - * // ... provide filter here - * } - * }) - */ - findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__IndicatorPointClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Find zero or more IndicatorPoints that matches the filter. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorPointFindManyArgs} args - Arguments to filter and select certain fields only. - * @example - * // Get all IndicatorPoints - * const indicatorPoints = await prisma.indicatorPoint.findMany() - * - * // Get first 10 IndicatorPoints - * const indicatorPoints = await prisma.indicatorPoint.findMany({ take: 10 }) - * - * // Only select the `id` - * const indicatorPointWithIdOnly = await prisma.indicatorPoint.findMany({ select: { id: true } }) - * - */ - findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> - - /** - * Create a IndicatorPoint. - * @param {IndicatorPointCreateArgs} args - Arguments to create a IndicatorPoint. - * @example - * // Create one IndicatorPoint - * const IndicatorPoint = await prisma.indicatorPoint.create({ - * data: { - * // ... data to create a IndicatorPoint - * } - * }) - * - */ - create(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorPointClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Create many IndicatorPoints. - * @param {IndicatorPointCreateManyArgs} args - Arguments to create many IndicatorPoints. - * @example - * // Create many IndicatorPoints - * const indicatorPoint = await prisma.indicatorPoint.createMany({ - * data: [ - * // ... provide data here - * ] - * }) - * - */ - createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Create many IndicatorPoints and returns the data saved in the database. - * @param {IndicatorPointCreateManyAndReturnArgs} args - Arguments to create many IndicatorPoints. - * @example - * // Create many IndicatorPoints - * const indicatorPoint = await prisma.indicatorPoint.createManyAndReturn({ - * data: [ - * // ... provide data here - * ] - * }) - * - * // Create many IndicatorPoints and only return the `id` - * const indicatorPointWithIdOnly = await prisma.indicatorPoint.createManyAndReturn({ - * select: { id: true }, - * data: [ - * // ... provide data here - * ] - * }) - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * - */ - createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> - - /** - * Delete a IndicatorPoint. - * @param {IndicatorPointDeleteArgs} args - Arguments to delete one IndicatorPoint. - * @example - * // Delete one IndicatorPoint - * const IndicatorPoint = await prisma.indicatorPoint.delete({ - * where: { - * // ... filter to delete one IndicatorPoint - * } - * }) - * - */ - delete(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorPointClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Update one IndicatorPoint. - * @param {IndicatorPointUpdateArgs} args - Arguments to update one IndicatorPoint. - * @example - * // Update one IndicatorPoint - * const indicatorPoint = await prisma.indicatorPoint.update({ - * where: { - * // ... provide filter here - * }, - * data: { - * // ... provide data here - * } - * }) - * - */ - update(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorPointClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Delete zero or more IndicatorPoints. - * @param {IndicatorPointDeleteManyArgs} args - Arguments to filter IndicatorPoints to delete. - * @example - * // Delete a few IndicatorPoints - * const { count } = await prisma.indicatorPoint.deleteMany({ - * where: { - * // ... provide filter here - * } - * }) - * - */ - deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Update zero or more IndicatorPoints. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorPointUpdateManyArgs} args - Arguments to update one or more rows. - * @example - * // Update many IndicatorPoints - * const indicatorPoint = await prisma.indicatorPoint.updateMany({ - * where: { - * // ... provide filter here - * }, - * data: { - * // ... provide data here - * } - * }) - * - */ - updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Update zero or more IndicatorPoints and returns the data updated in the database. - * @param {IndicatorPointUpdateManyAndReturnArgs} args - Arguments to update many IndicatorPoints. - * @example - * // Update many IndicatorPoints - * const indicatorPoint = await prisma.indicatorPoint.updateManyAndReturn({ - * where: { - * // ... provide filter here - * }, - * data: [ - * // ... provide data here - * ] - * }) - * - * // Update zero or more IndicatorPoints and only return the `id` - * const indicatorPointWithIdOnly = await prisma.indicatorPoint.updateManyAndReturn({ - * select: { id: true }, - * where: { - * // ... provide filter here - * }, - * data: [ - * // ... provide data here - * ] - * }) - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * - */ - updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> - - /** - * Create or update one IndicatorPoint. - * @param {IndicatorPointUpsertArgs} args - Arguments to update or create a IndicatorPoint. - * @example - * // Update or create a IndicatorPoint - * const indicatorPoint = await prisma.indicatorPoint.upsert({ - * create: { - * // ... data to create a IndicatorPoint - * }, - * update: { - * // ... in case it already exists, update - * }, - * where: { - * // ... the filter for the IndicatorPoint we want to update - * } - * }) - */ - upsert(args: Prisma.SelectSubset>): Prisma.Prisma__IndicatorPointClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - - /** - * Count the number of IndicatorPoints. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorPointCountArgs} args - Arguments to filter IndicatorPoints to count. - * @example - * // Count the number of IndicatorPoints - * const count = await prisma.indicatorPoint.count({ - * where: { - * // ... the filter for the IndicatorPoints we want to count - * } - * }) - **/ - count( - args?: Prisma.Subset, - ): Prisma.PrismaPromise< - T extends runtime.Types.Utils.Record<'select', any> - ? T['select'] extends true - ? number - : Prisma.GetScalarType - : number - > - - /** - * Allows you to perform aggregations operations on a IndicatorPoint. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorPointAggregateArgs} args - Select which aggregations you would like to apply and on what fields. - * @example - * // Ordered by age ascending - * // Where email contains prisma.io - * // Limited to the 10 users - * const aggregations = await prisma.user.aggregate({ - * _avg: { - * age: true, - * }, - * where: { - * email: { - * contains: "prisma.io", - * }, - * }, - * orderBy: { - * age: "asc", - * }, - * take: 10, - * }) - **/ - aggregate(args: Prisma.Subset): Prisma.PrismaPromise> - - /** - * Group by IndicatorPoint. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {IndicatorPointGroupByArgs} args - Group by arguments. - * @example - * // Group by city, order by createdAt, get count - * const result = await prisma.user.groupBy({ - * by: ['city', 'createdAt'], - * orderBy: { - * createdAt: true - * }, - * _count: { - * _all: true - * }, - * }) - * - **/ - groupBy< - T extends IndicatorPointGroupByArgs, - HasSelectOrTake extends Prisma.Or< - Prisma.Extends<'skip', Prisma.Keys>, - Prisma.Extends<'take', Prisma.Keys> - >, - OrderByArg extends Prisma.True extends HasSelectOrTake - ? { orderBy: IndicatorPointGroupByArgs['orderBy'] } - : { orderBy?: IndicatorPointGroupByArgs['orderBy'] }, - OrderFields extends Prisma.ExcludeUnderscoreKeys>>, - ByFields extends Prisma.MaybeTupleToUnion, - ByValid extends Prisma.Has, - HavingFields extends Prisma.GetHavingFields, - HavingValid extends Prisma.Has, - ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, - InputErrors extends ByEmpty extends Prisma.True - ? `Error: "by" must not be empty.` - : HavingValid extends Prisma.False - ? { - [P in HavingFields]: P extends ByFields - ? never - : P extends string - ? `Error: Field "${P}" used in "having" needs to be provided in "by".` - : [ - Error, - 'Field ', - P, - ` in "having" needs to be provided in "by"`, - ] - }[HavingFields] - : 'take' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - : 'Error: If you provide "take", you also need to provide "orderBy"' - : 'skip' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - : 'Error: If you provide "skip", you also need to provide "orderBy"' - : ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetIndicatorPointGroupByPayload : Prisma.PrismaPromise -/** - * Fields of the IndicatorPoint model - */ -readonly fields: IndicatorPointFieldRefs; -} - -/** - * The delegate class that acts as a "Promise-like" for IndicatorPoint. - * Why is this prefixed with `Prisma__`? - * Because we want to prevent naming conflicts as mentioned in - * https://github.com/prisma/prisma-client-js/issues/707 - */ -export interface Prisma__IndicatorPointClient extends Prisma.PrismaPromise { - readonly [Symbol.toStringTag]: "PrismaPromise" - indicator = {}>(args?: Prisma.Subset>): Prisma.Prisma__IndicatorClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise - /** - * Attaches a callback for only the rejection of the Promise. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of the callback. - */ - catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise - /** - * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The - * resolved value cannot be modified from the callback. - * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). - * @returns A Promise for the completion of the callback. - */ - finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise -} - - - - -/** - * Fields of the IndicatorPoint model - */ -export interface IndicatorPointFieldRefs { - readonly id: Prisma.FieldRef<"IndicatorPoint", 'Int'> - readonly indicatorId: Prisma.FieldRef<"IndicatorPoint", 'Int'> - readonly period: Prisma.FieldRef<"IndicatorPoint", 'String'> - readonly value: Prisma.FieldRef<"IndicatorPoint", 'Float'> - readonly createdAt: Prisma.FieldRef<"IndicatorPoint", 'DateTime'> - readonly updatedAt: Prisma.FieldRef<"IndicatorPoint", 'DateTime'> -} - - -// Custom InputTypes -/** - * IndicatorPoint findUnique - */ -export type IndicatorPointFindUniqueArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null - /** - * Filter, which IndicatorPoint to fetch. - */ - where: Prisma.IndicatorPointWhereUniqueInput -} - -/** - * IndicatorPoint findUniqueOrThrow - */ -export type IndicatorPointFindUniqueOrThrowArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null - /** - * Filter, which IndicatorPoint to fetch. - */ - where: Prisma.IndicatorPointWhereUniqueInput -} - -/** - * IndicatorPoint findFirst - */ -export type IndicatorPointFindFirstArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null - /** - * Filter, which IndicatorPoint to fetch. - */ - where?: Prisma.IndicatorPointWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of IndicatorPoints to fetch. - */ - orderBy?: Prisma.IndicatorPointOrderByWithRelationInput | Prisma.IndicatorPointOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for searching for IndicatorPoints. - */ - cursor?: Prisma.IndicatorPointWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` IndicatorPoints from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` IndicatorPoints. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of IndicatorPoints. - */ - distinct?: Prisma.IndicatorPointScalarFieldEnum | Prisma.IndicatorPointScalarFieldEnum[] -} - -/** - * IndicatorPoint findFirstOrThrow - */ -export type IndicatorPointFindFirstOrThrowArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null - /** - * Filter, which IndicatorPoint to fetch. - */ - where?: Prisma.IndicatorPointWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of IndicatorPoints to fetch. - */ - orderBy?: Prisma.IndicatorPointOrderByWithRelationInput | Prisma.IndicatorPointOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for searching for IndicatorPoints. - */ - cursor?: Prisma.IndicatorPointWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` IndicatorPoints from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` IndicatorPoints. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of IndicatorPoints. - */ - distinct?: Prisma.IndicatorPointScalarFieldEnum | Prisma.IndicatorPointScalarFieldEnum[] -} - -/** - * IndicatorPoint findMany - */ -export type IndicatorPointFindManyArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null - /** - * Filter, which IndicatorPoints to fetch. - */ - where?: Prisma.IndicatorPointWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of IndicatorPoints to fetch. - */ - orderBy?: Prisma.IndicatorPointOrderByWithRelationInput | Prisma.IndicatorPointOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for listing IndicatorPoints. - */ - cursor?: Prisma.IndicatorPointWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` IndicatorPoints from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` IndicatorPoints. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of IndicatorPoints. - */ - distinct?: Prisma.IndicatorPointScalarFieldEnum | Prisma.IndicatorPointScalarFieldEnum[] -} - -/** - * IndicatorPoint create - */ -export type IndicatorPointCreateArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null - /** - * The data needed to create a IndicatorPoint. - */ - data: Prisma.XOR -} - -/** - * IndicatorPoint createMany - */ -export type IndicatorPointCreateManyArgs = { - /** - * The data used to create many IndicatorPoints. - */ - data: Prisma.IndicatorPointCreateManyInput | Prisma.IndicatorPointCreateManyInput[] - skipDuplicates?: boolean -} - -/** - * IndicatorPoint createManyAndReturn - */ -export type IndicatorPointCreateManyAndReturnArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelectCreateManyAndReturn | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * The data used to create many IndicatorPoints. - */ - data: Prisma.IndicatorPointCreateManyInput | Prisma.IndicatorPointCreateManyInput[] - skipDuplicates?: boolean - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointIncludeCreateManyAndReturn | null -} - -/** - * IndicatorPoint update - */ -export type IndicatorPointUpdateArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null - /** - * The data needed to update a IndicatorPoint. - */ - data: Prisma.XOR - /** - * Choose, which IndicatorPoint to update. - */ - where: Prisma.IndicatorPointWhereUniqueInput -} - -/** - * IndicatorPoint updateMany - */ -export type IndicatorPointUpdateManyArgs = { - /** - * The data used to update IndicatorPoints. - */ - data: Prisma.XOR - /** - * Filter which IndicatorPoints to update - */ - where?: Prisma.IndicatorPointWhereInput - /** - * Limit how many IndicatorPoints to update. - */ - limit?: number -} - -/** - * IndicatorPoint updateManyAndReturn - */ -export type IndicatorPointUpdateManyAndReturnArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelectUpdateManyAndReturn | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * The data used to update IndicatorPoints. - */ - data: Prisma.XOR - /** - * Filter which IndicatorPoints to update - */ - where?: Prisma.IndicatorPointWhereInput - /** - * Limit how many IndicatorPoints to update. - */ - limit?: number - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointIncludeUpdateManyAndReturn | null -} - -/** - * IndicatorPoint upsert - */ -export type IndicatorPointUpsertArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null - /** - * The filter to search for the IndicatorPoint to update in case it exists. - */ - where: Prisma.IndicatorPointWhereUniqueInput - /** - * In case the IndicatorPoint found by the `where` argument doesn't exist, create a new IndicatorPoint with this data. - */ - create: Prisma.XOR - /** - * In case the IndicatorPoint was found with the provided `where` argument, update it with this data. - */ - update: Prisma.XOR -} - -/** - * IndicatorPoint delete - */ -export type IndicatorPointDeleteArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null - /** - * Filter which IndicatorPoint to delete. - */ - where: Prisma.IndicatorPointWhereUniqueInput -} - -/** - * IndicatorPoint deleteMany - */ -export type IndicatorPointDeleteManyArgs = { - /** - * Filter which IndicatorPoints to delete - */ - where?: Prisma.IndicatorPointWhereInput - /** - * Limit how many IndicatorPoints to delete. - */ - limit?: number -} - -/** - * IndicatorPoint without action - */ -export type IndicatorPointDefaultArgs = { - /** - * Select specific fields to fetch from the IndicatorPoint - */ - select?: Prisma.IndicatorPointSelect | null - /** - * Omit specific fields from the IndicatorPoint - */ - omit?: Prisma.IndicatorPointOmit | null - /** - * Choose, which related nodes to fetch as well - */ - include?: Prisma.IndicatorPointInclude | null -} diff --git a/generated/prisma/models/MihomeDevice.ts b/generated/prisma/models/MihomeDevice.ts index 1bc75d8..d48d193 100644 --- a/generated/prisma/models/MihomeDevice.ts +++ b/generated/prisma/models/MihomeDevice.ts @@ -737,6 +737,10 @@ export type NullableDateTimeFieldUpdateOperationsInput = { set?: Date | string | null } +export type BoolFieldUpdateOperationsInput = { + set?: boolean +} + export type MihomeDeviceCreateNestedOneWithoutSamplesInput = { create?: Prisma.XOR connectOrCreate?: Prisma.MihomeDeviceCreateOrConnectWithoutSamplesInput diff --git a/generated/prisma/models/OilPriceHistory.ts b/generated/prisma/models/OilPriceHistory.ts deleted file mode 100644 index c3d2204..0000000 --- a/generated/prisma/models/OilPriceHistory.ts +++ /dev/null @@ -1,1259 +0,0 @@ - -/* !!! This is code generated by Prisma. Do not edit directly. !!! */ -/* eslint-disable */ -// biome-ignore-all lint: generated file -// @ts-nocheck -/* - * This file exports the `OilPriceHistory` model and its related types. - * - * 🟢 You can import this file directly. - */ -import type * as runtime from "@prisma/client/runtime/client" -import type * as $Enums from "../enums.ts" -import type * as Prisma from "../internal/prismaNamespace.ts" - -/** - * Model OilPriceHistory - * - */ -export type OilPriceHistoryModel = runtime.Types.Result.DefaultSelection - -export type AggregateOilPriceHistory = { - _count: OilPriceHistoryCountAggregateOutputType | null - _avg: OilPriceHistoryAvgAggregateOutputType | null - _sum: OilPriceHistorySumAggregateOutputType | null - _min: OilPriceHistoryMinAggregateOutputType | null - _max: OilPriceHistoryMaxAggregateOutputType | null -} - -export type OilPriceHistoryAvgAggregateOutputType = { - id: number | null - price: number | null -} - -export type OilPriceHistorySumAggregateOutputType = { - id: number | null - price: number | null -} - -export type OilPriceHistoryMinAggregateOutputType = { - id: number | null - date: Date | null - province: string | null - fuel: string | null - price: number | null - source: string | null - createdAt: Date | null -} - -export type OilPriceHistoryMaxAggregateOutputType = { - id: number | null - date: Date | null - province: string | null - fuel: string | null - price: number | null - source: string | null - createdAt: Date | null -} - -export type OilPriceHistoryCountAggregateOutputType = { - id: number - date: number - province: number - fuel: number - price: number - source: number - createdAt: number - _all: number -} - - -export type OilPriceHistoryAvgAggregateInputType = { - id?: true - price?: true -} - -export type OilPriceHistorySumAggregateInputType = { - id?: true - price?: true -} - -export type OilPriceHistoryMinAggregateInputType = { - id?: true - date?: true - province?: true - fuel?: true - price?: true - source?: true - createdAt?: true -} - -export type OilPriceHistoryMaxAggregateInputType = { - id?: true - date?: true - province?: true - fuel?: true - price?: true - source?: true - createdAt?: true -} - -export type OilPriceHistoryCountAggregateInputType = { - id?: true - date?: true - province?: true - fuel?: true - price?: true - source?: true - createdAt?: true - _all?: true -} - -export type OilPriceHistoryAggregateArgs = { - /** - * Filter which OilPriceHistory to aggregate. - */ - where?: Prisma.OilPriceHistoryWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of OilPriceHistories to fetch. - */ - orderBy?: Prisma.OilPriceHistoryOrderByWithRelationInput | Prisma.OilPriceHistoryOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the start position - */ - cursor?: Prisma.OilPriceHistoryWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` OilPriceHistories from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` OilPriceHistories. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Count returned OilPriceHistories - **/ - _count?: true | OilPriceHistoryCountAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to average - **/ - _avg?: OilPriceHistoryAvgAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to sum - **/ - _sum?: OilPriceHistorySumAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to find the minimum value - **/ - _min?: OilPriceHistoryMinAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to find the maximum value - **/ - _max?: OilPriceHistoryMaxAggregateInputType -} - -export type GetOilPriceHistoryAggregateType = { - [P in keyof T & keyof AggregateOilPriceHistory]: P extends '_count' | 'count' - ? T[P] extends true - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType -} - - - - -export type OilPriceHistoryGroupByArgs = { - where?: Prisma.OilPriceHistoryWhereInput - orderBy?: Prisma.OilPriceHistoryOrderByWithAggregationInput | Prisma.OilPriceHistoryOrderByWithAggregationInput[] - by: Prisma.OilPriceHistoryScalarFieldEnum[] | Prisma.OilPriceHistoryScalarFieldEnum - having?: Prisma.OilPriceHistoryScalarWhereWithAggregatesInput - take?: number - skip?: number - _count?: OilPriceHistoryCountAggregateInputType | true - _avg?: OilPriceHistoryAvgAggregateInputType - _sum?: OilPriceHistorySumAggregateInputType - _min?: OilPriceHistoryMinAggregateInputType - _max?: OilPriceHistoryMaxAggregateInputType -} - -export type OilPriceHistoryGroupByOutputType = { - id: number - date: Date - province: string - fuel: string - price: number - source: string - createdAt: Date - _count: OilPriceHistoryCountAggregateOutputType | null - _avg: OilPriceHistoryAvgAggregateOutputType | null - _sum: OilPriceHistorySumAggregateOutputType | null - _min: OilPriceHistoryMinAggregateOutputType | null - _max: OilPriceHistoryMaxAggregateOutputType | null -} - -export type GetOilPriceHistoryGroupByPayload = Prisma.PrismaPromise< - Array< - Prisma.PickEnumerable & - { - [P in ((keyof T) & (keyof OilPriceHistoryGroupByOutputType))]: P extends '_count' - ? T[P] extends boolean - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType - } - > - > - - - -export type OilPriceHistoryWhereInput = { - AND?: Prisma.OilPriceHistoryWhereInput | Prisma.OilPriceHistoryWhereInput[] - OR?: Prisma.OilPriceHistoryWhereInput[] - NOT?: Prisma.OilPriceHistoryWhereInput | Prisma.OilPriceHistoryWhereInput[] - id?: Prisma.IntFilter<"OilPriceHistory"> | number - date?: Prisma.DateTimeFilter<"OilPriceHistory"> | Date | string - province?: Prisma.StringFilter<"OilPriceHistory"> | string - fuel?: Prisma.StringFilter<"OilPriceHistory"> | string - price?: Prisma.FloatFilter<"OilPriceHistory"> | number - source?: Prisma.StringFilter<"OilPriceHistory"> | string - createdAt?: Prisma.DateTimeFilter<"OilPriceHistory"> | Date | string -} - -export type OilPriceHistoryOrderByWithRelationInput = { - id?: Prisma.SortOrder - date?: Prisma.SortOrder - province?: Prisma.SortOrder - fuel?: Prisma.SortOrder - price?: Prisma.SortOrder - source?: Prisma.SortOrder - createdAt?: Prisma.SortOrder -} - -export type OilPriceHistoryWhereUniqueInput = Prisma.AtLeast<{ - id?: number - date_province_fuel?: Prisma.OilPriceHistoryDateProvinceFuelCompoundUniqueInput - AND?: Prisma.OilPriceHistoryWhereInput | Prisma.OilPriceHistoryWhereInput[] - OR?: Prisma.OilPriceHistoryWhereInput[] - NOT?: Prisma.OilPriceHistoryWhereInput | Prisma.OilPriceHistoryWhereInput[] - date?: Prisma.DateTimeFilter<"OilPriceHistory"> | Date | string - province?: Prisma.StringFilter<"OilPriceHistory"> | string - fuel?: Prisma.StringFilter<"OilPriceHistory"> | string - price?: Prisma.FloatFilter<"OilPriceHistory"> | number - source?: Prisma.StringFilter<"OilPriceHistory"> | string - createdAt?: Prisma.DateTimeFilter<"OilPriceHistory"> | Date | string -}, "id" | "date_province_fuel"> - -export type OilPriceHistoryOrderByWithAggregationInput = { - id?: Prisma.SortOrder - date?: Prisma.SortOrder - province?: Prisma.SortOrder - fuel?: Prisma.SortOrder - price?: Prisma.SortOrder - source?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - _count?: Prisma.OilPriceHistoryCountOrderByAggregateInput - _avg?: Prisma.OilPriceHistoryAvgOrderByAggregateInput - _max?: Prisma.OilPriceHistoryMaxOrderByAggregateInput - _min?: Prisma.OilPriceHistoryMinOrderByAggregateInput - _sum?: Prisma.OilPriceHistorySumOrderByAggregateInput -} - -export type OilPriceHistoryScalarWhereWithAggregatesInput = { - AND?: Prisma.OilPriceHistoryScalarWhereWithAggregatesInput | Prisma.OilPriceHistoryScalarWhereWithAggregatesInput[] - OR?: Prisma.OilPriceHistoryScalarWhereWithAggregatesInput[] - NOT?: Prisma.OilPriceHistoryScalarWhereWithAggregatesInput | Prisma.OilPriceHistoryScalarWhereWithAggregatesInput[] - id?: Prisma.IntWithAggregatesFilter<"OilPriceHistory"> | number - date?: Prisma.DateTimeWithAggregatesFilter<"OilPriceHistory"> | Date | string - province?: Prisma.StringWithAggregatesFilter<"OilPriceHistory"> | string - fuel?: Prisma.StringWithAggregatesFilter<"OilPriceHistory"> | string - price?: Prisma.FloatWithAggregatesFilter<"OilPriceHistory"> | number - source?: Prisma.StringWithAggregatesFilter<"OilPriceHistory"> | string - createdAt?: Prisma.DateTimeWithAggregatesFilter<"OilPriceHistory"> | Date | string -} - -export type OilPriceHistoryCreateInput = { - date: Date | string - province: string - fuel: string - price: number - source?: string - createdAt?: Date | string -} - -export type OilPriceHistoryUncheckedCreateInput = { - id?: number - date: Date | string - province: string - fuel: string - price: number - source?: string - createdAt?: Date | string -} - -export type OilPriceHistoryUpdateInput = { - date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - province?: Prisma.StringFieldUpdateOperationsInput | string - fuel?: Prisma.StringFieldUpdateOperationsInput | string - price?: Prisma.FloatFieldUpdateOperationsInput | number - source?: Prisma.StringFieldUpdateOperationsInput | string - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type OilPriceHistoryUncheckedUpdateInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - province?: Prisma.StringFieldUpdateOperationsInput | string - fuel?: Prisma.StringFieldUpdateOperationsInput | string - price?: Prisma.FloatFieldUpdateOperationsInput | number - source?: Prisma.StringFieldUpdateOperationsInput | string - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type OilPriceHistoryCreateManyInput = { - id?: number - date: Date | string - province: string - fuel: string - price: number - source?: string - createdAt?: Date | string -} - -export type OilPriceHistoryUpdateManyMutationInput = { - date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - province?: Prisma.StringFieldUpdateOperationsInput | string - fuel?: Prisma.StringFieldUpdateOperationsInput | string - price?: Prisma.FloatFieldUpdateOperationsInput | number - source?: Prisma.StringFieldUpdateOperationsInput | string - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type OilPriceHistoryUncheckedUpdateManyInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - province?: Prisma.StringFieldUpdateOperationsInput | string - fuel?: Prisma.StringFieldUpdateOperationsInput | string - price?: Prisma.FloatFieldUpdateOperationsInput | number - source?: Prisma.StringFieldUpdateOperationsInput | string - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type OilPriceHistoryDateProvinceFuelCompoundUniqueInput = { - date: Date | string - province: string - fuel: string -} - -export type OilPriceHistoryCountOrderByAggregateInput = { - id?: Prisma.SortOrder - date?: Prisma.SortOrder - province?: Prisma.SortOrder - fuel?: Prisma.SortOrder - price?: Prisma.SortOrder - source?: Prisma.SortOrder - createdAt?: Prisma.SortOrder -} - -export type OilPriceHistoryAvgOrderByAggregateInput = { - id?: Prisma.SortOrder - price?: Prisma.SortOrder -} - -export type OilPriceHistoryMaxOrderByAggregateInput = { - id?: Prisma.SortOrder - date?: Prisma.SortOrder - province?: Prisma.SortOrder - fuel?: Prisma.SortOrder - price?: Prisma.SortOrder - source?: Prisma.SortOrder - createdAt?: Prisma.SortOrder -} - -export type OilPriceHistoryMinOrderByAggregateInput = { - id?: Prisma.SortOrder - date?: Prisma.SortOrder - province?: Prisma.SortOrder - fuel?: Prisma.SortOrder - price?: Prisma.SortOrder - source?: Prisma.SortOrder - createdAt?: Prisma.SortOrder -} - -export type OilPriceHistorySumOrderByAggregateInput = { - id?: Prisma.SortOrder - price?: Prisma.SortOrder -} - - - -export type OilPriceHistorySelect = runtime.Types.Extensions.GetSelect<{ - id?: boolean - date?: boolean - province?: boolean - fuel?: boolean - price?: boolean - source?: boolean - createdAt?: boolean -}, ExtArgs["result"]["oilPriceHistory"]> - -export type OilPriceHistorySelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ - id?: boolean - date?: boolean - province?: boolean - fuel?: boolean - price?: boolean - source?: boolean - createdAt?: boolean -}, ExtArgs["result"]["oilPriceHistory"]> - -export type OilPriceHistorySelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ - id?: boolean - date?: boolean - province?: boolean - fuel?: boolean - price?: boolean - source?: boolean - createdAt?: boolean -}, ExtArgs["result"]["oilPriceHistory"]> - -export type OilPriceHistorySelectScalar = { - id?: boolean - date?: boolean - province?: boolean - fuel?: boolean - price?: boolean - source?: boolean - createdAt?: boolean -} - -export type OilPriceHistoryOmit = runtime.Types.Extensions.GetOmit<"id" | "date" | "province" | "fuel" | "price" | "source" | "createdAt", ExtArgs["result"]["oilPriceHistory"]> - -export type $OilPriceHistoryPayload = { - name: "OilPriceHistory" - objects: {} - scalars: runtime.Types.Extensions.GetPayloadResult<{ - id: number - date: Date - province: string - fuel: string - price: number - source: string - createdAt: Date - }, ExtArgs["result"]["oilPriceHistory"]> - composites: {} -} - -export type OilPriceHistoryGetPayload = runtime.Types.Result.GetResult - -export type OilPriceHistoryCountArgs = - Omit & { - select?: OilPriceHistoryCountAggregateInputType | true - } - -export interface OilPriceHistoryDelegate { - [K: symbol]: { types: Prisma.TypeMap['model']['OilPriceHistory'], meta: { name: 'OilPriceHistory' } } - /** - * Find zero or one OilPriceHistory that matches the filter. - * @param {OilPriceHistoryFindUniqueArgs} args - Arguments to find a OilPriceHistory - * @example - * // Get one OilPriceHistory - * const oilPriceHistory = await prisma.oilPriceHistory.findUnique({ - * where: { - * // ... provide filter here - * } - * }) - */ - findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__OilPriceHistoryClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> - - /** - * Find one OilPriceHistory that matches the filter or throw an error with `error.code='P2025'` - * if no matches were found. - * @param {OilPriceHistoryFindUniqueOrThrowArgs} args - Arguments to find a OilPriceHistory - * @example - * // Get one OilPriceHistory - * const oilPriceHistory = await prisma.oilPriceHistory.findUniqueOrThrow({ - * where: { - * // ... provide filter here - * } - * }) - */ - findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__OilPriceHistoryClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Find the first OilPriceHistory that matches the filter. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {OilPriceHistoryFindFirstArgs} args - Arguments to find a OilPriceHistory - * @example - * // Get one OilPriceHistory - * const oilPriceHistory = await prisma.oilPriceHistory.findFirst({ - * where: { - * // ... provide filter here - * } - * }) - */ - findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__OilPriceHistoryClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> - - /** - * Find the first OilPriceHistory that matches the filter or - * throw `PrismaKnownClientError` with `P2025` code if no matches were found. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {OilPriceHistoryFindFirstOrThrowArgs} args - Arguments to find a OilPriceHistory - * @example - * // Get one OilPriceHistory - * const oilPriceHistory = await prisma.oilPriceHistory.findFirstOrThrow({ - * where: { - * // ... provide filter here - * } - * }) - */ - findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__OilPriceHistoryClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Find zero or more OilPriceHistories that matches the filter. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {OilPriceHistoryFindManyArgs} args - Arguments to filter and select certain fields only. - * @example - * // Get all OilPriceHistories - * const oilPriceHistories = await prisma.oilPriceHistory.findMany() - * - * // Get first 10 OilPriceHistories - * const oilPriceHistories = await prisma.oilPriceHistory.findMany({ take: 10 }) - * - * // Only select the `id` - * const oilPriceHistoryWithIdOnly = await prisma.oilPriceHistory.findMany({ select: { id: true } }) - * - */ - findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> - - /** - * Create a OilPriceHistory. - * @param {OilPriceHistoryCreateArgs} args - Arguments to create a OilPriceHistory. - * @example - * // Create one OilPriceHistory - * const OilPriceHistory = await prisma.oilPriceHistory.create({ - * data: { - * // ... data to create a OilPriceHistory - * } - * }) - * - */ - create(args: Prisma.SelectSubset>): Prisma.Prisma__OilPriceHistoryClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Create many OilPriceHistories. - * @param {OilPriceHistoryCreateManyArgs} args - Arguments to create many OilPriceHistories. - * @example - * // Create many OilPriceHistories - * const oilPriceHistory = await prisma.oilPriceHistory.createMany({ - * data: [ - * // ... provide data here - * ] - * }) - * - */ - createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Create many OilPriceHistories and returns the data saved in the database. - * @param {OilPriceHistoryCreateManyAndReturnArgs} args - Arguments to create many OilPriceHistories. - * @example - * // Create many OilPriceHistories - * const oilPriceHistory = await prisma.oilPriceHistory.createManyAndReturn({ - * data: [ - * // ... provide data here - * ] - * }) - * - * // Create many OilPriceHistories and only return the `id` - * const oilPriceHistoryWithIdOnly = await prisma.oilPriceHistory.createManyAndReturn({ - * select: { id: true }, - * data: [ - * // ... provide data here - * ] - * }) - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * - */ - createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> - - /** - * Delete a OilPriceHistory. - * @param {OilPriceHistoryDeleteArgs} args - Arguments to delete one OilPriceHistory. - * @example - * // Delete one OilPriceHistory - * const OilPriceHistory = await prisma.oilPriceHistory.delete({ - * where: { - * // ... filter to delete one OilPriceHistory - * } - * }) - * - */ - delete(args: Prisma.SelectSubset>): Prisma.Prisma__OilPriceHistoryClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Update one OilPriceHistory. - * @param {OilPriceHistoryUpdateArgs} args - Arguments to update one OilPriceHistory. - * @example - * // Update one OilPriceHistory - * const oilPriceHistory = await prisma.oilPriceHistory.update({ - * where: { - * // ... provide filter here - * }, - * data: { - * // ... provide data here - * } - * }) - * - */ - update(args: Prisma.SelectSubset>): Prisma.Prisma__OilPriceHistoryClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Delete zero or more OilPriceHistories. - * @param {OilPriceHistoryDeleteManyArgs} args - Arguments to filter OilPriceHistories to delete. - * @example - * // Delete a few OilPriceHistories - * const { count } = await prisma.oilPriceHistory.deleteMany({ - * where: { - * // ... provide filter here - * } - * }) - * - */ - deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Update zero or more OilPriceHistories. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {OilPriceHistoryUpdateManyArgs} args - Arguments to update one or more rows. - * @example - * // Update many OilPriceHistories - * const oilPriceHistory = await prisma.oilPriceHistory.updateMany({ - * where: { - * // ... provide filter here - * }, - * data: { - * // ... provide data here - * } - * }) - * - */ - updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Update zero or more OilPriceHistories and returns the data updated in the database. - * @param {OilPriceHistoryUpdateManyAndReturnArgs} args - Arguments to update many OilPriceHistories. - * @example - * // Update many OilPriceHistories - * const oilPriceHistory = await prisma.oilPriceHistory.updateManyAndReturn({ - * where: { - * // ... provide filter here - * }, - * data: [ - * // ... provide data here - * ] - * }) - * - * // Update zero or more OilPriceHistories and only return the `id` - * const oilPriceHistoryWithIdOnly = await prisma.oilPriceHistory.updateManyAndReturn({ - * select: { id: true }, - * where: { - * // ... provide filter here - * }, - * data: [ - * // ... provide data here - * ] - * }) - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * - */ - updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> - - /** - * Create or update one OilPriceHistory. - * @param {OilPriceHistoryUpsertArgs} args - Arguments to update or create a OilPriceHistory. - * @example - * // Update or create a OilPriceHistory - * const oilPriceHistory = await prisma.oilPriceHistory.upsert({ - * create: { - * // ... data to create a OilPriceHistory - * }, - * update: { - * // ... in case it already exists, update - * }, - * where: { - * // ... the filter for the OilPriceHistory we want to update - * } - * }) - */ - upsert(args: Prisma.SelectSubset>): Prisma.Prisma__OilPriceHistoryClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - - /** - * Count the number of OilPriceHistories. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {OilPriceHistoryCountArgs} args - Arguments to filter OilPriceHistories to count. - * @example - * // Count the number of OilPriceHistories - * const count = await prisma.oilPriceHistory.count({ - * where: { - * // ... the filter for the OilPriceHistories we want to count - * } - * }) - **/ - count( - args?: Prisma.Subset, - ): Prisma.PrismaPromise< - T extends runtime.Types.Utils.Record<'select', any> - ? T['select'] extends true - ? number - : Prisma.GetScalarType - : number - > - - /** - * Allows you to perform aggregations operations on a OilPriceHistory. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {OilPriceHistoryAggregateArgs} args - Select which aggregations you would like to apply and on what fields. - * @example - * // Ordered by age ascending - * // Where email contains prisma.io - * // Limited to the 10 users - * const aggregations = await prisma.user.aggregate({ - * _avg: { - * age: true, - * }, - * where: { - * email: { - * contains: "prisma.io", - * }, - * }, - * orderBy: { - * age: "asc", - * }, - * take: 10, - * }) - **/ - aggregate(args: Prisma.Subset): Prisma.PrismaPromise> - - /** - * Group by OilPriceHistory. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {OilPriceHistoryGroupByArgs} args - Group by arguments. - * @example - * // Group by city, order by createdAt, get count - * const result = await prisma.user.groupBy({ - * by: ['city', 'createdAt'], - * orderBy: { - * createdAt: true - * }, - * _count: { - * _all: true - * }, - * }) - * - **/ - groupBy< - T extends OilPriceHistoryGroupByArgs, - HasSelectOrTake extends Prisma.Or< - Prisma.Extends<'skip', Prisma.Keys>, - Prisma.Extends<'take', Prisma.Keys> - >, - OrderByArg extends Prisma.True extends HasSelectOrTake - ? { orderBy: OilPriceHistoryGroupByArgs['orderBy'] } - : { orderBy?: OilPriceHistoryGroupByArgs['orderBy'] }, - OrderFields extends Prisma.ExcludeUnderscoreKeys>>, - ByFields extends Prisma.MaybeTupleToUnion, - ByValid extends Prisma.Has, - HavingFields extends Prisma.GetHavingFields, - HavingValid extends Prisma.Has, - ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, - InputErrors extends ByEmpty extends Prisma.True - ? `Error: "by" must not be empty.` - : HavingValid extends Prisma.False - ? { - [P in HavingFields]: P extends ByFields - ? never - : P extends string - ? `Error: Field "${P}" used in "having" needs to be provided in "by".` - : [ - Error, - 'Field ', - P, - ` in "having" needs to be provided in "by"`, - ] - }[HavingFields] - : 'take' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - : 'Error: If you provide "take", you also need to provide "orderBy"' - : 'skip' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - : 'Error: If you provide "skip", you also need to provide "orderBy"' - : ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetOilPriceHistoryGroupByPayload : Prisma.PrismaPromise -/** - * Fields of the OilPriceHistory model - */ -readonly fields: OilPriceHistoryFieldRefs; -} - -/** - * The delegate class that acts as a "Promise-like" for OilPriceHistory. - * Why is this prefixed with `Prisma__`? - * Because we want to prevent naming conflicts as mentioned in - * https://github.com/prisma/prisma-client-js/issues/707 - */ -export interface Prisma__OilPriceHistoryClient extends Prisma.PrismaPromise { - readonly [Symbol.toStringTag]: "PrismaPromise" - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise - /** - * Attaches a callback for only the rejection of the Promise. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of the callback. - */ - catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise - /** - * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The - * resolved value cannot be modified from the callback. - * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). - * @returns A Promise for the completion of the callback. - */ - finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise -} - - - - -/** - * Fields of the OilPriceHistory model - */ -export interface OilPriceHistoryFieldRefs { - readonly id: Prisma.FieldRef<"OilPriceHistory", 'Int'> - readonly date: Prisma.FieldRef<"OilPriceHistory", 'DateTime'> - readonly province: Prisma.FieldRef<"OilPriceHistory", 'String'> - readonly fuel: Prisma.FieldRef<"OilPriceHistory", 'String'> - readonly price: Prisma.FieldRef<"OilPriceHistory", 'Float'> - readonly source: Prisma.FieldRef<"OilPriceHistory", 'String'> - readonly createdAt: Prisma.FieldRef<"OilPriceHistory", 'DateTime'> -} - - -// Custom InputTypes -/** - * OilPriceHistory findUnique - */ -export type OilPriceHistoryFindUniqueArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelect | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * Filter, which OilPriceHistory to fetch. - */ - where: Prisma.OilPriceHistoryWhereUniqueInput -} - -/** - * OilPriceHistory findUniqueOrThrow - */ -export type OilPriceHistoryFindUniqueOrThrowArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelect | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * Filter, which OilPriceHistory to fetch. - */ - where: Prisma.OilPriceHistoryWhereUniqueInput -} - -/** - * OilPriceHistory findFirst - */ -export type OilPriceHistoryFindFirstArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelect | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * Filter, which OilPriceHistory to fetch. - */ - where?: Prisma.OilPriceHistoryWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of OilPriceHistories to fetch. - */ - orderBy?: Prisma.OilPriceHistoryOrderByWithRelationInput | Prisma.OilPriceHistoryOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for searching for OilPriceHistories. - */ - cursor?: Prisma.OilPriceHistoryWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` OilPriceHistories from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` OilPriceHistories. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of OilPriceHistories. - */ - distinct?: Prisma.OilPriceHistoryScalarFieldEnum | Prisma.OilPriceHistoryScalarFieldEnum[] -} - -/** - * OilPriceHistory findFirstOrThrow - */ -export type OilPriceHistoryFindFirstOrThrowArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelect | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * Filter, which OilPriceHistory to fetch. - */ - where?: Prisma.OilPriceHistoryWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of OilPriceHistories to fetch. - */ - orderBy?: Prisma.OilPriceHistoryOrderByWithRelationInput | Prisma.OilPriceHistoryOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for searching for OilPriceHistories. - */ - cursor?: Prisma.OilPriceHistoryWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` OilPriceHistories from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` OilPriceHistories. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of OilPriceHistories. - */ - distinct?: Prisma.OilPriceHistoryScalarFieldEnum | Prisma.OilPriceHistoryScalarFieldEnum[] -} - -/** - * OilPriceHistory findMany - */ -export type OilPriceHistoryFindManyArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelect | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * Filter, which OilPriceHistories to fetch. - */ - where?: Prisma.OilPriceHistoryWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of OilPriceHistories to fetch. - */ - orderBy?: Prisma.OilPriceHistoryOrderByWithRelationInput | Prisma.OilPriceHistoryOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for listing OilPriceHistories. - */ - cursor?: Prisma.OilPriceHistoryWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` OilPriceHistories from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` OilPriceHistories. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of OilPriceHistories. - */ - distinct?: Prisma.OilPriceHistoryScalarFieldEnum | Prisma.OilPriceHistoryScalarFieldEnum[] -} - -/** - * OilPriceHistory create - */ -export type OilPriceHistoryCreateArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelect | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * The data needed to create a OilPriceHistory. - */ - data: Prisma.XOR -} - -/** - * OilPriceHistory createMany - */ -export type OilPriceHistoryCreateManyArgs = { - /** - * The data used to create many OilPriceHistories. - */ - data: Prisma.OilPriceHistoryCreateManyInput | Prisma.OilPriceHistoryCreateManyInput[] - skipDuplicates?: boolean -} - -/** - * OilPriceHistory createManyAndReturn - */ -export type OilPriceHistoryCreateManyAndReturnArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelectCreateManyAndReturn | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * The data used to create many OilPriceHistories. - */ - data: Prisma.OilPriceHistoryCreateManyInput | Prisma.OilPriceHistoryCreateManyInput[] - skipDuplicates?: boolean -} - -/** - * OilPriceHistory update - */ -export type OilPriceHistoryUpdateArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelect | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * The data needed to update a OilPriceHistory. - */ - data: Prisma.XOR - /** - * Choose, which OilPriceHistory to update. - */ - where: Prisma.OilPriceHistoryWhereUniqueInput -} - -/** - * OilPriceHistory updateMany - */ -export type OilPriceHistoryUpdateManyArgs = { - /** - * The data used to update OilPriceHistories. - */ - data: Prisma.XOR - /** - * Filter which OilPriceHistories to update - */ - where?: Prisma.OilPriceHistoryWhereInput - /** - * Limit how many OilPriceHistories to update. - */ - limit?: number -} - -/** - * OilPriceHistory updateManyAndReturn - */ -export type OilPriceHistoryUpdateManyAndReturnArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelectUpdateManyAndReturn | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * The data used to update OilPriceHistories. - */ - data: Prisma.XOR - /** - * Filter which OilPriceHistories to update - */ - where?: Prisma.OilPriceHistoryWhereInput - /** - * Limit how many OilPriceHistories to update. - */ - limit?: number -} - -/** - * OilPriceHistory upsert - */ -export type OilPriceHistoryUpsertArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelect | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * The filter to search for the OilPriceHistory to update in case it exists. - */ - where: Prisma.OilPriceHistoryWhereUniqueInput - /** - * In case the OilPriceHistory found by the `where` argument doesn't exist, create a new OilPriceHistory with this data. - */ - create: Prisma.XOR - /** - * In case the OilPriceHistory was found with the provided `where` argument, update it with this data. - */ - update: Prisma.XOR -} - -/** - * OilPriceHistory delete - */ -export type OilPriceHistoryDeleteArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelect | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null - /** - * Filter which OilPriceHistory to delete. - */ - where: Prisma.OilPriceHistoryWhereUniqueInput -} - -/** - * OilPriceHistory deleteMany - */ -export type OilPriceHistoryDeleteManyArgs = { - /** - * Filter which OilPriceHistories to delete - */ - where?: Prisma.OilPriceHistoryWhereInput - /** - * Limit how many OilPriceHistories to delete. - */ - limit?: number -} - -/** - * OilPriceHistory without action - */ -export type OilPriceHistoryDefaultArgs = { - /** - * Select specific fields to fetch from the OilPriceHistory - */ - select?: Prisma.OilPriceHistorySelect | null - /** - * Omit specific fields from the OilPriceHistory - */ - omit?: Prisma.OilPriceHistoryOmit | null -} diff --git a/generated/prisma/models/Strategy.ts b/generated/prisma/models/Strategy.ts deleted file mode 100644 index 36383c5..0000000 --- a/generated/prisma/models/Strategy.ts +++ /dev/null @@ -1,1212 +0,0 @@ - -/* !!! This is code generated by Prisma. Do not edit directly. !!! */ -/* eslint-disable */ -// biome-ignore-all lint: generated file -// @ts-nocheck -/* - * This file exports the `Strategy` model and its related types. - * - * 🟢 You can import this file directly. - */ -import type * as runtime from "@prisma/client/runtime/client" -import type * as $Enums from "../enums.ts" -import type * as Prisma from "../internal/prismaNamespace.ts" - -/** - * Model Strategy - * - */ -export type StrategyModel = runtime.Types.Result.DefaultSelection - -export type AggregateStrategy = { - _count: StrategyCountAggregateOutputType | null - _avg: StrategyAvgAggregateOutputType | null - _sum: StrategySumAggregateOutputType | null - _min: StrategyMinAggregateOutputType | null - _max: StrategyMaxAggregateOutputType | null -} - -export type StrategyAvgAggregateOutputType = { - id: number | null -} - -export type StrategySumAggregateOutputType = { - id: number | null -} - -export type StrategyMinAggregateOutputType = { - id: number | null - name: string | null - description: string | null - createdAt: Date | null - updatedAt: Date | null -} - -export type StrategyMaxAggregateOutputType = { - id: number | null - name: string | null - description: string | null - createdAt: Date | null - updatedAt: Date | null -} - -export type StrategyCountAggregateOutputType = { - id: number - name: number - description: number - data: number - createdAt: number - updatedAt: number - _all: number -} - - -export type StrategyAvgAggregateInputType = { - id?: true -} - -export type StrategySumAggregateInputType = { - id?: true -} - -export type StrategyMinAggregateInputType = { - id?: true - name?: true - description?: true - createdAt?: true - updatedAt?: true -} - -export type StrategyMaxAggregateInputType = { - id?: true - name?: true - description?: true - createdAt?: true - updatedAt?: true -} - -export type StrategyCountAggregateInputType = { - id?: true - name?: true - description?: true - data?: true - createdAt?: true - updatedAt?: true - _all?: true -} - -export type StrategyAggregateArgs = { - /** - * Filter which Strategy to aggregate. - */ - where?: Prisma.StrategyWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of Strategies to fetch. - */ - orderBy?: Prisma.StrategyOrderByWithRelationInput | Prisma.StrategyOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the start position - */ - cursor?: Prisma.StrategyWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` Strategies from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` Strategies. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Count returned Strategies - **/ - _count?: true | StrategyCountAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to average - **/ - _avg?: StrategyAvgAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to sum - **/ - _sum?: StrategySumAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to find the minimum value - **/ - _min?: StrategyMinAggregateInputType - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * - * Select which fields to find the maximum value - **/ - _max?: StrategyMaxAggregateInputType -} - -export type GetStrategyAggregateType = { - [P in keyof T & keyof AggregateStrategy]: P extends '_count' | 'count' - ? T[P] extends true - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType -} - - - - -export type StrategyGroupByArgs = { - where?: Prisma.StrategyWhereInput - orderBy?: Prisma.StrategyOrderByWithAggregationInput | Prisma.StrategyOrderByWithAggregationInput[] - by: Prisma.StrategyScalarFieldEnum[] | Prisma.StrategyScalarFieldEnum - having?: Prisma.StrategyScalarWhereWithAggregatesInput - take?: number - skip?: number - _count?: StrategyCountAggregateInputType | true - _avg?: StrategyAvgAggregateInputType - _sum?: StrategySumAggregateInputType - _min?: StrategyMinAggregateInputType - _max?: StrategyMaxAggregateInputType -} - -export type StrategyGroupByOutputType = { - id: number - name: string - description: string - data: runtime.JsonValue - createdAt: Date - updatedAt: Date - _count: StrategyCountAggregateOutputType | null - _avg: StrategyAvgAggregateOutputType | null - _sum: StrategySumAggregateOutputType | null - _min: StrategyMinAggregateOutputType | null - _max: StrategyMaxAggregateOutputType | null -} - -export type GetStrategyGroupByPayload = Prisma.PrismaPromise< - Array< - Prisma.PickEnumerable & - { - [P in ((keyof T) & (keyof StrategyGroupByOutputType))]: P extends '_count' - ? T[P] extends boolean - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType - } - > - > - - - -export type StrategyWhereInput = { - AND?: Prisma.StrategyWhereInput | Prisma.StrategyWhereInput[] - OR?: Prisma.StrategyWhereInput[] - NOT?: Prisma.StrategyWhereInput | Prisma.StrategyWhereInput[] - id?: Prisma.IntFilter<"Strategy"> | number - name?: Prisma.StringFilter<"Strategy"> | string - description?: Prisma.StringFilter<"Strategy"> | string - data?: Prisma.JsonFilter<"Strategy"> - createdAt?: Prisma.DateTimeFilter<"Strategy"> | Date | string - updatedAt?: Prisma.DateTimeFilter<"Strategy"> | Date | string -} - -export type StrategyOrderByWithRelationInput = { - id?: Prisma.SortOrder - name?: Prisma.SortOrder - description?: Prisma.SortOrder - data?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder -} - -export type StrategyWhereUniqueInput = Prisma.AtLeast<{ - id?: number - AND?: Prisma.StrategyWhereInput | Prisma.StrategyWhereInput[] - OR?: Prisma.StrategyWhereInput[] - NOT?: Prisma.StrategyWhereInput | Prisma.StrategyWhereInput[] - name?: Prisma.StringFilter<"Strategy"> | string - description?: Prisma.StringFilter<"Strategy"> | string - data?: Prisma.JsonFilter<"Strategy"> - createdAt?: Prisma.DateTimeFilter<"Strategy"> | Date | string - updatedAt?: Prisma.DateTimeFilter<"Strategy"> | Date | string -}, "id"> - -export type StrategyOrderByWithAggregationInput = { - id?: Prisma.SortOrder - name?: Prisma.SortOrder - description?: Prisma.SortOrder - data?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder - _count?: Prisma.StrategyCountOrderByAggregateInput - _avg?: Prisma.StrategyAvgOrderByAggregateInput - _max?: Prisma.StrategyMaxOrderByAggregateInput - _min?: Prisma.StrategyMinOrderByAggregateInput - _sum?: Prisma.StrategySumOrderByAggregateInput -} - -export type StrategyScalarWhereWithAggregatesInput = { - AND?: Prisma.StrategyScalarWhereWithAggregatesInput | Prisma.StrategyScalarWhereWithAggregatesInput[] - OR?: Prisma.StrategyScalarWhereWithAggregatesInput[] - NOT?: Prisma.StrategyScalarWhereWithAggregatesInput | Prisma.StrategyScalarWhereWithAggregatesInput[] - id?: Prisma.IntWithAggregatesFilter<"Strategy"> | number - name?: Prisma.StringWithAggregatesFilter<"Strategy"> | string - description?: Prisma.StringWithAggregatesFilter<"Strategy"> | string - data?: Prisma.JsonWithAggregatesFilter<"Strategy"> - createdAt?: Prisma.DateTimeWithAggregatesFilter<"Strategy"> | Date | string - updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Strategy"> | Date | string -} - -export type StrategyCreateInput = { - name: string - description: string - data: Prisma.JsonNullValueInput | runtime.InputJsonValue - createdAt?: Date | string - updatedAt?: Date | string -} - -export type StrategyUncheckedCreateInput = { - id?: number - name: string - description: string - data: Prisma.JsonNullValueInput | runtime.InputJsonValue - createdAt?: Date | string - updatedAt?: Date | string -} - -export type StrategyUpdateInput = { - name?: Prisma.StringFieldUpdateOperationsInput | string - description?: Prisma.StringFieldUpdateOperationsInput | string - data?: Prisma.JsonNullValueInput | runtime.InputJsonValue - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type StrategyUncheckedUpdateInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - name?: Prisma.StringFieldUpdateOperationsInput | string - description?: Prisma.StringFieldUpdateOperationsInput | string - data?: Prisma.JsonNullValueInput | runtime.InputJsonValue - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type StrategyCreateManyInput = { - id?: number - name: string - description: string - data: Prisma.JsonNullValueInput | runtime.InputJsonValue - createdAt?: Date | string - updatedAt?: Date | string -} - -export type StrategyUpdateManyMutationInput = { - name?: Prisma.StringFieldUpdateOperationsInput | string - description?: Prisma.StringFieldUpdateOperationsInput | string - data?: Prisma.JsonNullValueInput | runtime.InputJsonValue - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type StrategyUncheckedUpdateManyInput = { - id?: Prisma.IntFieldUpdateOperationsInput | number - name?: Prisma.StringFieldUpdateOperationsInput | string - description?: Prisma.StringFieldUpdateOperationsInput | string - data?: Prisma.JsonNullValueInput | runtime.InputJsonValue - createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string -} - -export type StrategyCountOrderByAggregateInput = { - id?: Prisma.SortOrder - name?: Prisma.SortOrder - description?: Prisma.SortOrder - data?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder -} - -export type StrategyAvgOrderByAggregateInput = { - id?: Prisma.SortOrder -} - -export type StrategyMaxOrderByAggregateInput = { - id?: Prisma.SortOrder - name?: Prisma.SortOrder - description?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder -} - -export type StrategyMinOrderByAggregateInput = { - id?: Prisma.SortOrder - name?: Prisma.SortOrder - description?: Prisma.SortOrder - createdAt?: Prisma.SortOrder - updatedAt?: Prisma.SortOrder -} - -export type StrategySumOrderByAggregateInput = { - id?: Prisma.SortOrder -} - - - -export type StrategySelect = runtime.Types.Extensions.GetSelect<{ - id?: boolean - name?: boolean - description?: boolean - data?: boolean - createdAt?: boolean - updatedAt?: boolean -}, ExtArgs["result"]["strategy"]> - -export type StrategySelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ - id?: boolean - name?: boolean - description?: boolean - data?: boolean - createdAt?: boolean - updatedAt?: boolean -}, ExtArgs["result"]["strategy"]> - -export type StrategySelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ - id?: boolean - name?: boolean - description?: boolean - data?: boolean - createdAt?: boolean - updatedAt?: boolean -}, ExtArgs["result"]["strategy"]> - -export type StrategySelectScalar = { - id?: boolean - name?: boolean - description?: boolean - data?: boolean - createdAt?: boolean - updatedAt?: boolean -} - -export type StrategyOmit = runtime.Types.Extensions.GetOmit<"id" | "name" | "description" | "data" | "createdAt" | "updatedAt", ExtArgs["result"]["strategy"]> - -export type $StrategyPayload = { - name: "Strategy" - objects: {} - scalars: runtime.Types.Extensions.GetPayloadResult<{ - id: number - name: string - description: string - data: runtime.JsonValue - createdAt: Date - updatedAt: Date - }, ExtArgs["result"]["strategy"]> - composites: {} -} - -export type StrategyGetPayload = runtime.Types.Result.GetResult - -export type StrategyCountArgs = - Omit & { - select?: StrategyCountAggregateInputType | true - } - -export interface StrategyDelegate { - [K: symbol]: { types: Prisma.TypeMap['model']['Strategy'], meta: { name: 'Strategy' } } - /** - * Find zero or one Strategy that matches the filter. - * @param {StrategyFindUniqueArgs} args - Arguments to find a Strategy - * @example - * // Get one Strategy - * const strategy = await prisma.strategy.findUnique({ - * where: { - * // ... provide filter here - * } - * }) - */ - findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__StrategyClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> - - /** - * Find one Strategy that matches the filter or throw an error with `error.code='P2025'` - * if no matches were found. - * @param {StrategyFindUniqueOrThrowArgs} args - Arguments to find a Strategy - * @example - * // Get one Strategy - * const strategy = await prisma.strategy.findUniqueOrThrow({ - * where: { - * // ... provide filter here - * } - * }) - */ - findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__StrategyClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Find the first Strategy that matches the filter. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {StrategyFindFirstArgs} args - Arguments to find a Strategy - * @example - * // Get one Strategy - * const strategy = await prisma.strategy.findFirst({ - * where: { - * // ... provide filter here - * } - * }) - */ - findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__StrategyClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> - - /** - * Find the first Strategy that matches the filter or - * throw `PrismaKnownClientError` with `P2025` code if no matches were found. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {StrategyFindFirstOrThrowArgs} args - Arguments to find a Strategy - * @example - * // Get one Strategy - * const strategy = await prisma.strategy.findFirstOrThrow({ - * where: { - * // ... provide filter here - * } - * }) - */ - findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__StrategyClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Find zero or more Strategies that matches the filter. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {StrategyFindManyArgs} args - Arguments to filter and select certain fields only. - * @example - * // Get all Strategies - * const strategies = await prisma.strategy.findMany() - * - * // Get first 10 Strategies - * const strategies = await prisma.strategy.findMany({ take: 10 }) - * - * // Only select the `id` - * const strategyWithIdOnly = await prisma.strategy.findMany({ select: { id: true } }) - * - */ - findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> - - /** - * Create a Strategy. - * @param {StrategyCreateArgs} args - Arguments to create a Strategy. - * @example - * // Create one Strategy - * const Strategy = await prisma.strategy.create({ - * data: { - * // ... data to create a Strategy - * } - * }) - * - */ - create(args: Prisma.SelectSubset>): Prisma.Prisma__StrategyClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Create many Strategies. - * @param {StrategyCreateManyArgs} args - Arguments to create many Strategies. - * @example - * // Create many Strategies - * const strategy = await prisma.strategy.createMany({ - * data: [ - * // ... provide data here - * ] - * }) - * - */ - createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Create many Strategies and returns the data saved in the database. - * @param {StrategyCreateManyAndReturnArgs} args - Arguments to create many Strategies. - * @example - * // Create many Strategies - * const strategy = await prisma.strategy.createManyAndReturn({ - * data: [ - * // ... provide data here - * ] - * }) - * - * // Create many Strategies and only return the `id` - * const strategyWithIdOnly = await prisma.strategy.createManyAndReturn({ - * select: { id: true }, - * data: [ - * // ... provide data here - * ] - * }) - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * - */ - createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> - - /** - * Delete a Strategy. - * @param {StrategyDeleteArgs} args - Arguments to delete one Strategy. - * @example - * // Delete one Strategy - * const Strategy = await prisma.strategy.delete({ - * where: { - * // ... filter to delete one Strategy - * } - * }) - * - */ - delete(args: Prisma.SelectSubset>): Prisma.Prisma__StrategyClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Update one Strategy. - * @param {StrategyUpdateArgs} args - Arguments to update one Strategy. - * @example - * // Update one Strategy - * const strategy = await prisma.strategy.update({ - * where: { - * // ... provide filter here - * }, - * data: { - * // ... provide data here - * } - * }) - * - */ - update(args: Prisma.SelectSubset>): Prisma.Prisma__StrategyClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - /** - * Delete zero or more Strategies. - * @param {StrategyDeleteManyArgs} args - Arguments to filter Strategies to delete. - * @example - * // Delete a few Strategies - * const { count } = await prisma.strategy.deleteMany({ - * where: { - * // ... provide filter here - * } - * }) - * - */ - deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Update zero or more Strategies. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {StrategyUpdateManyArgs} args - Arguments to update one or more rows. - * @example - * // Update many Strategies - * const strategy = await prisma.strategy.updateMany({ - * where: { - * // ... provide filter here - * }, - * data: { - * // ... provide data here - * } - * }) - * - */ - updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise - - /** - * Update zero or more Strategies and returns the data updated in the database. - * @param {StrategyUpdateManyAndReturnArgs} args - Arguments to update many Strategies. - * @example - * // Update many Strategies - * const strategy = await prisma.strategy.updateManyAndReturn({ - * where: { - * // ... provide filter here - * }, - * data: [ - * // ... provide data here - * ] - * }) - * - * // Update zero or more Strategies and only return the `id` - * const strategyWithIdOnly = await prisma.strategy.updateManyAndReturn({ - * select: { id: true }, - * where: { - * // ... provide filter here - * }, - * data: [ - * // ... provide data here - * ] - * }) - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * - */ - updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> - - /** - * Create or update one Strategy. - * @param {StrategyUpsertArgs} args - Arguments to update or create a Strategy. - * @example - * // Update or create a Strategy - * const strategy = await prisma.strategy.upsert({ - * create: { - * // ... data to create a Strategy - * }, - * update: { - * // ... in case it already exists, update - * }, - * where: { - * // ... the filter for the Strategy we want to update - * } - * }) - */ - upsert(args: Prisma.SelectSubset>): Prisma.Prisma__StrategyClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> - - - /** - * Count the number of Strategies. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {StrategyCountArgs} args - Arguments to filter Strategies to count. - * @example - * // Count the number of Strategies - * const count = await prisma.strategy.count({ - * where: { - * // ... the filter for the Strategies we want to count - * } - * }) - **/ - count( - args?: Prisma.Subset, - ): Prisma.PrismaPromise< - T extends runtime.Types.Utils.Record<'select', any> - ? T['select'] extends true - ? number - : Prisma.GetScalarType - : number - > - - /** - * Allows you to perform aggregations operations on a Strategy. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {StrategyAggregateArgs} args - Select which aggregations you would like to apply and on what fields. - * @example - * // Ordered by age ascending - * // Where email contains prisma.io - * // Limited to the 10 users - * const aggregations = await prisma.user.aggregate({ - * _avg: { - * age: true, - * }, - * where: { - * email: { - * contains: "prisma.io", - * }, - * }, - * orderBy: { - * age: "asc", - * }, - * take: 10, - * }) - **/ - aggregate(args: Prisma.Subset): Prisma.PrismaPromise> - - /** - * Group by Strategy. - * Note, that providing `undefined` is treated as the value not being there. - * Read more here: https://pris.ly/d/null-undefined - * @param {StrategyGroupByArgs} args - Group by arguments. - * @example - * // Group by city, order by createdAt, get count - * const result = await prisma.user.groupBy({ - * by: ['city', 'createdAt'], - * orderBy: { - * createdAt: true - * }, - * _count: { - * _all: true - * }, - * }) - * - **/ - groupBy< - T extends StrategyGroupByArgs, - HasSelectOrTake extends Prisma.Or< - Prisma.Extends<'skip', Prisma.Keys>, - Prisma.Extends<'take', Prisma.Keys> - >, - OrderByArg extends Prisma.True extends HasSelectOrTake - ? { orderBy: StrategyGroupByArgs['orderBy'] } - : { orderBy?: StrategyGroupByArgs['orderBy'] }, - OrderFields extends Prisma.ExcludeUnderscoreKeys>>, - ByFields extends Prisma.MaybeTupleToUnion, - ByValid extends Prisma.Has, - HavingFields extends Prisma.GetHavingFields, - HavingValid extends Prisma.Has, - ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, - InputErrors extends ByEmpty extends Prisma.True - ? `Error: "by" must not be empty.` - : HavingValid extends Prisma.False - ? { - [P in HavingFields]: P extends ByFields - ? never - : P extends string - ? `Error: Field "${P}" used in "having" needs to be provided in "by".` - : [ - Error, - 'Field ', - P, - ` in "having" needs to be provided in "by"`, - ] - }[HavingFields] - : 'take' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - : 'Error: If you provide "take", you also need to provide "orderBy"' - : 'skip' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys - ? ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - : 'Error: If you provide "skip", you also need to provide "orderBy"' - : ByValid extends Prisma.True - ? {} - : { - [P in OrderFields]: P extends ByFields - ? never - : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` - }[OrderFields] - >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetStrategyGroupByPayload : Prisma.PrismaPromise -/** - * Fields of the Strategy model - */ -readonly fields: StrategyFieldRefs; -} - -/** - * The delegate class that acts as a "Promise-like" for Strategy. - * Why is this prefixed with `Prisma__`? - * Because we want to prevent naming conflicts as mentioned in - * https://github.com/prisma/prisma-client-js/issues/707 - */ -export interface Prisma__StrategyClient extends Prisma.PrismaPromise { - readonly [Symbol.toStringTag]: "PrismaPromise" - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise - /** - * Attaches a callback for only the rejection of the Promise. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of the callback. - */ - catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise - /** - * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The - * resolved value cannot be modified from the callback. - * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). - * @returns A Promise for the completion of the callback. - */ - finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise -} - - - - -/** - * Fields of the Strategy model - */ -export interface StrategyFieldRefs { - readonly id: Prisma.FieldRef<"Strategy", 'Int'> - readonly name: Prisma.FieldRef<"Strategy", 'String'> - readonly description: Prisma.FieldRef<"Strategy", 'String'> - readonly data: Prisma.FieldRef<"Strategy", 'Json'> - readonly createdAt: Prisma.FieldRef<"Strategy", 'DateTime'> - readonly updatedAt: Prisma.FieldRef<"Strategy", 'DateTime'> -} - - -// Custom InputTypes -/** - * Strategy findUnique - */ -export type StrategyFindUniqueArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelect | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * Filter, which Strategy to fetch. - */ - where: Prisma.StrategyWhereUniqueInput -} - -/** - * Strategy findUniqueOrThrow - */ -export type StrategyFindUniqueOrThrowArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelect | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * Filter, which Strategy to fetch. - */ - where: Prisma.StrategyWhereUniqueInput -} - -/** - * Strategy findFirst - */ -export type StrategyFindFirstArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelect | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * Filter, which Strategy to fetch. - */ - where?: Prisma.StrategyWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of Strategies to fetch. - */ - orderBy?: Prisma.StrategyOrderByWithRelationInput | Prisma.StrategyOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for searching for Strategies. - */ - cursor?: Prisma.StrategyWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` Strategies from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` Strategies. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of Strategies. - */ - distinct?: Prisma.StrategyScalarFieldEnum | Prisma.StrategyScalarFieldEnum[] -} - -/** - * Strategy findFirstOrThrow - */ -export type StrategyFindFirstOrThrowArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelect | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * Filter, which Strategy to fetch. - */ - where?: Prisma.StrategyWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of Strategies to fetch. - */ - orderBy?: Prisma.StrategyOrderByWithRelationInput | Prisma.StrategyOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for searching for Strategies. - */ - cursor?: Prisma.StrategyWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` Strategies from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` Strategies. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of Strategies. - */ - distinct?: Prisma.StrategyScalarFieldEnum | Prisma.StrategyScalarFieldEnum[] -} - -/** - * Strategy findMany - */ -export type StrategyFindManyArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelect | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * Filter, which Strategies to fetch. - */ - where?: Prisma.StrategyWhereInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * - * Determine the order of Strategies to fetch. - */ - orderBy?: Prisma.StrategyOrderByWithRelationInput | Prisma.StrategyOrderByWithRelationInput[] - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * - * Sets the position for listing Strategies. - */ - cursor?: Prisma.StrategyWhereUniqueInput - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Take `±n` Strategies from the position of the cursor. - */ - take?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * - * Skip the first `n` Strategies. - */ - skip?: number - /** - * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * - * Filter by unique combinations of Strategies. - */ - distinct?: Prisma.StrategyScalarFieldEnum | Prisma.StrategyScalarFieldEnum[] -} - -/** - * Strategy create - */ -export type StrategyCreateArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelect | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * The data needed to create a Strategy. - */ - data: Prisma.XOR -} - -/** - * Strategy createMany - */ -export type StrategyCreateManyArgs = { - /** - * The data used to create many Strategies. - */ - data: Prisma.StrategyCreateManyInput | Prisma.StrategyCreateManyInput[] - skipDuplicates?: boolean -} - -/** - * Strategy createManyAndReturn - */ -export type StrategyCreateManyAndReturnArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelectCreateManyAndReturn | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * The data used to create many Strategies. - */ - data: Prisma.StrategyCreateManyInput | Prisma.StrategyCreateManyInput[] - skipDuplicates?: boolean -} - -/** - * Strategy update - */ -export type StrategyUpdateArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelect | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * The data needed to update a Strategy. - */ - data: Prisma.XOR - /** - * Choose, which Strategy to update. - */ - where: Prisma.StrategyWhereUniqueInput -} - -/** - * Strategy updateMany - */ -export type StrategyUpdateManyArgs = { - /** - * The data used to update Strategies. - */ - data: Prisma.XOR - /** - * Filter which Strategies to update - */ - where?: Prisma.StrategyWhereInput - /** - * Limit how many Strategies to update. - */ - limit?: number -} - -/** - * Strategy updateManyAndReturn - */ -export type StrategyUpdateManyAndReturnArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelectUpdateManyAndReturn | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * The data used to update Strategies. - */ - data: Prisma.XOR - /** - * Filter which Strategies to update - */ - where?: Prisma.StrategyWhereInput - /** - * Limit how many Strategies to update. - */ - limit?: number -} - -/** - * Strategy upsert - */ -export type StrategyUpsertArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelect | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * The filter to search for the Strategy to update in case it exists. - */ - where: Prisma.StrategyWhereUniqueInput - /** - * In case the Strategy found by the `where` argument doesn't exist, create a new Strategy with this data. - */ - create: Prisma.XOR - /** - * In case the Strategy was found with the provided `where` argument, update it with this data. - */ - update: Prisma.XOR -} - -/** - * Strategy delete - */ -export type StrategyDeleteArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelect | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null - /** - * Filter which Strategy to delete. - */ - where: Prisma.StrategyWhereUniqueInput -} - -/** - * Strategy deleteMany - */ -export type StrategyDeleteManyArgs = { - /** - * Filter which Strategies to delete - */ - where?: Prisma.StrategyWhereInput - /** - * Limit how many Strategies to delete. - */ - limit?: number -} - -/** - * Strategy without action - */ -export type StrategyDefaultArgs = { - /** - * Select specific fields to fetch from the Strategy - */ - select?: Prisma.StrategySelect | null - /** - * Omit specific fields from the Strategy - */ - omit?: Prisma.StrategyOmit | null -} diff --git a/index.html b/index.html index 9d8210e..d00b75b 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - iBoard - 经济数据与策略 + iBoard - 个人看板
diff --git a/package.json b/package.json index 924b717..6ca5dc6 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,13 @@ "build:shell": "vite build --mode shell", "build:win": "node scripts/build-windows.mjs", "build:win:debug": "node scripts/build-windows.mjs --debug", + "win:dev": "node scripts/dev-windows-live.mjs", "build:ota": "node scripts/build-ota.mjs", "preview": "vite preview", "start": "node --experimental-strip-types --no-warnings server/index.js", "db:migrate": "prisma migrate deploy", "db:seed": "node scripts/seed.cjs", "registry:sync": "node scripts/sync-registry.mjs", - "data:refresh": "node --no-warnings scripts/refresh-indicators.cjs", - "data:discover": "node --no-warnings scripts/discover-mcp-tools.cjs", "icons": "node scripts/generate-icons.mjs", "tauri:dev": "tauri dev", "tauri:build": "tauri build", diff --git a/prisma/migrations/20261030000000_drop_macro_data/migration.sql b/prisma/migrations/20261030000000_drop_macro_data/migration.sql new file mode 100644 index 0000000..3a04fdb --- /dev/null +++ b/prisma/migrations/20261030000000_drop_macro_data/migration.sql @@ -0,0 +1,6 @@ +-- V0.2.2: 宏观「经济数据 / 策略研究」迁至外接 ifinance 模块, 油模块下线 +-- 彻底移除本地指标/策略/油价数据表 (数据源改由 tortoise/ifinance 承担) +DROP TABLE IF EXISTS "IndicatorPoint" CASCADE; +DROP TABLE IF EXISTS "Indicator" CASCADE; +DROP TABLE IF EXISTS "Strategy" CASCADE; +DROP TABLE IF EXISTS "OilPriceHistory" CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d8ef3c2..8519dce 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -44,41 +44,6 @@ model Article { updatedAt DateTime @updatedAt } -model Strategy { - id Int @id @default(autoincrement()) - name String - description String - data Json - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} - -model Indicator { - id Int @id @default(autoincrement()) - name String @unique - groupName String - title String @default("") - unit String @default("") - source String @default("") - frequency String @default("month") // year | quarter | month | day - hidden Boolean @default(false) // true = 数据/脚本保留, 但不在前端展示 - points IndicatorPoint[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} - -model IndicatorPoint { - id Int @id @default(autoincrement()) - indicatorId Int - indicator Indicator @relation(fields: [indicatorId], references: [id], onDelete: Cascade) - period String // "2024" | "2024-Q1" | "2024-01" | "2024-01-15" - value Float - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@unique([indicatorId, period]) -} - // 新闻: 每天 3 点由 mx_finance_search_news 拉取, 按影响大小排序, 累加多日供首页无限滚动 model News { @@ -96,7 +61,7 @@ model News { @@index([publishedAt]) } -// 报警: refresh 写库时比对同 (indicatorName, period) 的跨源数据, 差异超阈值记录 +// 报警: 定时任务失败等异常写入 (展示于「监控」报警 tab) model Alert { id Int @id @default(autoincrement()) indicatorName String @@ -194,25 +159,6 @@ model ScraperCache { } - - -// 浙江省油价历史 (apizero price 接口每日抓取) -// 每天 04:00 cron 触发: 检查 schedule 里待定且 <= 昨天的调价日, 抓 price?province=浙江 存本表 -// unique (date, province, fuel) 防重复 -model OilPriceHistory { - id Int @id @default(autoincrement()) - date DateTime // 调价日次日 (即数据实际抓取日) - province String // '浙江' - fuel String // '92' | '95' | '0' - price Float // 油价 (元/升, 4 位小数足够) - source String @default("apizero") - createdAt DateTime @default(now()) - - @@unique([date, province, fuel]) - @@index([province, fuel, date]) -} - - // ==================== 环境监控:米家 ==================== // 米家授权状态 (DiskCache 存凭证, 本表只存状态/错误供前端展示) diff --git a/public/ifinance-info.json b/public/ifinance-info.json new file mode 100644 index 0000000..4d58447 --- /dev/null +++ b/public/ifinance-info.json @@ -0,0 +1,347 @@ +{ + "schema": 2, + "name": "ifinance", + "version": "0.2.0", + "buildId": "20261030.1442", + "buildHash": "76353f01070db7938dd7225ea9719c004052f0f5c740d4bcddb13ca880328fe0", + "generatedAt": "2026-10-30T14:42:19+08:00", + "generator": "tortoise/tools/ifinance/build.py", + "source": { + "project": "tortoise", + "upstream": "iboard-prod-pg-1" + }, + "cache": { + "mode": "hash", + "digest": "sha256", + "manifest": "files", + "key": "path", + "revalidate": "info.json" + }, + "categories": [ + { + "id": "econ", + "name": "经济数据", + "count": 8, + "items": [ + { + "id": "china-bond-10y", + "kind": "indicator", + "title": "中国国债 10 年期收益率", + "group": "china-macro", + "unit": "%", + "frequency": "month", + "subtitle": "1.71% · 2026-08", + "points": 32, + "html": "public/econ/china-bond-10y.html", + "json": "public/econ/china-bond-10y.json", + "hash": "3b5fef761b59afb6869fa2a73fe117fb69dcbefea3d39d7cd1859004162a52e7", + "bytes": 5630, + "jsonHash": "72b493417070df9c772d6afc8604b6db99e5889d36c59d45bcd92f2dafa261ca" + }, + { + "id": "china-bond-1y", + "kind": "indicator", + "title": "中国国债 1 年期收益率", + "group": "china-macro", + "unit": "%", + "frequency": "month", + "subtitle": "1.17% · 2026-08", + "points": 32, + "html": "public/econ/china-bond-1y.html", + "json": "public/econ/china-bond-1y.json", + "hash": "7639c558bba083cc9a25f8a156419138937d8712612dee34ed2b89890a4d9fec", + "bytes": 5628, + "jsonHash": "1eb0575b5eac06c78104974b75b8f106cb92bc6d87113652f7f457ee5c988fc2" + }, + { + "id": "china-cpi", + "kind": "indicator", + "title": "中国 CPI", + "group": "china-macro", + "unit": "%", + "frequency": "year", + "subtitle": "0.06% · 2025", + "points": 11, + "html": "public/econ/china-cpi.html", + "json": "public/econ/china-cpi.json", + "hash": "9ff611705ff82a56acbbf96e337e0a79bfe2f2a22fc8c5b852928af85d7b3de2", + "bytes": 4236, + "jsonHash": "6007ad45542047c0e343c0cc590d9fcde63d9c820ff99f0a93f61a8560209ebf" + }, + { + "id": "china-cpi-yoy", + "kind": "indicator", + "title": "中国 CPI 同比", + "group": "china-macro", + "unit": "%", + "frequency": "month", + "subtitle": "1% · 2026-06", + "points": 36, + "html": "public/econ/china-cpi-yoy.html", + "json": "public/econ/china-cpi-yoy.json", + "hash": "cb51b54b2f6bf172aa3e87ac368ce67c7398dbc51e2f033b0eac0d367cb62a0e", + "bytes": 5627, + "jsonHash": "16735f992a47c448fe1b175abd15174c4c6a19df4e8f1084580f9d1882009d44" + }, + { + "id": "china-lpr-1y", + "kind": "indicator", + "title": "中国 LPR 1 年期", + "group": "china-macro", + "unit": "%", + "frequency": "month", + "subtitle": "3% · 2026-07", + "points": 31, + "html": "public/econ/china-lpr-1y.html", + "json": "public/econ/china-lpr-1y.json", + "hash": "5f7254dc86f98bf32cfd06d37d9b329bcff87513057583bdf669b8691a3f4bcf", + "bytes": 5327, + "jsonHash": "7300679627550b75fa2fae62888b79e6f12a8386d2d3f2dec9bd2925387d8aa7" + }, + { + "id": "china-lpr-5y", + "kind": "indicator", + "title": "中国 LPR 5 年期", + "group": "china-macro", + "unit": "%", + "frequency": "month", + "subtitle": "3.5% · 2026-07", + "points": 31, + "html": "public/econ/china-lpr-5y.html", + "json": "public/econ/china-lpr-5y.json", + "hash": "e32519d06f0ca7a601e933ce704d44d59a0ab3b2fb046a6754ad4ef3ed71b742", + "bytes": 5498, + "jsonHash": "99d4b4d900e5a99f60b294eb9fca87145ad20b44995ef2178061cc832c2599e8" + }, + { + "id": "us-bond-1y", + "kind": "indicator", + "title": "美国国债1年", + "group": "us-bond", + "unit": "%", + "frequency": "month", + "subtitle": "4.45% · 2024-06", + "points": 6, + "html": "public/econ/us-bond-1y.html", + "json": "public/econ/us-bond-1y.json", + "hash": "c972cd658b5db614ec94135dc8f7816366a8ed526360514cfb53c64f9df09dfa", + "bytes": 3736, + "jsonHash": "82a7af636dd597bde08bc9cb7e22ab4e9de6754ffc30614d78ba1a96e6715a4a" + }, + { + "id": "usa-cpi", + "kind": "indicator", + "title": "美国 CPI", + "group": "usa-macro", + "unit": "%", + "frequency": "year", + "subtitle": "2.95% · 2024", + "points": 10, + "html": "public/econ/usa-cpi.html", + "json": "public/econ/usa-cpi.json", + "hash": "f4aec9f8c2aee8835166e71a7799c87c8f0288f0fc9760b85620bc98c7bd9721", + "bytes": 4187, + "jsonHash": "0273f88e376d166528e60338beff1e0878e98336c26c759f36daa078acb92d87" + } + ] + }, + { + "id": "strategy", + "name": "策略研究", + "count": 5, + "items": [ + { + "id": "china-debt-gdp", + "kind": "strategy", + "title": "中国政府债务/GDP分析", + "subtitle": "债务/GDP 75.7% · 2024", + "points": 6, + "html": "public/strategy/china-debt-gdp.html", + "json": "public/strategy/china-debt-gdp.json", + "hash": "acffe06524dbfee764411b8690d02393bbaa1d7ad2275a58ac132fe256ff69c8", + "bytes": 7331, + "jsonHash": "6b2c3da5e4751c0c13dc2cc10d706c08ee6400eca8a9e6636ea0a25dca374590" + }, + { + "id": "us-debt-gdp", + "kind": "strategy", + "title": "美国政府债务/GDP分析", + "subtitle": "债务/GDP 123.9% · 2024", + "points": 6, + "html": "public/strategy/us-debt-gdp.html", + "json": "public/strategy/us-debt-gdp.json", + "hash": "cfb9ec15e457633e6d13689ab9802d646201a8fba9be3fb69710370a40ace365", + "bytes": 7477, + "jsonHash": "69d6d977057faac7ab10a5f43e4f5feb5e47539d6dc77e7fd93b533653abc50a" + }, + { + "id": "a500_chart", + "kind": "dashboard", + "title": "中证 A500 综合看板", + "subtitle": "价格 / PE-PB-股息率 四面板 + 持仓 + ETF", + "html": "public/dashboards/a500_chart.html", + "hash": "43d3a8849a6a83137f07d51602972b3f51226aa4ffaf765d4423b82298dd07c2", + "bytes": 1508412 + }, + { + "id": "cni_fcf_chart", + "kind": "dashboard", + "title": "国证自由现金流看板", + "subtitle": "980092 · 可与中证自由现金流叠加对比", + "html": "public/dashboards/cni_fcf_chart.html", + "hash": "9c239f41f525959a41594578efa041dfca5c76f652b0777fe79caae023643ed3", + "bytes": 1392856 + }, + { + "id": "csi_fcf_chart", + "kind": "dashboard", + "title": "中证自由现金流看板", + "subtitle": "932365 · 可与国证自由现金流叠加对比", + "html": "public/dashboards/csi_fcf_chart.html", + "hash": "4b0b6e24b01550ae7e1ab2d85237ff6cda9349d004ec35df73511d2a32bd88ec", + "bytes": 1344984 + } + ] + } + ], + "files": [ + { + "path": "public/dashboards/a500_chart.html", + "hash": "43d3a8849a6a83137f07d51602972b3f51226aa4ffaf765d4423b82298dd07c2", + "bytes": 1508412, + "type": "text/html" + }, + { + "path": "public/dashboards/cni_fcf_chart.html", + "hash": "9c239f41f525959a41594578efa041dfca5c76f652b0777fe79caae023643ed3", + "bytes": 1392856, + "type": "text/html" + }, + { + "path": "public/dashboards/csi_fcf_chart.html", + "hash": "4b0b6e24b01550ae7e1ab2d85237ff6cda9349d004ec35df73511d2a32bd88ec", + "bytes": 1344984, + "type": "text/html" + }, + { + "path": "public/econ/china-bond-10y.html", + "hash": "3b5fef761b59afb6869fa2a73fe117fb69dcbefea3d39d7cd1859004162a52e7", + "bytes": 5630, + "type": "text/html" + }, + { + "path": "public/econ/china-bond-10y.json", + "hash": "72b493417070df9c772d6afc8604b6db99e5889d36c59d45bcd92f2dafa261ca", + "bytes": 1803, + "type": "application/json" + }, + { + "path": "public/econ/china-bond-1y.html", + "hash": "7639c558bba083cc9a25f8a156419138937d8712612dee34ed2b89890a4d9fec", + "bytes": 5628, + "type": "text/html" + }, + { + "path": "public/econ/china-bond-1y.json", + "hash": "1eb0575b5eac06c78104974b75b8f106cb92bc6d87113652f7f457ee5c988fc2", + "bytes": 1806, + "type": "application/json" + }, + { + "path": "public/econ/china-cpi-yoy.html", + "hash": "cb51b54b2f6bf172aa3e87ac368ce67c7398dbc51e2f033b0eac0d367cb62a0e", + "bytes": 5627, + "type": "text/html" + }, + { + "path": "public/econ/china-cpi-yoy.json", + "hash": "16735f992a47c448fe1b175abd15174c4c6a19df4e8f1084580f9d1882009d44", + "bytes": 1932, + "type": "application/json" + }, + { + "path": "public/econ/china-cpi.html", + "hash": "9ff611705ff82a56acbbf96e337e0a79bfe2f2a22fc8c5b852928af85d7b3de2", + "bytes": 4236, + "type": "text/html" + }, + { + "path": "public/econ/china-cpi.json", + "hash": "6007ad45542047c0e343c0cc590d9fcde63d9c820ff99f0a93f61a8560209ebf", + "bytes": 667, + "type": "application/json" + }, + { + "path": "public/econ/china-lpr-1y.html", + "hash": "5f7254dc86f98bf32cfd06d37d9b329bcff87513057583bdf669b8691a3f4bcf", + "bytes": 5327, + "type": "text/html" + }, + { + "path": "public/econ/china-lpr-1y.json", + "hash": "7300679627550b75fa2fae62888b79e6f12a8386d2d3f2dec9bd2925387d8aa7", + "bytes": 1664, + "type": "application/json" + }, + { + "path": "public/econ/china-lpr-5y.html", + "hash": "e32519d06f0ca7a601e933ce704d44d59a0ab3b2fb046a6754ad4ef3ed71b742", + "bytes": 5498, + "type": "text/html" + }, + { + "path": "public/econ/china-lpr-5y.json", + "hash": "99d4b4d900e5a99f60b294eb9fca87145ad20b44995ef2178061cc832c2599e8", + "bytes": 1693, + "type": "application/json" + }, + { + "path": "public/econ/us-bond-1y.html", + "hash": "c972cd658b5db614ec94135dc8f7816366a8ed526360514cfb53c64f9df09dfa", + "bytes": 3736, + "type": "text/html" + }, + { + "path": "public/econ/us-bond-1y.json", + "hash": "82a7af636dd597bde08bc9cb7e22ab4e9de6754ffc30614d78ba1a96e6715a4a", + "bytes": 444, + "type": "application/json" + }, + { + "path": "public/econ/usa-cpi.html", + "hash": "f4aec9f8c2aee8835166e71a7799c87c8f0288f0fc9760b85620bc98c7bd9721", + "bytes": 4187, + "type": "text/html" + }, + { + "path": "public/econ/usa-cpi.json", + "hash": "0273f88e376d166528e60338beff1e0878e98336c26c759f36daa078acb92d87", + "bytes": 616, + "type": "application/json" + }, + { + "path": "public/strategy/china-debt-gdp.html", + "hash": "acffe06524dbfee764411b8690d02393bbaa1d7ad2275a58ac132fe256ff69c8", + "bytes": 7331, + "type": "text/html" + }, + { + "path": "public/strategy/china-debt-gdp.json", + "hash": "6b2c3da5e4751c0c13dc2cc10d706c08ee6400eca8a9e6636ea0a25dca374590", + "bytes": 841, + "type": "application/json" + }, + { + "path": "public/strategy/us-debt-gdp.html", + "hash": "cfb9ec15e457633e6d13689ab9802d646201a8fba9be3fb69710370a40ace365", + "bytes": 7477, + "type": "text/html" + }, + { + "path": "public/strategy/us-debt-gdp.json", + "hash": "69d6d977057faac7ab10a5f43e4f5feb5e47539d6dc77e7fd93b533653abc50a", + "bytes": 843, + "type": "application/json" + } + ] +} \ No newline at end of file diff --git a/scripts/.fix-cron.cjs b/scripts/.fix-cron.cjs deleted file mode 100644 index c7af3e1..0000000 --- a/scripts/.fix-cron.cjs +++ /dev/null @@ -1,72 +0,0 @@ -const fs = require('fs') -const content = `import cron from 'node-cron' -import { prisma } from './db.js' -import mysql from 'mysql2/promise' -import { fetchNewsList, persistNews } from '../scripts/fetchers/news.fetcher.js' -import { checkAfterRefresh } from './alert-engine.js' - -function parseUrl(url) { - const u = new URL(url) - return { host: u.hostname, port: Number(u.port) || 3306, user: decodeURIComponent(u.username), password: decodeURIComponent(u.password), database: u.pathname.replace(/^\\//, '') } -} - -async function openConn() { - return mysql.createConnection(parseUrl(process.env.DATABASE_URL)) -} - -// 每天凌晨 3:00 (本地时间) 拉取新闻 + 跑跨源对比 -function scheduleNews() { - cron.schedule('0 3 * * *', async () => { - console.log('[cron] 3:00 触发') - try { - const list = await fetchNewsList() - const r = await persistNews(list) - console.log('[cron] 新闻入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped) - const conn = await openConn() - try { - const a = await checkAfterRefresh(conn) - console.log('[cron] 报警检测: 新增 ' + a.inserted + ' 条 (扫描 ' + a.scanned + ')') - } finally { await conn.end() } - } catch (e) { - console.error('[cron] 任务失败:', e.message) - } - }) - console.log('[cron] 已注册: 每天 03:00 拉取新闻 + 跨源报警检测') -} - -// 每 2 分钟: 拉取 komari 节点 + 服务自检 -function scheduleMonitors() { - cron.schedule('*/2 * * * *', async () => { - try { - const k = require('./komari-monitor.cjs') - const r = await k.runOnce() - console.log('[cron] komari: nodes=' + r.nodes + ' snapshots=' + r.snapshots + ' history=' + r.history + ' failed=' + r.failed) - } catch (e) { console.error('[cron] komari 任务失败:', e.message) } - try { - const h = require('./health-check.cjs') - const arr = await h.runOnce() - const down = arr.filter(x => x.status !== 'up').map(x => x.name) - console.log('[cron] health: ' + arr.length + ' 项, down=' + (down.length ? down.join(',') : '无')) - } catch (e) { console.error('[cron] health 任务失败:', e.message) } - }) - console.log('[cron] 已注册: 每 2 分钟 komari + 服务自检') -} - -// 每小时: 清理 24h 前的 komari 历史 -function scheduleMonitorCleanup() { - cron.schedule('0 * * * *', async () => { - try { - const k = require('./komari-monitor.cjs') - const n = await k.cleanupOld() - console.log('[cron] 监控历史清理: 删除 ' + n + ' 条') - } catch (e) { console.error('[cron] 监控清理失败:', e.message) } - }) - console.log('[cron] 已注册: 每小时清理 24h 前监控历史') -} - -scheduleNews() -scheduleMonitors() -scheduleMonitorCleanup() -` -fs.writeFileSync('server/cron.js', content, 'utf8') -console.log('OK len=' + content.length) diff --git a/scripts/cleanup-duplicates.cjs b/scripts/cleanup-duplicates.cjs deleted file mode 100644 index da5367c..0000000 --- a/scripts/cleanup-duplicates.cjs +++ /dev/null @@ -1,17 +0,0 @@ -require('dotenv').config(); -const mysql = require('mysql2/promise'); -(async () => { - const u = new URL(process.env.DATABASE_URL); - const c = await mysql.createConnection({ - host: u.hostname, port: Number(u.port), - user: decodeURIComponent(u.username), - password: decodeURIComponent(u.password), - database: u.pathname.replace(/^\//, ''), - }); - // 保留 id 最小的, 删掉其余重名行 - for (const t of ['Strategy', 'Indicator']) { - const [r] = await c.query('DELETE FROM ' + t + ' WHERE id NOT IN (SELECT min_id FROM (SELECT MIN(id) AS min_id FROM ' + t + ' GROUP BY name) AS keep)'); - console.log('[' + t + '] 删除 ' + r.affectedRows + ' 条重复行'); - } - await c.end(); -})().catch(e=>{console.error(e);process.exit(1)}); diff --git a/scripts/deploy/prod.sh b/scripts/deploy/prod.sh new file mode 100644 index 0000000..9c739b5 --- /dev/null +++ b/scripts/deploy/prod.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# 0.2.1 生产部署 (master 分支 CI 调用; 也可在服务器手工执行) +# +# 架构: app(3001 对外) + pg(内网) + powersync(内网), 见 deploy/prod/ +# 幂等: 迁移 -> publication -> 起栈; 任一环节失败则退出, 不切换流量 +# +# 所需环境变量 (由 CI secrets 注入): +# PG_PASSWORD, PS_STORAGE_PASSWORD, JWT_SECRET, POWERSYNC_JWT_SECRET, +# POWERSYNC_JWT_KEY_B64, PUBLIC_URL(可选, 默认 https://iboard.honor3.com) +set -euo pipefail + +ROOT="${ROOT:-/app/iboard-prod}" +COMPOSE_FILE="$ROOT/deploy/prod/docker-compose.yml" +ENV_FILE="$ROOT/deploy/prod/.env" +PROJECT="iboard-prod" + +log() { echo "[deploy] $*"; } +die() { echo "[deploy][ERROR] $*" >&2; exit 1; } + +for v in PG_PASSWORD PS_STORAGE_PASSWORD JWT_SECRET POWERSYNC_JWT_SECRET POWERSYNC_JWT_KEY_B64; do + [ -n "${!v:-}" ] || die "缺少环境变量 $v (请在 Gitea Actions secrets 配置)" +done +[ -f "$COMPOSE_FILE" ] || die "找不到 $COMPOSE_FILE (源码需上传到 $ROOT)" + +log "写入 $ENV_FILE" +umask 077 +cat > "$ENV_FILE" </dev/null 2>&1; then + break + fi + sleep 2 +done + +log "执行数据库迁移" +docker run --rm --network "${PROJECT}_default" \ + -e DATABASE_URL="postgresql://iboard:${PG_PASSWORD}@pg:5432/iboard" \ + iboard:latest npx prisma migrate deploy + +log "确保逻辑复制 publication 存在" +docker compose --project-name "$PROJECT" --env-file "$ENV_FILE" -f "$COMPOSE_FILE" exec -T pg \ + psql -U iboard -d iboard -v ON_ERROR_STOP=1 -f /dev/stdin < sql/publication.sql + +log "启动应用与同步服务" +docker compose --project-name "$PROJECT" --env-file "$ENV_FILE" -f "$COMPOSE_FILE" up -d app powersync + +log "等待健康检查" +for i in $(seq 1 30); do + if curl -fsS --max-time 3 http://127.0.0.1:3001/api/health >/dev/null 2>&1; then + log "健康检查通过" + exit 0 + fi + sleep 2 +done +die "应用未通过健康检查, 请查看 docker compose logs" diff --git a/scripts/dev-windows-live.mjs b/scripts/dev-windows-live.mjs new file mode 100644 index 0000000..765fddb --- /dev/null +++ b/scripts/dev-windows-live.mjs @@ -0,0 +1,172 @@ +#!/usr/bin/env node +// Windows 外壳「本地实时预览」: 不打包, 直接把新前端挂到已安装外壳上跑 +// +// 原理: 外壳用 tauri-plugin-hotswap, 启动时先看 %APPDATA%\\hotswap\ +// 是否有生效的覆盖包, 有就从该目录读前端资源, 没有才回退到 exe 内置的 dist。 +// 所以只要构建 dist 并写进覆盖目录, 重启外壳即可看到改动 —— 无需 tauri build, +// 无需重打 NSIS/APK, 也不用重装。 +// +// 用法: +// npm run win:dev # 构建(shell 模式) + 挂载 + 重启外壳 +// npm run win:dev -- --no-build # 复用现有 dist, 只挂载+重启 (最快) +// npm run win:dev -- --no-launch # 只挂载, 不重启 +// npm run win:dev -- --exe "D:\path\iBoard.exe" +// npm run win:dev -- --clear # 卸载覆盖, 外壳回到内置版本 +// +// 覆盖目录里生成的东西 (全部可删): +// hotswap/seq-<时间戳>/ 本次前端 (dist 内容) + hotswap-meta.json +// hotswap/current 指向 seq-<时间戳> 的指针文件 +// +// 注意: 覆盖目录是外壳的真实 OTA 落地目录, 挂载期间「我的」页会显示本地版本; +// --clear 会清掉本脚本产生的覆盖, 恢复正常 OTA 行为。 +import { spawnSync } from 'node:child_process' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const args = process.argv.slice(2) +const has = (f) => args.includes(f) +const val = (f, d) => { const i = args.indexOf(f); return i >= 0 && args[i + 1] ? args[i + 1] : d } + +const CLEAR = has('--clear') +const NO_BUILD = has('--no-build') +const NO_LAUNCH = has('--no-launch') + +const conf = JSON.parse(fs.readFileSync(path.join(root, 'src-tauri', 'tauri.conf.json'), 'utf8')) +const APP_ID = conf.identifier // 覆盖目录名 = 外壳 identifier +const BINARY_VERSION = conf.version // 外壳版本: min_binary_version 必须与它相等 +const APP_VERSION = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version + +const HOTSWAP_DIR = path.join(os.homedir(), 'AppData', 'Roaming', APP_ID, 'hotswap') + +function log(msg) { console.log(`[win:dev] ${msg}`) } +function die(msg) { console.error(`[win:dev] 错误: ${msg}`); process.exit(1) } + +/** 找已安装的外壳 exe */ +function findExe() { + const explicit = val('--exe', '') + if (explicit) return explicit + const candidates = [ + 'C:\\Program Files\\iboard\\iBoard.exe', + 'C:\\Program Files\\iBoard\\iBoard.exe', + path.join(root, 'src-tauri', 'target', 'release', 'iBoard.exe'), + path.join(root, 'src-tauri', 'target', 'debug', 'iBoard.exe'), + ] + return candidates.find((p) => fs.existsSync(p)) || '' +} + +/** 清掉覆盖目录里本脚本(或上次 OTA)留下的生效版本 */ +function clearOverride() { + if (!fs.existsSync(HOTSWAP_DIR)) { log('覆盖目录不存在, 无需清理'); return } + const current = path.join(HOTSWAP_DIR, 'current') + if (fs.existsSync(current)) { fs.rmSync(current); log('已移除 current 指针') } + let n = 0 + for (const e of fs.readdirSync(HOTSWAP_DIR)) { + if (/^seq-\d+$/.test(e)) { fs.rmSync(path.join(HOTSWAP_DIR, e), { recursive: true, force: true }); n++ } + } + log(`已移除 ${n} 个覆盖版本目录 -> 外壳将回退到内置前端`) +} + +/** 构建前端 (shell 模式: VITE_API_BASE 指向远端后端, 与打包口径一致) */ +function buildFrontend() { + const vite = path.join(root, 'node_modules', 'vite', 'bin', 'vite.js') + if (!fs.existsSync(vite)) die('未找到 vite, 请先 npm install') + // esbuild 需要可写的临时目录; 指向仓库内, 避免系统 TEMP 权限问题 + const tmp = path.join(root, '.esbuild-tmp') + fs.mkdirSync(tmp, { recursive: true }) + log('构建前端 (vite build --mode shell) ...') + const r = spawnSync(process.execPath, [vite, 'build', '--mode', 'shell'], { + cwd: root, + stdio: 'inherit', + env: { ...process.env, TEMP: tmp, TMP: tmp }, + }) + if (r.status !== 0) die('前端构建失败') +} + +/** 把 dist 挂成生效的 hotswap 覆盖包 */ +function installOverride() { + const dist = path.join(root, 'dist') + if (!fs.existsSync(path.join(dist, 'index.html'))) die('dist/index.html 不存在, 请先构建') + + fs.mkdirSync(HOTSWAP_DIR, { recursive: true }) + const seq = Date.now() // 单调递增; 取大值可让 OTA 检查认为"已是最新", 不会覆盖本次本地版本 + const dirName = `seq-${seq}` + const dir = path.join(HOTSWAP_DIR, dirName) + + fs.rmSync(dir, { recursive: true, force: true }) + fs.mkdirSync(dir, { recursive: true }) + fs.cpSync(dist, dir, { recursive: true }) + + // 元数据字段见 tauri-plugin-hotswap/src/manifest.rs + // min_binary_version 必须 == 外壳版本: 默认 DiscardOnUpgrade 策略在 + // binary > min 时会丢弃缓存; 而安全线又要求 binary >= min, 故只能取等号 + // confirmed 必须为 true: 默认 SingleLaunch 策略对未确认版本会在启动时立刻回滚 + fs.writeFileSync(path.join(dir, 'hotswap-meta.json'), JSON.stringify({ + version: `${APP_VERSION}-local`, + sequence: seq, + min_binary_version: BINARY_VERSION, + confirmed: true, + unconfirmed_launch_count: 0, + }, null, 2)) + + fs.writeFileSync(path.join(HOTSWAP_DIR, 'current'), dirName) + + // 保留策略只留最近 2 个, 顺手清掉更早的本地版本 + const olds = fs.readdirSync(HOTSWAP_DIR) + .filter((e) => /^seq-\d+$/.test(e) && e !== dirName) + .sort() + .slice(0, -1) + for (const o of olds) fs.rmSync(path.join(HOTSWAP_DIR, o), { recursive: true, force: true }) + + log(`已挂载覆盖包: ${dirName}`) + log(` 版本 ${APP_VERSION}-local (外壳 ${BINARY_VERSION})`) + return dirName +} + +/** 结束正在运行的外壳 (需要重启才会读取覆盖包) */ +function stopRunning(exe) { + const r = spawnSync('tasklist', ['/FI', 'IMAGENAME eq iBoard.exe', '/NH'], { encoding: 'utf8' }) + const out = r.stdout || '' + if (!/iBoard\.exe/i.test(out)) return false + log('检测到外壳正在运行, 结束它以便重启 ...') + spawnSync('taskkill', ['/IM', 'iBoard.exe', '/F'], { stdio: 'inherit' }) + return true +} + +function launch(exe) { + if (!exe) { + log('未找到外壳 exe, 跳过启动。可用 --exe 指定路径, 或手动启动后再验证。') + return + } + log(`启动: ${exe}`) + // 用 Start-Process 让外壳脱离本进程, 脚本退出后应用继续运行 + const ps = `Start-Process -FilePath '${exe.replace(/'/g, "''")}'` + const r = spawnSync('powershell.exe', ['-NoProfile', '-Command', ps], { stdio: 'inherit' }) + if (r.status !== 0) log('启动命令返回非 0, 请手动启动外壳') +} + +// ---------- main ---------- +// --no-launch 表示"不碰外壳进程": 既不结束也不启动, 只改覆盖目录 +if (CLEAR) { + if (!NO_LAUNCH) stopRunning() + clearOverride() + if (!NO_LAUNCH) launch(findExe()) + else log('已清理。外壳下次启动即回到内置前端 (当前若在运行, 重启后生效)。') + process.exit(0) +} + +if (!NO_BUILD) buildFrontend() +installOverride() + +const exe = findExe() +if (!NO_LAUNCH) { + stopRunning(exe) + launch(exe) +} else { + log('已挂载。外壳下次启动生效 (当前若在运行, 重启后生效)。') +} + +log('完成。改完 UI 后重跑 `npm run win:dev` 即可看到新版本 (想更快可加 --no-build 复用 dist)。') +log('恢复到内置版本: `npm run win:dev -- --clear`') diff --git a/scripts/discover-mcp-tools.cjs b/scripts/discover-mcp-tools.cjs deleted file mode 100644 index 6b4345c..0000000 --- a/scripts/discover-mcp-tools.cjs +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 列出东方财富妙想 MCP 暴露的所有工具 - * 用法: npm run data:discover - * 输出: tool 名称 + 描述 + 输入 schema - */ -require('dotenv').config() - -async function main() { - if (!process.env.EM_API_KEY) { - console.error('缺少 EM_API_KEY (在 .env 配)') - process.exit(1) - } - const { listAvailableTools } = await import('./fetchers/eastmoney-mcp.fetcher.js') - console.log('[Discover] 调妙想 MCP tools/list...') - const tools = await listAvailableTools() - console.log(`[Discover] 找到 ${tools.length} 个工具\n`) - for (const t of tools) { - console.log(`── ${t.name} ${'─'.repeat(Math.max(0, 60 - t.name.length))}`) - if (t.description) console.log(` ${t.description}`) - if (t.inputSchema) { - console.log(' inputSchema:') - const s = JSON.stringify(t.inputSchema, null, 2) - s.split('\n').forEach((line) => console.log(' ' + line)) - } - console.log('') - } -} - -main().catch((e) => { - console.error('[Discover] 失败:', e.message) - process.exit(1) -}) diff --git a/scripts/etl-mariadb-to-pg.mjs b/scripts/etl-mariadb-to-pg.mjs index ee5f178..866fa6a 100644 --- a/scripts/etl-mariadb-to-pg.mjs +++ b/scripts/etl-mariadb-to-pg.mjs @@ -27,9 +27,6 @@ const TABLES = [ ['TaskMember', 'taskMember'], ['TaskTag', 'taskTag'], ['Article', 'article'], - ['Strategy', 'strategy'], - ['Indicator', 'indicator'], - ['IndicatorPoint', 'indicatorPoint'], ['News', 'news'], ['Alert', 'alert'], ['ServerNode', 'serverNode'], @@ -37,7 +34,6 @@ const TABLES = [ ['ServiceCheck', 'serviceCheck'], ['Econ_SowInventory', 'econ_SowInventory'], ['ScraperCache', 'scraperCache'], - ['OilPriceHistory', 'oilPriceHistory'], ['MihomeAccount', 'mihomeAccount'], ['MihomeHome', 'mihomeHome'], ['MihomeDevice', 'mihomeDevice'], diff --git a/scripts/fetch-one-year.js b/scripts/fetch-one-year.js deleted file mode 100644 index a41d91e..0000000 --- a/scripts/fetch-one-year.js +++ /dev/null @@ -1,79 +0,0 @@ - -import { spawn } from 'child_process'; - -async function runScraper(scraperPath, startDate, endDate) { - return new Promise((resolve, reject) => { - const args = [scraperPath]; - if (startDate) { - args.push('--start', startDate); - } - if (endDate) { - args.push('--end', endDate); - } - - const proc = spawn('bun', args, { stdio: 'inherit' }); - proc.on('close', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error(`Process exited with code ${code}`)); - } - }); - proc.on('error', (err) => { - reject(err); - }); - }); -} - -async function fetchOneYearData() { - console.log('='.repeat(70)); - console.log('📊 获取近一年数据'); - console.log('='.repeat(70)); - - const currentDate = new Date(); - const startDate = new Date(currentDate); - startDate.setFullYear(startDate.getFullYear() - 1); - - const startDateStr = startDate.toISOString().split('T')[0]; - const endDateStr = currentDate.toISOString().split('T')[0]; - - console.log(`时间范围: ${startDateStr} 至 ${endDateStr}`); - console.log(''); - - console.log('='.repeat(70)); - console.log('💱 开始获取 USD-CNY 汇率数据...'); - console.log('='.repeat(70)); - - let usdSuccess = false; - try { - await runScraper('./scraper/usd-cny-rate.scraper.js', startDateStr, endDateStr); - console.log(`✅ USD-CNY 汇率数据更新完成`); - usdSuccess = true; - } catch (error) { - console.error(`❌ USD-CNY 汇率数据更新失败:`, error.message); - } - - console.log(''); - console.log('='.repeat(70)); - console.log('📈 开始获取沪深300指数数据...'); - console.log('='.repeat(70)); - - let sh300Success = false; - try { - await runScraper('./scraper/sh300.scraper.js', startDateStr, endDateStr); - console.log(`✅ 沪深300指数数据更新完成`); - sh300Success = true; - } catch (error) { - console.error(`❌ 沪深300指数数据更新失败:`, error.message); - } - - console.log(''); - console.log('='.repeat(70)); - console.log('📊 数据获取完成'); - console.log('='.repeat(70)); - console.log(`USD-CNY 汇率: ${usdSuccess ? '✅ 成功' : '❌ 失败'}`); - console.log(`沪深300指数: ${sh300Success ? '✅ 成功' : '❌ 失败'}`); - console.log('='.repeat(70)); -} - -fetchOneYearData(); diff --git a/scripts/fetchers/eastmoney-mcp.fetcher.js b/scripts/fetchers/eastmoney-mcp.fetcher.js deleted file mode 100644 index 053dca8..0000000 --- a/scripts/fetchers/eastmoney-mcp.fetcher.js +++ /dev/null @@ -1,303 +0,0 @@ -// 东方财富妙想 MCP 数据源 -// 协议: StreamableHttp (MCP 2025-03-26 spec), HTTP POST + JSON-RPC 2.0 -// - 认证: header `em_api_key: ` -// - 妙想 MCP 是"自然语言驱动": 传 query 字符串, 内部 LLM 解析 + 调底层数据接口 -// - 重要: 每次调用 5-20s + LLM token 成本, 必须配合"DB 优先" (refresh 时已存在的指标跳过) -// -// 需要的 .env: -// EM_API_KEY=xxx 必填 -// EASTMONEY_MCP_BASE=... 可选, 默认 https://mxapi.eastmoney.com/mxds/mcp -// -// INDICATORS 数组: 配置要拉的指标 -// 每项: -// name → Indicator.name (主键) -// groupName → Indicator.groupName -// title → data.title -// unit → data.unit -// tool → MCP tool 名 -// query → 自然语言查询 -// pickSheet(sheets) → 选一张 sheet (默认取第一张) -// parseSheet(sheet) → [{period, value}, ...] 默认按宽表转长表 (列=日期, 行=值) - -import axios from 'axios' - -const DEFAULT_BASE = 'https://mxapi.eastmoney.com/mxds/mcp' -const TIMEOUT = 60000 - -// ====== INDICATORS 配置 ====== -// 跑 npm run data:discover 看可用 tool, 再加新指标 -const INDICATORS = [ - { - name: 'china-cpi-yoy', - groupName: 'china-macro', - title: '中国 CPI 同比', - unit: '%', - tool: 'mx_macro_data', - query: '查询中国 2015 年至今 CPI 月度同比数据,每一个月的数值', - pickSheet: (sheets) => - sheets.find((s) => s.items?.[0]?.[0]?.includes('CPI') && s.items[0][0].includes('同比')) - || sheets.find((s) => s.sheetName?.includes('宏观数据')) - || sheets[0], - }, - { - name: 'china-gdp-yoy', - groupName: 'china-macro', - title: '中国 GDP 同比', - unit: '%', - tool: 'mx_macro_data', - query: '查询中国 2015-2025 年 GDP 不变价同比增速,季度数据,每一季度的数值', - pickSheet: (sheets) => - sheets.find((s) => s.items?.[0]?.[0]?.includes('GDP') && s.items[0][0].includes('同比')) - || sheets[0], - }, - { - // 国债收益率: MCP 返回日频, 用月末降采样成月频点 - name: 'china-bond-10y', - groupName: 'china-macro', - title: '中国国债 10 年期收益率', - unit: '%', - frequency: 'month', - tool: 'mx_macro_data', - query: '查询中国10年期国债到期收益率 2024年至今 每日数据', - pickSheet: (sheets) => - sheets.find((s) => s.items?.some?.((row) => String(row[0] || '').includes('国债到期收益率:10年'))) - || sheets[0], - parseSheet: (sheet) => monthEndSample(sheet, '国债到期收益率:10年'), - }, - { - name: 'china-bond-1y', - groupName: 'china-macro', - title: '中国国债 1 年期收益率', - unit: '%', - frequency: 'month', - tool: 'mx_macro_data', - query: '查询中国1年期国债到期收益率 2024年至今 每日数据', - pickSheet: (sheets) => - sheets.find((s) => s.items?.some?.((row) => String(row[0] || '').includes('国债到期收益率:1年'))) - || sheets[0], - parseSheet: (sheet) => monthEndSample(sheet, '国债到期收益率:1年'), - }, - { - name: 'china-lpr-1y', - groupName: 'china-macro', - title: '中国 LPR 1 年期', - unit: '%', - frequency: 'month', - tool: 'mx_macro_data', - query: '查询中国LPR 1年期贷款市场报价利率 2024年至今 月度数据,每一个月的数值', - pickSheet: (sheets) => - sheets.find((s) => s.sheetName?.includes('宏观数据(月)')) - || sheets[0], - parseSheet: (sheet) => pickRowByKeyword(sheet, '(LPR):1年'), - }, - { - name: 'china-lpr-5y', - groupName: 'china-macro', - title: '中国 LPR 5 年期', - unit: '%', - frequency: 'month', - tool: 'mx_macro_data', - query: '查询中国LPR 5年期以上贷款市场报价利率 2024年至今 月度数据,每一个月的数值', - pickSheet: (sheets) => - sheets.find((s) => s.sheetName?.includes('宏观数据(月)')) - || sheets[0], - parseSheet: (sheet) => pickRowByKeyword(sheet, '(LPR):5年'), - }, -] - -export const META = { - name: 'eastmoney-mcp', - description: '东方财富妙想 MCP (StreamableHttp, NL 查询)', - docs: 'https://mxapi.eastmoney.com/mxds/doc/mcp-install.md', -} - -// ====== StreamableHttp 客户端 ====== - -class McpClient { - constructor(baseUrl, apiKey) { - this.baseUrl = baseUrl - this.apiKey = apiKey - this.id = 0 - } - async _request(method, params) { - const payload = { jsonrpc: '2.0', id: ++this.id, method, ...(params ? { params } : {}) } - const resp = await axios.post(this.baseUrl, payload, { - timeout: TIMEOUT, - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json, text/event-stream', - 'em_api_key': this.apiKey, - }, - responseType: 'text', - transformResponse: [(d) => d], - validateStatus: () => true, - }) - if (resp.status >= 400) { - throw new Error(`HTTP ${resp.status}: ${String(resp.data).slice(0, 300)}`) - } - const parsed = parseMcpResponse(String(resp.data || '')) - // JSON-RPC 信封 {jsonrpc,id,result|error}, 统一解包 result - if (parsed.error) { - throw new Error(`MCP error ${parsed.error.code}: ${parsed.error.message}`) - } - return parsed.result || parsed - } - async listTools() { - const r = await this._request('tools/list') - return r.tools || [] - } - async callTool(name, args = {}) { - const r = await this._request('tools/call', { name, arguments: args }) - if (r.isError) { - const msg = (r.content || []).map((c) => c.text || '').join('; ') - throw new Error(`tool ${name} error: ${msg || JSON.stringify(r).slice(0, 200)}`) - } - return r - } -} - -// 解析响应: JSON 或 SSE -function parseMcpResponse(text) { - const t = text.trim() - if (t.startsWith('{')) return JSON.parse(t) - if (t.startsWith('data:')) { - const lines = t.split('\n').filter((l) => l.startsWith('data:')) - let last = null - for (const line of lines) { - const json = line.slice(5).trim() - if (!json || json === '[DONE]') continue - try { last = JSON.parse(json) } catch { /* skip */ } - } - if (!last) throw new Error('SSE 无有效 data 块: ' + text.slice(0, 200)) - return last - } - throw new Error('未知响应格式: ' + t.slice(0, 200)) -} - -// 默认 sheet 解析: 宽表 (列=日期, 行=值) → 长表 -// 例: { columns:["指标","来源","2024-12","2024-11"], items:[["CPI","统计局","0.1","0.2"]] } -// → [{period:"2024-12", value:0.1}, {period:"2024-11", value:0.2}] -// 返回时反转成从老到新 -function defaultParseSheet(sheet) { - if (!sheet || !sheet.columns || !sheet.items || !sheet.items[0]) return [] - const cols = sheet.columns - const row = sheet.items[0] - const dates = cols.slice(2) // 跳过 [指标名, 来源] - const values = row.slice(2) - const out = [] - for (let i = 0; i < dates.length; i++) { - const v = parseFloat(values[i]) - if (!isNaN(v)) out.push({ period: dates[i], value: v }) - } - return out.reverse() // 从老到新 -} - -// 宽表 → 长表, 指定 item 行 (按指标全名前缀匹配), 日频列降采样为月末值 -// 例: columns=["指标","来源","2026-08-05","2026-08-04",...] → [{period:"2024-01",value},...] -function monthEndSample(sheet, keyword) { - if (!sheet || !sheet.columns || !sheet.items) return [] - const row = sheet.items.find((r) => String(r[0] || '').includes(keyword)) - if (!row) return [] - const cols = sheet.columns - const dates = cols.slice(2) // 新 → 老 - const values = row.slice(2) - const out = [] - const seen = new Set() - for (let i = 0; i < dates.length; i++) { - const v = parseFloat(values[i]) - const m = String(dates[i] || '').slice(0, 7) // "2024-01-02" → "2024-01" - if (!/^\d{4}-\d{2}$/.test(m) || Number.isNaN(v)) continue - if (seen.has(m)) continue // 列是降序, 第一次遇到的即当月最后一个交易日 - seen.add(m) - out.push({ period: m, value: v }) - } - return out.reverse() // 从老到新 -} - -// 宽表 → 长表, 指定 item 行 (按指标全名前缀匹配), 保留原频率 (月/季/年) -function pickRowByKeyword(sheet, keyword) { - if (!sheet || !sheet.columns || !sheet.items) return [] - const row = sheet.items.find((r) => String(r[0] || '').includes(keyword)) - if (!row) return [] - const cols = sheet.columns - const dates = cols.slice(2) - const values = row.slice(2) - const out = [] - for (let i = 0; i < dates.length; i++) { - const v = parseFloat(values[i]) - if (!Number.isNaN(v)) out.push({ period: String(dates[i]), value: v }) - } - return out.reverse() // 从老到新 -} - -// ====== fetcher 入口 ====== - -export async function listAvailableTools() { - const key = process.env.EM_API_KEY - if (!key) throw new Error('缺少 EM_API_KEY') - const base = process.env.EASTMONEY_MCP_BASE || DEFAULT_BASE - const client = new McpClient(base, key) - return client.listTools() -} - -// opts.skipNames: Set — DB 已存在的指标名, 跳过不调 MCP (省 token) -// opts.onlyNames: Set — 白名单, 只跑这些 (用于补跑单个) -export async function fetchAll(opts = {}) { - if (INDICATORS.length === 0) { - throw new Error('eastmoney-mcp: INDICATORS 数组为空, 加新指标见文件顶部注释') - } - const skip = opts.skipNames || new Set() - const force = opts.forceNames || new Set() - const only = opts.onlyNames || null - - const key = process.env.EM_API_KEY - if (!key) throw new Error('缺少 EM_API_KEY') - const base = process.env.EASTMONEY_MCP_BASE || DEFAULT_BASE - const client = new McpClient(base, key) - - const results = [] - for (const cfg of INDICATORS) { - if (skip.has(cfg.name) && !force.has(cfg.name)) { - console.log(`[MCP] ${cfg.name} 跳过 (DB 已存在)`) - continue - } - if (only && !only.has(cfg.name)) continue - try { - console.log(`[MCP] ${cfg.name} 调用 ${cfg.tool}: ${cfg.query}`) - const r = await client.callTool(cfg.tool, { query: cfg.query }) - const text = (r.content || []).map((c) => c.text || '').join('') - let payload - try { payload = JSON.parse(text) } catch { payload = { raw: text } } - const sheets = payload.data || (Array.isArray(payload) ? payload : [payload]) - const sheet = cfg.pickSheet ? cfg.pickSheet(sheets) : sheets[0] - const parsed = cfg.parseSheet ? cfg.parseSheet(sheet) : defaultParseSheet(sheet) - if (parsed.length === 0) { - console.log(`[MCP] ${cfg.name} 返回空 rows, 跳过`) - continue - } - results.push({ - name: cfg.name, - groupName: cfg.groupName, - data: { - unit: cfg.unit, - title: cfg.title, - source: 'eastmoney-mcp', - rows: parsed, - }, - }) - console.log(`[MCP] ${cfg.name} OK, ${parsed.length} rows (${parsed[0]?.period}..${parsed.at(-1)?.period})`) - } catch (e) { - console.error(`[MCP] ${cfg.name} FAIL: ${e.message}`) - } - } - // 全部被 DB 优先跳过时, 返回显式标记而不是空数组 (避免被误判为失败) - if (results.length === 0 && INDICATORS.every((cfg) => skip.has(cfg.name) && !force.has(cfg.name))) { - return { skipped: true, reason: 'DB 已存在全部指标' } - } - return results -} - - - - - diff --git a/scripts/fetchers/index.js b/scripts/fetchers/index.js deleted file mode 100644 index 1de51c0..0000000 --- a/scripts/fetchers/index.js +++ /dev/null @@ -1,83 +0,0 @@ -// Fetcher 注册表: 列出所有启用的数据源 -// 加新数据源只要: -// 1. 写一个 scripts/fetchers/.fetcher.js, 导出 META + fetchAll -// 2. 在本文件 FETCHERS 数组里加一行 -// 3. 完事, refresh 脚本会自动跑 - -// 每项的 schema: -// name: 数据源标识 (用于日志) -// module: fetcher 模块路径 (相对 scripts/fetchers/) -// enabled: true/false, 可由 env 关闭 (如 ENABLE_EASTMONEY_MCP=0) -// env: 需要的环境变量名列表, 缺一即跳过 -export const FETCHERS = [ - { - name: 'wb-indicators', - module: './wb-indicators.fetcher.js', - enabled: true, - env: [], - }, - { - name: 'eastmoney-mcp', - module: './eastmoney-mcp.fetcher.js', - // 默认开启, 有 APIKey 才真正能跑 (fetcher 内部会自检) - enabled: process.env.ENABLE_EASTMONEY_MCP !== '0', - env: ['EM_API_KEY'], - }, - // 后续在这里追加: - // { name: 'fred', module: './fred.fetcher.js', enabled: true, env: ['FRED_API_KEY'] }, - // { name: 'tushare', module: './tushare.fetcher.js', enabled: true, env: ['TUSHARE_TOKEN'] }, -] - -// 校验环境变量, 返回 { ok, missing[] } -export function checkEnv(required) { - const missing = required.filter((k) => !process.env[k] || !process.env[k].trim()) - return { ok: missing.length === 0, missing } -} - -// 加载并运行所有启用的 fetcher, 返回 [{ name, ok, items, error, skipped }] -// 错误隔离: 一个 fetcher 挂了不影响其他 -// opts.skipNames: Set — DB 优先, 已存在的指标名, 透传给 fetcher -// opts.forceNames: Set — 强制刷新名单, 即使 skipNames 里有也照拉 -export async function runFetchers(opts = {}) { - const results = [] - for (const meta of FETCHERS) { - if (!meta.enabled) { - results.push({ name: meta.name, skipped: true, reason: 'disabled' }) - console.log(`[Fetcher] ${meta.name} 跳过 (disabled)`) - continue - } - const envCheck = checkEnv(meta.env) - if (!envCheck.ok) { - results.push({ name: meta.name, skipped: true, reason: `缺少 env: ${envCheck.missing.join(', ')}` }) - console.log(`[Fetcher] ${meta.name} 跳过 (${envCheck.missing.join(', ')})`) - continue - } - try { - const mod = await import(meta.module) - if (!mod.META || !mod.fetchAll) { - throw new Error('fetcher 必须导出 META 和 fetchAll') - } - console.log(`[Fetcher] ${meta.name} 开始...`) - const out = await mod.fetchAll({ skipNames: opts.skipNames, onlyNames: opts.onlyNames, forceNames: opts.forceNames }) - // fetcher 可返回 { skipped: true, reason } 表示"全部被 DB 优先跳过", 不算失败 - if (out && out.skipped) { - results.push({ name: meta.name, skipped: true, reason: out.reason }) - console.log(`[Fetcher] ${meta.name} 跳过 (${out.reason})`) - continue - } - const items = Array.isArray(out) ? out : [] - // 校验返回结构 - const valid = items.filter((it) => it && it.name && it.data && Array.isArray(it.data.rows)) - if (valid.length === 0) { - throw new Error('fetcher 未返回任何有效指标 (需包含 name/data.rows)') - } - console.log(`[Fetcher] ${meta.name} 拉取 ${valid.length} 个指标`) - results.push({ name: meta.name, ok: true, items: valid }) - } catch (e) { - results.push({ name: meta.name, ok: false, error: e.message }) - console.error(`[Fetcher] ${meta.name} 失败: ${e.message}`) - } - } - return results -} - diff --git a/scripts/fetchers/sh300.fetcher.js b/scripts/fetchers/sh300.fetcher.js deleted file mode 100644 index 138c572..0000000 --- a/scripts/fetchers/sh300.fetcher.js +++ /dev/null @@ -1,168 +0,0 @@ -import axios from 'axios'; -import { fileURLToPath } from 'url'; - -async function fetch(startDate = null, endDate = null, retryCount = 3) { - try { - const currentDate = new Date(); - - if (!startDate) { - startDate = new Date(currentDate); - startDate.setDate(startDate.getDate() - 30); - startDate = startDate.toISOString().split('T')[0]; - } - - if (!endDate) { - endDate = currentDate.toISOString().split('T')[0]; - } - - console.log('正在获取沪深300指数数据...'); - console.log(`时间范围: ${startDate} 至 ${endDate}`); - console.log(`数据源: 腾讯财经API`); - - // 沪深300指数代码 - const symbol = '000300'; - - // 使用腾讯财经的API端点获取历史数据 - const response = await axios.get('https://web.ifzq.gtimg.cn/appstock/app/kline/kline?param=sh' + symbol + ',day,' + startDate + ',' + endDate + ',640', { - headers: { - 'Accept': 'application/json', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' - }, - timeout: 10000 // 设置10秒超时 - }); - - console.log(`\nAPI状态: ${response.status}`); - - const data = response.data; - console.log(`响应数据:`, JSON.stringify(data, null, 2)); - - const allData = []; - - if (data && data.data && data.data['sh' + symbol] && data.data['sh' + symbol].day) { - const klineData = data.data['sh' + symbol].day; - console.log(`数据条数: ${klineData.length}`); - - for (const item of klineData) { - allData.push({ - date: item[0], - open: parseFloat(item[1]), - close: parseFloat(item[2]), - high: parseFloat(item[3]), - low: parseFloat(item[4]), - volume: parseFloat(item[5]), - amount: 0, // 腾讯财经API没有返回成交额数据,设置为0 - source: '腾讯财经API', - fetchTime: new Date().toISOString() - }); - } - } else { - console.log('数据条数: 0'); - } - - allData.sort((a, b) => a.date.localeCompare(b.date)); - - console.log(`\n共获取到 ${allData.length} 条沪深300指数数据`); - - return { - symbol: 'sh000300', - name: '沪深300指数', - startDate, - endDate, - count: allData.length, - data: allData, - fetchTime: new Date().toISOString(), - note: '数据来源: 腾讯财经API' - }; - - } catch (error) { - console.error('获取沪深300指数数据失败:', error.message); - if (error.response) { - console.error(`HTTP状态码: ${error.response.status}`); - console.error(`响应数据:`, error.response.data); - } - - // 重试机制 - if (retryCount > 0) { - console.log(`正在重试... (剩余重试次数: ${retryCount - 1})`); - // 等待1秒后重试 - await new Promise(resolve => setTimeout(resolve, 1000)); - return fetch(startDate, endDate, retryCount - 1); - } - - throw error; - } -} - -export { - fetch -}; - -const isMainModule = () => { - const currentFile = fileURLToPath(import.meta.url); - const mainFile = process.argv[1]; - return currentFile === mainFile; -}; - -function parseCommandLineArgs() { - const args = process.argv.slice(2); - let startDate = null; - let endDate = null; - - for (let i = 0; i < args.length; i++) { - if (args[i] === '--start' && i + 1 < args.length) { - startDate = args[i + 1]; - i++; - } else if (args[i] === '--end' && i + 1 < args.length) { - endDate = args[i + 1]; - i++; - } else if (args[i] === '--help') { - console.log('使用方法:'); - console.log(' bun sh300.fetcher.js [选项]'); - console.log(''); - console.log('选项:'); - console.log(' --start YYYY-MM-DD 指定开始日期(默认:30天前)'); - console.log(' --end YYYY-MM-DD 指定结束日期(默认:今天)'); - console.log(' --help 显示帮助信息'); - console.log(''); - console.log('示例:'); - console.log(' bun sh300.fetcher.js'); - console.log(' bun sh300.fetcher.js --start 2024-01-01 --end 2024-01-31'); - process.exit(0); - } - } - - return { startDate, endDate }; -} - -if (isMainModule()) { - const { startDate, endDate } = parseCommandLineArgs(); - - console.log('='.repeat(50)); - console.log('沪深300指数数据获取脚本'); - console.log('='.repeat(50)); - - fetch(startDate, endDate) - .then(result => { - console.log('\n' + '='.repeat(50)); - console.log('获取结果:'); - console.log(` 指数代码: ${result.symbol}`); - console.log(` 指数名称: ${result.name}`); - console.log(` 开始日期: ${result.startDate}`); - console.log(` 结束日期: ${result.endDate}`); - console.log(` 数据条数: ${result.count}`); - console.log(` 获取时间: ${result.fetchTime}`); - console.log(` 提示: ${result.note}`); - if (result.count > 0) { - console.log('-'.repeat(50)); - console.log('指数数据:'); - result.data.forEach(item => { - console.log(` ${item.date} 开盘: ${item.open} 最高: ${item.high} 最低: ${item.low} 收盘: ${item.close}`); - }); - } - console.log('='.repeat(50)); - }) - .catch(error => { - console.error('执行失败:', error); - process.exit(1); - }); -} diff --git a/scripts/fetchers/usd-cny-rate.fetcher.js b/scripts/fetchers/usd-cny-rate.fetcher.js deleted file mode 100644 index c2b5034..0000000 --- a/scripts/fetchers/usd-cny-rate.fetcher.js +++ /dev/null @@ -1,150 +0,0 @@ -import axios from 'axios'; -import { fileURLToPath } from 'url'; - -const FRANKFURTER_API = 'https://api.frankfurter.app'; - -async function fetch(startDate = null, endDate = null) { - try { - const currentDate = new Date(); - - if (!startDate) { - startDate = new Date(currentDate); - startDate.setDate(startDate.getDate() - 30); - startDate = startDate.toISOString().split('T')[0]; - } - - if (!endDate) { - endDate = currentDate.toISOString().split('T')[0]; - } - - console.log('正在获取人民币美元汇率数据...'); - console.log(`时间范围: ${startDate} 至 ${endDate}`); - console.log(`数据源: Frankfurter API (欧洲央行汇率数据)`); - - const response = await axios.get(`${FRANKFURTER_API}/${startDate}..${endDate}`, { - params: { - from: 'USD', - to: 'CNY' - }, - headers: { - 'Accept': 'application/json' - } - }); - - const data = response.data; - console.log(`\nAPI状态: ${response.status}`); - console.log(`数据条数: ${Object.keys(data.rates || {}).length}`); - - const allRates = []; - - if (data.rates) { - for (const [date, rateObj] of Object.entries(data.rates)) { - const rateValue = rateObj.CNY; - allRates.push({ - date: date, - currency: 'USD/CNY', - centerPrice: parseFloat(rateValue.toFixed(4)), - sellingRate: parseFloat(rateValue.toFixed(4)), - buyingRate: parseFloat(rateValue.toFixed(4)), - source: '欧洲央行 (Frankfurter API)', - fetchTime: new Date().toISOString() - }); - } - } - - allRates.sort((a, b) => a.date.localeCompare(b.date)); - - console.log(`\n共获取到 ${allRates.length} 条汇率数据`); - - return { - currency: 'USD/CNY', - startDate, - endDate, - count: allRates.length, - data: allRates, - fetchTime: new Date().toISOString(), - note: '数据来源: 欧洲央行, 通过 Frankfurter API 提供' - }; - - } catch (error) { - console.error('获取汇率数据失败:', error.message); - if (error.response) { - console.error(`HTTP状态码: ${error.response.status}`); - console.error(`响应数据:`, error.response.data); - } - throw error; - } -} - -export { - fetch -}; - -const isMainModule = () => { - const currentFile = fileURLToPath(import.meta.url); - const mainFile = process.argv[1]; - return currentFile === mainFile; -}; - -function parseCommandLineArgs() { - const args = process.argv.slice(2); - let startDate = null; - let endDate = null; - - for (let i = 0; i < args.length; i++) { - if (args[i] === '--start' && i + 1 < args.length) { - startDate = args[i + 1]; - i++; - } else if (args[i] === '--end' && i + 1 < args.length) { - endDate = args[i + 1]; - i++; - } else if (args[i] === '--help') { - console.log('使用方法:'); - console.log(' bun usd-cny-rate.fetcher.js [选项]'); - console.log(''); - console.log('选项:'); - console.log(' --start YYYY-MM-DD 指定开始日期(默认:30天前)'); - console.log(' --end YYYY-MM-DD 指定结束日期(默认:今天)'); - console.log(' --help 显示帮助信息'); - console.log(''); - console.log('示例:'); - console.log(' bun usd-cny-rate.fetcher.js'); - console.log(' bun usd-cny-rate.fetcher.js --start 2024-01-01 --end 2024-01-31'); - process.exit(0); - } - } - - return { startDate, endDate }; -} - -if (isMainModule()) { - const { startDate, endDate } = parseCommandLineArgs(); - - console.log('='.repeat(50)); - console.log('人民币美元汇率获取脚本'); - console.log('='.repeat(50)); - - fetch(startDate, endDate) - .then(result => { - console.log('\n' + '='.repeat(50)); - console.log('获取结果:'); - console.log(` 货币对: ${result.currency}`); - console.log(` 开始日期: ${result.startDate}`); - console.log(` 结束日期: ${result.endDate}`); - console.log(` 数据条数: ${result.count}`); - console.log(` 获取时间: ${result.fetchTime}`); - console.log(` 提示: ${result.note}`); - if (result.count > 0) { - console.log('-'.repeat(50)); - console.log('汇率数据:'); - result.data.forEach(item => { - console.log(` ${item.date} 中间价: ${item.centerPrice}`); - }); - } - console.log('='.repeat(50)); - }) - .catch(error => { - console.error('执行失败:', error); - process.exit(1); - }); -} diff --git a/scripts/fetchers/wb-indicators.fetcher.js b/scripts/fetchers/wb-indicators.fetcher.js deleted file mode 100644 index 75b0d5e..0000000 --- a/scripts/fetchers/wb-indicators.fetcher.js +++ /dev/null @@ -1,113 +0,0 @@ -import axios from 'axios' - -// World Bank API 数据源 -// 文档: https://datahelpdesk.worldbank.org/knowledgebase/articles/898581 -// 优点: 官方、JSON、免 key、稳定, 中国数据滞后约 1 年 (这是全球宏观数据物理上限) -// 接口: GET /v2/country/{code}/indicator/{id}?format=json&date={startYear}:{endYear}&per_page=100 - -const WB_BASE = 'https://api.worldbank.org/v2' -const TIMEOUT = 15000 - -// 6 个核心宏观指标 -// - 中国 GDP (现价美元) - 国民经济总规模 -// - 中国 GDP 增速 - 增长动能 -// - 中国 CPI 同比 - 通胀 -// - 中国失业率 - 就业 -// - 美国 GDP (现价美元) - 对标 -// - 美国 CPI 同比 - 对标 -export const WB_INDICATORS = [ - { id: 'NY.GDP.MKTP.CD', name: 'china-gdp-usd', groupName: 'china-macro', title: '中国 GDP', unit: '万亿美元', scale: 1e-8, digits: 2 }, - { id: 'NY.GDP.MKTP.KD.ZG',name: 'china-gdp-growth', groupName: 'china-macro', title: '中国 GDP 增速', unit: '%', scale: 1, digits: 2 }, - { id: 'FP.CPI.TOTL.ZG', name: 'china-cpi', groupName: 'china-macro', title: '中国 CPI', unit: '%', scale: 1, digits: 2 }, - { id: 'SL.UEM.TOTL.ZS', name: 'china-unemploy', groupName: 'china-macro', title: '中国失业率', unit: '%', scale: 1, digits: 2 }, - { id: 'NY.GDP.MKTP.CD', name: 'usa-gdp-usd', groupName: 'usa-macro', title: '美国 GDP', unit: '万亿美元', scale: 1e-8, digits: 2 }, - { id: 'FP.CPI.TOTL.ZG', name: 'usa-cpi', groupName: 'usa-macro', title: '美国 CPI', unit: '%', scale: 1, digits: 2 }, -] - -// World Bank 中国是 CHN, 美国是 USA -function countryCodeOf(name) { - if (name.startsWith('china-')) return 'CHN' - if (name.startsWith('usa-')) return 'USA' - throw new Error('Unknown country for ' + name) -} - -// 拉单个指标从 startYear 到当前年 -// 返回 { name, groupName, title, unit, rows: [{ year, value }] } -// 注意: WB 对连续请求会限流返回 400 Invalid value, 加 3 次退避重试 -export async function fetchIndicator(meta, startYear = 2015) { - const country = countryCodeOf(meta.name) - const currentYear = new Date().getFullYear() - const url = `${WB_BASE}/country/${country}/indicator/${meta.id}?format=json&date=${startYear}:${currentYear}&per_page=200` - - let resp - const delays = [0, 1500, 4000] - for (let attempt = 0; attempt < delays.length; attempt++) { - if (delays[attempt]) await new Promise((r) => setTimeout(r, delays[attempt])) - try { - resp = await axios.get(url, { timeout: TIMEOUT, headers: { 'User-Agent': 'iboard-refresh/1.0' } }) - break - } catch (e) { - if (attempt === delays.length - 1) throw e - console.log(`[WB] ${meta.name} 请求失败, ${delays[attempt + 1] / 1000}s 后重试 (${e.message})`) - } - } - // WB 返回: [{page,pages,per_page,total,...}, [{indicator, country, value, date}, ...]] - const data = resp.data - if (!Array.isArray(data) || data.length < 2 || !Array.isArray(data[1])) { - throw new Error('World Bank 返回结构异常: ' + JSON.stringify(data).slice(0, 200)) - } - const points = data[1] - .filter((p) => p.value !== null && p.value !== undefined) - .map((p) => ({ - year: String(p.date), - value: Number((Number(p.value) * meta.scale).toFixed(meta.digits)), - })) - .sort((a, b) => Number(a.year) - Number(b.year)) - - return { - name: meta.name, - groupName: meta.groupName, - data: { - unit: meta.unit, - title: meta.title, - source: 'World Bank API', - rows: points, - }, - } -} - -export const META = { - name: 'wb-indicators', - description: 'World Bank 官方 API 宏观数据', - docs: 'https://datahelpdesk.worldbank.org/knowledgebase/articles/898581', -} - -// 拉全部 (返回符合 fetcher 契约的 [{name, groupName, data}, ...]) -// 错误隔离: 单个指标失败 -> 跳过 + 记日志, 不影响其他 -// opts.skipNames: Set — DB 已存在的指标名, 跳过不拉 (WB 免费但也要省请求) -export async function fetchAll(opts = {}) { - const skip = opts.skipNames || new Set() - const startYear = 2015 - const results = [] - for (const m of WB_INDICATORS) { - if (skip.has(m.name)) { - console.log(`[WB] ${m.name} 跳过 (DB 已存在)`) - continue - } - try { - const r = await fetchIndicator(m, startYear) - console.log(`[WB] ${r.name} OK, ${r.data.rows.length} rows (${r.data.rows[0]?.year}..${r.data.rows.at(-1)?.year})`) - results.push(r) - } catch (e) { - console.error(`[WB] ${m.name} FAIL: ${e.message}`) - } - } - // 全部被 DB 优先跳过时, 返回显式标记而不是空数组 (避免被误判为失败) - if (results.length === 0 && WB_INDICATORS.every((m) => skip.has(m.name))) { - return { skipped: true, reason: 'DB 已存在全部指标' } - } - return results -} - - - diff --git a/scripts/import-oil-history.mjs b/scripts/import-oil-history.mjs deleted file mode 100644 index ae71225..0000000 --- a/scripts/import-oil-history.mjs +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env node -// 一次性导入浙江省油价历史 (2025-2026) -// 来源: 手工整理 (省发改委公告 / 潮新闻 / 钱江晚报 / 杭州日报 / 杭州网 等) -// 用法: node --experimental-strip-types --no-warnings scripts/import-oil-history.mjs [--dry] - -import path from 'node:path' -import { fileURLToPath, pathToFileURL } from 'node:url' -import 'dotenv/config' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const ROOT = path.resolve(__dirname, '..') - -// 接入 prisma (用 file:// URL 避免 Windows 绝对路径 import 报错) -const adapterMod = await import('@prisma/adapter-mariadb') -const clientPath = pathToFileURL(path.join(ROOT, 'generated/prisma/client.ts')).href -const { PrismaClient } = await import(clientPath) - -const adapter = new adapterMod.PrismaMariaDb((() => { - const u = new URL(process.env.DATABASE_URL) - return { - host: u.hostname, - port: Number(u.port) || 3306, - user: decodeURIComponent(u.username), - password: decodeURIComponent(u.password), - database: u.pathname.replace(/^\//, ''), - } -})()) -const prisma = new PrismaClient({ adapter }) - -// 数据: [调价日, 92, 95, 0, source] -const RAW = [ - ['2026-07-31', 7.94, 8.44, 7.62, 'manual'], - ['2026-07-17', 7.39, 7.86, 7.06, 'manual'], - ['2026-06-18', 7.91, 8.41, 7.59, 'manual'], - ['2026-06-04', 8.32, 8.85, 8.02, 'manual'], - ['2026-05-21', 8.74, 9.30, 8.45, 'manual'], - ['2026-02-03', 6.91, 7.35, 6.56, 'manual'], - ['2025-12-22', 6.68, 7.11, 6.32, 'manual'], - ['2025-12-08', 6.82, 7.25, 6.46, 'manual'], - ['2025-11-24', 6.86, null, null, 'manual'], - ['2025-11-10', 6.92, 7.36, 6.57, 'manual'], - ['2025-10-13', 7.03, 7.48, 6.68, 'manual'], - ['2025-06-17', 7.15, 7.60, 6.81, 'manual'], - ['2025-06-03', 6.94, 7.38, 6.59, 'manual'], -] - -const PROVINCE = '浙江' -const FUEL_COLS = [ - { fuel: '92', idx: 1 }, - { fuel: '95', idx: 2 }, - { fuel: '0', idx: 3 }, -] - -const DRY = process.argv.includes('--dry') - -async function main() { - console.log('=== 浙江省油价历史导入 ===') - console.log('dry run:', DRY) - console.log('') - - let inserted = 0 - let updated = 0 - let skipped = 0 - - for (const row of RAW) { - const dateStr = row[0] - const source = row[4] - for (const { fuel, idx } of FUEL_COLS) { - const price = row[idx] - if (price == null || Number.isNaN(price)) { - console.log('[SKIP] ' + dateStr + ' ' + fuel + ': 价格为空') - skipped++ - continue - } - const date = new Date(dateStr + 'T00:00:00') - if (DRY) { - console.log('[DRY] ' + dateStr + ' ' + fuel + ' = ' + price + ' (source=' + source + ')') - inserted++ - continue - } - try { - await prisma.oilPriceHistory.create({ - data: { date, province: PROVINCE, fuel, price, source }, - }) - console.log('[OK] ' + dateStr + ' ' + fuel + ' = ' + price) - inserted++ - } catch (e) { - if (e?.code === 'P2002') { - try { - await prisma.oilPriceHistory.update({ - where: { date_province_fuel: { date, province: PROVINCE, fuel } }, - data: { price, source }, - }) - console.log('[UPD] ' + dateStr + ' ' + fuel + ' = ' + price) - updated++ - } catch (e2) { - console.log('[ERR] ' + dateStr + ' ' + fuel + ': ' + e2.message) - } - } else { - console.log('[ERR] ' + dateStr + ' ' + fuel + ': ' + e.message) - } - } - } - } - - console.log('') - console.log('=== 汇总 ===') - console.log('插入: ' + inserted) - console.log('更新: ' + updated) - console.log('跳过: ' + skipped) - - await prisma.$disconnect() -} - -main().catch((e) => { - console.error(e) - process.exit(1) -}) diff --git a/scripts/migrate-indicator-points.cjs b/scripts/migrate-indicator-points.cjs deleted file mode 100644 index fc3eb01..0000000 --- a/scripts/migrate-indicator-points.cjs +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 一次性数据迁移: 把 Indicator.data (JSON) 拆分到 IndicatorPoint 明细表 - * 用法: 在迁移 A (建表+加列) 之后、迁移 B (删 data 列) 之前运行 - * 幂等: 同 (indicatorId, period) 重复插入会更新 value, 可安全重跑 - */ -require('dotenv').config() -const mysql = require('mysql2/promise') - -function parseUrl(url) { - const u = new URL(url) - return { - host: u.hostname, - port: Number(u.port) || 3306, - user: decodeURIComponent(u.username), - password: decodeURIComponent(u.password), - database: u.pathname.replace(/^\//, ''), - } -} - -// 根据 row 的时间字段推断频率: year | quarter | month | day -function inferFrequency(row) { - if (row && row.month !== undefined) return 'month' - if (row && row.year !== undefined) return 'year' - const p = String((row && row.period) || '') - if (/^\d{4}$/.test(p)) return 'year' - if (/^\d{4}-Q[1-4]$/.test(p)) return 'quarter' - if (/^\d{4}-\d{2}$/.test(p)) return 'month' - if (/^\d{4}-\d{2}-\d{2}$/.test(p)) return 'day' - return 'month' -} - -// 统一 row 的时间字段为 period -function periodOf(row) { - if (!row) return '' - return String(row.period !== undefined ? row.period : (row.month !== undefined ? row.month : row.year)) -} - -async function main() { - const conn = await mysql.createConnection(parseUrl(process.env.DATABASE_URL)) - try { - const [rows] = await conn.execute('SELECT id, name, groupName, data FROM Indicator') - console.log('[Migrate] 读取', rows.length, '个 Indicator') - - let points = 0, updated = 0 - for (const ind of rows) { - // mysql2 对 JSON 列已自动解析为对象, 兼容字符串兜底 - let data = ind.data - if (typeof data === 'string') { - try { data = JSON.parse(data) } catch { data = null } - } - const title = (data && data.title) || ind.name - const unit = (data && data.unit) || '' - const source = (data && data.source) || '' - const rowsArr = (data && Array.isArray(data.rows)) ? data.rows : [] - const freq = rowsArr.length ? inferFrequency(rowsArr[0]) : 'month' - - // 更新 Indicator 元数据列 - await conn.execute( - 'UPDATE Indicator SET title=?, unit=?, source=?, frequency=?, updatedAt=NOW(3) WHERE id=?', - [title, unit, source, freq, ind.id], - ) - updated++ - - // 写入明细 - for (const r of rowsArr) { - const period = periodOf(r) - const value = Number(r.value) - if (!period || Number.isNaN(value)) continue - const [res] = await conn.execute( - 'INSERT INTO IndicatorPoint (indicatorId, period, value, createdAt, updatedAt) VALUES (?, ?, ?, NOW(3), NOW(3)) ' + - 'ON DUPLICATE KEY UPDATE value=VALUES(value), updatedAt=NOW(3)', - [ind.id, period, value], - ) - points += (res.affectedRows > 0 ? 1 : 0) - } - console.log('[Migrate]', ind.name, '->', rowsArr.length, '个点, 频率', freq) - } - console.log('[Migrate] 完成: 更新', updated, '个 Indicator, 写入/更新', points, '个明细点') - } finally { - await conn.end() - } -} - -main().catch((e) => { console.error('[Migrate] 失败:', e); process.exit(1) }) diff --git a/scripts/refresh-indicators.cjs b/scripts/refresh-indicators.cjs deleted file mode 100644 index 65a5021..0000000 --- a/scripts/refresh-indicators.cjs +++ /dev/null @@ -1,185 +0,0 @@ -/** - * 拉所有启用的数据源, upsert 到 Indicator + IndicatorPoint 明细表 - * 用法: - * npm run data:refresh # 只拉 DB 里没有的指标 - * npm run data:refresh -- --force a,b,c # 强制刷新指定指标 (忽略 DB 已有) - * - * 数据结构 (2026-08 重构): - * Indicator 元数据: name/groupName/title/unit/source/frequency/hidden - * IndicatorPoint 明细: (indicatorId, period, value), period 兼容年/季/月/日 - * - * DB 优先策略: - * 1. 先从 DB 查已存在的 name 集合 - * 2. 把集合传给每个 fetcher, fetcher 跳过已存在的 - * 3. 只对 DB 没有的指标调远程拉取, 节省 token (尤其妙想 MCP 这种 LLM 接口) - * - * 加新数据源: 写 scripts/fetchers/.fetcher.js 导出 META + fetchAll(opts), - * 然后在 scripts/fetchers/index.js 的 FETCHERS 注册 - */ -require('dotenv').config() -const mysql = require('mysql2/promise') - -// 隐藏指标: 数据与获取脚本保留, 但不参与前端展示 (API 过滤) -const HIDDEN_INDICATORS = new Set([ - 'china-gdp-usd', // 中国 GDP (现价美元) - 'china-gdp-growth', // 中国 GDP 同比 (World Bank) - 'china-gdp-yoy', // 中国 GDP 同比 (妙想季度) - 'china-unemploy', // 中国失业率 - 'usa-gdp-usd', // 美国 GDP -]) - -function parseUrl(url) { - const u = new URL(url) - return { - host: u.hostname, - port: Number(u.port) || 3306, - user: decodeURIComponent(u.username), - password: decodeURIComponent(u.password), - database: u.pathname.replace(/^\//, ''), - } -} - -function parseForceArg(argv) { - const i = argv.indexOf('--force') - if (i === -1) return new Set() - return new Set(String(argv[i + 1] || '').split(',').map((s) => s.trim()).filter(Boolean)) -} - -// 推断频率: year | quarter | month | day (优先用 fetcher 显式声明) -function inferFrequency(data) { - if (data && data.frequency) return data.frequency - const p = String(((data && data.rows) || [])[0]?.period || '') - if (/^\d{4}$/.test(p)) return 'year' - if (/^\d{4}-Q[1-4]$/.test(p)) return 'quarter' - if (/^\d{4}-\d{2}$/.test(p)) return 'month' - if (/^\d{4}-\d{2}-\d{2}$/.test(p)) return 'day' - return 'month' -} - -// 写 Indicator 元数据 + 全量替换明细点 (每指标一个事务) -async function upsertIndicatorWithPoints(conn, payload) { - const { name, groupName, data, hidden } = payload - const title = data.title || name - const unit = data.unit || '' - const source = data.source || '' - const frequency = inferFrequency(data) - const rows = Array.isArray(data.rows) ? data.rows : [] - - await conn.beginTransaction() - try { - const [res] = await conn.execute( - 'INSERT INTO `Indicator` (`name`, `groupName`, `title`, `unit`, `source`, `frequency`, `hidden`, `createdAt`, `updatedAt`) ' + - 'VALUES (?, ?, ?, ?, ?, ?, ?, NOW(3), NOW(3)) ' + - 'ON DUPLICATE KEY UPDATE `groupName` = VALUES(`groupName`), `title` = VALUES(`title`), ' + - '`unit` = VALUES(`unit`), `source` = VALUES(`source`), `frequency` = VALUES(`frequency`), ' + - '`hidden` = VALUES(`hidden`), `updatedAt` = NOW(3)', - [name, groupName, title, unit, source, frequency, hidden ? 1 : 0], - ) - const indicatorId = res.insertId - - // 明细: 先删旧点再插入新点 (全量替换, 保证与数据源一致) - await conn.execute('DELETE FROM `IndicatorPoint` WHERE `indicatorId` = ?', [indicatorId]) - let points = 0 - for (const r of rows) { - const period = String(r.period !== undefined ? r.period : (r.month !== undefined ? r.month : r.year)) - const value = Number(r.value) - if (!period || Number.isNaN(value)) continue - await conn.execute( - 'INSERT INTO `IndicatorPoint` (`indicatorId`, `period`, `value`, `createdAt`, `updatedAt`) VALUES (?, ?, ?, NOW(3), NOW(3))', - [indicatorId, period, value], - ) - points++ - } - await conn.commit() - return { indicatorId, points } - } catch (e) { - await conn.rollback() - throw e - } -} - -async function fetchExistingNames(conn) { - const [rows] = await conn.execute('SELECT `name` FROM `Indicator`') - return new Set(rows.map((r) => r.name)) -} - -async function main() { - if (!process.env.DATABASE_URL) { - console.error('缺少 DATABASE_URL 环境变量') - process.exit(1) - } - const force = parseForceArg(process.argv.slice(2)) - if (force.size) console.log('[Refresh] 强制刷新: ' + [...force].join(', ')) - - const conn = await mysql.createConnection(parseUrl(process.env.DATABASE_URL)) - try { - // 1) 查 DB 已存在的指标名 - const existing = await fetchExistingNames(conn) - console.log('[Refresh] DB 已有 ' + existing.size + ' 个指标: ' + ([...existing].join(', ') || '(空)')) - - // 2) 跑所有 fetcher, 传入 existing + force (DB 优先, force 覆盖) - const { runFetchers } = await import('./fetchers/index.js') - const fetcherResults = await runFetchers({ skipNames: existing, forceNames: force }) - - // 3) 汇总: force 名单里的指标即使 DB 已有也写入 - const allItems = [] - let skipped = 0, failed = 0 - for (const r of fetcherResults) { - if (r.skipped) { skipped++; continue } - if (!r.ok) { failed++; continue } - for (const it of r.items) { - if (existing.has(it.name) && !force.has(it.name)) continue - allItems.push(it) - } - } - console.log('\n[Refresh] 汇总: ' + allItems.length + ' 个指标待写 (跳过 ' + skipped + ' 个数据源, 失败 ' + failed + ' 个, DB 已存在 ' + existing.size + ' 个)') - - if (failed > 0) { - console.error('[Refresh] 警告: 有数据源失败, 仅写入成功的指标') - } - - // 5) 同步隐藏标记: 即使指标被 DB 优先跳过, 名单内的也确保 hidden=true - if (HIDDEN_INDICATORS.size > 0) { - const names = [...HIDDEN_INDICATORS] - const ph = names.map(() => '?').join(',') - const sql = "UPDATE `Indicator` SET `hidden` = 1, `updatedAt` = NOW(3) WHERE `name` IN (" + ph + ")" - await conn.execute(sql, names) - console.log('[Refresh] 同步隐藏标记: ' + names.length + ' 个指标 hidden=true (数据保留, 不展示)') - } - - // 6) 跨源对比报警: 全量跑一次 (即便无新指标也跑, 防止上次漏检) - try { - const m = await import('../server/alert-engine.js') - const a = await m.checkAfterRefresh(conn) - console.log('[Refresh] 报警检测: 新增 ' + a.inserted + ' 条 (扫描 ' + a.scanned + ')') - } catch (e) { console.error('[Refresh] 报警检测异常:', e.message) } - - if (allItems.length === 0) { - console.log('[Refresh] 无指标待写, 不写库 (DB 优先策略生效)') - return - } - - // 4) 写库 - console.log('[Refresh] 写入数据库...') - let ins = 0, upd = 0, totalPoints = 0 - for (const it of allItems) { - const hidden = HIDDEN_INDICATORS.has(it.name) - const { points } = await upsertIndicatorWithPoints(conn, { name: it.name, groupName: it.groupName, data: it.data, hidden }) - if (points > 0) ins++ - else upd++ - totalPoints += points - console.log('[Refresh] ' + it.name + (hidden ? ' (hidden)' : '') + ': ' + points + ' 个明细点') - } - - - } finally { - await conn.end() - } -} - -main().catch((e) => { - console.error('[Refresh] 异常:', e) - process.exit(1) -}) - - diff --git a/scripts/scraper/sh300.scraper.js b/scripts/scraper/sh300.scraper.js deleted file mode 100644 index ccd3497..0000000 --- a/scripts/scraper/sh300.scraper.js +++ /dev/null @@ -1,338 +0,0 @@ -import { createPool } from 'mariadb'; -import { fetch as fetchSH300Data } from '../fetchers/sh300.fetcher.js'; -import fs from 'fs'; -import path from 'path'; - -let pool; - -function loadDatabaseConfig() { - const envPath = path.join(process.cwd(), '.env'); - if (fs.existsSync(envPath)) { - console.log('读取 .env 文件...'); - const envContent = fs.readFileSync(envPath, 'utf-8'); - const lines = envContent.split('\n'); - - for (const line of lines) { - const trimmedLine = line.trim(); - if (trimmedLine.startsWith('DATABASE_URL=')) { - const url = trimmedLine.substring('DATABASE_URL='.length).replace(/^"|"$/g, ''); - console.log('从 .env 文件获取数据库配置成功!'); - console.log(`DATABASE_URL: ${url}`); - - // 解析URL - const parts = url.split('://')[1].split('@'); - const auth = parts[0].split(':'); - const hostAndDb = parts[1].split('/'); - const hostAndPort = hostAndDb[0].split(':'); - - return { - user: auth[0], - password: auth[1], - host: hostAndPort[0], - port: parseInt(hostAndPort[1]), - database: hostAndDb[1] - }; - } - } - } - - console.log('未找到 .env 文件或 DATABASE_URL 配置,使用默认配置'); - return { - host: '192.168.111.111', - port: 3306, - user: 'root', - password: 'fullstack', - database: 'iboard' - }; -} - -async function initPool() { - if (!pool) { - console.log('初始化数据库连接池...'); - const config = loadDatabaseConfig(); - - console.log('数据库配置:'); - console.log(` 主机: ${config.host}`); - console.log(` 端口: ${config.port}`); - console.log(` 用户: ${config.user}`); - console.log(` 数据库: ${config.database}`); - - pool = createPool({ - host: config.host, - port: config.port, - user: config.user, - password: config.password, - database: config.database, - connectionLimit: 5 - }); - - try { - const conn = await pool.getConnection(); - console.log('数据库连接成功!'); - conn.release(); - } catch (error) { - console.error('数据库连接失败:', error.message); - throw error; - } - } - return pool; -} - -async function checkAndFillSH300Data(startDate = null, endDate = null) { - try { - await initPool(); - - console.log('='.repeat(60)); - console.log('开始检查和补充沪深300指数数据...'); - console.log('='.repeat(60)); - - const currentDate = new Date(); - - if (!startDate) { - startDate = new Date(currentDate); - startDate.setDate(startDate.getDate() - 30); - startDate = startDate.toISOString().split('T')[0]; - } - - if (!endDate) { - endDate = currentDate.toISOString().split('T')[0]; - } - - console.log(`检查时间范围: ${startDate} 至 ${endDate}`); - - // 1. 检查数据库中是否存在沪深300指数数据表 - const hasTable = await checkTableExists(); - if (!hasTable) { - console.log('创建沪深300指数数据表...'); - await createSH300Table(); - } - - // 2. 检查数据库中已有的数据 - const existingData = await getExistingSH300Data(startDate, endDate); - console.log(`数据库中已有 ${existingData.length} 条数据`); - - // 3. 计算缺失的日期 - const missingDates = calculateMissingDates(startDate, endDate, existingData); - console.log(`缺失 ${missingDates.length} 天的数据`); - - // 4. 如果有缺失,调用 fetcher 补足 - if (missingDates.length > 0) { - console.log('\n开始获取缺失的沪深300指数数据...'); - - const firstMissingDate = missingDates[0]; - const lastMissingDate = missingDates[missingDates.length - 1]; - - console.log(`获取范围: ${firstMissingDate} 至 ${lastMissingDate}`); - - const fetchedData = await fetchSH300Data(firstMissingDate, lastMissingDate); - - console.log(`\n获取到 ${fetchedData.count} 条数据`); - - // 5. 存储新数据到数据库 - const storedCount = await storeSH300Data(fetchedData.data); - console.log(`成功存储 ${storedCount} 条数据`); - } else { - console.log('\n数据库数据完整,无需补充'); - } - - // 6. 验证最终数据完整性 - const finalData = await getExistingSH300Data(startDate, endDate); - console.log(`\n验证后的数据总量: ${finalData.length} 条`); - - return { - startDate, - endDate, - existingCount: existingData.length, - missingCount: missingDates.length, - finalCount: finalData.length, - success: true - }; - - } catch (error) { - console.error('检查和补充沪深300指数数据失败:', error.message); - throw error; - } finally { - if (pool) { - await pool.end(); - console.log('数据库连接池已关闭'); - } - } -} - -async function checkTableExists() { - try { - console.log('检查沪深300指数数据表是否存在...'); - const result = await pool.query('SHOW TABLES LIKE ?', ['econ_SH300Index']); - return result.length > 0; - } catch (error) { - console.error('检查表存在失败:', error.message); - return false; - } -} - -async function createSH300Table() { - try { - console.log('执行创建沪深300指数数据表SQL...'); - await pool.query(` - CREATE TABLE IF NOT EXISTS econ_SH300Index ( - id INT AUTO_INCREMENT PRIMARY KEY, - date DATE UNIQUE NOT NULL, - symbol VARCHAR(10) NOT NULL, - open DECIMAL(10,2) NOT NULL, - close DECIMAL(10,2) NOT NULL, - high DECIMAL(10,2) NOT NULL, - low DECIMAL(10,2) NOT NULL, - volume DECIMAL(20,2) NOT NULL, - amount DECIMAL(20,2) NOT NULL, - source VARCHAR(100) NOT NULL, - createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - `); - console.log('沪深300指数数据表创建成功'); - } catch (error) { - console.error('创建沪深300指数数据表失败:', error.message); - throw error; - } -} - -async function getExistingSH300Data(startDate, endDate) { - try { - console.log('查询已有沪深300指数数据...'); - const result = await pool.query( - 'SELECT date FROM econ_SH300Index WHERE date BETWEEN ? AND ? ORDER BY date', - [startDate, endDate] - ); - const dates = result.map(row => { - if (row.date instanceof Date) { - return row.date.toISOString().split('T')[0]; - } else if (typeof row.date === 'string') { - return row.date; - } - return null; - }).filter(Boolean); - console.log(`查询到 ${dates.length} 条数据`); - return dates; - } catch (error) { - console.error('查询已有沪深300指数数据失败:', error.message); - return []; - } -} - -function calculateMissingDates(startDate, endDate, existingDates) { - const existingSet = new Set(existingDates); - const missingDates = []; - - const currentDate = new Date(startDate); - const finalDate = new Date(endDate); - - while (currentDate <= finalDate) { - const dateStr = currentDate.toISOString().split('T')[0]; - const dayOfWeek = currentDate.getDay(); - - // 跳过周末(周末没有股市数据) - if (dayOfWeek !== 0 && dayOfWeek !== 6) { - if (!existingSet.has(dateStr)) { - missingDates.push(dateStr); - } - } - - currentDate.setDate(currentDate.getDate() + 1); - } - - return missingDates; -} - -async function storeSH300Data(data) { - let storedCount = 0; - - for (const item of data) { - try { - await pool.query( - 'INSERT INTO econ_SH300Index (date, symbol, open, close, high, low, volume, amount, source) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE open = VALUES(open), close = VALUES(close), high = VALUES(high), low = VALUES(low), volume = VALUES(volume), amount = VALUES(amount), source = VALUES(source)', - [item.date, 'sh000300', item.open, item.close, item.high, item.low, item.volume, item.amount, item.source] - ); - storedCount++; - } catch (error) { - console.error(`存储沪深300指数数据 ${item.date} 失败:`, error.message); - } - } - - return storedCount; -} - -export async function trigger(startDate = null, endDate = null) { - return await checkAndFillSH300Data(startDate, endDate); -} - -function parseCommandLineArgs() { - const args = process.argv.slice(2); - let startDate = null; - let endDate = null; - - for (let i = 0; i < args.length; i++) { - if (args[i] === '--start' && i + 1 < args.length) { - startDate = args[i + 1]; - i++; - } else if (args[i] === '--end' && i + 1 < args.length) { - endDate = args[i + 1]; - i++; - } else if (args[i] === '--help') { - console.log('使用方法:'); - console.log(' bun sh300.scraper.js [选项]'); - console.log(''); - console.log('选项:'); - console.log(' --start YYYY-MM-DD 指定开始日期'); - console.log(' --end YYYY-MM-DD 指定结束日期'); - console.log(' --help 显示帮助信息'); - console.log(''); - console.log('示例:'); - console.log(' bun sh300.scraper.js'); - console.log(' bun sh300.scraper.js --start 2024-01-01 --end 2024-01-31'); - process.exit(0); - } - } - - return { startDate, endDate }; -} - -async function runTest() { - console.log('='.repeat(60)); - console.log('测试 sh300.scraper.js'); - console.log('='.repeat(60)); - - const { startDate, endDate } = parseCommandLineArgs(); - - if (startDate) { - console.log(`\n使用命令行参数:`); - console.log(` 开始日期: ${startDate}`); - console.log(` 结束日期: ${endDate || '默认'}`); - } - - console.log('\n测试数据库配置读取...'); - const config = loadDatabaseConfig(); - console.log('配置读取结果:'); - console.log(` 主机: ${config.host}`); - console.log(` 端口: ${config.port}`); - console.log(` 用户: ${config.user}`); - console.log(` 数据库: ${config.database}`); - console.log('配置读取测试完成!'); - - try { - const result = await trigger(startDate, endDate); - console.log('\n' + '='.repeat(60)); - console.log('测试结果:'); - console.log(` 开始日期: ${result.startDate}`); - console.log(` 结束日期: ${result.endDate}`); - console.log(` 原有数据: ${result.existingCount} 条`); - console.log(` 缺失数据: ${result.missingCount} 条`); - console.log(` 最终数据: ${result.finalCount} 条`); - console.log(` 操作成功: ${result.success}`); - console.log('='.repeat(60)); - } catch (error) { - console.error('测试失败:', error); - process.exit(1); - } -} - -runTest(); diff --git a/scripts/scraper/usd-cny-rate.scraper.js b/scripts/scraper/usd-cny-rate.scraper.js deleted file mode 100644 index e56e491..0000000 --- a/scripts/scraper/usd-cny-rate.scraper.js +++ /dev/null @@ -1,335 +0,0 @@ -import { createPool } from 'mariadb'; -import { fetch as fetchUSDToCNYRate } from '../fetchers/usd-cny-rate.fetcher.js'; -import fs from 'fs'; -import path from 'path'; - -let pool; - -function loadDatabaseConfig() { - const envPath = path.join(process.cwd(), '.env'); - if (fs.existsSync(envPath)) { - console.log('读取 .env 文件...'); - const envContent = fs.readFileSync(envPath, 'utf-8'); - const lines = envContent.split('\n'); - - for (const line of lines) { - const trimmedLine = line.trim(); - if (trimmedLine.startsWith('DATABASE_URL=')) { - const url = trimmedLine.substring('DATABASE_URL='.length).replace(/^"|"$/g, ''); - console.log('从 .env 文件获取数据库配置成功!'); - console.log(`DATABASE_URL: ${url}`); - - // 解析URL - const parts = url.split('://')[1].split('@'); - const auth = parts[0].split(':'); - const hostAndDb = parts[1].split('/'); - const hostAndPort = hostAndDb[0].split(':'); - - return { - user: auth[0], - password: auth[1], - host: hostAndPort[0], - port: parseInt(hostAndPort[1]), - database: hostAndDb[1] - }; - } - } - } - - console.log('未找到 .env 文件或 DATABASE_URL 配置,使用默认配置'); - return { - host: '192.168.111.111', - port: 3306, - user: 'root', - password: 'fullstack', - database: 'iboard' - }; -} - -async function initPool() { - if (!pool) { - console.log('初始化数据库连接池...'); - const config = loadDatabaseConfig(); - - console.log('数据库配置:'); - console.log(` 主机: ${config.host}`); - console.log(` 端口: ${config.port}`); - console.log(` 用户: ${config.user}`); - console.log(` 数据库: ${config.database}`); - - pool = createPool({ - host: config.host, - port: config.port, - user: config.user, - password: config.password, - database: config.database, - connectionLimit: 5 - }); - - try { - const conn = await pool.getConnection(); - console.log('数据库连接成功!'); - conn.release(); - } catch (error) { - console.error('数据库连接失败:', error.message); - throw error; - } - } - return pool; -} - -async function checkAndFillUSDCNYRate(startDate = null, endDate = null) { - try { - await initPool(); - - console.log('='.repeat(60)); - console.log('开始检查和补充人民币美元汇率数据...'); - console.log('='.repeat(60)); - - const currentDate = new Date(); - - if (!startDate) { - startDate = new Date(currentDate); - startDate.setDate(startDate.getDate() - 30); - startDate = startDate.toISOString().split('T')[0]; - } - - if (!endDate) { - endDate = currentDate.toISOString().split('T')[0]; - } - - console.log(`检查时间范围: ${startDate} 至 ${endDate}`); - - // 1. 检查数据库中是否存在汇率数据表 - const hasTable = await checkTableExists(); - if (!hasTable) { - console.log('创建汇率数据表...'); - await createExchangeRateTable(); - } - - // 2. 检查数据库中已有的数据 - const existingData = await getExistingExchangeRates(startDate, endDate); - console.log(`数据库中已有 ${existingData.length} 条数据`); - - // 3. 计算缺失的日期 - const missingDates = calculateMissingDates(startDate, endDate, existingData); - console.log(`缺失 ${missingDates.length} 天的数据`); - - // 4. 如果有缺失,调用 fetcher 补足 - if (missingDates.length > 0) { - console.log('\n开始获取缺失的汇率数据...'); - - const firstMissingDate = missingDates[0]; - const lastMissingDate = missingDates[missingDates.length - 1]; - - console.log(`获取范围: ${firstMissingDate} 至 ${lastMissingDate}`); - - const fetchedData = await fetchUSDToCNYRate(firstMissingDate, lastMissingDate); - - console.log(`\n获取到 ${fetchedData.count} 条数据`); - - // 5. 存储新数据到数据库 - const storedCount = await storeExchangeRates(fetchedData.data); - console.log(`成功存储 ${storedCount} 条数据`); - } else { - console.log('\n数据库数据完整,无需补充'); - } - - // 6. 验证最终数据完整性 - const finalData = await getExistingExchangeRates(startDate, endDate); - console.log(`\n验证后的数据总量: ${finalData.length} 条`); - - return { - startDate, - endDate, - existingCount: existingData.length, - missingCount: missingDates.length, - finalCount: finalData.length, - success: true - }; - - } catch (error) { - console.error('检查和补充汇率数据失败:', error.message); - throw error; - } finally { - if (pool) { - await pool.end(); - console.log('数据库连接池已关闭'); - } - } -} - -async function checkTableExists() { - try { - console.log('检查汇率数据表是否存在...'); - const result = await pool.query('SHOW TABLES LIKE ?', ['econ_ExchangeRate']); - return result.length > 0; - } catch (error) { - console.error('检查表存在失败:', error.message); - return false; - } -} - -async function createExchangeRateTable() { - try { - console.log('执行创建汇率数据表SQL...'); - await pool.query(` - CREATE TABLE IF NOT EXISTS econ_ExchangeRate ( - id INT AUTO_INCREMENT PRIMARY KEY, - date DATE UNIQUE NOT NULL, - currency VARCHAR(10) NOT NULL, - centerPrice DECIMAL(10,4) NOT NULL, - sellingRate DECIMAL(10,4) NOT NULL, - buyingRate DECIMAL(10,4) NOT NULL, - source VARCHAR(100) NOT NULL, - createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - `); - console.log('汇率数据表创建成功'); - } catch (error) { - console.error('创建汇率数据表失败:', error.message); - throw error; - } -} - -async function getExistingExchangeRates(startDate, endDate) { - try { - console.log('查询已有汇率数据...'); - const result = await pool.query( - 'SELECT date FROM econ_ExchangeRate WHERE date BETWEEN ? AND ? ORDER BY date', - [startDate, endDate] - ); - const dates = result.map(row => { - if (row.date instanceof Date) { - return row.date.toISOString().split('T')[0]; - } else if (typeof row.date === 'string') { - return row.date; - } - return null; - }).filter(Boolean); - console.log(`查询到 ${dates.length} 条数据`); - return dates; - } catch (error) { - console.error('查询已有汇率数据失败:', error.message); - return []; - } -} - -function calculateMissingDates(startDate, endDate, existingDates) { - const existingSet = new Set(existingDates); - const missingDates = []; - - const currentDate = new Date(startDate); - const finalDate = new Date(endDate); - - while (currentDate <= finalDate) { - const dateStr = currentDate.toISOString().split('T')[0]; - const dayOfWeek = currentDate.getDay(); - - // 跳过周末(周末没有汇率数据) - if (dayOfWeek !== 0 && dayOfWeek !== 6) { - if (!existingSet.has(dateStr)) { - missingDates.push(dateStr); - } - } - - currentDate.setDate(currentDate.getDate() + 1); - } - - return missingDates; -} - -async function storeExchangeRates(rates) { - let storedCount = 0; - - for (const rate of rates) { - try { - await pool.query( - 'INSERT INTO econ_ExchangeRate (date, currency, centerPrice, sellingRate, buyingRate, source) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE centerPrice = VALUES(centerPrice), sellingRate = VALUES(sellingRate), buyingRate = VALUES(buyingRate), source = VALUES(source)', - [rate.date, rate.currency, rate.centerPrice, rate.sellingRate, rate.buyingRate, rate.source] - ); - storedCount++; - } catch (error) { - console.error(`存储汇率数据 ${rate.date} 失败:`, error.message); - } - } - - return storedCount; -} - -export async function trigger(startDate = null, endDate = null) { - return await checkAndFillUSDCNYRate(startDate, endDate); -} - -function parseCommandLineArgs() { - const args = process.argv.slice(2); - let startDate = null; - let endDate = null; - - for (let i = 0; i < args.length; i++) { - if (args[i] === '--start' && i + 1 < args.length) { - startDate = args[i + 1]; - i++; - } else if (args[i] === '--end' && i + 1 < args.length) { - endDate = args[i + 1]; - i++; - } else if (args[i] === '--help') { - console.log('使用方法:'); - console.log(' bun usd-cny-rate.scraper.js [选项]'); - console.log(''); - console.log('选项:'); - console.log(' --start YYYY-MM-DD 指定开始日期'); - console.log(' --end YYYY-MM-DD 指定结束日期'); - console.log(' --help 显示帮助信息'); - console.log(''); - console.log('示例:'); - console.log(' bun usd-cny-rate.scraper.js'); - console.log(' bun usd-cny-rate.scraper.js --start 2024-01-01 --end 2024-01-31'); - process.exit(0); - } - } - - return { startDate, endDate }; -} - -async function runTest() { - console.log('='.repeat(60)); - console.log('测试 usd-cny-rate.scraper.js'); - console.log('='.repeat(60)); - - const { startDate, endDate } = parseCommandLineArgs(); - - if (startDate) { - console.log(`\n使用命令行参数:`); - console.log(` 开始日期: ${startDate}`); - console.log(` 结束日期: ${endDate || '默认'}`); - } - - console.log('\n测试数据库配置读取...'); - const config = loadDatabaseConfig(); - console.log('配置读取结果:'); - console.log(` 主机: ${config.host}`); - console.log(` 端口: ${config.port}`); - console.log(` 用户: ${config.user}`); - console.log(` 数据库: ${config.database}`); - console.log('配置读取测试完成!'); - - try { - const result = await trigger(startDate, endDate); - console.log('\n' + '='.repeat(60)); - console.log('测试结果:'); - console.log(` 开始日期: ${result.startDate}`); - console.log(` 结束日期: ${result.endDate}`); - console.log(` 原有数据: ${result.existingCount} 条`); - console.log(` 缺失数据: ${result.missingCount} 条`); - console.log(` 最终数据: ${result.finalCount} 条`); - console.log(` 操作成功: ${result.success}`); - console.log('='.repeat(60)); - } catch (error) { - console.error('测试失败:', error); - process.exit(1); - } -} - -runTest(); diff --git a/scripts/seed.b64 b/scripts/seed.b64 deleted file mode 100644 index 73753d2..0000000 --- a/scripts/seed.b64 +++ /dev/null @@ -1 +0,0 @@ -LyoqCiAqIGlCb2FyZCDln7rnoYDmlbDmja7liJ3lp4vljJbohJrmnKwKICog55So5rOVOiBucG0gcnVuIGRiOnNlZWQKICog5bmC562JOiDkuLvplK4v5ZSv5LiA6ZSu5Yay56qB6Lez6L+HLCBTdHJhdGVneS9JbmRpY2F0b3Ig5oyJIG5hbWUg5a2Y5Zyo5YiZ5pu05pawCiAqLwoKcmVxdWlyZSgnZG90ZW52JykuY29uZmlnKCk7CmNvbnN0IGNyeXB0byA9IHJlcXVpcmUoJ2NyeXB0bycpOwpjb25zdCBteXNxbCA9IHJlcXVpcmUoJ215c3FsMi9wcm9taXNlJyk7CgpmdW5jdGlvbiBoYXNoUGFzc3dvcmQocGxhaW4pIHsKICBjb25zdCBzYWx0ID0gY3J5cHRvLnJhbmRvbUJ5dGVzKDE2KS50b1N0cmluZygnaGV4Jyk7CiAgY29uc3QgaGFzaCA9IGNyeXB0by5zY3J5cHRTeW5jKHBsYWluLCBzYWx0LCA2NCkudG9TdHJpbmcoJ2hleCcpOwogIHJldHVybiAnc2NyeXB0JCcgKyBzYWx0ICsgJyQnICsgaGFzaDsKfQoKZnVuY3Rpb24gcGFyc2VVcmwodXJsKSB7CiAgY29uc3QgdSA9IG5ldyBVUkwodXJsKTsKICByZXR1cm4gewogICAgaG9zdDogdS5ob3N0bmFtZSwKICAgIHBvcnQ6IE51bWJlcih1LnBvcnQpIHx8IDMzMDYsCiAgICB1c2VyOiBkZWNvZGVVUklDb21wb25lbnQodS51c2VybmFtZSksCiAgICBwYXNzd29yZDogZGVjb2RlVVJJQ29tcG9uZW50KHUucGFzc3dvcmQpLAogICAgZGF0YWJhc2U6IHUucGF0aG5hbWUucmVwbGFjZSgvXlwvLywgJycpLAogIH07Cn0KCmFzeW5jIGZ1bmN0aW9uIGluc2VydElnbm9yZShjb25uLCBzcWwsIHBhcmFtcykgewogIHRyeSB7CiAgICBjb25zdCBbcl0gPSBhd2FpdCBjb25uLmV4ZWN1dGUoc3FsLCBwYXJhbXMpOwogICAgcmV0dXJuIHIuYWZmZWN0ZWRSb3dzOwogIH0gY2F0Y2ggKGUpIHsKICAgIGlmIChlLmNvZGUgPT09ICdFUl9EVVBfRU5UUlknKSByZXR1cm4gMDsKICAgIHRocm93IGU7CiAgfQp9Cgphc3luYyBmdW5jdGlvbiB1cHNlcnRCeU5hbWUoY29ubiwgdGFibGUsIHBheWxvYWQsIGtleUZpZWxkKSB7CiAgY29uc3QgZmllbGRzID0gT2JqZWN0LmtleXMocGF5bG9hZCk7CiAgY29uc3QgcGxhY2Vob2xkZXJzID0gZmllbGRzLm1hcCgoKSA9PiAnPycpLmpvaW4oJywgJyk7CiAgY29uc3QgdXBkYXRlQ2xhdXNlID0gZmllbGRzCiAgICAuZmlsdGVyKChmKSA9PiBmICE9PSBrZXlGaWVsZCkKICAgIC5tYXAoKGYpID0+ICdgJyArIGYgKyAnYCA9IFZBTFVFUyhgJyArIGYgKyAnYCknKQogICAgLmpvaW4oJywgJyk7CiAgY29uc3Qgc3FsID0gJ0lOU0VSVCBJTlRPIGAnICsgdGFibGUgKyAnYCAoJyArIGZpZWxkcy5tYXAoKGYpID0+ICdgJyArIGYgKyAnYCcpLmpvaW4oJywgJykgKyAnKSBWQUxVRVMgKCcgKyBwbGFjZWhvbGRlcnMgKyAnKSBPTiBEVVBMSUNBVEUgS0VZIFVQREFURSAnICsgdXBkYXRlQ2xhdXNlOwogIGNvbnN0IHBhcmFtcyA9IGZpZWxkcy5tYXAoKGYpID0+IHBheWxvYWRbZl0pOwogIGNvbnN0IFtyXSA9IGF3YWl0IGNvbm4uZXhlY3V0ZShzcWwsIHBhcmFtcyk7CiAgcmV0dXJuIHIuYWZmZWN0ZWRSb3dzOwp9Cgphc3luYyBmdW5jdGlvbiBtYWluKCkgewogIGNvbnN0IGNmZyA9IHBhcnNlVXJsKHByb2Nlc3MuZW52LkRBVEFCQVNFX1VSTCk7CiAgaWYgKCFjZmcuaG9zdCkgdGhyb3cgbmV3IEVycm9yKCdEQVRBQkFTRV9VUkwg5pyq6YWN572u5oiW6Kej5p6Q5aSx6LSlJyk7CiAgY29uc3QgY29ubiA9IGF3YWl0IG15c3FsLmNyZWF0ZUNvbm5lY3Rpb24oY2ZnKTsKICBjb25zb2xlLmxvZygn5bey6L+e5o6lOicsIGNmZy5ob3N0ICsgJzonICsgY2ZnLnBvcnQgKyAnLycgKyBjZmcuZGF0YWJhc2UpOwoKICBjb25zdCBhZG1pblB3ZCA9IGhhc2hQYXNzd29yZCgnYWRtaW4xMjMnKTsKICBjb25zdCBuMSA9IGF3YWl0IGluc2VydElnbm9yZSgKICAgIGNvbm4sCiAgICAnSU5TRVJUIElOVE8gYFVzZXJgIChuYW1lLCBlbWFpbCwgcGFzc3dvcmQsIGNyZWF0ZWRBdCwgdXBkYXRlZEF0KSBWQUxVRVMgKD8sID8sID8sIE5PVygzKSwgTk9XKDMpKScsCiAgICBbJ0FkbWluJywgJ2FkbWluQGlib2FyZC5sb2NhbCcsIGFkbWluUHdkXSwKICApOwogIGNvbnNvbGUubG9nKCdbVXNlcl0gICAgICAgIOaWsOWinueuoeeQhuWRmDogJyArIG4xICsgJyDooYwgKGFkbWluQGlib2FyZC5sb2NhbCAvIGFkbWluMTIzKScpOwoKICBjb25zdCBzdHJhdGVnaWVzID0gWwogICAgeyBuYW1lOiAndXMtZGVidC1nZHAnLCBkZXNjcmlwdGlvbjogJ+e+juWbveaUv+W6nOWAuuWKoS9HRFDliIbmnpAnLAogICAgICBkYXRhOiB7IHNvdXJjZTogJ3N0cmF0ZWd5LmpzJywgdW5pdDogJ+S4h+S6v+e+juWFgycsIHJvd3M6IFsKICAgICAgICB7IHllYXI6ICcyMDE5JywgZGVidDogMjIuNywgZ2RwOiAyMS40LCBkZWJ0VG9HZHA6IDEwNi4xLCBpbnRlcmVzdFRvR2RwOiAxLjggfSwKICAgICAgICB7IHllYXI6ICcyMDIwJywgZGVidDogMjYuOSwgZ2RwOiAyMC45LCBkZWJ0VG9HZHA6IDEyOC43LCBpbnRlcmVzdFRvR2RwOiAxLjYgfSwKICAgICAgICB7IHllYXI6ICcyMDIxJywgZGVidDogMjguNCwgZ2RwOiAyMy4wLCBkZWJ0VG9HZHA6IDEyMy41LCBpbnRlcmVzdFRvR2RwOiAxLjUgfSwKICAgICAgICB7IHllYXI6ICcyMDIyJywgZGVidDogMzAuOSwgZ2RwOiAyNS40LCBkZWJ0VG9HZHA6IDEyMS42LCBpbnRlcmVzdFRvR2RwOiAxLjYgfSwKICAgICAgICB7IHllYXI6ICcyMDIzJywgZGVidDogMzMuMSwgZ2RwOiAyNi45LCBkZWJ0VG9HZHA6IDEyMy4wLCBpbnRlcmVzdFRvR2RwOiAxLjkgfSwKICAgICAgICB7IHllYXI6ICcyMDI0JywgZGVidDogMzUuMywgZ2RwOiAyOC41LCBkZWJ0VG9HZHA6IDEyMy45LCBpbnRlcmVzdFRvR2RwOiAyLjEgfSwKICAgICAgXX0sCiAgICB9LAogICAgeyBuYW1lOiAnY2hpbmEtZGVidC1nZHAnLCBkZXNjcmlwdGlvbjogJ+S4reWbveaUv+W6nOWAuuWKoS9HRFDliIbmnpAnLAogICAgICBkYXRhOiB7IHNvdXJjZTogJ3N0cmF0ZWd5LmpzJywgdW5pdDogJ+S4h+S6v+e+juWFgycsIHJvd3M6IFsKICAgICAgICB7IHllYXI6ICcyMDE5JywgZGVidDogOC44LCBnZHA6IDE0LjMsIGRlYnRUb0dkcDogNjEuNSwgaW50ZXJlc3RUb0dkcDogMS45IH0sCiAgICAgICAgeyB5ZWFyOiAnMjAyMCcsIGRlYnQ6IDEwLjMsIGdkcDogMTQuNywgZGVidFRvR2RwOiA3MC4xLCBpbnRlcmVzdFRvR2RwOiAxLjggfSwKICAgICAgICB7IHllYXI6ICcyMDIxJywgZGVidDogMTEuNywgZ2RwOiAxNi4xLCBkZWJ0VG9HZHA6IDcyLjcsIGludGVyZXN0VG9HZHA6IDEuNyB9LAogICAgICAgIHsgeWVhcjogJzIwMjInLCBkZWJ0OiAxMy4xLCBnZHA6IDE3LjksIGRlYnRUb0dkcDogNzMuMiwgaW50ZXJlc3RUb0dkcDogMS42IH0sCiAgICAgICAgeyB5ZWFyOiAnMjAyMycsIGRlYnQ6IDE0LjUsIGdkcDogMTkuNCwgZGVidFRvR2RwOiA3NC43LCBpbnRlcmVzdFRvR2RwOiAxLjYgfSwKICAgICAgICB7IHllYXI6ICcyMDI0JywgZGVidDogMTUuOSwgZ2RwOiAyMS4wLCBkZWJ0VG9HZHA6IDc1LjcsIGludGVyZXN0VG9HZHA6IDEuNSB9LAogICAgICBdfSwKICAgIH0sCiAgXTsKICBsZXQgbjIgPSAwOwogIGZvciAoY29uc3QgcyBvZiBzdHJhdGVnaWVzKSB7CiAgICBuMiArPSBhd2FpdCB1cHNlcnRCeU5hbWUoY29ubiwgJ1N0cmF0ZWd5JywKICAgICAgeyBuYW1lOiBzLm5hbWUsIGRlc2NyaXB0aW9uOiBzLmRlc2NyaXB0aW9uLCBkYXRhOiBKU09OLnN0cmluZ2lmeShzLmRhdGEpLCB1cGRhdGVkQXQ6IG5ldyBEYXRlKCkgfSwKICAgICAgJ25hbWUnKTsKICB9CiAgY29uc29sZS5sb2coJ1tTdHJhdGVneV0gICAgdXBzZXJ0IOetlueVpTogJyArIG4yICsgJyDooYwgKDE95paw5aKeLCAyPeabtOaWsCknKTsKCiAgY29uc3QgaW5kaWNhdG9ycyA9IFsKICAgIHsgbmFtZTogJ2NoaW5hLWJvbmQtMXknLCBncm91cE5hbWU6ICdjaGluYS1ib25kJywKICAgICAgZGF0YTogeyB1bml0OiAnJScsIHRpdGxlOiAn5Lit5Zu95Zu95YC6MeW5tCcsIHJvd3M6IFsKICAgICAgICB7IG1vbnRoOiAnMjAyNC0wMScsIHZhbHVlOiAxLjg1IH0sIHsgbW9udGg6ICcyMDI0LTAyJywgdmFsdWU6IDEuODIgfSwKICAgICAgICB7IG1vbnRoOiAnMjAyNC0wMycsIHZhbHVlOiAxLjc4IH0sIHsgbW9udGg6ICcyMDI0LTA0JywgdmFsdWU6IDEuNzUgfSwKICAgICAgICB7IG1vbnRoOiAnMjAyNC0wNScsIHZhbHVlOiAxLjcyIH0sIHsgbW9udGg6ICcyMDI0LTA2JywgdmFsdWU6IDEuNjggfSwKICAgICAgXX19LAogICAgeyBuYW1lOiAnY2hpbmEtYm9uZC0xMHknLCBncm91cE5hbWU6ICdjaGluYS1ib25kJywKICAgICAgZGF0YTogeyB1bml0OiAnJScsIHRpdGxlOiAn5Lit5Zu95Zu95YC6MTDlubQnLCByb3dzOiBbCiAgICAgICAgeyBtb250aDogJzIwMjQtMDEnLCB2YWx1ZTogMi42NSB9LCB7IG1vbnRoOiAnMjAyNC0wMicsIHZhbHVlOiAyLjYyIH0sCiAgICAgICAgeyBtb250aDogJzIwMjQtMDMnLCB2YWx1ZTogMi41OCB9LCB7IG1vbnRoOiAnMjAyNC0wNCcsIHZhbHVlOiAyLjU1IH0sCiAgICAgICAgeyBtb250aDogJzIwMjQtMDUnLCB2YWx1ZTogMi41MiB9LCB7IG1vbnRoOiAnMjAyNC0wNicsIHZhbHVlOiAyLjQ4IH0sCiAgICAgIF19fSwKICAgIHsgbmFtZTogJ3VzLWJvbmQtMXknLCBncm91cE5hbWU6ICd1cy1ib25kJywKICAgICAgZGF0YTogeyB1bml0OiAnJScsIHRpdGxlOiAn576O5Zu95Zu95YC6MeW5tCcsIHJvd3M6IFsKICAgICAgICB7IG1vbnRoOiAnMjAyNC0wMScsIHZhbHVlOiA0Ljg1IH0sIHsgbW9udGg6ICcyMDI0LTAyJywgdmFsdWU6IDQuODIgfSwKICAgICAgICB7IG1vbnRoOiAnMjAyNC0wMycsIHZhbHVlOiA0Ljc4IH0sIHsgbW9udGg6ICcyMDI0LTA0JywgdmFsdWU6IDQuNjUgfSwKICAgICAgICB7IG1vbnRoOiAnMjAyNC0wNScsIHZhbHVlOiA0LjU1IH0sIHsgbW9udGg6ICcyMDI0LTA2JywgdmFsdWU6IDQuNDUgfSwKICAgICAgXX19LAogICAgeyBuYW1lOiAnY2hpbmEtbHByLTF5JywgZ3JvdXBOYW1lOiAnY2hpbmEtbHByJywKICAgICAgZGF0YTogeyB1bml0OiAnJScsIHRpdGxlOiAn5Lit5Zu9TFBSIDHlubTmnJ8nLCByb3dzOiBbCiAgICAgICAgeyBtb250aDogJzIwMjQtMDEnLCB2YWx1ZTogMy40NSB9LCB7IG1vbnRoOiAnMjAyNC0wMicsIHZhbHVlOiAzLjQ1IH0sCiAgICAgICAgeyBtb250aDogJzIwMjQtMDMnLCB2YWx1ZTogMy40NSB9LCB7IG1vbnRoOiAnMjAyNC0wNCcsIHZhbHVlOiAzLjQwIH0sCiAgICAgICAgeyBtb250aDogJzIwMjQtMDUnLCB2YWx1ZTogMy40MCB9LCB7IG1vbnRoOiAnMjAyNC0wNicsIHZhbHVlOiAzLjQwIH0sCiAgICAgIF19fSwKICBdOwogIGxldCBuMyA9IDA7CiAgZm9yIChjb25zdCBpIG9mIGluZGljYXRvcnMpIHsKICAgIG4zICs9IGF3YWl0IHVwc2VydEJ5TmFtZShjb25uLCAnSW5kaWNhdG9yJywKICAgICAgeyBuYW1lOiBpLm5hbWUsIGdyb3VwTmFtZTogaS5ncm91cE5hbWUsIGRhdGE6IEpTT04uc3RyaW5naWZ5KGkuZGF0YSksIHVwZGF0ZWRBdDogbmV3IERhdGUoKSB9LAogICAgICAnbmFtZScpOwogIH0KICBjb25zb2xlLmxvZygnW0luZGljYXRvcl0gICB1cHNlcnQg5oyH5qCHOiAnICsgbjMgKyAnIOihjCAoMT3mlrDlop4sIDI95pu05pawKScpOwoKICBjb25zdCBhcnRpY2xlID0gewogICAgdGl0bGU6ICfmrKLov47kvb/nlKggaUJvYXJkJywKICAgIHNsdWc6ICd3ZWxjb21lLXRvLWlib2FyZCcsCiAgICBjb250ZW50OiAnaUJvYXJkIOeUqOS6juaUtumbhuWSjOaVtOeQhue7j+a1juaVsOaNru+8jOS7peetlueVpeS4uuS4u+S9k+mHjeaWsOe7hOe7h+OAglxuXG7mnKzmlofkuLrnpLrkvovmnaHnm67vvIzmraPlvI/mlofnq6Dku43nlLEgY29udGVudC9hcnRpY2xlcy8qLm1kIOaWh+S7tuaJv+i9veOAgicsCiAgICBkYXRlOiAnMjAyNi0wOC0wMycsCiAgfTsKICBjb25zdCBuNCA9IGF3YWl0IGluc2VydElnbm9yZSgKICAgIGNvbm4sCiAgICAnSU5TRVJUIElOVE8gYEFydGljbGVgICh0aXRsZSwgc2x1ZywgY29udGVudCwgZGF0ZSwgY3JlYXRlZEF0LCB1cGRhdGVkQXQpIFZBTFVFUyAoPywgPywgPywgPywgTk9XKDMpLCBOT1coMykpJywKICAgIFthcnRpY2xlLnRpdGxlLCBhcnRpY2xlLnNsdWcsIGFydGljbGUuY29udGVudCwgYXJ0aWNsZS5kYXRlXSwKICApOwogIGNvbnNvbGUubG9nKCdbQXJ0aWNsZV0gICAgIOaWsOWinuekuuS+i+aWh+eroDogJyArIG40ICsgJyDooYwnKTsKCiAgY29uc3Qgc293Um93cyA9IFsKICAgIHsgbW9udGg6ICcyMDI0LTAxJywgaW52ZW50b3J5OiA0MDY3IH0sCiAgICB7IG1vbnRoOiAnMjAyNC0wMicsIGludmVudG9yeTogNDA0MiB9LAogICAgeyBtb250aDogJzIwMjQtMDMnLCBpbnZlbnRvcnk6IDM5OTIgfSwKICAgIHsgbW9udGg6ICcyMDI0LTA0JywgaW52ZW50b3J5OiAzOTg2IH0sCiAgICB7IG1vbnRoOiAnMjAyNC0wNScsIGludmVudG9yeTogMzk5NiB9LAogICAgeyBtb250aDogJzIwMjQtMDYnLCBpbnZlbnRvcnk6IDQwMzggfSwKICBdOwogIGxldCBuNSA9IDA7CiAgZm9yIChjb25zdCByIG9mIHNvd1Jvd3MpIHsKICAgIG41ICs9IGF3YWl0IGluc2VydElnbm9yZSgKICAgICAgY29ubiwKICAgICAgJ0lOU0VSVCBJTlRPIGBFY29uX1Nvd0ludmVudG9yeWAgKG1vbnRoLCBpbnZlbnRvcnksIGNyZWF0ZWRBdCwgdXBkYXRlZEF0KSBWQUxVRVMgKD8sID8sIE5PVygzKSwgTk9XKDMpKScsCiAgICAgIFtyLm1vbnRoLCByLmludmVudG9yeV0sCiAgICApOwogIH0KICBjb25zb2xlLmxvZygnW0Vjb25fU293SW52ZW50b3J5XSDmlrDlop46ICcgKyBuNSArICcg6KGMJyk7CgogIGF3YWl0IGNvbm4uZW5kKCk7CiAgY29uc29sZS5sb2coJ1xuU2VlZCDlrozmiJAuJyk7Cn0KCm1haW4oKS5jYXRjaCgoZSkgPT4geyBjb25zb2xlLmVycm9yKCdTZWVkIOWksei0pTonLCBlKTsgcHJvY2Vzcy5leGl0KDEpOyB9KTs= \ No newline at end of file diff --git a/scripts/seed.cjs b/scripts/seed.cjs index 21317ae..7b426da 100644 --- a/scripts/seed.cjs +++ b/scripts/seed.cjs @@ -1,133 +1,87 @@ -/** - * iBoard 基础数据初始化脚本 - * 用法: npm run db:seed - * 幂等: 主键/唯一键冲突跳过, Strategy/Indicator 按 name 存在则更新 - */ - -require('dotenv').config(); -const crypto = require('crypto'); -const mysql = require('mysql2/promise'); - -function hashPassword(plain) { - const salt = crypto.randomBytes(16).toString('hex'); - const hash = crypto.scryptSync(plain, salt, 64).toString('hex'); - return 'scrypt$' + salt + '$' + hash; -} - -function parseUrl(url) { - const u = new URL(url); - return { - host: u.hostname, - port: Number(u.port) || 3306, - user: decodeURIComponent(u.username), - password: decodeURIComponent(u.password), - database: u.pathname.replace(/^\//, ''), - }; -} - -async function insertIgnore(conn, sql, params) { - try { - const [r] = await conn.execute(sql, params); - return r.affectedRows; - } catch (e) { - if (e.code === 'ER_DUP_ENTRY') return 0; - throw e; - } -} - -async function upsertByName(conn, table, payload, keyField) { - const fields = Object.keys(payload); - const placeholders = fields.map(() => '?').join(', '); - const updateClause = fields - .filter((f) => f !== keyField) - .map((f) => '`' + f + '` = VALUES(`' + f + '`)') - .join(', '); - const sql = 'INSERT INTO `' + table + '` (' + fields.map((f) => '`' + f + '`').join(', ') + ') VALUES (' + placeholders + ') ON DUPLICATE KEY UPDATE ' + updateClause; - const params = fields.map((f) => payload[f]); - const [r] = await conn.execute(sql, params); - return r.affectedRows; -} - -async function main() { - const cfg = parseUrl(process.env.DATABASE_URL); - if (!cfg.host) throw new Error('DATABASE_URL 未配置或解析失败'); - const conn = await mysql.createConnection(cfg); - console.log('已连接:', cfg.host + ':' + cfg.port + '/' + cfg.database); - - const adminPwd = hashPassword('admin123'); - const n1 = await insertIgnore( - conn, - 'INSERT INTO `User` (name, email, password, createdAt, updatedAt) VALUES (?, ?, ?, NOW(3), NOW(3))', - ['Admin', 'admin@iboard.local', adminPwd], - ); - console.log('[User] 新增管理员: ' + n1 + ' 行 (admin@iboard.local / admin123)'); - - const strategies = [ - { name: 'us-debt-gdp', description: '美国政府债务/GDP分析', - data: { source: 'strategy.js', unit: '万亿美元', rows: [ - { year: '2019', debt: 22.7, gdp: 21.4, debtToGdp: 106.1, interestToGdp: 1.8 }, - { year: '2020', debt: 26.9, gdp: 20.9, debtToGdp: 128.7, interestToGdp: 1.6 }, - { year: '2021', debt: 28.4, gdp: 23.0, debtToGdp: 123.5, interestToGdp: 1.5 }, - { year: '2022', debt: 30.9, gdp: 25.4, debtToGdp: 121.6, interestToGdp: 1.6 }, - { year: '2023', debt: 33.1, gdp: 26.9, debtToGdp: 123.0, interestToGdp: 1.9 }, - { year: '2024', debt: 35.3, gdp: 28.5, debtToGdp: 123.9, interestToGdp: 2.1 }, - ]}, - }, - { name: 'china-debt-gdp', description: '中国政府债务/GDP分析', - data: { source: 'strategy.js', unit: '万亿美元', rows: [ - { year: '2019', debt: 8.8, gdp: 14.3, debtToGdp: 61.5, interestToGdp: 1.9 }, - { year: '2020', debt: 10.3, gdp: 14.7, debtToGdp: 70.1, interestToGdp: 1.8 }, - { year: '2021', debt: 11.7, gdp: 16.1, debtToGdp: 72.7, interestToGdp: 1.7 }, - { year: '2022', debt: 13.1, gdp: 17.9, debtToGdp: 73.2, interestToGdp: 1.6 }, - { year: '2023', debt: 14.5, gdp: 19.4, debtToGdp: 74.7, interestToGdp: 1.6 }, - { year: '2024', debt: 15.9, gdp: 21.0, debtToGdp: 75.7, interestToGdp: 1.5 }, - ]}, - }, - ]; - let n2 = 0; - for (const s of strategies) { - n2 += await upsertByName(conn, 'Strategy', - { name: s.name, description: s.description, data: JSON.stringify(s.data), updatedAt: new Date() }, - 'name'); - } - console.log('[Strategy] upsert 策略: ' + n2 + ' 行 (1=新增, 2=更新)'); - - // Indicator 表: 改由 scripts/refresh-indicators.cjs 管理 (2026-08 重构后数据存 IndicatorPoint 明细表) - // 本脚本不再写入 Indicator, 如需初始化指标请运行 npm run data:refresh - - const article = { - title: '欢迎使用 iBoard', - slug: 'welcome-to-iboard', - content: 'iBoard 用于收集和整理经济数据,以策略为主体重新组织。\n\n本文为示例条目,正式文章仍由 content/articles/*.md 文件承载。', - date: '2026-08-03', - }; - const n4 = await insertIgnore( - conn, - 'INSERT INTO `Article` (title, slug, content, date, createdAt, updatedAt) VALUES (?, ?, ?, ?, NOW(3), NOW(3))', - [article.title, article.slug, article.content, article.date], - ); - console.log('[Article] 新增示例文章: ' + n4 + ' 行'); - - const sowRows = [ - { month: '2024-01', inventory: 4067 }, - { month: '2024-02', inventory: 4042 }, - { month: '2024-03', inventory: 3992 }, - { month: '2024-04', inventory: 3986 }, - { month: '2024-05', inventory: 3996 }, - { month: '2024-06', inventory: 4038 }, - ]; - let n5 = 0; - for (const r of sowRows) { - n5 += await insertIgnore( - conn, - 'INSERT INTO `Econ_SowInventory` (month, inventory, createdAt, updatedAt) VALUES (?, ?, NOW(3), NOW(3))', - [r.month, r.inventory], - ); - } - console.log('[Econ_SowInventory] 新增: ' + n5 + ' 行'); - - await conn.end(); - console.log('\nSeed 完成.'); -} - +/** + * iBoard 基础数据初始化脚本 + * 用法: npm run db:seed + * 幂等: 主键/唯一键冲突跳过 + */ + +require('dotenv').config(); +const crypto = require('crypto'); +const mysql = require('mysql2/promise'); + +function hashPassword(plain) { + const salt = crypto.randomBytes(16).toString('hex'); + const hash = crypto.scryptSync(plain, salt, 64).toString('hex'); + return 'scrypt$' + salt + '$' + hash; +} + +function parseUrl(url) { + const u = new URL(url); + return { + host: u.hostname, + port: Number(u.port) || 3306, + user: decodeURIComponent(u.username), + password: decodeURIComponent(u.password), + database: u.pathname.replace(/^\//, ''), + }; +} + +async function insertIgnore(conn, sql, params) { + try { + const [r] = await conn.execute(sql, params); + return r.affectedRows; + } catch (e) { + if (e.code === 'ER_DUP_ENTRY') return 0; + throw e; + } +} + +async function main() { + const cfg = parseUrl(process.env.DATABASE_URL); + if (!cfg.host) throw new Error('DATABASE_URL 未配置或解析失败'); + const conn = await mysql.createConnection(cfg); + console.log('已连接:', cfg.host + ':' + cfg.port + '/' + cfg.database); + + const adminPwd = hashPassword('admin123'); + const n1 = await insertIgnore( + conn, + 'INSERT INTO `User` (name, email, password, createdAt, updatedAt) VALUES (?, ?, ?, NOW(3), NOW(3))', + ['Admin', 'admin@iboard.local', adminPwd], + ); + console.log('[User] 新增管理员: ' + n1 + ' 行 (admin@iboard.local / admin123)'); + + const article = { + title: '欢迎使用 iBoard', + slug: 'welcome-to-iboard', + content: 'iBoard 用于收集和整理经济数据,以策略为主体重新组织。\n\n本文为示例条目,正式文章仍由 content/articles/*.md 文件承载。', + date: '2026-08-03', + }; + const n4 = await insertIgnore( + conn, + 'INSERT INTO `Article` (title, slug, content, date, createdAt, updatedAt) VALUES (?, ?, ?, ?, NOW(3), NOW(3))', + [article.title, article.slug, article.content, article.date], + ); + console.log('[Article] 新增示例文章: ' + n4 + ' 行'); + + const sowRows = [ + { month: '2024-01', inventory: 4067 }, + { month: '2024-02', inventory: 4042 }, + { month: '2024-03', inventory: 3992 }, + { month: '2024-04', inventory: 3986 }, + { month: '2024-05', inventory: 3996 }, + { month: '2024-06', inventory: 4038 }, + ]; + let n5 = 0; + for (const r of sowRows) { + n5 += await insertIgnore( + conn, + 'INSERT INTO `Econ_SowInventory` (month, inventory, createdAt, updatedAt) VALUES (?, ?, NOW(3), NOW(3))', + [r.month, r.inventory], + ); + } + console.log('[Econ_SowInventory] 新增: ' + n5 + ' 行'); + + await conn.end(); + console.log('\nSeed 完成.'); +} + main().catch((e) => { console.error('Seed 失败:', e); process.exit(1); }); \ No newline at end of file diff --git a/scripts/verify-data.cjs b/scripts/verify-data.cjs deleted file mode 100644 index 23a39aa..0000000 --- a/scripts/verify-data.cjs +++ /dev/null @@ -1,22 +0,0 @@ -require('dotenv').config(); -const mysql = require('mysql2/promise'); -(async () => { - const u = new URL(process.env.DATABASE_URL); - const c = await mysql.createConnection({ - host: u.hostname, port: Number(u.port), - user: decodeURIComponent(u.username), - password: decodeURIComponent(u.password), - database: u.pathname.replace(/^\//, ''), - }); - const [users] = await c.query('SELECT id,name,email,LEFT(password,20) AS pwd_prefix,createdAt FROM User'); - console.log('User:'); console.table(users); - const [sow] = await c.query('SELECT * FROM Econ_SowInventory ORDER BY month'); - console.log('Econ_SowInventory:'); console.table(sow); - const [str] = await c.query('SELECT id,name,description,JSON_LENGTH(data,"$.rows") AS row_count FROM Strategy'); - console.log('Strategy:'); console.table(str); - const [ind] = await c.query('SELECT id,name,groupName,JSON_LENGTH(data,"$.rows") AS row_count FROM Indicator'); - console.log('Indicator:'); console.table(ind); - const [art] = await c.query('SELECT id,title,slug,date FROM Article'); - console.log('Article:'); console.table(art); - await c.end(); -})().catch(e=>{console.error(e);process.exit(1)}); diff --git a/scripts/verify-prisma.mjs b/scripts/verify-prisma.mjs deleted file mode 100644 index 8c5afc1..0000000 --- a/scripts/verify-prisma.mjs +++ /dev/null @@ -1,31 +0,0 @@ -import 'dotenv/config' -import { PrismaMariaDb } from '@prisma/adapter-mariadb' -import { PrismaClient } from '../generated/prisma/client.ts' - -function parseDatabaseUrl(url) { - const u = new URL(url) - return { - host: u.hostname, - port: Number(u.port) || 3306, - user: decodeURIComponent(u.username), - password: decodeURIComponent(u.password), - database: u.pathname.replace(/^\//, ''), - } -} - -const p = new PrismaClient({ adapter: new PrismaMariaDb(parseDatabaseUrl(process.env.DATABASE_URL)) }) -try { - const users = await p.user.findMany() - console.log('User count:', users.length, '->', users.map((u) => u.email).join(',')) - const strategies = await p.strategy.findMany({ select: { id: true, name: true } }) - console.log('Strategy count:', strategies.length, '->', strategies.map((s) => s.name).join(',')) - const indicators = await p.indicator.findMany({ select: { id: true, name: true } }) - console.log('Indicator count:', indicators.length, '->', indicators.map((i) => i.name).join(',')) - const sow = await p.econ_SowInventory.findMany({ orderBy: { month: 'asc' } }) - console.log('SowInventory count:', sow.length, '->', sow.map((s) => s.month + '=' + s.inventory).join(',')) -} catch (e) { - console.error('ERR:', e.message) - process.exitCode = 1 -} finally { - await p.$disconnect() -} \ No newline at end of file diff --git a/scripts/verify-tables.cjs b/scripts/verify-tables.cjs deleted file mode 100644 index c5d3fc2..0000000 --- a/scripts/verify-tables.cjs +++ /dev/null @@ -1,22 +0,0 @@ -require('dotenv').config(); -const mysql = require('mysql2/promise'); - -(async () => { - const url = new URL(process.env.DATABASE_URL); - const conn = await mysql.createConnection({ - host: url.hostname, - port: Number(url.port), - user: decodeURIComponent(url.username), - password: decodeURIComponent(url.password), - database: url.pathname.replace(/^\//, ''), - }); - const [tables] = await conn.query('SHOW TABLES'); - console.log('TABLES:'); - for (const row of tables) console.log(' -', Object.values(row)[0]); - const tableNames = ['User', 'Article', 'Strategy', 'Indicator', 'Econ_SowInventory']; - for (const t of tableNames) { - const [r] = await conn.query('SELECT COUNT(*) AS c FROM ' + t + ''); - console.log(t + ': ' + r[0].c + ' rows'); - } - await conn.end(); -})().catch((e) => { console.error('ERR:', e.message); process.exit(1); }); diff --git a/scripts/verify-tables.js b/scripts/verify-tables.js deleted file mode 100644 index 570e0fe..0000000 --- a/scripts/verify-tables.js +++ /dev/null @@ -1,15 +0,0 @@ -const { PrismaClient } = require('../generated/prisma'); -(async () => { - const p = new PrismaClient(); - const tables = await p.$queryRawUnsafe('SHOW TABLES'); - console.log('TABLES:', JSON.stringify(tables, null, 2)); - const counts = { - User: await p.user.count(), - Article: await p.article.count(), - Strategy: await p.strategy.count(), - Indicator: await p.indicator.count(), - Econ_SowInventory: await p.econ_SowInventory.count(), - }; - console.log('COUNTS:', JSON.stringify(counts, null, 2)); - await p.$disconnect(); -})().catch((e) => { console.error(e); process.exit(1); }); diff --git a/scripts/verify-tables.mjs b/scripts/verify-tables.mjs deleted file mode 100644 index c121cff..0000000 --- a/scripts/verify-tables.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import 'dotenv/config'; -import { PrismaClient } from '../generated/prisma/client.ts'; - -const url = process.env.DATABASE_URL; -const p = new PrismaClient({ datasourceUrl: url }); -try { - const tables = await p.$queryRawUnsafe('SHOW TABLES'); - console.log('TABLES:', JSON.stringify(tables, null, 2)); - const counts = { - User: await p.user.count(), - Article: await p.article.count(), - Strategy: await p.strategy.count(), - Indicator: await p.indicator.count(), - Econ_SowInventory: await p.econ_SowInventory.count(), - }; - console.log('COUNTS:', JSON.stringify(counts, null, 2)); -} catch (e) { - console.error('ERR:', e.message); - process.exitCode = 1; -} finally { - await p.$disconnect(); -} diff --git a/server/alert-engine.js b/server/alert-engine.js deleted file mode 100644 index 7182973..0000000 --- a/server/alert-engine.js +++ /dev/null @@ -1,46 +0,0 @@ -import { prisma } from './db.js' - -// 跨源对比: 同 (indicatorName, period) 多源数据差异超阈值则插 Alert -// conn: mysql2 连接 -// 阈值: 绝对差 >= 0.01 且 相对差 >= 1% -// 返回 { inserted, scanned } -const ABS_TOL = 0.01 -const REL_TOL = 0.01 - -export async function checkAfterRefresh(conn) { - // conn 是 mysql2 连接, 走原始 SQL - const [rows] = await conn.execute(` - SELECT ia.name AS indicatorName, a.period, ia.source, a.value - FROM IndicatorPoint a - JOIN Indicator ia ON a.indicatorId = ia.id - ORDER BY ia.name, a.period - `) - const groups = new Map() - for (const r of rows) { - const k = r.indicatorName + '|' + r.period - if (!groups.has(k)) groups.set(k, []) - groups.get(k).push({ source: r.source, value: Number(r.value) }) - } - let inserted = 0 - for (const [k, arr] of groups) { - if (arr.length < 2) continue - const sources = new Set(arr.map((x) => x.source)) - if (sources.size < 2) continue - for (let i = 0; i < arr.length; i++) { - for (let j = i + 1; j < arr.length; j++) { - const A = arr[i], B = arr[j] - const diff = Math.abs(A.value - B.value) - const base = Math.max(Math.abs(A.value), Math.abs(B.value), 1e-9) - const ratio = diff / base - if (diff < ABS_TOL || ratio < REL_TOL) continue - const [name, period] = k.split('|') - const [r] = await conn.execute( - 'INSERT INTO `Alert` (`indicatorName`, `period`, `valueA`, `valueB`, `sourceA`, `sourceB`, `diff`, `diffRatio`, `createdAt`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(3))', - [name, period, A.value, B.value, A.source, B.source, diff, ratio], - ) - if (r.affectedRows === 1) inserted++ - } - } - } - return { inserted, scanned: rows.length } -} diff --git a/server/apizero/cache.js b/server/apizero/cache.js deleted file mode 100644 index 1f907b1..0000000 --- a/server/apizero/cache.js +++ /dev/null @@ -1,46 +0,0 @@ -// apizero 接口的文件缓存 -// - 按 key 存 JSON 到磁盘: /.json -// - 存在性检查: 文件在即视为有效 (用户要求: "同一个年份只要存在就不再调用接口") -// - 不做 TTL: 用户没要求, 也避免误命中过期数据 -// -// 环境变量: -// APIZERO_CACHE_DIR 缓存目录 (默认 /cache/apizero) -// -// 未来若要加 TTL / DB 后端, 替换本文件即可, 上层调用方不变. -import fs from 'node:fs' -import path from 'node:path' - -function dir() { - return process.env.APIZERO_CACHE_DIR - ? path.resolve(process.env.APIZERO_CACHE_DIR) - : path.resolve(process.cwd(), 'cache', 'apizero') -} - -// 读缓存, 不存在返回 null -export function read(key) { - const p = path.join(dir(), key + '.json') - if (!fs.existsSync(p)) return null - try { - const raw = fs.readFileSync(p, 'utf8') - return JSON.parse(raw) - } catch (e) { - console.warn(`[apizero-cache] 读 ${key} 失败, 忽略:`, e.message) - return null - } -} - -// 写缓存 (原子: 先写 .tmp 再 rename) -export function write(key, data) { - const d = dir() - fs.mkdirSync(d, { recursive: true }) - const final = path.join(d, key + '.json') - const tmp = final + '.tmp.' + process.pid - fs.writeFileSync(tmp, JSON.stringify(data), 'utf8') - fs.renameSync(tmp, final) - return final -} - -// 存在性检查 -export function has(key) { - return fs.existsSync(path.join(dir(), key + '.json')) -} diff --git a/server/apizero/client.js b/server/apizero/client.js deleted file mode 100644 index 9dfd1aa..0000000 --- a/server/apizero/client.js +++ /dev/null @@ -1,77 +0,0 @@ -// apizero (https://apizero.cn) 通用 HTTP 客户端 -// - 统一从 env APIZERO_KEY 读取 API key (test key 也能用) -// - 统一 QPS 节流 (apizero 限制 3 req/s) -// - 统一错误格式: 抛出 ApizeroError(code, message, httpStatus) -// - 所有 apizero 接口都进这一层, 后续新增模块复用 -// -// 依赖: 原生 fetch (Node 18+) - -// 简易令牌桶, 限速 3 req/s -let lastCallAt = 0 -const MIN_INTERVAL_MS = 1000 / 3 // ~333ms - -async function throttle() { - const now = Date.now() - const wait = lastCallAt + MIN_INTERVAL_MS - now - if (wait > 0) await new Promise((r) => setTimeout(r, wait)) - lastCallAt = Date.now() -} - -// apizero 标准错误 -export class ApizeroError extends Error { - constructor(code, message, httpStatus) { - super(message || `apizero error code=${code}`) - this.name = 'ApizeroError' - this.code = code - this.httpStatus = httpStatus - } -} - -const BASE_URL = 'https://v1.apizero.cn/api' - -// 读 key: 优先 env APIZERO_KEY, 否则用空 (匿名调用, 配额更小) -function getKey() { - return process.env.APIZERO_KEY || '' -} - -// 通用 GET 入口 -// - endpoint: 'oil-price-forecast' (不带 /api 前缀) -// - params: 普通对象 -// 返回: apizero 的 data 字段 (已解一层) -export async function get(endpoint, params = {}) { - await throttle() - - const qs = new URLSearchParams() - for (const [k, v] of Object.entries(params)) { - if (v !== undefined && v !== null && v !== '') qs.set(k, String(v)) - } - const key = getKey() - if (key) qs.set('key', key) - - const url = `${BASE_URL}/${endpoint}?${qs.toString()}` - let res - try { - res = await fetch(url, { method: 'GET', headers: { 'Accept': 'application/json' } }) - } catch (e) { - throw new ApizeroError('NETWORK', `apizero 网络错误: ${e.message}`, 0) - } - - // HTTP 层错误 - if (!res.ok) { - let body = '' - try { body = await res.text() } catch { /* ignore */ } - throw new ApizeroError('HTTP', `apizero HTTP ${res.status}: ${body.substring(0, 200)}`, res.status) - } - - // 解析 body - let json - try { json = await res.json() } - catch (e) { throw new ApizeroError('PARSE', 'apizero 响应非 JSON: ' + e.message, res.status) } - - // apizero 业务层错误: code !== 0 - if (json && typeof json.code === 'number' && json.code !== 0) { - throw new ApizeroError(json.code, json.msg || 'apizero 业务错误', res.status) - } - - return json && json.data !== undefined ? json.data : json -} diff --git a/server/apizero/index.js b/server/apizero/index.js deleted file mode 100644 index 95cbd21..0000000 --- a/server/apizero/index.js +++ /dev/null @@ -1,6 +0,0 @@ -// apizero 模块统一出口 -// 后续新增 apizero 接口就在这里再 export 一行 -export * as oil from './oil-price-forecast.js' -export * as history from './oil-history.js' -export { ApizeroError } from './client.js' - diff --git a/server/apizero/oil-history.js b/server/apizero/oil-history.js deleted file mode 100644 index 3d4b419..0000000 --- a/server/apizero/oil-history.js +++ /dev/null @@ -1,87 +0,0 @@ -// 浙江省油价历史: 走 Prisma 落库 + apizero price 接口写入 -// 设计: -// - 每天 04:00 cron 检查 schedule, 待定且 <= 昨天的调价日 -> 抓 price?province=浙江 存本表 -// - unique (date, province, fuel) 防重复 -// - 查询 API: 取某省某油品最近 N 天 (默认 180) -import { prisma } from '../db.js' -import * as client from './client.js' - -const PROVINCE = '浙江' -// 油品 -> apizero prices 对象里的 key (price action 返回 "92号汽油" 这种带后缀的 key) -const FUEL_KEYS = { - '92': '92号汽油', - '95': '95号汽油', - '0': '0号柴油', -} - -// 解析 apizero price action 返回的 prices (中文 key) -> 标准 fuel code -function parsePrice(data) { - if (!data || !data.prices) throw new Error('apizero 响应无 prices 字段') - const out = {} - for (const [fuel, label] of Object.entries(FUEL_KEYS)) { - const raw = data.prices[label] - if (typeof raw === 'string') { - // "7.64 元/升" -> 7.64 - const m = raw.match(/^([\d.]+)/) - if (m) out[fuel] = parseFloat(m[1]) - } else if (typeof raw === 'number') { - out[fuel] = raw - } - } - return out -} - -// 抓一次浙江当前价并入库 (date = 今日) -// 返回 { inserted, skipped } (按 unique 约束: 已存在则 skip) -export async function fetchAndStore() { - const data = await client.get('oil-price-forecast', { action: 'price', province: PROVINCE }) - const prices = parsePrice(data) - if (Object.keys(prices).length === 0) { - throw new Error('apizero 返回价格为空') - } - const date = new Date() - // 抹掉时分秒, 便于按天比较 - date.setHours(0, 0, 0, 0) - - let inserted = 0 - let skipped = 0 - for (const [fuel, price] of Object.entries(prices)) { - try { - await prisma.oilPriceHistory.create({ - data: { date, province: PROVINCE, fuel, price, source: 'apizero' }, - }) - inserted++ - } catch (e) { - // P2002: unique 冲突 -> 跳过 - if (e?.code === 'P2002') skipped++ - else throw e - } - } - return { inserted, skipped, date: date.toISOString().slice(0, 10) } -} - -// 查询某省某油品最近 N 天 -// province 必填, 默认 浙江 -// fuel 可选, 留空返回 4 个油品 -// days 默认 180 -export async function query({ province = PROVINCE, fuel, days = 180 }) { - const since = new Date() - since.setDate(since.getDate() - days) - since.setHours(0, 0, 0, 0) - - const where = { province, date: { gte: since } } - if (fuel) where.fuel = fuel - - const rows = await prisma.oilPriceHistory.findMany({ - where, - orderBy: [{ date: 'asc' }], - }) - - // 整形: Decimal -> number - return rows.map((r) => ({ - date: r.date.toISOString().slice(0, 10), - province: r.province, - fuel: r.fuel, - price: Number(r.price), - })) -} diff --git a/server/apizero/oil-price-forecast.js b/server/apizero/oil-price-forecast.js deleted file mode 100644 index 2f2ae3c..0000000 --- a/server/apizero/oil-price-forecast.js +++ /dev/null @@ -1,41 +0,0 @@ -// apizero: 今日油价 (oil-price-forecast) -// 4 个 action: -// forecast - 油价 + 国际原油 + 下次调价预测 (每次都查实时, 不缓存) -// price - 单省油价 -// price-all - 32 省全部油价 -// schedule - 某年调价日历 (按 year 缓存到文件, 同年份不重复调) -// -// 文档: https://apizero.cn/aidocs/oil-price-forecast/raw.md -import * as client from './client.js' -import * as cache from './cache.js' - -// 调价日历: 按年份缓存, 文件存在即视为有效, 不重复调用 -// cache key 形如 "oil-schedule-2026" -export async function getSchedule(year) { - const y = Number(year) - if (!Number.isInteger(y) || y < 2025 || y > 2026) { - throw new client.ApizeroError(4000, `year 不在 2025-2026 范围: ${year}`, 400) - } - const cacheKey = `oil-schedule-${y}` - const hit = cache.read(cacheKey) - if (hit) return { ...hit, _cached: true } - const data = await client.get('oil-price-forecast', { action: 'schedule', year: y }) - cache.write(cacheKey, data) - return { ...data, _cached: false } -} - -// 实时: 国际原油 + 下次调价预测 -export async function getForecast() { - return client.get('oil-price-forecast', { action: 'forecast' }) -} - -// 单省油价 -export async function getPrice(province) { - if (!province) throw new client.ApizeroError(4000, 'province 必填', 400) - return client.get('oil-price-forecast', { action: 'price', province }) -} - -// 32 省全部油价 -export async function getPriceAll() { - return client.get('oil-price-forecast', { action: 'price-all' }) -} diff --git a/server/cron.js b/server/cron.js index a5fa86c..4ccc33a 100644 --- a/server/cron.js +++ b/server/cron.js @@ -1,110 +1,52 @@ -import cron from 'node-cron' -import { prisma } from './db.js' -import mysql from 'mysql2/promise' -import { checkAfterRefresh } from './alert-engine.js' -import { ApizeroError, oil, history } from './apizero/index.js' - -// scripts/ 下的可选脚本模块 (部署容器可能缺失或依赖不全): 惰性加载, -// 单个 fetcher 缺失只降级对应任务, 不拖垮 cron 整体注册。 -async function importNewsFetcher() { - const mod = await import('../scripts/fetchers/news.fetcher.js') - return { fetchNewsList: mod.fetchNewsList, persistNews: mod.persistNews } -} - -async function alertError(taskName, message) { - try { - const { insertCronAlert } = await import('../scripts/lib/cron-alert.js') - await insertCronAlert(taskName, message) - } catch (e) { - console.error('[cron] 报警写入失败(忽略):', e.message) - } -} - -function parseUrl(url) { - const u = new URL(url) - return { host: u.hostname, port: Number(u.port) || 3306, user: decodeURIComponent(u.username), password: decodeURIComponent(u.password), database: u.pathname.replace(/^\//, '') } -} - -async function openConn() { - return mysql.createConnection(parseUrl(process.env.DATABASE_URL)) -} - -// 每天 6:00 (Asia/Shanghai) 拉取新闻 + 跑跨源对比, 出错生成报警 -function scheduleNews() { - cron.schedule('0 6 * * *', async () => { - console.log('[cron] 6:00 触发') - try { - const { fetchNewsList, persistNews } = await importNewsFetcher() - const list = await fetchNewsList() - const r = await persistNews(list) - console.log('[cron] 新闻入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped) - const conn = await openConn() - try { - const a = await checkAfterRefresh(conn) - console.log('[cron] 报警检测: 新增 ' + a.inserted + ' 条 (扫描 ' + a.scanned + ')') - } finally { await conn.end() } - } catch (e) { - console.error('[cron] 任务失败:', e.message) - await alertError('新闻拉取+报警检测', e.message) - } - }, { timezone: 'Asia/Shanghai' }) - console.log('[cron] 已注册: 每天 06:00 拉取新闻 + 跨源报警检测') -} - -// 每天 6:10 (Asia/Shanghai) 抓浙江油价历史, 出错生成报警 -// 逻辑: 拉今年 schedule, 检查待定且 <= 昨天的调价日, 命中就抓 price?province=浙江 存库 -// - 0 命中: 不调 apizero (省配额) -// - >0 命中: 调一次, 4 个油品批量入库 (unique 兜底去重) -function scheduleOilHistory() { - cron.schedule('10 6 * * *', async () => { - console.log('[cron] 6:10 油价历史触发') - try { - const year = new Date().getFullYear() - const sched = await oil.getSchedule(year) - const list = sched.schedule || [] - // 待定 且 日期 <= 昨天 (用 Asia/Shanghai 时区推导, 与 cron 触发时区对齐) - const todayStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Shanghai' }) - const today = new Date(todayStr + 'T00:00:00+08:00') - const yesterdayMs = today.getTime() - 86400000 - const pendingPast = list.filter((it) => { - if (it.status !== '待定') return false - const t = new Date(it.date + 'T00:00:00').getTime() - return Number.isFinite(t) && t <= yesterdayMs - }) - console.log('[cron] 待定且已过的调价日: ' + pendingPast.length + ' 个') - if (pendingPast.length === 0) { - console.log('[cron] 油价历史: 0 命中, 跳过 apizero 调用') - return - } - const r = await history.fetchAndStore() - console.log('[cron] 油价历史入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped + ' (date=' + r.date + ')') - } catch (e) { - if (e instanceof ApizeroError) { - console.error('[cron] 油价历史 apizero 错误: code=' + e.code + ' msg=' + e.message) - } else { - console.error('[cron] 油价历史任务失败:', e.message) - } - await alertError('浙江油价历史抓取', e.message) - } - }, { timezone: 'Asia/Shanghai' }) - console.log('[cron] 已注册: 每天 06:10 浙江油价历史抓取') -} - -// 每小时兜底: 任务自动归档扫描 (T-ARC-02; 项目加载时另有读时惰性执行) -function scheduleAutoArchive() { - cron.schedule('30 * * * *', async () => { - try { - const { runAutoArchiveSweep } = await import('./task-domain/archive.js') - const n = await runAutoArchiveSweep() - if (n > 0) console.log('[cron] 自动归档:', n, '条') - } catch (e) { - console.error('[cron] 自动归档失败:', e.message) - await alertError('任务自动归档', e.message) - } - }, { timezone: 'Asia/Shanghai' }) - console.log('[cron] 已注册: 每小时 30 分 任务自动归档扫描') -} - -scheduleNews() -scheduleOilHistory() -scheduleAutoArchive() +import cron from 'node-cron' + +// scripts/ 下的可选脚本模块 (部署容器可能缺失或依赖不全): 惰性加载, +// 单个 fetcher 缺失只降级对应任务, 不拖垮 cron 整体注册。 +async function importNewsFetcher() { + const mod = await import('../scripts/fetchers/news.fetcher.js') + return { fetchNewsList: mod.fetchNewsList, persistNews: mod.persistNews } +} + +async function alertError(taskName, message) { + try { + const { insertCronAlert } = await import('../scripts/lib/cron-alert.js') + await insertCronAlert(taskName, message) + } catch (e) { + console.error('[cron] 报警写入失败(忽略):', e.message) + } +} + +// 每天 6:00 (Asia/Shanghai) 拉取新闻, 出错生成报警 +function scheduleNews() { + cron.schedule('0 6 * * *', async () => { + console.log('[cron] 6:00 触发') + try { + const { fetchNewsList, persistNews } = await importNewsFetcher() + const list = await fetchNewsList() + const r = await persistNews(list) + console.log('[cron] 新闻入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped) + } catch (e) { + console.error('[cron] 任务失败:', e.message) + await alertError('新闻拉取', e.message) + } + }, { timezone: 'Asia/Shanghai' }) + console.log('[cron] 已注册: 每天 06:00 拉取新闻') +} + +// 每小时兜底: 任务自动归档扫描 (T-ARC-02; 项目加载时另有读时惰性执行) +function scheduleAutoArchive() { + cron.schedule('30 * * * *', async () => { + try { + const { runAutoArchiveSweep } = await import('./task-domain/archive.js') + const n = await runAutoArchiveSweep() + if (n > 0) console.log('[cron] 自动归档:', n, '条') + } catch (e) { + console.error('[cron] 自动归档失败:', e.message) + await alertError('任务自动归档', e.message) + } + }, { timezone: 'Asia/Shanghai' }) + console.log('[cron] 已注册: 每小时 30 分 任务自动归档扫描') +} + +scheduleNews() +scheduleAutoArchive() diff --git a/server/index.js b/server/index.js index 1d803de..1662099 100644 --- a/server/index.js +++ b/server/index.js @@ -9,7 +9,6 @@ import newsRouter from './routes/news.js' import alertsRouter from './routes/alerts.js' import articlesRouter from './routes/articles.js' import otaRouter from './routes/ota.js' -import apizeroRouter from './routes/apizero.js' import monitorsRouter from './routes/monitors.js' import mihomeRouter from './routes/mihome.js' import checkinRouter from './routes/checkin.js' @@ -17,12 +16,15 @@ import checklistsRouter from './routes/checklists.js' import projectsRouter, { columnRouter } from './routes/projects.js' import tasksRouter from './routes/tasks.js' import powersyncRouter from './routes/powersync.js' +import { powersyncProxy } from './powersync-proxy.js' const PORT = Number(process.env.PORT) || 3001 const DIST_DIR = path.join(process.cwd(), 'dist') const HAS_DIST = fs.existsSync(DIST_DIR) const app = express() +// 同步服务同域反代必须最先挂载 (流式长连接, 不能经过 json/cors 中间件) +app.use(powersyncProxy()) app.use(cors()) app.use(express.json()) @@ -40,7 +42,6 @@ app.use('/api', dataRouter) app.use('/api', newsRouter) app.use('/api', alertsRouter) app.use('/api', articlesRouter) -app.use('/api', apizeroRouter) app.use('/api', monitorsRouter) app.use('/api/mihome', mihomeRouter) // V0.2 打卡 / 日历 / 清单 diff --git a/server/powersync-proxy.js b/server/powersync-proxy.js new file mode 100644 index 0000000..ab3c0d2 --- /dev/null +++ b/server/powersync-proxy.js @@ -0,0 +1,54 @@ +// PowerSync 同域反代 (0.2.1): 把 /sync、/socket 转发到内网同步服务, +// 对外只暴露应用本身一个端口 (无需单独的 powersync 端口)。 +// - 未配置 POWERSYNC_UPSTREAM 时直接跳过 (本地开发/无同步服务时行为不变) +// - 必须挂在 express.json()/cors 之前: 同步走长连接流式响应, 不能被 body 解析打断 +import http from 'node:http' +import https from 'node:https' + +const PREFIXES = ['/sync', '/socket'] + +export function powersyncProxy() { + const upstream = process.env.POWERSYNC_UPSTREAM + if (!upstream) return (_req, _res, next) => next() + + let target + try { + target = new URL(upstream) + } catch { + console.error('[powersync-proxy] POWERSYNC_UPSTREAM 非法:', upstream) + return (_req, _res, next) => next() + } + const transport = target.protocol === 'https:' ? https : http + const basePort = Number(target.port) || (target.protocol === 'https:' ? 443 : 80) + console.log('[powersync-proxy] 同步请求转发到', target.origin) + + return (req, res, next) => { + if (!PREFIXES.some((p) => req.url === p || req.url.startsWith(p + '/') || req.url.startsWith(p + '?'))) { + return next() + } + const headers = { ...req.headers, host: target.host } + const upstreamReq = transport.request( + { + protocol: target.protocol, + hostname: target.hostname, + port: basePort, + method: req.method, + path: req.url, + headers, + }, + (upstreamRes) => { + res.writeHead(upstreamRes.statusCode || 502, upstreamRes.headers) + upstreamRes.pipe(res) + }, + ) + upstreamReq.on('error', (e) => { + console.error('[powersync-proxy] 转发失败:', e.message) + if (!res.headersSent) res.writeHead(502, { 'content-type': 'text/plain' }) + res.end('powersync upstream error') + }) + // 长连接: 客户端断开时同步断开上游 + req.on('aborted', () => upstreamReq.destroy()) + res.on('close', () => upstreamReq.destroy()) + req.pipe(upstreamReq) + } +} diff --git a/server/routes/apizero.js b/server/routes/apizero.js deleted file mode 100644 index 81371ad..0000000 --- a/server/routes/apizero.js +++ /dev/null @@ -1,63 +0,0 @@ -// apizero 代理路由 (前端不直接调 apizero, 一律走后端, key 不外泄) -// -// GET /api/apizero/oil/forecast - 油价 + 预测 (实时) -// GET /api/apizero/oil/price-all - 32 省油价 -// GET /api/apizero/oil/price?province= - 单省油价 -// GET /api/apizero/oil/schedule?year= - 某年调价日历 (按年缓存) -// -// 错误: apizero 错误统一包成 502 (上游错误) 或 400 (参数错误) -import { Router } from 'express' -import { authRequired } from '../middleware.js' -import { ApizeroError, oil, history } from '../apizero/index.js' - -const router = Router() -router.use(authRequired) - -function handleApizeroError(e, res, next) { - if (e instanceof ApizeroError) { - // 参数错 -> 400, 上游错 -> 502, QPS 限流 / 额度用完 -> 503 - if (e.httpStatus === 400) return res.status(400).json({ error: e.message, apizero_code: e.code }) - if (e.code === 4029 || e.code === 4030) return res.status(503).json({ error: e.message, apizero_code: e.code }) - return res.status(502).json({ error: e.message, apizero_code: e.code }) - } - next(e) -} - -// 实时: 油价 + 国际原油 + 下次调价预测 -router.get('/apizero/oil/forecast', async (req, res, next) => { - try { res.json(await oil.getForecast()) } - catch (e) { handleApizeroError(e, res, next) } -}) - -// 32 省油价 -router.get('/apizero/oil/price-all', async (req, res, next) => { - try { res.json(await oil.getPriceAll()) } - catch (e) { handleApizeroError(e, res, next) } -}) - -// 单省油价 -router.get('/apizero/oil/price', async (req, res, next) => { - try { res.json(await oil.getPrice(req.query.province)) } - catch (e) { handleApizeroError(e, res, next) } -}) - -// 调价日历 (按年缓存) -router.get('/apizero/oil/schedule', async (req, res, next) => { - try { res.json(await oil.getSchedule(req.query.year)) } - catch (e) { handleApizeroError(e, res, next) } -}) - -export default router - -// 浙江省油价历史 (DB) -// GET /api/apizero/oil/history?fuel=92&days=180 -// - fuel: 92 / 95 / 0 (留空 = 3 个油品都返回) -// - days: 默认 180 (半年) -router.get('/apizero/oil/history', async (req, res, next) => { - try { - const fuel = req.query.fuel ? String(req.query.fuel) : undefined - const days = Math.max(1, Math.min(Number(req.query.days) || 180, 730)) - const items = await history.query({ fuel, days }) - res.json({ items, fuel: fuel || 'all', days }) - } catch (e) { next(e) } -}) diff --git a/server/routes/data.js b/server/routes/data.js index 0015fa2..058106a 100644 --- a/server/routes/data.js +++ b/server/routes/data.js @@ -6,36 +6,7 @@ const router = Router() router.use(authRequired) -// 指标列表: 元数据 + 明细点, 过滤 hidden (数据与脚本保留, 仅不展示) -router.get('/indicators', async (_req, res, next) => { - try { - const rows = await prisma.indicator.findMany({ - where: { hidden: false }, - include: { points: { orderBy: { period: 'asc' } } }, - orderBy: [{ groupName: 'asc' }, { name: 'asc' }], - }) - // 组装前端友好结构: points 保持从老到新 - const indicators = rows.map((r) => ({ - id: r.id, - name: r.name, - groupName: r.groupName, - title: r.title, - unit: r.unit, - source: r.source, - frequency: r.frequency, - points: r.points.map((p) => ({ period: p.period, value: p.value })), - })) - res.json({ indicators }) - } catch (e) { next(e) } -}) - -router.get('/strategies', async (_req, res, next) => { - try { - const rows = await prisma.strategy.findMany({ orderBy: { name: 'asc' } }) - res.json({ strategies: rows }) - } catch (e) { next(e) } -}) - +// 能繁母猪存栏 (独立抓取域, 不属于宏观「经济数据」外接模块) router.get('/sow', async (_req, res, next) => { try { const rows = await prisma.econ_SowInventory.findMany({ orderBy: { month: 'asc' } }) @@ -43,4 +14,4 @@ router.get('/sow', async (_req, res, next) => { } catch (e) { next(e) } }) -export default router \ No newline at end of file +export default router diff --git a/server/routes/powersync.js b/server/routes/powersync.js index 0a6b095..fca5653 100644 --- a/server/routes/powersync.js +++ b/server/routes/powersync.js @@ -7,6 +7,7 @@ import jwt from 'jsonwebtoken' import { prisma } from '../db.js' import { JWT_SECRET, authRequired } from '../middleware.js' import { genUuid, normProjectCode, allocProjectCode, withTaskNumber } from '../task-domain/identity.js' +import { resolveTaskUpload } from '../task-domain/resolveStatus.js' const router = Router() @@ -96,6 +97,17 @@ async function applyPut(op, ctx) { if (d.taskKinds !== undefined) { data.taskKinds = Array.isArray(d.taskKinds) ? d.taskKinds.map((k) => String(k).slice(0, 20)).filter(Boolean) : null } + // 项目图标: data URL (客户端已裁剪为 96x96 JPEG); 校验口径同 REST 写接口 + if (d.icon !== undefined) { + if (d.icon === null || d.icon === '') { + data.icon = null + } else { + const s = String(d.icon) + if (!s.startsWith('data:image/')) throw new Error('图标格式非法') + if (s.length > 300_000) throw new Error('图标过大') + data.icon = s + } + } if (Object.keys(data).length) await prisma.project.update({ where: { uuid }, data }) return {} } @@ -183,18 +195,43 @@ async function applyPut(op, ctx) { if (d.extras !== undefined) data.extras = cleanExtras(d.extras) if (d.sortOrder !== undefined) data.sortOrder = Number(d.sortOrder) || 0 if (d.assigneeId !== undefined) data.assigneeId = d.assigneeId ? Number(d.assigneeId) : null - if (d.doneAt !== undefined) data.doneAt = isoTime(d.doneAt) // 归档状态 (T-ARC): 客户端本地归档/恢复走这里 if (d.archived !== undefined) data.archived = !!d.archived if (d.archivedAt !== undefined) data.archivedAt = isoTime(d.archivedAt) if (d.archiveSource !== undefined) { data.archiveSource = ['manual', 'auto'].includes(d.archiveSource) ? d.archiveSource : null } + + // 列 / 状态: 复用 REST 写接口的同一裁决口径 (server/task-domain/resolveStatus.js) + // 客户端上行的是本地整行 (含 status 与 columnId), 变更检测与裁决 + // 统一封装在 resolveTaskUpload 里 (那边有详细注释与用例) + let nextColumnId if (d.columnId !== undefined) { const col = await prisma.boardColumn.findUnique({ where: { uuid: d.columnId } }) if (!col || col.projectId !== existing.projectId) throw new Error('看板列不存在') - data.columnId = col.id + nextColumnId = col.id } + if (d.status !== undefined && !TASK_STATUSES.includes(d.status)) { + throw new Error('任务状态取值非法') + } + const resolved = resolveTaskUpload( + existing, + { columnId: nextColumnId, status: d.status }, + await ctx.loadColumns(existing.projectId), + ) + if (resolved) { + data.status = resolved.status + data.columnId = resolved.columnId + // doneAt 与最终状态保持一致 (口径同 REST 写接口): 进入 done 记时, 离开 done 清空 + if (resolved.status === 'done') { + if (existing.status !== 'done') data.doneAt = isoTime(d.doneAt) || new Date() + } else { + data.doneAt = null + } + } else if (d.doneAt !== undefined) { + data.doneAt = isoTime(d.doneAt) + } + if (d.parentId !== undefined) { if (!d.parentId) { data.parentId = null @@ -355,7 +392,19 @@ router.post('/upload', authRequired, async (req, res, next) => { memberCache.set(projectId, ok) return ok } - const ctx = { userId, assertMember } + // 项目列缓存: 列/状态裁决需要全部列; 同批内按项目缓存 (ops 已按表序排列, + // BoardColumn 先于 Task 落地, 故 Task 阶段读到的列是本次批次的最终结果) + const columnCache = new Map() + const loadColumns = async (projectId) => { + if (columnCache.has(projectId)) return columnCache.get(projectId) + const rows = await prisma.boardColumn.findMany({ + where: { projectId }, + orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }], + }) + columnCache.set(projectId, rows) + return rows + } + const ctx = { userId, assertMember, loadColumns } // 依赖顺序: 父表先落地 (Project → BoardColumn → ProjectMember → Task → TaskMember/TaskTag); 删除反向 const rank = (o) => TABLE_RANK[o?.table] ?? 99 diff --git a/server/task-domain/resolveStatus.js b/server/task-domain/resolveStatus.js index bcc3993..a69eed5 100644 --- a/server/task-domain/resolveStatus.js +++ b/server/task-domain/resolveStatus.js @@ -83,3 +83,32 @@ export function resolveTaskWrite(task, patch, columns) { return { status, columnId } } + +/** + * 离线同步上传路径专用: 客户端上行的是**本地整行**, 字段总是存在, 因此不能用 + * "字段是否存在"判断变更 —— 必须先与服务端现值比较, 得出"这次真正改了什么", + * 再交给 resolveTaskWrite 裁决。 + * + * 不这样做会踩的坑 (T-BD-16): 任务从 Plan 拖到未绑定状态的 Action 列, 客户端 + * 整行里 status 仍是 todo; 若把 status 当作"本次变更"提交, 场景 B 会把任务从 + * Action 归位回第一个绑定 todo 的 Plan —— 用户拖到 Action 又被弹回去。 + * + * @param {object} existing 服务端现有任务 (含 columnId/status/parentId) + * @param {object} incoming 本次上行值 { columnId?: number, status?: string } + * 外键需调用方先由 uuid 解析为内部 int id + * @param {object[]} columns 项目全部列 + * @returns {{ status: string, columnId: number } | null} null = 列/状态无变化 + */ +export function resolveTaskUpload(existing, incoming, columns) { + const nextColumnId = incoming?.columnId !== undefined ? incoming.columnId : existing.columnId + const patch = {} + if (nextColumnId !== existing.columnId) patch.columnId = nextColumnId + if (incoming?.status !== undefined && incoming.status !== existing.status) patch.status = incoming.status + if (!Object.keys(patch).length) return null + + // task.column 必须与 columnId 一起指向目标列, 否则场景 B 的 + // "当前列已匹配该状态" 会拿旧列判断, 同样导致误归位 + const targetCol = (columns || []).find((c) => c.id === nextColumnId) || null + const r = resolveTaskWrite({ ...existing, columnId: nextColumnId, column: targetCol }, patch, columns) + return { status: r.status, columnId: r.columnId } +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 33d518a..051fc46 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -32,7 +32,7 @@ } ], "security": { - "csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' data: blob: asset: http://asset.localhost; connect-src 'self' ipc: http://ipc.localhost https://iboard.honor3.com http://bh.vps.honor3.com:3001 https://bh.vps.honor3.com:3001; style-src 'self' 'unsafe-inline'; script-src 'self'" + "csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' data: blob: asset: http://asset.localhost https://*.honor3.com http://*.honor3.com; connect-src 'self' ipc: http://ipc.localhost https://iboard.honor3.com http://bh.vps.honor3.com:3001 https://bh.vps.honor3.com:3001 https://ifinance.honor3.com http://ifinance.honor3.com https://*.honor3.com https://*.honor3.com:* http://*.honor3.com http://*.honor3.com:*; frame-src 'self' https://ifinance.honor3.com http://ifinance.honor3.com https://*.honor3.com https://*.honor3.com:* http://*.honor3.com http://*.honor3.com:*; style-src 'self' 'unsafe-inline'; script-src 'self'" } }, "bundle": { diff --git a/src/App.jsx b/src/App.jsx index 068818e..58f2604 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -43,7 +43,7 @@ export default function App() { } /> } /> } /> - {/* 宏观: 报警 / 新闻 / 油价 / 经济数据 / 策略研究 / 经济文章 (V0.2 原首页并入, 默认新闻) */} + {/* 宏观: 新闻 / 经济文章 + 外接 ifinance 分类 (动态 tab); 报警已迁至监控 (V0.2.2) */} } /> } /> } /> diff --git a/src/api/oil.js b/src/api/oil.js deleted file mode 100644 index 913a375..0000000 --- a/src/api/oil.js +++ /dev/null @@ -1,32 +0,0 @@ -// 前端油价 API: 一律走后端 /api/apizero/oil/*, 不直接调 apizero (key 不外泄) -import { api, getApiBase } from './client.js' - -// 实时: 油价 + 国际原油 + 下次调价预测 -export async function getOilForecast() { - return api('/apizero/oil/forecast') -} - -// 32 省油价 (可暂未在 UI 用, 留作 API 暴露) -export async function getOilPriceAll() { - return api('/apizero/oil/price-all') -} - -// 单省油价 -export async function getOilPrice(province) { - return api('/apizero/oil/price?province=' + encodeURIComponent(province)) -} - -// 某年调价日历 (后端按年缓存, 同年份不会重复调 apizero) -export async function getOilSchedule(year) { - return api('/apizero/oil/schedule?year=' + encodeURIComponent(year)) -} - -// 浙江省油价历史 (走 DB, 由 cron 每天 04:00 抓取并落库) -// fuel: '92' | '95' | '0' | undefined (留空 = 3 个油品) -// days: 默认 180 (半年) -export async function getOilHistory({ fuel, days = 180 } = {}) { - const qs = new URLSearchParams() - if (fuel) qs.set('fuel', fuel) - qs.set('days', String(days)) - return api('/apizero/oil/history?' + qs.toString()) -} diff --git a/src/api/task.js b/src/api/task.js index b8e44ba..b4190a6 100644 --- a/src/api/task.js +++ b/src/api/task.js @@ -35,6 +35,13 @@ const DEFAULT_COLUMNS = [ export const DEFAULT_TASK_KINDS = ['需求', 'Bug', 'FEATURE', '优化'] const MAX_NAME = 20 +// 即时同步: 订阅用的本地表集合 (见 docs/V0.2/离线编辑模块/即时同步方案.md) +// TASK_TABLES 影响"某项目的任务列表/看板"的内容 +// PROJECT_TABLES 影响"项目列表"的内容 +// 只用于变更通知, 不参与查询; 表名必须与 PowerSync 客户端 schema 一致。 +export const TASK_TABLES = ['Task', 'BoardColumn', 'TaskMember', 'TaskTag'] +export const PROJECT_TABLES = ['Project', 'ProjectMember'] + function normTaskKinds(v) { if (!Array.isArray(v)) return null const out = [] @@ -135,6 +142,8 @@ function projectSummary(p, memberCount, taskCount) { updatedAt: p.updatedAt, taskKinds: normTaskKinds(parseJson(p.taskKinds, null)) || DEFAULT_TASK_KINDS, icon: p.icon || null, + // 自动归档档位 (T-ARC-02): 必须回传给前端, 否则该设置在界面上永远显示默认的「永不」 + autoArchive: p.autoArchive || 'never', memberCount, taskCount, } @@ -268,6 +277,11 @@ export const projectApi = { content: String(n?.content ?? '').slice(0, 20000), }))) } + if (body?.autoArchive !== undefined) { + // 自动归档档位 (T-ARC-02): 与服务端 AUTO_ARCHIVE_OPTIONS 同口径 + if (!['3d', '1w', '1m', 'never'].includes(body.autoArchive)) throw new Error('自动归档档位非法') + data.autoArchive = body.autoArchive + } const sets = Object.keys(data).map((k) => `"${k}" = ?`) if (sets.length) { await db().execute( @@ -536,10 +550,17 @@ export const taskApi = { } // 列 / 状态联动裁决 (与 server/task-domain/resolveStatus.js 同口径) + // 本地库主键是 uuid 字符串: 若调用方把 id 过了一道 Number() 会变成 NaN / "NaN", + // 这里直接拦下并给出可定位的错误, 而不是让后续比对静默失败 let status = task.status let columnId = String(task.columnId) const patch = {} - if (body?.columnId !== undefined) patch.columnId = String(body.columnId) + if (body?.columnId !== undefined) { + const cid = body.columnId === null ? '' : String(body.columnId) + if (!cid) throw new Error('看板列不能为空') + if (cid === 'NaN' || cid === 'undefined') throw new Error('看板列无效(编号已损坏)') + patch.columnId = cid + } if (body?.status !== undefined) { if (!['todo', 'doing', 'done'].includes(body.status)) throw new Error('任务状态取值非法') patch.status = body.status @@ -694,14 +715,16 @@ export const taskApi = { archive: async (id) => { const task = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)]) if (!task) throw new Error('任务不存在或无权限') - const now = now() + // 不能写 const now = now(): 同名 const 会遮蔽模块级 now, 右侧求值落入 TDZ, + // 压缩后报 "Cannot access 'a' before initialization" + const ts = now() await db().writeTransaction(async (tx) => { const kids = await tx.getAll('SELECT id FROM Task WHERE parentId = ?', [String(task.id)]) const ids = [String(task.id), ...kids.map((k) => String(k.id))] const ph = ids.map(() => '?').join(',') await tx.execute( `UPDATE Task SET archived = 1, archivedAt = ?, archiveSource = 'manual', updatedAt = ? WHERE id IN (${ph})`, - [now, now, ...ids], + [ts, ts, ...ids], ) }) return { ok: true, count: 1 } @@ -711,14 +734,14 @@ export const taskApi = { unarchive: async (id) => { const task = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)]) if (!task) throw new Error('任务不存在或无权限') - const now = now() + const ts = now() await db().writeTransaction(async (tx) => { const kids = await tx.getAll('SELECT id FROM Task WHERE parentId = ?', [String(task.id)]) const ids = [String(task.id), ...kids.map((k) => String(k.id))] const ph = ids.map(() => '?').join(',') await tx.execute( `UPDATE Task SET archived = 0, archivedAt = NULL, archiveSource = NULL, updatedAt = ? WHERE id IN (${ph})`, - [now, ...ids], + [ts, ...ids], ) }) return { ok: true, count: 1 } @@ -747,13 +770,23 @@ export const taskApi = { where.push('columnId = ?') args.push(String(p.columnId)) } + // 日期边界: 传 'YYYY-MM-DD' 时按**本地时区**的那一天换算成 UTC 瞬时再比较。 + // (原来直接拼 'T00:00:00Z', 本地选的日期会偏移 8 小时, 导致当天凌晨的归档被漏掉) + const dayToIso = (v, endOfDay) => { + const s = String(v) + if (s.includes('T')) return s // 已带时间的 ISO, 原样使用 + const d = new Date(`${s}T00:00:00`) // 本地 00:00 + if (Number.isNaN(d.getTime())) return null + if (endOfDay) d.setHours(23, 59, 59, 999) + return d.toISOString() + } if (p.from) { - where.push('archivedAt >= ?') - args.push(String(p.from) + 'T00:00:00+08:00'.replace('+08:00', 'Z')) + const iso = dayToIso(p.from, false) + if (iso) { where.push('archivedAt >= ?'); args.push(iso) } } if (p.to) { - where.push('archivedAt <= ?') - args.push(String(p.to) + 'T23:59:59.999Z') + const iso = dayToIso(p.to, true) + if (iso) { where.push('archivedAt <= ?'); args.push(iso) } } if (p.cursor) { const [ts, cid] = String(p.cursor).split('|') diff --git a/src/components/TrendBadge.jsx b/src/components/TrendBadge.jsx deleted file mode 100644 index 3561623..0000000 --- a/src/components/TrendBadge.jsx +++ /dev/null @@ -1,54 +0,0 @@ -// 趋势徽标: 上升 / 下降 / 持平 / 震荡 -// props: -// trend: 'up' | 'down' | 'flat' | 'volatile' -// showText: 是否显示文字 (默认 true) -const META = { - up: { icon: '\u2191', text: '上升' }, - down: { icon: '\u2193', text: '下降' }, - flat: { icon: '=', text: '持平' }, - volatile: { icon: '\u223F', text: '震荡' }, -} - -// 根据最近 N 个数据点计算趋势: -// - 数据不足 2 个 → flat -// - 取最近 6 个点的变化, 整体涨幅 < 1% → flat -// - 单调递增 → up, 单调递减 → down -// - 其余 (有升有降) → volatile -// 返回: { trend, change } (change 为百分比变化, 保留 1 位小数) -export function calcTrend(rows) { - if (!rows || rows.length < 2) return { trend: 'flat', change: 0 } - const N = Math.min(6, rows.length) - const recent = rows.slice(-N).map((r) => r.value).filter((v) => typeof v === 'number') - if (recent.length < 2) return { trend: 'flat', change: 0 } - const first = recent[0] - const last = recent[recent.length - 1] - const change = first === 0 ? 0 : ((last - first) / Math.abs(first)) * 100 - if (Math.abs(change) < 1) return { trend: 'flat', change } - let up = 0, down = 0 - for (let i = 1; i < recent.length; i++) { - if (recent[i] > recent[i - 1]) up++ - else if (recent[i] < recent[i - 1]) down++ - } - if (up === recent.length - 1) return { trend: 'up', change } - if (down === recent.length - 1) return { trend: 'down', change } - return { trend: 'volatile', change } -} - -// 取数据序列最后一个有效点: { month, value } 或 null -export function lastPoint(data) { - const rows = (data && data.rows) || [] - for (let i = rows.length - 1; i >= 0; i--) { - if (rows[i] && typeof rows[i].value === 'number') return rows[i] - } - return null -} - -export default function TrendBadge({ trend, showText = true }) { - const m = META[trend] || META.flat - return ( - - {m.icon} - {showText && {m.text}} - - ) -} diff --git a/src/components/checkin/DayRing.jsx b/src/components/checkin/DayRing.jsx new file mode 100644 index 0000000..ea57392 --- /dev/null +++ b/src/components/checkin/DayRing.jsx @@ -0,0 +1,66 @@ +// 打卡日历的日期标记: 环形进度 (F-CL-06) +// +// 为什么是环而不是圆点: +// 旧实现是 4px 小圆点, 只按状态上色 —— 3/5 和 5/5 都是同一个绿点, +// 看不出"当天做到几成"。改成环形后 **弧长 = 当天完成比例**, 颜色表状态: +// 0 < actual < expected -> 弧长按比例 (部分完成) +// actual >= expected -> 整圈 (全部完成) +// actual = 0 且 expected > 0 -> 只有底环 (未开始) +// expected = 0 由父组件判断, 不渲染本组件 (当天没有应打卡项) +// +// 颜色分级 (4 档, 由 CSS 按 `.mv-day` 作用域上色, 各视图可各自调色板): +// 小半 (ratio < 0.5) -> 红 +// 大半 (0.5 <= r < 1) -> 橘 +// 完成 (r = 1) -> 蓝 +// 超额 -> 绿 +// 注意: 服务端统计时把 actual 封顶在 expected (`Math.min(cnt, exp)`), +// 所以 ratio 永远 <= 1, **超额只能靠 status === 'over' 判断**, 不能看比例。 +const DEFAULT_SIZE = 30 +const DEFAULT_STROKE = 2.5 + +/** 4 档分级: 小半 / 大半 / 完成 / 超额 */ +function tierOf(status, p) { + if (status === 'over') return 'over' + if (status === 'full') return 'full' + if (p <= 0) return 'empty' + if (p >= 1) return 'full' + return p < 0.5 ? 'low' : 'high' +} + +export default function DayRing({ + status = 'partial', + ratio = 0, + size = DEFAULT_SIZE, + stroke = DEFAULT_STROKE, +}) { + const p = Math.max(0, Math.min(1, Number(ratio) || 0)) + const tier = tierOf(status, p) + const r = (size - stroke) / 2 + const c = 2 * Math.PI * r + const half = size / 2 + return ( + + ) +} diff --git a/src/components/task/TaskCard.jsx b/src/components/task/TaskCard.jsx index 9f3a781..a609c81 100644 --- a/src/components/task/TaskCard.jsx +++ b/src/components/task/TaskCard.jsx @@ -1,6 +1,7 @@ import StatusDot from './StatusDot.jsx' import Avatar from './Avatar.jsx' import { kindColor } from '../../pages/task/kind.js' +import { taskShortId } from '../../pages/task/date.js' // 看板卡片 (T-UI-02): 轻量化 // - 标题 + 状态点 (T-ST-09) @@ -9,11 +10,13 @@ import { kindColor } from '../../pages/task/kind.js' const MAX_SUB = 3 export default function TaskCard({ - task, codePrefix, bindDrag, onClick, onToggleSub, onToggle, onMore, dragging = false, highlight = false, + task, bindDrag, onClick, onToggleSub, onToggle, onMore, dragging = false, highlight = false, }) { const children = task.children || [] const shown = children.slice(0, MAX_SUB) const rest = children.length - shown.length + // 编号只显示 `#序号`; 完整编号 (项目编号-序号) 放详情页 (见 TaskDetail) + const shortId = taskShortId(task) return (
- {codePrefix && task.number != null && ( - {codePrefix}-{task.number} - )} + {shortId && {shortId}} {task.markDate && ( {task.markDate.slice(5, 10).replace('-', '/')} )} diff --git a/src/components/task/TaskRow.jsx b/src/components/task/TaskRow.jsx index 9bddf99..53880db 100644 --- a/src/components/task/TaskRow.jsx +++ b/src/components/task/TaskRow.jsx @@ -1,6 +1,7 @@ import StatusDot from './StatusDot.jsx' import Avatar from './Avatar.jsx' import { kindColor } from '../../pages/task/kind.js' +import { taskShortId } from '../../pages/task/date.js' const statusLabel = (s) => (s === 'done' ? '完成' : s === 'doing' ? '执行中' : '未开始') @@ -10,9 +11,11 @@ const statusLabel = (s) => (s === 'done' ? '完成' : s === 'doing' ? '执行中 // 复选框 / ⋯ 是独立控件: pointerdown 不冒泡, 不发起拖动, // 避免长按行后抬手时合成的 click 落在控件上误触状态切换 export default function TaskRow({ - task, codePrefix, onToggle, onClick, onMore, bindDrag, bindDragHandle, dragging = false, dragOver, indent = false, children, + task, onToggle, onClick, onMore, bindDrag, bindDragHandle, dragging = false, dragOver, indent = false, children, }) { const done = task.status === 'done' + // 编号只显示 `#序号`; 完整编号 (项目编号-序号) 放详情页 (见 TaskDetail) + const shortId = indent ? null : taskShortId(task) return ( <>
{task.title}
{/* 无类型/成员也保留该行, 与有标签的行等高 */}
- {codePrefix && task.number != null && ( - {codePrefix}-{task.number} - )} + {shortId && {shortId}} {task.markDate && ( {task.markDate.slice(5, 10).replace('-', '/')} )} diff --git a/src/components/task/useColumnDrag.js b/src/components/task/useColumnDrag.js index 58d477f..6c177dd 100644 --- a/src/components/task/useColumnDrag.js +++ b/src/components/task/useColumnDrag.js @@ -1,4 +1,5 @@ import { useCallback, useEffect, useRef, useState } from 'react' +import { datasetId, sameId } from '../../pages/task/ids.js' // 看板卡片列拖动 (重点设计 §7.1) // 方案要点: @@ -60,10 +61,11 @@ export default function useColumnDrag({ onDrop, scrollSelector = '.board-wrap' } /** 拖动刚结束 (350ms 内): 抑制松手合成的 click 误开详情页 */ const justEnded = useCallback(() => Date.now() - lastEnd.current < 350, []) + // 命中列 id: 本地库列 id 是 uuid 字符串, 不能 Number() (会变 NaN -> 落不下去) const hitColumn = (x, y) => { const el = document.elementFromPoint(x, y) const colEl = el?.closest?.('[data-column-id]') - return colEl ? Number(colEl.dataset.columnId) : null + return colEl ? datasetId(colEl.dataset.columnId) : null } // 边缘自动滚动 (±36px 触发, 每帧 10px) @@ -105,7 +107,7 @@ export default function useColumnDrag({ onDrop, scrollSelector = '.board-wrap' } if (s.active) { lastEnd.current = Date.now() const col = hitColumn(ev.clientX, ev.clientY) - if (col && s.task && col !== s.task.columnId) onDropRef.current?.(s.task, col) + if (col && s.task && !sameId(col, s.task.columnId)) onDropRef.current?.(s.task, col) } cleanup() } diff --git a/src/components/task/useRowDrag.js b/src/components/task/useRowDrag.js index c47943c..96db8ba 100644 --- a/src/components/task/useRowDrag.js +++ b/src/components/task/useRowDrag.js @@ -1,4 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { datasetId, sameId } from '../../pages/task/ids.js' // 列表视图任务行拖动: 组间换列 + 组内排序 // 触发: 行长按 400ms, 或按住行首 ⠿ 把手 (touch-action:none + touchstart @@ -78,15 +79,15 @@ export default function useRowDrag({ columns, onDrop, onReorder }) { moveGhost(ev.clientX, ev.clientY) // 组内落点: 命中本组 ([data-column-id]=当前列) 的某条顶层行 + // 本地库的列/任务 id 均为 uuid 字符串 -> 一律按字符串比对, 不要 Number() const hitEl = document.elementFromPoint(ev.clientX, ev.clientY) const rowEl = hitEl?.closest?.('[data-task-id]') const colEl = hitEl?.closest?.('[data-column-id]') - const colId = colEl ? Number(colEl.dataset.columnId) : null - const curCol = orderedRef.current[s.toIdx]?.id + const colId = colEl ? datasetId(colEl.dataset.columnId) : null - if (rowEl && colId === s.task.columnId) { - const id = Number(rowEl.dataset.taskId) - if (id !== s.task.id) { + if (rowEl && sameId(colId, s.task.columnId)) { + const id = datasetId(rowEl.dataset.taskId) + if (id && !sameId(id, s.task.id)) { const r = rowEl.getBoundingClientRect() const hit = { id, after: ev.clientY > r.top + r.height / 2 } if (!s.over || s.over.id !== hit.id || s.over.after !== hit.after) { @@ -100,7 +101,7 @@ export default function useRowDrag({ columns, onDrop, onReorder }) { // 换列: 手指划到哪个分组就是目标列, 划出所有分组时保持上一目标 if (colId != null) { - const to = orderedRef.current.findIndex((c) => c.id === colId) + const to = orderedRef.current.findIndex((c) => sameId(c.id, colId)) if (to >= 0 && to !== s.toIdx) { s.toIdx = to setDrag({ task: s.task, fromIdx: s.fromIdx, toIdx: to }) @@ -116,7 +117,7 @@ export default function useRowDrag({ columns, onDrop, onReorder }) { onReorderRef.current?.(s.task, s.over.id, s.over.after) } else { const target = orderedRef.current[s.toIdx] - if (target && target.id !== s.task.columnId) onDropRef.current?.(s.task, target.id) + if (target && !sameId(target.id, s.task.columnId)) onDropRef.current?.(s.task, target.id) } } cleanup() @@ -152,7 +153,7 @@ export default function useRowDrag({ columns, onDrop, onReorder }) { } const startDrag = (ev, task, instant) => { - const fromIdx = orderedRef.current.findIndex((c) => c.id === task.columnId) + const fromIdx = orderedRef.current.findIndex((c) => sameId(c.id, task.columnId)) if (fromIdx < 0) return // 不在任何列 (如「其他」组) 的任务不支持拖动 st.current = { ...st.current, startX: ev.clientX, startY: ev.clientY, task, fromIdx, toIdx: fromIdx, diff --git a/src/lib/ifinance.js b/src/lib/ifinance.js new file mode 100644 index 0000000..4cf4b81 --- /dev/null +++ b/src/lib/ifinance.js @@ -0,0 +1,116 @@ +// ifinance 外接模块 (宿主侧) +// ------------------------------------------------------------ +// ifinance 是静态托管模块包, 唯一索引是 info.json。宿主约定: +// - 根地址固定为 https://ifinance.honor3.com (宿主常量, 不写进 info.json) +// - 没有统一入口页, 每个 html 都是独立入口 (在 WebView/iframe 中打开) +// - categories[] 对应 App 的 tab, items[] 是条目; 未知字段忽略 +// +// 本模块采用「纯前端直连」方式: 直接 fetch 远端 info.json, 条目 HTML 直接 +// 用 iframe 加载远端地址, 不做端上文件缓存 (始终最新)。 +// 开发环境 (vite dev) 走 /ifinance-api 代理以规避跨域; 生产环境直连。 +// 若远端 info.json 拉取失败, 依次回退: 同源内置快照 (public/ifinance-info.json) +// → localStorage 上次成功快照, 保证分类 tab 仍可渲染。 +import { useEffect, useState } from 'react' + +// 根地址: 生产直连; 开发用 vite 代理前缀 (见 vite.config.js) +export const FINANCE_BASE = import.meta.env.DEV + ? '/ifinance-api' + : 'https://ifinance.honor3.com' + +// 同源兜底快照 (构建时由 ifinance/info.json 拷贝到 public/, 仅在远端不可用时使用) +const FALLBACK_INFO_URL = '/ifinance-info.json' + +const INFO_CACHE_KEY = 'ifinance_info_v1' + +// 模块级缓存: 一次会话内多处 (宏观页 / 详情页) 共享, 避免重复请求 +let memoInfo = null +let inflight = null + +// 拼接 info.json 中给出的相对路径 (如 public/econ/x.html) 为可加载的绝对 URL +export function financeUrl(relPath) { + const p = String(relPath || '').replace(/^\/+/, '') + return FINANCE_BASE + '/' + p +} + +// 条目 HTML 地址: Cloudflare Pages 走 clean URL, 去掉 .html 后缀直达 (避免 307 重定向) +export function financePageUrl(relPath) { + return financeUrl(String(relPath || '').replace(/\.html?$/i, '')) +} + +function readLocalInfo() { + try { + const raw = localStorage.getItem(INFO_CACHE_KEY) + if (!raw) return null + const parsed = JSON.parse(raw) + if (parsed && parsed.info && Array.isArray(parsed.info.categories)) return parsed.info + } catch { /* ignore */ } + return null +} + +function writeLocalInfo(info) { + try { + localStorage.setItem(INFO_CACHE_KEY, JSON.stringify({ ts: Date.now(), info })) + } catch { /* 配额满等忽略 */ } +} + +async function fetchInfoFrom(url) { + const res = await fetch(url, { cache: 'no-cache' }) + if (!res.ok) throw new Error('ifinance 索引加载失败 (' + res.status + ')') + const info = await res.json() + if (!info || !Array.isArray(info.categories)) throw new Error('ifinance 索引格式无效') + return info +} + +// 拉取 (并缓存) info.json: 远端优先, 失败回退同源内置快照 +export async function fetchIfinanceInfo() { + if (inflight) return inflight + inflight = (async () => { + let info + try { + info = await fetchInfoFrom(financeUrl('info.json')) + } catch (remoteErr) { + // 兜底: 同源内置快照 (ifinance 未开 CORS / 临时不可达时仍可渲染分类) + try { + info = await fetchInfoFrom(FALLBACK_INFO_URL) + } catch { + throw remoteErr + } + } + memoInfo = info + writeLocalInfo(info) + return info + })() + try { + return await inflight + } finally { + inflight = null + } +} + +// React Hook: 获取分类索引。优先内存 → localStorage 快照 → 远端 +export function useIfinanceInfo() { + const [state, setState] = useState(() => { + if (memoInfo) return { info: memoInfo, loading: false, error: '' } + const cached = readLocalInfo() + return { info: cached, loading: true, error: '' } + }) + + useEffect(() => { + let alive = true + setState((s) => ({ ...s, loading: true })) + fetchIfinanceInfo() + .then((info) => { if (alive) setState({ info, loading: false, error: '' }) }) + .catch((e) => { + if (!alive) return + const cached = readLocalInfo() + setState({ + info: cached, + loading: false, + error: cached ? '' : (e?.message || '外接模块加载失败'), + }) + }) + return () => { alive = false } + }, []) + + return state +} diff --git a/src/lib/powersync/db.js b/src/lib/powersync/db.js index e272f5e..90af016 100644 --- a/src/lib/powersync/db.js +++ b/src/lib/powersync/db.js @@ -13,6 +13,13 @@ export function getPowersyncDb() { database: { // 默认 IDBBatchAtomicVFS (IndexedDB), 兼容性最广; WebView2 (Chromium) 后续可切 OPFS 加速 dbFilename: 'iboard-powersync.db', + // 关闭多标签支持 —— 否则桌面端 (WebView2/Chromium) 会启用 **共享 IndexedDB worker**: + // enableMultiTabs 默认为 true (桌面且有 SharedWorker 时), 该 SharedWorker 会跨页面 + // reload 存活; OTA 升级后 reload 的新页面连上去时本地库读取会永久挂起 + // (无异常、纯 pending) —— 表现为任务页一直「加载中」, 其它不碰本地库的页面正常。 + // Android WebView 不支持 SharedWorker, 因此只有 Windows 外壳命中。 + // 本应用是单窗口外壳 (dock.html 不访问本地库), 不需要多标签同步。 + enableMultiTabs: false, }, }) } diff --git a/src/lib/powersync/useDbSignal.js b/src/lib/powersync/useDbSignal.js new file mode 100644 index 0000000..b05e3a1 --- /dev/null +++ b/src/lib/powersync/useDbSignal.js @@ -0,0 +1,70 @@ +// 本地库变更订阅 (任务模块即时同步, 见 docs/V0.2/离线编辑模块/即时同步方案.md) +// +// 作用: 当指定表在本地 SQLite 里发生变化时回调 —— 变化可能来自 +// 1) 本端写入 (乐观更新之后) +// 2) PowerSync 下行推送 (其它端 / 服务端改动) +// 两者最终都落到本地库, 所以订阅本地库即可覆盖跨端场景, 且离线时同样有效。 +// +// 为什么不用手写"信号 SQL"求和: SDK 的 watch 已支持按表名过滤 (tables) +// + 节流 (throttleMs), 由 SDK 判断是否真的变化, 比自己做 count/max 汇总可靠。 +// +// 用法: +// useDbSignal(['Task', 'BoardColumn'], () => load()) +// useDbSignal(TASK_TABLES, onChange, { throttleMs: 300, enabled: someFlag }) +import { useEffect, useRef } from 'react' +import { getPowersyncDb } from './db.js' + +// 无意义的占位查询: 只借 watch 的"变更通知"能力, 不关心结果 +const SIGNAL_SQL = 'SELECT 1 AS s' + +export default function useDbSignal(tables, onChange, options = {}) { + const { throttleMs = 250, enabled = true } = options + + // 回调走 ref, 避免调用方每次渲染都重新订阅 + const cbRef = useRef(onChange) + cbRef.current = onChange + + // 表名数组每帧都是新引用, 用内容做依赖 + const tablesKey = Array.isArray(tables) ? tables.join(',') : '' + + useEffect(() => { + // 回滚开关: localStorage['iboard_realtime'] = '0' 时完全不订阅, 退回"手动刷新"行为, + // 用于线上出问题时的快速止损 (改完需重启应用生效) + let realtimeOff = false + try { realtimeOff = localStorage.getItem('iboard_realtime') === '0' } catch { /* ignore */ } + if (!enabled || !tablesKey || realtimeOff) return undefined + + const db = getPowersyncDb() + const ac = new AbortController() + let disposed = false + // watch 订阅成功后会立刻给出一次当前结果, 而调用方挂载时已经主动 load 过, + // 这里跳过首次通知, 避免重复请求 + let first = true + + try { + db.watch( + SIGNAL_SQL, + [], + { + onResult: () => { + if (disposed) return + if (first) { first = false; return } + cbRef.current?.() + }, + onError: (e) => { + if (!disposed) console.warn('[db-signal] watch 出错:', e) + }, + }, + { tables: tablesKey.split(','), throttleMs, signal: ac.signal }, + ) + } catch (e) { + // 订阅失败不应影响页面主流程 (退化为"手动刷新") + console.warn('[db-signal] 订阅失败:', e) + } + + return () => { + disposed = true + ac.abort() + } + }, [tablesKey, throttleMs, enabled]) +} diff --git a/src/pages/MacroPage.jsx b/src/pages/MacroPage.jsx index 9454691..ffa4b47 100644 --- a/src/pages/MacroPage.jsx +++ b/src/pages/MacroPage.jsx @@ -1,43 +1,31 @@ -import { useState } from 'react' import { useNavigate, useLocation, Routes, Route } from 'react-router-dom' -import { Badge } from 'antd-mobile' import PageHeader from '../components/PageHeader.jsx' import SwipeTabs from '../components/SwipeTabs.jsx' -import DataView from './macro/DataView.jsx' -import StrategyView from './macro/StrategyView.jsx' import ArticlesView from './macro/ArticlesView.jsx' import ArticleDetail from './macro/ArticleDetail.jsx' -import OilPage from './macro/OilPage.jsx' -import AlertsView from './macro/AlertsView.jsx' import NewsView from './macro/NewsView.jsx' import NewsDetail from './news/NewsDetail.jsx' +import FinanceCategoryView from './macro/FinanceCategoryView.jsx' +import FinanceItemView from './macro/FinanceItemView.jsx' +import { useIfinanceInfo } from '../lib/ifinance.js' -// 宏观: 顶栏 6 tab, 默认 新闻 -// /macro → 显示 news tab (activeFromPath 决定) -// /macro/alerts → 报警 (V0.2: 原「首页」tab 合并进来, 置最左) -// /macro/news → 新闻 (V0.2: 原「首页」tab 合并进来, 置第二并作为默认) -// /macro/oil → 油价 -// /macro/data → 经济数据 -// /macro/strategy → 策略研究 -// /macro/articles → 经济文章 -// 详情: /macro/data/:name · /macro/strategy/:name · /macro/articles/:slug · /macro/news/:id -// 说明: tab 数量已超出屏宽, SwipeTabs 会在右侧显示渐变 + 箭头提示可继续滑动 -const TABS = [ - { key: 'alerts', label: '报警', path: '/macro/alerts' }, +// 宏观: 本地固定 2 个 tab (新闻 / 经济文章) + 外接 ifinance 分类动态挂载为 tab +// /macro/news → 新闻 (默认) +// /macro/articles → 经济文章 +// /macro/finance/:catId → 外接分类 (经济数据 / 策略研究 / …) +// 详情: /macro/news/:id · /macro/articles/:slug · /macro/finance/:catId/:itemId +// 说明: 外接分类不写死, 由 info.json 的 categories[] 决定; ifinance 新增分类后 +// 本页自动多出新 tab, 宿主无需改代码。报警已迁到「监控」。 +const BASE_TABS = [ { key: 'news', label: '新闻', path: '/macro/news' }, - { key: 'oil', label: '油价', path: '/macro/oil' }, - { key: 'data', label: '经济数据', path: '/macro/data' }, - { key: 'strategy', label: '策略研究', path: '/macro/strategy' }, { key: 'articles', label: '经济文章', path: '/macro/articles' }, ] function activeFromPath(pathname) { - if (pathname.startsWith('/macro/alerts')) return 'alerts' if (pathname.startsWith('/macro/news')) return 'news' - if (pathname.startsWith('/macro/oil')) return 'oil' - if (pathname.startsWith('/macro/data')) return 'data' - if (pathname.startsWith('/macro/strategy')) return 'strategy' if (pathname.startsWith('/macro/articles')) return 'articles' + const m = pathname.match(/^\/macro\/finance\/([^/]+)/) + if (m) return 'finance:' + m[1] // 父路由 /macro 走默认 tab (新闻) return 'news' } @@ -45,43 +33,47 @@ function activeFromPath(pathname) { export default function MacroPage() { const location = useLocation() const navigate = useNavigate() + const { info } = useIfinanceInfo() const active = activeFromPath(location.pathname) - // 报警角标: 由 AlertsView 回传总数 - const [alertCount, setAlertCount] = useState(0) + const categories = info?.categories || [] - const tabs = TABS.map((t) => ({ - key: t.key, - label: t.key === 'alerts' - ? 报警{alertCount > 0 && } - : t.label, - render: () => { - if (t.key === 'oil') return - if (t.key === 'data') return - if (t.key === 'strategy') return - if (t.key === 'articles') return - if (t.key === 'alerts') return - if (t.key === 'news') return - return null - }, - })) - - function onChange(key) { - const next = TABS.find((t) => t.key === key) - if (next) navigate(next.path) - } - - // 详情页单独走 React Router 渲染, 不参与滑屏 - if (location.pathname.match(/^\/macro\/(articles|data|strategy|news)\/[^/]+$/)) { + // 详情页单独走 React Router 渲染 (需要 Route 提供 params), 不参与滑屏 + const p = location.pathname + const isNewsDetail = /^\/macro\/news\/[^/]+$/.test(p) + const isArticleDetail = /^\/macro\/articles\/[^/]+$/.test(p) + const isFinanceDetail = /^\/macro\/finance\/[^/]+\/[^/]+$/.test(p) + if (isNewsDetail || isArticleDetail || isFinanceDetail) { return ( - } /> - } /> - } /> } /> + } /> + } /> ) } + const tabs = [ + ...BASE_TABS.map((t) => ({ + key: t.key, + label: t.label, + render: () => (t.key === 'news' ? : ), + })), + ...categories.map((c) => ({ + key: 'finance:' + c.id, + label: c.name, + render: () => , + })), + ] + + function onChange(key) { + if (key.startsWith('finance:')) { + navigate('/macro/finance/' + key.slice('finance:'.length)) + return + } + const next = BASE_TABS.find((t) => t.key === key) + if (next) navigate(next.path) + } + return (
diff --git a/src/pages/MonitorPage.jsx b/src/pages/MonitorPage.jsx index 04c2635..ae751c7 100644 --- a/src/pages/MonitorPage.jsx +++ b/src/pages/MonitorPage.jsx @@ -1,15 +1,18 @@ import { useEffect, useState, useCallback } from 'react' import { useNavigate } from 'react-router-dom' -import { Card, Empty, DotLoading } from 'antd-mobile' +import { Badge, Card, Empty, DotLoading } from 'antd-mobile' import { api } from '../api/client.js' import PageHeader from '../components/PageHeader.jsx' import SwipeTabs from '../components/SwipeTabs.jsx' import useRefreshOnVisible from '../hooks/useRefreshOnVisible.js' import EnvView from './monitor/EnvView.jsx' +import AlertsView from './monitor/AlertsView.jsx' -// 监控页: 两个横向分类 +// 监控页: 四个横向分类 // - 主机监控: 实时数据来自 komari (服务端实时代理, 不落库), 点击节点看 24h 时序 // - 服务监控: 暂留空占位 +// - 环境监控: 米家温湿度 +// - 报警: V0.2.2 从「宏观」迁入, 数据差异/任务异常按时间倒序 function fmtTime(s) { if (!s) return '' const d = new Date(s) @@ -40,10 +43,21 @@ function pct(used, total) { export default function MonitorPage() { const [active, setActive] = useState('servers') + // 报警角标: 由 AlertsView 回传总数 + const [alertCount, setAlertCount] = useState(0) const tabs = [ { key: 'servers', label: '主机监控', render: () => }, { key: 'services', label: '服务监控', render: () => }, { key: 'env', label: '环境监控', render: () => }, + { + key: 'alerts', + label: ( + + 报警{alertCount > 0 && } + + ), + render: () => , + }, ] return (
diff --git a/src/pages/checkin/CalendarView.jsx b/src/pages/checkin/CalendarView.jsx deleted file mode 100644 index 3196631..0000000 --- a/src/pages/checkin/CalendarView.jsx +++ /dev/null @@ -1,173 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' -import { Button, DotLoading, Empty, Segmented, Toast } from 'antd-mobile' -import { Celebrate } from '../../components/RingProgress.jsx' -import { checkinApi } from '../../api/checkin.js' -import useRefreshOnVisible from '../../hooks/useRefreshOnVisible.js' -import { - toDateStr, addDays, addMonths, monthGrid, weekdayLabels, dayLabel, fmtMonth, -} from './date.js' - -// 日历视图: 月历网格 + 今天/昨天/明天 三视图 + 日期标记 (F-CL-*) -export default function CalendarView({ date, onDateChange }) { - const today = toDateStr() - const [month, setMonth] = useState(() => date.slice(0, 7)) - const [cal, setCal] = useState(null) // 月历数据 - const [detail, setDetail] = useState(null) // 选中日详情 - const [err, setErr] = useState('') - const [bursts, setBursts] = useState({}) - - const loadCal = useCallback((silent) => { - if (!silent) setErr('') - checkinApi.calendar(month) - .then(setCal) - .catch((e) => { if (!silent) setErr(e.message) }) - }, [month]) - - const loadDetail = useCallback((silent) => { - checkinApi.day(date).then(setDetail).catch(() => {}) - }, [date]) - - useEffect(() => { loadCal(false) }, [loadCal]) - useEffect(() => { setDetail(null); loadDetail(false) }, [loadDetail]) - useRefreshOnVisible(() => { loadCal(true); loadDetail(true) }) - - // 切换日期时, 若跨月则同步月份 - useEffect(() => { - const m = date.slice(0, 7) - setMonth((cur) => (cur === m ? cur : m)) - }, [date]) - - const days = useMemo(() => monthGrid(month), [month]) - const byDate = useMemo(() => { - const map = new Map() - ;(cal?.days || []).forEach((d) => map.set(d.date, d)) - return map - }, [cal]) - - const quickValue = useMemo(() => { - if (date === today) return 0 - if (date === addDays(today, -1)) return -1 - if (date === addDays(today, 1)) return 1 - return null - }, [date, today]) - - async function checkFromCalendar(item) { - if (!item.canCheck) return - const snapshot = detail - setDetail((d) => ({ - ...d, - items: d.items.map((x) => { - if (x.id !== item.id) return x - const done = x.done + 1 - const countOnDate = (x.countOnDate || 0) + 1 - return { - ...x, done, countOnDate, - percent: Math.min(1, done / x.target), - completed: done >= x.target, - over: Math.max(0, done - x.target), - dayDone: countOnDate >= x.expectedPerDay, - } - }), - })) - try { - const res = await checkinApi.check(item.id, date) - const fresh = await checkinApi.day(date) - setDetail(fresh) - if (res.item.completed) setBursts((m) => ({ ...m, [item.id]: (m[item.id] || 0) + 1 })) - loadCal(true) - } catch (e) { - setDetail(snapshot) - Toast.show({ content: e.message || '打卡失败' }) - } - } - - const items = detail?.items || [] - const isFuture = !!detail?.isFuture - - return ( -
-
- setMonth((m) => addMonths(m, -1))}>‹ - {fmtMonth(month)} - setMonth((m) => addMonths(m, 1))}>› - onDateChange(today)}>回到今天 -
- -
- onDateChange(addDays(today, Number(v)))} - /> -
- - {err &&
{err} loadCal(false)}>重试
} - -
-
- {weekdayLabels().map((w) => {w})} -
-
- {days.map((d) => { - const info = byDate.get(d) - const inMonth = d.slice(0, 7) === month - const status = info?.status || 'empty' - return ( -
onDateChange(d)} - > - {Number(d.slice(8, 10))} - {inMonth && status !== 'empty' && ( - - )} -
- ) - })} -
-
- - {cal && ( -
本月完成率 {Math.round((cal.rate || 0) * 100)}%
- )} - -
-
- {dayLabel(date, today)} · {date} - {isFuture && 计划(不可打卡)} -
- - {detail === null &&
加载中...
} - {detail !== null && items.length === 0 && } - - {items.map((item) => ( -
- - {item.name} - - {item.periodLabel} {item.target} 次 · 当日 {item.countOnDate || 0}/{item.expectedPerDay} - - - {item.canCheck ? ( - - ) : ( - {item.dayDone ? '计划' : '待办'} - )} - - -
- ))} -
-
- ) -} diff --git a/src/pages/checkin/HabitView.jsx b/src/pages/checkin/HabitView.jsx deleted file mode 100644 index 56eeab4..0000000 --- a/src/pages/checkin/HabitView.jsx +++ /dev/null @@ -1,338 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' -import { - Button, Dialog, DotLoading, Empty, Input, Popup, Selector, Stepper, Toast, -} from 'antd-mobile' -import RingProgress, { Celebrate } from '../../components/RingProgress.jsx' -import useHabitDrag from '../../components/checkin/useHabitDrag.js' -import { checkinApi } from '../../api/checkin.js' -import useRefreshOnVisible from '../../hooks/useRefreshOnVisible.js' -import { toDateStr, dayLabel } from './date.js' - -const PERIOD_OPTIONS = [ - { label: '每天', value: 'day' }, - { label: '每周', value: 'week' }, - { label: '每月', value: 'month' }, - { label: '每年', value: 'year' }, -] - -const COLORS = ['#1677ff', '#00b578', '#ff8f1f', '#ff3141', '#a855f7', '#14b8a6', '#f43f9e'] - -const EMPTY_FORM = { id: null, name: '', period: 'day', target: 1, color: COLORS[0], note: '' } - -// 打卡视图: 今日待办列表 + 环形进度 + 打卡/撤销 + 新建/编辑 -export default function HabitView({ date, onDateChange }) { - const [data, setData] = useState(null) // { today, date, items, summary } - const [err, setErr] = useState('') - const [bursts, setBursts] = useState({}) // { habitId: 触发计数 } - const [form, setForm] = useState(EMPTY_FORM) - const [showForm, setShowForm] = useState(false) - - const load = useCallback((silent) => { - if (!silent) setErr('') - checkinApi.habits(date) - .then((d) => setData(d)) - .catch((e) => { if (!silent) setErr(e.message) }) - }, [date]) - - useEffect(() => { load(false) }, [load]) - useRefreshOnVisible(() => load(true)) - - const items = useMemo(() => { - const list = data?.items || [] - // 手动排序优先 (sortOrder), 拖动排序后覆盖 F-CK-16 的"未完成在前" - return [...list].sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0) || a.id - b.id) - }, [data]) - - // 按周期分组 (每天/每周/每月/每年), 组内顺序 = 手动排序 - const groups = useMemo(() => { - const out = [] - for (const p of PERIOD_OPTIONS) { - const list = items.filter((i) => i.period === p.value) - if (list.length) out.push({ key: p.value, label: p.label, list }) - } - const known = new Set(PERIOD_OPTIONS.map((p) => p.value)) - const rest = items.filter((i) => !known.has(i.period)) - if (rest.length) out.push({ key: 'other', label: '其他', list: rest }) - return out - }, [items]) - - const drag = useHabitDrag({ onDrop: reorderHabit }) - - // 组内拖动落位: 重排后按展平顺序整体重写 sortOrder - async function reorderHabit(dragged, targetId, after) { - const snapshot = data - const g = groups.find((x) => x.key === dragged.period) - if (!g) return - const rest = g.list.filter((i) => i.id !== dragged.id) - const ti = rest.findIndex((i) => i.id === targetId) - if (ti < 0) return - rest.splice(after ? ti + 1 : ti, 0, dragged) - const flat = groups.map((x) => (x.key === g.key ? rest : x.list)).flat() - const withOrder = flat.map((it, i) => ({ ...it, sortOrder: i })) - setData((d) => ({ ...d, items: withOrder })) - try { - await checkinApi.reorderHabits(flat.map((i) => i.id)) - } catch (e) { - setData(snapshot) - Toast.show({ content: e.message || '排序保存失败' }) - } - } - - const fireBurst = (id) => setBursts((m) => ({ ...m, [id]: (m[id] || 0) + 1 })) - - // 乐观更新: 先本地 +1, 成功用服务端结果覆盖, 失败回滚 (F-SY-06/E-02) - async function doCheck(item) { - const before = data - const patch = (it) => { - const done = it.done + 1 - return { - ...it, - done, - remaining: Math.max(0, it.target - done), - percent: Math.min(1, done / it.target), - completed: done >= it.target, - over: Math.max(0, done - it.target), - countOnDate: (it.countOnDate || 0) + 1, - dayDone: (it.countOnDate || 0) + 1 >= it.expectedPerDay, - } - } - setData((d) => ({ ...d, items: d.items.map((x) => (x.id === item.id ? patch(x) : x)) })) - try { - const res = await checkinApi.check(item.id, date) - setData((d) => ({ ...d, items: d.items.map((x) => (x.id === item.id ? res.item : x)) })) - if (res.item.completed) fireBurst(item.id) - } catch (e) { - setData(before) - Toast.show({ content: e.message || '打卡失败' }) - } - } - - async function doUncheck(item) { - const before = data - setData((d) => ({ - ...d, - items: d.items.map((x) => { - if (x.id !== item.id) return x - const done = Math.max(0, x.done - 1) - return { - ...x, - done, - remaining: Math.max(0, x.target - done), - percent: Math.min(1, done / x.target), - completed: done >= x.target, - over: Math.max(0, done - x.target), - countOnDate: Math.max(0, (x.countOnDate || 0) - 1), - } - }), - })) - try { - const res = await checkinApi.uncheck(item.id, date) - setData((d) => ({ ...d, items: d.items.map((x) => (x.id === item.id ? res.item : x)) })) - } catch (e) { - setData(before) - Toast.show({ content: e.message || '撤销失败' }) - } - } - - async function submitForm() { - const name = String(form.name || '').trim() - if (!name) return Toast.show({ content: '请填写名称' }) - if (name.length > 20) return Toast.show({ content: '名称不能超过 20 个字' }) - const payload = { name, period: form.period, target: Number(form.target) || 1, color: form.color, note: form.note } - try { - if (form.id) await checkinApi.updateHabit(form.id, payload) - else await checkinApi.createHabit(payload) - setShowForm(false) - setForm(EMPTY_FORM) - load(true) - } catch (e) { - Toast.show({ content: e.message || '保存失败' }) - } - } - - async function removeHabit(item) { - const ok = await Dialog.confirm({ - content: `归档「${item.name}」?归档后不再出现在待办,历史记录保留。`, - confirmText: '归档', - cancelText: '取消', - }) - if (!ok) return - try { - await checkinApi.deleteHabit(item.id) - Toast.show({ content: '已归档' }) - load(true) - } catch (e) { - Toast.show({ content: e.message || '操作失败' }) - } - } - - const summary = data?.summary || { total: 0, done: 0 } - const isToday = data ? data.date === data.today : true - - return ( -
-
-
-
{dayLabel(date, data?.today || toDateStr())}
-
- 已完成 {summary.done} / {summary.total} - {!isToday && 非今天} -
-
-
- -
-
- - {err && ( -
- {err} - load(false)}>重试 -
- )} - - {data === null && !err &&
加载中...
} - - {data !== null && !err && items.length === 0 && ( -
- - -
- )} - -
- {groups.map((g) => ( -
-
- {g.label} - {g.list.length} -
- {g.list.map((item) => ( -
- { if (!drag.justEnded()) doCheck(item) }} - /> -
-
{item.name}
-
- {item.periodLabel} {item.target} 次 - {item.streak > 0 && 连续 {item.streak}} -
- {item.note &&
{item.note}
} -
- {item.periodStart === item.periodEnd - ? '今日' - : `${item.periodStart.slice(5)} ~ ${item.periodEnd.slice(5)}`} - {item.over > 0 && 超额 {item.over}} -
-
-
- -
-
- {item.done > 0 && ( - { if (!drag.justEnded()) doUncheck(item) }}>撤销 - )} - { - if (drag.justEnded()) return - setForm({ id: item.id, name: item.name, period: item.period, target: item.target, color: item.color, note: item.note }) - setShowForm(true) - }} - >编辑 - { if (!drag.justEnded()) removeHabit(item) }}>归档 -
- -
- ))} -
- ))} -
- - {/* 新建 / 编辑 */} - setShowForm(false)} - bodyStyle={{ borderTopLeftRadius: 12, borderTopRightRadius: 12, paddingBottom: 16 }} - > -
-
{form.id ? '编辑打卡项' : '新建打卡项'}
-
- - setForm((f) => ({ ...f, name: v }))} - /> -
-
- - setForm((f) => ({ ...f, period: v[0] }))} - /> -
-
- - setForm((f) => ({ ...f, target: v }))} /> -
-
- -
- {COLORS.map((c) => ( - setForm((f) => ({ ...f, color: c }))} - /> - ))} -
-
-
- - setForm((f) => ({ ...f, note: v }))} - /> -
-
- - -
-
-
-
- ) -} diff --git a/src/pages/checkin/MergedView.jsx b/src/pages/checkin/MergedView.jsx index 60a09ee..d15fa2e 100644 --- a/src/pages/checkin/MergedView.jsx +++ b/src/pages/checkin/MergedView.jsx @@ -9,6 +9,7 @@ import { Button, Dialog, DotLoading, Empty, Input, Popup, Selector, Stepper, Toast, } from 'antd-mobile' import RingProgress, { Celebrate } from '../../components/RingProgress.jsx' +import DayRing from '../../components/checkin/DayRing.jsx' import useHabitDrag from '../../components/checkin/useHabitDrag.js' import { checkinApi } from '../../api/checkin.js' import useRefreshOnVisible from '../../hooks/useRefreshOnVisible.js' @@ -266,6 +267,8 @@ export default function MergedView({ date, onDateChange }) { const info = byDate.get(d) const inMonth = d.slice(0, 7) === month const status = info?.status || 'empty' + // 环的弧长 = 当天完成比例 (F-CL-06); 未来日期不画环 (计划 != 未完成) + const showRing = inMonth && !!info && !info.isFuture && (info.expected || 0) > 0 return (
onDateChange(d)} > - {Number(d.slice(8, 10))} - {inMonth && status !== 'empty' && } + + {showRing && ( + + )} + {Number(d.slice(8, 10))} +
) })} @@ -284,8 +291,14 @@ export default function MergedView({ date, onDateChange }) { {cal && (
- 本月完成率 - {Math.round((cal.rate || 0) * 100)}% + 本月完成率 {Math.round((cal.rate || 0) * 100)}% + {/* 图例: 弧长=完成比例, 颜色=4 档 (小半/大半/完成/超额) */} + + 小半 + 大半 + 完成 + 超额 +
)}
diff --git a/src/pages/macro/DataView.jsx b/src/pages/macro/DataView.jsx deleted file mode 100644 index e1fd485..0000000 --- a/src/pages/macro/DataView.jsx +++ /dev/null @@ -1,133 +0,0 @@ -import { useEffect, useState, useCallback } from 'react' -import { useParams, useNavigate } from 'react-router-dom' -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts' -import { Card, List, NavBar } from 'antd-mobile' -import { api } from '../../api/client.js' -import TrendBadge, { calcTrend, lastPoint } from '../../components/TrendBadge.jsx' -import useRefreshOnVisible from '../../hooks/useRefreshOnVisible.js' - -// 经济数据 -// 列表态 (默认): 列出所有指标, 每行显示最新值 + 趋势徽标, 点击进入详情 -// 详情态: 顶部 NavBar (返回) + 折线图 + 数据表 -// 频率中文标签 (对应 Indicator.frequency) -const FREQ_LABEL = { year: '年', quarter: '季', month: '月', day: '日' } - -// 统一数据行的时间字段: 兼容 {month}/{year}/{period} 三种存储形状 -function periodOf(r) { - return (r && (r.period || r.month || r.year)) || '' -} - -export default function DataView() { - const [indicators, setIndicators] = useState([]) - const [err, setErr] = useState('') - const [loading, setLoading] = useState(true) - // 路由驱动: /macro/data 列表, /macro/data/:name 详情, 浏览器/手机返回键天然可用 - const { name } = useParams() - const navigate = useNavigate() - - const load = useCallback(() => { - setLoading(true) - api('/indicators') - .then((data) => { setErr(''); setIndicators(data.indicators || []) }) - .catch((e) => setErr(e.message)) - .finally(() => setLoading(false)) - }, []) - - useEffect(() => { load() }, [load]) - // 后台回前台时重新拉取 - useRefreshOnVisible(load) - - if (loading) return
加载中...
- if (err) return
{err}
- if (!indicators.length) return
暂无指标数据, 请先执行 npm run db:seed
- - // 详情态 (路径 /macro/data/:name) - if (name) { - const cur = indicators.find((i) => i.name === name) - if (cur) return navigate('/macro/data')} /> - } - - // 列表态 - const labelOf = (i) => i.title || i.name - - return ( -
-
经济数据
- - {indicators.map((i) => { - const lp = lastPoint({ rows: i.points || [] }) - const { trend, change } = calcTrend(i.points || []) - const unit = i.unit || '' - return ( - {lp.value}{unit} · {periodOf(lp)} : '暂无数据'} - extra={} - - arrowIcon - clickable - onClick={() => navigate('/macro/data/' + i.name)} - /> - ) - })} - -
- ) -} - -// 详情: 折线图 + 数据表 -function DataDetail({ indicator, onBack }) { - const i = indicator - const rows = (i.points || []) - // 图表 X 轴统一用 period 字段 - const chartRows = rows.map((r) => ({ ...r, period: periodOf(r) })) - // 表格按时间反序: 最新在前 - const tableRows = [...rows].reverse() - const [showAll, setShowAll] = useState(false) - const visibleRows = showAll ? tableRows : tableRows.slice(0, 10) - const lp = lastPoint({ rows }) - const { trend } = calcTrend(rows) - const title = i.title || i.name - return ( -
- {title} - -
- 单位 {i.unit || '-'} · 频率 {FREQ_LABEL[i.frequency] || i.frequency || '-'} · 来源 {i.source || 'seed'} · - {lp && <> 最新 {lp.value}{i.unit || ''} ({periodOf(lp)}) } - -
-
- - - - - - - - - - -
-
- - - - - - {visibleRows.map((r) => ( - - ))} - -
月份数值
{periodOf(r)}{r.value}
- {tableRows.length > 10 && !showAll && ( -
setShowAll(true)}> - 展开全部(共 {tableRows.length} 行) -
- )} -
-
-
- ) -} diff --git a/src/pages/macro/FinanceCategoryView.jsx b/src/pages/macro/FinanceCategoryView.jsx new file mode 100644 index 0000000..4bba23a --- /dev/null +++ b/src/pages/macro/FinanceCategoryView.jsx @@ -0,0 +1,39 @@ +import { useNavigate } from 'react-router-dom' +import { List, DotLoading } from 'antd-mobile' +import { useIfinanceInfo } from '../../lib/ifinance.js' + +// 外接模块分类列表 (ifinance categories[].items[]) +// - 数据来源: info.json (运行时动态拉取), 当前分类由 MacroPage 传入 +// - 点击条目 → /macro/finance/:catId/:itemId 打开该条目独立 HTML +const KIND_LABEL = { indicator: '指标', strategy: '策略', dashboard: '看板' } + +export default function FinanceCategoryView({ category }) { + const navigate = useNavigate() + const { info, loading, error } = useIfinanceInfo() + + // MacroPage 拿到的 category 可能来自缓存; 若为空则用 hook 里的最新数据兜底 + const cat = category || (info?.categories || []).find((c) => c.id === category?.id) + const items = cat?.items || [] + + if (!cat && loading) return
加载中...
+ if (!cat && error) return
{error}
+ if (!items.length) return
暂无内容
+ + return ( +
+ + {items.map((it) => ( + {KIND_LABEL[it.kind] || it.kind || ''}} + arrowIcon + clickable + onClick={() => navigate('/macro/finance/' + cat.id + '/' + it.id)} + /> + ))} + +
+ ) +} diff --git a/src/pages/macro/FinanceItemView.jsx b/src/pages/macro/FinanceItemView.jsx new file mode 100644 index 0000000..8bf5115 --- /dev/null +++ b/src/pages/macro/FinanceItemView.jsx @@ -0,0 +1,33 @@ +import { useNavigate, useParams } from 'react-router-dom' +import { NavBar, DotLoading } from 'antd-mobile' +import { useIfinanceInfo, financePageUrl } from '../../lib/ifinance.js' + +// 外接条目详情: 用 iframe 加载 ifinance 生成的独立 HTML。 +// - 页面本身无导航/头部 (符合 ifinance 规范), 由宿主提供 NavBar 返回 +// - 直接指向远端地址 (纯前端直连, 不做端上缓存) +export default function FinanceItemView() { + const { catId, itemId } = useParams() + const navigate = useNavigate() + const { info, loading, error } = useIfinanceInfo() + + const cat = (info?.categories || []).find((c) => c.id === catId) + const item = (cat?.items || []).find((i) => i.id === itemId) + const title = item?.title || '详情' + + return ( +
+ navigate('/macro/finance/' + catId)}>{title} + {loading && !item &&
加载中...
} + {!loading && error &&
{error}
} + {!loading && !error && !item &&
内容不存在
} + {item?.html && ( +