Technical breakdown
How Netpost works
Netpost turns roughly 75 live industry items into five publishable LinkedIn posts in a single request. This page documents the scoring maths, the linter rules and the similarity thresholds that decide what actually reaches you.
Trend scoring
Every ingested item is scored with the same formula: keyword relevance × source weight + freshness bonus. Keyword relevance sums the weights of the scoring terms found in the item's title and summary. The freshness bonus rewards recency on a linear ramp — max(0, 24 − min(age_hours, 24)) / 12 — worth up to 2.0 for something published in the last few minutes and nothing at all after 24 hours.
Keyword weights
| Keyword | Weight |
|---|---|
| fintech | 2.5 |
| automation | 2.3 |
| banking | 2.0 |
| investment | 2.0 |
| underwriting | 1.8 |
| diligence | 1.8 |
| deal | 1.8 |
| workflow | 1.8 |
| ai | 1.7 |
| compliance | 1.7 |
| payments | 1.6 |
| agent | 1.5 |
| fraud | 1.4 |
| data | 1.2 |
Deduplication
Titles are lowercased and stripped of punctuation, then hashed to a SHA-1 fingerprint. Matching fingerprints collapse to a single item, which is what keeps a story syndicated across five outlets from occupying five slots in the brief.
The anti-slop linter
Each generated post starts at 100 and loses points for every flag raised. The score and the flags are shown on the card, so the judgement is visible rather than hidden behind a silent filter.
| Flag | What it catches |
|---|---|
| Weak hook | Opens with "I", "In my experience" or "As a" — the openers that signal a post nobody will stop scrolling for. |
| Vague claims | "Amazing", "incredible", "game-changing" and similar adjectives that assert impact without evidence. |
| Generic filler | "A lot of", "everyone knows" and other phrases that take up space without adding information. |
| Missing credibility | No metric and no named authority. A post should cite a number or a source like the SEC, McKinsey or Bloomberg. |
| Poor readability | Fewer than three paragraphs, or any single paragraph over 65 words. LinkedIn is read on phones. |
| Hashtag spam | More than five hashtags. |
| Filler CTA | "Feel free to", "hope this helps" and other soft closers that ask for nothing. |
| Corporate conclusion | "In conclusion", "to summarize" — essay scaffolding that does not belong in a feed. |
| Too many tags | More than two tagging hints. |
Similarity checking
Every draft is compared against the style corpus and previously generated posts using a weighted blend: 35% token overlap (Jaccard), 40% shingle overlap on 3-grams, and 25% cosine similarity. The blended score maps to three states.
- Below 25 — clear. The draft is materially different from anything it was compared against.
- 25 to 45 — review. Close enough to a known pattern to be worth a read before publishing. The matches are shown on the card.
- 45 and above — blocked. The batch is regenerated, up to three attempts, before the request fails with a real error.
Why repeat clicks do not repeat themselves
Freshness is enforced rather than hoped for. Each batch carries a UUID nonce, a fresh angle target drawn from eight banking-specific framings, and a list of recently generated hooks the model is instructed to avoid. Trend IDs used in recent batches are rotated out when enough alternatives exist, and duplicate content within a single batch is rejected outright.
Architecture
The Next.js frontend on Vercel calls a FastAPI backend on Render, which fans out to the trend sources with asyncio.gather, generates through the OpenAI chat completions API with structured JSON output, and persists trend events, batches, posts and feedback to Supabase over its REST API. If AI or database configuration is missing, the backend fails loudly with a real error rather than serving placeholder content.
Source is on GitHub. Back to the generator.