Choosing the Technology Stack for the AI Tender Opportunity Scanner

Introduction

The previous article defined the Minimum Viable Product for the AI Tender Opportunity Scanner.

The first version will allow a technology service provider to create a business profile, import public tender notices, receive AI-generated summaries, view relevance scores, and save or dismiss opportunities.

We now need to decide which technologies will turn that product definition into a working application.

Technology selection is not simply a matter of choosing the newest or most popular tools. Every component should support the requirements of the product while remaining manageable for a solo developer or small development team.

The selected stack must support:

  • Tender-data ingestion
  • Structured data processing
  • AI integration
  • Semantic matching
  • Relational data storage
  • Background processing
  • API development
  • A responsive web interface
  • Automated testing
  • Containerized deployment
  • Future SaaS features

In this article, we will select the backend framework, frontend tools, database, AI services, infrastructure, and development utilities for the AI Tender Opportunity Scanner.


Development Progress

Project Scope and Roadmap ████████████████████ 100%
MVP Definition ████████████████████ 100%
Technology Stack ████████████████████ 100%
System Architecture ░░░░░░░░░░░░░░░░░░░░ 0%
Database Design ░░░░░░░░░░░░░░░░░░░░ 0%
Backend Foundation ░░░░░░░░░░░░░░░░░░░░ 0%
Tender Ingestion ░░░░░░░░░░░░░░░░░░░░ 0%
AI Analysis ░░░░░░░░░░░░░░░░░░░░ 0%
Matching Engine ░░░░░░░░░░░░░░░░░░░░ 0%
Frontend Dashboard ░░░░░░░░░░░░░░░░░░░░ 0%

Technology-Selection Principles

Before selecting individual tools, we need a set of decision criteria.

1. Keep the First Version Simple

The MVP does not need a distributed architecture, multiple programming languages, or a large collection of infrastructure services.

The first version should be understandable by one developer.

A developer should be able to start the complete local environment, inspect the data flow, run tests, and debug failures without operating a miniature enterprise platform.


2. Choose Technologies That Match the Problem

The application will perform substantial text processing, data normalization, AI integration, and background work.

Python is particularly suitable for these tasks because of its broad ecosystem for:

  • HTTP requests
  • Data processing
  • Natural-language processing
  • AI APIs
  • Embeddings
  • Database access
  • Automation
  • Background jobs

The frontend has a different responsibility. It needs to provide a structured, interactive dashboard where users can review and manage opportunities.

React and TypeScript are well suited to that part of the system.


3. Preserve a Clear Upgrade Path

Simple does not mean disposable.

The initial stack should allow us to add:

  • Authentication
  • Multiple users
  • Subscription plans
  • Additional tender sources
  • Scheduled ingestion
  • Email notifications
  • More advanced search
  • Multiple business profiles
  • Team collaboration

We should not build all these features immediately, but the selected technologies should not prevent their introduction.


4. Prefer Mature and Well-Documented Tools

An MVP should not depend on experimental infrastructure unless the experiment itself is the product.

The AI matching logic may require experimentation.

The database, API framework, frontend build system, and migration tools should be predictable.


5. Avoid Premature Microservices

The AI Tender Opportunity Scanner contains several logical capabilities:

  • Tender ingestion
  • Tender normalization
  • AI analysis
  • Matching
  • Notifications
  • Account management
  • API delivery

These capabilities do not initially need to become separate deployed services.

We will organize them as modules inside one backend application.

This approach is known as a modular monolith.

It gives us clean separation without the operational cost of microservices.


Final Stack Overview

The selected MVP stack is:

Backend language: Python
Backend framework: FastAPI
Validation and schemas: Pydantic
ORM: SQLAlchemy
Database migrations: Alembic
Primary database: PostgreSQL
Vector search: pgvector
Frontend library: React
Frontend language: TypeScript
Frontend build tool: Vite
Routing: React Router
Data fetching: Native Fetch initially
AI integration: OpenAI API
HTTP collection: HTTPX
HTML parsing: Beautiful Soup when required
Scheduling: Simple worker or scheduler
Background jobs: In-process initially
Testing: Pytest and Vitest
Containers: Docker
Local orchestration: Docker Compose
Version control: Git and GitHub
Continuous integration: GitHub Actions

