Each project below is presented in three layers — a non-technical TL;DR for stakeholders, a visual workflow showing how the system thinks, and a deep technical breakdown for engineers. 14 projects spanning NLP pipelines at a global bank, knowledge-graph RAG for legal AI, real estate analytics, career AI platforms, fraud detection, and speech recognition — every system here was built to be deployed, not just demoed.
Project 01 · Standard Chartered GBS · Jul–Dec 2025
LMA Clause Identification Tool
A production NLP pipeline on Dataiku DSS that reads 100+ page Loan Market Association agreements and automatically flags every legally-significant clause — sanctions, indemnities, governing law — that compliance auditors must review. Ships with confidence calibration, semantic search, and an auditor-friendly threshold tuning UI.
NLPFine-TuningProduction
The Problem
Auditors at a global bank were manually reading 100+ page loan agreements to find ~30 specific compliance-critical clauses. One missed sanctions clause = regulatory failure. The work was slow, error-prone, and didn't scale.
The Approach
Train a small, fast language model (DistilBERT) to recognise each clause type, then add a second-stage semantic search layer that confirms the match against gold-standard reference clauses. Filter low-confidence predictions before showing to humans.
The Impact
90% reduction in audit time. 98.6% recall on rare clauses (the ones that matter most). 95%+ of false positives filtered before reaching auditors. Legal team can review 10× more agreements in the same window.
90%
Audit time reduced
98.6%
Recall on rare clauses
95%+
False positives filtered
0.93
Weighted F1 score
250
Token window size
System Architecture — How the pipeline thinks
End-to-end clause identification pipeline
A 100+ page PDF enters on the left. By the time it exits on the right, every legally-significant clause has been classified, scored for confidence, and matched against a gold-standard reference — ready for auditor review.
Step-by-step workflow
01
Ingest the agreement
A 100+ page LMA PDF lands in the Dataiku dataset. Text is extracted page-by-page, preserving section markers so we can later cite exact clause locations.
Dataiku DSS · PyMuPDF
02
Slice into context windows
The full text is sliced into 250-token windows with 50-token overlap. The overlap ensures clauses that span window boundaries aren't cut in half. An earlier 75-token version produced 1.0 F1 — a red flag for data leakage.
HuggingFace tokenizer · sliding window
03
Classify with DistilBERT
A fine-tuned DistilBERT classifies each window into one of N clause types (or "irrelevant"). DistilBERT was chosen for being 40% smaller than BERT while retaining 97% of accuracy — critical for batch-processing 100+ page documents at speed.
DistilBERT · WeightedTrainer (20× boost)
04
Calibrate confidence
For each prediction, we check the model's softmax confidence. The "minimum correct confidence" threshold is calibrated per clause type using validation data. Anything below the threshold is dropped before reaching humans.
Custom thresholding · KDE rejected
05
Verify with SBERT
Surviving predictions are embedded with a fine-tuned SBERT and cosine-matched against a curated library of gold-standard reference clauses. This catches synonym rephrasings the classifier alone might rank low.
SBERT · MultipleNegativesRankingLoss
06
Surface to auditors
Output is rendered in a Dataiku web app: each detected clause shown with its type, confidence, page reference, and the matching gold-standard text. A separate UI lets non-technical auditors recalibrate thresholds without touching code.
Dataiku Web App · threshold UI
Technical decisions
Why DistilBERT: Encoder-only, optimised for classification. 40% smaller than BERT, 97% of its accuracy — letting us batch-process documents fast.
Why 250-token windows: 75-token windows produced a suspicious 1.0 F1, indicating data leakage. 250 tokens preserved context without bleeding.
WeightedTrainer: 90%+ of document text is irrelevant. A 20× class weight boost prevents the model from defaulting to "irrelevant".
Minimum-correct-confidence: KDE thresholding rejected (no clean incorrect-probability curve). Empirical, data-driven cut-off used instead.
SBERT fine-tuning: MultipleNegativesRankingLoss on legal anchor-positive pairs — model learns synonym clauses semantically, not lexically.
Deployment: Dataiku DSS pipeline + threshold-calibration web app for non-technical auditors
Business impact: Legal team reviews 10× more agreements in the same time window
Key Learning
The single most important lesson: a perfect F1 score is a red flag, not a success. When 75-token windows produced 1.0 F1, I suspected data leakage — clause text was appearing in both training and validation chunks. Reducing to 250-token windows with careful document-level splits fixed the leakage and produced honest, deployable metrics. Trusting suspicious results would have shipped a broken model into a regulated environment.
Project 02 · Standard Chartered GBS · Aug–Nov 2025
AutoTransFlow: Multilingual Document AI
A layout-preserving multilingual PDF translation system. Translates financial and legal documents into 200 languages while keeping the exact spatial structure — headings, tables, columns, signatures — intact. Runs entirely on local models with zero external API calls.
Computer VisionTranslationDocument AI
The Problem
Legal documents must be readable across 200+ jurisdictions. Google Translate destroys layout (turns tables into paragraphs) — and bank policy forbids sending sensitive PDFs to external APIs. We needed a translator that runs internally and preserves every visual structure.
The Approach
Treat each PDF page as an image first, text second. A YOLO-based layout model finds every text block's bounding box; then NLLB-200 translates the text inside each box; finally each translation is re-rendered at its original coordinates.
The Impact
200 languages supported · 0 external API calls · 100% layout fidelity. The translated PDF looks identical to the original — same tables, same columns, same signatures — just in the target language.
200
Languages supported
0
External API calls
100%
Layout preserved
0.25
Detection threshold
System Architecture — Vision-first translation
Layout-aware PDF translation pipeline
The trick: solve a translation problem with a computer vision model first. By detecting the geometry of every text block before translating, we know where each translated word must go on the page.
Step-by-step workflow
01
Duplicate & embed fonts
BabelDoc creates a copy of the source PDF and embeds fonts compatible with the target language (Devanagari, CJK, Arabic, etc.) so translated glyphs render correctly.
BabelDoc · font embedding
02
Render to pixmap
Each page is rasterised to an image and normalised to [0,1] pixel intensity — the input format expected by the layout model.
PyMuPDF · NumPy
03
Detect layout with YOLO
doc-layout-yolo predicts a bounding box and class (heading, paragraph, table, caption) for every text block. Confidence threshold of 0.25 keeps recall high — false detections are cheaper to filter than missing text.
doc-layout-yolo · 0.25 threshold
04
Translate per block
For each box, the text is extracted and fed to NLLB-200 with forced_bos_token_id set to the target language code (e.g. hin_Deva). The model runs entirely on internal hardware.
NLLB-200 · 600M · local inference
05
Re-render at original coords
Translated text is drawn back into each bounding box at its original (x, y, w, h) — preserving the visual structure exactly. Auto font-size adjustment handles language length variance.
PyMuPDF · text-fit logic
06
Stitch & deliver
The original page and its translated counterpart are appended in the final PDF, giving auditors a side-by-side reference. Modular backend lets translation engine be swapped (e.g. Argos Translate).
Final PDF assembly
Why not pdfplumber or PyMuPDF text extraction?
Traditional PDF libraries extract text as a linear stream — no understanding of visual structure
Cannot differentiate headings from paragraphs, tables from captions
Spatial relationships between text blocks are completely lost
Output would be a long unformatted text block — legally unusable for financial documents
Solution: Treat the page as an image, use computer vision to understand layout first
Why NLLB-200 over Google Translate?
Data privacy: Standard Chartered policy prohibits sending documents to external cloud APIs
NLLB-200 runs entirely on internal servers — zero data leaves the network
forced_bos_token_id: Steers decoder to target language (e.g. eng_Latn, hin_Deva)
Covers 200 languages including low-resource languages that Google handles poorly
Modular design — translation backend can be swapped (Argos Translate as alternative)
Key Insight
The critical innovation is treating document translation as a computer vision problem first, not a text problem. By using doc-layout-yolo to map the visual structure before extracting text, the system knows exactly where every word lives on the page — not just what it says. This makes it possible to put translated text back in exactly the right position, preserving the legal and structural integrity of the document.
Production retrieval-augmented generation chatbot built over proprietary PDF documents. Multi-turn conversation support via LangChain's ConversationalRetrievalChain, with local StableLM Zephyr 3B inference — no external API calls, no data leakage.
RAGLangChainLLM
The Problem
A consulting firm had hundreds of proprietary client PDFs. Analysts wasted hours searching through them for specific facts. They couldn't use ChatGPT — client confidentiality forbids cloud APIs.
The Approach
Build a chatbot that retrieves before it generates. Embed all PDFs into a local vector database, retrieve the most relevant chunks for each question, and let a small local LLM produce a grounded answer — with full conversation memory.
The Impact
Analysts query proprietary docs in seconds. Zero data leaves the network. Multi-turn dialogue means follow-up questions like "and what about the fees?" work naturally. Runs on standard hardware via 4-bit quantisation.
A RAG system has two distinct workflows. The top row runs once when documents are ingested. The bottom row runs every time a user asks a question.
Step-by-step workflow
01
Load & chunk documents
LangChain document loaders pull in PDFs and web pages. RecursiveCharacterTextSplitter slices them into 500-character chunks with 50-character overlap, respecting sentence boundaries.
LangChain · RecursiveCharacterTextSplitter
02
Embed every chunk
all-MiniLM-L6-v2 turns each chunk into a 384-dim vector. Small, fast, and surprisingly accurate — chosen so embedding hundreds of documents takes minutes not hours on CPU.
sentence-transformers · 384-dim
03
Build FAISS index
Vectors are stored in an in-memory FAISS index for sub-millisecond similarity search. The index is persisted to disk so the system can boot instantly on subsequent runs.
FAISS · IndexFlatL2
04
Rewrite follow-ups
When a user asks "and what about the fees?", that question alone has no context. ConversationalRetrievalChain combines the question with chat history and rewrites it into a standalone query the retriever can act on.
ConversationalRetrievalChain · BufferMemory
05
Retrieve top-k chunks
The rewritten question is embedded with the same model, and FAISS returns the top-k most similar chunks from the index. These become the LLM's grounding context.
FAISS similarity_search · top-k
06
Generate grounded answer
StableLM Zephyr 3B (4-bit quantised, GGUF) receives the question + retrieved chunks and produces an answer grounded in those chunks. Source chunks are surfaced as citations.
StableLM Zephyr 3B · llama.cpp
Why ConversationalRetrievalChain matters
Standard RetrievalQA treats each question in isolation
Follow-up questions like "What about the fees?" have no context without history
ConversationalRetrievalChain rephrases follow-up questions into standalone queries before searching
ConversationBufferMemory stores the full conversation history
Result: the chatbot maintains coherent multi-turn dialogue across a session
Why StableLM Zephyr 3B?
Local inference: No API costs, no latency from external calls, no data privacy risk
Q4_K_M quantisation: 4-bit weights reduce model from ~6GB to ~2GB — fits on standard hardware
GGUF format: Optimised for CPU inference via llama.cpp
Instruction-tuned: Zephyr variant fine-tuned to follow instructions and stay grounded
Trade-off acknowledged: smaller than GPT-4, but sufficient for grounded Q&A over retrieved context
Key Insight
The most important design decision was local inference over API calls. By running StableLM Zephyr locally in GGUF format, the system has no ongoing API costs, no latency from network calls, and no risk of sending proprietary client documents to external servers. For a consulting firm handling sensitive client PDFs, this isn't optional — it's the only architecturally correct choice.
An end-to-end pipeline for extracting structured data from scanned hospital bills and automatically detecting fraudulent claims using four independent anomaly-detection algorithms running in parallel.
OCRFraud DetectionComputer Vision
The Problem
Insurance companies process millions of scanned hospital bills. Manual review is slow; fraudsters exploit this with inflated totals, duplicated submissions, and impossible service combinations. We needed an automated triage system.
The Approach
Two-stage pipeline. Stage 1: aggressive image cleanup → OCR → LLM extraction turns noisy scans into clean JSON. Stage 2: four independent fraud checks vote on a risk score that routes the claim to ACCEPT / REVIEW / REJECT.
The Impact
90%+ extraction accuracy on degraded scans. Four fraud signals catch issues a single check would miss. Risk-scored output means humans only spend time on the actually suspicious 10% — not all 100%.
90%+
Extraction accuracy
4
Fraud checks
0.8
Rejection threshold
30
Day SHA-256 window
300
DPI target resolution
System Architecture — Extraction + parallel fraud detection
Two-stage pipeline: image → structured data → risk score
The four fraud checks run in parallel, not sequence — each catches a different attack pattern. Their outputs are weighted into a single risk score that drives an automated routing decision.
Step-by-step workflow
01
Aggressive image cleanup
Real bills are blurry, skewed, and shadowed. We deskew via Hough transform, denoise with bilateral filter (preserves letter edges), apply CLAHE for local contrast, and upscale 4× to hit 300 DPI — the resolution OCR engines expect.
OpenCV · Hough · CLAHE · bilateral
02
OCR with PaddleOCR
PaddleOCR runs an angle classifier first (handles rotated docs), then text detection and recognition. Outputs raw text strings with bounding boxes — no structure yet, just words.
PaddleOCR · DB detector · CRNN
03
LLM-based extraction
Qwen 7B (via Ollama, local) receives raw OCR text and returns a strict JSON schema: patient name, line items, amounts, total, date. The LLM handles formatting variance across different hospital templates.
Qwen 7B · Ollama · JSON schema
04
Run all 4 fraud checks in parallel
IQR (per-bill outliers), reconciliation (sum vs declared total), SHA-256 (duplicate submission across 30-day window), and pattern analysis (impossible combos) each run independently and produce a sub-score.
NumPy · hashlib · rule engine
05
Aggregate to risk score
The four sub-scores are combined into a single weighted risk score in [0, 1]. Each check has a tunable weight, calibrated against historical fraud cases.
Weighted ensemble
06
Route the claim
< 0.4 → auto-accept, 0.4–0.8 → human review, ≥ 0.8 → auto-reject. Humans only see the 10–15% of bills the system is uncertain about — saving the bulk of review time.
Threshold routing
The 4 fraud detection checks
IQR Amount Anomaly: Flags charges exceeding 95th percentile × 1.5 on that bill. Adapts to bill-level context, not global averages.
Reconciliation Check: Sums all line items and compares to declared total. Any >1% discrepancy is flagged — inflated totals are a classic fraud signal.
SHA-256 Duplicate Detection: Hashes bill content, checks against 30-day cache. Prevents the same bill from being resubmitted to multiple insurers.
Pattern Analysis: Detects same service billed twice, unusually high quantities, and suspicious service combinations in one visit.
Image preprocessing pipeline
Deskewing: Hough Line Transform detects text angle and rotates image to straighten it
Bilateral filter: Removes noise while preserving letter edge sharpness (unlike regular blur)
CLAHE: Local contrast enhancement — brightens dark corners without overexposing the rest
4× DPI scaling: Upscales toward 300 DPI for OCR accuracy
Key Learning
No single fraud signal is reliable on its own — but four independent signals combined are nearly impossible to fool. A clever fraudster might bypass IQR by spreading inflated amounts across many small line items, but that breaks reconciliation. They might avoid reconciliation issues but get caught by SHA-256 duplication. Defence-in-depth, applied to model design.
Automatic speech recognition for Urdu — a low-resource language. Zero-shot Whisper transcription with a two-stage post-processing pipeline including IndicBERT-based MLM error correction.
Speech AINLPAudio Processing
The Problem
Speech recognition for English is excellent. For low-resource languages like Urdu, it's poor — and there's not enough labelled data to train a new model from scratch. We needed a working Urdu transcriber without millions of labelled hours.
The Approach
Use Whisper zero-shot for the acoustic part, then fix its mistakes using a second model that understands Urdu language. IndicBERT acts as a linguistic spell-checker over the transcript, replacing phonetically-similar words that don't fit the context.
The Impact
14% reduction in Word Error Rate over baseline Whisper alone. Pipeline works on noisy real-world recordings thanks to the audio cleanup stage. Generalisable architecture — swap IndicBERT for any MLM and it works for other languages.
14%
WER reduction
-10dB
Noise reduction
16kHz
Resampled rate
0.4
MLM threshold
System Architecture — Acoustic + linguistic two-pass
Instead of one giant model trying to do everything, we let two specialists each do what they're best at — Whisper handles audio, IndicBERT handles language. Their combination beats either alone.
Step-by-step workflow
01
Standardise the audio
Convert to mono (Whisper expects mono), normalise to -20dBFS so loudness is consistent across recordings, trim leading/trailing silence, and resample to 16kHz — Whisper's training rate.
pydub · ffmpeg backend
02
Subtract background noise
noisereduce estimates the noise floor from silent regions and subtracts it spectrally from the speech signal — about -10dB reduction without distorting voice.
librosa · noisereduce
03
Transcribe with Whisper
Whisper (small) is run zero-shot with a language token forcing Urdu output. The result is a transcript with the right number of words but plausible-sounding errors — phonetically close, semantically wrong.
openai/whisper-small
04
Mask each token
For every word in the transcript, we hide it (replace with [MASK]) and ask IndicBERT to score how likely the original word is given the surrounding context.
HuggingFace fill-mask
05
Threshold & replace
If IndicBERT's probability for the original word is below 0.4, that word probably doesn't fit — we replace it with the highest-probability alternative IndicBERT suggests.
argmax over vocab
06
Measure WER
Word Error Rate is computed against gold-standard transcripts. The two-stage pipeline produces ~14% lower WER than baseline Whisper alone — significant for a low-resource language.
jiwer · WER metric
Audio preprocessing rationale
Mono conversion: Whisper expects mono. Stereo creates phase issues that confuse the model.
-20dBFS normalisation: Standardises loudness across mics and environments.
Silence trimming: Removes leading/trailing silence that adds nothing but inflates WER.
16kHz resampling: Whisper was trained on 16kHz. Mismatched sample rates degrade quality.
Spectral subtraction: Estimates noise floor in silence, subtracts from speech. ~ -10dB reduction.
Why IndicBERT as the corrector?
Whisper makes phonetically plausible errors — e.g. transcribing a similar-sounding word
MLM (Masked Language Model): IndicBERT is pre-trained to predict masked tokens from context
For each transcribed word, check: given the surrounding context, is this the most likely word?
If confidence for the transcribed word is below 0.4, replace with the highest-probability alternative
IndicBERT is specifically trained on Indian languages — understands Urdu context better than generic models
Key Learning
The two-stage design — ASR first, then MLM correction — is more powerful than trying to build a perfect ASR model. Whisper handles the acoustic modelling, IndicBERT handles linguistic correction. This separation of concerns lets each model do what it's best at: Whisper is excellent at converting audio to text, IndicBERT is excellent at deciding whether a word makes sense in context. Combining specialist models often beats one general model trying to do everything.
An automated financial news sentiment pipeline. Scrapes, summarises, and analyses 500+ articles using BART and RoBERTa, producing real-time sentiment scores served via a Flask API with a Streamlit dashboard.
LLMSentimentFinTech
The Problem
Markets move on news, but no human can read 500+ articles a day. Existing sentiment APIs are either too generic (consumer Twitter sentiment) or too expensive at scale. We needed a financial-domain pipeline running locally.
The Approach
Three-stage pipeline. Scrape with BeautifulSoup. Summarise with two-pass BART (handles articles longer than the model's 1024-token limit). Score with RoBERTa trained on financial text — using a continuous compound score, not a discrete class.
The Impact
500+ articles/day processed. 70% length reduction via summarisation while keeping financial signals. Real-time API + Streamlit dashboard surfaces sentiment trends live. SMOTE applied correctly — only on training data, no leakage.
500+
Articles processed
70%
Text length reduction
67%
Sentiment accuracy
4
BART beam size
55:46
Class split (pre-SMOTE)
System Architecture — Scrape → summarise → score → serve
Two-pass summarisation pipeline with continuous sentiment scoring
The interesting part is the two-pass BART in the middle. Many financial articles exceed BART's 1024-token context window, so we summarise chunks first, then summarise the chunk-summaries.
Step-by-step workflow
01
Scrape financial news
BeautifulSoup pulls 500+ articles from financial sources daily — headlines, body text, publish date, ticker mentions.
BeautifulSoup · requests
02
Chunk long articles
Articles exceeding BART's 1024-token limit are split via LangChain's unstructured text workflow into manageable chunks.
LangChain · token-aware splitter
03
Two-pass BART summarisation
Pass 1 summarises each chunk independently. Pass 2 summarises the combined chunk-summaries into a final article summary. 4-beam search produces higher quality than greedy decoding by considering 4 alternatives at each step.
facebook/bart-large-cnn · 4-beam
04
Score sentiment with RoBERTa
cardiffnlp/twitter-roberta-base-sentiment scores the summary. Instead of argmax (one class), we compute a softmax-weighted compound score across positive/neutral/negative — a continuous signal more useful for tracking trends.
RoBERTa · softmax compound
05
Balance training data — correctly
For the supervised classifier, classes were 55:46. SMOTE was applied only after train/test split, on the training portion. Applying it earlier would leak synthetic samples into validation and inflate accuracy.
imblearn · SMOTE (post-split)
06
Serve via API + dashboard
A Flask endpoint returns sentiment-per-ticker for downstream consumers. A Streamlit dashboard shows live sentiment trends — green when positive sentiment dominates, red when negative.
Flask · Streamlit · WebSocket
Why two-pass BART summarisation?
Financial news articles often exceed BART's 1024-token context limit
Pass 1: Split article into chunks, summarise each independently
Pass 2: Summarise the combined chunk-summaries into a final summary
4-beam search produces better quality than greedy decoding — considers 4 possible next tokens at each step
cardiffnlp/twitter-roberta-base-sentiment is trained on financial Twitter data — closer domain than generic models
Compound score: Instead of argmax (pick one class), compute softmax-weighted sum across positive/neutral/negative
This gives a continuous sentiment score rather than a discrete class — more useful for tracking sentiment trends over time
SMOTE only on training data — a common mistake is applying SMOTE before splitting, which leaks synthetic samples into validation. Applied correctly here.
Key Learning
The most important data-science lesson from this project: SMOTE must only be applied to training data, never before the train-test split. Applying SMOTE on the full dataset before splitting creates data leakage — synthetic samples generated from real training examples end up in the test set, inflating accuracy metrics. The correct workflow: split first, then apply SMOTE only on the training portion.
An end-to-end NLP pipeline that ingests raw audit observations, findings, and recommendations — then automatically classifies sentiment, discovers latent topics, and clusters similar documents for rapid triage. Built for teams that need to surface risk patterns from thousands of audit texts in minutes, not weeks.
NLPSentimentAnalytics
The Problem
Audit teams manually read thousands of unstructured audit observations to identify risk patterns. Inconsistent categorization, missed connections between similar findings, and no systematic way to quantify sentiment across audit reports.
The Approach
End-to-end pipeline: spaCy preprocessing → RoBERTa transformer for sentiment classification → Gensim LDA for latent topic discovery → TF-IDF + SVD + t-SNE for dimensionality reduction → KMeans clustering for grouping similar findings.
The Impact
Automatic sentiment scoring with confidence levels. Latent topics surfaced across audit documents. Similar findings clustered for batch review. Interactive Streamlit dashboard with CSV export for downstream analysis.
Raw audit observations flow through spaCy preprocessing, then split into three parallel analysis paths — RoBERTa sentiment, LDA topic modeling, and TF-IDF clustering — before converging in an interactive dashboard.
Step-by-step workflow
01
Ingest audit CSV
Load raw audit observations from CSV. Validate text column, handle missing values, basic data quality checks.
pandas
02
Preprocess text
spaCy-powered tokenization, lemmatization, and stop-word removal. Clean text for downstream models.
spaCy · en_core_web_sm
03
Classify sentiment
RoBERTa transformer classifies each observation as positive/neutral/negative with confidence scores.
cardiffnlp/twitter-roberta-base-sentiment
04
Discover topics
Gensim LDA uncovers recurring themes across audit documents. Configurable number of topics with coherence scoring.
Gensim LDA · TF-IDF
05
Reduce & cluster
TF-IDF vectorization → SVD dimensionality reduction → t-SNE 2D projection → KMeans groups similar findings.
scikit-learn · KMeans
06
Dashboard & export
Streamlit web UI with upload, real-time analysis, and CSV export. CLI also available for batch processing.
Streamlit · CLI
Technical decisions
Why RoBERTa: Pre-trained on 58M tweets, excels at short informal text — similar register to audit observations.
Why LDA over BERTopic: LDA provides interpretable topic-word distributions; BERTopic requires GPU and adds complexity without clear benefit for this text size.
KMeans over DBSCAN: Known number of audit categories makes KMeans appropriate. DBSCAN better for unknown cluster count.
t-SNE over UMAP: t-SNE produces better local structure preservation for visualization at this scale.
Key results
Sentiment: 3-class classification with per-observation confidence scores
Topics: Configurable LDA with coherence-based topic count selection
Clustering: KMeans grouping with silhouette scoring
Deployment: Streamlit dashboard + CLI, full pytest suite with GitHub Actions CI
Key Insight
The most impactful design decision was building a dual interface (CLI + Streamlit). The CLI enables batch processing in automated pipelines, while the Streamlit dashboard gives non-technical auditors a point-and-click interface. This separation of concerns made the tool adoptable across both technical and business teams.
Not just another job tracker — a complete Career Operating System that transforms job search from scattered applications into a data-driven, AI-optimized conversion funnel. 11 intelligent modules powered by LangChain + FAISS + Groq LLaMA 3.
LLM/RAGNLPAI
The Problem
Job seekers follow a broken workflow: apply everywhere, pray for responses, get ghosted, repeat. No data-driven approach to prioritize applications, predict response likelihood, or optimize resumes for ATS systems.
The Approach
Build an 11-module AI operating system: Command Center with daily briefings, FAISS-powered resume-JD matching, response probability prediction, ghost job detection, hidden market radar, ATS keyword optimization, interview battle plans, salary intelligence, and RAG-powered cover letter generation.
The Impact
Complete career funnel from Lead → Qualified → Outreach → Interview → Offer with conversion analytics. AI-scored application priority. ATS optimization with keyword gap analysis.
11
AI Modules
RAG
Matching Engine
FAISS
Vector Search
LLaMA 3
LLM Backend
System Architecture — RAG matching + career funnel
Dual-path pipeline: resume RAG index + job board scraping
The resume is chunked and embedded into a FAISS index for RAG matching. Job descriptions flow through a separate scraping pipeline. Both paths converge at the Command Center dashboard.
Step-by-step workflow
01
Upload resume
PDF upload with text extraction, recursive chunking, and MiniLM-L6-v2 embedding. Creates a FAISS vector index for RAG-powered matching.
LangChain · FAISS · sentence-transformers
02
Discover jobs
Dual-mode job ingestion — Selenium scraper for LinkedIn/Indeed with anti-detection, or manual JD paste.
Selenium · BeautifulSoup
03
AI match scoring
FAISS vector similarity between resume chunks and JD. Response probability prediction based on match strength, company signals, and historical patterns.
FAISS · Groq LLaMA 3
04
ATS optimization
Keyword gap analysis against each JD. Identifies missing keywords, section-by-section optimization suggestions.
NLP keyword extraction
05
Generate content
RAG-powered cover letter generation, interview question prediction with STAR framework scaffolding, salary intelligence with location adjustment.
Why Groq over OpenAI: Free tier with 30 req/min, LLaMA 3 quality comparable to GPT-3.5 for structured tasks, zero cost for personal project.
Why FAISS over Pinecone: Local vector store, no API costs, fast enough for resume-scale (hundreds of chunks, not millions).
Why Selenium over APIs: LinkedIn/Indeed don't offer free job search APIs. Stealth scraping with randomized delays and user-agent rotation.
11 modules: Each module is independent — users can use just the ATS optimizer without the full pipeline.
Key results
Command Center with AI-generated daily strategic briefings
RAG matching with per-job opportunity scores
Ghost job detection and hidden market radar
Conversion funnel analytics with stage-to-stage rates
Key Insight
The biggest lesson was that building 11 modules in Streamlit requires aggressive session state management. Each module shares data (resume index, job pipeline, API keys) through st.session_state, but circular dependencies between modules can cause infinite reruns. The solution was a centralized state registry that initializes all shared state once at startup.
A production-grade machine learning system that predicts domestic airline ticket prices in India. 6 regression algorithms, advanced feature engineering, and Bayesian hyperparameter optimization. LightGBM achieves 93.91% R² with ₹689 MAE.
MLRegressionData Science
The Problem
Airlines use dynamic pricing that makes ticket prices volatile. Consumers need predictive models to understand what drives price changes and when to book for the best deals.
The Approach
Modular ML pipeline: data ingestion → cyclical sin/cos encoding, target-guided ordinal encoding, IQR outlier removal → 6 regression algorithms benchmarked with 5-fold CV → Bayesian optimization for hyperparameter tuning.
The Impact
LightGBM 93.91% R², ₹689 MAE, 9.62% MAPE. Top 4 ensemble models within 0.002 R² of each other — feature engineering matters more than algorithm choice.
93.9%
R² Score
₹689
Mean Abs Error
9.6%
MAPE
6
Models Benchmarked
System Architecture — Regression pipeline
End-to-end airline price prediction pipeline
Raw flight data enters on the left. Feature engineering transforms temporal and categorical variables. Six regression models are benchmarked with cross-validation. LightGBM emerges as the best performer.
Step-by-step workflow
01
Load data
Ingest domestic flight dataset with airline, route, stops, departure/arrival times, and price. Handle missing values and validate data types.
pandas · data validation
02
Feature engineering
Cyclical sin/cos encoding for temporal features (departure hour, day of week). Target-guided ordinal encoding for categorical variables. IQR-based outlier removal for price stability.
sin/cos · target-guided ordinal · IQR
03
Exploratory data analysis
Price distributions by airline, route, and time. Correlation analysis between features. Outlier visualization and handling strategy determination.
matplotlib · seaborn · statistical tests
04
Train 6 models
Benchmark Ridge, Decision Tree, Random Forest, Gradient Boosting, XGBoost, and LightGBM. Each trained with 5-fold stratified cross-validation.
scikit-learn · XGBoost · LightGBM
05
Evaluate
Compare R², MAE, RMSE, and MAPE across all models. Bayesian hyperparameter optimization on top performers. Residual analysis for model diagnostics.
Why cyclical encoding: Time features (hour, day) are circular — 23:00 is close to 00:00. Sin/cos encoding preserves this circular relationship that one-hot encoding destroys.
Why target-guided ordinal: Encodes categorical variables by their mean target value, capturing ordinal relationships that label encoding misses and avoiding high cardinality of one-hot.
Why IQR over z-score: IQR is robust to non-normal distributions. Flight prices are right-skewed, making z-score thresholds unreliable.
LightGBM wins: Leaf-wise growth finds deeper interactions than level-wise (XGBoost). Histogram-based splitting is faster on large datasets.
Key results
LightGBM: 93.91% R² — best overall performer with ₹689 MAE
Random Forest: 93.79% R² — within 0.12% of LightGBM
Ensemble top 4: All >93% R², within 0.002 of each other
Ridge: 89.73% R² — linear baseline, confirms non-linearity in data
Key Insight
Feature engineering matters more than algorithm choice. The top 4 ensemble models were within 0.002 R² of each other — proving that once features are well-engineered, most modern ensemble algorithms converge to similar performance. The real gains came from cyclical time encoding and target-guided ordinal encoding, not from hyperparameter tuning.
Dual-component academic project: ML pipeline for cross-cultural conflict resolution analysis (MANOVA, XGBoost, SHAP) + Flutter mobile app for Indian handicraft artisans with Hindi voice input.
ResearchMLMobile App
The Problem
Two intertwined challenges: understanding cultural and psychological factors in conflict resolution across demographics, and Indian handicraft artisans lacking digital tools to manage their business.
The Approach
6-step ML pipeline on 776 adults: Cronbach's α validation → MANOVA → XGBoost classification → SHAP explainability. Parallel Flutter app with Hindi voice input, Firebase backend, FL Chart visualizations.
The Impact
0.938 AUC with XGBoost. Instrumental Attitude identified as #1 predictor via SHAP. Flutter app tested with 8 artisans, achieving 80% Hindi voice input accuracy.
0.938
Best AUC
776
Subjects
12
Models
8
Artisans Tested
System Architecture — Dual-track pipeline
Parallel tracks: ML conflict analysis + Flutter artisan app
Two independent pipelines share a common research foundation. Track 1 analyzes conflict resolution patterns via ML. Track 2 empowers artisans with a Hindi-first mobile app.
Step-by-step workflow
01
Load survey data
Ingest survey responses from 776 adults across demographic groups. Validate response quality, handle missing data, and prepare for psychometric analysis.
pandas · survey design
02
Cronbach's α analysis
Validate internal consistency of psychological scales. Compute Cronbach's alpha for each construct. MANOVA for multivariate group differences (Wilks' Λ = 0.933).
pingouin · scipy · MANOVA
03
ML classification
Benchmark 12 classification models. Tertile encoding for conflict resolution styles. SMOTE applied selectively (only for Compromising style where class imbalance exists).
scikit-learn · XGBoost · SMOTE
04
SHAP explainability
SHAP TreeExplainer on best XGBoost model. Instrumental Attitude emerges as #1 predictor. Psychological variables dominate over demographics.
SHAP · TreeExplainer
05
Recoded analysis
Re-run pipeline with alternative variable coding to validate findings. Stability check ensures SHAP rankings are robust across encoding choices.
robustness testing
06
Flutter app
Cross-platform mobile app for artisans. Hindi voice input with compositional number parsing. Firebase backend for data persistence. FL Chart for sales analytics.
Flutter · Firebase · FL Chart
Technical decisions
XGBoost over deep learning: 776 samples is too small for neural networks. Tree-based methods handle tabular data with small N better.
Tertile encoding: Conflict resolution scores divided into low/medium/high tertiles — preserves ordinal structure while enabling classification.
SMOTE only for Compromising: Not all styles had class imbalance. Applying SMOTE globally would distort balanced classes.
Hindi voice parser: Custom compositional number parser handles Hindi numerals (e.g., "ek hazaar paanch sau" → 1500).
Key results
XGBoost: 0.938 AUC — best performer across all conflict styles
Random Forest: 0.897 AUC — strong second performer
SHAP #1: Instrumental Attitude — most predictive variable
Flutter app: 80% Hindi voice accuracy, tested with 8 artisans
Key Insight
Psychological variables are more predictive than demographic variables for conflict resolution. SHAP analysis consistently ranked Instrumental Attitude, Subjective Norms, and Perceived Behavioral Control above age, gender, and education. This finding held across recoded analyses, confirming that how people think about conflict matters more than who they are.
NLP-based classification of road accident severity from news articles. 4 data collection phases, 8 Hyderabad news outlets, spaCy NER for location extraction.
NLPNERScraping
The Problem
Fragmented road safety data in India. No centralized system for tracking accident severity across cities. Official data is delayed, incomplete, and lacks the granularity needed for targeted interventions.
The Approach
Iterative 4-phase data collection (Twitter → Nitter → YouTube → News articles). 8 Hyderabad outlets scraped. spaCy NER for location extraction + Nominatim geocoding. Severity classification into Fatal/Severe/Moderate/Minor.
The Impact
Automated pipeline from raw news → severity-classified, geocoded accidents. Reusable framework adaptable to any Indian city. 3 failed data sources documented — teaching the value of iteration.
8
News Sources
4
Iteration Phases
NER
Location Extraction
4
Severity Levels
System Architecture — Iterative data pipeline
Multi-source scraping pipeline with NER extraction
Three data sources were tried and rejected before news articles proved viable. The pipeline scrapes 8 outlets, extracts text, runs spaCy NER for locations, classifies severity, and geocodes results.
Step-by-step workflow
01
Source selection
Evaluated Twitter API (rate-limited), Nitter (shut down), YouTube (unstructured). Pivoted to 8 Hyderabad news outlets: verified, structured, freely accessible.
iterative evaluation · 4 phases
02
Article scraping
Custom scrapers for each outlet with multi-keyword filtering (accident, crash, collision, etc.). Rate-limited requests to respect robots.txt.
BeautifulSoup · requests
03
NER extraction
spaCy NER extracts GPE (geo-political entity) and LOC (location) entities from article text. Custom rules for Indian place name patterns.
spaCy · en_core_web_sm
04
Severity classification
Rule-based severity classification: Fatal (death keywords), Severe (hospitalization), Moderate (injuries), Minor (property damage only).
keyword matching · rule engine
05
Geocoding
Nominatim geocoding converts extracted location names to latitude/longitude coordinates for spatial analysis and mapping.
Nominatim · geopy
06
Analysis output
Structured dataset with severity-classified, geocoded accidents. Reusable framework adaptable to any Indian city by swapping news sources.
pandas · output pipeline
Technical decisions
News over social media: News articles are verified, structured, and freely accessible. Social media is noisy, rate-limited, and often unreliable.
spaCy over BERT NER: spaCy is fast and sufficient for Indian place name extraction. BERT NER adds latency without significant accuracy gains for this task.
Multi-keyword filtering: Boolean OR across accident-related keywords ensures high recall across different reporting styles.
8 sources for diversity: Multiple outlets reduce bias from any single publication's reporting patterns.
8 news sources: Diverse Hyderabad outlets for comprehensive coverage
spaCy NER + Nominatim: Automated location extraction and geocoding
Severity classification: 4-level system (Fatal/Severe/Moderate/Minor)
Reusable framework: Adaptable to any Indian city by swapping source configs
Key Insight
Failures taught the most. Three data sources were abandoned before news articles proved viable. Twitter's API rate limits, Nitter's shutdown, and YouTube's unstructured content each taught a lesson about data source evaluation. News articles won because they're verified, structured, and freely accessible — the trifecta of reliable data sources.
End-to-end data analytics platform for Hyderabad real estate. ELT pipeline (Python → PostgreSQL → dbt) powering four analytical modules with Power BI dashboards and ML-driven delay prediction.
Data EngineeringMLAnalytics
The Problem
Homebuyers lack data-driven tools for builder evaluation. RERA data is public but raw — no scoring, no delay prediction, no systematic way to compare builders across projects.
The Approach
ELT pipeline with dbt transforms producing 4 analytical marts: Builder Scorecard, Supply/Demand analysis, Price Fairness detection, and ML-driven Delay Prediction with SHAP explainability.
The Impact
Builder risk tiers from composite scoring. Oversupply flags by micro-market. Price fairness detection across comparable projects. XGBoost delay prediction with SHAP-driven feature importance.
4
Analytical Modules
ELT
Pipeline
SHAP
Explainability
dbt
Transform Layer
System Architecture — ELT analytics pipeline
End-to-end ELT pipeline with 4 analytical modules
Raw RERA data flows through Python ingestion into PostgreSQL. dbt transforms raw data into staging models and analytical marts. Power BI and Jupyter consume the marts for dashboards and ML analysis.
Step-by-step workflow
01
Data ingestion
Python scripts extract RERA public data. Raw records loaded into PostgreSQL without transformation — ELT paradigm preserves source fidelity.
Python · psycopg2 · ELT
02
Raw storage
PostgreSQL stores raw tables with schema-on-read. No premature transforms — dbt handles all business logic downstream.
PostgreSQL · raw schema
03
dbt transformation
Single dbt DAG transforms raw data into staging models, then into 4 analytical marts. NTILE scoring for builder rankings, window functions for time-series analysis.
dbt · SQL · Jinja macros
04
Builder scorecard
Composite builder scoring using NTILE percentiles across on-time delivery, project count, and complaint ratio. Risk tiers for buyer guidance.
NTILE · Power BI
05
Delay prediction
XGBoost classifier predicts project delay probability. SHAP explainability shows which builder and project features drive delay risk.
XGBoost · SHAP · Jupyter
06
Visualization
Power BI dashboards for interactive exploration. Supply/demand heatmaps, price fairness comparisons, and builder risk scorecards.
Power BI · interactive filters
Technical decisions
dbt over pandas: SQL-based transforms are reproducible, version-controlled, and testable. Pandas transforms live in notebooks — fragile and hard to audit.
ELT over ETL: Load raw data first, transform in-database. Preserves source fidelity and lets dbt handle all business logic declaratively.
NTILE for scoring: Percentile-based scoring (NTILE) is distribution-agnostic — works regardless of skewness in builder metrics.
Builder scoring: Composite NTILE-based risk tiers for buyer guidance
Oversupply detection: Micro-market flags for areas with excess inventory
Price fairness: Outlier detection across comparable projects
Delay prediction: XGBoost + SHAP for explainable delay risk forecasting
Key Insight
All 4 modules share the same data foundation. One dbt DAG produces all 4 analytical marts from the same raw PostgreSQL tables. This means a single data quality fix propagates to every module simultaneously. The power of ELT is that business logic lives in version-controlled SQL, not scattered across notebooks.
50M backlogged cases in Indian courts. Generic RAG systems miss legal structure — they can't distinguish between citing a statute and applying a precedent. Legal reasoning requires typed relationships, not just similarity search.
211 statutes, 1,285 precedents indexed. Balanced verdict predictions vs GraphMERT's class collapse (92.4% in one class). Parser recovers 55% of UNKNOWN predictions. Always report MCC and Cohen's κ.
A new case enters on the left. Entity extraction identifies statutes, parties, and legal concepts. Two parallel retrieval paths — Neo4j KG (structural) and Vespa (semantic) — converge to assemble context for IRAC-constrained Mistral 7B generation.
Step-by-step workflow
01
Entity extraction
NER identifies statutes, parties, judges, legal concepts, and case metadata from raw case text. Typed entities enable structured KG queries.
NER · typed entity recognition
02
Knowledge graph
Neo4j typed property graph with 7 node types and 8 relationship types. MERGE operations ensure graph density (11.1 mean degree). 211 statutes, 1,285 precedents indexed.
Neo4j · Cypher · MERGE
03
Vespa indexing
Case texts embedded with InLegalBERT (768-d, domain-specific legal embeddings). Indexed in Vespa for hybrid BM25 + semantic search.
InLegalBERT · Vespa · 768-d
04
Hybrid retrieval
Parallel retrieval: NER-Cypher queries traverse KG for structural matches. Vespa combines BM25 lexical + InLegalBERT semantic scores. Results merged and deduplicated.
Cypher + Vespa hybrid
05
IRAC generation
Mistral 7B constrained to IRAC format (Issue, Rule, Application, Conclusion). Sliding window attention handles long legal contexts. Prevents hallucination through structured output.
Mistral 7B · IRAC constraint
06
Evaluation
Benchmarked against GraphMERT. Report MCC and Cohen's κ alongside accuracy. Parser recovers 55% of UNKNOWN predictions through rule-based post-processing.
MCC · Cohen's κ · parser recovery
Technical decisions
KG over pure vector: Legal reasoning requires typed relationships (cites, overrules, interprets). Vector similarity alone can't distinguish between citing and overruling a case.
InLegalBERT over MiniLM: Domain-specific legal embeddings capture legal terminology nuances that general-purpose models miss.
MERGE for graph density: Neo4j MERGE operations ensure nodes are reused, not duplicated — creating a denser, more connected knowledge graph (11.1 mean degree).
Mistral over LLaMA: Sliding window attention handles the long context lengths typical of legal documents more efficiently.
Key results
211 statutes: Indexed in Neo4j with typed relationships
1,285 precedents: Linked via CITES, OVERRULES, INTERPRETS edges
11.1 mean degree: Dense graph connectivity from MERGE operations
Balanced predictions: KG-RAG avoids GraphMERT's class collapse
GraphMERT's accuracy was illusory — 92.4% of predictions fell into one class. High accuracy masked complete failure on minority classes. KG-RAG produced balanced recall across all verdict types. The lesson: always report MCC and Cohen's κ alongside accuracy. A model that predicts "allowed" every time gets 92.4% accuracy but is legally useless.
Repository
Private repository — BITS Pilani Study Project under Prof. Hrishikesh Rajesh Terdalkar. CS F266: Study Project, Hyderabad Campus.
Project 14 · Course Project · 2024
StemCells DBMS: Donor Management System
Full-stack donor, patient, hospital, and staff management system. Flask + MySQL with dynamic dashboard and real-time entity counting.
DBMSFull-StackWeb App
The Problem
Stem cell registries need centralized tracking of donors, patients, hospitals, and staff. Manual spreadsheets don't scale, can't enforce data integrity, and provide no real-time visibility.
The Approach
Flask + MySQL full-stack application. Dynamic dashboard with real-time entity counts. Registration forms for all 4 entity types. Normalized relational schema with foreign key constraints.
The Impact
Real-time dashboard with dynamic counts. Full CRUD operations for donors, patients, hospitals, and staff. Responsive UI that makes the system feel alive — not just a database wrapper.
4
Entity Types
Flask
Backend
MySQL
Database
CRUD
Operations
System Architecture — Full-stack CRUD application
Flask + MySQL donor management pipeline
User interactions flow through a Flask server to MySQL. Four entity tables (Donors, Patients, Hospitals, Staff) feed a dynamic dashboard with real-time counts.
Step-by-step workflow
01
Database schema
Normalized MySQL schema with 4 entity tables: Donors, Patients, Hospitals, Staff. Foreign key constraints enforce referential integrity across all relationships.
MySQL · normalized design
02
Flask backend
RESTful Flask routes for all CRUD operations. Jinja2 templates for server-side rendering. Form validation and error handling for data integrity.
Flask · Jinja2 · REST
03
Registration forms
Responsive HTML forms for donor, patient, hospital, and staff registration. Client-side validation for immediate feedback. Server-side validation for security.
HTML forms · validation
04
Dynamic dashboard
Real-time entity counts starting from a base of 25. Dashboard updates dynamically as new records are added. Visual indicators for system health.
Jinja2 · dynamic counts
Technical decisions
Flask over Django: Simpler for CRUD-focused applications. Django's ORM and admin panel add complexity unnecessary for a 4-entity system.
MySQL over SQLite: Proper multi-user support with concurrent connections. SQLite's file-based locking would bottleneck in multi-user scenarios.
Dynamic counts from base 25: Dashboard starts with a base count and increments with real registrations — making the system feel populated and active from day one.
Key results
CRUD operations: Full Create, Read, Update, Delete for all 4 entity types
Dynamic dashboard: Real-time counts with visual entity cards
Responsive forms: Client + server validation for data integrity
Normalized schema: Foreign key constraints across all relationships
Key Insight
The gap between knowing SQL and building a working app is enormous. Writing SELECT queries in a textbook is nothing like building a full CRUD system with form validation, error handling, and responsive UI. The dynamic dashboard — showing real-time counts that update with every new registration — was the feature that made the system feel alive, not just a database wrapper.