Skip to content

feat: add NHC tropical cyclone auto-posting#618

Merged
full-bars merged 14 commits into
mainfrom
feat/tropical
Jul 23, 2026
Merged

feat: add NHC tropical cyclone auto-posting#618
full-bars merged 14 commits into
mainfrom
feat/tropical

Conversation

@full-bars

@full-bars full-bars commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Adds a new TropicalCog that auto-posts NHC tropical cyclone products (advisories, discussions, outlooks, updates) to Discord.

Sources:

  • IEMBot NHC feed (/room/nhc) — primary, polls every 30s
  • NWWS XMPP firehose — fallback via nwws.py
  • IEMBot botstalk — secondary fallback

Product types: TCV/TCP (advisory), TCD (discussion), TWD (tropical weather discussion), TWO (tropical weather outlook), TCU (update), TCE (position estimate)

Features:

  • Parses storm name, type, position, winds, pressure, movement from advisory text
  • Posts summary embed to channel + full text in a thread
  • Storm-type emoji (🌀 hurricane, 🌧️ storm, 🌨️ depression, etc.)
  • Dedup via product_id set
  • Configurable channel via TROPICAL_CHANNEL_ID config
  • cache/nwws_products.log for debugging NHC product flow

Files changed: cogs/tropical.py, cogs/iembot.py, cogs/nwws.py, cogs/__init__.py, config.py, config/products.json

Summary by CodeRabbit

  • New Features
    • Added automatic posting of NHC tropical cyclone products from both IEMBot and NWWS sources.
    • Posts storm summaries with storm-type indicators, source/product details, and opens discussion threads with the full product text.
    • Added configurable tropical channel routing, including an override via warning_channel:tropical.
  • Bug Fixes
    • Improved tropical summary parsing and prevented posting failures when summaries are missing.
    • Corrected swapped NHC product labels and tightened summary extraction.
  • Documentation
    • Documented TROPICAL_CHANNEL_ID and the supported tropical product types.
  • Tests
    • Added regression tests for summary parsing, classification, and posting/thread behavior.

full-bars added 11 commits July 21, 2026 21:24
Routes TCV/TCD/TWD/TCU products from IEMBot botstalk feed and NWWS
XMPP to a new TropicalCog. Posts formatted embeds with storm
classification, name, summary, and product type to a Discord channel.
Uses dev channel for testing, production channel when ready.
post_tropical_product crashed on every real NHC product because
parsed["summary"] is None (not missing) for most product types, and
dict.get(key, default) doesn't apply the default when the key is
present with a None value. No tropical product had ever successfully
posted before this fix.

Also switches from the hardcoded dev channel to a configurable
TROPICAL_CHANNEL_ID (defaults to the existing prod channel), with a
state-store override hook for future runtime configuration.
TCP (the actual Public Advisory, containing 'ADVISORY NUMBER X') was
mislabeled PROBABILITIES, and TCV (the Watch/Warning summary product)
was mislabeled ADVISORY. Confirmed live: Hurricane Fausto Advisory 14
and Tropical Storm Bertha Advisory 12 both posted as 'NHC
PROBABILITIES' instead of 'NHC ADVISORY'. Corrected per NHC's product
descriptions (nhc.noaa.gov/aboutnhcprod.shtml).
The summary block only stopped at a marker Public Advisories never
contain, so it silently captured everything to the end of the
product (watches/warnings, full discussion, hazards, next advisory,
signature) and posted it all inline in the channel. Now bounded to
the next section header (all-caps line + dashed underline), matching
NHC's own document structure.

Also adds a discussion thread on the channel message carrying the
full raw product text, matching the SPC Outlook/MD thread convention
— the channel message stays a short summary.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@full-bars, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 16 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0aa25e0f-727a-4b1e-9881-d6154c087d8b

📥 Commits

Reviewing files that changed from the base of the PR and between c277c2d and 98ac94c.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • cogs/tropical.py
📝 Walkthrough

Walkthrough

Adds a Tropical Discord cog that parses and posts NHC products, routes products from IEMBot and NWWS feeds, supports configurable channels and threaded full text, and adds tests and documentation for the new behavior.

Changes

NHC Tropical Product Auto-Posting

