feat(assistant): POST /assistant/naming — AI-generated event title & description candidates #49 - #79
Merged
Conversation
- NamingRequestDto: slateSummary (≤500) + optional context (≤200)
- NamingResponseDto: 3 title candidates (≤40 chars), one description
(≤200 chars), fallbackUsed flag, cachedAt timestamp
- NamingService: LLM-backed via LlmService.complete(), in-process Map
cache keyed by SHA-256(slateSummary||context), 24h TTL
- Validation strategy: truncate at nearest word boundary then reject on
blocked term → replace with fallback title/description
- PROFANITY_BLOCKLIST constant in src/common/constants with
prefix-match helper (\bterm catches derivations like 'fucking')
- Deterministic fallback: 'Premier League Showdown — Week of {date}'
pattern, date label extracted from summary or today's date
- AssistantController: new POST /assistant/naming route
- AssistantModule: NamingService registered as provider
- 19 unit tests covering: length enforcement (truncation), blocklist,
cache hit / miss / expiry / key isolation, fallback (unconfigured
LLM, throws, non-JSON, missing fields, always-valid guarantee)
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.
closes #49
Summary
Resolves the blank-title UX problem for event creators. Given a free-text
fixture slate summary, the endpoint generates 3 catchy title candidates (≤ 40
chars each) and one event description (≤ 200 chars) via the shared LlmService.
Results are cached for 24 hours so identical slates cost zero additional LLM
calls.
Changes
New endpoint
and an optional context hint (tone/audience). Returns titles, description,
fallbackUsed, and cachedAt.
Validation (truncation strategy)
boundary then re-checked for blocked terms. Truncation is chosen over
regeneration because it is deterministic and avoids unbounded LLM round-trips.
This decision is documented in the service JSDoc.
with a containsBlockedTerm() helper. Matching uses a word-start anchor
(\bterm) so root forms catch common derivations ("fuck" catches "fucking",
"fucked", etc.).
deterministic fallback.
Caching
dependencies. Cache resets on restart, which is acceptable for this use case.
Fallback
output.
{date}" where the date label is extracted from the summary or defaults to
today's date.
fallback if needed.
Files changed
and helper
fallback)
Tests
19 unit tests covering all acceptance criteria:
descriptions to ≤ 200 chars; exact-40-char titles are preserved untouched
fall back to a safe generic string; clean output passes through unchanged
summaries, different contexts, and expired entries each trigger a fresh LLM
call
missing fields; fallback output always satisfies length and blocklist
constraints
Tested