Introduction to Software Engineering · Chapter 02

Lab-02: Types of Applications

Your team classifies five applications you use every day, decides which type of application ITC Club Hub should be for its three user groups, turns the important qualities into six measurable scenarios, draws a context diagram and a three-tier sketch, and then separates the C++ core from its command-line interface with an Event class and a list-events command. The challenge designs offline-first check-in for a venue with poor Wi-Fi.

ISO/IEC 25010:2023 C++20 · CMake 3.28+ · Git · Mermaid ≈ 4.5 hours (team of 4 to 5) 5 tasks + 1 challenge
How to work through this lab. Do the tasks in order; each builds on the previous. Read the Goal, follow the Steps, compare with the Expected output, and tick the Acceptance checklist (saved in your browser). Open a hint only when stuck for more than 10 minutes. This is team work: split the rows, scenarios and files between members, and let each person commit their own part so that individual contributions are visible in git log.
0

Setup

15 min
  1. Tools. Git; CMake 3.28 or later; a C++20 compiler (GCC 13, Clang 17 or MSVC in Visual Studio 2022, or newer); an editor with Mermaid preview (VS Code with the extension Markdown Preview Mermaid Support, or the online editor at mermaid.live to export PNG); draw.io is optional for Task 4. Open your team repository from Lab-01 and create the branch for this lab:
    git --version
    cmake --version          # 3.28 or later
    g++ --version            # or: clang++ --version, or cl in a VS prompt
    cd itc-club-hub          # your team repository from Lab-01
    git switch main && git pull
    git switch -c lab-02
    mkdir -p lab02 include/clubhub src/cli data
  2. Inputs from earlier labs. This lab reuses what you wrote in Lab-01. If something is missing or weak, use the fallback in the table and move on; do not stop to repair Lab-01.
    InputFileWhat you need from itFallback if missing
    Problem statementlab01/team-charter.md (or your separate problem-statement file)Who has the problem, what it costs them today, what Club Hub must change"Club events at ITC are announced in scattered chat groups; students miss events, organisers cannot tell who will come or who attended, and the student affairs office cannot see club activity."
    Stakeholder listlab01/team-charter.mdUser groups and external parties, with their main interestStudents, club leaders, event organisers, administrators (student affairs office), the ITC IT office (student directory), the course instructor as client.
    Team roles and decision ruleslab01/team-charter.md, lab01/decisions.mdWho is Product Owner (leads Task 2), how the team decidesProduct Owner = the member listed first in the README; decisions by consensus, else majority; record them in lab01/decisions.md.
    Repository skeletonCMakeLists.txt, src/, include/clubhub/A place for the C++ code of Task 5Use the CMakeLists.txt given in Task 5; it builds from an empty repository.
  3. Case-study brief. Everything this lab needs to know about the product is in this box.
    Case-study brief: ITC Club Hub
    Purpose
    An application for student clubs at the Institute of Technology of Cambodia. Club leaders register a club, which an administrator approves. Approved clubs publish events (title, date and time, location, capacity, description). Students browse events, register, cancel up to 24 hours before the start and receive a reminder. Organisers check registered students in at the door and see attendance. Administrators see a monthly activity report across clubs.
    User groups
    Students: about 3,000, almost all on Android or iPhone, often on mobile data, use it a few times a week. Organisers (club leaders and helpers): one or two phones at the door of a room or hall where the Wi-Fi is weak or overloaded. Administrators (student affairs office): 2 or 3 staff on desktop PCs in an office, monthly reports and club approvals.
    External systems
    E-mail service: sends registration confirmations and reminders. ITC student directory (run by the IT office): confirms that a student ID exists and gives name and e-mail; Club Hub never stores passwords itself.
    Business rules
    Capacity cannot be exceeded (a waiting list is a Should feature) · a student cannot register twice for the same event · cancellation closes 24 hours before the start · events in the past cannot be registered for · only approved clubs can publish events.
    Key numbers
    About 40 clubs, 5 to 15 events a week, 20 to 200 participants per event. Peak at the door: 200 arrivals in 10 minutes at one entrance. Registrations open on Friday evenings, when traffic is highest. The semester has 15 weeks.
    Personal data
    Student ID, name, e-mail, phone (optional), club membership, attendance records.
    In this course
    Teams analyse and model Club Hub as a web and mobile application and implement its core in C++20: domain model, business rules and a command-line front end, with CSV or JSON files for storage. Canonical names: namespace clubhub; classes Club, Event, Student, Registration, RegistrationService (registerStudent, cancel, checkIn); enums ClubStatus, RegistrationStatus (Registered, Cancelled, CheckedIn, Waitlisted). Layout: CMakeLists.txt, src/, include/clubhub/, tests/.
  4. Deliverables of this lab. Create the files as you go; every task fills some of them.
    itc-club-hub/
    ├── lab01/                              # Lab-01, read-only here
    ├── lab02/
    │   ├── app-classification.md           # Task 1
    │   ├── app-quadrant.mmd                # Task 1 (Mermaid quadrant chart)
    │   ├── adr-001-application-type.md     # Task 2
    │   ├── quality-scenarios.md            # Task 3
    │   ├── context-diagram.mmd  (+ .png)   # Task 4
    │   ├── architecture.mmd     (+ .png)   # Task 4
    │   ├── README.md                       # Task 5 (record what changed)
    │   └── challenge/offline-checkin.md    # Challenge
    ├── include/clubhub/event.hpp           # Task 5
    ├── include/clubhub/event_csv.hpp       # Task 5
    ├── include/clubhub/queued_checkin.hpp  # Challenge
    ├── src/event.cpp, src/event_csv.cpp    # Task 5
    ├── src/cli/main.cpp                    # Task 5
    ├── data/events.csv                     # Task 5
    └── CMakeLists.txt                      # Task 5
  5. Conventions. Every Markdown file starts with a title, a version and a date. Scenario ids are QAS-01..QAS-06, decision records ADR-001... Commit after each task with a message such as Lab-02 task 3: quality attribute scenarios.
