MIL-005 Documentation and wiki #27

Merged
Tirsvad merged 1 commits from mil-005-documentation-and-wiki into main 2026-10-24 18:01:27 +02:00
10 changed files with 412 additions and 1 deletions
+155 -1
View File
@@ -1,2 +1,156 @@
# 040-personal-flight-club
# 🚀 Personal Flight Club
A flight club (Day 40 of Udemy's "100 Days of Code: The Complete Python Pro Bootcamp"): it reads destinations and price limits from a Google Sheet, finds the cheapest return flight (direct first, then with stopovers), and emails every customer in a second sheet when a price is below the limit. It is for course participants who want to compare solutions and for anyone who browses the repository for ideas.
## 📚 Table of Contents
- [Overview](#-overview)
- [Requirements](#-requirements)
- [Setup](#-setup)
- [Run](#-run)
- [Tests](#-tests)
- [License](#-license)
- [Links](#-links)
- [Appendix: Project documents](#-appendix-project-documents)
## 🧭 Overview
The program keeps the assignment's class and function names. It builds on the Day 39 project and adds three things: a search for flights with stopovers when no direct flight exists, a customer list from a second sheet, and a deal email to every customer.
| Module (`src/flight_club/`) | What it does |
| --- | --- |
| `data_manager.py` | `DataManager`: reads the `prices` and `users` sheets from Sheety (`get_destination_data()`, `get_customer_emails()`) and writes a new lowest price back; reads all its settings in `__init__()` |
| `flight_search.py` | `FlightSearch.check_flights(..., is_direct=True)`: asks SerpAPI Google Flights, or the course's mock flight service, for nonstop flights, or for flights with up to two stops |
| `flight_data.py` | `FlightData` (with `stops`) and `find_cheapest_flight()`: picks the cheapest flight, with `N/A` when there is none |
| `notification_manager.py` | `NotificationManager`: builds the direct or stopover message, sends it by email with `send_emails()` and by WhatsApp |
| `main.py` | Wires everything together; `python -m flight_club` runs it |
| `constants.py` | All constants (URLs, origin airport, currency, SMTP port, variable names) |
| `cache.py` | Local response cache that never stores the Sheety answers |
![A sample run with mock data](docs/images/sample-run.png)
### Glossary
| Term | Meaning |
| --- | --- |
| API | Application programming interface: how one program asks another for data |
| CI | Continuous integration: the checks that run on every pull request |
| GBP | Pound sterling; the currency code used in all messages instead of the pound sign |
| IATA code | The three-letter airport code of the International Air Transport Association, such as `LHR` |
| SMTP | Simple Mail Transfer Protocol: how the program hands an email to your mail provider |
| Stopover | An intermediate stop of a flight; a direct flight has none |
## 📋 Requirements
- Python 3.13 or newer.
- Runtime packages (installed by pip): `requests`, `requests-cache`, `python-dotenv`. Email uses the standard library (`smtplib`).
- Optional, only to send WhatsApp messages: `pywhatkit`, a browser and a logged-in WhatsApp Web session.
- A Sheety project for your copy of the Google Sheet with a `prices` sheet and a `users` sheet (Bearer authentication, PUT enabled on `prices`), and either a SerpAPI key or the course's mock flight service credentials.
- An email account with an app password (not your normal password) to send the deal emails.
## 🛠️ Setup
Create and use a local virtual environment, then install the project.
PowerShell (Windows):
```powershell
py -3.13 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
Bash (Linux, macOS):
```bash
python3.13 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
For WhatsApp delivery add the optional extra: `python -m pip install -e ".[whatsapp]"`.
Create your own `.env` from the template and fill in your values. `.env` is ignored by git; never commit it.
```bash
cp .env.example .env
```
| Variable | Meaning |
| --- | --- |
| `SHEETY_PRICES_ENDPOINT` | Endpoint of the `prices` sheet in your Sheety project (the Day 39 name `SHEETY_ENDPOINT` still works) |
| `SHEETY_USERS_ENDPOINT` | Endpoint of the `users` sheet that your Google Form fills (columns `firstName`, `lastName`, `email`) |
| `SHEETY_TOKEN` | Bearer token of the Sheety project |
| `FLIGHT_PROVIDER` | `serpapi` or `mock` |
| `SERPAPI_API_KEY` | Your SerpAPI key (provider `serpapi`) |
| `MOCK_FLIGHTS_APP_ID`, `MOCK_FLIGHTS_APP_KEY` | Credentials of the mock flight service (provider `mock`) |
| `MOCK_FLIGHTS_URL` | Optional: override the mock service URL |
| `SMTP_ADDRESS` | Your mail provider's SMTP server, for example `smtp.gmail.com` (port 587, STARTTLS) |
| `EMAIL_ADDRESS`, `EMAIL_PASSWORD` | The sender address and its app password |
| `WHATSAPP_NUMBER` | Your own number with country code, for example `+441234567890` |
Keep 3 to 5 destinations in the sheet to stay inside the free tiers (SerpAPI 100 searches a month, Sheety 200 requests a month). Flight answers are cached for one hour in `flight_cache.sqlite`; delete the file to force fresh searches. Try the program with a test `users` sheet that holds only your own address before you use the real customer list.
## ▶️ Run
```bash
python -m flight_club
```
or, after the editable install, `flight-club`. For every sheet row the program prints "Getting direct flights for ...". When there is no direct flight it prints "No direct flight to ... Looking for indirect flights..." and searches again with up to two stops. When the cheapest price is lower than the sheet's lowest price, it updates the sheet, sends a WhatsApp message and emails every customer in the `users` sheet. The message says "Flight has 1 stopover." for a flight with stops. Without the SMTP settings, `WHATSAPP_NUMBER` or `pywhatkit` it prints the message instead.
## 🧪 Tests
The tests use no network, send no email or WhatsApp message and never read your `.env`.
```bash
python -m pytest
```
![Test run](docs/images/tests-run.png)
Optional checks used by the CI: `python -m ruff check .`, `python -m ruff format --check .` and `python -m mypy --strict src tests`. Build the API documentation with `doxygen Doxyfile` (output in `docs/doxygen/`).
## 📄 License
See [LICENSE][license].
## 🔗 Links
- [Repository][repository]
- [Documentation][documentation]
- [Issue tracker][issues]
## 📎 Appendix: Project documents
The documents a newcomer needs, in reading order.
| Document | What it tells you |
| --- | --- |
| [AGENTS.md][agents] | The rules AI agents follow in this repository |
| [Business Case][BC-001] | Why the project exists, its scope and success criteria |
| [Stakeholder Analysis][SA-001] | Who is involved; the stakeholder IDs used for owners and reviewers |
| [Project Plan][PP-001] | The gateways and their schedule |
| [Milestones][milestones] | One document per gateway: Go/No-Go criteria and tasks |
| [Architecture decision][ADR-0001] | Why the code is one flat package that prints to the console |
| [Artifact registry][registry] | Where each artifact type lives, the next version, the PO language and domain |
| [Framework README][framework-readme] | The framework this project uses |
| [Framework changelog][framework-changelog] | What changed in each framework release |
---
[license]: ./LICENSE
[repository]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/040-personal-flight-club
[documentation]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/040-personal-flight-club/wiki
[issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/040-personal-flight-club/issues
[agents]: ./AGENTS.md
[BC-001]: ./docs/business-case.md
[SA-001]: ./docs/stakeholder-analysis.md
[PP-001]: ./docs/project-plan.md
[milestones]: ./docs/milestones/
[ADR-0001]: ./docs/adr/adr-0001-flat-module-layout-and-console-output.md
[registry]: ./docs/artifact-registry.md
[framework-readme]: ./framework/README.md
[framework-changelog]: ./framework/CHANGELOG.md
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

+63
View File
@@ -0,0 +1,63 @@
[English](Getting-Started) · **Dansk** · [Bahasa Malaysia](Getting-Started-ms)
# Kom i gang
## Krav
- Python 3.13 eller nyere
- Et Sheety-projekt til din kopi af Google Sheet med arkene `prices` og `users` (Bearer-godkendelse, PUT slået til på `prices`)
- En SerpAPI-nøgle eller adgangsoplysninger til mock-flyservicen
- En e-mailkonto med en app-adgangskode til at sende tilbudsmails
- Valgfrit: `pywhatkit`, en browser og en logget ind WhatsApp Web-session
## Opsætning
Opret et lokalt virtuelt miljø, opgradér pip og installér projektet:
```powershell
py -3.13 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
```bash
python3.13 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
## Konfiguration
Kopiér `.env.example` til `.env` og udfyld værdierne. Læg aldrig `.env` i git. Hold dig til 3 til 5 destinationer i arket for at holde dig inden for de gratis grænser, og prøv programmet med et test-ark `users`, der kun indeholder din egen adresse.
| Variabel | Betydning |
| --- | --- |
| `SHEETY_PRICES_ENDPOINT` | Endpoint for arket `prices` |
| `SHEETY_USERS_ENDPOINT` | Endpoint for arket `users` (`firstName`, `lastName`, `email`) |
| `SHEETY_TOKEN` | Bearer-token til Sheety-projektet |
| `FLIGHT_PROVIDER` | `serpapi` eller `mock` |
| `SERPAPI_API_KEY` | SerpAPI-nøgle |
| `MOCK_FLIGHTS_APP_ID`, `MOCK_FLIGHTS_APP_KEY` | Adgangsoplysninger til mock-flyservicen |
| `SMTP_ADDRESS` | Din mailudbyders SMTP-server (port 587) |
| `EMAIL_ADDRESS`, `EMAIL_PASSWORD` | Afsenderadresse og dens app-adgangskode |
| `WHATSAPP_NUMBER` | Dit eget nummer med landekode |
## Kørsel
Start programmet. Uden SMTP-indstillinger eller WhatsApp-nummer bliver beskeden skrevet i konsollen i stedet.
```bash
python -m flight_club
```
## Test
Testene bruger intet netværk, sender ingen e-mails og læser aldrig din `.env`.
```bash
python -m pytest
```
![Testkørsel](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/040-personal-flight-club/raw/branch/main/docs/images/tests-run.png)
+63
View File
@@ -0,0 +1,63 @@
[English](Getting-Started) · [Dansk](Getting-Started-da) · **Bahasa Malaysia**
# Bermula
## Keperluan
- Python 3.13 atau lebih baharu
- Projek Sheety untuk salinan Google Sheet anda dengan helaian `prices` dan `users` (pengesahan Bearer, PUT dihidupkan pada `prices`)
- Kunci SerpAPI atau kelayakan perkhidmatan penerbangan mock
- Akaun e-mel dengan kata laluan aplikasi untuk menghantar e-mel tawaran
- Pilihan: `pywhatkit`, pelayar dan sesi WhatsApp Web yang telah log masuk
## Pemasangan
Cipta persekitaran maya setempat, naik taraf pip dan pasang projek:
```powershell
py -3.13 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
```bash
python3.13 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
## Konfigurasi
Salin `.env.example` ke `.env` dan isi nilainya. Jangan sekali-kali commit `.env`. Kekalkan 3 hingga 5 destinasi dalam helaian supaya kekal dalam had percuma, dan cuba program dengan helaian `users` ujian yang hanya mengandungi alamat anda sendiri.
| Pemboleh ubah | Maksud |
| --- | --- |
| `SHEETY_PRICES_ENDPOINT` | Endpoint helaian `prices` |
| `SHEETY_USERS_ENDPOINT` | Endpoint helaian `users` (`firstName`, `lastName`, `email`) |
| `SHEETY_TOKEN` | Token Bearer projek Sheety |
| `FLIGHT_PROVIDER` | `serpapi` atau `mock` |
| `SERPAPI_API_KEY` | Kunci SerpAPI |
| `MOCK_FLIGHTS_APP_ID`, `MOCK_FLIGHTS_APP_KEY` | Kelayakan perkhidmatan penerbangan mock |
| `SMTP_ADDRESS` | Pelayan SMTP pembekal e-mel anda (port 587) |
| `EMAIL_ADDRESS`, `EMAIL_PASSWORD` | Alamat penghantar dan kata laluan aplikasinya |
| `WHATSAPP_NUMBER` | Nombor anda dengan kod negara |
## Jalankan
Mulakan program. Tanpa tetapan SMTP atau nombor WhatsApp, mesej dicetak sebagai ganti.
```bash
python -m flight_club
```
## Ujian
Ujian tidak menggunakan rangkaian, tidak menghantar e-mel dan tidak membaca `.env` anda.
```bash
python -m pytest
```
![Larian ujian](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/040-personal-flight-club/raw/branch/main/docs/images/tests-run.png)
+63
View File
@@ -0,0 +1,63 @@
**English** · [Dansk](Getting-Started-da) · [Bahasa Malaysia](Getting-Started-ms)
# Getting started
## Requirements
- Python 3.13 or newer
- A Sheety project for your copy of the Google Sheet, with a `prices` sheet and a `users` sheet (Bearer authentication, PUT enabled on `prices`)
- A SerpAPI key or the mock flight service credentials
- An email account with an app password, to send the deal emails
- Optional: `pywhatkit`, a browser and a logged-in WhatsApp Web session
## Setup
Create a local virtual environment, upgrade pip and install the project:
```powershell
py -3.13 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
```bash
python3.13 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
## Configuration
Copy `.env.example` to `.env` and fill in the values. Never commit `.env`. Keep 3 to 5 destinations in the sheet to stay inside the free tiers, and try the program with a test `users` sheet that holds only your own address.
| Variable | Meaning |
| --- | --- |
| `SHEETY_PRICES_ENDPOINT` | Endpoint of the `prices` sheet |
| `SHEETY_USERS_ENDPOINT` | Endpoint of the `users` sheet (`firstName`, `lastName`, `email`) |
| `SHEETY_TOKEN` | Bearer token of the Sheety project |
| `FLIGHT_PROVIDER` | `serpapi` or `mock` |
| `SERPAPI_API_KEY` | SerpAPI key |
| `MOCK_FLIGHTS_APP_ID`, `MOCK_FLIGHTS_APP_KEY` | Mock flight service credentials |
| `SMTP_ADDRESS` | SMTP server of your mail provider (port 587) |
| `EMAIL_ADDRESS`, `EMAIL_PASSWORD` | Sender address and its app password |
| `WHATSAPP_NUMBER` | Your own number with country code |
## Run
Start the program. Without the SMTP settings or a WhatsApp number the message is printed instead.
```bash
python -m flight_club
```
## Tests
The tests use no network, send no email and never read your `.env`.
```bash
python -m pytest
```
![Test run](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/040-personal-flight-club/raw/branch/main/docs/images/tests-run.png)
+18
View File
@@ -0,0 +1,18 @@
[English](Home) · **Dansk** · [Bahasa Malaysia](Home-ms)
# Personal Flight Club
En flyklub fra dag 40 i Udemys 100 Days of Code. Programmet læser destinationer og prisgrænser fra et Google Sheet, finder den billigste returflyvning de næste seks måneder (først direkte, derefter med mellemlandinger) og sender en e-mail til alle kunder i et andet ark, når en pris er under grænsen.
## Sådan virker det
1. Læs rækkerne (by, IATA-kode, laveste pris) fra arket `prices` via Sheety.
2. Søg efter den billigste direkte returflyvning fra i morgen til seks måneder frem for hver række, med SerpAPI Google Flights eller kursets mock-flyservice.
3. Hvis der ikke er en direkte flyvning, søges der igen efter flyvninger med op til to stop, og antallet af stop noteres.
4. Hvis prisen er lavere end arkets laveste pris, opdateres arket, der sendes en WhatsApp-besked, og alle kunder i arket `users` får en e-mail. En flyvning med stop siger "Flight has 1 stopover." i beskeden.
## Et eksempel på en kørsel med mock-data
![Et eksempel på en kørsel med mock-data](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/040-personal-flight-club/raw/branch/main/docs/images/sample-run.png)
Næste: [Kom i gang](Getting-Started-da)
+18
View File
@@ -0,0 +1,18 @@
[English](Home) · [Dansk](Home-da) · **Bahasa Malaysia**
# Personal Flight Club
Kelab penerbangan dari hari ke-40 kursus Udemy 100 Days of Code. Program ini membaca destinasi dan had harga daripada Google Sheet, mencari penerbangan pergi-balik termurah untuk enam bulan akan datang (penerbangan terus dahulu, kemudian dengan persinggahan) dan menghantar e-mel kepada setiap pelanggan dalam helaian kedua apabila harga lebih rendah daripada had.
## Cara ia berfungsi
1. Baca baris (bandar, kod IATA, harga terendah) daripada helaian `prices` melalui Sheety.
2. Cari penerbangan pergi-balik terus yang termurah dari esok hingga enam bulan lagi untuk setiap baris, dengan SerpAPI Google Flights atau perkhidmatan penerbangan olok-olok (mock) kursus.
3. Jika tiada penerbangan terus, cari semula penerbangan dengan sehingga dua persinggahan dan catat bilangan persinggahan.
4. Jika harga lebih rendah daripada harga terendah dalam helaian, kemas kini helaian, hantar mesej WhatsApp dan e-mel kepada setiap pelanggan dalam helaian `users`. Penerbangan dengan persinggahan menyatakan "Flight has 1 stopover." dalam mesej.
## Contoh larian dengan data mock
![Contoh larian dengan data mock](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/040-personal-flight-club/raw/branch/main/docs/images/sample-run.png)
Seterusnya: [Bermula](Getting-Started-ms)
+18
View File
@@ -0,0 +1,18 @@
**English** · [Dansk](Home-da) · [Bahasa Malaysia](Home-ms)
# Personal Flight Club
A flight club from Day 40 of Udemy's 100 Days of Code. It reads destinations and price limits from a Google Sheet, finds the cheapest return flight for the next six months (direct first, then with stopovers) and emails every customer in a second sheet when a price is below the limit.
## How it works
1. Read the rows (city, IATA code, lowest price) from the `prices` sheet through Sheety.
2. Search the cheapest direct return flight from tomorrow to six months ahead for every row, with SerpAPI Google Flights or the course's mock flight service.
3. If there is no direct flight, search again for flights with up to two stops and note the number of stops.
4. If the price is lower than the sheet's lowest price, update the sheet, send a WhatsApp message and email every customer in the `users` sheet. A flight with stops says "Flight has 1 stopover." in the message.
## A sample run with mock data
![A sample run with mock data](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/040-personal-flight-club/raw/branch/main/docs/images/sample-run.png)
Next: [Getting started](Getting-Started)
+14
View File
@@ -0,0 +1,14 @@
**English**
- [Home](Home)
- [Getting started](Getting-Started)
**Dansk**
- [Forside](Home-da)
- [Kom i gang](Getting-Started-da)
**Bahasa Malaysia**
- [Laman utama](Home-ms)
- [Bermula](Getting-Started-ms)