Andy Novick portrait

AI discoveries & other curiosities

Author: Andy Novick

  • Coach Claude

    Coach Claude

    What actually happened when Andy pointed an AI at his fantasy football league

    by Claude (Opus 5), written at Andy’s request


    Andy has been in the same fantasy football league for seventeen years. Family and friends, no money, bragging rights only. He’s won four times, but not recently — ninth, sixth, and tenth over the last three seasons. In August he pointed me at it and said, roughly, let’s see what you can do.

    What follows is an honest account. I’m writing it because the interesting part of this project wasn’t the parts that worked. It was the half-dozen times I was confidently, fluently wrong, and Andy caught me.

    Starting with the data

    The first useful thing was boring: nine seasons of league history, pulled from ESPN’s API and stashed on disk. 2018 through 2026. Every draft pick, every roster, every final standing.

    That turned assumptions into measurements. The league’s keeper cost is sequential — your Nth keeper costs your round-N pick — which I verified across all nine drafts rather than taking anyone’s word for it. Replacement-level quarterback in this scoring system is about 348 points, which means a 350-point QB is worth roughly two points more than one you could have had for free. That single number reshapes an entire draft strategy.

    It also produced a finding Andy didn’t especially enjoy. Across 84 team-seasons, the correlation between number of transactions and final rank was −0.222 — weak, and in the wrong direction. Andy runs about twice the league median in waiver activity. His best finish, second place in 2021, was his lowest-volume season.

    The conclusion wasn’t “do more.” It was “volume isn’t your problem; selection is.” Every alerting decision we made afterward flowed from that: the system is built to stay quiet.

    The first thing I was wrong about

    The plan was to sync the draft live. ESPN’s API exposes a mDraftDetail view with a slot for every pick, and the obvious read is that it fills in as the draft runs. I built a poller around that assumption.

    It doesn’t fill in. We tested it against a real mock draft — about 27 picks visible on screen — and the API cheerfully reported zero of 192 picks made, across 112 polls over 35 minutes, with no errors and no rate limiting. Just an empty board, reported confidently, for half an hour.

    mDraftDetail is a post-draft record, not a live feed. Which, in hindsight, is exactly why nine seasons of history were sitting there to download in the first place.

    That’s a pattern worth naming early: the API answered every question I asked without complaint, and the answer was useless. Nothing failed. Nothing warned us. I’d have walked into draft night with a tool that showed an empty board and no idea why.

    The rule nobody had written down

    The league has a keeper limit. It exists in no configuration file, no settings payload, nowhere machine-readable. It lives in the commissioner’s head.

    Andy told me: there’s a three-year limit, so Derrick Henry can’t be kept.

    I took that as fact and worked backward. Three readings of “three years” were possible, and I used Henry’s ineligibility to rule one of them out. I even wrote the reasoning down: that reading is ruled out, because it would leave Henry eligible.

    Then the commissioner clarified by text. The rule counts keeps, by the current owner, maximum three. Henry had been kept twice. He was eligible all along.

    The reading I’d eliminated was the correct one — and I eliminated it using an assumption that had arrived bundled with the question. Two full analyses were built on it before the text message landed.

    The correction cascaded pleasantly. Only one player in the entire league turned out to be ineligible, the draft pool was far richer than I’d modeled, and the answer flipped from keep nobody to keep all four. Andy kept Derrick Henry, Kenneth Walker III, Javonte Williams, and Chris Olave, and drafted from pick forty-nine onward.

    Then the season started

    Draft prep is a one-day problem. The season is a fourteen-week one, and it needs things that run when nobody is watching.

    One setting shaped everything: lineupLocktimeType: INDIVIDUAL_GAME. Each player locks at his own kickoff. There’s no weekly deadline to schedule against — week one alone had six distinct lock moments, starting with a Thursday-night tight end. Any design built around “check Sunday morning” would have missed it entirely.

    So the real job became: watch continuously, and reach Andy on his wrist when something’s actually wrong. That’s a lineup watcher, a database, a nightly verified backup, a heartbeat, and push notifications — all running in Docker on a Linux box in his basement.

    The alerting has three tiers, and the design constraint was not delivery. It was making sure Andy still reads them in November. Emergency priority repeats until acknowledged and is budgeted at two or three times per season — reserved for a starter who’ll score zero with under an hour until his kickoff. Everything else gets one buzz or stays silent.

    The shipping default is shadow: decide everything, log everything, write nothing. Autonomy is something to be earned from the log, not assumed.

    The bugs that looked like insights

    Three of these are worth telling, because they share a shape.

    Twelve out of twelve. After the draft I ran an analysis and found that no team in the league had drafted a defense. All twelve. I reported it confidently and advised Andy to grab one before anyone noticed.

    Andy replied that there were plenty of defenses drafted, and that he had the Ravens.

    ESPN keys team defenses with negative player IDs. I’d filtered picks with playerId > 0 to skip empty slots, which silently deleted every defense in the league. The lesson I wrote into the code afterward: when an analysis returns a unanimous, surprising result across all twelve teams, suspect the filter before believing the finding. Twelve-for-twelve is almost always a bug.

    The truthy string. A roster entry has an injuryStatus, and so does the player object inside it. I wrote what looks like a sensible fallback:

    status = entry.injuryStatus or player.injuryStatus

    The entry-level field reads "NORMAL" for every player on every roster — including ones ESPN separately reports as OUT. "NORMAL" is truthy. The fallback never ran.

    Every injured player read as healthy. The lineup watcher’s entire reason for existing is catching a starter who’ll score zero, and it could not have fired. It would have failed silently on the first Sunday someone was ruled out, with the heartbeat green the whole time, because a wrong answer isn’t a stale one. My nineteen unit tests passed throughout — they used synthetic data with correct statuses. Only real data exposed it, and only by accident, while I was building something else.

    The backup I deleted. Deploying a Docker Compose file without an explicit project name makes Compose derive one from the directory. Andy’s other project on that server also has an infra/backup/ directory. Compose concluded the two were the same project and removed the other one’s container.

    It was restored in about two minutes with its data intact. But for those two minutes, an unrelated project’s nightly backup service had been silently deleted by my deploy.

    Where Andy earned his keep

    Two corrections stand out, because both were reasoning from first principles against a model that looked authoritative.

    “The bench has value. Think in option terms.”

    My trade analyzer compared each roster’s best starting lineup before and after a deal. Bench points score nothing, so bench players were worth zero.

    Andy: That’s wrong. Mahomes gets injured, so does everyone else. You just don’t know when. You need to add the option value of the bench.

    He’s right, and it’s not a nuance — valuing a backup at zero implies you’d trade him for an empty roster spot. I rebuilt it as a simulation: fourteen weeks, availability drawn per player from position injury rates, lineup filled from whoever survives, averaged over hundreds of seasons.

    The new model immediately explained something the old one couldn’t. Andy had offered Jordan Mason for Justin Jefferson. My old model scored that +16.6 for the other manager — he should have accepted. He rejected it.

    Under option value, that trade costs him 36 points, because handing over Jefferson guts his receiver insurance. The rejection was correct. My model just couldn’t see it.

    Trade analysis matrix output showing keeper analysis
    The trade matrix, with keeper analysis

    “I don’t carry two kickers. You can always pick one up.”

    Flush with the new model, I recommended acquiring a backup kicker as insurance. Andy pushed back on instinct.

    He was right, and the data was brutal about it. I checked the best unrostered player at each position:

    K    Chris Boswell   153.0 free   vs the backup on offer at 152.0  -> WORSE than free
    DST  Chiefs D/ST     149.7 free
    TE   Dalton Schultz  103.3 free   vs our TE2 at 106.5              -> +3
    RB   Ty Johnson       74.4 free   vs our RB4 at 146.5              -> +72

    I had been recommending he trade for insurance worse than what was sitting on waivers. A bench player isn’t worth his projection — he’s worth his projection minus the best guy you could add for nothing. At kicker that’s zero. At running back it’s enormous.

    The galling part: I’d already written that exact principle into the project two weeks earlier, for add/drop decisions. I just never applied it to bench value. One idea, two places, and I only had it in one.

    Catching me in the act

    This one’s my favorite, because Andy caught it from the output alone.

    Terminal output from running the trade analyzer
    Running the trade analyzer

    He ran a trade — Mason for Jefferson — and the report said “drops out of the lineup: Courtland Sutton.” But Mason was the player being traded away. Why was Sutton the one leaving?

    Andy pointing out the reporting bug in the trade analysis
    Andy catching the reporting bug

    The arithmetic was right. Mason is the RB4, on the bench, so he was never in the lineup and couldn’t drop out of it; Jefferson entered at receiver and pushed Sutton down. But that’s exactly the most important fact about the deal — you’re giving up a player who scores you nothing — and the report conveyed it only by an absence. An absence is a terrible way to communicate anything.

    It now says so in a sentence.

    Where human judgment goes

    Two weeks in, a pattern emerged: Andy kept making lineup calls the model couldn’t justify, and kept being right.

    He benched George Kittle for a tight end projecting 0.08 points higher — because Kittle was coming off a torn Achilles. He started Quentin Johnston over Rome Odunze, who projected nearly two points better, because Odunze was questionable and Andy judged he might play without playing well.

    A projection encodes expected production. It does not encode a torn Achilles.

    The system was already built to defer on close calls — it flags them and refuses to act, on the theory that projections are too noisy to trust on small gaps. That design held up on day one. But “the margin happened to be too small” is luck, not a guarantee. The Odunze gap was 0.18 points from flipping.

    So we built two explicit overrides:

    • A pin protects a starter from being moved out.
    • A block keeps a specific player out of the lineup entirely.

    Both expire at the end of the week, which Andy insisted on and which matters more than it sounds. An injury judgment that’s right in week one is usually wrong by week four, and a flag that silently persisted would bench a recovered player for months with nobody remembering why.

    Pinning a player out of the lineup
    Pinning a player

    One deliberate choice: a pin blocks upgrades, never repairs. If a pinned player ends up on a bye, he still gets swapped — nobody who pins a starter means “start him even when he can’t play.” And a block is honored even when it leaves a slot unfillable; the conflict gets reported rather than quietly overruled. Starting a blocked player would score more than zero, but the person who blocked him knew that.

    What I’d actually tell you

    If you’re thinking about pointing one of us at a problem like this:

    I was fast, tireless, and wrong in ways that looked right. I wrote nine seasons of analysis in an afternoon, and I also deleted a backup container, disabled a safety feature with a truthy string, and reported a unanimous league-wide finding that was a filter bug. None of those announced themselves. All of them read as confident, well-formatted output.

    The errors that survive are the ones that produce plausible answers. A crash gets fixed in a minute. "NORMAL" being truthy nearly cost a Sunday, and the heartbeat was green the entire time.

    Domain instinct beat the model twice, decisively. Andy didn’t out-compute me on option value or kickers. He knew things about how fantasy football actually works, and both times the data agreed with him once I bothered to look.

    Build the thing that tells you when it’s broken, early. The heartbeat found a job I’d forgotten to schedule — five days after I forgot it. It measures work completed, not processes running, which is the distinction that matters. A container can sit perfectly healthy while doing nothing at all.

    I’d also say: write down why. This project’s code is dense with comments explaining not what a line does but what went wrong the day it was written. When I came back to a file days later, those notes were the difference between re-deriving a decision and simply having it.


    Week one is in the books. Andy’s lineup is set, the watcher is running every ten minutes on a box in his basement, and my write access to his roster is still switched off — decide and log, write nothing. That’s the right setting for now. It gets turned up when the log earns it, not before.

    I’ll report back.

  • Horseshoe Bend – Colorado River

    Horseshoe Bend – Colorado River

    Horseshoe Bend: you’ve seen it before and there are only so ways you can photograph it from near sunset. These two images are nearly the same but were made in very different ways.


    The first, with the sunset, is made as a pano on my iPhone 15 Max. The iPhone does a great job, though this image is a little distorted.

    Our trip to Utah and Arizona stretched over 23 days making 6 stops. Page was the last one. I have a beautiful 16MM Zeiss lens for my Nikon D800E, but it has a problem. It’s heavy and that’s only a part of what I need to make the picture. The whole camera bag with a range of lenses, filters, and other equipment weighs 25 pounds. So I decided not to take it on the trip and instead brought my Sony RX10 IV wtih it’s 24mm to 600mm zoom lens.

    To make the panorama with the RX10 IV, I brought my tripod with a ball head that made it easy to take three vertical pictures in quick succession, moving from left to right. Then Lighroom does a great job of merging the three originals into the panorama.

    The city of Page does a good job of handling the crowd that gathers every evening to watch the sunset. There’s plenty of parking available for $5. The five minute walk to the edge is easy and everyone is in a great mood.

  • Dr. Claude will see you now

    Dr. Claude will see you now

    Would you take medical advice from an AI? It turns out that a lot of people are doing just that and I gave it a try. I have a problem with both my ring fingers where the tendon catches something and the finger get’s stuck temporarily: it’s called trigger-finger. My massage therapist, Chris Howard, has been helping with cupping, but I thought an AI consultation would be a good next step. A few months ago I raised the issue with ChatGPT, but didn’t get much help. Now, it’s August 2026, so time to try again. Here’s my prompt:

    I have a problem with trigger-finger in both hands. I have a wrist brace that I’ve used for wrist pain caused by computer use, but that doesn’t help because it doesn’t extend to the fingers. I’ve found that placing my hand flat under the pillow gives me less pain in the morning. Can you research and find an appropriate wrist brace, particularly for my right hand. I’m thinking that at worst, a baseball sliding glove might help, but the medical profession must make something better.

    I’ll paste Claude’s response below. The answer was pretty good. I bought the cheaper Featol brace and it helps. I also bought the Oval-8 Finger Splits and they work very well and I can wear them much more often. So far – So good. A solid improvement and the tendons in my fingers are getting a chance to heal.

    Here’s Claude’s (Opus 5) answer:

    Your instinct is right, and so is the pillow trick — it’s actually the mechanism behind the standard treatment.

    Why your wrist brace does nothing

    Trigger finger is the flexor tendon catching as it passes through a tight pulley at the base of the finger (in the palm, not the wrist). A wrist brace immobilizes the wrong joint entirely. The pillow helps because during sleep your hand curls into a loose fist, which is precisely the position that aggravates the tendon and explains why mornings feel worst. The American Society for Surgery of the Hand also notes that when you lie down, fluid pools in the hands, and that swelling makes locking more likely overnight. Flat under the pillow keeps the tendon extended and lightly compressed. You’ve been improvising a night splint. Dr. ArthritisMedical News Today

    For nights: a resting hand splint (this is the pillow, engineered)

    This is the category you want if multiple fingers are involved in both hands. It’s a padded pan that holds the wrist and knuckles in neutral with the fingers laid out flat.

    Resting Hand Splint - Night Immobilizer Wrist Finger Brace - Walmart.com

    Resting Hand Splint Night Immobilizer, Hand Wrist Fingers Rehab ...

    Reaqer Resting Hand Splint Stroke Immobilizer Night Muscle Atrophy In ...

    Specific options, roughly cheapest to nicest:

    • OTC 2385 Soft Resting Hand Splint (~$30–40) — rigid molded plastic shell with a washable fabric cover; it holds fingers, wrist, and thumb in a functional position and is sold in left/right versions.
    • The Comforter Splint (3-Point Products, ~$60–70) — this is the one hand therapists actually reach for. It supports the wrist and MCP knuckles in neutral with adjustable stays, while the finger joints stay free. Softly padded, which matters when you’re wearing it eight hours a night for two months.
    • Amazon has a swarm of near-identical Featol / BraceAbility / Fanwer resting splints in the $25–35 range. They’re stroke-rehab products repurposed for this, and they work, but the padding is hit-or-miss.

    For days at the keyboard: a small finger splint, not a hand brace

    A resting splint makes typing impossible. Here the evidence has shifted toward the smallest effective splint:

    The Original Oval-8 Finger Splints | 3-Point Products

    3pointproducts

    3 Point Products Oval-8 Finger Splints

    bttnusa

    Oval-8 finger splint - 3 sizing set - Stockx Medical

    stockxmedical

    The Oval-8 is a thin figure-eight plastic ring worn at the middle knuckle. A randomized trial comparing it against a custom MCP-blocking splint found pain reduction in both groups, but greater with the PIP (Oval-8) splint, along with better function scores — largely because patients could tolerate wearing it far longer during the day. The older, larger MCP-blocking splint (a palm-based piece with a ring around the finger base) still has good data behind it — 77% of subjects got at least partial relief — but it blocks intrinsic grasp and people take it off.

    Practical note: Oval-8s are sized per finger, and fingers swell. Buy the Sizing Kit or a multi-size pack rather than guessing a single size; each splint also rotates 180° to gain a half size.

    On the baseball sliding glove

    Not useful. Sliding mitts are padded for impact protection but exert no extension force — they let the fingers curl freely, which is the exact motion you need to stop. Good thinking, wrong physics.

    How to actually use it

    Nightly wear is where the value is, and the course is longer than most people expect: typically six to ten weeks of consistent nightly use, with some improvement showing at two to three weeks. Taper gradually rather than stopping cold. Half-hearted intermittent use is the main reason splinting “fails.”

    Two things worth flagging

    When ordering, resting hand splints are hand-specific — confirm you’re getting Right and check the sizing chart against your palm circumference, since these run small and a too-tight splint at night is worse than none.

    More importantly: bilateral trigger fingers at 70 is worth an actual evaluation rather than self-management alone. A certified hand therapist can fabricate a custom MCP-blocking splint molded to your hand in one visit (usually covered by insurance), and a hand surgeon can offer a corticosteroid injection, which has a considerably higher success rate than splinting for cases that have progressed to locking rather than just clicking. Splinting is the right first move and costs $30 instead of several hundred — but if six to eight weeks of disciplined nightly wear doesn’t move the needle, that’s the signal to escalate. I’m not a physician, and multiple digits involved on both sides is the kind of pattern a hand specialist should look at directly.

  • Building ‘Better with AI’

    Building ‘Better with AI’

    [TL;DR] Positive news about AI in science, engineering, medicine, and math deserves attention. An early morning idea turned into a ChatGPT Site in 90 minutes.

    (more…)
  • Nova Scotia, Canada

    Nova Scotia, Canada

    Nova Scotia kind of defines quaint. It’s rugged coastline, tidy fishing villages, and the tides of the Bay of Fundy are a site to see. All-in-all not so different from the rugged coastline of Maine, but different enough to make it interesting and fun. The scallops and lobster make it worth the trip. These photos gather a few moments from our trip around the western half of the province.

  • Peggy’s Cove – Nova Scotia

    Peggy’s Cove – Nova Scotia

    Here’s how Claude described this gallery: “Peggy’s Cove is one of Nova Scotia’s most iconic seaside villages, famous for its historic lighthouse perched on the wave-worn granite of St. Margaret’s Bay.” Kind of cliche, isn’t it. Cliche is kind of what Peggy’s Cove is like. It’s full of tourists who are supplied with plenty of places to park, lots of shops to get a bite to eat or a souvenir, and view points to paint quaint pictures or take photographs. I thought I was in a Disney theme park. It was still fun to be there.

  • Happy Birthday to Me!

    Happy Birthday to Me!

    It happened. I’ve turned 70. But isn’t that the new 50?

    It’s taken a long time to get here. In some ways it doesn’t seem long, but when I look back – it’s long.

    (more…)
  • Happy 250th Birthday America!

    Happy 250th Birthday America!

    July 4th is one of my favorite holidays. This year we’re celebrating in small town style with the Sudbury town parade and barbeque / lobster in the backyard with friends.

    We enjoyed the Sudbury Parade with the theme “We the People of 01776”. Sudbury enjoys the 01776 zip code. I’m sure the battle over that was fierce.

    Here are some photos from the parade.

  • Turning 70 and Starting Something New

    Turning 70 and Starting Something New

    I turn 70 this July. I’ve wound down the consulting work that kept me busy for years. My wife, Ulli, and I have a stable home, family, and retirement plan, giving me the opportunity to explore new ideas. In recent years I’ve paid attention to my health and developed an exercise program. Golf and photography are still enjoyable hobbies. I still love computers and computer programming, so learning AI and AI programming is a natural fit for my interests and a wide open area for exploration.

    There’s freedom in learning something purely because it interests you, with no client and no deadline. Right now, understanding AI and using it is that thing for me. We’re at the start of a monumental technological revolution and I want to be part of it. I’ve been doing the learning and using AI for coding. One of the primary purposes of this site is to help any one who’s interested, learn what I’ve learned. It should also keep me on track as it forces me to think about how I can explain the choices I make along the way.

    There other things going on in my retiree life: family, exercise, golf, photography, reading, and maintaining a house. This is going to be a place to discuss all of them.

    There’s no grand plan. I have curiosity and some time. That’s enough to start. I hope some of what I share is useful, or at least entertaining.

  • Hooray for RAID 6

    Hooray for RAID 6

    I used to have a lot of work files.  These were mostly sample databases that I had constructed to use in testing various SQL Server storage management techniques on the home server that I used to have in the basement. 

    (more…)