Placement service
Introduction#
PlacementService runs the LLM offer extractor. Detection heuristics, Gemini prompts, the offer schema, retries, and how validated offers reach notification delivery.
Project structure#
The placement system is composed of modular services that work together to fetch, process, validate, and publish placement offers:
- Email ingestion via Google Groups IMAP client
- LangGraph-based processing pipeline with classification, extraction, validation, and privacy sanitization
- Structured data models for placement offers and notifications
- Database persistence and retrieval
- Notification routing to Telegram and other channels
graph TB subgraph "Email Layer" GGC[GoogleGroupsClient] end subgraph "Processing Pipeline" PS[PlacementService] LG[LangGraph Nodes] CL[Classification] EX[Extraction] VA[Validation] PR[Privacy Sanitization] end subgraph "Data Layer" DB[DatabaseService] PO[Placement Offers Collection] NO[Notices Collection] end subgraph "Notification Layer" NS[NotificationService] TF[PlacementNotificationFormatter] end GGC --> PS PS --> LG LG --> CL LG --> EX LG --> VA LG --> PR PS --> DB DB --> PO DB --> NO TF --> NS DB --> TF
Core components#
- PlacementService: Orchestrates the end-to-end pipeline using LangGraph, integrating IMAP email fetching, LLM-based extraction, validation, and privacy sanitization.
- GoogleGroupsClient: Provides IMAP connectivity and email parsing for placement group feeds.
- DatabaseService: Manages MongoDB collections for notices, jobs, placement offers, and user data.
- PlacementNotificationFormatter: Transforms placement events into notification-ready notices.
- NotificationService: Routes formatted notices to Telegram and other channels.
- EmailNoticeService: Separate pipeline for non-placement notices (contrast to placement offers).
Key data models:
- PlacementOffer, RolePackage, Student: Structured representation of placement offers.
- NewOfferEvent, UpdateOfferEvent: Event structures for new and updated offers.
- NoticeDocument: Standardized notice format for publishing.
Architecture overview#
The PlacementService implements a four-stage LangGraph pipeline:
- Classification: Keyword-based relevance scoring to filter placement-related emails.
- Extraction: LLM-based structured extraction with reliable retry logic.
- Validation: Schema validation and enhancement (role/student mapping, package normalization).
- Privacy Sanitization: Removal of headers, forwarded markers, and sensitive metadata.
sequenceDiagram participant IMAP as "GoogleGroupsClient" participant PS as "PlacementService" participant LLM as "ChatGoogleGenerativeAI" participant DB as "DatabaseService" IMAP->>PS : Fetch unread email IDs PS->>IMAP : Fetch email content PS->>PS : Classify email (keywords) PS->>LLM : Extract structured offer LLM-->>PS : JSON offer data PS->>PS : Validate and enhance PS->>PS : Sanitize privacy PS->>DB : Upsert placement offers DB-->>PS : Events (new/update) PS->>PS : Format notices PS->>DB : Save notices
Detailed component analysis#
PlacementService#
The PlacementService encapsulates the LangGraph pipeline and integrates with external systems:
- Initialization: Configures LLM (Gemini), builds the workflow graph, and prepares dependencies.
- Classification: Computes a confidence score using placement-related keywords, company indicators, presence of names/emails, and negative indicators.
- Extraction: Invokes the LLM with a strict prompt template to validate final placement offers and extract structured data; includes retry logic on validation errors.
- Validation: Ensures required fields (company, roles, students), normalizes package values, and assigns default roles/packages when possible.
- Privacy Sanitization: Removes headers, forwarded markers, and sensitive metadata from extracted fields.
- Persistence: Uses DatabaseService to upsert offers and emit events for notification formatting.
classDiagram
class PlacementService {
-logger
-email_client
-db_service
-notification_formatter
-llm
-app
+process_emails(mark_as_read)
+process_single_email(email_data)
-_build_graph()
-_classify_email(state)
-_extract_info(state)
-_validate_and_enhance(state)
-_sanitize_privacy(state)
-_display_results(state)
}
class GraphState {
+email
+is_relevant
+confidence_score
+classification_reason
+rejection_reason
+extracted_offer
+validation_errors
+retry_count
}
class PlacementOffer {
+company
+roles
+job_location
+joining_date
+students_selected
+number_of_offers
+additional_info
+email_subject
+email_sender
+time_sent
}
PlacementService --> GraphState : "manages"
PlacementService --> PlacementOffer : "produces"Email processing pipeline#
The pipeline is designed specifically for placement notifications:
- Email fetching: IMAP-based retrieval of unread emails from the placement group.
- Content parsing: Decodes subject, sender, and body; extracts forwarded sender/date metadata.
- Classification: Keyword scoring and negative indicators to determine relevance.
- Extraction: Strict LLM prompt ensures only final placement offers are processed.
- Validation: Schema enforcement and data normalization.
- Privacy: Sanitization of headers and forwarded metadata.
- Storage: Upsert into placement offers collection with event emission for notifications.
flowchart TD Start([Start]) --> Fetch["Fetch unread email IDs"] Fetch --> Parse["Parse email content"] Parse --> Classify["Keyword-based classification"] Classify --> |Relevant| Extract["LLM extraction with retry"] Classify --> |Not relevant| Skip["Skip and mark read"] Extract --> Validate["Validate and enhance"] Validate --> Privacy["Sanitize privacy"] Privacy --> Persist["Upsert placement offers"] Persist --> Events["Emit events (new/update)"] Events --> Format["Format notices"] Format --> Save["Save notices"] Save --> End([End]) Skip --> End
Placement offer schema#
The system defines a detailed schema for placement offers:
- Company: Name of the organization.
- Roles: List of roles with associated packages and package details.
- Job Location: Optional locations.
- Joining Date: ISO date string.
- Students Selected: List of students with names, enrollment numbers, emails, roles, and packages.
- Number of Offers: Count of selected students.
- Additional Info: Supplementary details.
- Email Metadata: Subject, sender, and time sent.
erDiagram
PLACEMENT_OFFER {
string company
jsonb roles
string[] job_location
string joining_date
jsonb students_selected
int number_of_offers
string additional_info
string email_subject
string email_sender
string time_sent
}
ROLE_PACKAGE {
string role
float package
string package_details
}
STUDENT {
string name
string enrollment_number
string email
string role
float package
}
PLACEMENT_OFFER ||--o{ ROLE_PACKAGE : "contains"
PLACEMENT_OFFER ||--o{ STUDENT : "selects"LLM prompt engineering#
The LLM prompt enforces strict validation before extraction:
- Phase 1: Classification of final placement offers with explicit criteria (package presence, finality, placement status, training/PPO handling).
- Phase 2: Extraction into a strict schema with privacy rules forbidding headers and forwarded metadata.
- Package normalization rules: LPA conversion, range handling, stipend vs. CTC distinctions, and conditional offers.
- Retry logic: Validates JSON and Pydantic models, with up to three attempts.
flowchart TD A["Receive email content"] --> B["Phase 1: Final placement offer validation"] B --> |Valid| C["Phase 2: Extract structured data"] B --> |Invalid| D["Reject with reason"] C --> E["Validate JSON and schema"] E --> |Valid| F["Proceed to validation/sanitization"] E --> |Invalid| G["Retry up to 3 times"] G --> |Exceeded| D
Examples of processing different types of placement offers#
The system handles diverse offer types:
- Full-time offers with CTC and benefits.
- Internship-only offers with stipend conversion to LPA.
- Conditional offers (PPOs) with final CTC.
- Training programs leading to FTE with package disclosure.
- Multiple roles with different packages.
Examples are visible in the stored placement offers dataset.
Integration with notification system#
Placement events trigger formatted notices:
- New Offer: Creates a notice summarizing total placements and role breakdowns.
- Update Offer: Adds newly placed students and updates totals.
- NotificationService broadcasts notices to Telegram and other channels.
- DatabaseService persists notices and tracks sent status.
sequenceDiagram participant DB as "DatabaseService" participant PS as "PlacementService" participant TF as "PlacementNotificationFormatter" participant NS as "NotificationService" DB->>PS : Offers with events (new/update) PS->>TF : Format new/update offer notice TF-->>PS : NoticeDocument PS->>DB : Save notice DB-->>NS : Unsent notices NS->>NS : Broadcast to channels NS-->>DB : Mark as sent
Dependency analysis#
The PlacementService depends on:
- GoogleGroupsClient for email ingestion.
- LangChain ChatGoogleGenerativeAI for LLM processing.
- Pydantic models for validation.
- DatabaseService for persistence and event emission.
- NotificationService and PlacementNotificationFormatter for publishing.
graph TB PS[PlacementService] --> GGC[GoogleGroupsClient] PS --> LLM[ChatGoogleGenerativeAI] PS --> DB[DatabaseService] PS --> PY[Pydantic Models] DB --> TF[PlacementNotificationFormatter] TF --> NS[NotificationService]
Performance considerations#
- Retry Strategy: Up to three retries for extraction/validation failures to mitigate transient LLM issues.
- Lazy IMAP Usage: Fetches email IDs first, then processes emails individually to reduce memory overhead.
- Package Normalization: Converts all amounts to LPA and handles ranges conservatively (lowest quantifiable value).
- Forwarded Metadata Extraction: Reliable parsing of forwarded dates and senders to maintain accurate timestamps and attribution.
- Database Upserts: Efficient merge logic for offers to minimize duplication and maximize event generation for updates.
[No sources needed since this section provides general guidance]
Troubleshooting guide#
Common issues and resolutions:
- Empty LLM Responses: Treated as non-placement offers; review prompt clarity and content sanitization.
- Validation Errors: Inspect missing company names, roles, or student lists; ensure single-role defaults are applied.
- JSON Parsing Failures: Verify LLM output formatting; ensure raw JSON is returned without markdown wrappers.
- IMAP Connectivity: Confirm environment variables for email and app password; verify server configuration.
- Privacy Sanitization: Ensure headers and forwarded markers are removed; confirm additional_info and package details are cleaned.
- Notification Delivery: Check unsent notices and channel configurations; verify database marking as sent.
Conclusion#
Classify hard, extract into a schema, validate, strip privacy fields, publish. Notification delivery and historical stats both read the same stored offers.