1

Classify five applications you use every day

Easy 30 min

Goal

Practise the vocabulary of the chapter on real software before applying it to Club Hub: for each application name its type, where its code runs, its architecture and the quality attributes that matter most.

Steps

  1. Copy the starter into lab02/app-classification.md. The five applications are fixed: a mobile banking app one of you uses, Telegram, a car's ABS (anti-lock braking) controller, Moodle as used at ITC, and a game one of you plays (write its name).
  2. Split the rows: each team member fills at least one row and commits it with their own Git account (git log --format='%an' -- lab02/app-classification.md must show at least four names).
  3. Column Kind: system or application software. Column Type(s): use the chapter's terms (desktop, web MPA or SPA, mobile native, cross-platform or PWA, enterprise, embedded real-time, IoT, cloud SaaS, data-intensive or AI-enabled, game). Several types per row are normal.
  4. Column Where the code runs: name every place (phone, browser, server, ECU in the car) and how the software reaches it (app store, URL, installed at the factory, over-the-air update).
  5. Column Architecture: standalone, client-server, three-tier, embedded control loop, or a combination; one line of evidence (for example "works offline and syncs later").
  6. Column Top 3 qualities: choose from performance, availability, security, usability, portability, safety, and justify each with one concrete consequence of failure ("if Moodle is down during an online quiz...").
  7. Place the five applications on a Mermaid quadrantChart (timing demand against availability demand, as on the chapter slide) in lab02/app-quadrant.mmd, and write two sentences below the table on the biggest difference you found.

Starter

# Lab-02 · Classification of five applications
Version 1.0 · 2026-__-__ · Team: __

| # | Application | Kind | Type(s) | Where the code runs, how it is delivered | Architecture | Top 3 qualities (why) | Filled by |
|---|-------------|------|---------|------------------------------------------|--------------|-----------------------|-----------|
| 1 | Mobile banking app: __ | | | | | | |
| 2 | Telegram | | | | | | |
| 3 | ABS controller in a car | | | | | | |
| 4 | Moodle at ITC | | | | | | |
| 5 | Game: __ | | | | | | |

Biggest difference we found: __
quadrantChart
  title Five applications: timing vs availability
  x-axis Low timing demand --> High timing demand
  y-axis Low availability demand --> High availability demand
  quadrant-1 Critical and fast
  quadrant-2 Always on
  quadrant-3 Best effort
  quadrant-4 Fast but tolerant
  Telegram: [0.50, 0.50]
  %% TODO: move Telegram, then add the other four (values 0 to 1)

Expected output (one row, for an application that is not in your list)

| 0 | Google Maps | Application | Mobile native + web SPA; data-intensive
|   |             |             | (traffic from millions of phones)
| Where: phone app (store), browser SPA, Google servers; map tiles cached
|        on the phone, so the last route still shows offline
| Architecture: client-server; phone and browser clients call the same
|               back-end APIs
| Top 3: availability (drivers depend on it on the road), performance
|        (route in < 2 s or users switch app), usability (used while
|        driving: large buttons, voice)          | Filled by: Dara