Each choice is explained below.


Backend Language: Python

Python will be the primary backend language.

The product is highly dependent on data ingestion and language processing. These responsibilities align naturally with Python.

Python will be used to:

  • Retrieve tender notices
  • Validate source responses
  • Normalize external data
  • Store tender records
  • Call AI services
  • Generate embeddings
  • Calculate relevance scores
  • Create match explanations
  • Run scheduled jobs
  • Serve API endpoints

Using one language for ingestion, AI processing, business logic, and the API reduces unnecessary complexity.


Why Not Node.js for the Backend?

Node.js could also build the application successfully.

It offers strong support for APIs, web applications, scheduled tasks, and database access.

However, Python is the better fit for this project because the backend will contain substantial:

  • Text-analysis logic
  • Data-transformation code
  • AI experimentation
  • Similarity calculations
  • Procurement-data processing

Using Python also gives us access to a broad ecosystem if we later introduce local NLP models, document extraction, classification, or data-science workflows.

Node.js will remain part of the project through the frontend build environment.


Backend Framework: FastAPI

FastAPI will provide the application’s HTTP API.

FastAPI uses Python type hints and integrates closely with Pydantic models. It also generates OpenAPI documentation and interactive API interfaces automatically.

This makes it particularly suitable for an API-first SaaS application.


Why FastAPI Fits This Project

Type-Based Validation

Tender records contain many structured fields:

  • Identifiers
  • Dates
  • Currency values
  • Country codes
  • URLs
  • Lists of services
  • Match scores
  • Processing statuses

FastAPI and Pydantic allow these structures to be defined explicitly.

Invalid request and response data can therefore be detected early.


Automatic API Documentation

During development, FastAPI provides interactive API documentation.

This will help us test endpoints such as:

GET /api/tenders
GET /api/tenders/{id}
POST /api/business-profile
POST /api/admin/ingestion/run

We will be able to inspect parameters, send test requests, and review response structures without building a separate testing interface first.


Async Support

Tender collectors and AI integrations perform network requests.

These operations frequently spend time waiting for:

  • Procurement APIs
  • Source websites
  • AI providers
  • Email services
  • Object storage

FastAPI supports asynchronous request handling, making it suitable for I/O-heavy applications.

Not every function needs to be asynchronous, but the option is valuable for external integrations.


Clear Router Structure

FastAPI supports organizing endpoints into routers.

Our backend can eventually include modules such as:

api/routes/tenders.py
api/routes/business_profiles.py
api/routes/matches.py
api/routes/saved_tenders.py
api/routes/admin.py
api/routes/auth.py

This keeps the application structured as it grows.


Validation and API Schemas: Pydantic

Pydantic will define the data structures that enter and leave the API.

We will use Pydantic models for:

  • Tender creation data
  • Tender responses
  • Business profiles
  • Filter parameters
  • AI-generated structured output
  • Match explanations
  • Ingestion reports
  • Application configuration

A simplified tender response might eventually resemble:

class TenderResponse(BaseModel):
id: UUID
title: str
contracting_authority: str | None
country_code: str | None
publication_date: date | None
submission_deadline: datetime | None
estimated_value: Decimal | None
currency: str | None
relevance_score: float | None

Database models and API schemas will remain separate.

SQLAlchemy models describe how information is stored.

Pydantic models describe how information is accepted, validated, and returned.

This separation prevents the external API from becoming tightly coupled to the internal database structure.


Database: PostgreSQL

PostgreSQL will be the primary database.

The product has strongly relational data:

  • Users own business profiles.
  • Sources publish tenders.
  • Tenders have summaries.
  • Profiles are matched to tenders.
  • Users save or dismiss tenders.
  • Ingestion runs contain processing results.
  • Alerts contain multiple matched opportunities.

A relational database is therefore a natural choice.


Why PostgreSQL Fits This Project

Relational Integrity

PostgreSQL allows us to enforce relationships and constraints.

For example:

  • A tender must reference a valid source.
  • A tender match must reference a valid profile and tender.
  • A saved tender must reference a valid user.
  • A source and external identifier combination must be unique.
  • A relevance score must remain within an allowed range.

These rules help protect data quality.


Transaction Support

Tender ingestion involves multiple database operations.