Layer / File(s) Summary
Configuration and cog registration
config.py, config/products.json, cogs/__init__.py
Adds tropical channel and IEM NHC feed configuration, registers cogs.tropical, and reformats existing product configuration without changing values.
Tropical parsing and Discord posting
cogs/tropical.py, tests/test_tropical.py
Adds product classification, bounded summary parsing, channel resolution, deduplication, summary embeds, discussion threads, full-text embeds, and regression tests.
Feed routing and polling
cogs/iembot.py, cogs/nwws.py
Routes tropical products from NWWS and IEMBot sources and adds cursor-based IEM NHC polling with lifecycle and error handling.
Environment documentation and release notes
.env.example, CONTRIBUTING.md, PROJECT_STRUCTURE.md, CHANGELOG.md, .wiki
Documents the tropical channel setting, Tropical cog structure, release behavior, and updated wiki submodule reference.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant IEMBotCog
  participant NWWS
  participant TropicalCog
  participant DiscordChannel
  participant DiscussionThread
  IEMBotCog->>TropicalCog: Submit tropical product
  NWWS->>TropicalCog: Submit tropical product
  TropicalCog->>DiscordChannel: Post summary embed
  DiscordChannel->>DiscussionThread: Create discussion thread
  TropicalCog->>DiscussionThread: Post full product text
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding NHC tropical cyclone auto-posting.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tropical

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
cogs/nwws.py (1)

31-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Product log path hardcoded instead of using the configured cache directory.

config.py already exposes a configurable cache directory (cache_file_dir/CACHE_DIR) and a parallel nwws_firehose_log config entry for the raw firehose dump. This new handler instead hardcodes the literal "cache/nwws_products.log", so if an operator changes CACHE_DIR, this log ends up in a different, unconfigured location (and a missing cache/ directory would raise at handler-creation time, which — since it's swallowed by the caller's bare except: pass — would fail silently forever).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cogs/nwws.py` around lines 31 - 49, Update _ensure_product_log to derive the
RotatingFileHandler path from the configured cache directory
(cache_file_dir/CACHE_DIR) and the configured product-log filename, rather than
hardcoding "cache/nwws_products.log". Ensure the configured directory exists
before creating the handler so initialization does not silently fail when it is
absent.
config.py (1)

105-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded channel-ID fallback breaks the established config pattern.

Every other optional channel (dev_channel_id, sounding_channel_id, etc.) falls back through _optional_int(...) to another configured env key. tropical_channel_id instead bakes a literal production Discord snowflake directly into source. This is inconsistent with the rest of the file and is awkward for anyone self-hosting/forking this bot — the default silently points at a channel ID they don't own, with no configured chain to fall back to.

♻️ Suggested fix
-    "tropical_channel_id": int(os.getenv("TROPICAL_CHANNEL_ID") or 981540312688230420),
+    "tropical_channel_id": _optional_int("TROPICAL_CHANNEL_ID", "SPC_CHANNEL_ID"),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@config.py` at line 105, Replace the literal fallback in the
tropical_channel_id configuration with the established _optional_int(...)
pattern, using the appropriate configured environment-key fallback consistent
with dev_channel_id and sounding_channel_id. Preserve integer parsing while
ensuring self-hosted deployments do not silently default to a hardcoded
production channel ID.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.wiki:
- Line 1: Add a .gitmodules declaration for the .wiki submodule, specifying its
path and repository URL, and verify that the gitlink’s pinned commit exists in
the configured remote so fresh clones and CI can initialize it successfully.

In `@cogs/nwws.py`:
- Around line 617-628: Update the NWWS tropical dispatch in the handler
containing `post_tropical_product` so it does not pass the raw `pil_prefix` code
as the third argument. Pass no resolved prefix (or otherwise let
`post_tropical_product` invoke `_classify_product(product_id)`), while
preserving the existing tropical PIL detection and NWWS source value.

