Skip to main content
Platform Deep Dive

How We Built the AI-Powered Tender Matching Engine at Tenders-SA

A deep dive into the architecture of the Tenders-SA tender matching engine: the 3-layer industry matcher, 13-factor scoring model with dynamic weight adjustments, eligibility gates, and how we evolved from hard rules to a probabilistic scoring system that surfaces relevant opportunities for South African suppliers.

How We Built the AI-Powered Tender Matching Engine

South African tender opportunities are published across 50+ sources: National Treasury's eTenders portal, nine provincial government portals, municipal websites, SOE bid pages (Eskom, Transnet, SANRAL), and sector-specific bulletins. Each source uses different formats, terminology, and update cadences. For a construction SME in Gauteng, manually filtering this firehose to find relevant opportunities is like finding a specific grain of sand on a beach.

The Tenders-SA matching engine solves this by scoring every tender against every company profile and surfacing the best matches. This article explains how the engine works under the hood: the three-layer industry matcher, the 13-factor scoring model, dynamic weight adjustments, and the architectural decisions that shaped it.

The Problem: Why Simple Keyword Matching Fails

When we first started, we tried simple keyword-based matching. A company that registered as "cleaning services" would only match tenders containing the word "cleaning." This missed obvious opportunities: a tender for "hygiene services at Tshwane hospitals" or "industrial deep-cleaning at Eskom power stations" wouldn't surface because the terminology differed.

The core challenge is that South African tender descriptions use inconsistent vocabulary. A single service might be described as "cleaning," "hygiene services," "facilities management," "janitorial services," or "industrial deep-cleaning" depending on the issuing department's conventions. Simple keyword matching produces high precision but terrible recall.

Architecture Overview: Three-Layer Industry Matching

The matching engine uses a three-layer architecture for industry and capability matching. Each layer handles a different aspect of the similarity problem, and their outputs are combined with weighted scores:

