Employee Demographics Report

L3
ModelContextProtocolPostgresEmployees

Generate comprehensive employee demographics report with gender statistics, age groups, birth months, and hiring trends.

Created by Lingxiao Du
2025-08-14
Reporting And AnalyticsStatistical Aggregation

Model Ranking

Click on the dots to view the trajectory of each task run
Model
Run Results
Pass@4
Pass^4
Avg Time
Avg Turns
Input Tokens
Output Tokens
Total Tokens
OpenAI
gpt-5-mini-medium
3
/4
57.8s
8.8
37,645
5,878
43,523
DeepSeek
deepseek-chat
2
/4
303.1s
23.5
131,041
3,970
135,010
OpenAI
gpt-5-mini-high
2
/4
113.8s
9.3
42,343
12,332
54,675
OpenAI
gpt-5-mini-low
2
/4
39.1s
4.5
15,101
2,075
17,176
Claude
claude-sonnet-4-high
1
/4
116.3s
19.3
160,100
5,161
165,260
OpenAI
gpt-5-low
1
/4
160.8s
11.3
41,740
9,932
51,672
OpenAI
gpt-5-medium
1
/4
209.8s
11.5
58,359
10,069
68,428
Grok
grok-4
1
/4
121.1s
8.5
46,939
5,449
52,387
Grok
grok-code-fast-1
1
/4
46.1s
18.3
123,876
3,977
127,853
OpenAI
o3
1
/4
58.0s
8.8
26,750
3,304
30,054
Claude
claude-opus-4-1
0
/1
--
519.0s
29.0
234,688
9,902
244,590
Claude
claude-sonnet-4
0
/4
185.6s
19.5
141,499
6,006
147,505
Claude
claude-sonnet-4-low
0
/4
115.4s
18.5
155,339
4,979
160,318
Gemini
gemini-2-5-flash
0
/4
46.7s
10.0
28,740
6,745
35,485
Gemini
gemini-2-5-pro
0
/4
63.8s
7.0
21,269
6,372
27,641
Z.ai
glm-4-5
0
/4
90.4s
11.3
30,386
1,191
31,577
OpenAI
gpt-4-1
0
/4
18.8s
7.0
13,146
681
13,828
OpenAI
gpt-4-1-mini
0
/4
38.5s
18.5
69,604
1,266
70,870
OpenAI
gpt-4-1-nano
0
/4
26.2s
9.0
23,213
1,737
24,950
OpenAI
gpt-5-high
0
/4
858.0s
13.8
79,374
20,002
99,376
OpenAI
gpt-5-nano-high
0
/4
450.2s
19.8
182,331
96,373
278,704
OpenAI
gpt-5-nano-low
0
/4
109.1s
8.3
28,325
22,994
51,319
OpenAI
gpt-5-nano-medium
0
/4
167.1s
18.3
167,071
29,429
196,500
OpenAI
gpt-oss-120b
0
/4
28.4s
9.8
40,186
1,391
41,577
MoonshotAI
kimi-k2-0711
0
/4
300.0s
28.3
161,040
4,521
165,561
MoonshotAI
kimi-k2-0905
0
/4
320.6s
27.0
203,374
3,797
207,171
OpenAI
o4-mini
0
/4
228.2s
5.5
14,187
6,683
20,870
Qwen
qwen-3-coder-plus
0
/4
77.2s
26.3
215,944
3,492
219,437
Qwen
qwen-3-max
0
/4
44.4s
13.0
60,845
1,223
62,068

Task State

Enum "employees"."employee_gender" { "M" "F" } Table "employees"."department" { "id" bpchar(4) [pk, not null] "dept_name" varchar(40) [unique, not null] } Table "employees"."department_employee" { "employee_id" int8 [not null] "department_id" bpchar(4) [not null] "from_date" date [not null] "to_date" date [not null] Indexes { (employee_id, department_id) [type: btree, name: "idx_16982_primary"] department_id [type: btree, name: "idx_16982_dept_no"] } } Table "employees"."department_manager" { "employee_id" int8 [not null] "department_id" bpchar(4) [not null] "from_date" date [not null] "to_date" date [not null] Indexes { (employee_id, department_id) [type: btree, name: "idx_16985_primary"] department_id [type: btree, name: "idx_16985_dept_no"] } } Table "employees"."employee" { "id" int8 [pk, not null, increment] "birth_date" date [not null] "first_name" varchar(14) [not null] "last_name" varchar(16) [not null] "gender" employees.employee_gender [not null] "hire_date" date [not null] } Table "employees"."salary" { "employee_id" int8 [not null] "amount" int8 [not null] "from_date" date [not null] "to_date" date [not null] Indexes { (employee_id, from_date) [type: btree, name: "idx_16991_primary"] } } Table "employees"."title" { "employee_id" int8 [not null] "title" varchar(50) [not null] "from_date" date [not null] "to_date" date Indexes { (employee_id, title, from_date) [type: btree, name: "idx_16994_primary"] } } Ref "dept_emp_ibfk_1":"employees"."employee"."id" < "employees"."department_employee"."employee_id" [update: restrict, delete: cascade] Ref "dept_emp_ibfk_2":"employees"."department"."id" < "employees"."department_employee"."department_id" [update: restrict, delete: cascade] Ref "dept_manager_ibfk_1":"employees"."employee"."id" < "employees"."department_manager"."employee_id" [update: restrict, delete: cascade] Ref "dept_manager_ibfk_2":"employees"."department"."id" < "employees"."department_manager"."department_id" [update: restrict, delete: cascade] Ref "salaries_ibfk_1":"employees"."employee"."id" < "employees"."salary"."employee_id" [update: restrict, delete: cascade] Ref "titles_ibfk_1":"employees"."employee"."id" < "employees"."title"."employee_id" [update: restrict, delete: cascade]