For example:

  1. Create or update the tender.
  2. Store the raw source response.
  3. Create an ingestion log.
  4. Update processing status.
  5. Commit the transaction.

Transactions prevent partial updates from leaving the database in an inconsistent state.


JSONB Storage

External tender sources may return source-specific fields that do not belong in the normalized schema.

PostgreSQL’s jsonb type allows us to retain the original source payload while still storing normalized fields in regular columns. PostgreSQL also supports indexing and querying JSONB data.

A tender can therefore contain:

Normalized columns:
- title
- description
- authority
- deadline
- value
- country
JSONB column:
- raw source payload

This gives us both consistency and traceability.


Full-Text Search

PostgreSQL includes full-text search functionality, including document indexing, query parsing, result ranking, and highlighting.

This allows the early product to support search across:

  • Tender titles
  • Descriptions
  • Authorities
  • Categories
  • AI summaries

A separate search engine such as Elasticsearch or OpenSearch is not required for the MVP.


Vector Search

We plan to use embeddings for semantic matching.

PostgreSQL can be extended with pgvector so that vector representations can be stored alongside relational tender data.

This allows us to keep:

  • Business profiles
  • Tender descriptions
  • Embeddings
  • Match results

inside the same database.

A separate vector database is unnecessary at the beginning.


Why Not SQLite?

SQLite is excellent for prototypes, scripts, and small local applications.

However, the AI Tender Opportunity Scanner requires:

  • Concurrent access
  • Background processing
  • Strong data constraints
  • JSON storage
  • Vector search
  • Production deployment
  • Multi-user expansion

Starting with PostgreSQL avoids a later database migration and gives us a development environment that more closely resembles production.


Why Not MongoDB?

Tender source data may appear document-oriented, which could make MongoDB seem attractive.

However, the core application has many relational workflows:

  • Profiles matched to tenders
  • Users saving tenders
  • Sources producing notices
  • Alerts containing matches
  • Processing jobs connected to tenders
  • Unique constraints across source identifiers

PostgreSQL handles these relationships naturally while still supporting flexible JSONB fields.

MongoDB does not provide a compelling advantage for the first version.


Object-Relational Mapping: SQLAlchemy

SQLAlchemy will connect the Python application to PostgreSQL.

It will allow us to define database entities as Python classes and write database queries using Python.

Likely models include:

User
BusinessProfile
TenderSource
Tender
TenderSummary
TenderMatch
SavedTender
DismissedTender
IngestionRun
ProcessingError

Why Use an ORM?

An ORM provides:

  • Reusable database models
  • Relationship definitions
  • Query composition
  • Transaction management
  • Database-session handling
  • Reduced repetitive SQL
  • Easier testing

We will still need to understand the SQL generated by the application.

An ORM is not a substitute for database knowledge.

It is a structured interface for implementing the data layer.


SQLAlchemy 2-Style Development

The project will use SQLAlchemy’s modern typed model and query patterns.

Models should include explicit types, relationships, indexes, and constraints.

Example:

class Tender(Base):
__tablename__ = "tenders"
id: Mapped[UUID] = mapped_column(primary_key=True)
source_id: Mapped[UUID] = mapped_column(
ForeignKey("tender_sources.id")
)
external_id: Mapped[str] = mapped_column()
title: Mapped[str] = mapped_column()

The exact models will be designed in a later article.


Database Migrations: Alembic

Alembic will manage database schema changes.

Alembic is designed to create and execute relational database change scripts using SQLAlchemy as its underlying database toolkit.

As the project develops, we will need to:

  • Create tables
  • Add columns
  • Create indexes
  • Introduce constraints
  • Rename fields
  • Change relationships
  • Add vector columns

These changes should be recorded as migration files rather than applied manually.


Example Migration Workflow

A typical development workflow will be:

alembic revision --autogenerate -m "create tender tables"
alembic upgrade head

Alembic can compare SQLAlchemy metadata with the current database schema and generate candidate migration operations. Generated migrations must still be reviewed before execution.

Migration files will be committed to Git so that every environment can reproduce the same schema.


Vector Storage: pgvector

The first matching engine will combine deterministic rules with semantic similarity.

