Proof of concept: persist Brand Lookup runs server-side - #265
Open
harigenhealth wants to merge 1 commit into
Open
Proof of concept: persist Brand Lookup runs server-side#265harigenhealth wants to merge 1 commit into
harigenhealth wants to merge 1 commit into
Conversation
Brand Lookup fans out to several paid DataForSEO LLM-mentions endpoints, but its only memory was a 24h R2 result cache plus a localStorage list of queries. That made the two things the feature is actually for impossible: - History was per-browser. A lookup run in one browser was invisible in another, and on a fresh profile the page looked like nothing had ever run. - Nothing kept what a run RETURNED. Once the cache expired, re-opening a past lookup re-charged DataForSEO and could come back with different numbers, so month-over-month comparison had nothing to compare against. Adds a brand_lookup_runs table storing the query, resolved target, scope, competitor set, the headline metrics as typed columns, and the full result as a JSON payload. Every successful lookup is saved; a Saved lookups panel lists them and re-opens one from storage, which costs nothing. Notes on the shape: - Headline metrics are lifted into columns so a trend reads without parsing every payload, while the payload keeps everything for re-render. - Runs are capped at 100 per project and pruned on write, since payloads are large enough that an unbounded table would grow unnoticed. - A failed save logs and is swallowed: it must not fail the lookup the user just paid for. - Stored payloads are re-validated with brandLookupResultSchema on read, so a payload written by an older result shape is not trusted blindly. - The Postgres schema is hand-written to match, per the parity test contract. The localStorage history is kept as a fast per-browser jump list; this adds durable storage alongside it rather than replacing it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opening this as a proof of concept for an issue, not a merge request — I read
docs/CONTRIBUTING.mdand know external PRs aren't being merged right now. Feel free to close it and take the idea, or ignore it entirely.Being upfront on the thing you called out in CONTRIBUTING: this was written with Claude Code. It is tested rather than just generated — details below — but you should weigh it accordingly.
The problem
Brand Lookup fans out to several paid DataForSEO LLM-mentions endpoints, but nothing keeps what a run returned. Results cache in R2 for 24h and the "Recent searches" list is
localStorage. Two consequences:That second one is what pushed me to build something. I'm tracking whether a company shows up in AI answers over time, so I need to compare this month's run to last month's. Right now there's nothing to compare against — the data I paid for is gone in a day.
What this does
Adds a
brand_lookup_runstable storing the query, resolved target, scope, competitor set, headline metrics as typed columns, and the full result as a JSON payload. Every successful lookup saves. A "Saved lookups" panel lists them and re-opens one from storage, which costs nothing.Shape decisions, in case any are useful:
brandLookupResultSchemaon read, so one written by an older result shape isn't trusted blindly.localStoragehistory is kept as a fast per-browser jump list rather than replaced.Testing
tsc --noEmitandoxlint --type-awareboth clean.db:generateand applied locally.Happy to write this up as a plain issue instead if that's more useful.
🤖 Generated with Claude Code