In `@cogs/tropical.py`:
- Around line 37-46: Reorder STORM_TYPE_ORDER so specific storm types precede
the generic terms they contain: place “MAJOR HURRICANE” before “HURRICANE” and
“SUBTROPICAL DEPRESSION” before “TROPICAL DEPRESSION.” Preserve the existing
classifications and ordering of unrelated storm types.
- Around line 228-233: Update the full-text posting flow around full_text_embed
so parsed["raw_text"] is delivered in chunks that respect Discord’s
4096-character embed description limit, rather than slicing it to the first 4096
characters. Send the chunks to the existing target (thread or channel) in order
and preserve the full product text, including its tail.
- Around line 59-69: Update _extract_storm_name so it returns only the first
token after the matched storm type, rather than the entire remaining line.
Preserve the existing trimming and title-casing behavior, ensuring headlines
such as “HURRICANE FAUSTO ADVISORY NUMBER 14” produce “Fausto” while simple
names like “HURRICANE ANNA” still work.
- Around line 169-193: Update post_tropical_product to use the supplied raw_text
when available instead of unconditionally calling _fetch_nhc_product. Extract
the response-text parsing logic from _fetch_nhc_product into a reusable
_parse_nhc_text helper, parse raw_text directly, and retain the fetch path only
when raw_text is absent.

---

Nitpick comments:
In `@cogs/nwws.py`:
- Around line 31-49: Update _ensure_product_log to derive the
RotatingFileHandler path from the configured cache directory
(cache_file_dir/CACHE_DIR) and the configured product-log filename, rather than
hardcoding "cache/nwws_products.log". Ensure the configured directory exists
before creating the handler so initialization does not silently fail when it is
absent.

In `@config.py`:
- Line 105: Replace the literal fallback in the tropical_channel_id
configuration with the established _optional_int(...) pattern, using the
appropriate configured environment-key fallback consistent with dev_channel_id
and sounding_channel_id. Preserve integer parsing while ensuring self-hosted
deployments do not silently default to a hardcoded production channel ID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ca705dd-4ba6-4a09-930f-815e2fae0c6c

📥 Commits

Reviewing files that changed from the base of the PR and between 131d4d7 and de3b77d.

📒 Files selected for processing (12)
  • .env.example
  • .wiki
  • CHANGELOG.md
  • CONTRIBUTING.md
  • PROJECT_STRUCTURE.md
  • cogs/__init__.py
  • cogs/iembot.py
  • cogs/nwws.py
  • cogs/tropical.py
  • config.py
  • config/products.json
  • tests/test_tropical.py

Comment thread .wiki
@@ -0,0 +1 @@
Subproject commit 869e0f950b9d4f8d6caf7f1ef2edd3e6c0de2431

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

git config -f .gitmodules --get-regexp '^submodule\..*\.(path|url)$'
git ls-tree HEAD .wiki
git -C .wiki rev-parse --verify 869e0f950b9d4f8d6caf7f1ef2edd3e6c0de2431^{commit}

Repository: full-bars/spc-bot

Length of output: 392


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '## root files'
git ls-files .gitmodules .wiki || true

echo
echo '## .gitmodules'
if [ -f .gitmodules ]; then
  cat -n .gitmodules
else
  echo '(missing)'
fi

echo
echo '## tree entry for .wiki'
git ls-tree HEAD .wiki

echo
echo '## submodule metadata from index'
git submodule status -- .wiki || true

echo
echo '## commit object in repo'
git cat-file -t 869e0f950b9d4f8d6caf7f1ef2edd3e6c0de2431 || true

Repository: full-bars/spc-bot

Length of output: 585


Add a .gitmodules entry for .wiki
A gitlink alone won’t initialize in fresh clones or CI; declare the submodule path/url in .gitmodules and ensure the pinned commit is available from that remote.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.wiki at line 1, Add a .gitmodules declaration for the .wiki submodule,
specifying its path and repository URL, and verify that the gitlink’s pinned
commit exists in the configured remote so fresh clones and CI can initialize it
successfully.

Comment thread cogs/nwws.py
Comment on lines +617 to +628
elif any(
afos_pil.startswith(x) for x in ("TCV", "TCD", "TWD", "TCU", "TWO", "TCE", "TCP")
):
tropical_cog = self.bot.get_cog("Tropical")
if tropical_cog:
nhc_pils = ("TCV", "TCD", "TWD", "TCU", "TWO", "TCE", "TCP")
pil_prefix = next(p for p in nhc_pils if afos_pil.startswith(p))
await tropical_cog.post_tropical_product(
product_id, raw_text, pil_prefix, source="NWWS"
)
logger.info(f"Triggered NHC {pil_prefix} via XMPP")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

