This project is a fully-modular data pipeline that processes NYC Taxi trip data using Python and PostgreSQL.
It follows the Medallion Architecture (Bronze → Silver → Gold) and is built with real-world engineering principles in mind: incremental loading, metadata tracking, fault tolerance, testing, and CI/CD automation.
Breaking the pipeline into Bronze, Silver, and Gold layers helps keep raw data separate from cleaned and aggregated data.
Each layer has a clear purpose:
- Bronze → raw, untouched data
- Silver → cleaned, validated, feature-engineered data
- Gold → final business summaries
This makes debugging easier, transformations more transparent, and analytics more reliable.
the code are split into 4 parts
tasks.pycontains the ETL stepsqueries.pystores all SQLutils.pyhandles helpers and retry logicmain.pyruns the pipeline
The pipeline keeps track of the last processed month.
This ensures:
- Only new data is loaded
- Failed runs can resume safely
- The pipeline is idempotent (running it twice won’t duplicate data)
A GitHub Actions workflow runs tests and linting automatically to keep the project stable and production-ready.
The raw Parquet data is loaded into PostgreSQL with minimal changes.
The Bronze layer acts as the ground truth of the pipeline: everything else depends on it.
The Silver layer standardizes and enriches the data:
- duplicates removed
- invalid rows handled
- timestamps normalized
- new columns added (e.g., trip duration, speed)
The Gold layer contains aggregated results such as:
- daily revenue
- vendor performance
- monthly trends
- payment behavior
- pickup zones
These tables are designed for dashboards and BI tools and this an example built with the result in PowerBi.

The orchestrator in main.py follows a simple flow:
-
Check metadata
Read the last successful load month. -
Figure out the next month to process
If January was completed, the system automatically moves to February. -
Extract
Load the raw Parquet file for that month into the Bronze layer. -
Transform
Clean the data and apply business rules before inserting into Silver. -
Load
Update the Gold layer using upsert method.
Real pipelines fail — network timeouts, database locks, temporary connection issues.
To handle this, the project includes a retry decorator in utils.py.
It allows any database operation to automatically retry with a delay.
You can configure:
- number of retries
- wait time
- error logging
- backoff strategy
If something temporary goes wrong, the pipeline doesn’t crash, it simply retries and keeps moving.
Metadata is stored in a dedicated table that logs:
- which month was processed
- execution time
- success/failure status
- any error messages
This enables:
- incremental loading
- safe restarts
- full auditability
- monitoring of pipeline health
Metadata is the key reason incremental loads work reliably.
Here’s how the two modes differ:
Used mainly during first-time setup or historical backfills.
It processes all available data from scratch.
The normal mode.
Processes only the next unprocessed month based on metadata.
Incremental loads make the pipeline fast and efficient.
uses
- flake8 for linting
- unit tests
- dependency installation
- general project validation
And without this been passed, new updates will not able to effect in the already working github code.
pythonnyc/ ├── .github/workflows/ci.yml # CI pipeline ├── .flake8 # Linter config ├── config.py # DB settings & constants ├── main.py # Pipeline entry point ├── tasks.py # ETL logic ├── queries.py # SQL statements ├── utils.py # Retry logic and helpers ├── test_etl_pipeline.py # Tests └── requirements.txt # Dependencies
Here are some examples of insights you can pull once the pipeline runs.
SELECT trip_date, total_revenue
FROM gold.daily_summary
ORDER BY trip_date;