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: PythonBackend framework: FastAPIValidation and schemas: PydanticORM: SQLAlchemyDatabase migrations: AlembicPrimary database: PostgreSQLVector search: pgvectorFrontend library: ReactFrontend language: TypeScriptFrontend build tool: ViteRouting: React RouterData fetching: Native Fetch initiallyAI integration: OpenAI APIHTTP collection: HTTPXHTML parsing: Beautiful Soup when requiredScheduling: Simple worker or schedulerBackground jobs: In-process initiallyTesting: Pytest and VitestContainers: DockerLocal orchestration: Docker ComposeVersion control: Git and GitHubContinuous 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/tendersGET /api/tenders/{id}POST /api/business-profilePOST /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.pyapi/routes/business_profiles.pyapi/routes/matches.pyapi/routes/saved_tenders.pyapi/routes/admin.pyapi/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:
- Create or update the tender.
- Store the raw source response.
- Create an ingestion log.
- Update processing status.
- 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- countryJSONB 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:
UserBusinessProfileTenderSourceTenderTenderSummaryTenderMatchSavedTenderDismissedTenderIngestionRunProcessingError
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:
TenderCardTenderTableTenderFiltersMatchScoreBadgeDeadlineBadgeTenderSummaryMatchExplanationBusinessProfileFormSaveTenderButtonDismissTenderButtonIngestionStatus
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:
TenderTenderSummaryTenderMatchBusinessProfileIngestionRunPaginatedTenderResponse
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.tssrc/api/tenders.tssrc/api/businessProfiles.tssrc/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:
AIAnalysisServiceEmbeddingServiceTenderSummaryServiceMatchExplanationService
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:
RedisCeleryDramatiqArqRQ
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_ENVAPP_NAMEDATABASE_URLOPENAI_API_KEYFRONTEND_URLTENDER_SOURCE_BASE_URLTENDER_SOURCE_API_KEYLOG_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=developmentDATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/tender_scannerOPENAI_API_KEY=replace-with-your-keyFRONTEND_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 containerFrontend containerPostgreSQL container
A later version may add:
Worker containerRedis containerReverse 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: - backendvolumes: 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 detectionTender normalizationScore calculationExpired-deadline handlingIncluded keywordsExcluded keywordsContract-value rulesSource-adapter failuresAI 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:
mainfeature/*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 dependenciesRun backend lintingRun backend testsInstall frontend dependenciesRun TypeScript checksRun frontend lintingRun frontend testsBuild 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 startedSource page retrievedTender normalizedDuplicate tender skippedAI summary generatedEmbedding createdMatching completedTender 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:
GitPythonNode.jsnpmDocker DesktopVisual Studio Code or another editor
A typical local workflow will eventually be:
git clone <repository>cd tender-opportunity-scannerdocker compose up -d databasecd backendpython -m venv .venvpip install -r requirements.txtalembic upgrade headuvicorn app.main:app --reloadcd ../frontendnpm installnpm 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
| Area | Selected Technology | Primary Reason |
|---|---|---|
| Backend language | Python | Strong data and AI ecosystem |
| API framework | FastAPI | Typed APIs and automatic documentation |
| Validation | Pydantic | Structured input and output validation |
| ORM | SQLAlchemy | Mature relational data access |
| Migrations | Alembic | Repeatable schema evolution |
| Database | PostgreSQL | Relational data, JSONB and search |
| Vector storage | pgvector | Semantic search inside PostgreSQL |
| Frontend | React | Reusable dashboard components |
| Frontend language | TypeScript | Safer API integration |
| Build tool | Vite | Simple and fast frontend workflow |
| AI | OpenAI API | Summaries, extraction and embeddings |
| HTTP client | HTTPX | External source integration |
| HTML parsing | Beautiful Soup | Controlled fallback parsing |
| Backend testing | Pytest | Flexible Python testing |
| Frontend testing | Vitest | Vite-compatible test environment |
| Containers | Docker | Reproducible environments |
| Local orchestration | Docker Compose | Simple multi-service management |
| Source control | GitHub | Repository, collaboration and CI |
| CI | GitHub Actions | Automated quality checks |
Final Technology Stack
The MVP will use:
React + TypeScript + Vite | v FastAPI API | +---------+----------+ | | v vPostgreSQL OpenAI API | v pgvectorSupporting tools:SQLAlchemyAlembicPydanticHTTPXPytestVitestDockerDocker ComposeGitHub 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.