To calculate semantic similarity, we will generate vector embeddings for:

  • Business descriptions
  • Offered services
  • Tender titles
  • Tender descriptions
  • AI-extracted deliverables

pgvector will allow PostgreSQL to store and compare these embeddings.


Why Not Use a Separate Vector Database?

Dedicated vector databases can be useful when handling:

  • Very large embedding collections
  • Highly specialized vector workloads
  • Distributed retrieval infrastructure
  • Billions of vectors
  • Complex hybrid-search requirements

The MVP will contain a relatively modest collection of tender and profile vectors.

Using a separate vector database would introduce:

  • Another service
  • Another data model
  • Data synchronization requirements
  • Additional credentials
  • Additional backups
  • Additional deployment work

PostgreSQL with pgvector is the simpler starting point.


Frontend Library: React

React will be used to build the user interface.

React applications are composed from reusable UI components, ranging from small controls to complete pages.

This component model fits the dashboard-oriented interface of the Tender Opportunity Scanner.


Planned React Components

The application will likely include components such as:

TenderCard
TenderTable
TenderFilters
MatchScoreBadge
DeadlineBadge
TenderSummary
MatchExplanation
BusinessProfileForm
SaveTenderButton
DismissTenderButton
IngestionStatus

These components can be reused across the dashboard, saved-tender page, and tender-detail page.


Why React?

Reusable UI

Tender data appears in multiple contexts.

For example, a relevance-score component can appear in:

  • The dashboard table
  • The tender detail page
  • The saved-tender page
  • A future email preview
  • An administrative matching screen

React encourages us to create this logic once and reuse it.


Strong Ecosystem

The product may later require:

  • Forms
  • Tables
  • Authentication
  • Charts
  • Date pickers
  • Notifications
  • Accessible components
  • Internationalization

React provides a broad ecosystem for these needs.

We will only add libraries when a clear requirement appears.


Incremental Development

We can begin with a small number of pages and expand gradually.

React does not require us to design the entire interface before the first screen becomes usable.


Frontend Language: TypeScript

TypeScript will be used instead of plain JavaScript.

The application handles structured data returned by FastAPI.

Examples include:

Tender
TenderSummary
TenderMatch
BusinessProfile
IngestionRun
PaginatedTenderResponse

TypeScript allows us to represent these structures explicitly.

Example:

interface TenderListItem {
id: string;
title: string;
contractingAuthority: string | null;
countryCode: string | null;
submissionDeadline: string | null;
estimatedValue: number | null;
currency: string | null;
relevanceScore: number | null;
}

This reduces common errors involving missing fields, incorrect assumptions, and inconsistent API handling.


Frontend Build Tool: Vite

Vite will create and build the React application.

Vite provides a development environment, plugin support, and a production build process that outputs deployable frontend assets.

It gives us a straightforward project setup without the heavier conventions of a full-stack frontend framework.


Why Vite Instead of a Full-Stack React Framework?

Frameworks such as Next.js are powerful and appropriate for many applications.

However, the Tender Opportunity Scanner will initially be:

  • An authenticated dashboard
  • Backed by a separate FastAPI API
  • Primarily client-rendered
  • Not dependent on public search-engine indexing
  • Not dependent on server-side React rendering

Vite keeps the frontend architecture simple.

A public marketing website can later be implemented separately or added using a suitable content-management system.


Frontend Routing: React Router

React Router will handle client-side navigation.

Initial routes may include:

/
/tenders
/tenders/:tenderId
/saved
/profile
/admin/ingestion

A later authenticated version may add:

/login
/register
/settings
/billing

Using a routing library keeps page navigation explicit and manageable.


Frontend Data Fetching

The first version can use the browser’s native Fetch API through a small reusable API client.

Example structure:

src/api/client.ts
src/api/tenders.ts
src/api/businessProfiles.ts
src/api/admin.ts

We should not introduce a complex state-management system before it is needed.

A later version may adopt a server-state library if requirements such as caching, automatic refetching, request deduplication, and optimistic updates become significant.

For the MVP, simple and explicit data fetching is preferable.


AI Provider: OpenAI API

The OpenAI API will initially be used for:

  • Structured tender extraction
  • Tender summarization
  • Requirement identification
  • Certification extraction
  • Match explanation generation
  • Embedding generation

