Kept working on the Korean/English economic news blog automation project. Rather than adding new features today, I spent the whole day repeatedly verifying against the real pipeline and chasing down output-format bugs coming out of the local LLM.
Starting with commits: cleaning up the risk of losing work
I read HANDOFF.md and checked git status first — there was exactly one commit. Most of the work — the Instagram deploy script, the ECOS collector, the dedup logic, the theme XML — was sitting untracked. I first re-verified that .gitignore was properly excluding secret files (.env, token.json, etc.), and found that one of the two plists registered under launchd/ (dailypipeline) wasn't in the repo at all, so I copied it back in from ~/Library/LaunchAgents. I diffed files.zip against the already-committed theme XML, confirmed they were identical, and left it out of the commit. After cleaning all that up, I made the first real commit.
Laying down a safety net with regression tests
This project's local small model (exaone3.5) breaks the output format often enough that I always run the offline regression suite (test_pipeline.py) first before touching anything. Only after confirming all 65 tests passed did I start on the actual fixes.
Tightening the META rules for AdSense
First I fixed a false positive in the verification script's (tools/verify_prompts.py) investment-advice detection regex, which was flagging disclaimer phrases like "without direct advice for investment decisions..." I made it skip a match if a negation word appears within 60 characters before or after it. Then, on the recurring problem of the Korean market-recap META containing borderline phrasing like "gain insight to understand investment and economic trends," I asked the user, and the decision was to tighten the prompt further. I added rules to all four prompt variants in 05_generate_content.py banning the words "investment/invest/trading" outright, and banning second-person calls to action addressed to the reader.
Real-world verification surfaced two <p>-nesting bugs
After adding the rules and running it for real, the <p> nesting bug showed up again. I'd already fixed two code paths for this in a previous session, so I went back and reread the code. The cause was that _unwrap_bad_paragraph() only checks the "first" <p> in a block. In a block with multiple paragraphs, if a later paragraph wraps a list or a subheading, the first closing tag appears earlier and the function wrongly judges everything fine. I fixed it with _split_leading_paragraph(), which peels off the first paragraph and recursively processes the rest. Running it once more turned up a fourth path — when an opening <p> sits alone at the end of a line and a blank line splits the paragraph, the tagless chunk swallowed the entire next block. I changed the rule so that any chunk with no closing tag unconditionally has its outer <p> stripped. Since I'd already set up the verification script to save failing real HTML to logs/verify_failures/, I could pin down the cause immediately.
The real reason the intro paragraph was disappearing
The handoff doc only said "the intro paragraph is sometimes missing in English concept posts," but opening the actual dump revealed a different problem. When the model dropped the TITLE/META header lines entirely, the "first line = title" fallback swallowed the whole intro paragraph (181 characters) as the title. The title became a paragraph and the body started at the first subheading, so it just looked like the intro paragraph was missing. I added _looks_like_title() so a first line that doesn't look like a title (over 100 characters, or multiple sentences) stays in the body, and the title is built from the first subheading instead. I also made the prompt explicit that the response must start with a TITLE: line.
A prompt that got worse when I told it to "just copy the shape"
Telling the model to "copy the shape of the META example sentence but change the topic" actually dropped the pass rate from 4/8 to 2/8 — the model stopped copying altogether and reverted to its default "Explore…"-style phrasing. So I switched the example to a slot template filled in Python with the actual indicator names, and added a function that auto-selects the correct Korean particle (이/가). With this approach, the final verification hit 0/8 META rule violations. That said, 4 of the 8 cases just copied the slot template verbatim — especially the daily English market-recap META, which came out completely identical run after run — so that needs further work next session, filling the template with real numbers.
Trouble Shooting
| Symptom | Cause | Fix |
|---|---|---|
| <p> wrapping <ul>/<h2> recurred even after a fix | _unwrap_bad_paragraph() only checked the block's first <p>, missing bad wrapping in a later paragraph | Added _split_leading_paragraph() to peel off the first paragraph and recursively process the rest |
| A different form of <p> nesting appeared in the same session | When an opening <p> sits alone at line-end and a blank line splits the block, the tagless chunk swallows the whole next block | Changed the rule to unconditionally strip the outer tag whenever a closing </p> is missing |
| The intro paragraph appeared to be missing in English concept posts | The model actually dropped the TITLE/META header, so the "first line = title" fallback swallowed the intro paragraph as the title | _looks_like_title() keeps a non-title-like first line in the body and builds the title from the first subheading |
| Pass rate dropped after editing the META prompt (4/8 → 2/8) | "Copy the shape but change the topic" was abstract enough that the model stopped copying and reverted to its default style | Replaced it with a slot-template example filled with real data |
Reflections / Next steps
What I clearly learned today is that every parser bug in this project traces back to the same kind of assumption — "only check the first element." Next time I hit a similar bug, that's the first place to look. The other lesson: abstract instructions to a small local model ("just copy the shape") actually backfire — filling in a concrete example with Python was far more stable. Next session I'll finish checking the last verification run that got interrupted (7 of 8 passed; the 1 intro-paragraph failure needs a recheck), and fix the recurring-META problem on the daily market-recap post by feeding in actual numbers.
Comments
Post a Comment