Instruction

Generate a comprehensive employee demographics and basic statistics report for the annual company overview. The HR team needs simple, clear statistical summaries about our workforce composition to include in the annual report and diversity initiatives.

Your Tasks:

  1. Create the gender statistics table — build a table called gender_statistics in the employees schema with these exact columns:

    • gender (varchar) — gender ('M' or 'F')
    • total_employees (integer) — total number of employees of this gender
    • current_employees (integer) — current employees of this gender (have active salary)
    • percentage_of_workforce (decimal) — percentage of current workforce
  2. Create the age group analysis table — build a table called age_group_analysis in the employees schema with:

    • age_group (varchar) — age range ('20-29', '30-39', '40-49', '50-59', '60+')
    • employee_count (integer) — number of current employees in age group
    • avg_salary (decimal) — average current salary for age group
    • avg_tenure_days (decimal) — average days of service
  3. Create the birth month distribution table — build a table called birth_month_distribution in the employees schema with:

    • birth_month (integer) — month number (1-12)
    • month_name (varchar) — month name ('January', 'February', etc.)
    • employee_count (integer) — total employees born in this month
    • current_employee_count (integer) — current employees born in this month
  4. Create the hiring year summary table — build a table called hiring_year_summary in the employees schema with:

    • hire_year (integer) — year employees were hired
    • employees_hired (integer) — number of employees hired that year
    • still_employed (integer) — how many from that year are still employed
    • retention_rate (decimal) — percentage still employed (still_employed/employees_hired * 100)
  5. Apply age group classification based on current age:

    • 20-29: Ages 20-29
    • 30-39: Ages 30-39
    • 40-49: Ages 40-49
    • 50-59: Ages 50-59
    • 60+: Ages 60 and above
  6. Calculate workforce composition — determine current workforce demographics using employees with active salary records (to_date = '9999-01-01').

  7. Focus on basic statistics — create simple counts, averages, and percentages that are easy to understand and verify.

The analysis will provide clear demographic insights for HR reporting and workforce planning.



Verify

*.py
Python
"""
Verification script for PostgreSQL Task 3: Employee Demographics Report
"""

import os
import sys
import psycopg2
from decimal import Decimal

def rows_match(actual_row, expected_row):
    """
    Compare two rows with appropriate tolerance.
    For Decimal types: allows 0.1 tolerance
    For other types: requires exact match
    """
    if len(actual_row) != len(expected_row):
        return False
    
    for actual, expected in zip(actual_row, expected_row):
        if isinstance(actual, Decimal) and isinstance(expected, Decimal):
            if abs(float(actual) - float(expected)) > 0.1:
                return False
        elif actual != expected:
            return False
    
    return True

def get_connection_params() -> dict:
    """Get database connection parameters."""
    return {
        "host": os.getenv("POSTGRES_HOST", "localhost"),
        "port": int(os.getenv("POSTGRES_PORT", 5432)),
        "database": os.getenv("POSTGRES_DATABASE"),
        "user": os.getenv("POSTGRES_USERNAME"),
        "password": os.getenv("POSTGRES_PASSWORD")
    }

def verify_gender_statistics_results(conn) -> bool:
    """Verify the gender statistics results."""
    with conn.cursor() as cur:
        # Get actual results from the created table
        cur.execute("""
            SELECT gender, total_employees, current_employees, percentage_of_workforce
            FROM employees.gender_statistics
            ORDER BY gender
        """)
        actual_results = cur.fetchall()
        
        # Execute ground truth query
        cur.execute("""
            WITH current_emp AS (
            SELECT DISTINCT s.employee_id
            FROM employees.salary s
            WHERE s.to_date = DATE '9999-01-01'
            ),
            total_current AS (
            SELECT COUNT(*) AS cnt
            FROM current_emp
            )
            SELECT
            e.gender::varchar AS gender,
            COUNT(*) AS total_employees,
            COUNT(*) FILTER (WHERE ce.employee_id IS NOT NULL) AS current_employees,
            (COUNT(*) FILTER (WHERE ce.employee_id IS NOT NULL))::DECIMAL
                / NULLIF((SELECT cnt FROM total_current), 0) * 100 AS percentage_of_workforce
            FROM employees.employee e
            LEFT JOIN current_emp ce ON ce.employee_id = e.id
            WHERE e.gender IN ('M','F')
            GROUP BY e.gender
            ORDER BY gender;
        """)
        expected_results = cur.fetchall()

        if len(actual_results) != len(expected_results):
            print(f"❌ Expected {len(expected_results)} gender statistics results, got {len(actual_results)}")
            return False

        mismatches = 0
        for i, (actual, expected) in enumerate(zip(actual_results, expected_results)):
            if not rows_match(actual, expected):
                if mismatches < 5:  # Only show first 5 mismatches
                    print(f"❌ Row {i+1} mismatch: expected {expected}, got {actual}")
                mismatches += 1

        if mismatches > 0:
            print(f"❌ Total mismatches: {mismatches}")
            return False

        print(f"✅ Gender statistics results are correct ({len(actual_results)} records)")
        return True

def verify_age_group_results(conn) -> bool:
    """Verify the age group analysis results."""
    with conn.cursor() as cur:
        # Get actual results from the created table
        cur.execute("""
            SELECT age_group, employee_count, avg_salary, avg_tenure_days
            FROM employees.age_group_analysis
            ORDER BY age_group
        """)
        actual_results = cur.fetchall()
        
        # Execute ground truth query
        cur.execute("""
WITH current_salary AS (
  SELECT employee_id, amount
  FROM (
    SELECT s.*,
           ROW_NUMBER() OVER (
             PARTITION BY s.employee_id
             ORDER BY s.from_date DESC, s.amount DESC
           ) AS rn
    FROM employees.salary s
    WHERE s.to_date = DATE '9999-01-01'
  ) x
  WHERE rn = 1
),
emp_age AS (
  SELECT
    e.id AS employee_id,
    e.hire_date,
    EXTRACT(YEAR FROM AGE(CURRENT_DATE, e.birth_date))::INT AS age_years
  FROM employees.employee e
  WHERE e.birth_date IS NOT NULL
)
SELECT
  CASE
    WHEN a.age_years BETWEEN 20 AND 29 THEN '20-29'
    WHEN a.age_years BETWEEN 30 AND 39 THEN '30-39'
    WHEN a.age_years BETWEEN 40 AND 49 THEN '40-49'
    WHEN a.age_years BETWEEN 50 AND 59 THEN '50-59'
    WHEN a.age_years >= 60 THEN '60+'
  END AS age_group,
  COUNT(*)::INT AS employee_count,
  AVG(cs.amount) AS avg_salary,
  AVG((CURRENT_DATE - a.hire_date)::INT) AS avg_tenure_days
FROM emp_age a
JOIN current_salary cs ON cs.employee_id = a.employee_id
WHERE a.age_years >= 20
GROUP BY 1
ORDER BY 1;
        """)
        expected_results = cur.fetchall()

        if len(actual_results) != len(expected_results):
            print(f"❌ Expected {len(expected_results)} age group results, got {len(actual_results)}")
            return False

        mismatches = 0
        for i, (actual, expected) in enumerate(zip(actual_results, expected_results)):
            if not rows_match(actual, expected):
                if mismatches < 5:  # Only show first 5 mismatches
                    print(f"❌ Row {i+1} mismatch: expected {expected}, got {actual}")
                mismatches += 1

        if mismatches > 0:
            print(f"❌ Total mismatches: {mismatches}")
            return False

        print(f"✅ Age group analysis results are correct ({len(actual_results)} records)")
        return True

