{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "Nishikanta Ray",
  "title_listed": "8 LLMs, 480 Questions, 1 Kaggle Benchmark: Who Can Explain a Traffic Drop?",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://dev.to/nishikantaray/i-gave-8-llms-my-analytics-products-ai-job-the-cheap-ones-either-invent-a-reason-or-shrug-3f41",
  "publish_date": "2026-09-27",
  "corpus_stage": "",
  "notes": "",
  "id": "93939fc0b4e3",
  "collected_at": "2026-10-02T14:04:37",
  "title": "8 LLMs, 480 Questions, 1 Kaggle Benchmark: Who Can Explain a Traffic Drop? - DEV Community",
  "body_markdown": "This is a submission for the\nKaggle Benchmarking Challenge\nWhat I Benchmarked\nThe short version\nI build\nInsightTrack\n, an open-source web analytics\nplatform. It has an AI analyst called\nPulse\n. You ask\n\"why did traffic to /pricing drop last\nweek?\"\n, and Pulse picks the right tools, reads the numbers, and answers in plain English.\nPulse needs a language model behind it. I wanted to choose that model with evidence, not vibes, so I\nturned Pulse's real job into a benchmark:\n480 questions, graded automatically, with answers\nthat come from InsightTrack's own code.\nThe problem: an AI analyst can be confidently wrong\nAn analytics assistant isn't a chatbot for fun facts. People act on what it says: they rewrite pages,\npause campaigns, or stop worrying. When it's wrong, it's wrong in one of two costly ways:\nIt invents a reason.\nTraffic moves for a boring reason (a holiday, a newsletter, plain noise), and the assistant confidently blames Google rankings. You spend a day fixing SEO that was never broken.\nIt misses a real problem.\nTraffic falls by half, and the assistant says \"nothing significant happened\". You find out a month later.\nBoth are worse than saying \"I don't know\". A useful analyst needs three habits that don't show up on\ngeneral AI leaderboards:\nRestraint:\nsaying \"this is noise\" or \"the data doesn't show that\" when that's the truth.\nRule-following on numbers:\napplying thresholds exactly. A 15% change on 6 visitors is not a trend.\nReading real tool output:\nthe exact fields, rounding and formats a product actually returns, not textbook examples.\nWhy general benchmarks don't answer this\nPublic benchmarks are excellent at telling you which model knows more maths, writes better code, or\ncalls functions correctly. But choosing a model for a product is a\nproduct decision\n, and it has\nthree questions they can't answer for you:\nAccuracy on\nmy\njob:\nmy tools, my data formats, my database dialect (DuckDB, not PostgreSQL).\nThe kind of mistakes:\na model that is wrong 10% of the time by saying \"not sure\" is safer than one that is wrong 5% of the time by making things up.\nCost:\nis the expensive model worth it, or does a model at a tenth of the price do the same job?\nThe only way to answer those is to test models on the actual work. That's what this benchmark does.\nWhat's in it\nInsightTrack Analyst Bench\nhas 480 auto-graded cases across four tasks. The first three are Pulse's job today; the fourth, SQL, is the step Pulse doesn't take yet: writing a query when no built-in tool fits.\nTask\nWhat Pulse must do\nThe model gets\nIt's right when\nTool choice\nDecide how to answer\nA user question + InsightTrack's real 23-tool catalogue\nIt picks the right tool and arguments, or says \"none\" when no tool can do it\nData reading\nUnderstand a result\nA tool result in InsightTrack's real output format\nIt gets the number right, or says\nnot_available\ninstead of inventing one\nDiagnosis\nExplain a change\n8 weeks of page traffic + Google results before and after\nIt says whether the change is real, which way it went, and the\nexact\nset of causes\nSQL\nDig deeper (next on Pulse's roadmap)\nInsightTrack's DuckDB\nevents\nand\nsessions\ntables\nIts query,\nexecuted\nread-only, returns the right rows\nEach task has\n80 standard + 40 hard\ncases. The hard ones are where careless reasoning shows:\nchanges that land exactly on a threshold (a 15.0% drop)\nseveral keywords pointing in different directions\npages that stopped ranking entirely\nlookalike domains:\ngetsite.com\nis a different site, but\nwww.site.com\nis yours\nOne case, step by step\nHere's what a diagnosis question looks like, simplified:\nWeekly pageviews:\n… 495, 517, 347\n. The last week dropped by 170 views, or 33%.\nGoogle, before:\nthe page ranked\n#3\nfor its keyword.\nGoogle, now:\nit ranks\n#5\n. Nothing else changed.\nThe rules\nare all in the prompt. A change counts only if it's at least 15%\nand\nat least 20 views. A 1–2 place rank move is normal jitter, never a cause.\nThe correct answer:\nthe drop is real, but search didn't cause it.\nIn InsightTrack's terms,\n{\"significant\": true, \"direction\": \"drop\", \"causes\": [\"unexplained_by_serp\"]}\n. The tempting wrong\nanswer is \"your rank dropped\". A model that says that sends the user to fix SEO that isn't broken.\nThat's the heart of the benchmark:\ncan the model tell a real cause from a coincidence?\nWhy the answers can be trusted\nNo LLM grades another LLM:\nDiagnosis:\nthe answer key is InsightTrack's own correlation engine, ported to Python. A test runs the\noriginal JavaScript\non every case plus 1,500 random ones and fails on any disagreement.\nSQL:\nanswers are graded by\nexecuting\nthe model's query, so two different but correct queries both pass.\nEverything else:\nanswers are computed from the same data the model sees.\nData:\nall synthetic (5,940 events, 2,494 sessions), with no real person's data, and the whole set rebuilds byte-for-byte.\nEvery grader also labels\nhow\na model was wrong:\ninvented a cause\n,\ncalled a real change noise\n,\nmade up a number\n,\nrefused an answerable question\n,\nused a tool that doesn't exist\n. That turns a\nscore into something you can act on.\nWho this helps\nDevelopers building AI features on analytics or BI data.\nYou face the same choice I did. These results, and the failure types above, show what to expect from each class of model.\nAnyone picking a model on a budget.\nThe results put a price on accuracy: where paying more buys a lot, and where it buys nothing.\nPeople who build and evaluate models.\nMost tests reward knowing things. This one rewards\nnot\nclaiming things, which is harder to measure and matters in real products.\nInsightTrack's users.\nPulse gets its model and its safety checks from evidence instead of guesswork.\nModels Tested\nAll models ran through Kaggle's model proxy on the same task version. I picked them in pairs, so each\ncomparison answers a practical question:\nFrontier vs. cheap, same company:\nClaude Sonnet 5 vs. Claude Haiku 4.5, and Gemini 3.7 Flash vs. Gemini 3.5 Flash-Lite.\nHow much do you lose by saving money?\nReasoning on vs. off, same model:\nGrok 4.20 with and without reasoning.\nDoes \"thinking\" change the answer?\nThe cheapest option:\nGPT-5.4 nano.\nIs \"good enough\" good enough?\nOpenAI's flagship:\nGPT-5.5, as a reference point.\nFindings\nModel\nTool choice\nData reading\nDiagnosis\nSQL\nOverall\nCost (480 cases)\nClaude Sonnet 5\n100\n100\n100\n100\n100.0\n$2.08\nGPT-5.5\n100\n100\n96.7\n100\n99.2\n$3.23\nGrok 4.20 (reasoning)\n100\n96.7\n100\n95.0\n97.9\n$1.21\nGemini 3.7 Flash\n100\n100\n100\n90.8\n97.7\n$1.07\nGemini 3.5 Flash-Lite\n98.3\n88.3\n49.2\n88.3\n81.0\n$0.20\nClaude Haiku 4.5\n98.3\n82.5\n44.2\n86.7\n77.9\n$0.63\nGrok 4.20 (no reasoning)\n98.3\n82.5\n35.0\n92.5\n77.1\n$0.40\nGPT-5.4 nano\n93.3\n61.7\n20.0\n93.3\n67.1\n$0.07\n1. Tool choice is solved. Diagnosis is where models split.\nEvery model picked the right tool at least 93% of the time. So \"can it call tools?\" is no longer the\ninteresting question for an analytics assistant. The real question is whether it understands what the\nnumbers\nmean\n. On diagnosis, the top three models score 100%, GPT-5.5 scores 96.7%, and every cheaper\nmodel scores between 20% and 49%.\n2. Same model, reasoning on vs. off: 100% vs. 35%\nGrok 4.20 with reasoning got\nevery\ndiagnosis case right. The same model without reasoning got\n35%\n. It lost all 16 multi-cause cases, and 9 of the 12 decoys. Nothing else in the results moved this\nmuch. If your assistant answers \"why did X change?\", reasoning isn't a nice-to-have.\n3. Cheap models fail in two opposite ways\nThe alarmist.\nWithout reasoning, Grok invented a search cause 34 times. In one case, traffic\nrose\nfrom 437 to 538 views while the page slipped from #3 to #5 on Google. The prompt says plainly\nthat a 1–2 place move is normal jitter and never a cause. Grok answered:\n{\n\"significant\"\n:\ntrue\n,\n\"direction\"\n:\n\"spike\"\n,\n\"causes\"\n:\n[\n\"rank_drop\"\n]}\nEnter fullscreen mode\nExit fullscreen mode\nIt blamed a rank\ndrop\nfor a traffic\nrise\n. The right answer was\nunexplained_by_serp\n: this change\ndidn't come from search.\nThe shrugger.\nGPT-5.4 nano went the other way, calling real changes noise 73 times. A page fell\nfrom\n209 to 110 views (−47%)\nright after sliding from #14 to #26 on Google. Nano answered:\n{\n\"significant\"\n:\nfalse\n,\n\"direction\"\n:\nnull\n,\n\"causes\"\n:\n[]}\nEnter fullscreen mode\nExit fullscreen mode\nFor an analytics product, this is the dangerous one. An alarmist wastes your afternoon. A shrugger\ntells you everything is fine while your traffic halves.\n4. Mixed signals break every cheaper model: 0 of 8\nIn the hard cases, one keyword lost rank while another\ngained\n, and traffic fell. The correct answer\nnames only the loss, since a gain can't explain a drop. Flash-Lite, Haiku, Grok without reasoning, and\nnano\nall scored 0 out of 8\n. Haiku, for instance, blamed the drop on\nai_citation_gained\n, a rule the\nprompt says applies only when traffic rises. Real pages rank for many keywords at once, so this is\nthe everyday case, not an edge case.\n5. Reading data: the cheaper models refuse and invent\nOn data reading, all 7 models correctly said\nnot_available\non the 24 standard questions the data\ncouldn't answer. The traps in the hard set split them:\nMade-up numbers:\nasked for the total unique visitors across a list of pages (impossible, because the same person can visit several pages), nano confidently answered\n9,149\n.\nRefused answerable questions:\nnano did this 13 times, and the other cheaper models 5–7 times each. They answered \"not available\" when the answer took one calculation.\nA model that can't tell \"I can't compute this\" from \"this can't be computed\" isn't safe in either direction.\n6. The SQL failures were habits, not logic\nGemini 3.7 Flash scored 0/7 on JSON questions\n, and nearly everything else it got right. Every time,\nit wrote:\nWHERE\nproperties\n->>\n'name'\n=\n'signup'\nEnter fullscreen mode\nExit fullscreen mode\nThat's fine in PostgreSQL. In DuckDB,\n->>\nbinds more loosely than\n=\n, so it parses as\nproperties ->> ('name' = 'signup')\nand crashes. One pair of parentheses fixes it. It looks like a\nhabit carried over from PostgreSQL, which is far more common in examples than DuckDB.\nClaude Haiku corrects itself mid-answer.\nIn 9 SQL replies it wrote a query, then\n\"Wait, let me\nreconsider…\"\n, then a second query. The prompt asks for the query only, so the grader takes the first\none. In 8 of those 9 cases, the second query was right. Graded on its last query, Haiku's SQL score would\nbe 93.3% instead of 86.7%. That's a real cost of thinking out loud inside a tool call: whatever sits\nbetween your model and your database reads the first answer.\n7. GPT-5.5's only misses came from my prompt\nGPT-5.5 made exactly four mistakes on diagnosis, and all four are the same one. Each time, a page slid\njust past #10 (#10 to #11 twice, #10 to #12 once, #9 to #11 once): it fell off page one, so the\nanswer is\npage_one_exit\n. GPT-5.5 said\nunexplained_by_serp\nevery time.\nRereading my prompt, I found two rules that clash:\npage_one_exit\n: \"position 1–10 before; now below 10\".\n\"Moves of 1–2 places are normal jitter and are\nnever a cause\n.\"\nI meant the jitter rule to cover\nrank_drop\nand\nrank_gain\n. GPT-5.5 took \"never\" literally. Claude\nSonnet 5, Gemini 3.7 Flash and Grok with reasoning all read the intent. Neither reading is careless.\nOne model follows the letter of an instruction and others follow its spirit, and that's worth knowing\nabout any model you give rules to. It's also a reminder that\na benchmark's prompt is code too\n.\nThe next version will say \"never counts as\nrank_drop\nor\nrank_gain\n\", and I'm reporting the\nscore as it stands rather than quietly fixing it.\n8. Value: Gemini 3.7 Flash is the sweet spot\nGemini 3.7 Flash reaches 97.7% at about half the cost of Claude Sonnet 5. GPT-5.5 was the most\nexpensive run ($3.23) and still finished just behind Sonnet. Below the top four there's a cliff: no model\nunder $0.65 per run scored above 81%, and all of them fell apart on diagnosis.\nSo what will power Pulse?\nGemini 3.7 Flash by default.\nIt scored 100% on the three tasks that are about judgement, at half the\nprice of the most accurate model. Its one weakness is fixable in code: wrap\n->>\ncomparisons in\nparentheses before running any generated SQL.\nClaude Sonnet 5 as the fallback\nfor anything SQL-heavy, since it was perfect everywhere, and\ncheaper than GPT-5.5 for the same job.\nNo cheaper model for diagnosis.\nAn assistant that shrugs at a 47% drop costs more than it saves.\nThe benchmark also backed a design choice InsightTrack already makes. In the product, Pulse's\nexplain_traffic_change\ntool works out significance and causes\nin code\n, and the model only\nexplains the result in plain English. These results show why: left to do that reasoning alone, every\ncheaper model got it wrong at least half the time.\nWhat this means if you're building an AI assistant\nThese lessons carry over to any assistant that works with numbers:\nCompute, don't ask.\nAnything with a threshold (significance, \"is this a trend?\") belongs in code. Let the model explain the result, not decide it.\nMake \"nothing happened\" and \"I can't tell\" valid answers, and test them.\nIf your tests only have cases with a cause, you'll never see the shrugger or the alarmist until a user does.\nUse reasoning where the answer is a\nwhy\n.\nIt was the biggest single factor in the results: 100% vs. 35% on the same model.\nCheck generated SQL against your real database dialect.\nMost failures here were PostgreSQL habits, not wrong logic, and a small rewrite or check catches them.\nDon't parse the first thing a model says.\nAsk for a clean final answer, or read the\nlast\none, because models do correct themselves.\nRoute by task.\nTool choice is cheap to get right, so a small model can handle it. Diagnosis isn't.\nWhat I'd measure next\nConsistency:\nrun each model three times and count how often the same case flips.\nThe fixed jitter rule:\nrerun every model and see whether GPT-5.5 reaches 100%, and whether any other model's score changes.\nReal tool calls:\nmulti-turn runs where the model actually calls InsightTrack's tools, instead of naming the one it would call.\nMessier evidence:\nseasonality, like a holiday dip that isn't search, alongside keywords that point in different directions.\nLimits, honestly\n\"Correct\" means correct by InsightTrack's rules.\nEvery rule and threshold is written into the prompt, so this tests careful application, not guessing a hidden convention.\nSynthetic data, one run per model\n, all through Kaggle's model proxy.\nSQL is graded strictly on the first query.\nThat costs Haiku, as described above.\nOne rule in the diagnosis prompt is ambiguous\n(the jitter rule vs.\npage_one_exit\n). It accounts for all four of GPT-5.5's misses. Fixed wording is planned for the next version.\nMy Benchmark\nKaggle benchmark:\nhttps://www.kaggle.com/benchmarks/nishikantaray/insighttrack-analyst-bench\nDataset (cases, sample tables, graders):\nhttps://www.kaggle.com/datasets/nishikantaray/insighttrack-analyst-bench\nThe product it's built from:\nhttps://github.com/NishikantaRay/InsightTrack\nHow you can use it\nTest another model:\nthe four tasks are public on Kaggle, so anyone can run them against any model in Kaggle's catalogue and compare with the table above.\nReuse the method for your own product:\ntake a rule your code already enforces, turn it into generated test cases with the answer from your code, include cases where the right answer is \"nothing\", and label the\nkind\nof mistake as well as the score.\nBuilt with\nKaggle Benchmarks\n. The task pattern follows\nits\ndataset_evaluation.py\nexample. It covers ground near BFCL and τ-bench (tool calling),\nInfiAgent-DABench and DSBench (data analysis), and Spider 2.0 and BIRD (text-to-SQL). It differs by\ntesting one real product's job end to end, and by scoring whether a model knows when\nnothing\nhappened.",
  "metadata": {
    "title": "8 LLMs, 480 Questions, 1 Kaggle Benchmark: Who Can Explain a Traffic Drop? - DEV Community",
    "description": "This is a submission for the Kaggle Benchmarking Challenge           What I Benchmarked           ... Tagged with devchallenge, kagglechallenge, ai, machinelearning.",
    "keywords": "devchallenge, kagglechallenge, ai, machinelearning, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/nishikantaray/i-gave-8-llms-my-analytics-products-ai-job-the-cheap-ones-either-invent-a-reason-or-shrug-3f41",
    "og:title": "8 LLMs, 480 Questions, 1 Kaggle Benchmark: Who Can Explain a Traffic Drop?",
    "og:description": "This is a submission for the Kaggle Benchmarking Challenge           What I Benchmarked           ...",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6xwndctnm2hspz03tsvq.png",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@",
    "twitter:title": "8 LLMs, 480 Questions, 1 Kaggle Benchmark: Who Can Explain a Traffic Drop?",
    "twitter:description": "This is a submission for the Kaggle Benchmarking Challenge           What I Benchmarked           ...",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6xwndctnm2hspz03tsvq.png"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/93939fc0b4e3/code/body-000.txt",
      "chars": 69,
      "language": "text"
    },
    {
      "file": "attachments/93939fc0b4e3/code/body-001.txt",
      "chars": 56,
      "language": "text"
    },
    {
      "file": "attachments/93939fc0b4e3/code/body-002.txt",
      "chars": 37,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4t2xqh5lc6lblx9wmhtn.png",
      "bytes": 70514,
      "reused": true,
      "local_path": "attachments/93939fc0b4e3/images/002.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4t2xqh5lc6lblx9wmhtn.png",
      "alt": "Diagnosis accuracy by model",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxvsg1yr9otsys84ij0x2.png",
      "bytes": 80172,
      "reused": true,
      "local_path": "attachments/93939fc0b4e3/images/003.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxvsg1yr9otsys84ij0x2.png",
      "alt": "Two ways to be wrong about a traffic change",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0o8be9ul3y6qruli0era.png",
      "bytes": 79388,
      "reused": true,
      "local_path": "attachments/93939fc0b4e3/images/004.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0o8be9ul3y6qruli0era.png",
      "alt": "What you get for what you pay",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://dev.to/challenges/kaggle-2026-09-23",
      "label": "Kaggle Benchmarking Challenge",
      "type": "a"
    },
    {
      "href": "https://github.com/NishikantaRay/InsightTrack",
      "label": "InsightTrack",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4t2xqh5lc6lblx9wmhtn.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxvsg1yr9otsys84ij0x2.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0o8be9ul3y6qruli0era.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://www.kaggle.com/benchmarks/nishikantaray/insighttrack-analyst-bench",
      "label": "https://www.kaggle.com/benchmarks/nishikantaray/insighttrack-analyst-bench",
      "type": "a"
    },
    {
      "href": "https://www.kaggle.com/datasets/nishikantaray/insighttrack-analyst-bench",
      "label": "https://www.kaggle.com/datasets/nishikantaray/insighttrack-analyst-bench",
      "type": "a"
    },
    {
      "href": "https://github.com/Kaggle/kaggle-benchmarks",
      "label": "Kaggle Benchmarks",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:49:49Z",
  "source_url": "https://dev.to/nishikantaray/i-gave-8-llms-my-analytics-products-ai-job-the-cheap-ones-either-invent-a-reason-or-shrug-3f41",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 15710,
  "expected_body_images": 3,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/93939fc0b4e3/pages/source.html",
  "local_html": "attachments/93939fc0b4e3/pages/article.html",
  "body_base_path": "attachments/93939fc0b4e3/pages/",
  "body_markdown_path": "attachments/93939fc0b4e3/pages/article.md"
}