NWWS path passes the raw AFOS PIL as pil_prefix, bypassing the product-name mapping the PR set out to fix.

post_tropical_product uses pil_prefix or _classify_product(product_id) for the display label — i.e. it expects pil_prefix, when supplied, to already be the resolved human-readable name (that's exactly what route_from_product_id in cogs/tropical.py does: pil_prefix = _classify_product(product_id)). Here pil_prefix is set to the raw 3-letter code (e.g. "TCP") and passed straight through, so product_type becomes the literal string "TCP" instead of "ADVISORY". Every NWWS-sourced tropical post will show the raw PIL in its title/thread name, not the corrected label the CHANGELOG describes.

🐛 Suggested fix — don't pass the raw PIL; let it resolve via `_classify_product`
             elif any(
                 afos_pil.startswith(x) for x in ("TCV", "TCD", "TWD", "TCU", "TWO", "TCE", "TCP")
             ):
                 tropical_cog = self.bot.get_cog("Tropical")
                 if tropical_cog:
                     nhc_pils = ("TCV", "TCD", "TWD", "TCU", "TWO", "TCE", "TCP")
                     pil_prefix = next(p for p in nhc_pils if afos_pil.startswith(p))
                     await tropical_cog.post_tropical_product(
-                        product_id, raw_text, pil_prefix, source="NWWS"
+                        product_id, raw_text, source="NWWS"
                     )
                     logger.info(f"Triggered NHC {pil_prefix} via XMPP")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
elif any(
afos_pil.startswith(x) for x in ("TCV", "TCD", "TWD", "TCU", "TWO", "TCE", "TCP")
):
tropical_cog = self.bot.get_cog("Tropical")
if tropical_cog:
nhc_pils = ("TCV", "TCD", "TWD", "TCU", "TWO", "TCE", "TCP")
pil_prefix = next(p for p in nhc_pils if afos_pil.startswith(p))
await tropical_cog.post_tropical_product(
product_id, raw_text, pil_prefix, source="NWWS"
)
logger.info(f"Triggered NHC {pil_prefix} via XMPP")
elif any(
afos_pil.startswith(x) for x in ("TCV", "TCD", "TWD", "TCU", "TWO", "TCE", "TCP")
):
tropical_cog = self.bot.get_cog("Tropical")
if tropical_cog:
nhc_pils = ("TCV", "TCD", "TWD", "TCU", "TWO", "TCE", "TCP")
pil_prefix = next(p for p in nhc_pils if afos_pil.startswith(p))
await tropical_cog.post_tropical_product(
product_id, raw_text, source="NWWS"
)
logger.info(f"Triggered NHC {pil_prefix} via XMPP")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cogs/nwws.py` around lines 617 - 628, Update the NWWS tropical dispatch in
the handler containing `post_tropical_product` so it does not pass the raw
`pil_prefix` code as the third argument. Pass no resolved prefix (or otherwise
let `post_tropical_product` invoke `_classify_product(product_id)`), while
preserving the existing tropical PIL detection and NWWS source value.

Comment thread cogs/tropical.py
Comment thread cogs/tropical.py
Comment on lines +59 to +69
def _extract_storm_name(text: str) -> str:
lines = text.splitlines()
for i, line in enumerate(lines):
upper = line.upper()
for t in STORM_TYPE_ORDER:
if t in upper:
parts = upper.split(t, 1)
if len(parts) > 1:
name = parts[1].strip().strip(".").strip()
return name.title()
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Storm-name extraction captures the whole remainder of the line, not just the name.

Real NHC headlines look like HURRICANE FAUSTO ADVISORY NUMBER 14 (see the FAUSTO_TCP fixture in tests/test_tropical.py). Splitting on the matched storm-type token and title-casing everything after it yields "Fausto Advisory Number 14" as the extracted "storm name," which then pollutes the embed title and the thread name for essentially every real advisory. The existing test only checks the trivial no-trailing-text case ("HURRICANE ANNA"), so this doesn't get caught.

🐛 Suggested fix — take only the first token after the matched type
             if t in upper:
                 parts = upper.split(t, 1)
                 if len(parts) > 1:
-                    name = parts[1].strip().strip(".").strip()
-                    return name.title()
+                    remainder = parts[1].strip().strip(".").strip()
+                    if remainder:
+                        return remainder.split()[0].title()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _extract_storm_name(text: str) -> str:
lines = text.splitlines()
for i, line in enumerate(lines):
upper = line.upper()
for t in STORM_TYPE_ORDER:
if t in upper:
parts = upper.split(t, 1)
if len(parts) > 1:
name = parts[1].strip().strip(".").strip()
return name.title()
return None
def _extract_storm_name(text: str) -> str:
lines = text.splitlines()
for i, line in enumerate(lines):
upper = line.upper()
for t in STORM_TYPE_ORDER:
if t in upper:
parts = upper.split(t, 1)
if len(parts) > 1:
remainder = parts[1].strip().strip(".").strip()
if remainder:
return remainder.split()[0].title()
return None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cogs/tropical.py` around lines 59 - 69, Update _extract_storm_name so it
returns only the first token after the matched storm type, rather than the
entire remaining line. Preserve the existing trimming and title-casing behavior,
ensuring headlines such as “HURRICANE FAUSTO ADVISORY NUMBER 14” produce
“Fausto” while simple names like “HURRICANE ANNA” still work.

Comment thread cogs/tropical.py
Comment thread cogs/tropical.py
Comment on lines +228 to +233
full_text_embed = discord.Embed(
title=f"Full Text — {product_type}",
description=parsed["raw_text"][:4096],
color=discord.Color.dark_gray(),
)
target = thread or channel

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Full product text silently truncated at 4096 chars, defeating the "full text in thread" goal.

Discord's embed description hard limit is 4096 characters. TWD/TCD discussion products routinely exceed that. Slicing parsed["raw_text"][:4096] avoids a Discord API error but silently drops the tail of the product (including, potentially, the forecaster signature or later sections) with no indication to readers that anything was cut.

♻️ Suggested fix — chunk the full text across multiple embeds/messages
-        full_text_embed = discord.Embed(
-            title=f"Full Text — {product_type}",
-            description=parsed["raw_text"][:4096],
-            color=discord.Color.dark_gray(),
-        )
-        target = thread or channel
-        try:
-            await target.send(embed=full_text_embed)
-        except Exception as e:
-            logger.warning(f"Failed to post full text for {product_id}: {e}")
+        target = thread or channel
+        raw = parsed["raw_text"]
+        chunks = [raw[i : i + 4000] for i in range(0, len(raw), 4000)] or [""]
+        for idx, chunk in enumerate(chunks):
+            title = f"Full Text — {product_type}" + (f" ({idx + 1}/{len(chunks)})" if len(chunks) > 1 else "")
+            try:
+                await target.send(embed=discord.Embed(title=title, description=chunk, color=discord.Color.dark_gray()))
+            except Exception as e:
+                logger.warning(f"Failed to post full text chunk {idx} for {product_id}: {e}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
full_text_embed = discord.Embed(
title=f"Full Text — {product_type}",
description=parsed["raw_text"][:4096],
color=discord.Color.dark_gray(),
)
target = thread or channel
target = thread or channel
raw = parsed["raw_text"]
chunks = [raw[i : i + 4000] for i in range(0, len(raw), 4000)] or [""]
for idx, chunk in enumerate(chunks):
title = f"Full Text — {product_type}" + (f" ({idx + 1}/{len(chunks)})" if len(chunks) > 1 else "")
try:
await target.send(embed=discord.Embed(title=title, description=chunk, color=discord.Color.dark_gray()))
except Exception as e:
logger.warning(f"Failed to post full text chunk {idx} for {product_id}: {e}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cogs/tropical.py` around lines 228 - 233, Update the full-text posting flow
around full_text_embed so parsed["raw_text"] is delivered in chunks that respect
Discord’s 4096-character embed description limit, rather than slicing it to the
first 4096 characters. Send the chunks to the existing target (thread or
channel) in order and preserve the full product text, including its tail.

@full-bars
full-bars merged commit bda5316 into main Jul 23, 2026
10 checks passed
@full-bars
full-bars deleted the feat/tropical branch July 23, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant