Paper 2 Ethics Issues Answers

These answers correspond to Paper 2 Ethics Issues Drills.

Verification note: every Python code block in this answer file has been executed locally.

Answer 1: Privacy Checklist

Model answer:

def privacy_issues(form):
    required = ["purpose", "consent", "retention"]
    missing = []
    for item in required:
        if item not in form:
            missing.append(item)
    return missing
 
 
form = {"name": "Amy", "phone": "91234567", "purpose": "event registration", "consent": True}
print(privacy_issues(form))

Expected output:

['retention']

Mark points:

  • defines the required privacy items;
  • checks purpose;
  • checks consent;
  • checks retention notice;
  • returns the exact missing item list.

Common weak answer:

  • checking only whether the form has a name and phone number. The question asks for privacy-notice items.

Answer 2: Access Log Analysis

Model answer:

def flag_after_hours(logs):
    flagged = []
    for record in logs:
        if record["hour"] < 8 or record["hour"] >= 18:
            flagged.append(record["user"])
    return flagged
 
 
logs = [
    {"user": "u1", "hour": 7},
    {"user": "u2", "hour": 9},
    {"user": "u3", "hour": 18},
    {"user": "u4", "hour": 17}
]
print(flag_after_hours(logs))

Expected output:

['u1', 'u3']

Mark points:

  • loops through all log records;
  • checks before opening time;
  • checks at or after closing time;
  • treats 18 as outside office hours;
  • appends user IDs, not whole records;
  • preserves record order.

Common weak answer:

  • using hour > 18, which fails to flag exactly 18.

Model answer:

def has_consent(record, purpose):
    return purpose in record["consent"]
 
 
record = {"user": "u1", "consent": ["billing", "support"]}
print(has_consent(record, "billing"))
print(has_consent(record, "marketing"))

Expected output:

True
False

Mark points:

  • reads the consent-purpose list;
  • checks the requested purpose;
  • returns True for billing;
  • returns False for marketing;
  • does not treat any consent as consent for every purpose;
  • returns Boolean values.

Common weak answer:

  • returning True just because the consent list is not empty.

Answer 4: Anonymisation

Model answer:

def anonymise(records):
    anonymised = []
    for record in records:
        anonymised.append({
            "age_group": record["age_group"],
            "region": record["region"],
            "score": record["score"]
        })
    return anonymised
 
 
records = [
    {"name": "Amy", "email": "amy@example.com", "age_group": "16-18", "region": "East", "score": 82}
]
print(anonymise(records))

Expected output:

[{'age_group': '16-18', 'region': 'East', 'score': 82}]

Mark points:

  • loops through records;
  • removes name;
  • removes email;
  • keeps age_group;
  • keeps region;
  • keeps score.

Common weak answer:

  • replacing only the name while leaving the email address.

Answer 5: Risk Register

Model answer:

def risk_register():
    return [
        ("unauthorised access", "access control"),
        ("excessive retention", "delete after 12 months"),
        ("biased output", "compare approval rates")
    ]
 
 
print(risk_register())

Expected output:

[('unauthorised access', 'access control'), ('excessive retention', 'delete after 12 months'), ('biased output', 'compare approval rates')]

Mark points:

  • includes unauthorised access risk;
  • matches it with access control;
  • includes excessive retention risk;
  • matches it with deletion after 12 months;
  • includes biased output risk;
  • matches it with comparing approval rates.

Common weak answer:

  • listing risks without mitigations.

Answer 6: Audit Function

Model answer:

def count_missing_consent(records):
    count = 0
    for record in records:
        if "consent" not in record or record["consent"] == False:
            count = count + 1
    return count
 
 
records = [
    {"user": "u1", "consent": True},
    {"user": "u2", "consent": False},
    {"user": "u3"}
]
print(count_missing_consent(records))

Expected output:

2

Mark points:

  • initializes a counter;
  • loops through all records;
  • counts explicit False consent;
  • counts missing consent field;
  • returns the correct count.

Common weak answer:

  • counting only records where consent exists and is False, missing records with no consent field.

Answer 7: Retention Rule

Model answer:

def should_archive(record_date, cutoff_date):
    return record_date < cutoff_date
 
 
print(should_archive("2024-01-15", "2025-01-01"))
print(should_archive("2025-02-10", "2025-01-01"))

Expected output:

True
False

Mark points:

  • compares record date with cutoff date;
  • uses ISO date strings where lexical order matches date order;
  • returns True for a record before cutoff;
  • returns False for a record after cutoff;
  • produces both expected outputs;
  • keeps the rule clear.

Common weak answer:

  • archiving records newer than the cutoff date.

Answer 8: Stakeholder Report

Model answer:

def impact_counts(records):
    counts = {}
    for record in records:
        stakeholder = record["stakeholder"]
        if stakeholder not in counts:
            counts[stakeholder] = 0
        counts[stakeholder] = counts[stakeholder] + 1
    return counts
 
 
records = [
    {"stakeholder": "students", "impact": "privacy concern"},
    {"stakeholder": "staff", "impact": "less manual work"},
    {"stakeholder": "students", "impact": "faster service"}
]
print(impact_counts(records))

Expected output:

{'students': 2, 'staff': 1}

Mark points:

  • initializes a dictionary;
  • loops through impact records;
  • groups by stakeholder;
  • initializes unseen stakeholders;
  • increments counts correctly.

Common weak answer:

  • counting impact descriptions rather than stakeholder groups.

Answer 9: Bias Check

Model answer:

def approval_rates(records):
    approved = {}
    totals = {}
 
    for record in records:
        group = record["group"]
        if group not in approved:
            approved[group] = 0
            totals[group] = 0
        totals[group] = totals[group] + 1
        if record["approved"]:
            approved[group] = approved[group] + 1
 
    rates = {}
    for group in totals:
        rates[group] = round(approved[group] / totals[group], 2)
    return rates
 
 
records = [
    {"group": "A", "approved": True},
    {"group": "A", "approved": True},
    {"group": "A", "approved": False},
    {"group": "B", "approved": True},
    {"group": "B", "approved": False},
    {"group": "B", "approved": False}
]
print(approval_rates(records))

Expected output:

{'A': 0.67, 'B': 0.33}

Mark points:

  • counts total records per group;
  • counts approved records per group;
  • calculates approval rate for Group A;
  • calculates approval rate for Group B;
  • rounds rates to two decimal places;
  • returns a dictionary by group;
  • matches the expected output.

Common weak answer:

  • comparing only approved counts without considering different group totals.

Answer 10: Incident Response

Model answer:

def ordered_incident_steps():
    return [
        "contain leak",
        "preserve evidence",
        "assess affected data",
        "notify responsible parties",
        "fix cause",
        "review controls"
    ]
 
 
print(ordered_incident_steps())

Expected output:

['contain leak', 'preserve evidence', 'assess affected data', 'notify responsible parties', 'fix cause', 'review controls']

Mark points:

  • contains the leak first;
  • preserves evidence;
  • assesses affected data;
  • includes notification/reporting;
  • fixes the cause and reviews controls.

Common weak answer:

  • deleting evidence immediately before assessing what happened.