Formatting services
Introduction#
Turn structured records into messages people can read. NoticeFormatterService for general notices, PlacementNotificationFormatter for offers, including Telegram length limits and HTML/Markdown quirks.
Project structure#
The formatting services are part of a modular notification pipeline:
- Data ingestion from SuperSet and email sources
- Structured job and notice models
- LLM-powered notice formatting
- Placement-specific formatting
- Channel-agnostic notification routing
graph TB subgraph "Data Sources" SS[SuperSet API] EMAIL[Email Sources] end subgraph "Processing Layer" SCRAPER[SupersetClientService] RUNNER_UPDATE[UpdateRunner] RUNNER_NOTIFY[NotificationRunner] FORMATTER[NoticeFormatterService] PLACEMENT_FORMATTER[PlacementNotificationFormatter] end subgraph "Storage" DB[(MongoDB)] end subgraph "Channels" TELEGRAM[TelegramService] WEBPUSH[WebPushService] NOTIF[NotificationService] end SS --> SCRAPER EMAIL --> RUNNER_UPDATE SCRAPER --> RUNNER_UPDATE RUNNER_UPDATE --> FORMATTER RUNNER_UPDATE --> PLACEMENT_FORMATTER FORMATTER --> DB PLACEMENT_FORMATTER --> DB DB --> RUNNER_NOTIFY RUNNER_NOTIFY --> NOTIF NOTIF --> TELEGRAM NOTIF --> WEBPUSH
Core components#
This section introduces the two primary formatting services and their roles in the notification pipeline.
NoticeFormatterService#
A LangGraph-based LLM service that:
- Extracts and normalizes raw notice content
- Classifies notices into categories (update, shortlisting, announcement, hackathon, webinar, job posting)
- Performs fuzzy matching against job listings to enrich notices with structured job data
- Extracts structured information based on category
- Generates final formatted messages with consistent metadata and formatting
It can:
- Category classification with explicit tie-break rules
- Company name extraction and fuzzy matching with configurable thresholds
- Structured extraction using LLM prompts tailored to each category
- Consistent formatting patterns with bold headers, bullet lists, and IST timestamp formatting
- Job enrichment callback for on-demand detail fetching
PlacementNotificationFormatter#
A dedicated service for placement offer notifications that:
- Processes placement events (new offers, updates)
- Builds role and student breakdowns with counts and package details
- Generates placement-specific summaries with emoji indicators and structured layouts
- Maintains consistent metadata for database storage and channel distribution
It can:
- Role breakdown calculation with package aggregation
- Student selection tracking with role assignment
- Placement update detection and messaging differentiation
- Consistent NoticeDocument generation for database persistence
Architecture overview#
The formatting services operate within a broader notification architecture :
sequenceDiagram participant SRC as "Data Source" participant UPD as "UpdateRunner" participant LLM as "NoticeFormatterService" participant DB as "DatabaseService" participant NOTIF as "NotificationRunner" participant CH as "Channel Services" SRC->>UPD : Raw notices/jobs UPD->>LLM : format_notice(notice, jobs, enricher) LLM->>LLM : extract_text/classify_post/match_job LLM->>LLM : extract_info/format_message LLM-->>UPD : formatted notice UPD->>DB : save_notice(formatted) DB->>NOTIF : get_unsent_notices() NOTIF->>CH : broadcast_to_all_users(message) CH-->>NOTIF : send results NOTIF->>DB : mark_as_sent(_id)
Detailed component analysis#
NoticeFormatterService analysis#
The NoticeFormatterService implements a multi-stage LangGraph workflow:
flowchart TD START([Start]) --> EXTRACT["extract_text()<br/>HTML parsing + text normalization"] EXTRACT --> CLASSIFY["classify_post()<br/>Category classification"] CLASSIFY --> MATCH["match_job()<br/>Company fuzzy matching"] MATCH --> ENRICH["enrich_matched_job()<br/>Optional job enrichment"] ENRICH --> EXTRACT_INFO["extract_info()<br/>Structured extraction"] EXTRACT_INFO --> FORMAT["format_message()<br/>Final formatting"] FORMAT --> END([End]) STYLE_DEFAULT fill:#35;fff,stroke:#35;333,color:#35;000
Classification categories and rules#
The service uses explicit tie-break rules for category classification:
- update: Operational/procedural info, timetable shifts, portal status
- shortlisting: Lists of selected candidates with names/enrollments
- announcement: General broad notices to all students
- hackathon: Coding competitions with themes, durations, prizes
- webinar: Online/offline seminars with speakers and topics
- job posting: Job/internship/placement opportunities
Structured extraction patterns#
Each category defines specific extraction targets:
- shortlisting: Students with name and enrollment, company_name, role
- job posting: company_name, role, package, deadline, location, hiring_flow, eligibility_criteria
- webinar: event_name, topic, speaker, date/time, venue/platform, registration_link, deadline
- hackathon: event_name, theme, start/end dates, registration_deadline, registration_link, prize_pool, team_size, venue
- others: relevant contextual details (message, event_name)
Formatting templates and consistency#
The service enforces consistent formatting:
- Bold headers for key labels (Company, Role, Location, etc.)
- Bullet-point lists for structured information
- IST timestamp formatting for deadlines and posting dates
- Metadata footer with author and timestamp
- Category-specific emojis and layout variations
PlacementNotificationFormatter analysis#
The PlacementNotificationFormatter focuses on placement-specific notifications:
classDiagram
class PlacementNotificationFormatter {
+format_package(package) str
+format_new_offer_notice(event) NoticeDocument
+format_update_offer_notice(event) NoticeDocument
+format_event(event) NoticeDocument
+process_events(events, save_to_db) NoticeDocument[]
}
class NoticeDocument {
+string id
+string title
+string content
+string author
+string type
+string source
+string placement_offer_ref
+string formatted_message
+int createdAt
+int updatedAt
+bool sent_to_telegram
+bool is_update
+int new_students_count
}
class NewOfferEvent {
+string type
+string company
+string offer_id
+OfferData offer_data
+RoleData[] roles
+int total_students
+string time_sent
+string email_sender
}
class UpdateOfferEvent {
+string type
+string company
+string offer_id
+StudentData[] newly_added_students
+RoleData[] roles
+int total_students
+string email_sender
+string time_sent
}
PlacementNotificationFormatter --> NoticeDocument
PlacementNotificationFormatter --> NewOfferEvent
PlacementNotificationFormatter --> UpdateOfferEventRole breakdown and package formatting#
The formatter calculates role distributions :
- Package aggregation per role when available
- Pluralization and suffix handling (“offer” vs “offers”)
- Consistent package display (“LPA” for lakhs per annum, “LPM” for lakhs per month)
Placement update differentiation#
- New offers: Standard congratulatory messaging with role breakdowns
- Updates: Emphasized " Placement Update" with newly added student counts
- Consistent metadata for database tracking and channel distribution
Integration with data models#
Both formatters rely on structured data models:
erDiagram
NOTICE {
string id PK
string title
string content
string author
int updatedAt
int createdAt
}
JOB {
string id PK
string job_profile
string company
int placement_category_code
string placement_category
string content
int createdAt
int deadline
float package
string annum_months
string package_info
string location
string job_description
}
NOTICE ||--o{ JOB : "matched via fuzzy matching"Dependency analysis#
The formatting services integrate with the broader notification ecosystem:
graph TB subgraph "External Dependencies" LLM[LangChain + Gemini LLM] BEAUTIFULSOUP[BeautifulSoup] RAPIDFUZZ[RapidFuzz] LANGGRAPH[LangGraph] end subgraph "Internal Services" SUPSET[SupersetClientService] DB[DatabaseService] NOTIF[NotificationService] end subgraph "Formatters" NFS[NoticeFormatterService] PNF[PlacementNotificationFormatter] end LLM --> NFS BEAUTIFULSOUP --> NFS RAPIDFUZZ --> NFS LANGGRAPH --> NFS SUPSET --> NFS DB --> NFS DB --> PNF NOTIF --> DB
Performance considerations#
- LLM Cost Optimization: NoticeFormatterService uses targeted prompts and structured JSON extraction to minimize token usage while maintaining accuracy.
- Fuzzy Matching Efficiency: RapidFuzz provides fast similarity scoring with configurable thresholds to balance accuracy and performance.
- Selective Enrichment: Job enrichment occurs only when LLM identifies a likely match, avoiding unnecessary API calls.
- Batch Processing: PlacementNotificationFormatter processes multiple events efficiently and maintains consistent metadata for database operations.
- Memory Management: Both formatters avoid loading entire datasets into memory by processing incrementally and using generators where appropriate.
Troubleshooting guide#
Common issues and resolutions:
LLM classification failures#
- Symptom: Notices misclassified or missing categories
- Resolution: Verify Google API key configuration and LLM model settings in environment variables
- Debugging: Check classification prompts and category definitions in the service code
Fuzzy matching issues#
- Symptom: Companies not matched despite presence in notices
- Resolution: Adjust matching thresholds and verify job listing data completeness
- Debugging: Examine extracted company names and job listing company names for normalization differences
Formatting inconsistencies#
- Symptom: Inconsistent timestamp formats or layout variations
- Resolution: Verify IST timezone configuration and formatting helper functions
- Debugging: Check timestamp conversion functions and formatting templates
Conclusion#
NoticeFormatterService classifies and structures general notices. PlacementNotificationFormatter keeps offer messages detailed without inventing a second delivery path. Both feed the same channels.