The AI provider will be accessed through a dedicated application service rather than directly from API routes.


AI Service Boundary

The backend should contain an abstraction such as:

AIAnalysisService
EmbeddingService
TenderSummaryService
MatchExplanationService

Application modules will call these services without depending heavily on provider-specific request structures.

This will make it easier to:

  • Change models
  • Adjust prompts
  • Record token usage
  • Add retries
  • Test without making real API calls
  • Evaluate another provider later

Structured Outputs

AI-generated content should not be accepted as unstructured text when the application expects specific fields.

The summary pipeline should request structured data such as:

{
"overview": "...",
"deliverables": [],
"requirements": [],
"certifications": [],
"risks": [],
"contract_duration": null,
"language_requirements": []
}

The returned data will be validated before being stored.

If validation fails, the system should record the error and retry according to a controlled policy.


AI Should Not Control the Entire Match Score

The language model will help interpret tender text, but it should not independently produce the final relevance score.

The final score should combine measurable criteria:

  • Keyword overlap
  • Semantic similarity
  • Geographic compatibility
  • Contract-value fit
  • Industry fit
  • Certification fit
  • Deadline viability

This hybrid design is more explainable and easier to test.


Tender Collection: HTTPX

HTTPX will be used for outbound HTTP requests.

Collectors may need to call:

  • REST APIs
  • JSON feeds
  • XML feeds
  • Public search endpoints
  • Tender-detail endpoints

HTTPX supports both synchronous and asynchronous request patterns, making it compatible with our FastAPI environment.

The source adapter will be responsible for:

  • Constructing requests
  • Applying authentication when required
  • Handling pagination
  • Respecting timeouts
  • Retrying transient failures
  • Validating responses
  • Logging source errors

HTML Parsing: Beautiful Soup

Official APIs and structured feeds should be preferred.

However, a procurement source may occasionally expose important information only through HTML pages.

Beautiful Soup can be used for carefully controlled HTML parsing when:

  • Collection is permitted
  • No suitable API exists
  • The page structure is stable enough
  • Robots and usage conditions are respected
  • Request frequency is controlled

Scraping should not become the default ingestion strategy.

Every source must be assessed individually.


Background Processing

Tender ingestion and AI analysis should not run inside normal user-facing HTTP requests once the application becomes operational.

These tasks can take time and may fail independently.

Examples include:

  • Importing several pages of notices
  • Generating summaries
  • Calculating embeddings
  • Reprocessing failed tenders
  • Updating matches
  • Sending alerts

MVP Approach

For the earliest local prototype, we can use:

  • A command-line script
  • An administrative endpoint
  • A simple scheduled process
  • Lightweight in-process background execution

This keeps the setup manageable while we test the pipeline.


Later Upgrade Path

When reliability requirements increase, we can introduce:

Redis
Celery
Dramatiq
Arq
RQ

A dedicated task queue becomes valuable when we need:

  • Persistent jobs
  • Multiple workers
  • Retry policies
  • Queue prioritization
  • Scheduled execution
  • Job monitoring
  • Horizontal scaling

We should not introduce Redis and a full worker system before the ingestion and AI pipeline have been proven.


Application Configuration

Configuration will be loaded from environment variables.

Typical settings may include:

APP_ENV
APP_NAME
DATABASE_URL
OPENAI_API_KEY
FRONTEND_URL
TENDER_SOURCE_BASE_URL
TENDER_SOURCE_API_KEY
LOG_LEVEL

Local secret values will be stored in a .env file.

The .env file must not be committed to Git.

A safe template will be added as:

.env.example

Example:

APP_ENV=development
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/tender_scanner
OPENAI_API_KEY=replace-with-your-key
FRONTEND_URL=http://localhost:5173

Production secrets should be supplied by the deployment environment rather than stored in repository files.


Containerization: Docker

Docker will package the application components into reproducible containers.

The project will eventually contain at least:

Backend container
Frontend container
PostgreSQL container

A later version may add:

Worker container
Redis container
Reverse proxy

Containers reduce differences between development, testing, and production environments.


Local Orchestration: Docker Compose

Docker Compose will define and run the local multi-container application.

Compose allows services, networks, and volumes to be described in a YAML configuration and started together.

A future compose.yaml may contain:

