Domain: Human Resources & Employee Management

I run a hospital and my current staffing is often not enough because of absences and skill requirements. I want the cheapest plan that becomes feasible by using temporary locum doctors only when needed.

Files attached to your enviroment:
patients.csv
doctors.csv
shifts.csv
service_times.csv
department_constraints.csv
absences.csv
locums.csv

Goal:
Create a staffing plan that satisfies all policies, with the fewest locums possible, and then the lowest total cost.

Rules and definitions:
- doctors.csv contains the regular staff doctors.
- locums.csv contains temporary doctors that can be hired for a single shift.
- Each clinician (regular doctor OR locum) can work at most one shift.
- A clinician can only be assigned to a shift if their department matches the shift’s department.
- A regular doctor cannot be assigned on a date if they appear in absences.csv for that date.
- A locum can only be assigned if locums.csv has available_date equal to the shift’s date (YYYY-MM-DD).
- Each shift covers a continuous time window [start_ts, end_ts] in local time. Use standard 60-minute hours.
- Every patient must be assigned to exactly one shift in their department such that arrival_ts >= start_ts and arrival_ts <= end_ts. If any patient cannot be assigned, output "INFEASIBLE".
- Capacity requirement per shift:
  - mean_minutes comes from service_times.csv by severity.
  - workload_minutes for a shift = sum(mean_minutes) over all patients assigned to that shift.
  - shift_duration_minutes = (end_ts - start_ts) in minutes.
  - Each assigned clinician provides exactly shift_duration_minutes minutes of capacity.
  - clinicians_required = ceil(workload_minutes / shift_duration_minutes).
  - If clinicians_required is 0, treat it as 1 (every shift must have at least 1 clinician).
- Waiting-time policy (must be satisfied per shift):
  - max_wait_minutes comes from department_constraints.csv by department.
  - avg_wait_minutes = max(0, (workload_minutes - clinicians_assigned * shift_duration_minutes) / clinicians_assigned).
  - If clinicians_assigned is 0, the shift is invalid.
  - A shift is compliant only if avg_wait_minutes <= max_wait_minutes.
- Skill policy (must be satisfied per assigned clinician):
  - min_skill comes from department_constraints.csv by department.
  - Every clinician assigned to a shift must have skill_level >= min_skill.

Cost:
- hourly_cost is a unit cost for that clinician.
- shift_cost = sum(hourly_cost * (shift_duration_minutes / 60)) over clinicians assigned to that shift.
- total_cost = sum(shift_cost) over all shifts.
- Round all monetary values to exactly 2 decimal places using ROUND_HALF_UP (standard rounding: 0.5 rounds away from zero).

Optimization objective (strict, in this order):
1) Minimize the total number of locums used across all shifts.
2) Among those, minimize total_cost.
3) If still tied, choose the solution with the lexicographically smallest concatenation of all clinician_ids across all shifts (after sorting each shift’s clinician_ids, then concatenating shifts in shift_id order).

Output format (strict):
- If infeasible even with locums, print exactly: INFEASIBLE (one line only).
- Otherwise print exactly TWO lines:
Line 1: a Python-style list of dictionaries, one dictionary per shift, sorted by shift_id ascending.
Each dictionary must have exactly these keys in this order:
"shift_id", "department", "clinician_ids", "clinicians_assigned", "locums_used", "shift_cost"
Where:
- clinician_ids is a list of clinician_id strings sorted ascending.
- clinicians_assigned = len(clinician_ids)
- locums_used = number of clinician_ids that start with "LOC"
- shift_cost is rounded to 2 decimals
Line 2: a single string in this exact format:
LOCUMS_USED=<integer>;TOTAL_COST=<rounded_to_2_decimals>

Notes:
- Do not include any explanation, only the final outputs.

locums.csv schema (fixed):
clinician_id, department, hourly_cost, skill_level, available_date
Where clinician_id values always start with "LOC".

