Snow stake reporting service using Vertex AI (Gemini) + SQL Server, designed to run in a Linux Docker container. Reads webcam images of physical snow stakes, asks Gemini to read the depth from the markings, and persists observations with change-or-heartbeat logic.
Authored by Harrison Kairalla.
flowchart LR
A[Webcam HTTPS<br/>JPEG] --> B[snow_reporter.py]
B -->|crop + pair prev/curr| C[Vertex AI<br/>Gemini Vision]
C -->|depth, confidence,<br/>visibility, notes| B
B -->|change or heartbeat| D[(SQL Server<br/>snowcam_observations)]
B -->|state| E[data/cameras/<code>/]
F[(dim_camera<br/>dim_location<br/>dim_resort)] --> B
- Loads all active cameras from
dbo.dim_camera. - Fetches all camera images.
- Calls Vertex AI for each camera image pair (previous + current).
- Writes observations to SQL Server with change-or-heartbeat logic.
- Stores per-camera local state under
data/cameras/<camera_code>/.
| column | value |
|---|---|
observation_utc |
2026-02-14 11:00:03 |
camera_id |
7 |
current_depth_in |
18.5 |
delta_in |
0.7 |
interval_snowfall_in |
0.7 |
today_snowfall_total_in |
2.1 |
stake_cleared |
0 |
confidence |
0.92 |
visibility |
good |
run_status |
ok |
notes |
stake markings clear; snow surface continuous |
snow_reporter.py: main appDockerfile: container imagedocker-compose.yml: runtime configsql/normalize_snowcam_example.sql: normalized schema + catalog metadatasql/add_camera.sql: helper script to add/update resort/location/camera metadatasql/add_camera_crop_columns.sql: adds per-camera crop columns + metadatasql/set_camera_crops.sql: applies crop settings for existing camerassql/add_camera_model_notes.sql: adds per-camera prompt notes column + metadatasql/set_camera_model_notes.sql: applies prompt notes for existing cameras.env.example: environment template
- Copy
.env.exampleto.envand fill values. - Apply SQL schema:
sqlcmd -S <server> -d <database> -U <user> -P <password> -i sql\normalize_snowcam_example.sql- Build image:
docker compose build- Run one cycle (all active cameras):
docker compose run --rm snowcam-reporter python snow_reporter.pyIf using a service account key file:
- Place key at
./secrets/adc.json - Uncomment key mount in
docker-compose.yml - Set in
.env:
GOOGLE_APPLICATION_CREDENTIALS=/app/secrets/adc.jsonUse SQL metadata upsert and keep cameras active in dbo.dim_camera.
sqlcmd -S <server> -d <database> -U <user> -P <password> ^
-v RESORT_CODE="EXM" RESORT_NAME="Example Resort" TIMEZONE_NAME="America/Denver" ^
LOCATION_CODE="SNOWSTAKE1" LOCATION_NAME="Main Mountain Snow Stake" ELEVATION_FT="0" ^
CAMERA_CODE="EXM-CAM1" CAMERA_NAME="example_snowstake1" ^
IMAGE_URL="https://example.com/cam-images/snowstake1.jpg" ^
POLL_INTERVAL_MINUTES="30" ^
CROP_X="" CROP_Y="" CROP_W="" CROP_H="" ^
MODEL_NOTES="" ^
-i sql\add_camera.sqldbo.dim_camera supports per-camera image and prompt tuning:
crop_x,crop_y,crop_w,crop_h: optional crop rectangle in source-image pixels- All four must be set together, or all null.
model_notes: optional camera-specific instructions appended to the global model prompt.
These settings are useful for handling camera-specific quirks (fixed objects, logos, framing differences, etc.).
Additional guardrails are configurable in .env:
SNOW_MIN_CONFIDENCE_FOR_VALID_DEPTH(default0.6)SNOW_FORCE_UNREADABLE_ON_POOR_VISIBILITY(defaulttrue)SNOW_MIN_CONFIDENCE_FOR_INCREMENT(default0.85)SNOW_REQUIRE_GOOD_VISIBILITY_FOR_INCREMENT(defaulttrue)SNOW_MAX_INCREMENT_IN(default3.0)
- Install Docker Desktop and enable Linux containers.
- Clone/copy repo to
C:\WebCamSnowMeasure. - Create
.envfrom.env.examplewith production values. - Verify SQL connectivity.
- Register scheduled task every 30 minutes to run:
docker compose run --rm snowcam-reporter python snow_reporter.py- Confirm rows are inserted in
dbo.snowcam_observations.
- This app is DB-driven and processes all active cameras each run.
- Overlap protection uses
data/snow_reporter.lock. - Keep
.envandsecrets/out of source control.