services:
database:
image: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
backend:
build: ./backend
depends_on:
- database
frontend:
build: ./frontend
depends_on:
- backend
volumes:
postgres_data:

The real configuration will include environment variables, health checks, ports, and development commands.


Testing Strategy

The technology stack must support testing from the beginning.

Backend Testing: Pytest

Pytest will be used for:

  • Unit tests
  • API tests
  • Database tests
  • Collector tests
  • Normalization tests
  • Matching tests
  • AI-service tests

Critical test areas include:

Duplicate detection
Tender normalization
Score calculation
Expired-deadline handling
Included keywords
Excluded keywords
Contract-value rules
Source-adapter failures
AI output validation

External AI and procurement requests should normally be mocked during automated tests.


Frontend Testing: Vitest

Vitest will be used for frontend unit and component tests.

Initial tests may cover:

  • Match-score formatting
  • Deadline formatting
  • Filter behavior
  • Tender-card rendering
  • Empty states
  • Error messages
  • Save and dismiss interactions

End-to-end browser testing can be introduced later when the principal workflow is stable.


Code Quality Tools

Python

The backend can use:

  • Ruff for linting and formatting
  • Mypy or another static checker where valuable
  • Pre-commit hooks
  • Pytest for tests

TypeScript

The frontend can use:

  • ESLint
  • TypeScript compiler checks
  • Prettier where desired
  • Vitest for tests

The objective is not to create an excessively strict development environment.

The objective is to catch predictable errors automatically.


Version Control: Git and GitHub

Git will track the project’s source code.

GitHub will host the repository and support:

  • Issues
  • Pull requests
  • Development branches
  • Release tags
  • GitHub Actions
  • Documentation
  • Project discussions

The proposed repository name is:

tender-opportunity-scanner

The initial branch strategy can remain simple:

