An automated competitive intelligence system for founders of early-stage automobile parts / EV component manufacturing startups β pulls news + job posting signals about competitors, extracts structured info with an LLM, ranks it by priority with a trained ML classifier, and answers plain-English questions grounded in the collected signals (RAG).
Most competitive-intelligence tools (Crayon, Klue, Kompyte) are built for large enterprises with dedicated teams. This is a lightweight, self-serve version aimed at a founder who has 5 minutes a week to check "what did my competitors do," not 5 hours to dig through news and job boards manually.
Founders in capital- and R&D-intensive industries like EV/auto-component manufacturing need to track:
- Competitor funding rounds (runway, aggression capacity)
- OEM partnerships (Tata, Mahindra, Ola, Hyundai-Kia β landing a big client is often make-or-break)
- Capacity expansion (new plants, scaled production = pricing/volume threats)
- Regulatory wins (e.g., PLI scheme approvals, subsidies)
- Hiring trends (a proxy for what a competitor is building next)
Doing this manually doesn't scale. This project automates ingestion, filtering, and summarization of these signals so a founder can ask a plain-English question and get a grounded, sourced answer in seconds.
βββββββββββββββββββ
NewsAPI ββββββββΆβ β
β Ingestion βββββΆ raw_signals (SQLite)
Adzuna Jobs βββββΆβ β
βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β LLM Extraction β (Groq / Llama 3.3 70B)
β event_type, βββββΆ structured_signals (SQLite)
β summary, β
β sentiment, β
β severity β
βββββββββββββββββββ
β
ββββββββββββββββ΄ββββββββββββββββ
βΌ βΌ
βββββββββββββββββββββββ ββββββββββββββββββββββββββ
β Classical ML β β RAG Pipeline β
β Priority Classifier β β (embeddings + FAISS β
β (TF-IDF + Logistic β β + Groq generation) β
β Regression, tuned) β β β
βββββββββββββββββββββββ ββββββββββββββββββββββββββ
β β
βββββββββββββββββ¬ββββββββββββββββ
βΌ
ββββββββββββββββββββ
β FastAPI β
β /signals β
β /competitors β
β /ask β
β /digest β
ββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββ
β Streamlit β
β Dashboard β
ββββββββββββββββββββ
Why two ML layers instead of one? The LLM does deep, one-time understanding of each signal (extraction). Re-calling an LLM every time the dashboard needs to rank/filter signals doesn't scale in cost or latency as signal volume grows. The classical ML classifier is trained on the LLM's structured output plus hand-labeled priority judgments, so repeated scoring of new signals is instant and free β a distillation-adjacent pattern used in real production LLM systems.
| Layer | Tool |
|---|---|
| Data ingestion | NewsAPI, Adzuna Jobs API |
| Storage | SQLite |
| LLM extraction & generation | Groq (Llama 3.3 70B) |
| Embeddings | sentence-transformers (all-MiniLM-L6-v2, local) |
| Vector search | FAISS |
| Classical ML | scikit-learn (TF-IDF + tuned Logistic Regression) |
| Backend | FastAPI |
| Frontend | Streamlit |
competitor-intel-radar/
βββ api/
β βββ main.py # FastAPI endpoints
βββ dashboard/
β βββ app.py # Streamlit dashboard
βββ data/
β βββ signals.db # SQLite database
β βββ faiss_index.bin # Vector index
β βββ faiss_ids.npy # ID mapping for the index
βββ db/
β βββ schema.sql # Table definitions
βββ ingestion/
β βββ news_api.py # NewsAPI ingestion
β βββ jobs_api.py # Adzuna Jobs ingestion
βββ llm/
β βββ extractor.py # LLM-based structured extraction
β βββ rag_pipeline.py # Embedding + retrieval + grounded Q&A
βββ models/
β βββ priority_classifier.py # Trains + saves the final model used by the pipeline
β βββ priority_classifier.pkl # Saved model (tuned Logistic Regression)
β βββ tfidf_vectorizer.pkl # Saved vectorizer
β βββ onehot_encoder.pkl # Saved encoder
βββ notebooks/
β βββ 01_eda.ipynb # EDA on collected signals
β βββ 02_model_training_and_comparison.ipynb # model comparison, tuning, final training
βββ label_signals.py # CLI tool for manual priority labeling
βββ dedupe_signals.py # exact-match dedup pass
βββ fix_competitor_names.py # cleans up whitespace in competitor names
βββ check_labels.py / check_competitor.py # small sanity-check scripts
βββ run_ingestion.py # end-to-end ingestion + extraction runner
βββ requirements.txt
βββ .env # API keys (not committed)
βββ README.md
git clone <your-repo-url>
cd competitor-intel-radar
pip install -r requirements.txt| Service | Link | Free tier |
|---|---|---|
| NewsAPI | https://newsapi.org/register | 100 requests/day |
| Adzuna | https://developer.adzuna.com/signup | 250 calls/month |
| Groq | https://console.groq.com/keys | Generous free tier, fast inference |
Create a .env file in the project root:
NEWS_API_KEY=your_key_here
ADZUNA_APP_ID=your_id_here
ADZUNA_APP_KEY=your_key_here
GROQ_API_KEY=your_key_here
python run_ingestion.pyThis pulls news + job signals for the tracked competitor list, runs each through the LLM extraction pipeline, and stores structured results in SQLite.
python label_signals.py # manually tag signals as high/medium/low priority
python models/priority_classifier.py # train + evaluate the final modelThe choice of algorithm and hyperparameters used here isn't arbitrary β see notebooks/02_model_training_and_comparison.ipynb for the comparison across 5 algorithms and the tuning that led to it.
python llm/rag_pipeline.py# Terminal 1
uvicorn api.main:app --reload
# Terminal 2
streamlit run dashboard/app.pyVisit http://localhost:8501 for the dashboard, or http://127.0.0.1:8000/docs for the raw API.
01_eda.ipynbβ class balance, event type / sentiment / severity distributions, event type vs priority crosstab, summary length. Basically figuring out what the labeled data actually looks like before modeling it.02_model_training_and_comparison.ipynbβ same TF-IDF + one-hot + severity feature set as the pipeline script, but compares 5 algorithms (Logistic Regression, Linear SVM, Random Forest, Gradient Boosting, Naive Bayes) with 5-fold stratified cross-validation, tunes the winner with GridSearchCV, and retrains it on the full labeled set. The saved.pklfiles inmodels/come directly from this notebook.
Ultraviolette, EKA Mobility, Euler Motors, Log9 Materials, Exicom, Ather Energy, Battery Smart β a mix of real, currently active Indian EV/auto-component startups. The competitor list is fully configurable in run_ingestion.py.
Signals are classified into categories designed specifically for the manufacturing/hardware startup context (not generic SaaS categories):
fundingβ capital raisescapacity_expansionβ new plants, scaled productionoem_partnershipβ deals with automakersraw_material_supply_chainβ input sourcing, localizationregulatory_policyβ PLI scheme approvals, subsidieshiringβ engineering/manufacturing job postingspricing_or_product_launchβ new products, pricing changesotherβ everything else (filtered as low priority by default)
Trained and compared on 107 hand-labeled signals (after deduplication):
| Model | Macro F1 (5-fold CV) |
|---|---|
| Logistic Regression (tuned) | 0.65 |
| Linear SVM | 0.64 |
| Gradient Boosting | 0.56 |
| Random Forest | 0.53 |
| Naive Bayes | 0.33 |
Logistic Regression + Linear SVM (both linear models) clearly outperform the tree ensembles and Naive Bayes on this dataset β makes sense given how small and high-dimensional (sparse TF-IDF) the feature set is. Logistic Regression was tuned via GridSearchCV over C, best value came out to C=2.
Held-out test set (27 rows):
| Metric | Score |
|---|---|
| Accuracy | ~0.64 |
| High-priority recall | ~0.50-0.75 (varies run to run β 4 samples in the test fold) |
| Macro F1 | ~0.57 |
Honest limitation: with only ~107 labeled examples and a natural class imbalance (most signals are genuinely "medium" priority), the test set is small (27 rows) and per-class metrics are noisy β a handful of misclassifications swing scores by 20+ points. class_weight="balanced" was used specifically to prioritize recall on high-priority signals, since missing a competitor's funding round or OEM win is a worse failure for this product than a false alarm. More labeled data would meaningfully improve reliability more than swapping algorithms would at this point β see notebooks/02_model_training_and_comparison.ipynb for the full comparison.
- LLM extraction + classical ML classifier, not LLM-only: avoids re-querying an LLM for every ranking/filtering operation as signal volume grows β controls cost and latency at scale.
- Two-table schema (
raw_signals/structured_signals): raw data is never overwritten, so extraction can be re-run (e.g., after a prompt fix) without re-hitting rate-limited APIs. - Metadata filtering + vector search combined in RAG: pure semantic search alone can return topically similar but wrong-company results, so retrieval over-fetches and filters by competitor before generation.
- Local embeddings (sentence-transformers) instead of an API-based embedding model: free, fast enough for this scale, and removes an external dependency from the retrieval path.
- Notebook-driven model selection: rather than guessing at Logistic Regression,
notebooks/02_model_training_and_comparison.ipynbactually compares it against 4 other algorithms with cross-validation before it gets used in the pipeline.
Deliberately scoped out for a focused build, and worth naming explicitly rather than pretending they're solved:
- Scheduled/continuous ingestion β currently a manual batch run, not a live pipeline.
- Time-series momentum forecasting (LSTM) β a natural extension once enough historical signal volume accumulates; needs weeks of backfilled data to be meaningful, which wasn't available in this build window.
- Rate-limit/retry handling on external API calls β a production version would need backoff logic for NewsAPI/Adzuna/Groq.
- Deduplication is exact-text only β near-duplicate signals with slightly different wording (e.g., the same job posting reworded) aren't currently caught; a fuzzy-matching or embedding-similarity dedup pass would close this gap.
Q: "What has Ultraviolette done around capacity expansion recently?"
A: "Ultraviolette is aggressively expanding its capacity, aiming to reach 1.5 lakh units ahead of the Tesseract launch and targeting 1,000 monthly sales by year-end."
Grounded entirely in retrieved signals β not the LLM's general knowledge.