1// Layer weights for the combined score
2export const LAYER_WEIGHTS = {
3    exact: 0.30,   // 30% weight for exact matching
4    fuzzy: 0.35,   // 35% weight for fuzzy + synonym matching
5    tfidf: 0.35,   // 35% weight for TF-IDF similarity
6} as const;
7
8export function calculateEnhancedIndustryMatch(
9    company: { industryCodes: string[]; capabilitiesDescription: string },
10    tender: { industryCategories: string[]; description: string; title: string }
11): EnhancedMatchResult {
12    const companyTerms = [...new Set([...companyFromCodes, ...companyFromDesc])];
13    const tenderTerms = [...new Set([...tenderFromCats, ...tenderFromDesc, ...tenderFromTitle])];
14
15    const exactScore = calculateExactMatch(companyTerms, tenderTerms);
16// ... (truncated)
TYPESCRIPT

Layer 1: Exact Matching (30%)

Layer 1 checks whether the company's registered industry codes (SIC codes, CIDB classifications, or category tags) appear in the tender's industry categories. This is the simplest layer but also the most reliable when the data is clean. It provides a strong baseline score for companies operating in well-classified industries.

Layer 2: Fuzzy and Synonym Matching (35%)

Layer 2 uses Levenshtein distance for fuzzy matching (catching typos and small variations like "electrical" vs. "electric") and a synonym dictionary for semantic expansions. The synonym dictionary is the secret sauce here:

1// From the synonym dictionary — a curated mapping of semantic equivalents
2export const INDUSTRY_SYNONYMS: Record<string, string[]> = {
3    'cleaning': ['hygiene', 'janitorial', 'facilities management', 'deep-cleaning', 'sanitation'],
4    'security': ['guarding', 'access control', 'surveillance', 'protection', 'risk management'],
5    'construction': ['building', 'infrastructure', 'civil works', 'development', 'renovation'],
6    'catering': ['food service', 'nutrition', 'hospitality', 'meal provision', 'kitchen services'],
7    // ... hundreds more mappings
8};
TYPESCRIPT

Layer 3: TF-IDF Cosine Similarity (35%)

Layer 3 computes a TF-IDF (Term Frequency-Inverse Document Frequency) vector for both the company's capability description and the tender's title-plus-description, then measures cosine similarity between them. This catches conceptual matches that the first two layers miss: for example, a company that describes itself as "providing mobile catering for mining camps" would match a tender for "remote site food services at coal mines" even though they share no common keywords.

The 13-Factor Scoring Model

Industry matching is just one dimension. The full matching engine scores each tender-company pair across 13 factors, each weighted by its relevance to the specific tender type:

1// Base weights for all 13 factors (sum = 100%)
2const BASE_WEIGHTS: FactorWeights = {
3    industry_match: 0.12,
4    bbbee_compliance: 0.10,
5    geographic_match: 0.08,
6    document_readiness: 0.03,
7    technical_capability: 0.05,
8    financial_capability: 0.10,
9    compliance_readiness: 0.03,
10    evaluation_alignment: 0.02,
11    experience_relevance: 0.15,
12    qualification_match: 0.12,
13    operational_capacity: 0.10,
14    requirement_compliance: 0.08,
15    historical_success: 0.02
16// ... (truncated)
TYPESCRIPT

Dynamic Weight Adjustment

The critical insight is that not all factors matter equally for every tender. A R50 million infrastructure project should weigh financial capability and CIDB grading much higher than a R200,000 catering contract. The weight calculator adjusts dynamically:

1static calculateWeights(tender: TenderCharacteristics): FactorWeights {
2    const weights = { ...BASE_WEIGHTS };
3
4    if (this.isHighValueTender(tender.estimatedValue))  // > R10M
5        this.applyAdjustments(weights, HIGH_VALUE_ADJUSTMENTS);
6    if (this.isTechnicalTender(tender))
7        this.applyAdjustments(weights, TECHNICAL_ADJUSTMENTS);
8    if (this.isConstructionTender(tender))
9        this.applyAdjustments(weights, CONSTRUCTION_ADJUSTMENTS);
10    if (this.isBBBEEFocused(tender))
11        this.applyAdjustments(weights, BBBEE_FOCUSED_ADJUSTMENTS);
12    if (this.isLocalFocused(tender))
13        this.applyAdjustments(weights, LOCAL_FOCUSED_ADJUSTMENTS);
14
15    this.applyEvaluationCriteriaWeights(weights, tender.evaluationCriteria);
16// ... (truncated)
TYPESCRIPT

For high-value tenders (>R10M), we increase the weight of financial capability and operational capacity. For technical tenders, qualification_match and technical_capability get boosted. For B-BBEE-focused tenders (where preference points dominate), bbbee_compliance weight increases significantly. The weights are always re-normalized to sum to exactly 100%.

Eligibility: From Hard Gates to Advisory Scores

One of our most important architectural decisions was moving from hard eligibility gates to advisory scoring. Previously, if a company's CIDB grade was below the tender's requirement, the match was killed entirely — no score, no visibility, nothing. The company would never even see the opportunity.

In June 2026, we changed this. Eligibility became a weighted signal (18/100) in the scoring engine instead of a hard gate. Now every tender-company pair gets a real score with a full factor breakdown. A small contractor sees: "You scored 52/100. Your CIDB grade is 2GB but this requires 4GB (+ you'd score 78 with a JV partner). Your B-BBEE level is strong. Your geographical match is good." This transparency lets companies surface their gaps and act on them.

The Matching Service Architecture

The engine is split into specialized matchers, each responsible for one factor:

  • exact-matcher.service.ts — Industry code and category matching
  • fuzzy-matcher.service.ts — Levenshtein distance and synonym matching
  • experience-matcher.service.ts — Past project relevance scoring
  • qualification-matcher.service.ts — Key personnel qualifications
  • capacity-matcher.service.ts — Operational capacity assessment
  • requirement-matcher.service.ts — Mandatory requirement compliance
  • historical-matcher.service.ts — Past success pattern analysis
  • competitive-analyzer.service.ts — Competitive landscape
  • feedback-loop.service.ts — User feedback integration
  • eligibility-checker.service.ts — Pre-screening eligibility (now advisory)

Each matcher implements a common interface and returns a normalized score (0-100). The orchestrator collects all scores, applies the dynamic weights, and produces the final match result. This modular design makes it easy to add new factors, tune existing ones, or A/B test weight configurations in production.

Cron Scheduling and Automation

Matching runs on a cron schedule. New tenders are ingested continuously via the ingestion pipeline. Every 40 minutes (was daily before June 2026), the full-matching cron scores new tenders against active company profiles. Five minutes later, the recommendations-worker generates personalized AI recommendations for high-scoring matches. At 06:00 daily, the digest cron sends a personalized email digest to each user.

1// From package.json — matching cron schedules
2"cron:trigger": "tsx scripts/trigger-cron.ts",
3
4// Every 40 minutes: full matching pass
5// 06:00 daily: personalized digest email
6// 5 min after matching: recommendations generation
TYPESCRIPT

Tender Radar: The User-Facing Interface

The matching engine powers Tender Radar, the user-facing interface where suppliers see their ranked matches. Tender Radar evolved from a simple list of tenders to a full workspace with:

  • Company profiles — 55 pre-built archetype profiles (from "Small Electrical Contractor" to "Mining Support Services") that users can adopt or customize
  • What-if scenario modelling — Users can model how upgrading their CIDB grade, forming a JV, or expanding provinces would affect their match scores
  • AI recommendations — Each match card can expand to show the AI's reasoning, estimated success probability, and concrete next-step actions
  • Saved and dismissed tenders — Synced across devices, with undo support
  • Daily email alerts — Ranked by fit and urgency, delivered every morning

Lessons Learned

  1. Hard gates hide opportunities. Moving from binary eligibility to advisory scoring was our highest-impact change. Companies that were silently excluded now see their gaps and can take action — including forming JVs to close them.
  2. Synonyms matter more than AI. The synonym dictionary has hundreds of curated mappings. In practice, this simple lookup table catches more real-world matches than our TF-IDF layer because South African tender vocabulary follows predictable patterns.
  3. Dynamic weights beat static models. A construction tender and a catering tender need completely different scoring priorities. The dynamic weight system lets us specialize without building separate engines.
  4. Feedback loops improve recall. Users can mark tenders as "Not Relevant," and the feedback-loop service adjusts future scoring for that user. Over time, this personalizes the engine without requiring explicit training data.
  5. Speed matters at scale. With 47,000+ tenders and thousands of company profiles, naive O(n*m) comparison is too slow. We batch score new tenders and cache results aggressively.

What's Next

The matching engine continues to evolve. We're exploring learned embeddings for the TF-IDF layer, expanding the synonym dictionary from community feedback, and building an automated A/B testing framework for weight configurations. The goal remains the same: help every South African supplier find the tender opportunities they would otherwise miss.

All matching engine code is in /home/ubuntu/tendersa/src/lib/services/matching/. The engine is fully open for inspection by our users — transparency is a core design principle.

Tags

AIMatching EngineArchitectureMachine LearningTender RadarTypeScriptNext.js
Relevant Tender Opportunities

Based on this article's topics, here are some current tenders that might interest you

Administrative and Support Activities

PROCUREMENT OF AN E-RECRUITMENT DIGITAL SERVICES PLATFORM WITH INTERGRATED CR...

Thulamela Local Municipality
Limpopo
23 Sept 2026
28d left
Services: Professional

Digital Trade Corridor platform (as defined by WCO) and third-party pre-arriv...

Interfront
Western Cape
17 Sept 2026
22d left
Services: Professional

APPOINTMENT OF A PANEL OF FIVE SERVICE PROVIDERS TO RENDER VARIOUS TRAINING SERVICES FOR A PERIOD OF 36 MONTHS Tenders are hereby invited from capable and experienced service providers for the above tender. 1. Only tenderers who have provided the following mandatory information and documents to be used to evaluate the bidder's responsiveness will be considered for further evaluation on functionality: 1.1 Only service providers that are registered on the Central Supplier Database will be considered for the awarding of this request for quotations and a copy of CSD report not later than three months should be attached. 1.2 Price quoted must be firm, VAT and other taxes inclusive, valid and fixed for duration of the contract. 1.3 No tenders shall be considered from a person/s who are in the service of the state. 1.4 Service providers are required to fully complete the attached MBD forms and submit together with their written tenders. 1.5 Attach a bank account confirmation letter with bank stamp not older than three months accompanied with an affidavit confirming the business bank account details - if the banking details are not verified on the CSD report. 1.6 Attach certified copy of identity documents (ID) of company directors. 1.7 Attach certified copy of the company registration certificate issued by the Companies and Intellectual Property Commission (CIPC). 1.8 Valid SARS pin certificate must be attached. 1.9 Attach verifiable Municipal Account/s not older than three months for both the tenderer and entity owner/s or director/s. In areas where the municipalities are not issuing municipal accounts, attach valid lease agreements or confirmation of residence or address for both the tenderer and entity owner/s or director/s issued by a relevant authority not older than three months. 1.10 Attach a certified copy for valid proof of registration with relevant SETA’s or Quality Council for Trades and Occupations (QCTO) accreditation. 1.11 Attach set of reviewed and signed Annual Financial Statements for the past three years or since the company came into existence if not older than three years or if so, required by law to prepare the Annual Financial Statements for auditing, attach set of audited annual financial statements for the past three years. 1.12 Bidders are advised not to commit any fraudulent activities, including forgery of documents. All abuses of the Supply Chain Management (SCM) systems including but not limited to forgery of returnable documents, may be reported to the South African Police Service (SAPS) and restricted from doing business with any public institution or organ of the state for a period not exceeding 10 years in line with the Prevention of Fraud and Corrupt Activities Act 12 of 2004. 1.13 Joint Venture or Consortium Agreement if applicable. 1.14 All tender documents must be duly signed and submitted on the PDF document that has been issued. All the certified documents as stated must not be older than three months. 2. The tender will be evaluated on the 80/20 preference point system in terms of the Preferential Procurement Policy of the Ehlanzeni District Municipality. The policy preference point system will be applied as follows: 2.1 The 80 points will be for price; and 2.2 The 20 points will be allocated for the specific goals on a proportional or pro rata basis as follows:- POINTS FOR CONTRACTING AN ENTERPRISE OWNED BY HISTORICALLY DISADVANTAGED PERSONS OR INDIVIDUALS HISTORICALLY DISADVANTAGED PERSONS OR INDIVIDUALS POINTS ALLOCATION SOURCE DOCUMENTS REQUIRED TO CLAIM POINTS 100% black person or people owned Enterprise 3,00 A copy of a Full CSD report not older than 3 months More than 30% woman or women shareholding or owned enterprise 2,00 More than 30% youth shareholding or owned enterprise 2,00 More than 30% people living with disability shareholding or owned Enterprise 2,00 A copy of a Medical Certificate to confirm disability or stated on the CSD report More than 30% military veteran’s shareholding or owned Enterprise 2,00 A copy of a Full CSD report not older than 3 months POINTS FOR IMPLEMENTING OF RDP PROGRAMMES Enterprises regarded as *EME’s located within the Ehlanzeni District Municipality area of jurisdiction 2,00 ? A copy of a Full CSD report not older than 3 months NB: Points will only be awarded if the CSD physical address is the same as the address for the proof of residence required in 1.7 above. Sub-contract minimum of 30% of the contract value to EME’s in the ward or local communities where the services to be rendered or works to be undertaken 2,00 ? Commitment letter of works or services to be sub-contracted. Corporate Social Investment (CSI) or Social Labour Plan proposition 3,00 ? Attach a CSI plan or Social Labour Plan B-BBEE level 1 contribution 2,00 ? Certified Valid SANAS Accredited BBBEE certificate; or ? Certified Valid EME and SME a sworn affidavit; or ? Certified Valid CIPC issued certificate confirming annual turnover and level of Black Ownership. TOTAL PREFERENCE POINTS TO CLAIMED 20,00 *EME’s are Exempted Micro Enterprise with an annual turnover of R10 million or less. Received bids will be evaluated for responsiveness based on mandatory requirements, functionality and bidders who obtain a minimum of 70 points out of a possible 100 points for further evaluation on the preference point scoring system. Bid documents can be viewed and downloaded at no cost on the Document Sharing and Collaboration Platform or Portal (NEPTUNE): http://edmservices.ehlanzeni.gov.za and National Treasury Portal from Monday, 03 August 2026. Further information regarding the downloading and uploading of documents will be explained at the compulsory briefing session. A compulsory briefing session will be held on Tuesday, 11 August 2026, 10h00 at Ehlanzeni District Municipality Office Complex, DMC, 8 Van Niekerk Street, Sonheuwel Central, Mbombela, 1201. Where bids should be submitted - Completed bid and other returnable documents must be submitted only in PDF format on the Document Sharing and Collaboration Platform or Portal: http://edmservices.ehlanzeni.gov.za on or before Monday, 31 August 2026 not later than 12h00. Enquiries: Contact Person - ADMINISTRATION: Mr. P Khumalo at 013 759 8-573 or [email protected] Contact Person – TECHNICAL: Mrs. C de Lange at 013 759 8500 or [email protected] Visit our website: www.ehlanzeni.gov.za for tender information. Employer: Acting Municipal Manager: Ms. S S Madlopha Ehlanzeni District Municipality P O Box 333,M MBOMBELA 1200

Ehlanzeni District Municipality
Mpumalanga
31 Aug 2026
5d left
Information and Communication

REQUEST FOR PRICE QUOTATIONS FOR PROCUREMENT AND DELIVERY OF ANNUAL VEEAM DATA PLATFORM ADVANCED UNIVERSAL LICENSES FOR THE NATIONAL LOTTERIES COMMISSION.

National Lotteries Commission
Gauteng
31 Aug 2026
5d left

Want to see all available tenders?

Browse All Tenders →
AI-Powered Matching
Never Miss a Perfect Tender Again
Our AI analyzes thousands of tenders and finds the ones YOUR company can actually win
AI Match Scoring for every tender
Instant alerts for 85%+ matches
B-BBEE level optimization
Document readiness checks

Share this article

How We Built the AI-Powered Tender Matching Engine at Tenders-SA

A deep dive into the architecture of the Tenders-SA tender matching engine: the 3-layer industry matcher, 13-factor scoring model with dynamic weight adjustments, eligibility gates, and how we evolved from hard rules to a probabilistic scoring system that surfaces relevant opportunities for South African suppliers.

https://www.tenders-sa.org/blog/building-ai-tender-matching-engine