===== analytics-report-live-lknt8-archive-logs-pod.log ===== [pod/analytics-report-live-lknt8-archive-logs-pod/prepare] 2026-07-13T12:59:15.212884970-05:00 2026/07/13 17:59:15 Entrypoint initialization [pod/analytics-report-live-lknt8-archive-logs-pod/place-scripts] 2026-07-13T12:59:16.679775036-05:00 2026/07/13 17:59:16 Decoded script /tekton/scripts/script-0-4h5k4 [pod/analytics-report-live-lknt8-archive-logs-pod/place-scripts] 2026-07-13T12:59:16.702887848-05:00 2026/07/13 17:59:16 Decoded script /tekton/scripts/script-1-ffzk8 [pod/analytics-report-live-lknt8-archive-logs-pod/step-collect-logs] 2026-07-13T12:59:19.963596293-05:00 ==> collecting pod logs for PipelineRun=analytics-report-live-lknt8 ===== analytics-report-live-lknt8-clone-pod.log ===== [pod/analytics-report-live-lknt8-clone-pod/prepare] 2026-07-13T12:54:43.697927957-05:00 2026/07/13 17:54:43 Entrypoint initialization [pod/analytics-report-live-lknt8-clone-pod/place-scripts] 2026-07-13T12:54:44.087059011-05:00 2026/07/13 17:54:44 Decoded script /tekton/scripts/script-0-l79pk [pod/analytics-report-live-lknt8-clone-pod/step-clone] 2026-07-13T12:54:49.596836071-05:00 From https://github.com/storeminder-ai/storeminder [pod/analytics-report-live-lknt8-clone-pod/step-clone] 2026-07-13T12:54:49.596864711-05:00 * branch fa11db9bd216426d9f1cdf921f0cf771afc49902 -> FETCH_HEAD [pod/analytics-report-live-lknt8-clone-pod/step-clone] 2026-07-13T12:54:49.632091549-05:00 Cloned fa11db9bd216426d9f1cdf921f0cf771afc49902 ===== analytics-report-live-lknt8-live-build-pod.log ===== [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.773399763-05:00 Wrote Dockerfile.reportcheck: [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.774271931-05:00 # Throwaway image for the live report build. Layers the checkout [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.774279901-05:00 # (analytics/ + notebooks/ + tests) over the prod runtime so the live [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.774283551-05:00 # trait suite can run with real deps + real secrets (mounted by the Job). [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.774286491-05:00 FROM registry.storeminder.ai/images/storeminder:latest [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.774289361-05:00 COPY . /prsrc [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.774292381-05:00 WORKDIR /prsrc [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.774295931-05:00 # Overlay the report package over the baked one (deps already resolved in [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.774298921-05:00 # the base), then add the test runner. Prefer the [test] extra (adds [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.774301991-05:00 # papermill/nbclient/scrapbook); fall back to bare pytest. [pod/analytics-report-live-lknt8-live-build-pod/step-prepare-dockerfile] 2026-07-13T12:55:00.774306421-05:00 RUN pip install --no-cache-dir --no-deps -e /prsrc/analytics && (pip install --no-cache-dir -e "/prsrc/analytics[test]" || pip install --no-cache-dir pytest) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:00.996122734-05:00 INFO[0000] Retrieving image manifest registry.storeminder.ai/images/storeminder:latest [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:00.996157604-05:00 INFO[0000] Retrieving image registry.storeminder.ai/images/storeminder:latest from registry registry.storeminder.ai [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.080039067-05:00 INFO[0000] Retrieving image manifest registry.storeminder.ai/images/storeminder:latest [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.080057577-05:00 INFO[0000] Returning cached image manifest [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.096667223-05:00 INFO[0000] Built cross stage deps: map[] [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.096691642-05:00 INFO[0000] Retrieving image manifest registry.storeminder.ai/images/storeminder:latest [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.096699162-05:00 INFO[0000] Returning cached image manifest [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.096718062-05:00 INFO[0000] Retrieving image manifest registry.storeminder.ai/images/storeminder:latest [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.096725342-05:00 INFO[0000] Returning cached image manifest [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.096869502-05:00 INFO[0000] Executing 0 build triggers [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.097116391-05:00 INFO[0000] Building stage 'registry.storeminder.ai/images/storeminder:latest' [idx: '0', base-idx: '-1'] [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.120677218-05:00 INFO[0000] Checking for cached layer registry.storeminder.ai/images/cache:7831a4b2a4cd0b635dc14895eca6c64bf0c93a603279e4b1649dd699c29e96aa... [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.149864959-05:00 INFO[0000] No cached layer found for cmd RUN pip install --no-cache-dir --no-deps -e /prsrc/analytics && (pip install --no-cache-dir -e "/prsrc/analytics[test]" || pip install --no-cache-dir pytest) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:01.149882409-05:00 INFO[0000] Unpacking rootfs as cmd COPY . /prsrc requires it. [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:10.283026380-05:00 INFO[0009] COPY . /prsrc [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:10.372219203-05:00 INFO[0009] Taking snapshot of files... [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:10.476652264-05:00 INFO[0009] WORKDIR /prsrc [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:10.476747694-05:00 INFO[0009] Cmd: workdir [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:10.476752914-05:00 INFO[0009] Changed working directory to /prsrc [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:10.476865494-05:00 INFO[0009] No files changed in this command, skipping snapshotting. [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:10.476882094-05:00 INFO[0009] RUN pip install --no-cache-dir --no-deps -e /prsrc/analytics && (pip install --no-cache-dir -e "/prsrc/analytics[test]" || pip install --no-cache-dir pytest) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:10.476887574-05:00 INFO[0009] Initializing snapshotter ... [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:10.476901264-05:00 INFO[0009] Taking snapshot of full filesystem... [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:17.613378160-05:00 INFO[0016] Cmd: /bin/sh [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:17.613491240-05:00 INFO[0016] Args: [-c pip install --no-cache-dir --no-deps -e /prsrc/analytics && (pip install --no-cache-dir -e "/prsrc/analytics[test]" || pip install --no-cache-dir pytest)] [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:17.613499040-05:00 INFO[0016] Running: [/bin/sh -c pip install --no-cache-dir --no-deps -e /prsrc/analytics && (pip install --no-cache-dir -e "/prsrc/analytics[test]" || pip install --no-cache-dir pytest)] [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:18.031453222-05:00 Obtaining file:///prsrc/analytics [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:18.033634576-05:00 Installing build dependencies: started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:20.404892474-05:00 Installing build dependencies: finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:20.405967941-05:00 Checking if build backend supports build_editable: started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:20.610535590-05:00 Checking if build backend supports build_editable: finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:20.611807047-05:00 Getting requirements to build editable: started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:20.782836371-05:00 Getting requirements to build editable: finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:20.783931118-05:00 Preparing editable metadata (pyproject.toml): started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:20.967149577-05:00 Preparing editable metadata (pyproject.toml): finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.010070746-05:00 Building wheels for collected packages: storeminder [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.011650631-05:00 Building editable for storeminder (pyproject.toml): started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.259129686-05:00 Building editable for storeminder (pyproject.toml): finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.259937264-05:00 Created wheel for storeminder: filename=storeminder-0.1.0-0.editable-py3-none-any.whl size=1920 sha256=01fbb2b15f703a278949c57b5a64941c7fbd0d56509acdc55383f524c77b2677 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.259959714-05:00 Stored in directory: /tmp/pip-ephem-wheel-cache-65_15m6i/wheels/3d/79/a1/5b9945b9b299ef8e95d41afe8b877f821e7a9e81cca81ac5fa [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.262428557-05:00 Successfully built storeminder [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.262590277-05:00 Installing collected packages: storeminder [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.262722546-05:00 Attempting uninstall: storeminder [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.305770224-05:00 Found existing installation: storeminder 0.1.0 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.312051686-05:00 Uninstalling storeminder-0.1.0: [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.313792201-05:00 Successfully uninstalled storeminder-0.1.0 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.380356571-05:00 Successfully installed storeminder-0.1.0 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.380581131-05:00 WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.538833770-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.538952340-05:00 [notice] A new release of pip is available: 24.0 -> 26.1.2 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:21.538959460-05:00 [notice] To update, run: pip install --upgrade pip [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:22.043420963-05:00 Obtaining file:///prsrc/analytics [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:22.046106376-05:00 Installing build dependencies: started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:24.640377615-05:00 Installing build dependencies: finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:24.641377752-05:00 Checking if build backend supports build_editable: started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:24.846272844-05:00 Checking if build backend supports build_editable: finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:24.848374378-05:00 Getting requirements to build editable: started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.135999392-05:00 Getting requirements to build editable: finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.137614787-05:00 Preparing editable metadata (pyproject.toml): started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.345086081-05:00 Preparing editable metadata (pyproject.toml): finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.421024462-05:00 Requirement already satisfied: pandas>=1.5.0 in /usr/local/lib/python3.11/site-packages (from storeminder==0.1.0) (3.0.3) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.422084129-05:00 Requirement already satisfied: numpy>=1.23.0 in /usr/local/lib/python3.11/site-packages (from storeminder==0.1.0) (2.4.6) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.423136396-05:00 Requirement already satisfied: requests>=2.28.0 in /usr/local/lib/python3.11/site-packages (from storeminder==0.1.0) (2.34.2) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.423960684-05:00 Requirement already satisfied: openpyxl>=3.0.0 in /usr/local/lib/python3.11/site-packages (from storeminder==0.1.0) (3.1.5) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.424815471-05:00 Requirement already satisfied: anthropic>=0.25.0 in /usr/local/lib/python3.11/site-packages (from storeminder==0.1.0) (0.116.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.426363177-05:00 Requirement already satisfied: psycopg>=3.1 in /usr/local/lib/python3.11/site-packages (from psycopg[binary,pool]>=3.1->storeminder==0.1.0) (3.3.4) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.427951972-05:00 Requirement already satisfied: boto3>=1.34 in /usr/local/lib/python3.11/site-packages (from storeminder==0.1.0) (1.43.46) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.428747740-05:00 Requirement already satisfied: PyYAML>=6.0 in /usr/local/lib/python3.11/site-packages (from storeminder==0.1.0) (6.0.3) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.584139583-05:00 Collecting pytest>=7 (from storeminder==0.1.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.646431134-05:00 Downloading pytest-9.1.1-py3-none-any.whl.metadata (7.6 kB) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.663450625-05:00 Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.11/site-packages (from anthropic>=0.25.0->storeminder==0.1.0) (4.14.2) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.664766641-05:00 Requirement already satisfied: distro<2,>=1.7.0 in /usr/local/lib/python3.11/site-packages (from anthropic>=0.25.0->storeminder==0.1.0) (1.9.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.665906138-05:00 Requirement already satisfied: docstring-parser<1,>=0.15 in /usr/local/lib/python3.11/site-packages (from anthropic>=0.25.0->storeminder==0.1.0) (0.18.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.667033965-05:00 Requirement already satisfied: httpx<1,>=0.25.0 in /usr/local/lib/python3.11/site-packages (from anthropic>=0.25.0->storeminder==0.1.0) (0.28.1) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.668029012-05:00 Requirement already satisfied: jiter<1,>=0.4.0 in /usr/local/lib/python3.11/site-packages (from anthropic>=0.25.0->storeminder==0.1.0) (0.16.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.670531615-05:00 Requirement already satisfied: pydantic<3,>=1.9.0 in /usr/local/lib/python3.11/site-packages (from anthropic>=0.25.0->storeminder==0.1.0) (2.13.4) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.671145623-05:00 Requirement already satisfied: sniffio in /usr/local/lib/python3.11/site-packages (from anthropic>=0.25.0->storeminder==0.1.0) (1.3.1) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.671950921-05:00 Requirement already satisfied: typing-extensions<5,>=4.14 in /usr/local/lib/python3.11/site-packages (from anthropic>=0.25.0->storeminder==0.1.0) (4.16.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.676623667-05:00 Requirement already satisfied: botocore<1.44.0,>=1.43.46 in /usr/local/lib/python3.11/site-packages (from boto3>=1.34->storeminder==0.1.0) (1.43.46) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.677688094-05:00 Requirement already satisfied: jmespath<2.0.0,>=0.7.1 in /usr/local/lib/python3.11/site-packages (from boto3>=1.34->storeminder==0.1.0) (1.1.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.678471532-05:00 Requirement already satisfied: s3transfer<0.20.0,>=0.19.0 in /usr/local/lib/python3.11/site-packages (from boto3>=1.34->storeminder==0.1.0) (0.19.1) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.681482723-05:00 Requirement already satisfied: et-xmlfile in /usr/local/lib/python3.11/site-packages (from openpyxl>=3.0.0->storeminder==0.1.0) (2.0.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.735377648-05:00 Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.11/site-packages (from pandas>=1.5.0->storeminder==0.1.0) (2.9.0.post0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.779162152-05:00 Requirement already satisfied: psycopg-binary==3.3.4 in /usr/local/lib/python3.11/site-packages (from psycopg[binary,pool]>=3.1->storeminder==0.1.0) (3.3.4) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.780839527-05:00 Requirement already satisfied: psycopg-pool in /usr/local/lib/python3.11/site-packages (from psycopg[binary,pool]>=3.1->storeminder==0.1.0) (3.3.1) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.822335758-05:00 Collecting iniconfig>=1.0.1 (from pytest>=7->storeminder==0.1.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.835571330-05:00 Downloading iniconfig-2.3.0-py3-none-any.whl.metadata (2.5 kB) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.839082180-05:00 Requirement already satisfied: packaging>=22 in /usr/local/lib/python3.11/site-packages (from pytest>=7->storeminder==0.1.0) (26.2) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.868759875-05:00 Collecting pluggy<2,>=1.5 (from pytest>=7->storeminder==0.1.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.882929714-05:00 Downloading pluggy-1.6.0-py3-none-any.whl.metadata (4.8 kB) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.885550036-05:00 Requirement already satisfied: pygments>=2.7.2 in /usr/local/lib/python3.11/site-packages (from pytest>=7->storeminder==0.1.0) (2.20.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.893757063-05:00 Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.11/site-packages (from requests>=2.28.0->storeminder==0.1.0) (3.4.9) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.894748830-05:00 Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.11/site-packages (from requests>=2.28.0->storeminder==0.1.0) (3.18) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.895646567-05:00 Requirement already satisfied: urllib3<3,>=1.26 in /usr/local/lib/python3.11/site-packages (from requests>=2.28.0->storeminder==0.1.0) (2.7.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.896402405-05:00 Requirement already satisfied: certifi>=2023.5.7 in /usr/local/lib/python3.11/site-packages (from requests>=2.28.0->storeminder==0.1.0) (2026.6.17) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.953237892-05:00 Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.11/site-packages (from httpx<1,>=0.25.0->anthropic>=0.25.0->storeminder==0.1.0) (1.0.9) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.959906952-05:00 Requirement already satisfied: h11>=0.16 in /usr/local/lib/python3.11/site-packages (from httpcore==1.*->httpx<1,>=0.25.0->anthropic>=0.25.0->storeminder==0.1.0) (0.16.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.987589613-05:00 Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.11/site-packages (from pydantic<3,>=1.9.0->anthropic>=0.25.0->storeminder==0.1.0) (0.7.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.988592040-05:00 Requirement already satisfied: pydantic-core==2.46.4 in /usr/local/lib/python3.11/site-packages (from pydantic<3,>=1.9.0->anthropic>=0.25.0->storeminder==0.1.0) (2.46.4) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:25.990331585-05:00 Requirement already satisfied: typing-inspection>=0.4.2 in /usr/local/lib/python3.11/site-packages (from pydantic<3,>=1.9.0->anthropic>=0.25.0->storeminder==0.1.0) (0.4.2) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.002326381-05:00 Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.11/site-packages (from python-dateutil>=2.8.2->pandas>=1.5.0->storeminder==0.1.0) (1.17.0) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.074894601-05:00 Downloading pytest-9.1.1-py3-none-any.whl (386 kB) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.127915328-05:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 386.5/386.5 kB 7.7 MB/s eta 0:00:00 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.162474369-05:00 Downloading iniconfig-2.3.0-py3-none-any.whl (7.5 kB) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.186735759-05:00 Downloading pluggy-1.6.0-py3-none-any.whl (20 kB) [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.191883614-05:00 Checking if build backend supports build_editable: started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.337503194-05:00 Checking if build backend supports build_editable: finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.338170262-05:00 Building wheels for collected packages: storeminder [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.339300899-05:00 Building editable for storeminder (pyproject.toml): started [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.580619173-05:00 Building editable for storeminder (pyproject.toml): finished with status 'done' [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.581395761-05:00 Created wheel for storeminder: filename=storeminder-0.1.0-0.editable-py3-none-any.whl size=1920 sha256=895e793f1700612cd0e134d31a561bdc7206c83c941689ffdd5c72d1292218d6 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.581539890-05:00 Stored in directory: /tmp/pip-ephem-wheel-cache-eddwyp2u/wheels/3d/79/a1/5b9945b9b299ef8e95d41afe8b877f821e7a9e81cca81ac5fa [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:26.583725634-05:00 Successfully built storeminder [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:27.476698136-05:00 Installing collected packages: pluggy, iniconfig, pytest, storeminder [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:27.720577401-05:00 Attempting uninstall: storeminder [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:27.761441443-05:00 Found existing installation: storeminder 0.1.0 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:27.763387797-05:00 Uninstalling storeminder-0.1.0: [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:27.935747369-05:00 Successfully uninstalled storeminder-0.1.0 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:28.139539879-05:00 Successfully installed iniconfig-2.3.0 pluggy-1.6.0 pytest-9.1.1 storeminder-0.1.0 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:28.139809158-05:00 WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:28.324457213-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:28.324586353-05:00 [notice] A new release of pip is available: 24.0 -> 26.1.2 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:28.324593883-05:00 [notice] To update, run: pip install --upgrade pip [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:28.475813615-05:00 INFO[0027] Taking snapshot of full filesystem... [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:30.361401128-05:00 INFO[0029] Pushing layer registry.storeminder.ai/images/cache:7831a4b2a4cd0b635dc14895eca6c64bf0c93a603279e4b1649dd699c29e96aa to cache now [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:30.361625688-05:00 INFO[0029] Pushing image to registry.storeminder.ai/images/cache:7831a4b2a4cd0b635dc14895eca6c64bf0c93a603279e4b1649dd699c29e96aa [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:30.635873319-05:00 INFO[0029] Pushed registry.storeminder.ai/images/cache@sha256:d903b6ed2cd6a7f0511ad9cc874c2413cc9eedcec55c2cce563bde00f21569b4 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:30.636510477-05:00 INFO[0029] Pushing image to registry.storeminder.ai/images/storeminder-report-live:fa11db9bd216426d9f1cdf921f0cf771afc49902 [pod/analytics-report-live-lknt8-live-build-pod/step-build-pr-image] 2026-07-13T12:55:30.986875377-05:00 INFO[0030] Pushed registry.storeminder.ai/images/storeminder-report-live@sha256:ceefb86d02f4c9ac417e3397ec8f92d8fada6fe4a7278698dcabe2e71daab036 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:55:31.446285766-05:00 /tekton/scripts/script-2-lhbsz: line 27: steps.build-pr-image.exitCode.path: command not found [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:55:31.558074839-05:00 job.batch/report-live-fa11db9 created [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.020667117-05:00 ----- live Job logs (report-live-fa11db9) ----- [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476175350-05:00 Running live report trait suite (real Vertex + DB, DRY_RUN): analytics/tests/test_report_traits_live.py [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476202080-05:00 ============================= test session starts ============================== [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476208140-05:00 platform linux -- Python 3.11.15, pytest-9.1.1, pluggy-1.6.0 -- /usr/local/bin/python [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476213240-05:00 cachedir: .pytest_cache [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476217850-05:00 rootdir: /prsrc/analytics [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476222690-05:00 configfile: pyproject.toml [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476227020-05:00 plugins: anyio-4.14.2 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476231700-05:00 collecting ... collected 10 items [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476236010-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476240490-05:00 analytics/tests/test_report_traits_live.py::test_daily_builds_and_renders ERROR [ 10%] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476245220-05:00 analytics/tests/test_report_traits_live.py::test_daily_is_sendable_and_no_weekly_leak ERROR [ 20%] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476249810-05:00 analytics/tests/test_report_traits_live.py::test_daily_headline_labeled_total_sales ERROR [ 30%] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476254230-05:00 analytics/tests/test_report_traits_live.py::test_daily_sms_not_truncated ERROR [ 40%] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476260010-05:00 analytics/tests/test_report_traits_live.py::test_daily_artifact_bundle_written ERROR [ 50%] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476264410-05:00 analytics/tests/test_report_traits_live.py::test_weekly_builds_and_renders ERROR [ 60%] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476268890-05:00 analytics/tests/test_report_traits_live.py::test_weekly_is_sendable ERROR [ 70%] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476273590-05:00 analytics/tests/test_report_traits_live.py::test_weekly_headline_labeled_total_sales ERROR [ 80%] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476277940-05:00 analytics/tests/test_report_traits_live.py::test_weekly_sms_not_truncated ERROR [ 90%] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476283290-05:00 analytics/tests/test_report_traits_live.py::test_weekly_artifact_bundle_written ERROR [100%] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476287540-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476291900-05:00 ==================================== ERRORS ==================================== [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476296380-05:00 _______________ ERROR at setup of test_daily_builds_and_renders ________________ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476300590-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476304920-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476309300-05:00 def daily_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476313790-05:00 > return _run("daily") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476318160-05:00 ^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476322430-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476326790-05:00 analytics/tests/test_report_traits_live.py:94: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476331170-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476336050-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476340530-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476344890-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476349270-05:00 analytics/src/storeminder/runner.py:339: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476353660-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476358029-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476362409-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476366849-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476383219-05:00 analytics/src/storeminder/crew.py:464: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476386399-05:00 report_result = _agent.run_daily( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476389379-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1055: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476392249-05:00 return self.run(report_type="daily", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476395219-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476398079-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476400999-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476403909-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476406699-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476409659-05:00 self = [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476412739-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476416249-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476419739-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476422769-05:00 baseline = {'sample_count': 15, 'day_of_week': 4, 'total_revenue': 16270.22, 'total_transactions': 590.87, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476425919-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476430679-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...nd signage visibility overnight — especially between 21:00–23:00 | Measure: Operational status and customer access log' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476433669-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal Tuesday)** \nTobacco / Nicotine surged on strong Marlb...ause: Zero sales on $1.2k+ baseline cigars — high OOS risk | Measure: Inventory verification, urgent reorder if needed' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476442829-05:00 weather = {'yesterday': {'temp_max': 90.1, 'temp_min': 76.8, 'precip_in': 0.0, 'conditions': 'Partly cloudy'}, 'today_forecast':...rtly cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476445959-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...:** \n- Audit freshness and prep standards for top-dropped food items (1 Fish, Egg Roll, Breast) — Do by EOD tomorrow' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476449939-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...re success of Red Bull Original and SF Juneberry with team to reinforce effective selling tactics — Do by end of shift' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476452909-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.10 (+2.3% vs normal Fri...Daily | Monitor P90 service times during peak PM | Because: Slower top-decile service | Measure: Sustain avg under 65s' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476455889-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...te if either drops below $3.390 \n- Update price-volume tracking system by 2026-07-12 to restore correlation analysis' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476458809-05:00 prior_actions = None, report_type = 'daily', weekly_days = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476467559-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476471099-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476473919-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476476929-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476479919-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476482829-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476485799-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476488759-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476491899-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476494829-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476497709-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476500769-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476503749-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476509659-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476512899-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476515819-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476518799-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476521739-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476524999-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476527909-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476530859-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476533789-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476536679-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476539539-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476542439-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476545379-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476548389-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476551429-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476554379-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476557289-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476560189-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476563089-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476566199-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476569129-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476572089-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476575009-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476577999-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476586098-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476589088-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476592148-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476595028-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476601748-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476605018-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476608038-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476611038-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476613928-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476616838-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476619728-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476622588-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476625458-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476628428-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476631308-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476634228-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476637098-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476640058-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476643028-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476645928-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476648818-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476651918-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476654878-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476657828-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476660748-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476663638-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476667298-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476670138-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476673088-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476676098-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476679118-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476682018-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476684848-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476687678-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476690608-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476693488-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476696558-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476699508-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476702468-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476705528-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476708458-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476711378-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476714338-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476722028-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476725038-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476727948-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476730948-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476733948-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476736888-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476739818-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476743328-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476746248-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476749148-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476752328-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476755388-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476758298-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476761198-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476764978-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476768938-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476772538-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476775468-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476778478-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476781398-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476784738-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476788288-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476791208-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476794178-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476797078-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476799998-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476803758-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476806778-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476809648-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476812588-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476820177-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476823307-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476827567-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476830607-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476833707-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476841567-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476844507-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476847507-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476850457-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476853397-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476856347-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476859267-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476862217-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476865187-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476868087-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476872877-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476875807-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476878777-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476881717-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476884807-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476887747-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476890627-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476893637-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476896547-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476900037-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476903007-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476905897-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476909307-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476912297-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476915217-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476927767-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476932917-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476935887-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476939857-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476942817-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476945797-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476948797-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476951747-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476954647-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476958297-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476961647-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476964577-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476967517-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476970407-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476973387-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476976357-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476979327-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476982247-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476990217-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476993157-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476996157-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.476999147-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477002117-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477005037-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477008427-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477011347-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477014367-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477017297-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477020187-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477023097-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477026047-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477028967-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477031997-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477034917-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477037837-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477040777-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477043657-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477046577-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477049567-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477052527-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477055687-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477060026-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477062936-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477069446-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477072506-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477075546-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477078646-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477081556-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477084486-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477087416-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477090336-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477093226-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477096306-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477099296-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477102196-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477105106-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477112816-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477115806-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477118776-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477121646-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477124586-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477127666-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477130646-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477133576-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477136496-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477139376-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477142386-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477145356-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477148226-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477151216-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477154626-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477157466-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477160296-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477163166-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477166006-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477168866-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477173266-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477176216-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477179116-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477182046-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477184926-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477187806-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477190676-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477193656-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477196526-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477199466-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477202446-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477205396-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477208286-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477211156-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477214066-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477216946-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477219846-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477227496-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477230466-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477233356-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477236186-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477239046-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477241946-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477244786-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477247676-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477250576-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477253456-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477256316-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477259186-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477262056-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477264916-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477271906-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477274946-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477277826-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477280786-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477283736-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477286626-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477289496-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477292416-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477295326-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477298266-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477301145-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477304005-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477306805-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477309675-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477312535-05:00 ---------------------------- Captured stdout setup ----------------------------- [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477315425-05:00 Found 2 XML file(s) in /scratch/v6/Fastime/2026-07-10/raw [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477318365-05:00 Parsed: 2026-07-10.369.xml (966 tx rows, 1940 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477321285-05:00 Parsed: 2026-07-10.527.xml (966 tx rows, 1940 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477324045-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477326875-05:00 Transactions : 966 rows [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477329745-05:00 Date range : 2026-07-09 20:46:32 → 2026-07-10 20:34:48 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477332655-05:00 Types : [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477335525-05:00 Type [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477338435-05:00 sale 618 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477341335-05:00 network sale 277 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477344315-05:00 error_correct 40 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477347235-05:00 safedrop 15 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477350075-05:00 cashier_open 4 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477352905-05:00 cashier_close 4 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477355845-05:00 cashier_audit 4 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477363375-05:00 payout 2 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477366285-05:00 refundevent 1 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477369115-05:00 void 1 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477372025-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477374875-05:00 Scanned items: 1940 rows [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477377775-05:00 Line types : [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477380655-05:00 Line Type [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477383535-05:00 item 1626 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477386405-05:00 fuel 279 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477389355-05:00 merch_code 35 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477392305-05:00 Fuel volume: 2,582.638 gal [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477395095-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477397995-05:00 Saved → /scratch/v6/Fastime/2026-07-10/curated/combined_raw_transactions_20260713_175545.csv [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477400905-05:00 Saved → /scratch/v6/Fastime/2026-07-10/curated/combined_raw_scanned_20260713_175545.csv [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477403695-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477406565-05:00 ============================================================ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477409425-05:00 C-Store Analytics — v4 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477412315-05:00 Store: Fastime | Date: 2026-07-10 | Lang: English [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477415345-05:00 ============================================================ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477418155-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477420985-05:00 📂 [1/10] Loading data... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477424585-05:00 ✓ Loaded 1,940 scan rows | cols: ['time', 'date', 'Type', 'Transaction ID', 'Event Seq ID', 'Store ID', 'Register ID', 'Cashier ID', 'Payment Type', 'Payment Sub Type', 'Source File', 'Line Seq', 'Line Status', 'category', 'UPC Code', 'item', 'Entry Method', 'Merchandise Code', 'Sales Qty', 'Sales UOM', 'amount', 'Regular Price', 'Actual Price', 'Price Override', 'Price Override Reason', 'Promotion ID', 'Promotion Reason', 'Promotion Amount', 'Tax Level', 'Age Restricted', 'Min Age', 'Fuel Grade ID', 'Fuel Position ID', 'Fuel UOM'] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477429825-05:00 ✓ Loaded 966 trans rows | cols: ['Time', 'End Time', 'Business Date', 'Type', 'Transaction ID', 'Event Seq ID', 'Store ID', 'Register ID', 'Cashier ID', 'Till ID', 'Outside Sale', 'Suspended', 'Training Mode', 'Offline', 'Gross Amount', 'Net Amount', 'Tax Amount', 'Total Amount', 'Grand Direction', 'Item Line Count', 'Fuel Line Count', 'Tender Count', 'Tender Type', 'Payment Sub Type', 'Change Amount', 'Void Reason', 'Cash In Drawer', 'Food Stamps', 'Financial Sub-Type', 'Financial Amount', 'Financial Tender', 'Memo', 'Loyalty Discount', 'Loyalty Program', 'Report Seq', 'Report Interval', 'Report Period', 'Report Begin', 'Report End', 'Source File', 'Vendor', 'Vendor Model', 'datetime'] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477433055-05:00 Places hours status: 200 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477435955-05:00 ✓ Store hours (Google): 4:00–21:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477438825-05:00 ✓ Scan: 1,940 rows | Trans: 618 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477441715-05:00 ✓ Fuel merged from scan: 2,582.6 gal | $9,446.27 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477444585-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477447425-05:00 📊 [2/10] Loading baseline... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477450295-05:00 ✓ Baseline: 15 same-DOW samples [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477453055-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477455875-05:00 🔍 [3/10] Anomaly detection... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477458725-05:00 ✓ 18 HIGH / 22 MEDIUM flags [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477461485-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477464325-05:00 🌐 [4/10] External context... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477467325-05:00 Places API (New) status: 200 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477470255-05:00 → 5 reviews returned (overall rating 4.3, 532 total ratings on Google) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477473225-05:00 ✓ Reviews: 5 fetched, avg 3.0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477476215-05:00 ✓ Weather: Partly cloudy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477483905-05:00 Routes API status: 200 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477486935-05:00 ✓ Traffic: Google Routes API (live road traffic) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477489835-05:00 ✓ External: 1652 chars | reviews=5 | traffic=LOW [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477492655-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477495575-05:00 ⛽ [5/10] Fuel Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477498635-05:00 ✓ Fuel section written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477501425-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477504335-05:00 💰 [6/10] Fuel Pricing Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477507355-05:00 Fetching competitor fuel prices... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477510315-05:00 → 16 gas stations found with price data [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477513425-05:00 ✓ Fuel pricing section written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477517175-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477520245-05:00 🛒 [7/10] Category Management Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477526095-05:00 ✓ Category management section written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477528985-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477531885-05:00 🎁 [8/10] Loyalty & Promotions Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477534965-05:00 ✓ Loyalty section written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477537805-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477542524-05:00 ⚙️ [9/10] Operations Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477545534-05:00 ✓ Operations section written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477548354-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477551254-05:00 📝 [10/10] Synthesis Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477554244-05:00 _________ ERROR at setup of test_daily_is_sendable_and_no_weekly_leak __________ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477557064-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477559984-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477562904-05:00 def daily_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477565804-05:00 > return _run("daily") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477568834-05:00 ^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477571624-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477574514-05:00 analytics/tests/test_report_traits_live.py:94: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477577554-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477580504-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477583554-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477586474-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477589354-05:00 analytics/src/storeminder/runner.py:339: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477592374-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477595264-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477598134-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477601074-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477604034-05:00 analytics/src/storeminder/crew.py:464: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477606904-05:00 report_result = _agent.run_daily( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477609874-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1055: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477612744-05:00 return self.run(report_type="daily", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477615654-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477618604-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477621474-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477624414-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477627244-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477630294-05:00 self = [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477637884-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477641124-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477644234-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477648664-05:00 baseline = {'sample_count': 15, 'day_of_week': 4, 'total_revenue': 16270.22, 'total_transactions': 590.87, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477651714-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477654734-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...nd signage visibility overnight — especially between 21:00–23:00 | Measure: Operational status and customer access log' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477657804-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal Tuesday)** \nTobacco / Nicotine surged on strong Marlb...ause: Zero sales on $1.2k+ baseline cigars — high OOS risk | Measure: Inventory verification, urgent reorder if needed' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477660724-05:00 weather = {'yesterday': {'temp_max': 90.1, 'temp_min': 76.8, 'precip_in': 0.0, 'conditions': 'Partly cloudy'}, 'today_forecast':...rtly cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477663644-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...:** \n- Audit freshness and prep standards for top-dropped food items (1 Fish, Egg Roll, Breast) — Do by EOD tomorrow' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477667444-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...re success of Red Bull Original and SF Juneberry with team to reinforce effective selling tactics — Do by end of shift' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477670494-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.10 (+2.3% vs normal Fri...Daily | Monitor P90 service times during peak PM | Because: Slower top-decile service | Measure: Sustain avg under 65s' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477673404-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...te if either drops below $3.390 \n- Update price-volume tracking system by 2026-07-12 to restore correlation analysis' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477676424-05:00 prior_actions = None, report_type = 'daily', weekly_days = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477679314-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477685314-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477688194-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477691304-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477694334-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477697264-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477700184-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477703094-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477706404-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477709294-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477716854-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477719874-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477722834-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477725814-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477728884-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477731804-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477734824-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477737724-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477740594-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477743594-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477746614-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477749534-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477752454-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477755364-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477758244-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477761144-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477764044-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477766934-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477769914-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477772784-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477775654-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477778544-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477781404-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477784334-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477787193-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477790533-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477793453-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477796373-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477799293-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477802183-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477805043-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477807973-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477810913-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477813823-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477816823-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477819713-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477827253-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477830283-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477833163-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477836053-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477838953-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477841843-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477844763-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477847683-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477850553-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477853443-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477856313-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477859243-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477862143-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477865063-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477867993-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477870903-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477873813-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477876783-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477879713-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477882663-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477885683-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477888603-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477891583-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477894513-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477897443-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477912873-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477929093-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477958083-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477962403-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477965913-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477968873-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477984953-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477988063-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.477997173-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478006543-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478010423-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478013503-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478016463-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478019393-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478022433-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478025412-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478028582-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478042132-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478045232-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478048212-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478051292-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478054202-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478057032-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478059902-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478062742-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478065672-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478068512-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478071442-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478074362-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478077672-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478080682-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478083712-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478086612-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478089492-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478092412-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478095442-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478098412-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478101292-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478104202-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478107872-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478110772-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478113682-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478116922-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478119912-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478122802-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478125712-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478129692-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478132692-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478135672-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478138602-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478141652-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478144632-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478147612-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478150512-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478153412-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478156292-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478159312-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478167982-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478170982-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478174032-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478176972-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478179942-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478182802-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478186182-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478189092-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478191982-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478195612-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478198562-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478201512-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478204402-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478207392-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478210322-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478213222-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478217702-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478220642-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478223602-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478231152-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478234362-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478237382-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478240442-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478243422-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478246342-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478250112-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478253092-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478256071-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478258981-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478261931-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478264831-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478267701-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478270641-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478273611-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478276531-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478279431-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478282341-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478285231-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478288161-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478291091-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478293991-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478302021-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478305041-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478307961-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478310961-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478313811-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478316691-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478319601-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478322531-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478325671-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478328561-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478331481-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478334361-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478337261-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478340151-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478344061-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478347081-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478350021-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478352961-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478355821-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478358821-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478361811-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478364731-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478367701-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478370611-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478373651-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478376631-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478379571-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478382461-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478385521-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478388431-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478392561-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478395501-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478398551-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478401481-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478404421-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478407421-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478421501-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478424541-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478427411-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478430261-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478433181-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478436111-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478442221-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478445241-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478448201-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478451141-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478454011-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478456911-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478459801-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478462861-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478465801-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478468771-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478471701-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478474601-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478477471-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478480401-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478483281-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478486271-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478489130-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478492060-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478494930-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478497840-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478500740-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478503680-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478506590-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478509540-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478512420-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478515290-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478518200-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478521100-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478523980-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478526950-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478529890-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478532800-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478535710-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478545360-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478548380-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478551380-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478554250-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478557200-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478560620-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478563580-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478566540-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478569480-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478572360-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478575640-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478578570-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478581540-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478584470-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478587440-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478590250-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478593280-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478596280-05:00 __________ ERROR at setup of test_daily_headline_labeled_total_sales ___________ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478599040-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478602000-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478604990-05:00 def daily_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478607930-05:00 > return _run("daily") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478610840-05:00 ^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478613710-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478616670-05:00 analytics/tests/test_report_traits_live.py:94: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478619610-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478622510-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478625390-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478628360-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478631250-05:00 analytics/src/storeminder/runner.py:339: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478634150-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478637030-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478639890-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478642820-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478645680-05:00 analytics/src/storeminder/crew.py:464: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478648540-05:00 report_result = _agent.run_daily( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478651430-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1055: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478654320-05:00 return self.run(report_type="daily", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478657180-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478660050-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478665880-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478668890-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478676650-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478679680-05:00 self = [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478682650-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478685970-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478689020-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478691970-05:00 baseline = {'sample_count': 15, 'day_of_week': 4, 'total_revenue': 16270.22, 'total_transactions': 590.87, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478695320-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478698710-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...nd signage visibility overnight — especially between 21:00–23:00 | Measure: Operational status and customer access log' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478701680-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal Tuesday)** \nTobacco / Nicotine surged on strong Marlb...ause: Zero sales on $1.2k+ baseline cigars — high OOS risk | Measure: Inventory verification, urgent reorder if needed' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478704750-05:00 weather = {'yesterday': {'temp_max': 90.1, 'temp_min': 76.8, 'precip_in': 0.0, 'conditions': 'Partly cloudy'}, 'today_forecast':...rtly cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478707740-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...:** \n- Audit freshness and prep standards for top-dropped food items (1 Fish, Egg Roll, Breast) — Do by EOD tomorrow' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478710670-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...re success of Red Bull Original and SF Juneberry with team to reinforce effective selling tactics — Do by end of shift' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478713580-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.10 (+2.3% vs normal Fri...Daily | Monitor P90 service times during peak PM | Because: Slower top-decile service | Measure: Sustain avg under 65s' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478716490-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...te if either drops below $3.390 \n- Update price-volume tracking system by 2026-07-12 to restore correlation analysis' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478719429-05:00 prior_actions = None, report_type = 'daily', weekly_days = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478722359-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478725749-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478728599-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478731539-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478734529-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478737559-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478740469-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478743429-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478751199-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478754209-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478759129-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478762089-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478765059-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478767989-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478771019-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478774529-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478777499-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478780449-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478783709-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478786699-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478789609-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478795479-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478798549-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478801509-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478804469-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478807449-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478810439-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478813519-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478816459-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478819449-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478822329-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478825209-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478828129-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478831079-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478834019-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478836929-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478839929-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478842849-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478845739-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478848599-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478851459-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478854579-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478857459-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478860369-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478868749-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478871779-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478874659-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478877559-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478880429-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478883649-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478886589-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478889489-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478892409-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478895389-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478898279-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478901159-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478904179-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478907139-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478910149-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478913099-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478916099-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478930859-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478936539-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478939569-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478942489-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478946079-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478949009-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478951968-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478954888-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478957778-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478960728-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478963608-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478966528-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478969538-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478972438-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478975318-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478978188-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478981228-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478984138-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478987178-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478990128-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478993288-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478996208-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.478999098-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479001998-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479004928-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479007838-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479016758-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479019728-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479026488-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479029468-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479032398-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479035278-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479039588-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479042638-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479045508-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479048518-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479051398-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479054278-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479057188-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479061038-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479063958-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479067258-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479070138-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479073158-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479076048-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479078938-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479081818-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479084708-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479087568-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479090508-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479093418-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479096298-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479099288-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479102178-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479105928-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479108818-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479111758-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479114678-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479117568-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479120508-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479123388-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479126308-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479129248-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479132218-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479141428-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479144658-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479147518-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479150488-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479153398-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479156338-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479159228-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479162208-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479165088-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479168248-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479171158-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479174068-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479177518-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479180508-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479184248-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479187178-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479190148-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479193158-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479196047-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479198987-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479201887-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479205317-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479208217-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479211087-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479214017-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479216917-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479219897-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479222897-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479225817-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479228797-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479231767-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479234667-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479237567-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479240467-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479243387-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479246397-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479252167-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479255137-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479258127-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479261037-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479263947-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479266887-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479275537-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479278547-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479281447-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479284347-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479287297-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479290217-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479293087-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479296207-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479299197-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479302117-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479305047-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479307987-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479310857-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479313727-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479316577-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479319537-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479322617-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479325547-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479328467-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479331387-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479334377-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479337247-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479340167-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479343107-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479346047-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479348927-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479351807-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479355347-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479358227-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479361107-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479363997-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479367467-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479370457-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479373347-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479376307-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479379207-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479382067-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479385027-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479392817-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479395777-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479398707-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479401577-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479404447-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479407377-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479410227-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479413087-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479415937-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479418787-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479421727-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479424627-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479427517-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479430497-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479433427-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479436317-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479439276-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479442176-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479448116-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479451086-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479454046-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479456996-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479460636-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479463546-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479466506-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479469416-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479472306-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479475196-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479478176-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479481116-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479483976-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479486866-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479489726-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479492646-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479495506-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479498406-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479501256-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479509156-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479512156-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479515076-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479517986-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479520986-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479523876-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479526906-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479529756-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479532646-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479535496-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479538366-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479541356-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479544276-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479547146-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479550046-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479552906-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479555786-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479558556-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479561466-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479564356-05:00 ________________ ERROR at setup of test_daily_sms_not_truncated ________________ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479567176-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479570546-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479573476-05:00 def daily_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479576376-05:00 > return _run("daily") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479579266-05:00 ^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479582046-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479584956-05:00 analytics/tests/test_report_traits_live.py:94: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479587916-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479590866-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479593806-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479596696-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479599596-05:00 analytics/src/storeminder/runner.py:339: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479602496-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479605386-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479608356-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479611296-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479614256-05:00 analytics/src/storeminder/crew.py:464: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479617136-05:00 report_result = _agent.run_daily( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479620016-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1055: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479622946-05:00 return self.run(report_type="daily", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479625796-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479628686-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479636196-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479639126-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479641956-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479644896-05:00 self = [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479647816-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479650796-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479653766-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479656696-05:00 baseline = {'sample_count': 15, 'day_of_week': 4, 'total_revenue': 16270.22, 'total_transactions': 590.87, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479669856-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479675806-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...nd signage visibility overnight — especially between 21:00–23:00 | Measure: Operational status and customer access log' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479685535-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal Tuesday)** \nTobacco / Nicotine surged on strong Marlb...ause: Zero sales on $1.2k+ baseline cigars — high OOS risk | Measure: Inventory verification, urgent reorder if needed' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479690055-05:00 weather = {'yesterday': {'temp_max': 90.1, 'temp_min': 76.8, 'precip_in': 0.0, 'conditions': 'Partly cloudy'}, 'today_forecast':...rtly cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479694825-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...:** \n- Audit freshness and prep standards for top-dropped food items (1 Fish, Egg Roll, Breast) — Do by EOD tomorrow' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479699185-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...re success of Red Bull Original and SF Juneberry with team to reinforce effective selling tactics — Do by end of shift' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479703615-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.10 (+2.3% vs normal Fri...Daily | Monitor P90 service times during peak PM | Because: Slower top-decile service | Measure: Sustain avg under 65s' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479707985-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...te if either drops below $3.390 \n- Update price-volume tracking system by 2026-07-12 to restore correlation analysis' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479711435-05:00 prior_actions = None, report_type = 'daily', weekly_days = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479714405-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479718245-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479721125-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479724065-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479726975-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479729945-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479740415-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479743555-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479746545-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479749385-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479753005-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479755935-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479758815-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479761865-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479764815-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479767865-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479770795-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479773655-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479776545-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479779495-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479782455-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479785365-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479788245-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479791195-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479794105-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479797015-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479799915-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479802825-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479805745-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479808685-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479811645-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479814575-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479817475-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479821295-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479824205-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479827115-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479833565-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479836555-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479839525-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479842475-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479845415-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479848385-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479859385-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479862385-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479865385-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479868285-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479871275-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479874235-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479877185-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479880105-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479883015-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479885925-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479888805-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479891735-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479894765-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479897675-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479900615-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479903535-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479907025-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479909975-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479912885-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479915865-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479926174-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479931944-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479934884-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479937784-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479940684-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479943604-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479947144-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479950104-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479953054-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479956034-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479958964-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479961894-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479965924-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479968884-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479971794-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479974714-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479977704-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479981104-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479984094-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479986954-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479989924-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.479992844-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480001034-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480004084-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480007064-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480009994-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480012964-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480016174-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480019124-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480022084-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480024984-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480027894-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480030874-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480033904-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480036834-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480039764-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480042684-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480045614-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480048644-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480051634-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480054584-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480061184-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480064154-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480067084-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480069974-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480072894-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480075784-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480078734-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480081644-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480084544-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480087404-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480090414-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480093314-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480096284-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480099224-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480102134-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480105114-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480107984-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480110894-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480113784-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480121424-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480125724-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480128754-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480131784-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480134754-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480137734-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480140654-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480143634-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480146804-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480149724-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480152634-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480155594-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480158583-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480161513-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480164423-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480167353-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480170913-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480173863-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480176813-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480179833-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480182773-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480185693-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480188623-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480191553-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480194493-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480197423-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480200363-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480203363-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480206273-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480209203-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480212143-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480215103-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480218913-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480221903-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480224793-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480227733-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480230703-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480233623-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480236533-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480239423-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480242343-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480245263-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480248223-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480255943-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480258913-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480261843-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480264763-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480267673-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480270593-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480273493-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480276473-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480279433-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480282393-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480285293-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480288253-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480291173-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480294143-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480300373-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480304003-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480306973-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480309953-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480312993-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480315953-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480318953-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480322213-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480325163-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480328163-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480331083-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480333963-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480336903-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480340493-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480343443-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480346373-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480349273-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480352273-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480355203-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480358153-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480361053-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480363983-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480366943-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480374773-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480377723-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480380743-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480383713-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480386613-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480389502-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480392402-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480395372-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480398302-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480401172-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480404502-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480407512-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480410372-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480413232-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480416162-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480419072-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480421992-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480424882-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480427832-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480430732-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480433642-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480436562-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480439462-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480442392-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480445302-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480449032-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480451962-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480454872-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480457762-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480460672-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480463602-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480466532-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480469422-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480472322-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480475362-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480478272-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480481202-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480484062-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480491882-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480494852-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480499122-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480503362-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480507702-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480516532-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480520602-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480523652-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480526572-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480529492-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480533192-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480536132-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480539032-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480541912-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480544792-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480547682-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480550572-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480553452-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480556422-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480559232-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480562132-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480565022-05:00 _____________ ERROR at setup of test_daily_artifact_bundle_written _____________ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480567832-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480570742-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480573632-05:00 def daily_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480576542-05:00 > return _run("daily") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480579452-05:00 ^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480582252-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480585132-05:00 analytics/tests/test_report_traits_live.py:94: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480588032-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480590972-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480593862-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480596782-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480599752-05:00 analytics/src/storeminder/runner.py:339: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480602672-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480605642-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480608562-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480611492-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480614412-05:00 analytics/src/storeminder/crew.py:464: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480617302-05:00 report_result = _agent.run_daily( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480620181-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1055: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480629081-05:00 return self.run(report_type="daily", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480632101-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480635051-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480637921-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480640821-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480643631-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480646551-05:00 self = [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480649531-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480652491-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480655491-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480658491-05:00 baseline = {'sample_count': 15, 'day_of_week': 4, 'total_revenue': 16270.22, 'total_transactions': 590.87, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480661461-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480664411-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...nd signage visibility overnight — especially between 21:00–23:00 | Measure: Operational status and customer access log' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480667371-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal Tuesday)** \nTobacco / Nicotine surged on strong Marlb...ause: Zero sales on $1.2k+ baseline cigars — high OOS risk | Measure: Inventory verification, urgent reorder if needed' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480670281-05:00 weather = {'yesterday': {'temp_max': 90.1, 'temp_min': 76.8, 'precip_in': 0.0, 'conditions': 'Partly cloudy'}, 'today_forecast':...rtly cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480673171-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...:** \n- Audit freshness and prep standards for top-dropped food items (1 Fish, Egg Roll, Breast) — Do by EOD tomorrow' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480678911-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...re success of Red Bull Original and SF Juneberry with team to reinforce effective selling tactics — Do by end of shift' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480681961-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.10 (+2.3% vs normal Fri...Daily | Monitor P90 service times during peak PM | Because: Slower top-decile service | Measure: Sustain avg under 65s' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480684891-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...te if either drops below $3.390 \n- Update price-volume tracking system by 2026-07-12 to restore correlation analysis' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480687811-05:00 prior_actions = None, report_type = 'daily', weekly_days = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480690721-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480693681-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480696501-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480704061-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480707111-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480710051-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480712981-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480716021-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480718951-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480721951-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480724851-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480727791-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480730751-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480733751-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480736691-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480739621-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480742551-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480745531-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480748431-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480751401-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480754291-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480757211-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480760141-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480763111-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480765981-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480768861-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480772511-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480775451-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480778381-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480781331-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480784221-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480787161-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480790031-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480792951-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480795831-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480798761-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480801641-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480804541-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480807471-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480810371-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480817961-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480820991-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480823891-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480826881-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480829771-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480832691-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480835571-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480838441-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480841351-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480844321-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480850191-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480853150-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480856080-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480859000-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480861940-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480864830-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480867750-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480870650-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480873590-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480876570-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480879540-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480882450-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480885340-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480888270-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480891230-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480894200-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480897290-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480900210-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480903170-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480906080-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480908980-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480911910-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480914840-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480954660-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480961720-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480964700-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480967730-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480970690-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480973620-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480976590-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480979610-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480982550-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480985470-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480994100-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.480997130-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481000100-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481003030-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481005920-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481008800-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481011680-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481014570-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481017440-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481020390-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481023300-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481026210-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481029100-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481031980-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481034870-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481037770-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481040680-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481043680-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481046630-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481049530-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481052480-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481055450-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481058370-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481061360-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481064260-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481067220-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481070220-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481073120-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481076030-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481082600-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481086390-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481089310-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481092239-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481095339-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481098279-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481101249-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481104159-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481107179-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481119159-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481122249-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481125319-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481128229-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481131129-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481134149-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481137069-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481140049-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481143019-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481145919-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481148859-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481152629-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481155569-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481158659-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481161579-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481164479-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481167389-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481170289-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481173229-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481176169-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481179129-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481182059-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481184949-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481187849-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481190739-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481193649-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481196619-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481199529-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481203309-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481206269-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481209159-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481212049-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481215029-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481217929-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481220959-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481223879-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481226779-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481229699-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481232699-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481235659-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481238559-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481241469-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481249079-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481252119-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481255069-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481258159-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481261109-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481264059-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481267109-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481270049-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481273019-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481275959-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481278849-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481281759-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481284719-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481287619-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481290589-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481293469-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481296389-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481299479-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481302389-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481305309-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481308589-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481311529-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481314419-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481317319-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481320299-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481323189-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481326089-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481328999-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481331979-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481344478-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481350258-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481353468-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481357348-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481360328-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481366858-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481369838-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481372858-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481375898-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481386258-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481389488-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481392418-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481395348-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481398338-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481401258-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481404208-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481407088-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481410038-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481412938-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481415958-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481418798-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481421708-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481424558-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481427418-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481430258-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481433188-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481436078-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481438988-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481441918-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481444798-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481447688-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481450578-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481453468-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481456708-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481459718-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481462638-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481465508-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481468368-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481471268-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481474138-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481477038-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481479968-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481482838-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481485728-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481488598-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481491488-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481498928-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481501868-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481504758-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481507698-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481510578-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481513458-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481516348-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481519198-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481522098-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481524958-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481527968-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481530918-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481533788-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481536648-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481539548-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481542408-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481545318-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481548298-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481551338-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481554338-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481557248-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481560038-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481563008-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481566908-05:00 _______________ ERROR at setup of test_weekly_builds_and_renders _______________ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481569788-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481572738-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481575678-05:00 def weekly_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481581977-05:00 > return _run("weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481584957-05:00 ^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481587777-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481590767-05:00 analytics/tests/test_report_traits_live.py:99: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481593677-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481596567-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481599507-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481602457-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481605357-05:00 analytics/src/storeminder/runner.py:358: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481608287-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481611167-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481614047-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481616957-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481619827-05:00 analytics/src/storeminder/crew.py:457: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481622707-05:00 report_result = _agent.run_weekly( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481630717-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1060: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481633677-05:00 return self.run(report_type="weekly", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481636587-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481639947-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481642837-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481645787-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481648587-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481651487-05:00 self = [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481654487-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481657467-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481660467-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481663447-05:00 baseline = {'day_of_week': 21, 'total_revenue': 100081.6, 'total_transactions': 3568.94, 'fuel_gallons': 16961.39, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481666487-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481669507-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...d signage visibility — ensure no operational blockage | Measure: Evening fuel volume tomorrow vs normal Friday evening' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481672447-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal week)** \nTobacco/Nicotine surged, but Foodservice und...eout | review void log and pricing overrides | Because: 100% above baseline voids | Measure: zero unapproved overrides' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481675417-05:00 weather = {'yesterday': {'temp_max': 88.3, 'temp_min': 75.4, 'precip_in': 0.0, 'conditions': 'Clear'}, 'today_forecast': {'temp_...cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481678487-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...Roll, Breast) — Do by EOD \n- Cross-check staff schedule against POS drop timing to assess service impact — Do by EOD' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481681387-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...omorrow \n- Consider increasing signage for Copenhagen Mint Nicotine, which is below average despite active promotion' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481684907-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.12 (+8% vs normal Frida...g highest-margin window.* \n\nNo additional hours recommended — optimize existing FTE through outcome-aligned timing.' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481687817-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...petitor within 1 mile adjusts Regular below $3.450. \n\n*Note: No competitor has updated prices in the last 3 hours.*' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481690717-05:00 prior_actions = None, report_type = 'weekly' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481693687-05:00 weekly_days = [{'date': '2026-07-04', 'dow_name': 'Saturday', 'total_revenue': 7392.12, 'fuel_gallons': 1142.1, ...}, {'date': '2026...42.4, ...}, {'date': '2026-07-09', 'dow_name': 'Thursday', 'total_revenue': 16227.6, 'fuel_gallons': 2780.6, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481701287-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481704317-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481707157-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481712807-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481715797-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481718827-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481721817-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481724717-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481727587-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481730637-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481733567-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481736597-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481739497-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481742537-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481745507-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481749097-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481751997-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481754937-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481757887-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481760797-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481763767-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481766837-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481769767-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481772637-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481775487-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481778477-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481781397-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481784317-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481787267-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481790157-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481793027-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481795907-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481798777-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481801777-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481804707-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481814967-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481817887-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481820766-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481823656-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481827186-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481830136-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481833036-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481835916-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481838796-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481841676-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481844546-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481847476-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481850346-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481853186-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481856106-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481859046-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481861936-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481864806-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481867696-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481870576-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481873446-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481876426-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481879376-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481882306-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481885276-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481888206-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481891116-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481894006-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481896916-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481899796-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481902726-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481905636-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481908546-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481911406-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481914286-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481927686-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481934186-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481937256-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481940286-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481946356-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481949356-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481952296-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481955186-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481958456-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481967146-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481970186-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481973126-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481976006-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481978906-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481981816-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481984876-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481987766-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481990706-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481993616-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481996486-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.481999336-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482002196-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482005066-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482007916-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482010906-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482014496-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482017576-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482020516-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482023426-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482026356-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482029466-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482033046-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482036066-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482039046-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482041996-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482044946-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482047836-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482050746-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482053656-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482056615-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482059605-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482062545-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482066095-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482069165-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482076785-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482079725-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482082655-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482085665-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482088595-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482091475-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482094515-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482098025-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482100945-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482103915-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482106805-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482109745-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482112625-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482115545-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482118525-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482121485-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482124365-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482127255-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482130205-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482133065-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482136095-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482138995-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482141965-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482144925-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482147815-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482150705-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482153595-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482159105-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482162155-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482165165-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482168085-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482170965-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482173855-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482176695-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482179535-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482182555-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482185445-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482188535-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482192165-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482195095-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482198085-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482201025-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482203855-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482211445-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482214395-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482217315-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482220235-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482223135-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482226025-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482228975-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482232525-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482235445-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482238465-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482241395-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482244255-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482247095-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482250005-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482252895-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482255825-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482258755-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482261665-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482264705-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482267605-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482270505-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482273375-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482276235-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482279115-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482282095-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482284975-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482288015-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482290964-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482293904-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482296814-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482299694-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482302604-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482305524-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482308394-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482311334-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482314254-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482317164-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482320094-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482327864-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482330804-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482333734-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482336634-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482339584-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482342524-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482345494-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482349584-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482352544-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482355554-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482358474-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482364114-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482367094-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482370084-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482372974-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482375914-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482378944-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482381864-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482384724-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482387604-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482390454-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482393304-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482396234-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482399114-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482401974-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482404864-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482407764-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482410694-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482413564-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482416444-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482419314-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482422284-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482425214-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482428104-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482430994-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482433914-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482436794-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482444374-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482447314-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482450264-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482453254-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482456164-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482459094-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482462034-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482464924-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482467914-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482470824-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482474644-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482477574-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482480524-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482483464-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482486374-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482489234-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482492084-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482494954-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482497824-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482500684-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482503654-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482506544-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482509504-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482512384-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482515254-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482518204-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482521123-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482523893-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482526763-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482529723-05:00 ---------------------------- Captured stdout setup ----------------------------- [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482532653-05:00 Found 14 XML file(s) in /scratch/v6/Fastime/2026-07-10/raw [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482536613-05:00 Parsed: 2026-07-04.363.xml (460 tx rows, 810 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482539633-05:00 Parsed: 2026-07-04.521.xml (460 tx rows, 810 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482542523-05:00 Parsed: 2026-07-05.364.xml (442 tx rows, 789 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482545373-05:00 Parsed: 2026-07-05.522.xml (442 tx rows, 789 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482548203-05:00 Parsed: 2026-07-06.365.xml (853 tx rows, 1623 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482551103-05:00 Parsed: 2026-07-06.523.xml (853 tx rows, 1623 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482553953-05:00 Parsed: 2026-07-07.366.xml (873 tx rows, 1716 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482556883-05:00 Parsed: 2026-07-07.524.xml (873 tx rows, 1716 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482559753-05:00 Parsed: 2026-07-08.367.xml (953 tx rows, 1837 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482567973-05:00 Parsed: 2026-07-08.525.xml (953 tx rows, 1837 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482573443-05:00 Parsed: 2026-07-09.368.xml (958 tx rows, 1876 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482576503-05:00 Parsed: 2026-07-09.526.xml (958 tx rows, 1876 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482579393-05:00 Parsed: 2026-07-10.369.xml (966 tx rows, 1940 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482582263-05:00 Parsed: 2026-07-10.527.xml (966 tx rows, 1940 scan rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482585073-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482588043-05:00 Transactions : 5505 rows [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482590953-05:00 Date range : 2026-07-03 19:56:24 → 2026-07-10 20:34:48 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482593993-05:00 Types : [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482596863-05:00 Type [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482599803-05:00 sale 3542 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482602723-05:00 network sale 1572 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482605593-05:00 error_correct 233 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482608513-05:00 safedrop 67 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482611333-05:00 cashier_open 20 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482614173-05:00 cashier_close 20 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482616993-05:00 void 19 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482619803-05:00 cashier_audit 19 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482622643-05:00 payout 11 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482625503-05:00 refundevent 2 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482628233-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482631153-05:00 Scanned items: 10591 rows [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482634013-05:00 Line types : [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482636873-05:00 Line Type [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482639753-05:00 item 8891 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482642603-05:00 fuel 1583 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482645443-05:00 merch_code 117 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482648293-05:00 Fuel volume: 15,703.814 gal [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482651043-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482654003-05:00 Saved → /scratch/v6/Fastime/2026-07-10/curated/combined_raw_transactions_20260713_175748.csv [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482656943-05:00 Saved → /scratch/v6/Fastime/2026-07-10/curated/combined_raw_scanned_20260713_175748.csv [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482659763-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482662653-05:00 ============================================================ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482665533-05:00 C-Store Analytics — v4 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482668413-05:00 Store: Fastime | Date: 2026-07-10 | Lang: English [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482671303-05:00 ============================================================ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482674053-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482676913-05:00 📂 [1/10] Loading data... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482681733-05:00 ✓ Loaded 10,591 scan rows | cols: ['time', 'date', 'Type', 'Transaction ID', 'Event Seq ID', 'Store ID', 'Register ID', 'Cashier ID', 'Payment Type', 'Payment Sub Type', 'Source File', 'Line Seq', 'Line Status', 'category', 'UPC Code', 'item', 'Entry Method', 'Merchandise Code', 'Sales Qty', 'Sales UOM', 'amount', 'Regular Price', 'Actual Price', 'Price Override', 'Price Override Reason', 'Promotion ID', 'Promotion Reason', 'Promotion Amount', 'Tax Level', 'Age Restricted', 'Min Age', 'Fuel Grade ID', 'Fuel Position ID', 'Fuel UOM'] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482685173-05:00 ✓ Loaded 5,505 trans rows | cols: ['Time', 'End Time', 'Business Date', 'Type', 'Transaction ID', 'Event Seq ID', 'Store ID', 'Register ID', 'Cashier ID', 'Till ID', 'Outside Sale', 'Suspended', 'Training Mode', 'Offline', 'Gross Amount', 'Net Amount', 'Tax Amount', 'Total Amount', 'Grand Direction', 'Item Line Count', 'Fuel Line Count', 'Tender Count', 'Tender Type', 'Payment Sub Type', 'Change Amount', 'Void Reason', 'Cash In Drawer', 'Food Stamps', 'Financial Sub-Type', 'Financial Amount', 'Financial Tender', 'Memo', 'Loyalty Discount', 'Loyalty Program', 'Report Seq', 'Report Interval', 'Report Period', 'Report Begin', 'Report End', 'Source File', 'Vendor', 'Vendor Model', 'datetime'] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482692903-05:00 Places hours status: 200 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482695823-05:00 ✓ Store hours (Google): 4:00–21:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482698703-05:00 ✓ Scan: 1,940 rows | Trans: 618 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482701613-05:00 ✓ Fuel merged from scan: 2,582.6 gal | $9,446.27 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482704363-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482707253-05:00 📊 [2/10] Loading baseline... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482710153-05:00 ✓ Baseline: 15 same-DOW samples [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482712903-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482715803-05:00 🔍 [3/10] Anomaly detection... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482718703-05:00 ✓ 18 HIGH / 22 MEDIUM flags [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482721473-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482724323-05:00 🌐 [4/10] External context... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482727173-05:00 Places API (New) status: 200 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482730093-05:00 → 5 reviews returned (overall rating 4.3, 532 total ratings on Google) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482733013-05:00 ✓ Reviews: 5 fetched, avg 3.0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482735853-05:00 ✓ Weather: Clear [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482739513-05:00 Routes API status: 200 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482742363-05:00 ✓ Traffic: Google Routes API (live road traffic) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482745273-05:00 ✓ External: 1752 chars | reviews=5 | traffic=LOW [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482748033-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482750913-05:00 ⛽ [5/10] Fuel Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482753842-05:00 ✓ Fuel section written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482756592-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482759492-05:00 💰 [6/10] Fuel Pricing Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482762492-05:00 Fetching competitor fuel prices... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482765412-05:00 → 16 gas stations found with price data [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482768332-05:00 ✓ Fuel pricing section written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482771132-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482774032-05:00 🛒 [7/10] Category Management Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482776932-05:00 ✓ Category management section written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482779672-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482782512-05:00 🎁 [8/10] Loyalty & Promotions Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482785382-05:00 ✓ Loyalty section written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482788162-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482791132-05:00 ⚙️ [9/10] Operations Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482793972-05:00 ✓ Operations section written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482796822-05:00 📅 Generating schedule optimisation... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482799702-05:00 ✓ Weekly per-day breakdown: 7 trading day(s) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482802622-05:00 ✓ Weekly baseline: summed across 7 day(s) (totals scaled, ratios recomputed) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482805372-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482808212-05:00 📝 [10/10] Synthesis Agent... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482811112-05:00 __________________ ERROR at setup of test_weekly_is_sendable ___________________ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482813872-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482816762-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482819632-05:00 def weekly_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482822502-05:00 > return _run("weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482825412-05:00 ^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482828182-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482831062-05:00 analytics/tests/test_report_traits_live.py:99: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482834032-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482837012-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482844912-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482850682-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482853622-05:00 analytics/src/storeminder/runner.py:358: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482856532-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482859372-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482862212-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482865042-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482867882-05:00 analytics/src/storeminder/crew.py:457: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482870712-05:00 report_result = _agent.run_weekly( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482873582-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1060: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482876432-05:00 return self.run(report_type="weekly", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482879282-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482882142-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482884972-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482887902-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482890672-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482893552-05:00 self = [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482896442-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482899442-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482902412-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482905332-05:00 baseline = {'day_of_week': 21, 'total_revenue': 100081.6, 'total_transactions': 3568.94, 'fuel_gallons': 16961.39, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482908282-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482911252-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...d signage visibility — ensure no operational blockage | Measure: Evening fuel volume tomorrow vs normal Friday evening' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482914162-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal week)** \nTobacco/Nicotine surged, but Foodservice und...eout | review void log and pricing overrides | Because: 100% above baseline voids | Measure: zero unapproved overrides' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482923222-05:00 weather = {'yesterday': {'temp_max': 88.3, 'temp_min': 75.4, 'precip_in': 0.0, 'conditions': 'Clear'}, 'today_forecast': {'temp_...cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482932372-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...Roll, Breast) — Do by EOD \n- Cross-check staff schedule against POS drop timing to assess service impact — Do by EOD' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482935522-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...omorrow \n- Consider increasing signage for Copenhagen Mint Nicotine, which is below average despite active promotion' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482944882-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.12 (+8% vs normal Frida...g highest-margin window.* \n\nNo additional hours recommended — optimize existing FTE through outcome-aligned timing.' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482947912-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...petitor within 1 mile adjusts Regular below $3.450. \n\n*Note: No competitor has updated prices in the last 3 hours.*' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482950832-05:00 prior_actions = None, report_type = 'weekly' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482953832-05:00 weekly_days = [{'date': '2026-07-04', 'dow_name': 'Saturday', 'total_revenue': 7392.12, 'fuel_gallons': 1142.1, ...}, {'date': '2026...42.4, ...}, {'date': '2026-07-09', 'dow_name': 'Thursday', 'total_revenue': 16227.6, 'fuel_gallons': 2780.6, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482956812-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482959852-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482962662-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482965532-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482968422-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482971392-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482974342-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482977222-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482980112-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.482993631-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483003111-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483006311-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483008871-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483016591-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483019581-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483022251-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483024741-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483027111-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483029541-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483031931-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483034611-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483036991-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483039431-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483041721-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483044171-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483047321-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483049731-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483052091-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483054471-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483063441-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483065901-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483068951-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483071371-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483073751-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483076051-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483078421-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483080791-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483083151-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483085491-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483087811-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483090201-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483092601-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483095141-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483097551-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483099861-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483102201-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483104551-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483106821-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483109221-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483111581-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483114221-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483116621-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483118911-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483121361-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483123661-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483125951-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483128341-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483130681-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483133091-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483135461-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483137801-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483141411-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483143771-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483146751-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483149061-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483151441-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483153741-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483156071-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483158371-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483160661-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483167421-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483169771-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483172091-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483174441-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483176751-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483179061-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483181371-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483183751-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483186121-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483192071-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483194531-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483196901-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483199201-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483201491-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483203811-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483206191-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483208501-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483211071-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483213361-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483215641-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483218141-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483220411-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483222701-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483225051-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483227321-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483229631-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483232490-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483234800-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483237180-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483239480-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483242220-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483244690-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483247090-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483249460-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483251940-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483258900-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483261250-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483263590-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483265900-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483268190-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483271710-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483274130-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483276420-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483279430-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483281780-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483284100-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483286450-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483288840-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483291170-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483293570-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483296570-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483298920-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483301240-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483303640-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483305950-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483308510-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483310810-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483313120-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483316590-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483319070-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483321490-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483323800-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483326150-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483328470-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483331220-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483333580-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483336040-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483338350-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483340680-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483342980-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483345290-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483347660-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483350040-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483352360-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483354680-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483356970-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483359270-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483364140-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483366610-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483374210-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483376740-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483379030-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483381380-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483383660-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483386000-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483388400-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483390710-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483393030-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483395390-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483397690-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483400030-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483402340-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483404650-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483407020-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483409340-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483411680-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483414040-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483416410-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483418780-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483421070-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483423360-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483425660-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483427970-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483430320-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483432630-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483435050-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483437400-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483439800-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483442130-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483444450-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483447340-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483449690-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483452050-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483454490-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483456860-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483459170-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483461540-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483467750-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483470190-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483472510-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483474929-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483477239-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483479599-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483482009-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483484329-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483486629-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483489809-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483492129-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483494469-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483496829-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483499149-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483501559-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483503959-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483506259-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483508659-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483510969-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483513409-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483515719-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483518009-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483520389-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483522789-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483527459-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483529879-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483532229-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483534959-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483537289-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483539559-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483542449-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483544889-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483547249-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483550319-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483552669-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483555039-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483557319-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483559609-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483566069-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483568499-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483570779-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483573099-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483575419-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483577809-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483580109-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483582449-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483584839-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483587169-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483589529-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483591869-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483594269-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483596579-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483598859-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483601209-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483603559-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483605849-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483608279-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483610589-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483612959-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483615329-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483618469-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483620849-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483623669-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483626069-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483628549-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483630849-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483633309-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483636289-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483638709-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483641959-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483644359-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483646799-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483649079-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483651449-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483653889-05:00 __________ ERROR at setup of test_weekly_headline_labeled_total_sales __________ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483656129-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483658469-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483660819-05:00 def weekly_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483663159-05:00 > return _run("weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483670049-05:00 ^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483672309-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483674659-05:00 analytics/tests/test_report_traits_live.py:99: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483677049-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483679389-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483681689-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483684039-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483686409-05:00 analytics/src/storeminder/runner.py:358: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483688699-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483690979-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483693269-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483696489-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483698779-05:00 analytics/src/storeminder/crew.py:457: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483701059-05:00 report_result = _agent.run_weekly( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483703339-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1060: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483705609-05:00 return self.run(report_type="weekly", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483708119-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483710409-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483715099-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483717488-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483719708-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483722098-05:00 self = [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483724498-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483727848-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483730198-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483732538-05:00 baseline = {'day_of_week': 21, 'total_revenue': 100081.6, 'total_transactions': 3568.94, 'fuel_gallons': 16961.39, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483736058-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483739538-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...d signage visibility — ensure no operational blockage | Measure: Evening fuel volume tomorrow vs normal Friday evening' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483741958-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal week)** \nTobacco/Nicotine surged, but Foodservice und...eout | review void log and pricing overrides | Because: 100% above baseline voids | Measure: zero unapproved overrides' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483744378-05:00 weather = {'yesterday': {'temp_max': 88.3, 'temp_min': 75.4, 'precip_in': 0.0, 'conditions': 'Clear'}, 'today_forecast': {'temp_...cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483746688-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...Roll, Breast) — Do by EOD \n- Cross-check staff schedule against POS drop timing to assess service impact — Do by EOD' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483753848-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...omorrow \n- Consider increasing signage for Copenhagen Mint Nicotine, which is below average despite active promotion' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483756258-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.12 (+8% vs normal Frida...g highest-margin window.* \n\nNo additional hours recommended — optimize existing FTE through outcome-aligned timing.' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483758558-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...petitor within 1 mile adjusts Regular below $3.450. \n\n*Note: No competitor has updated prices in the last 3 hours.*' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483761018-05:00 prior_actions = None, report_type = 'weekly' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483763448-05:00 weekly_days = [{'date': '2026-07-04', 'dow_name': 'Saturday', 'total_revenue': 7392.12, 'fuel_gallons': 1142.1, ...}, {'date': '2026...42.4, ...}, {'date': '2026-07-09', 'dow_name': 'Thursday', 'total_revenue': 16227.6, 'fuel_gallons': 2780.6, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483765768-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483768168-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483770408-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483772788-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483775678-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483778108-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483780528-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483782848-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483785218-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483788088-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483790468-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483792788-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483795098-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483797508-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483799938-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483802808-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483805198-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483807558-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483812288-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483814678-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483817328-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483819698-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483822008-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483824378-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483827068-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483833628-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483836068-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483838388-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483840668-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483842968-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483845278-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483847548-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483849918-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483852258-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483854568-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483856958-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483859248-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483861568-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483863948-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483866248-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483868588-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483870968-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483873278-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483875578-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483877848-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483880218-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483882528-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483884828-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483887108-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483889408-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483891728-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483894038-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483896328-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483898668-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483900998-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483903288-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483905598-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483907948-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483910278-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483912688-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483915738-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483922588-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483930798-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483933228-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483935548-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483937898-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483947018-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483949458-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483951758-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483954118-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483956498-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483958777-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483961107-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483963437-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483965807-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483968107-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483971257-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483973647-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483975957-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483978337-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483980677-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483982977-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483985327-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483987617-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483989927-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483992257-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483994567-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.483999367-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484001767-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484004127-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484006417-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484008717-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484011097-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484013387-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484015747-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484018047-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484020317-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484022707-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484025017-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484027307-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484029807-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484036657-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484039037-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484041357-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484043677-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484045967-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484048327-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484050657-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484052987-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484055317-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484057617-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484061237-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484063677-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484066077-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484068377-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484070667-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484074207-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484076617-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484079047-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484081437-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484083727-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484086277-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484088637-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484090937-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484093277-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484095647-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484098037-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484100347-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484102657-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484105027-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484107727-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484110007-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484112417-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484114717-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484117147-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484119457-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484121757-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484124067-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484126367-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484128677-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484131017-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484133377-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484135707-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484141577-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484143997-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484146317-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484148647-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484150967-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484153377-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484156467-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484158757-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484161077-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484163367-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484165687-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484168017-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484170367-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484172707-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484174997-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484179747-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484182157-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484184547-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484186917-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484189206-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484191556-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484193936-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484196296-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484198696-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484201046-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484203356-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484205736-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484208076-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484210486-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484212796-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484215106-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484217416-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484219706-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484222036-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484224336-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484226636-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484228956-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484231266-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484233626-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484235956-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484242436-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484244906-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484248076-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484250416-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484252776-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484255076-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484257856-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484260146-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484262496-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484264786-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484267096-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484269406-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484271946-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484274266-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484277376-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484279686-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484281986-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484284316-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484286666-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484288996-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484291336-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484293696-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484296026-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484298346-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484300626-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484302906-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484305196-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484307466-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484309786-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484312056-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484314346-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484316626-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484318906-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484321166-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484323456-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484325746-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484328076-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484334786-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484347736-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484353956-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484357386-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484360316-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484363286-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484370366-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484373426-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484376486-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484379396-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484382296-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484385256-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484388146-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484391136-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484394106-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484397026-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484399966-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484402886-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484406036-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484408916-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484411856-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484414746-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484417616-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484420556-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484423465-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484426425-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484429345-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484432255-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484436095-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484439095-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484442065-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484444995-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484447965-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484450865-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484453745-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484456645-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484459525-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484462435-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484465335-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484476305-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484479325-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484482285-05:00 _______________ ERROR at setup of test_weekly_sms_not_truncated ________________ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484485085-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484488025-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484490965-05:00 def weekly_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484493895-05:00 > return _run("weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484496765-05:00 ^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484499555-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484502425-05:00 analytics/tests/test_report_traits_live.py:99: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484505395-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484508315-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484511255-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484514175-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484517175-05:00 analytics/src/storeminder/runner.py:358: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484520065-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484522945-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484525785-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484528685-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484531615-05:00 analytics/src/storeminder/crew.py:457: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484534475-05:00 report_result = _agent.run_weekly( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484537405-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1060: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484540325-05:00 return self.run(report_type="weekly", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484543165-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484546065-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484548905-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484551785-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484554555-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484557425-05:00 self = [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484560385-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484563455-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484566455-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484569355-05:00 baseline = {'day_of_week': 21, 'total_revenue': 100081.6, 'total_transactions': 3568.94, 'fuel_gallons': 16961.39, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484572635-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484579155-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...d signage visibility — ensure no operational blockage | Measure: Evening fuel volume tomorrow vs normal Friday evening' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484582295-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal week)** \nTobacco/Nicotine surged, but Foodservice und...eout | review void log and pricing overrides | Because: 100% above baseline voids | Measure: zero unapproved overrides' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484590085-05:00 weather = {'yesterday': {'temp_max': 88.3, 'temp_min': 75.4, 'precip_in': 0.0, 'conditions': 'Clear'}, 'today_forecast': {'temp_...cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484593185-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...Roll, Breast) — Do by EOD \n- Cross-check staff schedule against POS drop timing to assess service impact — Do by EOD' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484596135-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...omorrow \n- Consider increasing signage for Copenhagen Mint Nicotine, which is below average despite active promotion' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484599055-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.12 (+8% vs normal Frida...g highest-margin window.* \n\nNo additional hours recommended — optimize existing FTE through outcome-aligned timing.' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484602055-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...petitor within 1 mile adjusts Regular below $3.450. \n\n*Note: No competitor has updated prices in the last 3 hours.*' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484604965-05:00 prior_actions = None, report_type = 'weekly' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484607855-05:00 weekly_days = [{'date': '2026-07-04', 'dow_name': 'Saturday', 'total_revenue': 7392.12, 'fuel_gallons': 1142.1, ...}, {'date': '2026...42.4, ...}, {'date': '2026-07-09', 'dow_name': 'Thursday', 'total_revenue': 16227.6, 'fuel_gallons': 2780.6, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484610835-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484614565-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484617385-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484620475-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484623465-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484626365-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484629265-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484632155-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484635025-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484637885-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484640845-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484643775-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484646755-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484649905-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484652834-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484655834-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484658684-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484661634-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484664634-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484667514-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484676834-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484679774-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484682934-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484685814-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484688714-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484691574-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484694584-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484697464-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484700384-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484703304-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484708864-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484711904-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484714814-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484717764-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484720654-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484724124-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484727084-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484730084-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484732984-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484735954-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484738834-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484741724-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484744664-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484747554-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484750464-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484753314-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484756204-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484759074-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484761944-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484764844-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484767884-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484770764-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484773614-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484776504-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484779374-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484782244-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484785144-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484788074-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484790964-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484798714-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484801674-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484804554-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484807454-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484810474-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484813354-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484816334-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484819234-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484822104-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484824974-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484827924-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484830784-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484833634-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484836594-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484839574-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484842434-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484845304-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484848184-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484851124-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484854064-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484856994-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484859944-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484862894-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484865734-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484868574-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484871414-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484874324-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484877194-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484880054-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484882904-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484885783-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484888643-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484892163-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484895163-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484898203-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484901063-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484903913-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484911433-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484921843-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484927573-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484930553-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484933853-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484936843-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484939713-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484942583-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484945523-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484948413-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484951253-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484954133-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484957003-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484959923-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484962783-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484965623-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484968523-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484971403-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484974273-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484977093-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484980083-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484982973-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484985933-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484988813-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484991723-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484994613-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.484997453-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485000373-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485003313-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485006163-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485009003-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485011863-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485014763-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485017703-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485020653-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485023483-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485026363-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485029323-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485032473-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485035373-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485038333-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485041183-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485051443-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485054463-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485057343-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485060333-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485063253-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485066143-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485069063-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485102853-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485132802-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485136432-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485156592-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485160362-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485163372-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485166252-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485169202-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485172072-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485181042-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485190712-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485193632-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485196512-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485199392-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485202252-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485205212-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485208142-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485214652-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485217672-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485220622-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485223502-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485226472-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485229392-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485244992-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485248782-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485251702-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485254662-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485266502-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485269602-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485272542-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485275512-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485278422-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485281292-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485314662-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485324092-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485327022-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485330002-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485333822-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485336842-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485345272-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485348392-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485351332-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485354262-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485357152-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485360042-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485362972-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485369051-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485372151-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485375151-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485378151-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485381071-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485383961-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485386861-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485404691-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485407711-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485410591-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485413861-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485416761-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485431401-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485434421-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485437331-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485440281-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485443241-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485446151-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485451471-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485458951-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485461881-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485464731-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485467651-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485470501-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485479031-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485482001-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485484911-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485488221-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485495651-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485498611-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485501501-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485504441-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485507391-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485510301-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485513241-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485516231-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485519091-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485521991-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485543501-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485547171-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485550961-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485553941-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485556921-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485562411-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485571141-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485586451-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485589571-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485592531-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485595471-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485598371-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485601231-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485605451-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485608351-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485611241-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485614150-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485617080-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485620010-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485622880-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485625720-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485628530-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485631360-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485634220-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485637080-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485646600-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485649600-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485652480-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485655340-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485658240-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485661020-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485664000-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485666920-05:00 ____________ ERROR at setup of test_weekly_artifact_bundle_written _____________ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485669780-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485672650-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485675560-05:00 def weekly_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485678930-05:00 > return _run("weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485683460-05:00 ^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485686290-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485689220-05:00 analytics/tests/test_report_traits_live.py:99: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485692090-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485695130-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485698030-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485701060-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485703960-05:00 analytics/src/storeminder/runner.py:358: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485706830-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485709690-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485712570-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485715470-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485718370-05:00 analytics/src/storeminder/crew.py:457: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485721260-05:00 report_result = _agent.run_weekly( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485724140-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1060: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485727130-05:00 return self.run(report_type="weekly", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485730500-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485733430-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485736340-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485739350-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485742160-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485745070-05:00 self = [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485748010-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485750980-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485753950-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485756840-05:00 baseline = {'day_of_week': 21, 'total_revenue': 100081.6, 'total_transactions': 3568.94, 'fuel_gallons': 16961.39, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485759800-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485767270-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...d signage visibility — ensure no operational blockage | Measure: Evening fuel volume tomorrow vs normal Friday evening' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485770560-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal week)** \nTobacco/Nicotine surged, but Foodservice und...eout | review void log and pricing overrides | Because: 100% above baseline voids | Measure: zero unapproved overrides' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485777020-05:00 weather = {'yesterday': {'temp_max': 88.3, 'temp_min': 75.4, 'precip_in': 0.0, 'conditions': 'Clear'}, 'today_forecast': {'temp_...cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485780110-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...Roll, Breast) — Do by EOD \n- Cross-check staff schedule against POS drop timing to assess service impact — Do by EOD' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485783170-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...omorrow \n- Consider increasing signage for Copenhagen Mint Nicotine, which is below average despite active promotion' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485786090-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.12 (+8% vs normal Frida...g highest-margin window.* \n\nNo additional hours recommended — optimize existing FTE through outcome-aligned timing.' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485788960-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...petitor within 1 mile adjusts Regular below $3.450. \n\n*Note: No competitor has updated prices in the last 3 hours.*' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485791950-05:00 prior_actions = None, report_type = 'weekly' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485794940-05:00 weekly_days = [{'date': '2026-07-04', 'dow_name': 'Saturday', 'total_revenue': 7392.12, 'fuel_gallons': 1142.1, ...}, {'date': '2026...42.4, ...}, {'date': '2026-07-09', 'dow_name': 'Thursday', 'total_revenue': 16227.6, 'fuel_gallons': 2780.6, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485797850-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485800820-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485803610-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485806490-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485809440-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485812350-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485815230-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485818170-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485821070-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485823960-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485826860-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485829810-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485832730-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485835700-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485838610-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485841590-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485849190-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485852120-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485855080-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485964839-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485970899-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485975159-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485979389-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485983759-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485987989-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485992299-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.485996609-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486000859-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486005149-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486009369-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486013629-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486018059-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486022299-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486026519-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486030749-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486035589-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486039989-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486047859-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486052229-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486056539-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486069569-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486075239-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486079829-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486084549-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486089209-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486093819-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486098438-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486102798-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486107378-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486111888-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486116478-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486121098-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486125878-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486139008-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486142168-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486145118-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486147998-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486150868-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486153738-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486156608-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486159528-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486162428-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486165318-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486168268-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486171218-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486174098-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486177018-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486179888-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486182768-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486185638-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486188608-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486191498-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486194378-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486197278-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486200218-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486203688-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486206628-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486209578-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486212558-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486215528-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486218418-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486221328-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486224248-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486227108-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486229988-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486232898-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486236348-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486239238-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486242228-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486245188-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486248078-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486250988-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486253838-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486261628-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486264608-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486267518-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486270378-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486273298-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486276218-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486279188-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486282178-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486285078-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486288008-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486291008-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486293978-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486296908-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486308458-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486312868-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486315458-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486317858-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486320258-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486322698-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486324978-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486330588-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486333058-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486335408-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486337948-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486340498-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486342857-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486345237-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486347687-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486350067-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486352417-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486354757-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486357137-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486359437-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486361747-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486364137-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486366507-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486368867-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486371187-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486373537-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486376707-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486379017-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486381517-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486392917-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486395307-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486397677-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486399997-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486402327-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486404697-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486407087-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486409447-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486411767-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486414107-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486416467-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486418777-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486421087-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486423417-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486425797-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486428097-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486430417-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486432737-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486435037-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486437457-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486439817-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486442217-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486444557-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486446877-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486450767-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486453227-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486455677-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486458017-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486460397-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486462767-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486465147-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486467657-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486470267-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486472597-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486474927-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486477367-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486480317-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486483287-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486486187-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486494107-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486497087-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486500017-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486502907-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486505897-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486509337-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486512277-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486515207-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486518107-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486521137-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486524107-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486530027-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486533047-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486536097-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486539067-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486542007-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486544927-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486548267-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486551167-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486554167-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486557167-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486560117-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486563027-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486565967-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486568877-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486571867-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486574787-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486577777-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486580687-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486583627-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486586566-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486589506-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486592396-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486595326-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486598216-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486601136-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486604036-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486612666-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486615606-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486618476-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486621356-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486624236-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486627186-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486630096-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486633026-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486636036-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486638916-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486641826-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486644686-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486647586-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486650486-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486653416-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486656376-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486659286-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486662226-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486665166-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486668116-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486671076-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486673996-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486677576-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486680526-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486683476-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486686436-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486689346-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486692326-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486695226-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486698206-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486701126-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486704036-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486707126-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486710076-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486712986-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486715916-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486718876-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486721766-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486730206-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486736216-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486739286-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486742236-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486745606-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486748636-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486751636-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486754556-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486757456-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486760376-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486763176-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486766106-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486769186-05:00 --------------------- generated xml file: /tmp/traits.xml ---------------------- [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486772536-05:00 =========================== short test summary info ============================ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486775476-05:00 ERROR analytics/tests/test_report_traits_live.py::test_daily_builds_and_renders [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486778346-05:00 ERROR analytics/tests/test_report_traits_live.py::test_daily_is_sendable_and_no_weekly_leak [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486781276-05:00 ERROR analytics/tests/test_report_traits_live.py::test_daily_headline_labeled_total_sales [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486784236-05:00 ERROR analytics/tests/test_report_traits_live.py::test_daily_sms_not_truncated [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486788006-05:00 ERROR analytics/tests/test_report_traits_live.py::test_daily_artifact_bundle_written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486790906-05:00 ERROR analytics/tests/test_report_traits_live.py::test_weekly_builds_and_renders [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486793776-05:00 ERROR analytics/tests/test_report_traits_live.py::test_weekly_is_sendable - A... [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486796646-05:00 ERROR analytics/tests/test_report_traits_live.py::test_weekly_headline_labeled_total_sales [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486799526-05:00 ERROR analytics/tests/test_report_traits_live.py::test_weekly_sms_not_truncated [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486802406-05:00 ERROR analytics/tests/test_report_traits_live.py::test_weekly_artifact_bundle_written [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486805286-05:00 ======================== 10 errors in 214.27s (0:03:34) ======================== [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486808276-05:00 ===JUNIT_BEGIN=== [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486811616-05:00 @pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486814576-05:00 def daily_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486817496-05:00 > return _run("daily") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486820406-05:00 ^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486823196-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486826086-05:00 analytics/tests/test_report_traits_live.py:94: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486829045-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486832045-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486835465-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486844465-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486847485-05:00 analytics/src/storeminder/runner.py:339: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486850405-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486853295-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486856175-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486859105-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486862065-05:00 analytics/src/storeminder/crew.py:464: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486865035-05:00 report_result = _agent.run_daily( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486867915-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1055: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486870805-05:00 return self.run(report_type="daily", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486873725-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486876645-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486879575-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486882455-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486885285-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486888245-05:00 self = <storeminder.agents.synthesis_agent.SynthesisAgent object at 0x70ca784bb690> [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486891175-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486894195-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486897185-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486900125-05:00 baseline = {'sample_count': 15, 'day_of_week': 4, 'total_revenue': 16270.22, 'total_transactions': 590.87, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486903155-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486909755-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...nd signage visibility overnight — especially between 21:00–23:00 | Measure: Operational status and customer access log' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486912925-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal Tuesday)** \nTobacco / Nicotine surged on strong Marlb...ause: Zero sales on $1.2k+ baseline cigars — high OOS risk | Measure: Inventory verification, urgent reorder if needed' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486915995-05:00 weather = {'yesterday': {'temp_max': 90.1, 'temp_min': 76.8, 'precip_in': 0.0, 'conditions': 'Partly cloudy'}, 'today_forecast':...rtly cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486923845-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...:** \n- Audit freshness and prep standards for top-dropped food items (1 Fish, Egg Roll, Breast) — Do by EOD tomorrow' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486929425-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...re success of Red Bull Original and SF Juneberry with team to reinforce effective selling tactics — Do by end of shift' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486932395-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.10 (+2.3% vs normal Fri...Daily | Monitor P90 service times during peak PM | Because: Slower top-decile service | Measure: Sustain avg under 65s' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486940645-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...te if either drops below $3.390 \n- Update price-volume tracking system by 2026-07-12 to restore correlation analysis' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486943665-05:00 prior_actions = None, report_type = 'daily', weekly_days = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486946645-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486949595-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486952365-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486955245-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486958175-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486961155-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486964135-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486967065-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486970045-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486972945-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486975895-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486978815-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486981755-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486985075-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486988075-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486990975-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486993945-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486996845-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.486999745-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487002655-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487005535-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487008415-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487011325-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487014235-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487017135-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487020025-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487022945-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487025865-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487028775-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487031675-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487034585-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487044025-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487050185-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487053195-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487056085-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487059005-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487061885-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487065434-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487068394-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487071404-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487074274-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487077224-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487080524-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487083444-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487086384-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487089244-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487092174-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487095114-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487098034-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487100914-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487103804-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487106704-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487109544-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487112444-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487115304-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487118244-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487121124-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487124014-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487126944-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487129884-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487132774-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487135644-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487138584-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487141484-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487144424-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487147394-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487150314-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487153634-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487156604-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487159514-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487162394-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487165364-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487168254-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487171214-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487180004-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487182964-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487185874-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487188814-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487191734-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487194804-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487197814-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487200714-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487203594-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487206474-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487209384-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487212374-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487215334-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487218264-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487221234-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487224114-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487227014-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487229884-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487232754-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487235614-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487238474-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487241384-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487244254-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487247124-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487250014-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487252924-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487259824-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487262824-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487265854-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487268774-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487271664-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487274574-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487277474-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487280394-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487288824-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487291864-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487294864-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487297773-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487300803-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487303813-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487306783-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487309693-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487312703-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487315653-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487318583-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487321593-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487324473-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487327403-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487330363-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487333813-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487336723-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487339603-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487342563-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487345483-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487348403-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487351323-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487354233-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487357173-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487360103-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487362983-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487366053-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487368973-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487371863-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487374773-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487377703-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487380643-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487383603-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487386593-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487389543-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487392483-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487395383-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487398313-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487401223-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487404173-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487407093-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487410073-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487413013-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487415933-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487423763-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487426673-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487429723-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487432663-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487435553-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487438453-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487441433-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487444363-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487447293-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487450273-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487453263-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487456193-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487459123-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487462063-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487464983-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487467913-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487470813-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487474183-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487476483-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487478793-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487481943-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487484263-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487486663-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487491393-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487493793-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487496133-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487498543-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487500853-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487503203-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487505553-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487507853-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487510233-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487512563-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487514843-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487517163-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487519463-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487521763-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487524113-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487530422-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487532812-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487535102-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487537412-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487539712-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487542012-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487544312-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487546672-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487548992-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487551292-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487553602-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487555972-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487558292-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487560582-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487562982-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487565342-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487567652-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487569952-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487572232-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487574522-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487576932-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487579232-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487581512-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487583842-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487586142-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487588422-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487590692-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487592992-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487595812-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487598902-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487601252-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487603642-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487606012-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487608392-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487610772-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487613082-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487615482-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487621732-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487624122-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487626452-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487630202-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487632582-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487634922-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487638262-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487640602-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487643722-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487646022-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487648382-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487651522-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487656052-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487658452-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487660882-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487663272-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487665602-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487667942-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487670242-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487673152-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487675452-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487677732-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487680012-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487682352-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487684742-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487687092-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487689902-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487692232-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487694572-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487696962-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487699262-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487701662-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487703902-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487706592-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError@pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487708962-05:00 def daily_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487711302-05:00 > return _run("daily") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487714262-05:00 ^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487716512-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487722832-05:00 analytics/tests/test_report_traits_live.py:94: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487725402-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487727692-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487730062-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487732402-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487734732-05:00 analytics/src/storeminder/runner.py:339: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487737042-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487739322-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487741612-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487743912-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487746212-05:00 analytics/src/storeminder/crew.py:464: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487748532-05:00 report_result = _agent.run_daily( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487750902-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1055: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487753202-05:00 return self.run(report_type="daily", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487755482-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487757812-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487760101-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487762401-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487764641-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487767011-05:00 self = <storeminder.agents.synthesis_agent.SynthesisAgent object at 0x70ca784bb690> [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487769401-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487772471-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487774891-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487777221-05:00 baseline = {'sample_count': 15, 'day_of_week': 4, 'total_revenue': 16270.22, 'total_transactions': 590.87, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487779621-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487782061-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...nd signage visibility overnight — especially between 21:00–23:00 | Measure: Operational status and customer access log' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487784511-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal Tuesday)** \nTobacco / Nicotine surged on strong Marlb...ause: Zero sales on $1.2k+ baseline cigars — high OOS risk | Measure: Inventory verification, urgent reorder if needed' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487796781-05:00 weather = {'yesterday': {'temp_max': 90.1, 'temp_min': 76.8, 'precip_in': 0.0, 'conditions': 'Partly cloudy'}, 'today_forecast':...rtly cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487809061-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...:** \n- Audit freshness and prep standards for top-dropped food items (1 Fish, Egg Roll, Breast) — Do by EOD tomorrow' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487823971-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...re success of Red Bull Original and SF Juneberry with team to reinforce effective selling tactics — Do by end of shift' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487827391-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.10 (+2.3% vs normal Fri...Daily | Monitor P90 service times during peak PM | Because: Slower top-decile service | Measure: Sustain avg under 65s' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487830401-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...te if either drops below $3.390 \n- Update price-volume tracking system by 2026-07-12 to restore correlation analysis' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487833401-05:00 prior_actions = None, report_type = 'daily', weekly_days = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487836561-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487839621-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487842461-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487845531-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487848471-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487851591-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487854531-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487857481-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487860421-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487863361-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487866281-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487869341-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487872321-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487875591-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487878761-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487882611-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487887241-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487891711-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487895411-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487898371-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487901321-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487904231-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487907191-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487910101-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487913071-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487916011-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487923631-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487929431-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487932421-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487941391-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487944351-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487947331-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487951941-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487955061-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487957981-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487961471-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487964461-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487967361-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487970271-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487973211-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487976111-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487979111-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487982001-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487984991-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487987891-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487994900-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.487998000-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488001070-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488003970-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488006870-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488009780-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488012700-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488015650-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488018620-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488021580-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488024500-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488027430-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488030420-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488033460-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488036390-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488039400-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488042340-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488045290-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488048280-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488051270-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488054250-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488057200-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488060190-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488063120-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488066050-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488073840-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488076860-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488079810-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488082760-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488085670-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488088550-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488091430-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488094380-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488097270-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488100350-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488103270-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488106170-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488109130-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488112000-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488114940-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488117890-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488120820-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488123820-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488126780-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488129700-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488132590-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488135520-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488138390-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488141280-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488144150-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488147120-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488150030-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488152950-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488155860-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488158780-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488161930-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488164950-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488167930-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488170890-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488173820-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488176770-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488190980-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488193950-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488196880-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488199790-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488202760-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488205700-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488209560-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488215660-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488218660-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488221610-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488224590-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488227550-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488230500-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488233430-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488236360-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488239259-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488242179-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488245129-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488248059-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488250999-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488253899-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488256819-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488259749-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488262679-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488265659-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488268549-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488271519-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488274429-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488277479-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488280489-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488283399-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488286319-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488289259-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488292149-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488295129-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488298709-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488301679-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488304609-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488307519-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488310459-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488313409-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488316389-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488319279-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488327229-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488330329-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488333239-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488336139-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488339039-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488341989-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488344979-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488347869-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488350759-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488353689-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488356579-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488359509-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488362419-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488365369-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488368469-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488371569-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488374479-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488377469-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488380419-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488383329-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488386319-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488389269-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488392129-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488395049-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488397939-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488402099-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488405159-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488408089-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488410979-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488413869-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488416779-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488419759-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488422799-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488425739-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488429759-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488432809-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488438479-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488441609-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488449869-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488452959-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488455889-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488458809-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488462429-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488465379-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488468499-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488471479-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488474359-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488478109-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488481068-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488484018-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488486978-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488489918-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488493728-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488496708-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488499688-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488502768-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488505738-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488508668-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488511578-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488514478-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488517378-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488520458-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488523468-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488526478-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488529448-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488532368-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488535288-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488538198-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488541138-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488544078-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488546968-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488549878-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488552798-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488555748-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488558678-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488561598-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488569738-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488572848-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488575798-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488578698-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488581608-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488584498-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488587378-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488590298-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488593218-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488596138-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488598998-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488601948-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488605198-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488609188-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488612158-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488615108-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488617998-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488620898-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488623838-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488626768-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488629718-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488632698-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488635618-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488638548-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488641788-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488647438-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488651848-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488654898-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488657858-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488660758-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488663698-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488666598-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488669488-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488672408-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488675208-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488678878-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError@pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488686768-05:00 def daily_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488689788-05:00 > return _run("daily") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488692718-05:00 ^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488695518-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488698428-05:00 analytics/tests/test_report_traits_live.py:94: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488701438-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488704318-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488707278-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488710238-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488713118-05:00 analytics/src/storeminder/runner.py:339: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488716048-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488719028-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488722388-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488725317-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488728307-05:00 analytics/src/storeminder/crew.py:464: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488731257-05:00 report_result = _agent.run_daily( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488734237-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1055: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488737547-05:00 return self.run(report_type="daily", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488740517-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488743447-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488746327-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488749277-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488752067-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488755057-05:00 self = <storeminder.agents.synthesis_agent.SynthesisAgent object at 0x70ca784bb690> [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488758017-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488761127-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488764617-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488767617-05:00 baseline = {'sample_count': 15, 'day_of_week': 4, 'total_revenue': 16270.22, 'total_transactions': 590.87, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488770557-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488773537-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...nd signage visibility overnight — especially between 21:00–23:00 | Measure: Operational status and customer access log' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488776557-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal Tuesday)** \nTobacco / Nicotine surged on strong Marlb...ause: Zero sales on $1.2k+ baseline cigars — high OOS risk | Measure: Inventory verification, urgent reorder if needed' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488780387-05:00 weather = {'yesterday': {'temp_max': 90.1, 'temp_min': 76.8, 'precip_in': 0.0, 'conditions': 'Partly cloudy'}, 'today_forecast':...rtly cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488783337-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...:** \n- Audit freshness and prep standards for top-dropped food items (1 Fish, Egg Roll, Breast) — Do by EOD tomorrow' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488791037-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...re success of Red Bull Original and SF Juneberry with team to reinforce effective selling tactics — Do by end of shift' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488796777-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.10 (+2.3% vs normal Fri...Daily | Monitor P90 service times during peak PM | Because: Slower top-decile service | Measure: Sustain avg under 65s' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488799837-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...te if either drops below $3.390 \n- Update price-volume tracking system by 2026-07-12 to restore correlation analysis' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488802797-05:00 prior_actions = None, report_type = 'daily', weekly_days = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488805737-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488808757-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488811587-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488814557-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488817507-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488821097-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488824047-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488827007-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488830087-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488833117-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488836897-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488839887-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488842897-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488845847-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488848907-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488851887-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488854897-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488857857-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488860767-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488864157-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488867077-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488869977-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488872877-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488875827-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488878817-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488881717-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488884667-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488892067-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488895057-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488897977-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488900867-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488903737-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488906617-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488909557-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488912437-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488915377-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488927327-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488932397-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488935447-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488938427-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488941387-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488944287-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488947187-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488950117-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488953647-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488956677-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488959617-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488962537-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488965406-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488968326-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488971266-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488974176-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488977036-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488979956-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488982896-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488985786-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488988706-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488991716-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.488998786-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489002796-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489005966-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489008956-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489011856-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489014826-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489017856-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489020846-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489023786-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489031696-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489034646-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489037526-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489040406-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489043276-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489046216-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489049216-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489052166-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489055066-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489058046-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489060966-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489063896-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489066926-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489069886-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489072806-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489075696-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489078596-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489081576-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489084536-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489087466-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489090386-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489093306-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489096196-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489100126-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489103096-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489105996-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489108886-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489111806-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489114696-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489117616-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489120536-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489123456-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489126366-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489129376-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489132886-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489140436-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489143506-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489146496-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489149446-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489152346-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489155266-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489158336-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489161256-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489164136-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489167026-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489169946-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489172966-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489175876-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489178786-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489181716-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489184606-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489187576-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489190586-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489193516-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489196535-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489199425-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489202435-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489205375-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489208385-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489211315-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489214275-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489219865-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489222875-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489225875-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489228835-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489231885-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489234785-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489237795-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489240775-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489243865-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489246875-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489249795-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489252705-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489255605-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489258605-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489261785-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489264905-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489267875-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489270795-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489278425-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489281365-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489284275-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489287265-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489290185-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489293095-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489295975-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489298915-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489301885-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489304855-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489307745-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489310665-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489313575-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489316505-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489319485-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489322375-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489325295-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489328205-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489331125-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489334065-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489337035-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489339925-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489342915-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489345815-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489348735-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489351695-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489354655-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489357585-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489360585-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489363605-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489366575-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489369535-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489372425-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489375365-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489379015-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489381975-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489384905-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489387955-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489395645-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489398685-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489401645-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489404595-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489407495-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489410395-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489413765-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489416705-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489419625-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489422615-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489425585-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489438864-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489446014-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489449174-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489451814-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489454454-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489456854-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489459444-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489461874-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489464224-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489471144-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489474114-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489476544-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489479364-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489482294-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489485314-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489488224-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489491124-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489494114-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489497504-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489500434-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489503304-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489506164-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489509164-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489512064-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489514964-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489517874-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489530894-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489534054-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489537004-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489539914-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489542804-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489546054-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489549074-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489551994-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489555024-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489557974-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489560854-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489563834-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489566754-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489569724-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489572654-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489575634-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489578634-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489581574-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489584474-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489587424-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489590314-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489593274-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489596184-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489599094-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489602054-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489605004-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489607924-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489610854-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489613724-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489616644-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489619544-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489622554-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489625484-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489628374-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489631274-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489634184-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489637064-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489639954-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489642794-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489646204-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError@pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489654844-05:00 def daily_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489657834-05:00 > return _run("daily") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489660753-05:00 ^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489663563-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489667133-05:00 analytics/tests/test_report_traits_live.py:94: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489673413-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489677573-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489680623-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489683563-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489686463-05:00 analytics/src/storeminder/runner.py:339: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489689423-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489692283-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489695193-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489698093-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489700993-05:00 analytics/src/storeminder/crew.py:464: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489703943-05:00 report_result = _agent.run_daily( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489706833-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1055: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489709703-05:00 return self.run(report_type="daily", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489712643-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489715533-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489718433-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489721343-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489724143-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489727113-05:00 self = <storeminder.agents.synthesis_agent.SynthesisAgent object at 0x70ca784bb690> [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489730173-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489733403-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489736353-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489740093-05:00 baseline = {'sample_count': 15, 'day_of_week': 4, 'total_revenue': 16270.22, 'total_transactions': 590.87, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489743403-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489746513-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...nd signage visibility overnight — especially between 21:00–23:00 | Measure: Operational status and customer access log' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489749543-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal Tuesday)** \nTobacco / Nicotine surged on strong Marlb...ause: Zero sales on $1.2k+ baseline cigars — high OOS risk | Measure: Inventory verification, urgent reorder if needed' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489757423-05:00 weather = {'yesterday': {'temp_max': 90.1, 'temp_min': 76.8, 'precip_in': 0.0, 'conditions': 'Partly cloudy'}, 'today_forecast':...rtly cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489760763-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...:** \n- Audit freshness and prep standards for top-dropped food items (1 Fish, Egg Roll, Breast) — Do by EOD tomorrow' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489763753-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...re success of Red Bull Original and SF Juneberry with team to reinforce effective selling tactics — Do by end of shift' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489766683-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.10 (+2.3% vs normal Fri...Daily | Monitor P90 service times during peak PM | Because: Slower top-decile service | Measure: Sustain avg under 65s' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489769643-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...te if either drops below $3.390 \n- Update price-volume tracking system by 2026-07-12 to restore correlation analysis' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489772593-05:00 prior_actions = None, report_type = 'daily', weekly_days = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489775503-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489778673-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489781523-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489784443-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489787353-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489790283-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489793293-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489796913-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489799883-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489805983-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489809033-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489811973-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489814863-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489817973-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489820983-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489823863-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489826813-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489829793-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489832783-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489835753-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489838653-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489841573-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489844463-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489852483-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489855473-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489858383-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489861353-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489864373-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489868033-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489871153-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489874103-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489876973-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489879923-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489882883-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489885773-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489888683-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489891602-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489894482-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489897462-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489900362-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489903222-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489906222-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489909112-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489912062-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489914982-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489926822-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489933142-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489936112-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489939002-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489941932-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489944822-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489947722-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489950602-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489953512-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489956432-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489959282-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489962142-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489965102-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489968052-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489971192-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489974102-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489976962-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489979882-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489982832-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489990762-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489993772-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489996742-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.489999652-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490002622-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490005492-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490008432-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490011282-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490014192-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490017112-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490020002-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490022892-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490025802-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490028732-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490031802-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490037962-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490041052-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490044062-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490046982-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490049902-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490052782-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490055762-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490058692-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490061552-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490064532-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490067422-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490070302-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490073312-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490076182-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490079052-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490081922-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490084832-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490087762-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490090642-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490093512-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490101192-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490104282-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490107292-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490110222-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490113122-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490116752-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490120402-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490123402-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490126382-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490129322-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490132302-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490135201-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490138101-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490141091-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490144681-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490147611-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490150561-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490153581-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490156551-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490159481-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490162381-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490165361-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490169381-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490172311-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490175281-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490178181-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490181121-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490184151-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490187061-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490190011-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490192961-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490196001-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490198881-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490201841-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490204721-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490207731-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490210641-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490213571-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490216481-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490219471-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490222361-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490225271-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490228251-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490236091-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490239051-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490242081-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490245001-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490247931-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490253791-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490256851-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490259881-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490262891-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490265831-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490268731-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490271611-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490274641-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490277611-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490280531-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490283511-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490286371-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490289321-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490292871-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490295781-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490298741-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490301641-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490304581-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490307451-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490310411-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490313431-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490316381-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490319271-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490322191-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490325131-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490328061-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490331011-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490333971-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490336961-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490339891-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490342791-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490345661-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490348591-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490351481-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490358901-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490361861-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490364881-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490367831-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490370841-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490373811-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490377420-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490380420-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490383370-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490386270-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490389220-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490392150-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490395120-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490398080-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490401060-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490404020-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490406990-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490409880-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490412870-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490415810-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490418820-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490421840-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490424780-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490427830-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490431460-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490434420-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490437440-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490440370-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490443260-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490446650-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490452260-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490455340-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490458400-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490461420-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490463930-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490466220-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490468530-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490474900-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490477330-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490479650-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490481980-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490484350-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490486710-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490489110-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490491410-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490494560-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490498020-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490501810-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490505120-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490508210-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490511710-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490515630-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490519070-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490522340-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490525840-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490529130-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490532700-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490536240-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490539680-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490542050-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490544380-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490546690-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490549230-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490551540-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490553850-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490556240-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490558730-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490561030-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490563390-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490565690-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490568130-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490570450-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490572770-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490575110-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490577440-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490579760-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490582070-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490588820-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490591090-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490593530-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError@pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490596020-05:00 def daily_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490598400-05:00 > return _run("daily") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490600730-05:00 ^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490603000-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490605310-05:00 analytics/tests/test_report_traits_live.py:94: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490607630-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490610180-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490612530-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490614920-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490617350-05:00 analytics/src/storeminder/runner.py:339: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490620469-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490623049-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490625479-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490627799-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490630199-05:00 analytics/src/storeminder/crew.py:464: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490632579-05:00 report_result = _agent.run_daily( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490638209-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1055: in run_daily [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490640629-05:00 return self.run(report_type="daily", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490643069-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490645439-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490647729-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490650149-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490652369-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490654699-05:00 self = <storeminder.agents.synthesis_agent.SynthesisAgent object at 0x70ca784bb690> [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490657079-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490659499-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490661909-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490664349-05:00 baseline = {'sample_count': 15, 'day_of_week': 4, 'total_revenue': 16270.22, 'total_transactions': 590.87, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490666779-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490669169-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...nd signage visibility overnight — especially between 21:00–23:00 | Measure: Operational status and customer access log' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490671569-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal Tuesday)** \nTobacco / Nicotine surged on strong Marlb...ause: Zero sales on $1.2k+ baseline cigars — high OOS risk | Measure: Inventory verification, urgent reorder if needed' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490679069-05:00 weather = {'yesterday': {'temp_max': 90.1, 'temp_min': 76.8, 'precip_in': 0.0, 'conditions': 'Partly cloudy'}, 'today_forecast':...rtly cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490681499-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...:** \n- Audit freshness and prep standards for top-dropped food items (1 Fish, Egg Roll, Breast) — Do by EOD tomorrow' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490683869-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...re success of Red Bull Original and SF Juneberry with team to reinforce effective selling tactics — Do by end of shift' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490686179-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.10 (+2.3% vs normal Fri...Daily | Monitor P90 service times during peak PM | Because: Slower top-decile service | Measure: Sustain avg under 65s' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490688569-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...te if either drops below $3.390 \n- Update price-volume tracking system by 2026-07-12 to restore correlation analysis' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490690989-05:00 prior_actions = None, report_type = 'daily', weekly_days = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490693329-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490695789-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490698039-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490700449-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490702769-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490705079-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490708029-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490710329-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490712629-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490714979-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490717289-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490719629-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490721969-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490724309-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490726689-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490729069-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490731489-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490733899-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490738819-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490741279-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490743609-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490750239-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490752599-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490754909-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490757219-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490759669-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490762129-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490764739-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490767069-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490769349-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490771639-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490773939-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490776239-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490778599-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490781019-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490783329-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490785999-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490788319-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490790719-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490793039-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490795339-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490797719-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490800069-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490802449-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490804769-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490807109-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490809429-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490811759-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490814089-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490816399-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490818759-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490821069-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490824329-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490827069-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490829489-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490831809-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490834779-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490837159-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490839549-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490841929-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490848189-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490850549-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490852859-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490855209-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490857569-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490860039-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490862599-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490864968-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490867298-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490869598-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490871898-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490874188-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490876498-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490878828-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490881178-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490883568-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490885948-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490888268-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490890588-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490892968-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490895368-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490897738-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490900178-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490902478-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490904808-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490907148-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490909468-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490934008-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490945838-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490949458-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490952648-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490961428-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490964608-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490967558-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490970528-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490973408-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490976408-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490987328-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490990348-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490993348-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490996418-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.490999558-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491002608-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491005518-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491008438-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491011398-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491014328-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491017418-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491020468-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491023348-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491026238-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491029138-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491032148-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491035518-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491038458-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491041388-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491044338-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491047278-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491050208-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491053088-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491056018-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491059008-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491061918-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491064888-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491067788-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491070688-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491073568-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491076488-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491079448-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491082398-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491085288-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491088218-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491091168-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491094058-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491097147-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491100087-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491103057-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491105987-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491108927-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491111857-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491119867-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491122927-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491125897-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491128837-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491131797-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491134707-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491137657-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491140587-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491143477-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491146507-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491149397-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491152297-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491155247-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491158137-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491161077-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491164027-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491166997-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491170017-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491172967-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491175907-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491178877-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491181787-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491185597-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491188607-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491195337-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491198357-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491201417-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491204367-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491207337-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491210947-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491213887-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491216797-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491219697-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491222667-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491225547-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491228447-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491231427-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491234307-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491237267-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491244877-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491247887-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491250767-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491253647-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491256727-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491259687-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491262657-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491265647-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491268567-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491271447-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491274347-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491277237-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491280167-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491283087-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491285987-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491288917-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491291797-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491294687-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491297637-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491300547-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491303487-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491306437-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491309457-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491312387-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491315287-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491318277-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491321237-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491324147-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491327087-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491329986-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491332926-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491335816-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491338696-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491376616-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491382476-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491390376-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491408916-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491412086-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491415026-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491417956-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491421516-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491424436-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491427366-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491430346-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491433306-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491436216-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491443186-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491446256-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491449306-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491452246-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491455256-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491458206-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491461186-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491464106-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491467026-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491470176-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491473086-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491476026-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491478936-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491481906-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491484856-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491487836-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491490746-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491493646-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491496556-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491499476-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491502416-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491505356-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491508246-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491511116-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491514086-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491516946-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491519846-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491522756-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491525676-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491533526-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491536516-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491539426-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491542396-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491545286-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491548096-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491551226-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError@pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491554186-05:00 def weekly_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491557146-05:00 > return _run("weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491560096-05:00 ^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491562885-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491565845-05:00 analytics/tests/test_report_traits_live.py:99: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491569775-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491572765-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491575685-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491578605-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491581565-05:00 analytics/src/storeminder/runner.py:358: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491584545-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491587525-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491591495-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491594495-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491597495-05:00 analytics/src/storeminder/crew.py:457: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491600415-05:00 report_result = _agent.run_weekly( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491603425-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1060: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491606375-05:00 return self.run(report_type="weekly", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491609275-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491612215-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491615115-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491618065-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491620875-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491624195-05:00 self = <storeminder.agents.synthesis_agent.SynthesisAgent object at 0x70ca785497d0> [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491627155-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491630265-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491633235-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491636175-05:00 baseline = {'day_of_week': 21, 'total_revenue': 100081.6, 'total_transactions': 3568.94, 'fuel_gallons': 16961.39, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491641905-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491650095-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...d signage visibility — ensure no operational blockage | Measure: Evening fuel volume tomorrow vs normal Friday evening' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491653435-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal week)** \nTobacco/Nicotine surged, but Foodservice und...eout | review void log and pricing overrides | Because: 100% above baseline voids | Measure: zero unapproved overrides' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491656395-05:00 weather = {'yesterday': {'temp_max': 88.3, 'temp_min': 75.4, 'precip_in': 0.0, 'conditions': 'Clear'}, 'today_forecast': {'temp_...cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491659435-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...Roll, Breast) — Do by EOD \n- Cross-check staff schedule against POS drop timing to assess service impact — Do by EOD' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491662315-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...omorrow \n- Consider increasing signage for Copenhagen Mint Nicotine, which is below average despite active promotion' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491665175-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.12 (+8% vs normal Frida...g highest-margin window.* \n\nNo additional hours recommended — optimize existing FTE through outcome-aligned timing.' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491668125-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...petitor within 1 mile adjusts Regular below $3.450. \n\n*Note: No competitor has updated prices in the last 3 hours.*' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491671005-05:00 prior_actions = None, report_type = 'weekly' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491674025-05:00 weekly_days = [{'date': '2026-07-04', 'dow_name': 'Saturday', 'total_revenue': 7392.12, 'fuel_gallons': 1142.1, ...}, {'date': '2026...42.4, ...}, {'date': '2026-07-09', 'dow_name': 'Thursday', 'total_revenue': 16227.6, 'fuel_gallons': 2780.6, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491677015-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491680055-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491682885-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491685805-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491688735-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491691745-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491694725-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491698265-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491701195-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491704075-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491707005-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491709995-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491712945-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491715925-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491719705-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491722655-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491733355-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491737555-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491741155-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491744065-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491746965-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491749835-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491752905-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491755805-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491758645-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491761555-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491764545-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491770215-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491773225-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491776155-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491779105-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491782065-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491784925-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491787825-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491790695-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491793604-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491796564-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491799474-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491802354-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491805214-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491808164-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491811384-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491814304-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491817174-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491820054-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491823014-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491825934-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491828784-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491831734-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491834644-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491837544-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491840404-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491851554-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491854604-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491857544-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491860414-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491863304-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491866334-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491869314-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491872264-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491875194-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491878124-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491881084-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491883994-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491886884-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491889814-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491892754-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491895704-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491898584-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491901494-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491904384-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491907244-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491910184-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491913124-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491916084-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491929154-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491936114-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491939114-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491942054-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491945074-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491948034-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491951674-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491954544-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491957494-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491966494-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491969514-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491972514-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491975514-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491978454-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491981354-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491984224-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491987174-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.491995244-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492001574-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492004584-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492014044-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492017064-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492019974-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492022954-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492025904-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492028904-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492031843-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492034763-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492037733-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492041323-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492044283-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492047183-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492050073-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492053063-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492055993-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492058893-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492061783-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492064703-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492067693-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492070593-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492073533-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492076473-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492079443-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492082363-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492085243-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492088173-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492091113-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492094013-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492096933-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492099833-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492102723-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492105633-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492108523-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492111463-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492114433-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492117373-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492120273-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492123783-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492126733-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492157973-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492164033-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492167033-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492170073-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492172993-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492175973-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492178923-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492181923-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492184883-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492187933-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492190913-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492193893-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492196843-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492199753-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492202653-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492205653-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492208543-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492211453-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492214423-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492217313-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492220663-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492223593-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492226483-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492229493-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492232403-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492235293-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492238273-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492241183-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492244163-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492247123-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492250113-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492253023-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492255963-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492258893-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492265343-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492268353-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492271273-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492274272-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492277202-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492284822-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492287892-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492290912-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492293852-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492296832-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492300592-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492303542-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492306542-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492309502-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492312432-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492315492-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492318432-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492321382-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492324392-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492327282-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492330522-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492333502-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492336492-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492339482-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492342432-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492345392-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492348312-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492351182-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492354142-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492357082-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492359992-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492362942-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492365872-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492368902-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492371832-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492374792-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492377772-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492380712-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492383672-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492386702-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492389602-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492392662-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492400422-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492403432-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492406312-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492409892-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492412762-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492415692-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492418622-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492421522-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492424462-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492427382-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492430292-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492433432-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492436392-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492439322-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492442252-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492445172-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492448122-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492451082-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492454002-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492459772-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492462812-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492465812-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492468762-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492471682-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492474682-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492477592-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492480502-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492483482-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492486432-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492489392-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492492342-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492495222-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492498122-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492501042-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492503932-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492506832-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492509772-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492512692-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492515551-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492523181-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492526091-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492529071-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492531971-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492535001-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492537931-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492540911-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492543961-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492546941-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492549841-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492552651-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492555771-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError@pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492558741-05:00 def weekly_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492561731-05:00 > return _run("weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492564711-05:00 ^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492567561-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492570471-05:00 analytics/tests/test_report_traits_live.py:99: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492573911-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492577041-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492580051-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492583021-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492586561-05:00 analytics/src/storeminder/runner.py:358: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492589511-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492592411-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492595391-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492598361-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492601321-05:00 analytics/src/storeminder/crew.py:457: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492604221-05:00 report_result = _agent.run_weekly( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492607111-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1060: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492610041-05:00 return self.run(report_type="weekly", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492612931-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492615871-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492618781-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492621691-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492624511-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492628121-05:00 self = <storeminder.agents.synthesis_agent.SynthesisAgent object at 0x70ca785497d0> [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492631031-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492634051-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492641881-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492644801-05:00 baseline = {'day_of_week': 21, 'total_revenue': 100081.6, 'total_transactions': 3568.94, 'fuel_gallons': 16961.39, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492647771-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492665971-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...d signage visibility — ensure no operational blockage | Measure: Evening fuel volume tomorrow vs normal Friday evening' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492679251-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal week)** \nTobacco/Nicotine surged, but Foodservice und...eout | review void log and pricing overrides | Because: 100% above baseline voids | Measure: zero unapproved overrides' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492689261-05:00 weather = {'yesterday': {'temp_max': 88.3, 'temp_min': 75.4, 'precip_in': 0.0, 'conditions': 'Clear'}, 'today_forecast': {'temp_...cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492694181-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...Roll, Breast) — Do by EOD \n- Cross-check staff schedule against POS drop timing to assess service impact — Do by EOD' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492698621-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...omorrow \n- Consider increasing signage for Copenhagen Mint Nicotine, which is below average despite active promotion' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492701591-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.12 (+8% vs normal Frida...g highest-margin window.* \n\nNo additional hours recommended — optimize existing FTE through outcome-aligned timing.' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492704481-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...petitor within 1 mile adjusts Regular below $3.450. \n\n*Note: No competitor has updated prices in the last 3 hours.*' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492708321-05:00 prior_actions = None, report_type = 'weekly' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492711261-05:00 weekly_days = [{'date': '2026-07-04', 'dow_name': 'Saturday', 'total_revenue': 7392.12, 'fuel_gallons': 1142.1, ...}, {'date': '2026...42.4, ...}, {'date': '2026-07-09', 'dow_name': 'Thursday', 'total_revenue': 16227.6, 'fuel_gallons': 2780.6, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492714561-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492717991-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492720791-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492724251-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492727611-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492731221-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492734251-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492737201-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492740171-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492743181-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492755031-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492758670-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492761650-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492766050-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492770480-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492773510-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492776440-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492780240-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492783260-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492786190-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492789110-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492791980-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492794830-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492797680-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492800540-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492803490-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492806560-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492809510-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492812420-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492815340-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492818200-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492821100-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492823960-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492827000-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492829950-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492832850-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492835890-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492843170-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492847580-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492852130-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492855730-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492858710-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492861670-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492864560-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492867460-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492870340-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492879080-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492882110-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492885060-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492888070-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492891040-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492893950-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492896850-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492899830-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492902770-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492905730-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492908700-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492911720-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492914750-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492927910-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492936640-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492939610-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492942610-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492945600-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492948600-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492951840-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492954760-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492957730-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492960630-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492963530-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492966430-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492969320-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492972330-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492975240-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492978140-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492981040-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492983920-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492986810-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492989790-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492992770-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492995770-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.492998789-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493001719-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493004609-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493007529-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493010469-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493013379-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493016319-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493024909-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493027949-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493030819-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493033679-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493036549-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493039429-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493042329-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493045199-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493048129-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493051019-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493053909-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493056789-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493061329-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493064389-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493067359-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493070299-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493073279-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493080999-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493086169-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493090499-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493094159-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493097139-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493100079-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493103029-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493105999-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493109259-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493112189-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493115119-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493118029-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493120979-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493123889-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493126829-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493129769-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493132649-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493135579-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493138789-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493141689-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493144569-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493147449-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493150439-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493158869-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493161899-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493164879-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493167789-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493170759-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493173649-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493178869-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493182029-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493184979-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493187909-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493190839-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493193739-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493196639-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493199619-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493202569-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493205599-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493208529-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493211409-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493214309-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493217219-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493220089-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493223049-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493225959-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493228909-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493231828-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493234748-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493237758-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493240738-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493243648-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493246558-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493249498-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493252458-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493255388-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493258338-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493261298-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493264278-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493267258-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493270188-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493273288-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493276318-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493279278-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493287188-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493290148-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493293098-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493295998-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493298908-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493301798-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493304788-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493307698-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493310598-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493313478-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493316338-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493323198-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493327628-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493332118-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493335768-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493338738-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493341678-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493344578-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493347498-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493350418-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493353368-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493356308-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493359308-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493362228-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493365128-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493368018-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493370948-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493373808-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493376718-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493379668-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493382578-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493385478-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493388428-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493391378-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493394288-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493397208-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493405398-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493408478-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493411348-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493414258-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493417178-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493420058-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493422958-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493425808-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493429488-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493432478-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493435438-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493438978-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493441868-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493444818-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493447708-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493450588-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493453458-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493456378-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493459248-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493462157-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493465047-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493468007-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493470907-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493473797-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493476707-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493479627-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493482517-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493485427-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493488337-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493491277-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493494237-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493497107-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493500087-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493503047-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493505937-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493508807-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493511787-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493514677-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493517697-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493525347-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493532347-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493536927-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493541447-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493545217-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493548677-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493551627-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493554537-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493557447-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493560447-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493563367-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493566267-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493569137-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493572017-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493575437-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493578237-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493583227-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError@pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493586297-05:00 def weekly_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493589287-05:00 > return _run("weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493592237-05:00 ^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493595007-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493597867-05:00 analytics/tests/test_report_traits_live.py:99: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493600877-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493603777-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493607207-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493610207-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493613177-05:00 analytics/src/storeminder/runner.py:358: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493616077-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493619057-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493621997-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493624997-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493628157-05:00 analytics/src/storeminder/crew.py:457: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493631087-05:00 report_result = _agent.run_weekly( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493634017-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1060: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493636907-05:00 return self.run(report_type="weekly", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493639767-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493642727-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493645607-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493654227-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493657157-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493660177-05:00 self = <storeminder.agents.synthesis_agent.SynthesisAgent object at 0x70ca785497d0> [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493663177-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493667057-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493670037-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493673097-05:00 baseline = {'day_of_week': 21, 'total_revenue': 100081.6, 'total_transactions': 3568.94, 'fuel_gallons': 16961.39, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493676847-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493679847-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...d signage visibility — ensure no operational blockage | Measure: Evening fuel volume tomorrow vs normal Friday evening' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493682807-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal week)** \nTobacco/Nicotine surged, but Foodservice und...eout | review void log and pricing overrides | Because: 100% above baseline voids | Measure: zero unapproved overrides' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493686537-05:00 weather = {'yesterday': {'temp_max': 88.3, 'temp_min': 75.4, 'precip_in': 0.0, 'conditions': 'Clear'}, 'today_forecast': {'temp_...cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493693486-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...Roll, Breast) — Do by EOD \n- Cross-check staff schedule against POS drop timing to assess service impact — Do by EOD' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493697966-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...omorrow \n- Consider increasing signage for Copenhagen Mint Nicotine, which is below average despite active promotion' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493702366-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.12 (+8% vs normal Frida...g highest-margin window.* \n\nNo additional hours recommended — optimize existing FTE through outcome-aligned timing.' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493705926-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...petitor within 1 mile adjusts Regular below $3.450. \n\n*Note: No competitor has updated prices in the last 3 hours.*' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493708866-05:00 prior_actions = None, report_type = 'weekly' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493711806-05:00 weekly_days = [{'date': '2026-07-04', 'dow_name': 'Saturday', 'total_revenue': 7392.12, 'fuel_gallons': 1142.1, ...}, {'date': '2026...42.4, ...}, {'date': '2026-07-09', 'dow_name': 'Thursday', 'total_revenue': 16227.6, 'fuel_gallons': 2780.6, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493714756-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493717796-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493720566-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493723456-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493731886-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493734986-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493737916-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493740816-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493743776-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493746686-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493749576-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493752486-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493755456-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493758426-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493761426-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493764346-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493767326-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493770406-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493773326-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493776266-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493779176-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493782056-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493784936-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493787786-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493790806-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493793756-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493796646-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493799546-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493802436-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493805416-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493808326-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493811186-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493814046-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493816946-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493819856-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493822756-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493825656-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493828596-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493831536-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493834446-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493841886-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493848716-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493853286-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493857786-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493861356-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493864226-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493867116-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493869996-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493872996-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493875936-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493879596-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493882476-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493885336-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493888246-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493891196-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493894046-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493896996-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493900026-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493902966-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493905966-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493908916-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493911826-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493914766-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493923216-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493928525-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493931485-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493934465-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493937405-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493940305-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493943185-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493946135-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493949025-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493952035-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493955005-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493957925-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493960915-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493963815-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493966755-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493969675-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493972655-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493975635-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493978535-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493981425-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493990055-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493992985-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493995905-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.493998795-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494001725-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494004655-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494008105-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494011065-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494014055-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494017015-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494019925-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494022825-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494025695-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494028655-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494031535-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494034415-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494037325-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494040265-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494043195-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494046095-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494048995-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494051965-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494054885-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494057785-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494060745-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494063655-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494066605-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494073775-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494078355-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494083055-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494086785-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494089725-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494092635-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494095555-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494098455-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494101395-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494104345-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494112705-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494115735-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494118665-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494121575-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494124455-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494128005-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494131045-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494133995-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494136885-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494139775-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494142665-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494145605-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494148565-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494151425-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494154455-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494157385-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494160275-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494163235-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494166135-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494169054-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494171974-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494174954-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494177904-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494180864-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494183814-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494186734-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494189654-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494192604-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494195474-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494199204-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494202154-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494205084-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494207964-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494210824-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494213784-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494216734-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494219654-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494222564-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494225464-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494228384-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494231264-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494234164-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494242224-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494245244-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494248134-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494251044-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494253974-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494256904-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494259894-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494262824-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494265694-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494268634-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494271534-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494274414-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494277294-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494280204-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494283124-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494286004-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494288904-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494291774-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494294654-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494297544-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494300404-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494316434-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494322214-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494327044-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494331454-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494335404-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494341514-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494344604-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494347484-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494350474-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494353374-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494357094-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494359984-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494362974-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494365874-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494368814-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494371724-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494374664-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494377574-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494386494-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494389464-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494392384-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494395394-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494398394-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494401404-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494404394-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494407264-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494410144-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494413083-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494416013-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494419583-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494422593-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494425533-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494428393-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494431343-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494434373-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494437273-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494440233-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494443143-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494446763-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494449663-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494453183-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494456153-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494459143-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494462173-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494465273-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494468193-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494471113-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494473983-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494476923-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494479833-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494483793-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494486743-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494489713-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494492633-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494495613-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494503353-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494506273-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494509203-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494512123-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494514993-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494517933-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494520853-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494523763-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494526753-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494529663-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494532573-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494535483-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494538383-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494544253-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494547293-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494550203-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494553133-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494556013-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494558893-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494561763-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494564743-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494567633-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494570873-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError@pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494573853-05:00 def weekly_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494577343-05:00 > return _run("weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494580263-05:00 ^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494583083-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494585993-05:00 analytics/tests/test_report_traits_live.py:99: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494588953-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494591903-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494594883-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494597783-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494600663-05:00 analytics/src/storeminder/runner.py:358: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494603583-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494606453-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494609303-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494612183-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494615123-05:00 analytics/src/storeminder/crew.py:457: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494623013-05:00 report_result = _agent.run_weekly( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494625953-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1060: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494628903-05:00 return self.run(report_type="weekly", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494632453-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494635483-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494639743-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494642703-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494645543-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494648493-05:00 self = <storeminder.agents.synthesis_agent.SynthesisAgent object at 0x70ca785497d0> [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494651533-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494654482-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494657462-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494660432-05:00 baseline = {'day_of_week': 21, 'total_revenue': 100081.6, 'total_transactions': 3568.94, 'fuel_gallons': 16961.39, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494663462-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494666452-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...d signage visibility — ensure no operational blockage | Measure: Evening fuel volume tomorrow vs normal Friday evening' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494669432-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal week)** \nTobacco/Nicotine surged, but Foodservice und...eout | review void log and pricing overrides | Because: 100% above baseline voids | Measure: zero unapproved overrides' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494672402-05:00 weather = {'yesterday': {'temp_max': 88.3, 'temp_min': 75.4, 'precip_in': 0.0, 'conditions': 'Clear'}, 'today_forecast': {'temp_...cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494675332-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...Roll, Breast) — Do by EOD \n- Cross-check staff schedule against POS drop timing to assess service impact — Do by EOD' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494678232-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...omorrow \n- Consider increasing signage for Copenhagen Mint Nicotine, which is below average despite active promotion' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494683882-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.12 (+8% vs normal Frida...g highest-margin window.* \n\nNo additional hours recommended — optimize existing FTE through outcome-aligned timing.' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494686942-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...petitor within 1 mile adjusts Regular below $3.450. \n\n*Note: No competitor has updated prices in the last 3 hours.*' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494689892-05:00 prior_actions = None, report_type = 'weekly' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494692802-05:00 weekly_days = [{'date': '2026-07-04', 'dow_name': 'Saturday', 'total_revenue': 7392.12, 'fuel_gallons': 1142.1, ...}, {'date': '2026...42.4, ...}, {'date': '2026-07-09', 'dow_name': 'Thursday', 'total_revenue': 16227.6, 'fuel_gallons': 2780.6, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494699972-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494703032-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494705822-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494708692-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494711632-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494714582-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494717522-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494720482-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494723412-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494726302-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494729172-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494732052-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494734952-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494737862-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494741102-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494744122-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494747052-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494749982-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494752872-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494755752-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494758672-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494761572-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494764452-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494767322-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494770222-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494773142-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494776012-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494778892-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494781752-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494784632-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494787892-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494790792-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494793722-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494796672-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494803962-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494806942-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494809852-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494812742-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494815642-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494818542-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494821952-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494824862-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494827892-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494831402-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494834392-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494837282-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494840692-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494843662-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494846732-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494849632-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494852612-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494858202-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494861242-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494864172-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494867052-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494870042-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494872942-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494875842-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494878782-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494881712-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494884632-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494887532-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494890472-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494893452-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494896362-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494899301-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494902241-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494905121-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494908041-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494910921-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494913841-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494916761-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494927621-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494930611-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494933531-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494936411-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494939311-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494942301-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494950191-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494953241-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494956201-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494959441-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494962381-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494965331-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494968251-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494971221-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494974131-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494977111-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494980851-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494983711-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494986591-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494989451-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494992321-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494995181-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.494998061-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495000981-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495003881-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495006761-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495009651-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495012541-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495015611-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495018591-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495021511-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495024481-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495027431-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495030351-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495033261-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495036681-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495039651-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495042591-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495045521-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495048451-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495051401-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495059171-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495062171-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495065091-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495068061-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495070951-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495073891-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495076831-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495079731-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495082651-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495088451-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495091411-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495094361-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495097271-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495100201-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495103111-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495106011-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495108951-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495111901-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495114801-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495117721-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495120621-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495123611-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495126531-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495129441-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495132410-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495135330-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495138280-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495141180-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495144230-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495147240-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495150180-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495153290-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495156250-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495159170-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495162070-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495164940-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495167900-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495170860-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495173770-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495176690-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495179660-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495182690-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495185630-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495193420-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495196390-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495199490-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495202450-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495205880-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495208840-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495211740-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495214690-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495217590-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495220510-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495223490-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495226470-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495229340-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495232230-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495235120-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495237990-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495240870-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495243770-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495246790-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495249700-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495252630-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495255500-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495258370-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495261250-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495264130-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495267000-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495269980-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495272940-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495275890-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495278780-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495281750-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495285320-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495288270-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495291180-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495294110-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495297010-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495302570-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495305520-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495313520-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495316570-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495319480-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495322420-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495325300-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495328210-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495331130-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495334070-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495336980-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495339880-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495342810-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495345750-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495349280-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495352150-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495355070-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495357990-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495360920-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495363819-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495366679-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495369609-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495372489-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495375399-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495378269-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495381159-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495384109-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495386979-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495389909-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495392839-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495395739-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495398599-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495401489-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495404379-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495407299-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495410219-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495413139-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495416059-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495418959-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495426669-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495430199-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495433209-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495436119-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495438999-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495441909-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495444919-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495447819-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495450699-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495453589-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495456469-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495459349-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495462239-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495465149-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495468189-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495471139-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495474039-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495476929-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495479799-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495482759-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495485659-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495488569-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495491499-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495494409-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495497299-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495502779-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495505769-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495508689-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495511519-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495514499-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError@pytest.fixture(scope="module") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495517479-05:00 def weekly_report(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495520389-05:00 > return _run("weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495523289-05:00 ^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495526079-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495528999-05:00 analytics/tests/test_report_traits_live.py:99: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495531909-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495534779-05:00 analytics/tests/test_report_traits_live.py:75: in _run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495537769-05:00 res = fn(store, end_date=d, dry_run=True, deliver=False, clean=True) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495540819-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495548439-05:00 analytics/src/storeminder/runner.py:358: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495551359-05:00 return _run_report(params, clean=clean) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495554249-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495557189-05:00 analytics/src/storeminder/runner.py:192: in _run_report [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495560099-05:00 result = run_pipeline( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495563029-05:00 analytics/src/storeminder/crew.py:457: in run_pipeline [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495566539-05:00 report_result = _agent.run_weekly( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495569489-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1060: in run_weekly [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495572509-05:00 return self.run(report_type="weekly", **kwargs) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495575919-05:00 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495578899-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1105: in run [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495581779-05:00 user = self._build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495584719-05:00 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495587529-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495590439-05:00 self = <storeminder.agents.synthesis_agent.SynthesisAgent object at 0x70ca785497d0> [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495593349-05:00 report_date = datetime.date(2026, 7, 10), store_id = 'Fastime' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495596328-05:00 trans = {'total_transactions': 618, 'open_hour_start': 4, 'open_hour_end': 21, 'report_date_filter': '2026-07-10', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495599238-05:00 scan = {'total_scan_rows': 1940, 'report_date_filter': '2026-07-10', 'total_sales': 6436.97, 'avg_item_price': 3.88, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495602128-05:00 baseline = {'day_of_week': 21, 'total_revenue': 100081.6, 'total_transactions': 3568.94, 'fuel_gallons': 16961.39, ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495605018-05:00 flags = [{'metric': 'rank_drop', 'label': 'Rank Drop: COPENHAGEN FINE CUT', 'today': 40, 'baseline': 13, ...}, {'metric': 'ran...': 29, ...}, {'metric': 'rank_drop', 'label': 'Rank Drop: BUD LIGHT BEER 12 O', 'today': 33, 'baseline': 22, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495607988-05:00 fuel_section = '## 2. Fuel \n**2,583 gal | $9,446 | 279 fuel transactions** \nFuel volume came in at 2,583 gallons, down from 2,758...d signage visibility — ensure no operational blockage | Measure: Evening fuel volume tomorrow vs normal Friday evening' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495610958-05:00 inside_section = '## 3. Inside Sales\n\n**Inside Sales: $6,437 (+9.5% vs normal week)** \nTobacco/Nicotine surged, but Foodservice und...eout | review void log and pricing overrides | Because: 100% above baseline voids | Measure: zero unapproved overrides' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495614518-05:00 weather = {'yesterday': {'temp_max': 88.3, 'temp_min': 75.4, 'precip_in': 0.0, 'conditions': 'Clear'}, 'today_forecast': {'temp_...cloudy'}, 'rain_hours_today': [], 'weather_impact_assessment': 'Low — weather was mild, not a significant factor', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495618478-05:00 external_section = '## External Context\n\n**Google Reviews (5 reviews)** \nAverage rating of recent reviews (3.0/5) is below overall st...Roll, Breast) — Do by EOD \n- Cross-check staff schedule against POS drop timing to assess service impact — Do by EOD' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495621478-05:00 loyalty_section = '## Loyalty & Promotions\n\n**Redemption Summary**\n| Promotion | Item | Today | Period Avg | Delta | Status |\n|-----...omorrow \n- Consider increasing signage for Copenhagen Mint Nicotine, which is below average despite active promotion' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495624428-05:00 ops_section = '## 5. Operations\n\n**Transactions & Throughput** \n962 total transactions | Avg ticket: $17.12 (+8% vs normal Frida...g highest-margin window.* \n\nNo additional hours recommended — optimize existing FTE through outcome-aligned timing.' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495634728-05:00 fuel_pricing_section = '## Fuel Pricing\n\n**Current Market** \n| Station | Regular | Midgrade | Premium | Diesel | Distance | \n|---------...petitor within 1 mile adjusts Regular below $3.450. \n\n*Note: No competitor has updated prices in the last 3 hours.*' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495637778-05:00 prior_actions = None, report_type = 'weekly' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495640748-05:00 weekly_days = [{'date': '2026-07-04', 'dow_name': 'Saturday', 'total_revenue': 7392.12, 'fuel_gallons': 1142.1, ...}, {'date': '2026...42.4, ...}, {'date': '2026-07-09', 'dow_name': 'Thursday', 'total_revenue': 16227.6, 'fuel_gallons': 2780.6, ...}, ...] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495643738-05:00 dow_profile = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495646688-05:00 kwargs = {'category_baselines': None, 'schedule_section': None, 'weekly_category_totals': None, 'ext_fetch_date': '2026-07-13', ...} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495649488-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495652348-05:00 def _build_user_prompt( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495655238-05:00 self, report_date, store_id, trans, scan, baseline, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495658198-05:00 flags, fuel_section, inside_section, weather, external_section, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495661098-05:00 loyalty_section, ops_section, fuel_pricing_section, prior_actions, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495663988-05:00 report_type: str = "daily", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495666948-05:00 weekly_days: list[dict] | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495669898-05:00 dow_profile: dict | None = None, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495672808-05:00 **kwargs, [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495675688-05:00 ) -> str: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495679118-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495682148-05:00 # ── KPI table ───────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495685178-05:00 # WEEKLY MODE: trans_summary and scan_summary contain only the last day's [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495688128-05:00 # data (the final preprocessed frame). For a weekly report the KPI table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495691128-05:00 # must show 7-day week totals vs the weekly baseline — not one day vs a [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495694308-05:00 # 7-day benchmark (which is the bug that produced "+10% fuel gallons" when [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495697168-05:00 # gallons were actually -7%). When weekly_days is available, aggregate the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495700028-05:00 # totals here in Python so the model sees correct pre-computed numbers [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495702918-05:00 # and cannot hallucinate them from a mismatched prompt. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495705798-05:00 _is_weekly = (report_type == "weekly") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495708708-05:00 _kpi_source: dict[str, float] | None = None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495711578-05:00 if _is_weekly and weekly_days: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495714478-05:00 _wt: dict[str, float] = {} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495717728-05:00 _wt['total_revenue'] = sum(d.get('total_revenue', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495720618-05:00 _wt['total_sales'] = sum(d.get('total_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495723508-05:00 _wt['fuel_gallons'] = sum(d.get('fuel_gallons', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495726388-05:00 _wt['fuel_sales'] = sum(d.get('fuel_sales', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495729248-05:00 _wt['fuel_transactions'] = sum(d.get('fuel_transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495736868-05:00 _wt['total_transactions']= sum(d.get('transactions', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495739898-05:00 _wt['void_count'] = sum(d.get('void_count', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495742848-05:00 _wt['void_total_amt'] = sum(d.get('void_total_amt', 0) for d in weekly_days) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495745758-05:00 # Derived ratios — recomputed from week totals so they're correct [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495748698-05:00 if _wt['fuel_gallons'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495751588-05:00 _wt['inside_per_100_gal'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495754468-05:00 _wt['total_sales'] / (_wt['fuel_gallons'] / 100), 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495757348-05:00 _wt['effective_fuel_price'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495760278-05:00 _wt['fuel_sales'] / _wt['fuel_gallons'], 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495763198-05:00 if _wt['fuel_transactions']: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495766168-05:00 _wt['avg_gallons_per_tx'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495769038-05:00 _wt['fuel_gallons'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495771898-05:00 _wt['avg_fuel_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495774798-05:00 _wt['fuel_sales'] / _wt['fuel_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495777668-05:00 if _wt['total_transactions'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495780548-05:00 _wt['avg_ticket'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495783438-05:00 _wt['total_sales'] / _wt['total_transactions'], 2) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495786368-05:00 if _wt['total_revenue'] > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495789278-05:00 _wt['inside_sales_share_pct'] = round( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495792168-05:00 _wt['total_sales'] / _wt['total_revenue'] * 100, 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495795058-05:00 _kpi_source = _wt [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495797958-05:00 _kpi_label = "This Week" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495800808-05:00 _bench_label = "4-Wk Benchmark" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495803678-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495806548-05:00 _kpi_label = "Today" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495809398-05:00 _bench_label = "Baseline" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495812308-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495815278-05:00 kpi_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495825517-05:00 f"| Metric | {_kpi_label} | {_bench_label} | Delta | Priority |", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495828677-05:00 "|--------|------------|------------|-------|----------|", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495831577-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495834487-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495837387-05:00 # Keys that live in scan_summary (inside sales) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495840347-05:00 _scan_keys = {'total_sales', 'avg_item_price', 'total_scan_rows'} [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495843237-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495846167-05:00 def kpi_row(label, key, fmt_str=",.0f", prefix="", priority=None): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495849027-05:00 # Weekly: read from pre-computed week totals; daily: prefer scan for [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495851917-05:00 # inside-sales metrics, trans for everything else. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495854817-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495857757-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495861877-05:00 elif key in _scan_keys: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495864867-05:00 t = scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495872747-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495875747-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495878737-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495881647-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495884557-05:00 return [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495887517-05:00 t_str = f"{prefix}{t:{fmt_str}}" if isinstance(t, int | float) else str(t) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495890517-05:00 b_str = f"{prefix}{b:{fmt_str}}" if isinstance(b, int | float) else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495893457-05:00 delta = f"{((t-b)/b*100):+.1f}%" if isinstance(t, int | float) and isinstance(b, int | float) and b else "—" # noqa: E501 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495896367-05:00 # Priority: use flag severity if exists, else spec default [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495899267-05:00 flag = next((f for f in flags if f['metric'] == key), None) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495902897-05:00 pri = flag['severity'] if flag else (priority or "—") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495905767-05:00 kpi_lines.append(f"| {label} | {t_str} | {b_str} | {delta} | {pri} |") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495908667-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495911567-05:00 kpi_row("Total Sales", "total_revenue", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495914497-05:00 kpi_row("Inside Sales", "total_sales", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495922157-05:00 kpi_row("Fuel Gallons", "fuel_gallons", ",.1f", "", "HIGH") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495927077-05:00 kpi_row("Fuel Sales $", "fuel_sales", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495930007-05:00 kpi_row("Inside/100 Gal", "inside_per_100_gal", ",.0f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495932887-05:00 kpi_row("Eff. Fuel Price", "effective_fuel_price", ",.3f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495935767-05:00 kpi_row("Avg Inside Ticket", "avg_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495938707-05:00 kpi_row("Avg Fuel Ticket", "avg_fuel_ticket", ",.2f", "$", "MEDIUM") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495941627-05:00 kpi_row("Fuel Transactions", "fuel_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495944537-05:00 kpi_row("Total Transactions", "total_transactions", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495947697-05:00 kpi_row("Void Count", "void_count", ",.0f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495950577-05:00 kpi_row("Void Total $", "void_total_amt", ",.2f", "$", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495953427-05:00 kpi_row("Inside Share %", "inside_sales_share_pct", ",.1f", "", "LOW") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495956387-05:00 kpi_summary = "\n".join(kpi_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495959297-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495962307-05:00 # ── Derived metrics for snapshot header ──────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495965327-05:00 def _dm(key, label, prefix="", suffix="", decimals=2): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495968287-05:00 # Weekly: prefer pre-computed week totals; daily: use trans/scan [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495971197-05:00 if _kpi_source and key in _kpi_source: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495974087-05:00 t = _kpi_source.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495976987-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495979887-05:00 t = trans.get(key) if trans.get(key) is not None else scan.get(key) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495989947-05:00 b = baseline.get(key) if baseline else None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495992887-05:00 if t is None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495995797-05:00 return None [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.495998757-05:00 t_str = f"{prefix}{float(t):,.{decimals}f}{suffix}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496001637-05:00 if b and float(b) > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496004487-05:00 pct = (float(t) - float(b)) / float(b) * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496007367-05:00 return f"{label}: {t_str} ({pct:+.1f}% vs {'4-week benchmark' if _is_weekly else 'baseline'})" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496010287-05:00 return f"{label}: {t_str}" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496013147-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496016087-05:00 dm_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496018967-05:00 v = _dm('inside_per_100_gal', 'Inside/100gal', '$', '', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496021907-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496025697-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496028637-05:00 v = _dm('effective_fuel_price', 'Effective fuel price', '$', '/gal', 3) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496031557-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496034477-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496037467-05:00 v = _dm('inside_sales_share_pct','Inside share of sales', '', '%', 1) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496040367-05:00 if v: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496043237-05:00 dm_lines.append(v) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496046157-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496049077-05:00 gap = trans.get('fuel_inside_gap_pct') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496052017-05:00 if gap is not None: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496055007-05:00 direction = "FUEL OUTPACING INSIDE" if gap > 0 else "INSIDE OUTPACING FUEL" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496057917-05:00 dm_lines.append(f"Fuel-vs-Inside gap: {gap:+.1f}pp ({direction})") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496060807-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496063747-05:00 derived_metrics = "\n".join(dm_lines) if dm_lines else "N/A" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496066636-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496069636-05:00 # ── Flags summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496072546-05:00 if flags: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496075466-05:00 flags_str = "\n".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496078406-05:00 f"- [{f['severity']}] {f['label']}: {f['note']}" for f in flags) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496081346-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496087196-05:00 flags_str = "No significant anomalies detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496090226-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496093196-05:00 # ── Shift summary ───────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496096136-05:00 shifts = trans.get('shift_summary', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496099026-05:00 if shifts: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496101926-05:00 shift_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496104836-05:00 for name, data in shifts.items(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496107816-05:00 rev = data.get('revenue', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496110736-05:00 tx = data.get('transactions', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496113606-05:00 gals = data.get('fuel_gallons', 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496117296-05:00 shift_lines.append(f"- {name}: ${rev:,.2f} inside | {tx} tx | {gals:,.1f} gal fuel") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496125066-05:00 shift_summary = "\n".join(shift_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496128016-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496130916-05:00 shift_summary = "Shift data not available." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496133796-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496136806-05:00 # ── Gap / zero-tx hours ─────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496139746-05:00 gaps = trans.get('top_hourly_gaps', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496142706-05:00 zero_tx = trans.get('zero_tx_hours', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496145686-05:00 gap_lines = [] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496148586-05:00 if gaps: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496151566-05:00 gap_str = ", ".join( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496154466-05:00 "{:02d}:00 ({} tx)".format(g['_hour'], g['tx_count']) for g in gaps) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496157366-05:00 gap_lines.append(f"Top gap hours: {gap_str}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496160256-05:00 if zero_tx: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496163156-05:00 gap_lines.append(f"Zero-tx hours (open window): {zero_tx}") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496166036-05:00 gap_summary = "\n".join(gap_lines) if gap_lines else "No unusual gaps detected." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496169056-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496171976-05:00 # Authoritative store hours (issue #128): when Google Places supplies the [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496174906-05:00 # store's real hours, prepend them and instruct the model to treat [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496177796-05:00 # off-hours zero activity as expected closure rather than hedging. The [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496180686-05:00 # gap/zero-tx figures above are already computed against this same window. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496183576-05:00 store_hours_val = kwargs.get('store_hours') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496186466-05:00 if store_hours_val and store_hours_val.get('weekday_text'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496189376-05:00 hrs_lines = ["AUTHORITATIVE STORE HOURS (Google Places — treat as ground truth):"] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496192326-05:00 hrs_lines += [f" {ln}" for ln in store_hours_val['weekday_text']] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496195206-05:00 if store_hours_val.get('open_24h'): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496198126-05:00 hrs_lines.append(" This store operates 24 hours on at least one day.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496201006-05:00 hrs_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496203916-05:00 "Zero-transaction hours OUTSIDE these posted hours are EXPECTED " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496206836-05:00 "CLOSURE — do NOT flag them as gaps and do NOT hedge about whether " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496209726-05:00 "the store was open. Treat zero/low activity DURING posted open " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496212646-05:00 "hours as a real gap worth investigating.") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496215566-05:00 gap_summary = "\n".join(hrs_lines) + "\n\n" + gap_summary [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496218456-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496221456-05:00 # ── Weather ─────────────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496224396-05:00 if weather: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496227356-05:00 impact = weather.get('weather_impact_assessment', '') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496230256-05:00 yd = weather.get('yesterday', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496233136-05:00 td = weather.get('today_forecast', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496240686-05:00 rain_hrs= weather.get('rain_hours_today', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496243736-05:00 weather_str = ( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496246716-05:00 f"Report day: {yd.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496249596-05:00 f"{yd.get('temp_min','?')}–{yd.get('temp_max','?')}°F | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496252466-05:00 f"{yd.get('precip_in',0)}\" precip\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496255436-05:00 f"Today forecast: {td.get('conditions','N/A')} | " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496259706-05:00 f"{td.get('temp_min','?')}–{td.get('temp_max','?')}°F\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496262686-05:00 f"Impact assessment: {impact}\n" [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496265656-05:00 + (f"Rain hours: {rain_hrs}\n" if rain_hrs else "") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496268596-05:00 ) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496271506-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496274406-05:00 weather_str = "No weather data provided." [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496277316-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496280276-05:00 # ── Schedule section ────────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496283196-05:00 schedule_section = kwargs.get('schedule_section', None) or '' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496286076-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496289096-05:00 # ── Category baselines ──────────────────────────────────────────────── [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496296536-05:00 cat_baselines = kwargs.get('category_baselines', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496299556-05:00 # weekly_category_totals: actual accumulated weekly revenue per category [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496302436-05:00 # passed from Cell 39 _week_cat — USE THESE VERBATIM in the report table [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496305336-05:00 weekly_cat_totals = kwargs.get('weekly_category_totals', {}) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496308255-05:00 _is_weekly = (report_type == 'weekly') [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496311135-05:00 # Authoritative per-category revenue comes from the owner-mapped [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496314035-05:00 # category_mix (preprocessor). Post-mapping it is aggregated by taxonomy [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496316985-05:00 # name and already reconciles to inside sales, so it — NOT the seeded [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496319905-05:00 # baselines — must drive the category table. Previously this block only [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496322785-05:00 # emitted categories that had a seeded history baseline; when baselines [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496325645-05:00 # were sparse or unseeded the model received no authoritative totals and [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496328505-05:00 # dumped the bulk of inside sales into "Other / unclassified" (issue #81). [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496331405-05:00 _cat_mix_rows = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496334315-05:00 c for c in scan.get('category_mix', []) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496337225-05:00 if isinstance(c.get('category'), str) and str(c.get('category')).strip() [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496340125-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496343025-05:00 _period_lbl = 'This Week' if _is_weekly else 'Today' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496345905-05:00 if _is_weekly and weekly_cat_totals: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496349275-05:00 # Legacy notebook path passes pre-accumulated weekly totals verbatim. [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496352145-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496355015-05:00 "WEEKLY Category Totals — USE THESE EXACT NUMBERS in Section 3 table.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496362815-05:00 "DO NOT invent or estimate different values. These are computed from actual data.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496365795-05:00 f" {'Category':<30} {'This Week':>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496368715-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496371695-05:00 for cat, week_rev in sorted(weekly_cat_totals.items(), [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496374605-05:00 key=lambda x: -x[1]): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496377515-05:00 if not isinstance(cat, str) or not cat.strip(): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496380395-05:00 continue [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496383285-05:00 avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496386315-05:00 if avg > 0: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496389215-05:00 pct = (week_rev - avg) / avg * 100 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496392145-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496395125-05:00 f" {cat:<30} ${week_rev:>10,.0f} ${avg:>10,.0f}/day {pct:>+7.1f}%") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496398045-05:00 else: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496400995-05:00 _cb_lines.append( [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496403915-05:00 f" {cat:<30} ${week_rev:>10,.0f} (no baseline)") [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496406855-05:00 category_baselines_str = "\n".join(_cb_lines) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496409745-05:00 elif _cat_mix_rows: [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496412665-05:00 _classified = sum((c.get('revenue') or 0) for c in _cat_mix_rows) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496415615-05:00 _cb_lines = [ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496419095-05:00 f"Per-category inside revenue ({_period_lbl}) — USE THESE EXACT NUMBERS " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496422085-05:00 f"in the Section 3 category table. They are computed from the owner's " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496425005-05:00 f"merchandise-code taxonomy and already sum to inside sales " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496427905-05:00 f"(${_classified:,.0f}). Only the row explicitly labelled " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496430825-05:00 f"'Other / Unclassified' is unclassified — do NOT move any other " [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496433715-05:00 f"category's revenue into 'Other', and do NOT invent different values.", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496436635-05:00 f" {'Category':<30} {_period_lbl:>12} {'Normal/day':>12} {'Delta':>8}", [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496440095-05:00 ] [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496443025-05:00 for c in sorted(_cat_mix_rows, key=lambda x: -(x.get('revenue') or 0)): [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496445925-05:00 cat = str(c['category']) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496448855-05:00 rev = c.get('revenue') or 0 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496451775-05:00 > avg = cat_baselines.get(cat, 0) [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496454725-05:00 ^^^^^^^^^^^^^^^^^ [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496457635-05:00 E AttributeError: 'NoneType' object has no attribute 'get' [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496460455-05:00 [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496463485-05:00 analytics/src/storeminder/agents/synthesis_agent.py:1506: AttributeError===JUNIT_END=== [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.496466435-05:00 ----- end live Job logs ----- [pod/analytics-report-live-lknt8-live-build-pod/step-run-live] 2026-07-13T12:59:12.534546455-05:00 ==> live build: status=Failed conclusion=failure [pod/analytics-report-live-lknt8-live-build-pod/prepare] 2026-07-13T12:54:54.685339190-05:00 2026/07/13 17:54:54 Entrypoint initialization [pod/analytics-report-live-lknt8-live-build-pod/place-scripts] 2026-07-13T12:54:55.334435181-05:00 2026/07/13 17:54:55 Decoded script /tekton/scripts/script-0-ztttq [pod/analytics-report-live-lknt8-live-build-pod/place-scripts] 2026-07-13T12:54:55.354211869-05:00 2026/07/13 17:54:55 Decoded script /tekton/scripts/script-2-lhbsz ===== analytics-report-live-lknt8-notify-on-failure-pod.log ===== [pod/analytics-report-live-lknt8-notify-on-failure-pod/prepare] 2026-07-13T12:59:14.436227876-05:00 2026/07/13 17:59:14 Entrypoint initialization [pod/analytics-report-live-lknt8-notify-on-failure-pod/place-scripts] 2026-07-13T12:59:15.390657590-05:00 2026/07/13 17:59:15 Decoded script /tekton/scripts/script-0-xxjmz [pod/analytics-report-live-lknt8-notify-on-failure-pod/step-slack-alert] 2026-07-13T12:59:19.954960039-05:00 slack-preview-token absent — skipping Slack alert (see the storeminder/slack Vault grant) ===== analytics-report-live-lknt8-post-check-live-pod.log ===== [pod/analytics-report-live-lknt8-post-check-live-pod/prepare] 2026-07-13T12:59:15.456649621-05:00 2026/07/13 17:59:15 Entrypoint initialization [pod/analytics-report-live-lknt8-post-check-live-pod/place-scripts] 2026-07-13T12:59:16.579789888-05:00 2026/07/13 17:59:16 Decoded script /tekton/scripts/script-0-xxshp [pod/analytics-report-live-lknt8-post-check-live-pod/step-post-check] 2026-07-13T12:59:20.664107550-05:00 Installing PyJWT + cryptography + requests… ===== analytics-report-live-lknt8-post-status-live-pod.log ===== [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:18.828555714-05:00 Posting commit status to GitHub [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:18.828584914-05:00 Repo: storeminder-ai/storeminder [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:18.828588864-05:00 SHA: fa11db9bd216426d9f1cdf921f0cf771afc49902 [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:18.828591534-05:00 State: failure (Failed) [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:18.828594433-05:00 Target URL: https://ci-logs.storeminder.ai/storeminder-ai/storeminder/pr-fa11db9bd216426d9f1cdf921f0cf771afc49902/analytics-report-live-lknt8/logs.txt [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.268986347-05:00 { [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277572271-05:00 "url": "https://api.github.com/repos/storeminder-ai/storeminder/statuses/fa11db9bd216426d9f1cdf921f0cf771afc49902", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277583691-05:00 "avatar_url": "https://avatars.githubusercontent.com/u/702924?v=4", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277589561-05:00 "id": 50394721031, [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277594981-05:00 "node_id": "SC_kwDOS9o4_c8AAAALu8JrBw", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277600191-05:00 "state": "failure", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277604851-05:00 "description": "Checks failed", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277610721-05:00 "target_url": "https://ci-logs.storeminder.ai/storeminder-ai/storeminder/pr-fa11db9bd216426d9f1cdf921f0cf771afc49902/analytics-report-live-lknt8/logs.txt", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277615361-05:00 "context": "tekton/report-build-live", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277619781-05:00 "created_at": "2026-07-13T17:59:19Z", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277624231-05:00 "updated_at": "2026-07-13T17:59:19Z", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277629011-05:00 "creator": { [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277633961-05:00 "login": "steveblackmon", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277638471-05:00 "id": 702924, [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277642971-05:00 "node_id": "MDQ6VXNlcjcwMjkyNA==", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277647971-05:00 "avatar_url": "https://avatars.githubusercontent.com/u/702924?v=4", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277652371-05:00 "gravatar_id": "", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277657081-05:00 "url": "https://api.github.com/users/steveblackmon", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277661371-05:00 "html_url": "https://github.com/steveblackmon", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277665920-05:00 "followers_url": "https://api.github.com/users/steveblackmon/followers", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277670660-05:00 "following_url": "https://api.github.com/users/steveblackmon/following{/other_user}", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277675030-05:00 "gists_url": "https://api.github.com/users/steveblackmon/gists{/gist_id}", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277710260-05:00 "starred_url": "https://api.github.com/users/steveblackmon/starred{/owner}{/repo}", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277737430-05:00 "subscriptions_url": "https://api.github.com/users/steveblackmon/subscriptions", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277743720-05:00 "organizations_url": "https://api.github.com/users/steveblackmon/orgs", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277749220-05:00 "repos_url": "https://api.github.com/users/steveblackmon/repos", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277754420-05:00 "events_url": "https://api.github.com/users/steveblackmon/events{/privacy}", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277758850-05:00 "received_events_url": "https://api.github.com/users/steveblackmon/received_events", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277765130-05:00 "type": "User", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277769860-05:00 "user_view_type": "public", [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277774250-05:00 "site_admin": false [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277778780-05:00 } [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277783130-05:00 } [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277798390-05:00 [pod/analytics-report-live-lknt8-post-status-live-pod/step-post-status] 2026-07-13T12:59:19.277803660-05:00 Status posted successfully [pod/analytics-report-live-lknt8-post-status-live-pod/prepare] 2026-07-13T12:59:15.042223161-05:00 2026/07/13 17:59:15 Entrypoint initialization [pod/analytics-report-live-lknt8-post-status-live-pod/place-scripts] 2026-07-13T12:59:15.383826879-05:00 2026/07/13 17:59:15 Decoded script /tekton/scripts/script-0-jvq8z ===== analytics-report-live-lknt8-reclaim-workspace-pod.log ===== [pod/analytics-report-live-lknt8-reclaim-workspace-pod/prepare] 2026-07-13T12:59:17.410843295-05:00 2026/07/13 17:59:17 Entrypoint initialization [pod/analytics-report-live-lknt8-reclaim-workspace-pod/place-scripts] 2026-07-13T12:59:17.638405662-05:00 2026/07/13 17:59:17 Decoded script /tekton/scripts/script-0-bhqhm