def verify_birth_month_results(conn) -> bool:
    """Verify the birth month distribution results."""
    with conn.cursor() as cur:
        # Get actual results from the created table
        cur.execute("""
            SELECT birth_month, month_name, employee_count, current_employee_count
            FROM employees.birth_month_distribution
            ORDER BY birth_month
        """)
        actual_results = cur.fetchall()
        
        # Execute ground truth query
        cur.execute("""
            WITH current_emp AS (
            SELECT DISTINCT s.employee_id
            FROM employees.salary s
            WHERE s.to_date = DATE '9999-01-01'
            ),
            months AS (
            SELECT gs AS birth_month
            FROM generate_series(1, 12) AS gs
            )
            SELECT
            m.birth_month::INTEGER AS birth_month,
            CASE m.birth_month
                WHEN 1 THEN 'January'   WHEN 2 THEN 'February' WHEN 3 THEN 'March'
                WHEN 4 THEN 'April'     WHEN 5 THEN 'May'      WHEN 6 THEN 'June'
                WHEN 7 THEN 'July'      WHEN 8 THEN 'August'   WHEN 9 THEN 'September'
                WHEN 10 THEN 'October'  WHEN 11 THEN 'November'WHEN 12 THEN 'December'
            END AS month_name,
            COUNT(e.id)::INTEGER AS employee_count,
            COUNT(ce.employee_id)::INTEGER AS current_employee_count
            FROM months m
            LEFT JOIN employees.employee e
            ON EXTRACT(MONTH FROM e.birth_date) = m.birth_month
            LEFT JOIN current_emp ce
            ON ce.employee_id = e.id
            GROUP BY m.birth_month
            ORDER BY m.birth_month;
        """)
        expected_results = cur.fetchall()

        if len(actual_results) != len(expected_results):
            print(f"❌ Expected {len(expected_results)} birth month results, got {len(actual_results)}")
            return False

        mismatches = 0
        for i, (actual, expected) in enumerate(zip(actual_results, expected_results)):
            if not rows_match(actual, expected):
                if mismatches < 5:  # Only show first 5 mismatches
                    print(f"❌ Row {i+1} mismatch: expected {expected}, got {actual}")
                mismatches += 1

        if mismatches > 0:
            print(f"❌ Total mismatches: {mismatches}")
            return False

        print(f"✅ Birth month distribution results are correct ({len(actual_results)} records)")
        return True

def verify_hiring_year_results(conn) -> bool:
    """Verify the hiring year summary results."""
    with conn.cursor() as cur:
        # Get actual results from the created table
        cur.execute("""
            SELECT hire_year, employees_hired, still_employed, retention_rate
            FROM employees.hiring_year_summary
            ORDER BY hire_year
        """)
        actual_results = cur.fetchall()
        
        # Execute ground truth query
        cur.execute("""
            WITH current_emp AS (
            SELECT DISTINCT s.employee_id
            FROM employees.salary s
            WHERE s.to_date = DATE '9999-01-01'
            ),
            base AS (
            SELECT e.id, EXTRACT(YEAR FROM e.hire_date)::INT AS hire_year
            FROM employees.employee e
            WHERE e.hire_date IS NOT NULL
            )
            SELECT
            b.hire_year,
            COUNT(*)::INT AS employees_hired,
            COUNT(*) FILTER (WHERE ce.employee_id IS NOT NULL)::INT AS still_employed,
            (COUNT(*) FILTER (WHERE ce.employee_id IS NOT NULL))::DECIMAL
                / NULLIF(COUNT(*), 0) * 100 AS retention_rate
            FROM base b
            LEFT JOIN current_emp ce ON ce.employee_id = b.id
            GROUP BY b.hire_year
            ORDER BY b.hire_year;
        """)
        expected_results = cur.fetchall()

        if len(actual_results) != len(expected_results):
            print(f"❌ Expected {len(expected_results)} hiring year results, got {len(actual_results)}")
            return False

        mismatches = 0
        for i, (actual, expected) in enumerate(zip(actual_results, expected_results)):
            if not rows_match(actual, expected):
                if mismatches < 5:  # Only show first 5 mismatches
                    print(f"❌ Row {i+1} mismatch: expected {expected}, got {actual}")
                mismatches += 1

        if mismatches > 0:
            print(f"❌ Total mismatches: {mismatches}")
            return False

        print(f"✅ Hiring year summary results are correct ({len(actual_results)} records)")
        return True

def main():
    """Main verification function."""
    print("=" * 50)

    # Get connection parameters
    conn_params = get_connection_params()

    if not conn_params["database"]:
        print("❌ No database specified")
        sys.exit(1)

    try:
        # Connect to database
        conn = psycopg2.connect(**conn_params)

        # Verify all four analysis results
        success = (
            verify_gender_statistics_results(conn) and 
            verify_age_group_results(conn) and 
            verify_birth_month_results(conn) and
            verify_hiring_year_results(conn)
        )

        conn.close()

        if success:
            print("\n🎉 Task verification: PASS")
            sys.exit(0)
        else:
            print("\n❌ Task verification: FAIL")
            sys.exit(1)

    except psycopg2.Error as e:
        print(f"❌ Database error: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"❌ Verification error: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()