Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/elecodes/TenderCheck-AI/llms.txt

Use this file to discover all available pages before exploring further.

Proposal validation is the second stage of the TenderCheck AI workflow. After a tender has been analysed and its requirements extracted, you upload the vendor’s proposal PDF. The system adopts the Senior Evaluator persona — a specialist in IT public tenders — and produces a ValidationResult for every requirement in the tender, with a MET, PARTIALLY_MET, or NOT_MET verdict, an AI-generated reasoning paragraph, and a numeric confidence score.

Validation Result Schema

Each comparison between a tender requirement and the proposal document is represented by a ValidationResult object.
export type ValidationStatus = "MET" | "NOT_MET" | "PARTIALLY_MET" | "AMBIGUOUS";

export interface ValidationResult {
  requirementId: string;
  status: ValidationStatus;

  // Evidence found in the proposal
  evidence?: {
    text: string;
    pageNumber: number;
    fileUrl?: string;
  };

  // AI-generated explanation for the verdict
  reasoning: string;

  // Confidence in the verdict — 0.0 to 1.0
  confidence: number;

  // Optional human override
  manualOverride?: {
    isOverride: boolean;
    newStatus: ValidationStatus;
    comment: string;
    updatedAt: Date;
  };
}
FieldDescription
requirementIdLinks the result back to a specific Requirement from the tender analysis
statusMET — fully satisfied; PARTIALLY_MET — partially satisfied; NOT_MET — not satisfied; AMBIGUOUS — insufficient evidence
evidence.textThe specific passage from the proposal that the AI identified as supporting or contradicting compliance
reasoningA full Spanish-language explanation of why the verdict was reached
confidenceA 0.0–1.0 score representing the AI’s certainty in its verdict
manualOverrideOptional human correction that supersedes the AI verdict without deleting the original reasoning

How Validation Works

1

Fetch tender requirements from the database

The ValidateProposal use case loads the saved TenderAnalysis (including all extracted Requirement objects) from Turso using the tenderId parameter. If no tender is found, a 404 error is returned immediately.
2

Parse the proposal PDF

The uploaded proposal PDF is parsed to extract its full plain text. If the extracted text is shorter than MIN_JUSTIFICATION_LENGTH (10 characters), the proposal is rejected as unreadable.
3

Generate embeddings for requirements if missing

Before running vector search, the system checks each requirement in the database for a stored embedding vector. Any requirement without one gets an embedding generated via gemini-embedding-001 and persisted back to the requirements table. This step ensures the vector index is always up to date.
4

Semantic similarity search

An embedding is generated for the full proposal text. The system then calculates the cosine similarity between the proposal embedding and every requirement embedding. Requirements above the SIMILARITY_THRESHOLD of 0.3 are flagged as relevant.
5

Batch LLM comparison

Relevant requirements are split into batches of BATCH_CHUNK_SIZE (3 requirements per batch). Up to MAX_AI_CONCURRENCY (3) batches are sent to Gemini in parallel using the Senior Evaluator system prompt, which instructs the model to perform a deep semantic search — not to return “not specified” if any numerical or project experience requirement is present in the text.
6

Map internal AI statuses to ValidationStatus

The AI returns internal statuses (COMPLIANT, NON_COMPLIANT, PARTIAL). These are normalised to the domain model:
  • COMPLIANTMET
  • PARTIALPARTIALLY_MET
  • NON_COMPLIANT or any other value → NOT_MET
7

Mark non-relevant requirements as NOT_MET

Requirements that did not pass the similarity threshold are automatically assigned a NOT_MET status with a confidence of 0.3 and a note that the requirement was filtered by semantic search. This completes the result set without incurring additional LLM calls.
8

Save results to the database

All ValidationResult objects are merged with any pre-existing SCOPE_CHECK result and saved back to the TenderAnalysis record in Turso. The complete list of results is then returned to the frontend.

Vector Search Pre-filtering

Before any LLM call is made, TenderCheck AI uses Google gemini-embedding-001 to generate high-dimensional vector embeddings (3,072 dimensions as implemented by VectorSearchService) and filters out requirements that are semantically unrelated to the proposal content.
ConstantValuePurpose
SIMILARITY_THRESHOLD0.3Minimum cosine similarity to consider a requirement relevant
TOP_K_SIMILAR5Default maximum results from findSimilar()
MAX_RELEVANT_REQUIREMENTS999Effective cap — all requirements are processed for maximum accuracy
VECTOR_DIMENSIONS768Dimension constant in constants.ts (actual model returns 3,072 D)
The similarity check uses cosine similarity computed entirely in memory — no external vector database is required. The result of this pre-filtering step is logged:
🎯 Vector search: 12/18 relevant requirements (33% reduction)
Requirements that fall below the threshold receive a NOT_MET verdict automatically, avoiding up to 60–80% of LLM calls compared to processing every requirement individually.
If vector search returns zero relevant requirements (e.g., embeddings are missing for all requirements), the system falls back to processing all requirements through the LLM pipeline so that no compliance signal is lost.

Confidence Score

The confidence field in every ValidationResult is normalised to the 0.0–1.0 range before being stored.
  • The AI may return confidence as a fraction (0.95) or as a percentage (95). TenderCheck AI detects this automatically: if the raw score is greater than 1, it is divided by 100.
  • If the AI returns no score at all, the system falls back to DEFAULT_CONFIDENCE_SCORE = 75, which normalises to 0.75.
  • The frontend displays confidence as a percentage by multiplying by 100 if the stored value is already in the 0–1 range.
Raw AI score → 95    → stored as 0.95  (95 > 1, divide by 100)
Raw AI score → 0.88  → stored as 0.88  (≤ 1, kept as-is)
No score     → 75    → stored as 0.75  (default, 75 > 1, divide by 100)

How to Validate a Proposal

1

Complete a tender analysis first

Proposal validation requires an existing, COMPLETED tender analysis. Follow the Tender Analysis flow to extract requirements before proceeding.
2

Open the completed analysis

Click the analysis in the History Sidebar or remain on the results page after the analysis completes.
3

Upload the proposal PDF

In the Validate Proposal upload zone (labeled Oferta), drop or select the vendor’s proposal PDF. Only .pdf files up to 50 MB are accepted.
4

Submit and wait for validation

Click Validate. The backend runs the full 8-step pipeline described above. Processing time scales with the number of requirements and the size of the proposal.
5

Review the Validation Summary

The Validation Summary panel shows three metrics: overall compliance score (%), mandatory requirements met (e.g., 12 / 15), and optional requirements met.
6

Inspect individual verdicts in ComparisonResults

Each requirement card shows its full text, a MET / PARTIALLY_MET / NOT_MET badge, the AI’s reasoning paragraph, and any evidence quote extracted from the proposal. AI confidence is shown as a percentage.

Exporting Results

Once validation is complete, the full analysis — including all requirements and their validation verdicts — can be exported from the AnalysisResults header card:

PDF Export

Generates a professionally formatted, branded PDF report via export.service.ts. Suitable for sharing with procurement teams or archiving.

JSON Export

Exports the complete TenderAnalysis object (including all ValidationResult entries) as a structured JSON file. Useful for integration with other systems.
Use the sample proposal document at docs/Testing_docs/Oferta_Offer_IT_Security.pdf (included in the repository) to test the full validation pipeline against the matching IT security tender. The pair is designed to produce a mix of MET, PARTIALLY_MET, and NOT_MET results.

Build docs developers (and LLMs) love