Grading rubric:
criterion: Line 2 is exactly: LOCUMS_USED=17;TOTAL_COST=35015.44 (strict match, no extra spaces).
weight: 5
criterion: Line 1 is a single Python-style list (starts with [ and ends with ]) and contains exactly 18 dictionaries (exactly 18 occurrences of {'shift_id':).
weight: 10
criterion: In line 1, the dictionary for Cardiology_S1 matches exactly this substring (including spacing and numeric formatting): {'shift_id': 'Cardiology_S1', 'department': 'Cardiology', 'clinician_ids': ['DOC0001', 'DOC0005', 'DOC0009'], 'clinicians_assigned': 3, 'locums_used': 0, 'shift_cost': 2365.60}
weight: 20
criterion: In line 1, the dictionary for Emergency_S3 matches exactly this substring (including spacing and numeric formatting): {'shift_id': 'Emergency_S3', 'department': 'Emergency', 'clinician_ids': ['LOC0037', 'LOC0067', 'LOC0068', 'LOC0072', 'LOC0084'], 'clinicians_assigned': 5, 'locums_used': 5, 'shift_cost': 4830.00}
weight: 20
criterion: In line 1, the dictionary for Neurology_S3 matches exactly this substring (including spacing and numeric formatting): {'shift_id': 'Neurology_S3', 'department': 'Neurology', 'clinician_ids': ['LOC0018', 'LOC0066'], 'clinicians_assigned': 2, 'locums_used': 2, 'shift_cost': 1709.60}
weight: 20
criterion: Line 1 contains exactly 18 occurrences of 'shift_cost': and every one is followed by a number with exactly two decimal places.
weight: 10
criterion: If line 1 is parsed as a Python literal list of dicts, then for every shift dict, clinicians_assigned equals len(clinician_ids) and locums_used equals the count of clinician_ids that start with 'LOC'.
weight: 5
criterion: If line 1 is parsed as a Python literal list of dicts, then the sum of locums_used across all 18 dicts equals the LOCUMS_USED value in line 2, and the sum of shift_cost across all 18 dicts equals the TOTAL_COST value in line 2 (both compared at exactly 2 decimals).
weight: 10

Golden answer:
# Python Code:
```python
from decimal import Decimal, ROUND_HALF_UP

import pandas as pd


def money_to_cents(x: str) -> int:
    d = Decimal(str(x)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
    return int((d * 100).to_integral_value(rounding=ROUND_HALF_UP))


def main() -> None:
    # -----------------------------
    # Load CSVs
    # -----------------------------
    patients = pd.read_csv(
        "patients.csv",
        dtype={
          "patient_id": str,
          "arrival_ts": str,
          "severity": str,
          "department": str},
    )
    doctors = pd.read_csv(
        "doctors.csv",
        dtype={
          "doctor_id": str,
          "department": str,
          "hourly_cost": str,
          "skill_level": "int64"},
    )
    shifts = pd.read_csv(
        "shifts.csv",
        dtype={"shift_id": str, "start_ts": str, "end_ts": str},
    )
    service_times = pd.read_csv(
        "service_times.csv",
        dtype={"severity": str, "mean_minutes": "int64"},
    )
    dept_constraints = pd.read_csv(
        "department_constraints.csv",
        dtype={
          "department": str,
          "max_wait_minutes": "int64",
          "min_skill": "int64"},
    )
    absences = pd.read_csv(
        "absences.csv",
        dtype={"doctor_id": str, "date": str},
    )
    locums = pd.read_csv(
        "locums.csv",
        dtype={
            "clinician_id": str,
            "department": str,
            "hourly_cost": str,
            "skill_level": "int64",
            "available_date": str,
        },
    )

    # -----------------------------
    # 1) Parse shifts (derive department/date/duration)
    # -----------------------------
    shifts["start_ts"] = pd.to_datetime(
      shifts["start_ts"],
      format="%Y-%m-%d %H:%M"
      )
    shifts["end_ts"] = pd.to_datetime(
      shifts["end_ts"],
      format="%Y-%m-%d %H:%M"
      )

    shifts["department"] = shifts["shift_id"].str.split(
      "_S",
      n=1,
      expand=True
      )[0]
    shifts["duration_minutes"] = (
        (shifts["end_ts"] - shifts["start_ts"])
        .dt.total_seconds().astype("int64") // 60
    )
    shifts["shift_date"] = shifts["start_ts"].dt.strftime("%Y-%m-%d")

    shifts = shifts[
        [
          "shift_id",
          "department",
          "start_ts",
          "end_ts",
          "duration_minutes",
          "shift_date"
          ]
    ].copy()

    # -----------------------------
    # 2) Lookups (service time, constraints, absences)
    # -----------------------------
    severity_to_minutes = dict(zip(
      service_times["severity"],
      service_times["mean_minutes"]
      ))
    dept_min_skill = dept_constraints.set_index("department")[
      "min_skill"
      ].to_dict()

    absent_by_date = absences.groupby("date")["doctor_id"].apply(set).to_dict()

    # -----------------------------
    # 3) Assign each patient to exactly one shift
    # -----------------------------
    patients["arrival_ts"] = pd.to_datetime(
      patients["arrival_ts"],
      format="%Y-%m-%d %H:%M"
      )
    patients["mean_minutes"] = patients["severity"].map(severity_to_minutes)
    if patients["mean_minutes"].isna().any():
        print("INFEASIBLE")
        return

    patients["shift_id"] = pd.NA

    for dept, grp in patients.groupby("department", sort=False):
        dept_shifts = shifts[shifts["department"] == dept].sort_values(
          "start_ts"
          )
        if dept_shifts.empty:
            print("INFEASIBLE")
            return

        idx = grp.index
        assigned = pd.Series(pd.NA, index=idx, dtype="object")

        for _, sh in dept_shifts.iterrows():
            in_window = (
                (patients.loc[idx, "arrival_ts"] >= sh["start_ts"])
                & (patients.loc[idx, "arrival_ts"] <= sh["end_ts"])
                & assigned.isna()
            )
            if in_window.any():
                assigned.loc[in_window[in_window].index] = sh["shift_id"]

        if assigned.isna().any():
            print("INFEASIBLE")
            return

        patients.loc[idx, "shift_id"] = assigned.values

    # -----------------------------
    # 4) Workload + clinicians_required per shift
    # -----------------------------
    workload = (
        patients.groupby("shift_id", as_index=False)["mean_minutes"]
        .sum()
        .rename(columns={"mean_minutes": "workload_minutes"})
    )

    shift_req = shifts.merge(workload, on="shift_id", how="left")
    shift_req["workload_minutes"] = shift_req[
      "workload_minutes"
      ].fillna(0).astype("int64")

    shift_req["clinicians_required"] = (
        (shift_req["workload_minutes"] + shift_req["duration_minutes"] - 1)
        // shift_req["duration_minutes"]
    ).astype("int64")
    shift_req["clinicians_required"] = shift_req[
      "clinicians_required"
      ].clip(lower=1)

    shift_req = shift_req.sort_values("shift_id").reset_index(drop=True)

    # -----------------------------
    # 5) Build eligible pools (regular + locums)
    # -----------------------------
    doctors["hourly_cost_cents"] = doctors["hourly_cost"].map(money_to_cents)
    locums["hourly_cost_cents"] = locums["hourly_cost"].map(money_to_cents)

    chosen_by_dept = {}

    for dept, grp in shift_req.groupby("department", sort=False):
        date0 = grp["shift_date"].iloc[0]
        needed = int(grp["clinicians_required"].sum())
        min_skill = int(dept_min_skill.get(dept, 10**9))

        absent = absent_by_date.get(date0, set())

        eligible_regular = doctors[
            (doctors["department"] == dept)
            & (doctors["skill_level"] >= min_skill)
            & (~doctors["doctor_id"].isin(absent))
        ].copy()

        eligible_locums = locums[
            (locums["department"] == dept)
            & (locums["skill_level"] >= min_skill)
            & (locums["available_date"] == date0)
        ].copy()

        eligible_regular = eligible_regular.sort_values(
            ["hourly_cost_cents", "doctor_id"], ascending=[True, True]
        )
        eligible_locums = eligible_locums.sort_values(
            ["hourly_cost_cents", "clinician_id"], ascending=[True, True]
        )

        if len(eligible_regular) + len(eligible_locums) < needed:
            print("INFEASIBLE")
            return

        # Objective: (1) minimize locums, (2) minimize cost, (3) smallest IDs
        if len(eligible_regular) >= needed:
            chosen = (
                eligible_regular.head(needed)[[
                  "doctor_id",
                  "hourly_cost_cents"
                  ]]
                .rename(columns={"doctor_id": "clinician_id"})
                .copy()
            )
        else:
            chosen_regular = eligible_regular[[
              "doctor_id",
              "hourly_cost_cents"
              ]].rename(
                columns={"doctor_id": "clinician_id"}
            )
            locums_needed = needed - len(chosen_regular)
            chosen_locums = eligible_locums.head(locums_needed)[
                ["clinician_id", "hourly_cost_cents"]
            ].copy()
            chosen = pd.concat([
              chosen_regular,
              chosen_locums
              ], ignore_index=True)

        chosen_by_dept[dept] = chosen

    # -----------------------------
    # 6) Assign clinicians to shifts (shift_id order) + compute costs
    # -----------------------------
    plan_rows = []
    total_cost = Decimal("0.00")

    for dept, grp in shift_req.groupby("department", sort=False):
        dept_shifts = grp.sort_values("shift_id")
        chosen = chosen_by_dept[dept].copy()

        clinician_ids = sorted(chosen["clinician_id"].tolist())
        cost_cents_by_id = dict(
            zip(
              chosen["clinician_id"],
              chosen["hourly_cost_cents"].astype("int64"))
        )

        cursor = 0
        for _, sh in dept_shifts.iterrows():
            k = int(sh["clinicians_required"])
            assigned_ids = clinician_ids[cursor: cursor + k]
            cursor += k

            hours = Decimal(int(sh["duration_minutes"])) / Decimal(60)

            shift_cost = Decimal("0.00")
            for cid in assigned_ids:
                shift_cost += (Decimal(int(
                  cost_cents_by_id[cid]
                  )) / Decimal(100)) * hours
            shift_cost = shift_cost.quantize(
              Decimal("0.01"),
              rounding=ROUND_HALF_UP
              )

            total_cost += shift_cost

            plan_rows.append(
                {
                    "shift_id": sh["shift_id"],
                    "department": sh["department"],
                    "clinician_ids": sorted(assigned_ids),
                    "clinicians_assigned": len(assigned_ids),
                    "locums_used":
                    sum(1 for x in assigned_ids if x.startswith("LOC")),
                    "shift_cost": shift_cost,  # keep Decimal
                }
            )

    # -----------------------------
    # 7) Output (strict)
    # -----------------------------
    def format_plan_row(row: dict) -> str:
        shift_cost_str = f"{row['shift_cost']:.2f}"  # exactly 2 decimals
        return (
            "{'shift_id': " + repr(row["shift_id"])
            + ", 'department': " + repr(row["department"])
            + ", 'clinician_ids': " + repr(row["clinician_ids"])
            + ", 'clinicians_assigned': " + str(row["clinicians_assigned"])
            + ", 'locums_used': " + str(row["locums_used"])
            + ", 'shift_cost': " + shift_cost_str
            + "}"
        )

    plan_rows = sorted(plan_rows, key=lambda x: x["shift_id"])
    locums_used_total = sum(r["locums_used"] for r in plan_rows)
    total_cost = total_cost.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

    line1 = "[" + ", ".join(format_plan_row(r) for r in plan_rows) + "]"
    print(line1)
    print(f"LOCUMS_USED={locums_used_total};TOTAL_COST={total_cost:.2f}")


if __name__ == "__main__":
    main()

```
# Explanation: 
The staffing plan is obtained by
- Loading the seven CSV files and parsing timestamps in `patients.csv` and `shifts.csv` using the fixed `YYYY-MM-DD HH:MM` format;
- Deriving each shift’s department from `shift_id`, computing `shift_date` from `start_ts`, and computing `shift_duration_minutes` as `(end_ts - start_ts)` in minutes;
- Assigning every patient to exactly one eligible shift in the same department whose time window contains the patient’s `arrival_ts` (choosing the earliest shift when multiple windows overlap); if any patient cannot be assigned, returning `INFEASIBLE`;
- Mapping each patient’s `severity` to `mean_minutes` using `service_times.csv`, summing `mean_minutes` per `shift_id` to get `workload_minutes`, and computing `clinicians_required = ceil(workload_minutes / shift_duration_minutes)` with a minimum of 1 clinician per shift;
- Building the eligible clinician pools per department and shift date by applying all constraints:
  - department match for all clinicians,
  - removing absent regular doctors using `absences.csv` for that date,
  - allowing only locums whose `available_date` matches the shift date,
  - filtering all clinicians by minimum skill using `department_constraints.csv`;
  if the combined eligible pool cannot cover the required clinicians, returning `INFEASIBLE`;
- Selecting clinicians to satisfy the strict objective by first taking as many eligible regular doctors as possible (to minimize locums), then filling any remaining required slots with the cheapest eligible locums, breaking cost ties by the smallest clinician IDs;
- Assigning the chosen clinicians to shifts in `shift_id` order, taking exactly `clinicians_required` clinicians per shift (and ensuring each clinician works at most one shift overall);
- Computing each shift’s cost as the sum over assigned clinicians of `hourly_cost × (shift_duration_minutes / 60)`, rounding each printed `shift_cost` to exactly 2 decimals with `ROUND_HALF_UP`, summing shift costs to get `TOTAL_COST` (also rounded to 2 decimals), and printing:
  - Line 1: the list of per-shift dictionaries sorted by `shift_id`,
  - Line 2: `LOCUMS_USED=<integer>;TOTAL_COST=<two_decimals>`.

# GTFA: 
[{'shift_id': 'Cardiology_S1', 'department': 'Cardiology', 'clinician_ids': ['DOC0001', 'DOC0005', 'DOC0009'], 'clinicians_assigned': 3, 'locums_used': 0, 'shift_cost': 2365.60}, {'shift_id': 'Cardiology_S2', 'department': 'Cardiology', 'clinician_ids': ['DOC0011', 'DOC0029', 'DOC0048'], 'clinicians_assigned': 3, 'locums_used': 0, 'shift_cost': 2066.72}, {'shift_id': 'Cardiology_S3', 'department': 'Cardiology', 'clinician_ids': ['LOC0010', 'LOC0048', 'LOC0070'], 'clinicians_assigned': 3, 'locums_used': 3, 'shift_cost': 2758.88}, {'shift_id': 'Emergency_S1', 'department': 'Emergency', 'clinician_ids': ['DOC0004', 'DOC0017', 'DOC0025', 'DOC0053'], 'clinicians_assigned': 4, 'locums_used': 0, 'shift_cost': 2690.72}, {'shift_id': 'Emergency_S2', 'department': 'Emergency', 'clinician_ids': ['LOC0004', 'LOC0020', 'LOC0025', 'LOC0034'], 'clinicians_assigned': 4, 'locums_used': 4, 'shift_cost': 3568.64}, {'shift_id': 'Emergency_S3', 'department': 'Emergency', 'clinician_ids': ['LOC0037', 'LOC0067', 'LOC0068', 'LOC0072', 'LOC0084'], 'clinicians_assigned': 5, 'locums_used': 5, 'shift_cost': 4830.00}, {'shift_id': 'Neurology_S1', 'department': 'Neurology', 'clinician_ids': ['DOC0021', 'DOC0022'], 'clinicians_assigned': 2, 'locums_used': 0, 'shift_cost': 1288.24}, {'shift_id': 'Neurology_S2', 'department': 'Neurology', 'clinician_ids': ['DOC0039', 'DOC0041'], 'clinicians_assigned': 2, 'locums_used': 0, 'shift_cost': 1269.44}, {'shift_id': 'Neurology_S3', 'department': 'Neurology', 'clinician_ids': ['LOC0018', 'LOC0066'], 'clinicians_assigned': 2, 'locums_used': 2, 'shift_cost': 1709.60}, {'shift_id': 'Oncology_S1', 'department': 'Oncology', 'clinician_ids': ['DOC0003', 'DOC0030'], 'clinicians_assigned': 2, 'locums_used': 0, 'shift_cost': 1516.16}, {'shift_id': 'Oncology_S2', 'department': 'Oncology', 'clinician_ids': ['LOC0028'], 'clinicians_assigned': 1, 'locums_used': 1, 'shift_cost': 994.24}, {'shift_id': 'Oncology_S3', 'department': 'Oncology', 'clinician_ids': ['LOC0033', 'LOC0065'], 'clinicians_assigned': 2, 'locums_used': 2, 'shift_cost': 2056.88}, {'shift_id': 'Orthopedics_S1', 'department': 'Orthopedics', 'clinician_ids': ['DOC0032', 'DOC0035'], 'clinicians_assigned': 2, 'locums_used': 0, 'shift_cost': 1293.12}, {'shift_id': 'Orthopedics_S2', 'department': 'Orthopedics', 'clinician_ids': ['DOC0047', 'DOC0050'], 'clinicians_assigned': 2, 'locums_used': 0, 'shift_cost': 1415.68}, {'shift_id': 'Orthopedics_S3', 'department': 'Orthopedics', 'clinician_ids': ['DOC0055', 'DOC0060'], 'clinicians_assigned': 2, 'locums_used': 0, 'shift_cost': 1115.04}, {'shift_id': 'Pediatrics_S1', 'department': 'Pediatrics', 'clinician_ids': ['DOC0002', 'DOC0013'], 'clinicians_assigned': 2, 'locums_used': 0, 'shift_cost': 1319.52}, {'shift_id': 'Pediatrics_S2', 'department': 'Pediatrics', 'clinician_ids': ['DOC0014', 'DOC0046'], 'clinicians_assigned': 2, 'locums_used': 0, 'shift_cost': 1384.40}, {'shift_id': 'Pediatrics_S3', 'department': 'Pediatrics', 'clinician_ids': ['DOC0049', 'DOC0052'], 'clinicians_assigned': 2, 'locums_used': 0, 'shift_cost': 1372.56}]
LOCUMS_USED=17;TOTAL_COST=35015.44