{
  "generated_at": "2026-07-27T03:11:49+00:00",
  "mode": "hourly",
  "overall_status": "attention",
  "failure_count": 2,
  "summary": {
    "meeting_manifest_count": 522,
    "meeting_transcripts": 512,
    "youtube_uploads": 51,
    "youtube_channel_pull": 85,
    "youtube_captions": 510,
    "official_video_records": 520,
    "news_items": 74,
    "news_sources": 12,
    "search_records": 838,
    "finance_transcripts": 81,
    "agenda_packets": 24
  },
  "jobs": [
    {
      "name": "Discover agenda packets and build audit pages",
      "command": [
        "python3",
        "/usr/local/bin/exposemiamiok_agenda_packet_auto.py"
      ],
      "started_at": "2026-07-27T03:11:01+00:00",
      "status": "ok",
      "exit_code": 0,
      "duration_seconds": 9.16,
      "stdout_tail": "agenda packet auto: discovered=24 rendered=24\n",
      "stderr_tail": ""
    },
    {
      "name": "Crawl official Miami Web TV videos",
      "command": [
        "python3",
        "/opt/crawl_miamiok_videos.py"
      ],
      "started_at": "2026-07-27T03:11:10+00:00",
      "status": "ok",
      "exit_code": 0,
      "duration_seconds": 1.6,
      "stdout_tail": "Crawled 522 official Granicus videos\nMerged manifest now has 522 meetings\nWrote /opt/miamiok-granicus-videos.json\n",
      "stderr_tail": ""
    },
    {
      "name": "Rebuild official/YouTube video API",
      "command": [
        "python3",
        "/opt/gen_videos_json.py"
      ],
      "started_at": "2026-07-27T03:11:12+00:00",
      "status": "failed",
      "exit_code": 1,
      "duration_seconds": 0.07,
      "stdout_tail": "",
      "stderr_tail": "Traceback (most recent call last):\n  File \"/opt/gen_videos_json.py\", line 85, in <module>\n    main()\n  File \"/opt/gen_videos_json.py\", line 34, in main\n    uploads = uploaded_map()\n              ^^^^^^^^^^^^^^\n  File \"/opt/gen_videos_json.py\", line 26, in uploaded_map\n    uploads[clip_id] = json.load(f)\n                       ^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/__init__.py\", line 293, in load\n    return loads(fp.read(),\n           ^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/__init__.py\", line 346, in loads\n    return _default_decoder.decode(s)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/decoder.py\", line 337, in decode\n    obj, end = self.raw_decode(s, idx=_w(s, 0).end())\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/decoder.py\", line 355, in raw_decode\n    raise JSONDecodeError(\"Expecting value\", s, err.value) from None\njson.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)\n"
    },
    {
      "name": "Pull YouTube channel uploads from YouTube API",
      "command": [
        "python3",
        "/usr/local/bin/exposemiamiok_youtube_feed_build.py"
      ],
      "started_at": "2026-07-27T03:11:12+00:00",
      "status": "ok",
      "exit_code": 0,
      "duration_seconds": 1.85,
      "stdout_tail": "youtube feed build: pulled=85 playlist=UUPEzVd4eQtJyVtOMt327PVA wrote=/var/www/exposemiamiok/html/youtube.html\n",
      "stderr_tail": ""
    },
    {
      "name": "Rebuild public videos page",
      "command": [
        "python3",
        "/opt/youtube_gallery.py"
      ],
      "started_at": "2026-07-27T03:11:14+00:00",
      "status": "failed",
      "exit_code": 1,
      "duration_seconds": 0.14,
      "stdout_tail": "",
      "stderr_tail": "Traceback (most recent call last):\n  File \"/opt/gen_videos_json.py\", line 85, in <module>\n    main()\n  File \"/opt/gen_videos_json.py\", line 34, in main\n    uploads = uploaded_map()\n              ^^^^^^^^^^^^^^\n  File \"/opt/gen_videos_json.py\", line 26, in uploaded_map\n    uploads[clip_id] = json.load(f)\n                       ^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/__init__.py\", line 293, in load\n    return loads(fp.read(),\n           ^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/__init__.py\", line 346, in loads\n    return _default_decoder.decode(s)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/decoder.py\", line 337, in decode\n    obj, end = self.raw_decode(s, idx=_w(s, 0).end())\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/decoder.py\", line 355, in raw_decode\n    raise JSONDecodeError(\"Expecting value\", s, err.value) from None\njson.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)\nTraceback (most recent call last):\n  File \"/opt/youtube_gallery.py\", line 180, in <module>\n    main()\n  File \"/opt/youtube_gallery.py\", line 60, in main\n    subprocess.run([\"python3\", \"/opt/gen_videos_json.py\"], check=True)\n  File \"/usr/lib/python3.11/subprocess.py\", line 571, in run\n    raise CalledProcessError(retcode, process.args,\nsubprocess.CalledProcessError: Command '['python3', '/opt/gen_videos_json.py']' returned non-zero exit status 1.\n"
    },
    {
      "name": "Refresh local news RSS feeds",
      "command": [
        "python3",
        "/usr/local/bin/exposemiamiok_news_feed_build.py"
      ],
      "started_at": "2026-07-27T03:11:14+00:00",
      "status": "ok",
      "exit_code": 0,
      "duration_seconds": 26.53,
      "stdout_tail": "wrote /var/www/exposemiamiok/html/data/local-news-feeds.json items=74 sources=12\n",
      "stderr_tail": ""
    },
    {
      "name": "Rebuild site search index",
      "command": [
        "python3",
        "/usr/local/bin/exposemiamiok_search_index_build.py"
      ],
      "started_at": "2026-07-27T03:11:40+00:00",
      "status": "ok",
      "exit_code": 0,
      "duration_seconds": 4.31,
      "stdout_tail": "wrote /var/www/exposemiamiok/html/data/site-search-index.json records=838\n",
      "stderr_tail": ""
    },
    {
      "name": "Crime watch feed aggregator",
      "command": [
        "python3",
        "/opt/crime-feed.py"
      ],
      "started_at": "2026-07-27T03:11:45+00:00",
      "status": "ok",
      "exit_code": 0,
      "duration_seconds": 3.68,
      "stdout_tail": "=== Crime Feed Aggregator ===\nFetching: Google News - Miami OK ...\n  84 raw items\n  61 matched\nFetching: Google News - Ottawa County ...\n  64 raw items\n  54 matched\nFetching: KOAM News ...\n  2 raw items\n  2 matched\nFetching: FourStatesHomepage ...\n  0 raw items\n  0 matched\nFetching: KGGF Radio ...\n  50 raw items\n  8 matched\nFetching: NewsOn6 Crime ...\n  Failed: HTTP Error 404: Not Found\nFetching: KJRH Tulsa ...\n  Failed: not well-formed (invalid token): line 2, column 39\nFetching: OSCN Recent Cases ...\n  Failed: HTTP Error 404: Not Found\nFetching: Joplin Globe ...\n  50 raw items\n  31 matched\n\nTotal unique matches: 143\nJSON saved to /var/www/exposemiamiok/html/data/crime-feed.json\nHTML saved to /var/www/exposemiamiok/html/crime-watch.html\n\nTop 10 matches:\n  [ 6] Miami police identify suspect fatally shot in home burglary - KOAM News Now — Google News - Miami \n  [ 6] Suspicious death investigation underway; body found in Tar Creek in Ottawa Count — Google News - Miami \n  [ 6] Wake Up Recap: Ottawa County death investigation, fatal shooting update | News - — Google News - Ottawa\n  [ 6] Man stopped in Jasper County convicted of drug possession — Joplin Globe\n  [ 6] Joplin man sentenced to more than 16 years in prison on child porn charges — Joplin Globe\n  [ 6] Large fentanyl stash found in Joplin drug arrests — Joplin Globe\n  [ 5] Florida murder suspect spotted near Oklahoma mall last week arrested at Miami ho — Google News - Miami \n  [ 5] Inmate found unresponsive in Ottawa County jail; died in hospital - KOAM News No — Google News - Ottawa\n  [ 5] Investigation underway after woman found dead in Ottawa County - FourStatesHomep — Google News - Ottawa\n  [ 5] Shooting suspect pleas guilty to 2021 Ottawa County Murder - KOAM News Now — Google News - Ottawa\n",
      "stderr_tail": ""
    },
    {
      "name": "Normalize menu and logo shell",
      "command": [
        "python3",
        "/usr/local/bin/exposemiamiok_navigation_update.py"
      ],
      "started_at": "2026-07-27T03:11:48+00:00",
      "status": "ok",
      "exit_code": 0,
      "duration_seconds": 0.79,
      "stdout_tail": "full menu updated checked=764 changed=31 version=20260601-unified-menu-agenda6\n",
      "stderr_tail": ""
    },
    {
      "name": "Final menu normalization after status page write",
      "command": [
        "python3",
        "/usr/local/bin/exposemiamiok_navigation_update.py"
      ],
      "started_at": "2026-07-27T03:11:49+00:00",
      "status": "ok",
      "exit_code": 0,
      "duration_seconds": 0.71,
      "stdout_tail": "full menu updated checked=764 changed=1 version=20260601-unified-menu-agenda6\n",
      "stderr_tail": ""
    }
  ]
}