Show hints

  • The ABS controller is the only row where a missed deadline can hurt someone: think about safety and hard real-time, and about who updates its software (a garage, not the driver).
  • Telegram has several clients (phone, desktop, web) talking to the same servers. List them all under "where the code runs".
  • Moodle is a web application hosted by the institute; ask yourself what happens to it in exam week.
  • A banking app is a mobile client of an enterprise system (the bank's core banking system). Security is obvious; what is the second quality?

Acceptance checklist

2

Decide the application type(s) for ITC Club Hub

Medium 45 min · led by the Product Owner

Goal

Choose how each user group (students on phones, organisers at the door, administrators on desktops) will use Club Hub, compare the options with a weighted matrix, and record the decision in an architecture decision record that a new team member could understand.

Steps

  1. In lab02/adr-001-application-type.md, fill the User groups table: for students, organisers and administrators write device, location, connectivity, how often they use Club Hub and the one thing that must never fail for them. Take the facts from the case-study brief and your Lab-01 stakeholder list.
  2. List at least four options from: native apps (Android + iOS), cross-platform app, PWA, responsive web app, desktop application. Combinations are allowed (for example "PWA for students and organisers + the same web app for administrators").
  3. Agree on at least five criteria and their weights (1 to 3) with the Product Owner. Include cost for your team in one semester, reach, offline check-in at the door, reminders, and one criterion that matters to administrators. Write one line explaining each weight.
  4. Score every option from 1 to 5 per criterion, multiply by the weight and add up. Show the arithmetic (3 + 9 + 10 = 22), not only the totals.
  5. Check the winner against the deal-breakers (for example "check-in must work without Wi-Fi"). If it fails one, say what you add or change.
  6. Write the decision part of ADR-001: Status, Context, Decision, Consequences (at least two + and two -), Rejected options with one reason each. State how the front ends will reach the C++ core you build in this course (for example "the core would sit behind an HTTP API; in this course the CLI calls it directly").
  7. The Product Owner presents the ADR to the teaching assistant or instructor (your client) in 3 minutes; set Status: Accepted with the date, and add a line to lab01/decisions.md pointing to ADR-001.

Starter

# ADR-001 · Application types for ITC Club Hub
Version 1.0 · 2026-__-__ · Status: Proposed | Accepted (date) · Deciders: __

## User groups
| Group          | Device | Where | Connectivity | How often | Must never fail |
|----------------|--------|-------|--------------|-----------|-----------------|
| Students       |        |       |              |           |                 |
| Organisers     |        |       |              |           |                 |
| Administrators |        |       |              |           |                 |

## Options
A. __   B. __   C. __   D. __

## Weighted comparison (score 1-5 x weight)
| Criterion (weight) | why this weight | A | B | C | D |
|--------------------|-----------------|---|---|---|---|
| Cost for our team in one semester (_) | | | | | |
| Reach (_)                             | | | | | |
| Offline check-in at the door (_)      | | | | | |
| Reminders (_)                         | | | | | |
| __ for administrators (_)             | | | | | |
| **Total**                             | | = | = | = | = |

Deal-breaker check: __

## Decision
## Consequences
+ __
+ __
- __
- __
## Rejected options
## How the front ends reach the C++ core

Expected output (excerpt; your options, weights and scores will differ)

| Criterion (weight)          | why this weight              | A native | B PWA | C web |
| Admin report printing (1)   | 2-3 users, once a month      | 1 x 1=1  | 4x1=4 | 5x1=5 |
...
Deal-breaker check: C (responsive web) fails "check-in without Wi-Fi";
we keep B and add an offline check-in queue (see the challenge).

Consequences
+ one codebase for students, organisers and administrators
- iPhone users receive reminders by push only after installing the PWA;
  e-mail reminders remain the fallback
Rejected options
A native apps: three front ends for a 5-person team in one semester

Show hints

  • Decide per user group first, then look for one option that serves several groups. Administrators on desktops rarely need an app.
  • Weights are the Product Owner's call, but they must be written down before scoring, otherwise the matrix only confirms what you already wanted.
  • The chapter slide "Club Hub as a native app, a PWA or a responsive web app" is an example, not the answer: your weights, your fifth criterion and your deal-breakers must be your own.
  • A consequence with no cost ("-") means you have not looked hard enough: think about iOS, store review, offline storage limits, team skills.

Acceptance checklist

3

Write six quality attribute scenarios

Medium 45 min

Goal

Turn "Club Hub must be fast, reliable and secure" into six testable statements, one per quality attribute, each with a source, stimulus, environment, artifact, response and a measurable response measure.

Steps

  1. Create lab02/quality-scenarios.md from the starter. Write exactly six scenarios: QAS-01 availability, QAS-02 performance at check-in, QAS-03 usability, QAS-04 security, QAS-05 modifiability, QAS-06 portability.
  2. Each member writes at least one scenario and commits it; the Scrum Master reviews all six against the checklist below before the task is ticked.
  3. Make every Environment concrete (time, load, network, device) using the key numbers in the case-study brief, for example "Friday 18:00, registration opens" or "200 arrivals in 10 minutes".
  4. Make every Response measure a number with a unit that someone could test: seconds, percent, minutes of downtime, number of taps, files changed, days of work. Show any calculation (for availability, the downtime per month it allows).
  5. For QAS-04 (security) choose a realistic threat for this application: another student reading attendance records, a guessed organiser account, a scraped list of phone numbers. Name the personal data at risk.
  6. For QAS-06 (portability) use the decision from Task 2: which browsers, phones or operating systems must work, and how you will check them.
  7. End the file with a priority table: each scenario rated for business importance (H/M/L) and technical risk (H/M/L), and the user group it protects. Mark the two scenarios the architecture must satisfy first.

Starter

# Lab-02 · Quality attribute scenarios for ITC Club Hub
Version 1.0 · 2026-__-__ · Reviewed by (Scrum Master): __

## QAS-01 · Availability · written by __
| Part             | Content |
|------------------|---------|
| Source           |         |
| Stimulus         |         |
| Environment      |         |
| Artifact         |         |
| Response         |         |
| Response measure |         |

## QAS-02 · Performance of check-in at the door · written by __
...
## QAS-06 · Portability · written by __

## Priorities
| Scenario | Business importance | Technical risk | Protects | First? |
|----------|---------------------|----------------|----------|--------|
| QAS-01   |                     |                |          |        |

Expected output (one scenario that the chapter did not show)

## QAS-05 · Modifiability · written by Sokha
| Source           | a developer of the team                              |
| Stimulus         | adds the waiting list (a Should feature): a full     |
|                  | event puts new registrations in status Waitlisted    |
| Environment      | design time, after Sprint 1, core already tested     |
| Artifact         | RegistrationService, Registration (include/clubhub/) |
| Response         | change made in the core only; the CLI gains one      |
|                  | column; existing behaviour unchanged                 |
| Response measure | done in <= 2 person-days; <= 4 files changed under    |
|                  | include/ and src/; 0 changes in src/cli/ other than  |
|                  | display; all existing tests still pass               |

Show hints

  • Availability arithmetic: a 30-day month has 720 hours, so 99 % allows 7.2 h of downtime and 99.9 % allows about 43 minutes. Pick a target a student club can really pay for.
  • Usability measures can be counted: "a first-time student registers for an event in at most 3 taps and 30 seconds, without help".
  • Security responses are actions: deny, log, alert, lock after N attempts. Measures: "0 attendance records visible to other students", "account locked after 5 failed logins within 10 minutes".
  • A scenario without a number is not finished. Read each response measure aloud and ask: "how would a tester prove this is false?"

Acceptance checklist

4

Draw a context diagram and a three-tier sketch

Medium 40 min

Goal

Show Club Hub as one black box with everything around it (users and external systems) and the data that flows between them; then open the box one level and show its three tiers, marking which part your C++ code implements this semester.

Steps

  1. In lab02/context-diagram.mmd (Mermaid flowchart LR, or draw.io exported to PNG) draw Club Hub as a single node in the centre. Do not draw anything inside it.
  2. Add the four human roles as nodes: Student, Club leader, Organiser, Administrator, and the two external systems: E-mail service and ITC student directory. Use a different shape for external systems (for example [[ ]]).
  3. Label every arrow with the data that flows, in the direction it flows: "registration, cancellation", "confirmation, reminder", "student ID lookup", "name and e-mail", "monthly report"... Aim for 10 to 14 labelled arrows.
  4. In lab02/architecture.mmd draw three subgraphs Presentation, Logic, Data. Presentation holds the front ends from your ADR-001 plus the C++ CLI; Logic holds RegistrationService and EventService; Data holds the CSV files (and "database later"). Draw the external systems where Logic calls them.
  5. Mark with a style or a note which nodes your team implements in C++ this semester (the core and the CLI) and which are only modelled (the PWA or web front ends). Write the matching folder next to each implemented node: include/clubhub/, src/, src/cli/, data/.
  6. Export both diagrams to PNG next to the .mmd files, and embed them in lab02/README.md later (Task 5) so they show on GitHub.

Starter

flowchart LR
  S([Student]) -- browse, register, cancel --> CH[ITC Club Hub]
  CH -- confirmation, reminder request --> MAIL[[E-mail service]]
  MAIL -- e-mail --> S
  %% TODO: Club leader, Organiser, Administrator
  %% TODO: ITC student directory (what does Club Hub ask, what comes back?)
  %% TODO: label every arrow with the data that flows
flowchart TB
  subgraph P[Presentation]
    CLI[C++ CLI · src/cli/]
    %% TODO: the front ends from ADR-001
  end
  subgraph L[Logic]
    RS[RegistrationService · include/clubhub/, src/]
  end
  subgraph D[Data]
    CSV[(CSV files · data/)]
  end
  CLI --> RS
  RS --> CSV
  %% TODO: external systems, and mark implemented vs modelled nodes

Expected output (the context diagram started; yours has all six neighbours)

flowchart LR
  S([Student]) -- browse, register, cancel --> CH[ITC Club Hub]
  CH -- event list, registration status --> S
  O([Organiser]) -- check-in scans --> CH
  CH -- attendance list --> O
  CH -- confirmation, reminder request --> MAIL[[E-mail service]]
  MAIL -- e-mail --> S

Show hints

  • A context diagram answers "what is outside and what crosses the border?" If you draw a database or a class, it is no longer a context diagram.
  • The student directory is read by Club Hub: the arrow to it carries a request (student ID), the arrow back carries data (name, e-mail, exists or not).
  • Club leaders and administrators exchange different data: club registration goes one way, approval or rejection comes back.
  • Mermaid styles: classDef built fill:#d1e7dd,stroke:#198754 and class RS,CLI built mark implemented nodes; or add a note-like node "implemented in C++".

Acceptance checklist

5

C++: separate the core from the interface

Hard 75 min

Goal

Build the first piece of the Club Hub core, an Event class with its start time as a std::chrono time point, and a CSV loader, in a library that never prints and never opens a file by name. Then add a thin command-line front end with a list-events command. The same core could later serve a web or mobile front end through an API, exactly as in your three-tier sketch.

Steps

  1. Create include/clubhub/event.hpp and include/clubhub/event_csv.hpp exactly as in the starter (they are the agreed interface; do not add <iostream> to them).
  2. In src/event.cpp complete the Event constructor: throw std::invalid_argument when the id or title is empty or the capacity is not positive. Then finish parseTimePoint so that "2026-10-14 14:00" parses and "2026-02-30 10:00", "2026-10-14" and "14:00 2026-10-14" throw.
  3. In src/event_csv.cpp implement loadEventsCsv: skip the header, split each line at commas, require 5 fields, build an Event, and put every bad row into errors with its line number instead of stopping.
  4. In src/cli/main.cpp finish listEvents: sort by start time, print an aligned table, the count, and the skipped rows on std::cerr. All console output lives in this file only.
  5. Create data/events.csv from the starter (keep the invalid row E004: it proves the error path), add the CMakeLists.txt targets clubhub_core (library) and clubhub (executable), then build and run:
    cmake -S . -B build
    cmake --build build
    ./build/clubhub list-events data/events.csv     # Windows: build\Debug\clubhub.exe
    grep -rn "iostream\|cout\|ifstream" include/ src/event*.cpp   # must print nothing
  6. Split the work so that at least three members commit: one the header and event.cpp, one the CSV loader, one the CLI and CMakeLists.txt. Review each other's code in the pull request.
  7. Record what changed: create lab02/README.md with (a) a table of every Lab-02 file (documents, diagrams, code) with a one-line description and its main author, (b) a table Inputs reused listing the Lab-01 files you used (lab01/team-charter.md, lab01/decisions.md) with their version or commit hash (git log -1 --format=%h -- lab01/team-charter.md), (c) the two PNG diagrams embedded, and (d) a change-log row (date, version 1.0, what changed, by whom). Commit as Lab-02 task 5: event core, list-events CLI, README and push the branch.

Starter: the core interface (include/clubhub/event.hpp, include/clubhub/event_csv.hpp)

// include/clubhub/event.hpp
#pragma once
#include <chrono>
#include <string>

namespace clubhub {

// Minute precision is enough for club events. Times are ITC local
// time (Asia/Phnom_Penh); this lab does no time-zone conversion.
using TimePoint = std::chrono::sys_time<std::chrono::minutes>;

class Event {
public:
    // Throws std::invalid_argument when a field breaks a rule.
    Event(std::string id, std::string title, TimePoint start,
          std::string location, int capacity);

    const std::string& id() const noexcept { return id_; }
    const std::string& title() const noexcept { return title_; }
    TimePoint start() const noexcept { return start_; }
    const std::string& location() const noexcept { return location_; }
    int capacity() const noexcept { return capacity_; }

    // Business rule: events in the past cannot be registered for.
    bool hasStarted(TimePoint now) const noexcept { return start_ <= now; }

private:
    std::string id_;
    std::string title_;
    TimePoint start_;
    std::string location_;
    int capacity_;
};

// "2026-10-14 14:00" -> TimePoint; throws std::invalid_argument
TimePoint parseTimePoint(const std::string& text);

// TimePoint -> "2026-10-14 14:00"
std::string formatTimePoint(TimePoint t);

} // namespace clubhub

// include/clubhub/event_csv.hpp
#pragma once
#include <istream>
#include <string>
#include <vector>

#include "clubhub/event.hpp"

namespace clubhub {

struct CsvError {
    int line;
    std::string message;
};

struct EventLoadResult {
    std::vector<Event> events;
    std::vector<CsvError> errors;
};

// Reads "id,title,start,location,capacity" rows after one header line.
// A bad row goes to errors; the good rows are still returned.
EventLoadResult loadEventsCsv(std::istream& in);

} // namespace clubhub

Starter: src/event.cpp (complete the TODOs)

#include "clubhub/event.hpp"

#include <iomanip>
#include <sstream>
#include <stdexcept>
#include <utility>

namespace clubhub {

Event::Event(std::string id, std::string title, TimePoint start,
             std::string location, int capacity)
    : id_{std::move(id)}, title_{std::move(title)}, start_{start},
      location_{std::move(location)}, capacity_{capacity} {
    // TODO: throw std::invalid_argument when id_ or title_ is empty
    // TODO: throw std::invalid_argument when capacity_ <= 0
}

TimePoint parseTimePoint(const std::string& text) {
    using namespace std::chrono;
    std::istringstream in{text};
    int y{}, mo{}, d{}, h{}, mi{};
    char dash1{}, dash2{}, colon{};
    in >> y >> dash1 >> mo >> dash2 >> d >> h >> colon >> mi;
    const year_month_day date{year{y}, month{static_cast<unsigned>(mo)},
                              day{static_cast<unsigned>(d)}};
    // TODO: throw std::invalid_argument{"bad date-time '" + text + "'"}
    //       unless the stream is good, the separators are '-', '-', ':',
    //       nothing follows, date.ok(), 0 <= h <= 23 and 0 <= mi <= 59
    return sys_days{date} + hours{h} + minutes{mi};
}

// Given: TimePoint -> "2026-10-14 14:00"
std::string formatTimePoint(TimePoint t) {
    using namespace std::chrono;
    const auto midnight = floor<days>(t);
    const year_month_day date{midnight};
    const hh_mm_ss time{t - midnight};
    std::ostringstream out;
    out << std::setfill('0') << static_cast<int>(date.year()) << '-'
        << std::setw(2) << static_cast<unsigned>(date.month()) << '-'
        << std::setw(2) << static_cast<unsigned>(date.day()) << ' '
        << std::setw(2) << time.hours().count() << ':'
        << std::setw(2) << time.minutes().count();
    return out.str();
}

} // namespace clubhub

Starter: src/event_csv.cpp, src/cli/main.cpp, CMakeLists.txt, data/events.csv

// src/event_csv.cpp
#include "clubhub/event_csv.hpp"

#include <sstream>
#include <stdexcept>

namespace clubhub {

EventLoadResult loadEventsCsv(std::istream& in) {
    EventLoadResult result;
    std::string line;
    int lineNo = 1;
    std::getline(in, line);  // skip the header
    while (std::getline(in, line)) {
        ++lineNo;
        // TODO: strip a trailing '\r' (files edited on Windows)
        // TODO: split the line at ',' into fields; expect exactly 5
        // TODO: build an Event (parseTimePoint for the start, an int for
        //       the capacity) and add it to result.events; on
        //       std::invalid_argument add {lineNo, e.what()} to result.errors
    }
    return result;
}

} // namespace clubhub

// src/cli/main.cpp: the only place that talks to the user
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>

#include "clubhub/event.hpp"
#include "clubhub/event_csv.hpp"

namespace {

int listEvents(const std::string& path) {
    std::ifstream file{path};
    if (!file) {
        std::cerr << "error: cannot open " << path << '\n';
        return 2;
    }
    auto [events, errors] = clubhub::loadEventsCsv(file);
    // TODO: sort events by start time (std::ranges::sort, projection
    //       &clubhub::Event::start)
    // TODO: print a header line and one aligned row per event
    //       (std::left, std::setw; formatTimePoint for the start)
    // TODO: print "<n> event(s)", then every error to std::cerr as
    //       <path>:<line>: skipped: <message>
    return errors.empty() ? 0 : 1;
}

void printUsage() {
    std::cerr << "usage: clubhub list-events [events.csv]\n";
}

} // namespace

int main(int argc, char* argv[]) {
    if (argc < 2) {
        printUsage();
        return 2;
    }
    const std::string command{argv[1]};
    if (command == "list-events") {
        return listEvents(argc > 2 ? argv[2] : "data/events.csv");
    }
    std::cerr << "unknown command: " << command << '\n';
    printUsage();
    return 2;
}
cmake_minimum_required(VERSION 3.28)
project(clubhub LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Core: domain model and rules. No console, no file names.
add_library(clubhub_core src/event.cpp src/event_csv.cpp)
target_include_directories(clubhub_core PUBLIC include)

# Interface: the command-line front end. A web or mobile front end
# would be another target that links the same core.
add_executable(clubhub src/cli/main.cpp)
target_link_libraries(clubhub PRIVATE clubhub_core)
id,title,start,location,capacity
E001,Robotics Workshop,2026-10-14 14:00,Lab F-301,30
E002,Debate Night,2026-10-16 18:00,Amphi A,120
E003,Intro to Git,2026-10-09 15:30,Room I-204,40
E004,Charity Run Briefing,2026-10-21 17:00,Sports Hall,0
E005,Photo Walk,2026-10-18 07:00,Main Gate,25

Expected output

$ ./build/clubhub list-events data/events.csv
START             TITLE                 LOCATION     CAPACITY
2026-10-09 15:30  Intro to Git          Room I-204   40
2026-10-14 14:00  Robotics Workshop     Lab F-301    30
2026-10-16 18:00  Debate Night          Amphi A      120
2026-10-18 07:00  Photo Walk            Main Gate    25
4 event(s)
data/events.csv:5: skipped: capacity must be positive
$ echo $?
1
$ ./build/clubhub
usage: clubhub list-events [events.csv]

Show hints

  • Splitting at commas: std::istringstream in{line}; while (std::getline(in, field, ',')) fields.push_back(field);. A line ending in a comma needs one extra empty field.
  • Parsing the capacity: std::from_chars (header <charconv>) reports an error instead of throwing; convert that into your own std::invalid_argument with a clear message such as bad number 'abc'.
  • "Nothing follows" in parseTimePoint: (in >> std::ws).eof() is true only when the rest of the text was blank.
  • Sorting with a projection: std::ranges::sort(events, {}, &clubhub::Event::start); compares the start times of two events.
  • If the final grep finds cout in the core, move that printing into src/cli/main.cpp and return data instead.

Acceptance checklist

Challenge: offline-first check-in for a PWA

Hard 90–120 min · optional, extra credit

Scenario

The Debate Night takes place in Amphi A. The Wi-Fi there drops for minutes at a time and 120 students arrive in 10 minutes through two doors, each staffed by an organiser with a phone running the Club Hub PWA. Check-in must never block on the network: each phone records check-ins locally, queues them, and synchronises with the server when it can. Two doors, one flaky network and a student who shows the same QR code twice will produce conflicts. Design how the system resolves them.

Requirements

  1. In lab02/challenge/offline-checkin.md draw a Mermaid sequenceDiagram with participants Organiser, PWA, ServiceWorker, IndexedDB queue and Club Hub API, showing: a scan while offline, the local green tick, the queued record, the network coming back, the batch upload and the server's per-item answer (accepted or rejected with a reason).
  2. Write a conflict rules table with at least five cases: the same student scanned at both doors; a scan for a registration cancelled on the server meanwhile; a student not registered at all; a retry of an upload that the server already applied; a phone whose clock is 7 minutes wrong. For each: who wins, what the organiser sees, what is stored.
  3. Define the C++ data structure for a queued check-in in include/clubhub/queued_checkin.hpp: a struct QueuedCheckIn and a class CheckInQueue (enqueue, pending, markAccepted, markRejected). Explain in the Markdown file which field makes a retried upload idempotent (applied at most once).
  4. Link the design to your quality scenarios: which of QAS-01..06 does offline-first improve, and which does it make harder? Write one new scenario for "check-in during a 5-minute network outage" with a measurable response.

Skeleton

// include/clubhub/queued_checkin.hpp
#pragma once
#include <chrono>
#include <optional>
#include <string>
#include <vector>

namespace clubhub {

enum class SyncState { Pending, Sent, Accepted, Rejected };

// One check-in recorded on an organiser's phone while offline.
struct QueuedCheckIn {
    std::string localId;    // TODO: why a UUID made on the device?
    std::string deviceId;
    std::string eventId;
    std::string studentId;
    std::chrono::sys_seconds scannedAt;   // device clock: trust it?
    // TODO: attempts, state, the server's reject reason (optional)
};

class CheckInQueue {
public:
    // false if this student is already queued for this event
    bool enqueue(QueuedCheckIn item);
    // oldest first; what the next sync attempt uploads
    std::vector<QueuedCheckIn> pending() const;
    void markAccepted(const std::string& localId);
    void markRejected(const std::string& localId, std::string reason);

private:
    std::vector<QueuedCheckIn> items_;
};

} // namespace clubhub
## Conflict rules
| # | Case | Winner | Organiser sees | Stored on the server |
|---|------|--------|----------------|----------------------|
| 1 | same student scanned at door 1 and door 2 | | | |
| 2 | registration cancelled on the server before the scan synced | | | |
| 3 | student not registered | | | |
| 4 | upload retried after a timeout, server had applied it | | | |
| 5 | phone clock 7 minutes wrong | | | |

Expected output (the start of the sequence diagram and one rule)

sequenceDiagram
  actor O as Organiser
  participant P as PWA
  participant Q as IndexedDB queue
  participant A as Club Hub API
  Note over P,A: Wi-Fi down
  O->>P: scan QR of student e20230117
  P->>Q: store check-in, state Pending
  P-->>O: green tick, marked offline
  Note over P,A: Wi-Fi back
  P->>A: POST batch of pending check-ins
  A-->>P: per item: accepted or rejected with reason
| 4 | upload retried after a timeout | server (first copy) | nothing new |
|   | the server keeps a set of applied localIds; a second POST with the |
|   | same localId returns "accepted" again without a second record      |

Show hints

  • The server is the single source of truth for registrations; the phone is the source of truth for "this student stood at my door at 18:02".
  • Check-in is naturally idempotent for the business ("checked in" twice is still checked in), so duplicate scans can be merged: keep the earliest scan and remember both devices.
  • Never order events across phones by device clocks alone; store the server's receivedAt as well, and use device time only for ordering within one phone.
  • Think about what the organiser can do at the door when the server later rejects a check-in: the rejected list must reach the organiser's screen, not only a log file.

Acceptance checklist

Grading rubric

ComponentPointsFull marks when…
Task 1 · Classify five applications10Five complete rows in chapter vocabulary, justified qualities, quadrant chart renders.
Task 2 · Application type decision15Three user groups analysed, four options, five weighted criteria with visible arithmetic, deal-breaker check, ADR-001 accepted with rejected options.
Task 3 · Quality attribute scenarios20Six complete six-part scenarios, every response measure testable, priorities set, reviewed.
Task 4 · Context and three-tier diagrams20Black-box context with all roles and both external systems, every flow labelled; three tiers with implemented parts mapped to folders.
Task 5 · C++ core vs interface + README25Builds cleanly, list-events output as expected including the error path, no I/O in the core, std::chrono start time, complete README.
Quality of documents, diagrams and code10Headers with version and date, consistent ids, readable diagrams, modern C++ (const, RAII, no raw new), contributions of every member visible in Git.
★ Challenge (extra credit)+20Sequence diagram, five or more conflict rules, compiling data structure with an idempotency key, new outage scenario and trade-offs.
Total100 (+20)
Automatic deductions: -5 per quality attribute scenario whose response measure has no number · -5 if ADR-001 lists no rejected options or no negative consequences · -5 if the context diagram shows internal parts of Club Hub or omits the e-mail service or the student directory · -5 if the core headers or src/event*.cpp use std::cout or open files by name · -10 if the code does not build from a clean checkout · -5 if lab02/README.md is missing · -5 if one member has no commit in this lab without a note explaining why.

Submission

  1. Repository layout after this lab:
    itc-club-hub/
    ├── CMakeLists.txt
    ├── README.md
    ├── data/events.csv
    ├── include/clubhub/event.hpp, event_csv.hpp (, queued_checkin.hpp)
    ├── src/event.cpp, src/event_csv.cpp, src/cli/main.cpp
    ├── lab01/ ...
    └── lab02/
        ├── README.md
        ├── app-classification.md, app-quadrant.mmd
        ├── adr-001-application-type.md
        ├── quality-scenarios.md
        ├── context-diagram.mmd + .png, architecture.mmd + .png
        └── challenge/offline-checkin.md   (optional)
  2. Self-check from a clean clone before you open the pull request (there are no tests yet; ctest simply reports none):
    cmake -S . -B build && cmake --build build && ctest --test-dir build
    ./build/clubhub list-events data/events.csv
    git log --format='%an' main..lab-02 | sort | uniq -c   # every member listed
  3. Branch lab-02, pull request titled Lab-02 – <team name>, reviewed by at least one team member other than the author, and the teaching assistant added as reviewer.