main
feature/*
fix/*

A complicated release-branch model is unnecessary for an early-stage product.


Continuous Integration: GitHub Actions

GitHub Actions will run automated checks when code is pushed or a pull request is created.

The first workflow should eventually perform:

Install backend dependencies
Run backend linting
Run backend tests
Install frontend dependencies
Run TypeScript checks
Run frontend linting
Run frontend tests
Build frontend

Later workflows can include:

  • Docker image builds
  • Security scans
  • Migration checks
  • Staging deployment
  • Production deployment

Continuous integration helps prevent changes in one module from silently breaking another.


Logging and Observability

The MVP needs useful logs, especially around ingestion and AI processing.

Each important operation should record:

  • Timestamp
  • Operation type
  • Tender source
  • Tender identifier
  • Processing status
  • Error category
  • Retry count
  • Execution duration

Examples:

Tender ingestion started
Source page retrieved
Tender normalized
Duplicate tender skipped
AI summary generated
Embedding created
Matching completed
Tender processing failed

The first version can use structured application logs written to standard output.

A hosted monitoring platform can be introduced after deployment.


Security Foundations

Authentication and billing are postponed, but security cannot be ignored.

The initial stack should support:

  • Environment-based secret management
  • Input validation
  • Parameterized database queries
  • HTTPS in production
  • CORS restrictions
  • Request-size limits
  • Controlled source requests
  • Dependency scanning
  • Audit logging
  • Separation of raw and normalized data

The application must never expose AI provider keys or source credentials to the React frontend.

All third-party API calls requiring secrets must pass through the backend.


Proposed Development Environment

The developer workstation will require:

Git
Python
Node.js
npm
Docker Desktop
Visual Studio Code or another editor

A typical local workflow will eventually be:

git clone <repository>
cd tender-opportunity-scanner
docker compose up -d database
cd backend
python -m venv .venv
pip install -r requirements.txt
alembic upgrade head
uvicorn app.main:app --reload
cd ../frontend
npm install
npm run dev

These commands are illustrative.

The exact setup will be defined after we create the project structure.


Proposed Repository Layout

The project will use one repository containing the backend, frontend, and infrastructure files.

tender-opportunity-scanner/
├── backend/
├── app/
├── tests/
├── alembic/
├── pyproject.toml
└── Dockerfile
├── frontend/
├── src/
├── public/
├── package.json
└── Dockerfile
├── infrastructure/
└── scripts/
├── docs/
├── compose.yaml
├── .env.example
├── .gitignore
├── README.md
└── LICENSE

The detailed folder structure will be designed in a later implementation article.


Technologies We Will Not Introduce Yet

Kubernetes

The MVP does not need container orchestration at Kubernetes scale.

Docker-based deployment is sufficient.


Elasticsearch or OpenSearch

PostgreSQL full-text search is adequate for the first version.

A dedicated search platform should only be introduced if measurable search requirements exceed what PostgreSQL can handle conveniently.


Separate Vector Database

pgvector is sufficient for the expected initial volume.


GraphQL

The frontend requires straightforward resource-based operations.

A REST API is simpler to build, document, and test.


Microservices

The application will begin as a modular monolith.

Service extraction can occur later when there is a demonstrated scaling or organizational reason.


Complex Global State Management

The initial frontend will use local component state and small shared contexts where needed.

A larger state-management library should only be added when the interface develops a genuine shared-state problem.


Multiple AI Providers

The application should contain an abstraction around its AI services, but the first version will use one provider.

Supporting multiple providers immediately would multiply configuration, testing, and prompt-compatibility work.


Data Warehouse

Operational PostgreSQL queries and basic reporting are enough for the MVP.

A separate warehouse becomes relevant only when historical analytics and large-scale reporting become important.


Technology Decision Summary

AreaSelected TechnologyPrimary Reason
Backend languagePythonStrong data and AI ecosystem
API frameworkFastAPITyped APIs and automatic documentation
ValidationPydanticStructured input and output validation
ORMSQLAlchemyMature relational data access
MigrationsAlembicRepeatable schema evolution
DatabasePostgreSQLRelational data, JSONB and search
Vector storagepgvectorSemantic search inside PostgreSQL
FrontendReactReusable dashboard components
Frontend languageTypeScriptSafer API integration
Build toolViteSimple and fast frontend workflow
AIOpenAI APISummaries, extraction and embeddings
HTTP clientHTTPXExternal source integration
HTML parsingBeautiful SoupControlled fallback parsing
Backend testingPytestFlexible Python testing
Frontend testingVitestVite-compatible test environment
ContainersDockerReproducible environments
Local orchestrationDocker ComposeSimple multi-service management
Source controlGitHubRepository, collaboration and CI
CIGitHub ActionsAutomated quality checks

Final Technology Stack

The MVP will use:

React + TypeScript + Vite
|
v
FastAPI API
|
+---------+----------+
| |
v v
PostgreSQL OpenAI API
|
v
pgvector
Supporting tools:
SQLAlchemy
Alembic
Pydantic
HTTPX
Pytest
Vitest
Docker
Docker Compose
GitHub Actions

This stack provides all the capabilities required for the MVP without introducing unnecessary infrastructure.


Development Principle

A technology should only be introduced when it solves a current, clearly defined problem.

We will not add infrastructure because it may become useful someday.

We will build the simplest version that supports the current product stage and preserve clean boundaries so that individual components can be upgraded later.


What We Will Build Next

Now that the technology stack has been selected, the next step is designing how the components will work together.

The next article will define:

  • The application boundaries
  • The frontend-to-backend flow
  • The tender-ingestion pipeline
  • The AI-processing pipeline
  • The matching workflow
  • Background execution
  • Database responsibilities
  • Failure handling
  • Security boundaries

The next article in the development series is:

Designing the High-Level System Architecture for the AI Tender Opportunity Scanner.


Conclusion

The technology stack for the AI Tender Opportunity Scanner has been selected around the actual requirements of the MVP.

Python and FastAPI will handle the API, tender ingestion, AI processing, and matching logic.

PostgreSQL will store relational data, raw source payloads, searchable text, and embeddings.

React, TypeScript, and Vite will provide a structured and maintainable user interface.

Docker and Docker Compose will create a reproducible development environment, while GitHub and automated testing will support reliable development.

Most importantly, the architecture will begin as a modular monolith.

This gives us enough structure to build a serious SaaS application without introducing the operational burden of microservices, multiple databases, or unnecessary infrastructure.

With the technology choices established, we are ready to design the complete system architecture.

Discover more from BidRadar

Subscribe now to keep reading and get access to the full archive.

Continue reading