Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.formatting.provider": "none"
}
22 changes: 21 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,29 @@ Contributing guidelines
=======================

### Git workflow

* Use git-flow - create a feature branch from `develop`, e.g. `feature/new-feature`
* Pull requests must contain a succinct, clear summary of what the user need is driving this feature change
* Ensure your branch contains logical atomic commits before sending a pull request
* You may rebase your branch after feedback if it's to include relevant updates from the develop branch. It is preferable to rebase here then a merge commit as a clean and straight history on develop with discrete merge commits for features is preferred
* To find out more about contributing click [here](https://contributing.md/)

### Commit messages
Please use the following format for commit messages:

```
* fix: for a bug fix
* feat: for a new feature
* docs: for documentation changes
* style: for changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
* refactor: for refactoring production code
* test: for adding tests
* chore: for updating build tasks, package manager configs, etc
* build: for changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
```

### Pull requests
* Pull requests should be made to the `develop` branch
* Pull requests should be made from a feature branch, not `develop`
* Pull requests should be made with a succinct, clear summary of what the user need is driving this feature change
* An automated template will be provided to help with this process, please fill it out as best you can

145 changes: 35 additions & 110 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,127 +1,52 @@
# tdse-accessForce-bids-api
Bids API training project with Python and MongoDB
# API Documentation

# Bid Library
This API provides a simple "Hello World" message.

## Contents
## Prerequisites

- [Background](#background)
- [Before working on a bid](#before-working-on-a-bid)
- [Bid phases](#phases)
- [Brief](#brief)
- [Acceptance Criteria](#acceptance-criteria)
- [Iterations](#iterations)
- Python 3.x
- Flask

## Background
## Running the API

Methods being a consultancy agency to win new work we make to make bids on client tenders.
1. Clone the repository to your local machine:

- A tender is a piece of work that an organisation (potential client) needs an external team to work on or to supplement an existing team
- A bid can comprise of several stages to win a tender, usually there are two phases which comprise of phase 1 and phase 2
```bash
git clone
```
2. Navigate to the root directory of the project:

### Before working on a bid
```bash
cd tdse-accessForce-bids-api
```
3. Install python 3.x if not already installed. You can check if it is installed by running the following command:

Before phase 1, there is time for bidders like Methods to ask questions of the client tender. These questions are open to all those looking to bid on the tender and all questions and answers are available to all bidders on a single tender.
```bash
python3 --version
```
4. Create the virtual environment by running the following command:

This step is a necessary is really important for Methods to understand whether we really want to bid on a particular tender. Some considerations before bidding:
```
python3 -m venv .venv
```
5. Run the virtual environment by running the following command:

- Do we like the project and hence want it?
- Is it good value?
- Will this get us known with a new client and give Methods leverage in future work?
- Is the tender something different that would expand our portfolio?
- Can we do it?
```bash
source .venv/bin/activate
```
6. Install the required dependencies by running the following command:

### Phases
```bash
pip install -r requirements.txt
```
7. Run the following command to start the API:

**Phase 1** compromises of a list of questions set by the tender; in government the scoring system for each question is out of 3, so if there are 6 questions a bidder can achieve a maximum score of 18. The answers to the questions usually have a word limit of 100 or 200 (in this phase).
```bash
python app/app.py
```
8. The API will be available at http://localhost:3000/api/bids

The client that puts out a tender decides the pass rate in phase 1 to progress to phase 2. The pass rate may not be known until results of all bids are completed for each of the bidders. The list below are common pass criteria you might come across on a bid (remember this can vary from client to client and even within multiple tenders by the same client):

- All questions must have a score greater than 1, (so 2 minimum)
- A minimum overall score, e.g. 14 out of 18
- The 3 highest bids assuming the bids met criteria 1 or/and 2

**Phase 2** is a lot more involved than phase 1, it can comprise of a face to face (or virtual) presentation alongside answers to questions limited to x number of words (limit set by client). These questions will cover team culture and technical solution.

There are usually 3 categories that Methods are scored on and these are weighted by the client. The categories are:

- Technical - questions from presentation or form and results from phase 1
- Culture - questions in phase 2
- Cost (a.k.a. Rate)

The overall score is worked out as a percentage, (out of 100). Whichever bidder scores highest wins the bid and that is the end of the tendering process.

Next steps: Statement of Work (SoW, the contract) is put together by the client, handing over CVs of potential staff Methods are going to supply and agreement on project start dates.

--------------

## Brief

Currently Methods store all the information for tenders and bids in Sharepoint, the way the documents are stored and the information available can vary quite a lot making it hard for the bid team to find good answers to questions and successful bids for reuse. We currently do not store who helped answering questions against a bid and in some cases where Methods have done so it only informs us of their initials.

What intend to build is an API that can store tender/bid information in a structured way to facilitate finding successful bids and high scoring questions.

### Acceptance Criteria

**Must** have:

1. Ability to access all bid data, see list of data below:

- tender title
- tender short description (problem statement)
- client
- date of the tender
- were Methods successful
- what phase did we get to
- how well we did in each of the phases
- any technologies or skills reuired by client tender
- tender questions and Methods answers and the respective scores
- who helped answer a question
- provide links to further information on bids stored in sharepoint
- when was the data last updated
- any skills or technologies listed in the answers to questions

1. Ability to find any bid
1. Ability to add new bids
1. Ability to update a bid that is still in progress
1. Ability to delete a bid and associated
1. Ability to recover deleted bid data within 4 weeks of deletion
1. Ability to filter bids and questions based on success and score
1. Ability to sort bids and questions alphanumerically and page through the results
1. Ability to secure access to changing the data to certain users

**Should** have:

1. Ability to search for bids containing particular text
1. Ability to search for questions containing particular text

**Could** have:

1. Ability to control different user access (permissions) based on roles

- Admin
- Bid writers
- Bid viewers

1. Ability to access this software anywhere in the UK

**Would not** have:

1. Due to size of some answers and the content not being soley text but images, diagrams etc. Methods does not wish to duplicate this information from Sharepoint into a filesystem like AWS S3 or Azure Storage

### Iterations

**Iteration 1** Build API and initial storage system to find, add, update and remove. Steps 1 to 8 from Must section

**Iteration 2** Secure the API to users who need access, based on the "Principle least priviledge principle. Step 9 from Must section

**Iteration 3** Build search engine to allow for a more sophisticated way of finding questions and bids related to your needs. Steps 1 and 2 from Should section

**Iteration 4** Expand on access control to bid library based on roles, users and teams where necessary. Step 1 of Could section

**Iteration 5** Host the bid library to be accessed by users across the country. Step 2 of Could section

**Iteration 6** Build a web app to integrate with the bids API, create user journeys that allow users to find, add and update bid content

--------------

Expand Down
Empty file added api/controllers/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions api/controllers/bid_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

from flask import Blueprint
from api.models.bid_models import get_test, create_bid

bid = Blueprint('bid', __name__)

@bid.route("/bids", methods=["GET"])
def get_tested():
response = get_test()
return response

@bid.route("/bids", methods=["POST"])
def post_bid():
response = create_bid()
return response

Empty file added api/models/__init__.py
Empty file.
40 changes: 40 additions & 0 deletions api/models/bid_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from flask import request, jsonify
from api.schemas.bid_schema import BidSchema, Status

def get_test():
return 'test', 200

def create_bid():
mandatory_fields = ['tender', 'client', 'bid_date']
if not request.is_json:
return jsonify({'error': 'Invalid JSON'}), 400

for field in mandatory_fields:
if field not in request.json:
return jsonify({'error': f'Missing mandatory field: {field}'}), 400

# BidSchema object
bid_schema = BidSchema(
tender=request.json['tender'],
client=request.json['client'],
alias=request.json.get('alias', ''),
bid_date=request.json['bid_date'],
bid_folder_url=request.json.get('bid_folder_url', ''),
feedback_description=request.json.get('feedback_description', ''),
feedback_url=request.json.get('feedback_url', '')
)
# Append phase information to the success list
bid_schema.addSuccessPhase(phase=2, has_score=True, score=80, out_of=100)
# Set failed phase info
# bid_schema.setFailedPhase(phase=3, has_score=True, score=50, out_of=100)
# Change status
# bid_schema.setStatus('deleted')
# Convert the mock BidSchema object to a dictionary
bid_json = bid_schema.toDbCollection()

# Save data in memory
f=open('./db.txt','a')
f.write(str(bid_json))
f.close

return bid_json, 201
Empty file added api/schemas/__init__.py
Empty file.
45 changes: 45 additions & 0 deletions api/schemas/bid_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from uuid import uuid4
from datetime import datetime
from .phase_schema import PhaseInfo
from .status_schema import Status

# Description: Schema for the bid object
class BidSchema:
def __init__(self, tender, client, bid_date, alias='', bid_folder_url='', status='in_progress', was_successful=True,success=[], failed={}, feedback_description=None, feedback_url=None):
self.id = uuid4()
self.tender = tender
self.client = client
self.alias = alias
self.bid_date = datetime.strptime(bid_date, '%d-%m-%Y').isoformat() # DD-MM-YYYY
self.bid_folder_url = bid_folder_url
self.status = status # enum: "deleted", "in_progress" or "completed"
self.links = {
'self': f"/bids/{self.id}",
'questions': f"/bids/{self.id}/questions"
}
self.was_successful = was_successful
self.success = success
self.failed = failed
self.feedback = {"description": feedback_description,
"url": feedback_url}
self.last_updated = datetime.now().isoformat()

def addSuccessPhase(self, phase, has_score, score=None, out_of=None):
phase_info = PhaseInfo(phase=phase, has_score=has_score, score=score, out_of=out_of)
self.success.append(phase_info)

def setFailedPhase(self, phase, has_score, score=None, out_of=None):
self.was_successful = False
self.failed = PhaseInfo(phase=phase, has_score=has_score, score=score, out_of=out_of)

def setStatus(self, status):
if hasattr(Status, status):
self.status = status
else:
raise ValueError("Invalid status. Please provide a valid Status enum value")

def toDbCollection(self):
self.success = [s.__dict__ for s in self.success] if self.success else []
self.failed = self.failed.__dict__ if self.failed else {}
return self.__dict__

7 changes: 7 additions & 0 deletions api/schemas/phase_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Schema for phaseInfo object
class PhaseInfo:
def __init__(self, phase, has_score, score=None, out_of=None):
self.phase = phase
self.has_score = has_score
self.score = score
self.out_of = out_of
7 changes: 7 additions & 0 deletions api/schemas/status_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from enum import Enum

# Enum for status
class Status(Enum):
deleted = 1
in_progress = 2
completed = 3
11 changes: 11 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from flask import Flask

from api.controllers.bid_controller import bid

app = Flask(__name__)

app.register_blueprint(bid, url_prefix='/api')


if __name__ == '__main__':
app.run(debug=True, port=3000)
1 change: 1 addition & 0 deletions db.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{'id': UUID('471fea1f-705c-4851-9a5b-df7bc2651428'), 'tender': 'test_tender', 'client': 'test_client', 'alias': 'test_alias', 'bid_date': '2023-06-21T00:00:00', 'bid_folder_url': 'test_folder_url', 'status': 'in_progress', 'links': {'self': '/bids/471fea1f-705c-4851-9a5b-df7bc2651428', 'questions': '/bids/471fea1f-705c-4851-9a5b-df7bc2651428/questions'}, 'was_successful': True, 'success': [{'phase': 2, 'has_score': True, 'score': 80, 'out_of': 100}], 'failed': {}, 'feedback': {'description': 'test_description', 'url': 'test_url'}, 'last_updated': '2023-06-23T11:16:59.404946'}{'id': UUID('0b695d23-5d4f-4cd7-b3a9-bb6497abd2f8'), 'tender': 'test_tender', 'client': 'test_client', 'alias': 'test_alias', 'bid_date': '2023-06-21T00:00:00', 'bid_folder_url': 'test_folder_url', 'status': 'in_progress', 'links': {'self': '/bids/0b695d23-5d4f-4cd7-b3a9-bb6497abd2f8', 'questions': '/bids/0b695d23-5d4f-4cd7-b3a9-bb6497abd2f8/questions'}, 'was_successful': True, 'success': [{'phase': 2, 'has_score': True, 'score': 80, 'out_of': 100}, {'phase': 2, 'has_score': True, 'score': 80, 'out_of': 100}], 'failed': {}, 'feedback': {'description': 'test_description', 'url': 'test_url'}, 'last_updated': '2023-06-23T11:17:23.593171'}
Empty file added helpers/helpers.py
Empty file.
9 changes: 9 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
blinker==1.6.2
click==8.1.3
Flask==2.3.2
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
Werkzeug==2.3.6
pip==23.1.2
pytest==7.3.2
12 changes: 12 additions & 0 deletions test_request.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
POST http://localhost:3000/api/bids HTTP/1.1
Content-Type: application/json

{
"tender": "test_tender",
"client": "test_client",
"alias": "test_alias",
"bid_date": "21-06-2023",
"bid_folder_url": "test_folder_url",
"feedback_description": "test_description",
"feedback_url": "test_url"
}
Empty file added tests/__init__.py
Empty file.
Loading