[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-2774":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":8,"htmlUrl":8,"language":9,"languages":8,"totalLinesOfCode":8,"stars":10,"forks":11,"watchers":12,"openIssues":13,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":15,"stars7d":16,"stars30d":17,"stars90d":14,"forks30d":14,"starsTrendScore":18,"compositeScore":19,"rankGlobal":8,"rankLanguage":8,"license":8,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":8,"pushedAt":8,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":14,"starSnapshotCount":14,"syncStatus":27,"lastSyncTime":28,"discoverSource":29},2774,"ews","kylemcdonald\u002Fews","kylemcdonald",null,"JavaScript",306,35,7,3,0,17,39,165,51,88.17,false,"main",true,[],"2026-06-12 04:00:15","# Early Warning System\n\nWeb app for monitoring business-jet cohorts against concurrent-activity and calendar-learned baselines.\n\nThis project now supports both local development and a low-cost public deployment model:\n\n- Node\u002FExpress API with SQLite persistence\n- React dashboard with an alarm dial, world map, and historical charts\n- Python backfill script that reuses the ADS-B Exchange heatmap approach from the referenced parsing workflow\n- FAA and global cohort importers that build tracked sets from public aircraft metadata\n- Latest-state refresh via ADS-B Exchange half-hour heatmaps\n- Static snapshot publishing for public hosting\n\n## What is included\n\n- Demo mode: if no cohort has been imported yet, the dashboard serves synthetic review data\n- Main\u002Fglobal historical store: `data\u002Fews-main.sqlite`\n- Military historical store: `data\u002Fews-military.sqlite`\n- Non-ICAO historical store: `data\u002Fews-untracked.sqlite`\n- FAA importer: `scripts\u002Fimport_faa_cohort.py`\n- Global importer: `scripts\u002Fimport_global_cohort.py`\n- Backfill script: `scripts\u002Fbackfill_history.py`\n- Snapshot exporter: `scripts\u002Fexport_dashboard_snapshot.js`\n\n## Quick start\n\n1. Install dependencies:\n\n```bash\nnpm install\n```\n\n2. Run the app in demo mode:\n\n```bash\nnpm run dev\n```\n\nThe API runs on `http:\u002F\u002Flocalhost:3030` and the Vite client runs on `http:\u002F\u002Flocalhost:5173`.\n\nTo export a static snapshot for a public frontend:\n\n```bash\nnpm run export:snapshot\n```\n\n## Real cohort setup\n\n1. Import the global business-jet cohort:\n\n```bash\nnpm run import:faa\nnpm run import:global\n```\n\n2. Run a 365-day backfill:\n\n```bash\nnpm run backfill\n```\n\nOptional examples:\n\n```bash\npython3 scripts\u002Fimport_faa_cohort.py --refresh\npython3 scripts\u002Fimport_global_cohort.py --dry-run\npython3 scripts\u002Fimport_faa_cohort.py --db data\u002Fews-main.sqlite\npython3 scripts\u002Fimport_global_cohort.py --db data\u002Fews-main.sqlite --refresh\npython3 scripts\u002Fimport_faa_cohort.py --min-seats 4 --max-seats 16\npython3 scripts\u002Fbackfill_history.py --start-date 2025-04-07 --end-date 2026-04-07\npython3 scripts\u002Fbackfill_history.py --db data\u002Fews-main.sqlite --start-date 2024-04-08 --end-date 2026-05-04 --keep-cache\npython3 scripts\u002Fbackfill_history.py --relative-days 1\npython3 scripts\u002Fbackfill_history.py --skip-download\npython3 scripts\u002Ftrack_non_icao_hex.py --start-date 2026-04-20 --end-date 2026-05-01 --skip-download\n```\n\nThe default backfill reads tracked aircraft directly from SQLite, so once the global cohort is imported you do not need a separate watchlist file.\n\nWhen the import or backfill starts, any previously seeded demo data is removed so it cannot pollute the real baseline.\n\n## Useful commands\n\n```bash\nnpm run dev\nnpm run build\nnpm run pages:dev\nnpm run d1:migrate:local\nnpm run d1:migrate:remote\nnpm run lint\nnpm run import:faa\nnpm run import:global\nnpm run seed:demo\nnpm run update:main\nnpm run update:military\nnpm run update:untracked\nnpm run export:snapshot\nnpm run export:main\nnpm run export:military\nnpm run export:untracked\nnpm run rss:update\nnpm run telegram:alert\n```\n\n## Notes\n\n- Historical ingestion uses ADS-B Exchange heatmap binaries from `globe_history`.\n- The current “live” view also comes from ADS-B Exchange heatmaps, updated every 30 minutes and cached between refreshes.\n- The FAA importer uses a pragmatic business-jet heuristic so the tracked set excludes helicopters, props, large airliners, and government aircraft.\n- The global importer merges ADS-B Exchange and tar1090\u002FMictronics metadata into `aircraft_metadata`, classifies rows into broad categories such as `business_jet`, `large_airliner`, `regional_airliner`, `military`, and `non_jet_aircraft`, then adds only `business_jet` rows to the main tracked cohort.\n- The concurrent-count model uses an all-history weekly baseline and learned half-hour profiles around U.S. federal holidays, with a standard-deviation band exported for the dashboard.\n- Non-ICAO `~hex` activity can be scanned into aggregate SQLite tables with `scripts\u002Ftrack_non_icao_hex.py`; rows are split by ADS-B\u002FADS-R\u002FTIS-B message type so synthetic rebroadcast traffic can be analyzed separately from direct ADS-B reports.\n- The production build currently emits a large JS bundle warning because the map and chart stack are bundled together. The app still builds and runs locally.\n- `data\u002F` is ignored so the SQLite file can be moved independently without checking it into source control.\n\n## Public deployment\n\nThe public deployment can run with:\n\n- Cloudflare Pages for the static frontend\n- Cloudflare R2 for the public `dashboard.json`, `military-dashboard.json`, and `untracked-dashboard.json` snapshots, plus canonical main, military, and untracked SQLite state files\n- GitHub Actions for the scheduled refresh jobs\n\nThe repository includes scheduled workflows in `.github\u002Fworkflows\u002Frefresh-live-data.yml` and `.github\u002Fworkflows\u002Frefresh-daily-history.yml` for that setup. The main site uses the global business-jet state DB, while military and non-ICAO cohorts are published as JSON data sources for the root dashboard toggles. All three are refreshed from the newest heatmap on the half-hour cadence and uploaded to R2.\n\nProduction frontend builds require explicit dashboard snapshot URLs. The build fails if `VITE_DASHBOARD_URL`, `VITE_MILITARY_DASHBOARD_URL`, or `VITE_UNTRACKED_DASHBOARD_URL` is missing, and `npm run build` also verifies that the emitted bundle contains those URLs rather than the local `\u002Fdashboard.json` fallback.\n\nFor local Pages deploys, prefer the guarded deploy script:\n\n```bash\nnpm run verify:deploy-env\nnpm run deploy:pages\n```\n\n`deploy:pages` loads `.env`, builds, verifies the bundle, preserves the currently deployed RSS file, deploys to Cloudflare Pages, then runs a Playwright smoke test against the live site.\n\nFor a clean local deploy, make sure `.env` includes the Cloudflare API token plus the public dashboard snapshot URLs before running the deploy script:\n\n```bash\nCLOUDFLARE_API_TOKEN=...\nVITE_DASHBOARD_URL=https:\u002F\u002Fpub-49bb6a6f314c47be9b481c25e5f6ca9e.r2.dev\u002Fdashboard.json\nVITE_MILITARY_DASHBOARD_URL=https:\u002F\u002Fpub-49bb6a6f314c47be9b481c25e5f6ca9e.r2.dev\u002Fmilitary-dashboard.json\nVITE_UNTRACKED_DASHBOARD_URL=https:\u002F\u002Fpub-49bb6a6f314c47be9b481c25e5f6ca9e.r2.dev\u002Funtracked-dashboard.json\n```\n\nIf those `VITE_*` values are missing, `deploy:pages` refuses to deploy before build. If Wrangler warns that the working directory has uncommitted changes, that warning is expected when deploying an urgent local fix from a dirty checkout; the script still deploys the current built workspace and runs the live smoke test. Review `git status --short` first so unrelated local edits are not accidentally published.\n\nFor a Codex-assisted visual check, run:\n\n```bash\nnpm run smoke:live:prompt\n```\n\n### Cloudflare credentials\n\nDo not use `wrangler login` output or a copied `WRANGLER_OAUTH_CONFIG` secret in GitHub Actions. That is interactive user login state and can stop refreshing. The workflows use `CLOUDFLARE_API_TOKEN` instead.\n\nFor a durable setup, create an account-owned Cloudflare API token in `Manage Account > Account API Tokens`, with no expiration date and no IP address restriction. Add it to GitHub as the repository secret `CLOUDFLARE_API_TOKEN`, alongside `CLOUDFLARE_ACCOUNT_ID`.\n\nRequired account-owned token permissions for this repo:\n\n- `Cloudflare Pages: Edit`\n- `Workers R2 Storage: Edit`\n- `Account Settings: Read`\n\nIf you use a user-owned API token instead of an account-owned token, also include `User Details: Read` and `User Memberships: Read`. Account-owned tokens are preferred here because they act as CI service credentials rather than as copied user session state.\n\nAfter replacing the secret, rerun the `Deploy Pages` workflow. The old `WRANGLER_OAUTH_CONFIG` repository secret is no longer used by these workflows.\n\n### Paid SMS and email notifications\n\nThe public Pages deployment includes Cloudflare Pages Functions for paid notification signup, Stripe webhooks, and the Cloudflare Access-protected `\u002Fadmin` pages. Subscriber contact details are stored in Cloudflare D1 using encrypted email\u002Fphone fields plus keyed hashes for lookup and dedupe.\n\nCreate the D1 database:\n\n```bash\nnpx wrangler d1 create ews-notifications\n```\n\nCopy `wrangler.example.toml` to the ignored `wrangler.toml`, replace the `database_id`, then apply the schema:\n\n```bash\nnpm run d1:migrate:local\nnpm run d1:migrate:remote\n```\n\nIn the Cloudflare Pages project, add a D1 binding named `EWS_NOTIFY_DB` pointing at `ews-notifications`. Add these Pages secrets\u002Fenvironment variables:\n\n```text\nAPP_BASE_URL=https:\u002F\u002Fews.kylemcdonald.net\nEWS_PUBLIC_URL=https:\u002F\u002Fews.kylemcdonald.net\u002F\nEWS_NOTIFICATION_URL=https:\u002F\u002Faews.cc\u002F\nSTRIPE_SECRET_KEY=...\nSTRIPE_WEBHOOK_SECRET=...\nSTRIPE_PRODUCT_ID=prod_USlMnoY4GL7OAn\nSTRIPE_PRICE_ID=...\nSENDGRID_API_KEY=...\nSENDGRID_FROM_EMAIL=alerts@your-domain.example\nSENDGRID_FROM_NAME=Apocalypse EWS\nTELNYX_API_KEY=...\nTELNYX_NUMBER=+1...\nTELNYX_PUBLIC_KEY=...\nTELNYX_WEBHOOK_URL=\nTELNYX_WEBHOOK_FAILOVER_URL=\nINTERNAL_ALERT_TOKEN=...\nNOTIFICATION_HASH_SECRET=...\nNOTIFICATION_ENCRYPTION_KEY=...\n```\n\n`STRIPE_PRICE_ID` is optional. If it is blank, the signup function resolves the active `$5\u002Fyear` price for `STRIPE_PRODUCT_ID`. `EWS_NOTIFICATION_URL` controls the short dashboard and manage\u002Fcancel URLs used in email notifications and account-management flows; emergency SMS alerts intentionally omit URLs. Keep `APP_BASE_URL` and `EWS_PUBLIC_URL` on the canonical hostname. `TELNYX_NUMBER` is the E.164 sender number. `TELNYX_PUBLIC_KEY` is required to process webhooks and is available in the Telnyx Mission Control Portal under Keys & Credentials. Without it, webhooks are acknowledged but ignored. `TELNYX_WEBHOOK_URL` is optional; if it is blank, outbound SMS sends use `${APP_BASE_URL}\u002Fapi\u002Ftelnyx\u002Fwebhook` for delivery callbacks. Generate `NOTIFICATION_ENCRYPTION_KEY` with:\n\n```bash\nopenssl rand -base64 32\n```\n\nFor local testing, copy `.dev.vars.example` to `.dev.vars` and fill the same values. This workspace already has ignored local scaffolding in `.dev.vars` and `wrangler.toml`; replace the blank SendGrid, Telnyx, and Stripe webhook values before testing end to end.\n\nRun the local Pages app with Functions and local D1:\n\n```bash\nnpm run pages:dev\n```\n\nStripe Checkout redirects to `\u002Fsignup?success=1`, but the subscription is only activated by the webhook. For local webhook testing:\n\n```bash\nstripe listen --forward-to http:\u002F\u002Flocalhost:8788\u002Fapi\u002Fstripe\u002Fwebhook\n```\n\nCopy the printed `whsec_...` value into `STRIPE_WEBHOOK_SECRET` in `.dev.vars`, then restart `npm run pages:dev`.\n\nFor production, add a Stripe webhook endpoint at:\n\n```text\nhttps:\u002F\u002Fews.kylemcdonald.net\u002Fapi\u002Fstripe\u002Fwebhook\n```\n\nSubscribe it to `checkout.session.completed`, `checkout.session.expired`, `customer.subscription.created`, `customer.subscription.updated`, and `customer.subscription.deleted`.\n\nThe scheduled refresh workflows call `\u002Fapi\u002Finternal\u002Flevel5-alert` after dashboard export. Add a GitHub repository secret named `EWS_INTERNAL_ALERT_TOKEN` with the same value as the Cloudflare Pages `INTERNAL_ALERT_TOKEN` secret. SMS\u002Femail alerts send only for emergency level 5 and are globally cooled down for 24 hours.\n\nFor Telnyx SMS, configure the messaging profile's webhook URL to:\n\n```text\nhttps:\u002F\u002Fews.kylemcdonald.net\u002Fapi\u002Ftelnyx\u002Fwebhook\n```\n\nIf you want to use the short domain as the failover URL, set:\n\n```text\nhttps:\u002F\u002Faews.cc\u002Fapi\u002Ftelnyx\u002Fwebhook\n```\n\nThe app records Telnyx `message.sent` and `message.finalized` callbacks in D1. Inbound `message.received` callbacks process STOP\u002FSTART\u002FHELP replies and update SMS preferences.\n\n### Telegram emergency alerts\n\nSet `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_USERNAME`, and `TELEGRAM_CHANNEL` in `.env` for the Node server runtime. For the scheduled public refresh workflows, set the same values as GitHub repository secrets.\n\nAfter each successful heatmap refresh, the backend builds the current dashboard signal. If the emergency level is 5, it posts:\n\n```text\nemergency level 5!\n521 airborne (+121 above expected)\nhttps:\u002F\u002Fews.kylemcdonald.net\u002F\n```\n\nThe last alerted heatmap slot is stored in SQLite so the same slot is not reposted after a restart or retry. Run `npm run telegram:alert -- --dry-run` to verify the current alert decision without posting.\n\n### RSS emergency feed\n\nThe Node server exposes the same level-5 alert stream at `\u002Frss.xml` and `\u002Ffeed.xml`. The public static deployment also includes `\u002Frss.xml`. Scheduled refresh workflows update that feed only when a new heatmap slot reaches emergency level 5, then publish the new RSS file to R2 and redeploy the Pages endpoint.\n\nRun `npm run rss:update -- --dry-run --output tmp\u002Frss.xml` to verify the current RSS decision without recording a new feed item.\n","该项目是一个用于监控商务机队活动的Web应用程序，通过与并发活动和日历学习基线对比来提供预警。其核心功能包括使用Node\u002FExpress API结合SQLite数据库进行数据持久化、React前端展示警报表盘、世界地图及历史图表等信息；采用ADS-B Exchange热图方法更新最新状态，并支持FAA及全球机队元数据导入以构建跟踪集。此外，还提供了Python脚本用于历史数据回填以及静态快照导出以便于低成本公开部署。该系统适用于需要对特定机队或飞行活动进行实时监控与异常检测的场景，如航空安全监测、私人飞行管理等领域。",2,"2026-06-11 02:51:10","CREATED_QUERY"]