# Click Gap 数据看板 运行目标是 **mac2**。另一个独立的外部 AppsFlyer 任务每天把汇总结果写入 SQLite;本项目不调用该任务、不访问 AppsFlyer、Superset 或其它提取 API,只从固定数据库只读查询并发布静态 JSON。 固定数据库:`/Users/ads-center/Documents/database/af_click_gap.db`。数据库必须预先存在 `click_gap` 表,脚本不建表、不迁移、不写入数据库。表要求包含: ```text pid TEXT NOT NULL, dt TEXT NOT NULL, attr_platform TEXT NOT NULL, pkg_name TEXT NOT NULL, shareit_clicks REAL NOT NULL, af_clicks REAL NOT NULL, installs_ua REAL NOT NULL, clicks_diff REAL NOT NULL, clicks_diff_rate REAL, updated_at TEXT NOT NULL ``` 主键必须为 `(pid, dt, pkg_name)`。脚本只读取 `pid='shareit'`,并校验字段、日期、数值、重复业务键和有限数值。 ## 输出协议 - `data/YYYYMMDD.json`:直接 JSON 数组;最近 7 天每天生成一个文件,即使当天无数据也生成 `[]`。 - `data/index.json`:包含 `schema_version`、`generated_at` 和最近 7 天的 `data/YYYYMMDD.json` 清单。 - 每行字段为 `dt`(`YYYYMMDD`)、`attr_platform`、`pkg_name`、`shareit_clicks`、`af_clicks`、`installs_ua`、`clicks_diff`、`clicks_diff_rate`;最后一项可以为 `null`。 默认日期边界为运行日往前 6 天至运行日,首尾包含,共 7 天。手工补数使用 `--start-date` 和 `--end-date`,两者必须同时提供且最多 7 天。 ## 手工运行 mac2 推荐始终通过包装脚本运行: ```sh cd /Users/shareit./Library/CloudStorage/OneDrive-个人/ubuntu/Gitea/af-click-gap scripts/run_pipeline.sh scripts/run_pipeline.sh --start-date 2026-10-10 --end-date 2026-10-16 ``` 包装脚本默认定位项目根目录,并读取 sibling 项目 `../direct-report-dashboard/.env`(mac2 默认路径为 `~/ubuntu/Gitea/af-click-gap` 与 `~/ubuntu/Gitea/direct-report-dashboard`)。也可以用 `PROJECT_ROOT` 和 `ENV_FILE` 覆盖路径。它只解析 `AGENTSTAGE_USER`、`AGENTSTAGE_TOKEN`,不执行整个 `.env`;凭据只进入子进程环境,不写入文件、remote URL、命令行参数或日志。脚本使用临时 `GIT_ASKPASS` 响应 Git 的用户名和密码提示,临时文件权限为仅当前用户可读写,并在退出时清理。 不要直接运行 `python3 scripts/af_click_gap_pipeline.py --push`:该命令可能使用 mac2 Keychain 中没有仓库写权限的旧凭据。包装脚本会执行相同的 SQLite 最近 7 天提取和 `--push`,额外参数会继续传给流水线。需要只检查数据时可直接使用 `python3 scripts/af_click_gap_pipeline.py --dry-run`。 `--dry-run` 只读取数据库并在仓库外临时目录生成、校验 JSON,不写 `data` 或 Git。正式运行先原子替换 `data/` 输出目录中的数据文件,再执行 Git;数据库查询或校验失败不会发布文件。Git 安全检查只允许本次生成的 `data/*.json`(含 `data/index.json`),工作区存在其它未提交改动时拒绝继续。push 失败会保留本地 commit,不会 force push。 ## 看板 `index.html` 是无构建依赖的静态页面,读取并校验 `data/index.json` 及其清单,支持日期范围、包名筛选和表头排序。直接打开文件或执行 `python3 -m http.server` 后访问即可。 ## mac2 launchd launchd 会调用 `scripts/run_pipeline.sh`,因此也使用 sibling 项目 `.env` 中的凭据;模板通过 `PYTHON_BIN` 传入 mac2 的 Python 路径,并保留工作目录和 stdout/stderr 日志路径: ```sh cd /Users/shareit./Library/CloudStorage/OneDrive-个人/ubuntu/Gitea/af-click-gap mkdir -p "$HOME/Library/Logs/af-click-gap" "$HOME/Library/LaunchAgents" PY=$(command -v python3) sed -e "s#__MAC2_PYTHON3__#$PY#" -e "s#__PROJECT_DIR__#$PWD#g" -e "s#__LOG_DIR__#$HOME/Library/Logs/af-click-gap#g" launchd/com.roasbloom.af-click-gap.plist.template > /tmp/com.roasbloom.af-click-gap.plist plutil -lint /tmp/com.roasbloom.af-click-gap.plist cp /tmp/com.roasbloom.af-click-gap.plist "$HOME/Library/LaunchAgents/" launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.roasbloom.af-click-gap.plist" ``` 卸载:`launchctl bootout "gui/$(id -u)/com.roasbloom.af-click-gap"` 后删除 LaunchAgents 中的 plist。日志写在 mac2 用户日志目录;数据库、`.env`、密钥、日志和缓存均不会提交。 ## Git 创建和推送 远程仓库约定为 Gitea `https://git.ops.roasbloom.site/liuji/af-click-gap.git`。仓库创建和首次配置可使用已有流程;日常 mac2 发布必须使用 `scripts/run_pipeline.sh`,它从 `/Users/shareit./Library/CloudStorage/OneDrive-个人/ubuntu/Gitea/direct-report-dashboard/.env`(或 `ENV_FILE` 指定路径)读取 `AGENTSTAGE_USER` 和 `AGENTSTAGE_TOKEN`。凭据不进入 Git、remote URL、命令行、临时 askpass 文件或日志,且 `.env` 绝不提交。 ## 验证和安全 ```sh python3 -m py_compile scripts/af_click_gap_pipeline.py python3 scripts/af_click_gap_pipeline.py --help plutil -lint launchd/com.roasbloom.af-click-gap.plist.template python3 -m json.tool data/index.json ``` 禁止提交数据库文件、`.env*`、密钥、日志、浏览器状态、缓存或临时文件;`.gitignore` 已覆盖这些类型。