Portfolio laboratory

Python + SQL projects

End-to-end analytical workflows that join, validate, model and communicate business data.

All datasets and commercial scenarios on this page are synthetic and created for portfolio demonstration.

Choose a case.

Each case includes the business question, source structure, working method, selected code or decision logic, measured result, recommendation and limitations.

Synthetic case studyPython

Executive sales KPI pipeline

Can leadership trust the number before discussing the trend?

A small analytical pipeline that removes duplicate orders, standardises channels, filters business-valid revenue and exposes an auditable customer-level mart.

47
duplicates removed
3,171
valid completed orders
£346k
governed revenue
View full case study
Synthetic case studySQL

Customer 360 & retention queue

Which valuable customers are becoming quiet?

A relational customer mart and RFM-style prioritisation layer that connects value, frequency and recency to an actionable outreach queue.

134
high-value at-risk customers
40.9%
revenue from top 20%
5
joined source fields
View full case study
Case 01
Synthetic case study600 customers · 3,547 raw ordersPython · SQLite · pandas · Data quality

Executive sales KPI pipeline

Can leadership trust the number before discussing the trend?

A small analytical pipeline that removes duplicate orders, standardises channels, filters business-valid revenue and exposes an auditable customer-level mart.

47
duplicates removed
3,171
valid completed orders
£346k
governed revenue

Business context

Leadership receives different revenue totals from sales and finance. This project demonstrates the governance layer that should precede any executive dashboard.

Questions tested

  1. Which records fail uniqueness and completeness rules?
  2. What is the agreed definition of reportable revenue?
  3. Can a monthly KPI table be reproduced from the same transformation every time?

Working method

From raw information to a decision.

  1. 01

    Profiled duplicates, missing channel values and status distribution before changing data.

  2. 02

    Removed 47 duplicate order IDs, standardised blank channels and retained completed orders only.

  3. 03

    Loaded controlled tables to SQLite and calculated monthly KPIs from one query.

  4. 04

    Kept the data-quality counts beside the commercial output for auditability.

Dataset

Visible grain, fields and sample.

order_idExpected unique order key
customer_idJoin key to customer master
order_value_gbpOrder value before validity filter
statusCompleted, returned or cancelled
channelWeb, marketplace, assisted or unknown
OrderCustomerDateValueStatusChannel
O-00001C-030931 Jan 2025£42.59CompletedWeb
O-00002C-048611 Dec 2025£88.16CompletedWeb
O-00003C-052008 Mar 2026£90.94CompletedSales-assisted

Preview shows three records; the complete synthetic dataset is available above.

Selected workingPython + SQL
orders = raw_orders.drop_duplicates("order_id").copy()
orders["channel"] = orders["channel"].fillna("Unknown")
orders = orders.loc[orders["status"].eq("Completed")]

monthly_kpis = pd.read_sql_query("""
  SELECT substr(order_date, 1, 7) AS month,
         COUNT(*) AS completed_orders,
         ROUND(SUM(order_value_gbp), 2) AS revenue_gbp,
         ROUND(AVG(order_value_gbp), 2) AS aov_gbp
  FROM orders
  GROUP BY 1 ORDER BY 1
""", database)

Result

  • The raw extract contained 3,547 rows and 47 duplicate order IDs.
  • After deduplication and status filtering, 3,171 completed orders remained.
  • The governed dataset reconciled to £345,737 of completed revenue.

Recommendation

Publish KPI definitions with the pipeline, assign an owner to each exception rule, and block dashboard refresh when uniqueness or reconciliation checks fail.

Limits & next evidence

This local example does not model incremental loads, slowly changing dimensions, refunds posted in later periods or role-based access. Those are required for production governance.

Case 02
Synthetic case study600 customers · relational order historySQL · Python · RFM · Segmentation

Customer 360 & retention queue

Which valuable customers are becoming quiet?

A relational customer mart and RFM-style prioritisation layer that connects value, frequency and recency to an actionable outreach queue.

134
high-value at-risk customers
40.9%
revenue from top 20%
5
joined source fields

Business context

Account teams need to distinguish a naturally infrequent buyer from a valuable customer whose engagement has changed. The case builds a relational customer view and a ranked re-engagement queue.

Questions tested

  1. How much value, frequency and recency belongs to each customer?
  2. How concentrated is revenue among the top-value cohort?
  3. Which previously active customers have now been quiet for more than 120 days?

Working method

From raw information to a decision.

  1. 01

    Joined the 600-row customer master to cleaned completed orders in SQLite.

  2. 02

    Calculated lifetime value, order frequency, AOV and last-order date at customer grain.

  3. 03

    Created quartile value bands and a transparent recency/frequency rule.

  4. 04

    Ranked qualified customers by relative value and recency for account-team action.

Dataset

Visible grain, fields and sample.

customer_idStable relational key
segmentConsumer, small business or enterprise
marketing_sourceRecorded acquisition origin
order_frequencyCount of completed orders
lifetime_value_gbpCompleted-order revenue
recency_daysDays since last order at snapshot
CustomerSigned upRegionSegmentSource
C-000116 Oct 2024LondonSmall BusinessSocial
C-000230 Sep 2024MidlandsConsumerReferral
C-000323 May 2024NorthConsumerReferral

Preview shows three records; the complete synthetic dataset is available above.

Selected workingSQL + Python
SELECT c.customer_id, c.region, c.segment, c.marketing_source,
       COUNT(o.order_id) AS order_frequency,
       ROUND(COALESCE(SUM(o.order_value_gbp), 0), 2) AS lifetime_value_gbp,
       MAX(o.order_date) AS last_order_date
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_id, c.region, c.segment, c.marketing_source;

retention_queue = customer_360[
    (customer_360.order_frequency >= 4)
    & (customer_360.recency_days > 120)
]

Result

  • The top 20% of customers contributed 40.9% of completed revenue.
  • The rule identified 134 high-value at-risk customers for evidence-based re-engagement.
  • Consumer was the highest-revenue segment because it dominated the synthetic customer mix.

Recommendation

Give account teams the ranked queue with last purchase, segment and value context; record contact outcome so future prioritisation can learn from actual reactivation.

Limits & next evidence

The sample has no cost-to-serve, consent, contact history or margin. Production prioritisation should optimise incremental value and comply with marketing permissions.