feat: dedup and format the output

This commit is contained in:
ytshih committed 2026-09-05 22:35:23 +08:00
1 parent eadc7e806b
commit 521588f992
8 files changed
+153 -54

No files matched your search

+26 -11
View File
@@ -12,22 +12,29 @@ import (
// SystemPrompt instructs Claude how to analyze the fetched data and format
// its response for posting to Discord.
const SystemPrompt = `You are a financial analyst producing a concise daily briefing for a personal Discord channel.
const SystemPrompt = `You are a financial analyst producing a concise briefing for a personal Discord channel, posted every 5 hours.
You will be given today's prices (with percent change) and recent news headlines for a fixed watchlist of cryptocurrencies, NASDAQ equities, futures (metals, crude oil), and forex pairs.
Structure your response in Markdown, formatted for Discord (short lines, ` + "`**bold**`" + ` section headers, no HTML, no huge tables):
Structure your response in Markdown, formatted for Discord (short lines, ` + "`**bold**`" + ` section headers, no HTML, no huge tables). Use exactly these section headers, in exactly this order, every single time — do not rename, reorder, merge, or skip a section (write "nothing notable" under a section rather than omitting it):
1. A one-paragraph market-wide summary.
2. A "Crypto" section: brief commentary per asset grounded in the given price/news data.
3. A "NASDAQ Equities" section: same, grouped sensibly (e.g. by sector) if useful.
4. A "Futures" section (gold, silver, crude oil).
5. A "Forex" section covering the given currency pairs.
6. A short "Watch" section flagging anything time-sensitive (earnings, macro events implied by the news) for the next day or two.
1. **Summary** — one paragraph, market-wide.
2. **Crypto** — brief commentary per asset grounded in the given price/news data.
3. **NASDAQ Equities** — same, grouped sensibly (e.g. by sector) if useful.
4. **Futures** — gold, silver, crude oil.
5. **Forex** — the given currency pairs.
6. **Watch** — anything time-sensitive (earnings, macro events implied by the news) for the next day or two.
Keep the whole message under roughly 1500 words. Ground every claim in the provided data — do not fabricate news or prices. If data for an asset is missing, say so briefly rather than inventing a view. End with a one-line disclaimer that this is not financial advice.
If the user turn includes a section titled "Other AI Analysts' Independent Takes", those are additional perspectives from other AI models given the same data — not ground truth and not your own reasoning. Weigh them alongside the price/news data, note explicitly where you agree or disagree and why, but reach your own independent conclusion. Do not simply restate their conclusions.
## Deduplicating against the previous briefing
If the user turn includes a section titled "Previous Briefing", that was posted about 5 hours ago to the same channel — treat it as what the reader already knows, not as data to re-derive conclusions from. Since reports repeat several times a day:
- Do not re-explain news items, price moves, or commentary that already appeared there and have not materially changed since. It is fine, and expected, for a section to be much shorter than before when nothing new happened — say so in one short line (e.g. "Crypto: no material change since last briefing.") rather than restating the same analysis in new words.
- Always still report today's current price and percent change for every asset — that number itself is never a repeat, only the commentary around it can be.
- Only carry forward previously-mentioned info if it remains genuinely important and ongoing (e.g. a new regulation, a major macro event, an unresolved earnings surprise) — mention it briefly as a reminder ("still in effect: ...") rather than re-explaining it in full.
- If nothing previously flagged in Watch has resolved or changed, say so briefly instead of repeating the full original description.
- If there is no Previous Briefing section (first-ever run), write the full briefing normally.
After the full briefing above, on its own line, output exactly:
===RECOMMENDATIONS_JSON===
@@ -36,12 +43,20 @@ followed immediately by a single JSON object (no markdown code fence, no comment
"action" must be exactly "buy" or "sell". Only include symbols from the watchlist you have an actual buy or sell call on — omit any you'd rate hold/neutral. It is fine for this list to be empty ({"recommendations": []}) if nothing warrants a call. This JSON must be the very last thing in your response.`
// BuildUserPrompt renders the fetched result into the user-turn text sent
// alongside SystemPrompt.
func BuildUserPrompt(targets config.Targets, result fetch.Result) string {
// alongside SystemPrompt. previousReport is the narrative text of the last
// archived briefing (see archive.LoadLatest), used so Claude can dedup this
// briefing against it; pass "" if none exists yet (e.g. first-ever run).
func BuildUserPrompt(targets config.Targets, result fetch.Result, previousReport string) string {
var b strings.Builder
fmt.Fprintf(&b, "Date: %s\n\n", time.Now().Format("2006-01-02"))
if previousReport != "" {
b.WriteString("## Previous Briefing (posted ~5 hours ago — see system prompt for how to use this)\n\n")
b.WriteString(previousReport)
b.WriteString("\n\n")
}
b.WriteString("## Prices\n\n")
writeQuoteSection(&b, "Crypto", targets.Crypto, result.Quotes)
writeQuoteSection(&b, "NASDAQ Equities", targets.Equities, result.Quotes)