Skip to content

Repository files navigation

πŸ” Competitor Intel Radar

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.


The Problem

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.


System Architecture

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   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.


Tech Stack

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

Project Structure

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

Setup

1. Clone and install dependencies

git clone <your-repo-url>
cd competitor-intel-radar
pip install -r requirements.txt

2. Get free API keys

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

3. Configure environment variables

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

4. Run ingestion + extraction

python run_ingestion.py

This pulls news + job signals for the tracked competitor list, runs each through the LLM extraction pipeline, and stores structured results in SQLite.

5. Label data and train the priority classifier

python label_signals.py                # manually tag signals as high/medium/low priority
python models/priority_classifier.py   # train + evaluate the final model

The 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.

6. Build the RAG index

python llm/rag_pipeline.py

7. Run the API and dashboard

# Terminal 1
uvicorn api.main:app --reload

# Terminal 2
streamlit run dashboard/app.py

Visit http://localhost:8501 for the dashboard, or http://127.0.0.1:8000/docs for the raw API.


Notebooks

  • 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 .pkl files in models/ come directly from this notebook.

Tracked Competitors (example set)

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.


Event Taxonomy

Signals are classified into categories designed specifically for the manufacturing/hardware startup context (not generic SaaS categories):

  • funding β€” capital raises
  • capacity_expansion β€” new plants, scaled production
  • oem_partnership β€” deals with automakers
  • raw_material_supply_chain β€” input sourcing, localization
  • regulatory_policy β€” PLI scheme approvals, subsidies
  • hiring β€” engineering/manufacturing job postings
  • pricing_or_product_launch β€” new products, pricing changes
  • other β€” everything else (filtered as low priority by default)

Model Selection & Evaluation

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.


Design Decisions & Tradeoffs

  • 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.ipynb actually compares it against 4 other algorithms with cross-validation before it gets used in the pipeline.

What's Not in Scope (This Version)

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.

Example Query

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.

About

Automated competitive intelligence for EV/auto startups using LLM extraction (Groq/Llama 3.3), classical ML priority classification, and grounded RAG Q&A.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages