<- 3.1 Agent PAL core demos

[3.3.3] pal_core_03_allocate.py (BINGO) 26.0411 (03 resources / allocation)


drones


Next logical step: pal_core_03_allocate.py
Because now the story becomes:
1.	Detect problem 
2.	Predict impact 
3.	Allocate resources to respond 
That is a beautiful trilogy.

This should be:
given tasks, priorities, skills, and limited resources, assign the best resources to the most important tasks.
Best v1 is fully deterministic.
Clean v1 concept
Inputs:
•	tasks 
•	workers/resources 
•	priority 
•	required skill 
•	duration 
System computes:
•	best assignments 
•	unassigned tasks 
•	utilization 
•	weighted score 
Good civilian analogy: Field service dispatch:
•	tasks: deliveries, repairs, inspections 
•	workers: people with different skills 
•	allocate scarce workers to highest-value tasks 
Suggested commands
python pal_core_03_allocate.py demo
python pal_core_03_allocate.py status
python pal_core_03_allocate.py reset
Output should show
•	assigned tasks 
•	unassigned tasks 
•	total priority completed 
•	resource usage 
Minimal example
Tasks:
•	T001 repair, priority 10 
•	T002 delivery, priority 6 
•	T003 inspect, priority 4 
Workers:
•	W001 repair, capacity 1 
•	W002 delivery/inspect, capacity 2 
Then compute best allocation.
Why this is a strong third demo
It completes the trilogy:
•	detect what is happening 
•	predict what disruption causes 
•	allocate what to do next 
#549b pal_core_03_allocate.py
#
# PAL Core 03 - Allocate
#
# Commands:
#   1) demo   -> load demo tasks/workers and show allocation
#   2) status -> show current state and allocation
#   3) reset  -> reset to demo state and show allocation
#
# Examples:
#   python pal_core_03_allocate.py demo
#   python pal_core_03_allocate.py status
#   python pal_core_03_allocate.py reset
#
# Notes:
# - Deterministic only
# - No AI needed in v1
# - Greedy priority-based allocator
# - Assigns limited workers to highest-priority tasks they can perform

import json
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

# --------------------------------------------------
# 1 FILES / CONSTANTS
# --------------------------------------------------
STATE_FILE = Path("pal_core_allocate_state.json")

DEMO_STATE = {
    "tasks": [
        {
            "task_id": "T001",
            "type": "repair",
            "priority": 10,
            "duration": 2,
            "required_skill": "repair",
            "location": "Site_A",
        },
        {
            "task_id": "T002",
            "type": "delivery",
            "priority": 6,
            "duration": 1,
            "required_skill": "delivery",
            "location": "Site_B",
        },
        {
            "task_id": "T003",
            "type": "inspection",
            "priority": 4,
            "duration": 1,
            "required_skill": "inspect",
            "location": "Site_C",
        },
        {
            "task_id": "T004",
            "type": "repair",
            "priority": 8,
            "duration": 2,
            "required_skill": "repair",
            "location": "Site_D",
        },
        {
            "task_id": "T005",
            "type": "delivery",
            "priority": 3,
            "duration": 1,
            "required_skill": "delivery",
            "location": "Site_E",
        },
    ],
    "workers": [
        {
            "worker_id": "W001",
            "skills": ["repair"],
            "capacity": 2,
            "location": "Depot_1",
        },
        {
            "worker_id": "W002",
            "skills": ["delivery", "inspect"],
            "capacity": 2,
            "location": "Depot_2",
        },
        {
            "worker_id": "W003",
            "skills": ["repair", "inspect"],
            "capacity": 1,
            "location": "Depot_3",
        },
    ],
}

# --------------------------------------------------
# 2 HELPERS
# --------------------------------------------------
def load_state() -> Dict[str, Any]:
    if not STATE_FILE.exists():
        return json.loads(json.dumps(DEMO_STATE))
    return json.loads(STATE_FILE.read_text(encoding="utf-8"))

def save_state(state: Dict[str, Any]) -> None:
    STATE_FILE.write_text(
        json.dumps(state, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )

def reset_state() -> Dict[str, Any]:
    state = json.loads(json.dumps(DEMO_STATE))
    save_state(state)
    return state

def print_usage() -> None:
    print(
        "Usage:\n"
        "  python pal_core_03_allocate.py demo\n"
        "  python pal_core_03_allocate.py status\n"
        "  python pal_core_03_allocate.py reset\n\n"
        "Examples:\n"
        "  python pal_core_03_allocate.py demo\n"
        "  python pal_core_03_allocate.py status\n"
        "  python pal_core_03_allocate.py reset"
    )

def worker_can_do_task(worker: Dict[str, Any], task: Dict[str, Any], remaining_capacity: int) -> bool:
    return (
        task["required_skill"] in worker["skills"]
        and remaining_capacity >= int(task["duration"])
    )

def sort_tasks(tasks: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    # Highest priority first, then shortest duration first, then task_id
    return sorted(
        tasks,
        key=lambda t: (-int(t["priority"]), int(t["duration"]), t["task_id"])
    )

def find_best_worker_for_task(
    workers: List[Dict[str, Any]],
    remaining_capacity: Dict[str, int],
    task: Dict[str, Any],
) -> Optional[Dict[str, Any]]:
    candidates: List[Dict[str, Any]] = []

    for w in workers:
        wid = w["worker_id"]
        rem = remaining_capacity.get(wid, 0)
        if worker_can_do_task(w, task, rem):
            candidates.append(w)

    if not candidates:
        return None

    # Choose worker with:
    # 1) smallest adequate remaining capacity after assignment
    # 2) fewest skills (more specialized first)
    # 3) worker_id
    def score(w: Dict[str, Any]) -> Tuple[int, int, str]:
        rem = remaining_capacity[w["worker_id"]]
        post = rem - int(task["duration"])
        return (post, len(w["skills"]), w["worker_id"])

    return sorted(candidates, key=score)[0]

def allocate(state: Dict[str, Any]) -> Dict[str, Any]:
    tasks = sort_tasks(state["tasks"])
    workers = list(state["workers"])

    remaining_capacity: Dict[str, int] = {
        w["worker_id"]: int(w["capacity"]) for w in workers
    }

    assignments: List[Dict[str, Any]] = []
    unassigned: List[Dict[str, Any]] = []

    for task in tasks:
        best_worker = find_best_worker_for_task(workers, remaining_capacity, task)

        if best_worker is None:
            unassigned.append({  ############22222222222222
                "task_id": task["task_id"],
                "type": task["type"],
                "priority": task["priority"],
                "duration": task["duration"],
                "required_skill": task["required_skill"],
                "location": task["location"],
                "reason": "no_available_worker_with_skill_and_capacity",
            })
            continue

        wid = best_worker["worker_id"]
        remaining_capacity[wid] -= int(task["duration"])

        assignments.append({  ###########1111111111111111
            "task_id": task["task_id"],
            "task_type": task["type"],
            "priority": task["priority"],
            "duration": task["duration"],
            "required_skill": task["required_skill"],
            "task_location": task["location"],
            "worker_id": wid,
            "worker_skills": best_worker["skills"],
            "worker_location": best_worker["location"],
        })

    worker_utilization: List[Dict[str, Any]] = []  ###########7777777777
    for w in workers:
        wid = w["worker_id"]
        cap = int(w["capacity"])
        rem = remaining_capacity[wid]
        used = cap - rem
        util = round((used / cap), 2) if cap > 0 else 0.0
        worker_utilization.append({
            "worker_id": wid,
            "capacity": cap,
            "used": used,
            "remaining": rem,
            "utilization": util,
        })

    #############33333333333333333-----6666666666666666
    total_priority_completed = sum(int(a["priority"]) for a in assignments)
    total_priority_unassigned = sum(int(t["priority"]) for t in unassigned)
    assigned_task_count = len(assignments)
    unassigned_task_count = len(unassigned)

    return {   #############11111111111---777777777777777777777
        "assignments": assignments,
        "unassigned_tasks": unassigned,
        "assigned_task_count": assigned_task_count,
        "unassigned_task_count": unassigned_task_count,
        "total_priority_completed": total_priority_completed,
        "total_priority_unassigned": total_priority_unassigned,
        "worker_utilization": worker_utilization,
    }

def print_state_and_allocation(state: Dict[str, Any]) -> None:
    print("=== ALLOCATION STATE ===")
    print(json.dumps(state, indent=2, ensure_ascii=False))

    print("\n=== ALLOCATION RESULT ===")
    print(json.dumps(allocate(state), indent=2, ensure_ascii=False))

# --------------------------------------------------
# 3 COMMANDS
# --------------------------------------------------
def cmd_demo() -> None:
    state = reset_state()
    print("DEMO OK")
    print(f"Saved state to: {STATE_FILE.resolve()}")
    print_state_and_allocation(state)

def cmd_status() -> None:
    state = load_state()
    print_state_and_allocation(state)

def cmd_reset() -> None:
    state = reset_state()
    print("RESET OK")
    print(f"Saved state to: {STATE_FILE.resolve()}")
    print_state_and_allocation(state)

# --------------------------------------------------
# 4 MAIN
# --------------------------------------------------
def main() -> None:
    if len(sys.argv) < 2:
        print_usage()
        return

    command = sys.argv[1].strip().lower()

    if command == "demo":
        cmd_demo()
    elif command == "status":
        cmd_status()
    elif command == "reset":
        cmd_reset()
    else:
        print(f"Unknown command: {command}\n")
        print_usage()

if __name__ == "__main__":
    main()

See docx 603 for more demos.

python pal_core_03_allocate.py demo

$ python pal_core_03_allocate.py demo
DEMO OK
Saved state to: C:\Users\terry\Downloads\d1_agent\pal_core_allocate_state.json
=== ALLOCATION STATE ===
{
  "tasks": [
    {
      "task_id": "T001",
      "type": "repair",
      "priority": 10,
      "duration": 2,
      "required_skill": "repair",
      "location": "Site_A"
    },
    {
      "task_id": "T002",
      "type": "delivery",
      "priority": 6,
      "duration": 1,
      "required_skill": "delivery",
      "location": "Site_B"
    },
    {
      "task_id": "T003",
      "type": "inspection",
      "priority": 4,
      "duration": 1,
      "required_skill": "inspect",
      "location": "Site_C"
    },
    {
      "task_id": "T004",
      "type": "repair",
      "priority": 8,
      "duration": 2,
      "required_skill": "repair",
      "location": "Site_D"
    },
    {
      "task_id": "T005",
      "type": "delivery",
      "priority": 3,
      "duration": 1,
      "required_skill": "delivery",
      "location": "Site_E"
    }
  ],
  "workers": [
    {
      "worker_id": "W001",
      "skills": [
        "repair"
      ],
      "capacity": 2,
      "location": "Depot_1"
    },
    {
      "worker_id": "W002",
      "skills": [
        "delivery",
        "inspect"
      ],
      "capacity": 2,
      "location": "Depot_2"
    },
    {
      "worker_id": "W003",
      "skills": [
        "repair",
        "inspect"
      ],
      "capacity": 1,
      "location": "Depot_3"
    }
  ]
}

=== ALLOCATION RESULT ===
{
  "assignments": [
    {
      "task_id": "T001",
      "task_type": "repair",
      "priority": 10,
      "duration": 2,
      "required_skill": "repair",
      "task_location": "Site_A",
      "worker_id": "W001",
      "worker_skills": [
        "repair"
      ],
      "worker_location": "Depot_1"
    },
    {
      "task_id": "T002",
      "task_type": "delivery",
      "priority": 6,
      "duration": 1,
      "required_skill": "delivery",
      "task_location": "Site_B",
      "worker_id": "W002",
      "worker_skills": [
        "delivery",
        "inspect"
      ],
      "worker_location": "Depot_2"
    },
    {
      "task_id": "T003",
      "task_type": "inspection",
      "priority": 4,
      "duration": 1,
      "required_skill": "inspect",
      "task_location": "Site_C",
      "worker_id": "W002",
      "worker_skills": [
        "delivery",
        "inspect"
      ],
      "worker_location": "Depot_2"
    }
  ],
  "unassigned_tasks": [
    {
      "task_id": "T004",
      "type": "repair",
      "priority": 8,
      "duration": 2,
      "required_skill": "repair",
      "location": "Site_D",
      "reason": "no_available_worker_with_skill_and_capacity"
    },
    {
      "task_id": "T005",
      "type": "delivery",
      "priority": 3,
      "duration": 1,
      "required_skill": "delivery",
      "location": "Site_E",
      "reason": "no_available_worker_with_skill_and_capacity"
    }
  ],
  "assigned_task_count": 3,
  "unassigned_task_count": 2,
  "total_priority_completed": 20,
  "total_priority_unassigned": 11,
  "worker_utilization": [
    {
      "worker_id": "W001",
      "capacity": 2,
      "used": 2,
      "remaining": 0,
      "utilization": 1.0
    },
    {
      "worker_id": "W002",
      "capacity": 2,
      "used": 2,
      "remaining": 0,
      "utilization": 1.0
    },
    {
      "worker_id": "W003",
      "capacity": 1,
      "used": 0,
      "remaining": 1,
      "utilization": 0.0
    }
  ]
}
(venv) 
terry@LAPTOP-HKPDHF7M MINGW64 ~/Downloads/d1_agent (main)
You now built 3 real PAL core demos
1. Detect
pal_core_01_detect_ids.py
Find meaningful event constellations and generate alerts.
2. Predict
pal_core_02_predict.py
Simulate downstream effects of disruptions.
3. Allocate
pal_core_03_allocate.py
Assign scarce resources to prioritized tasks.
________________________________________
Why this is important
This is a strong conceptual framework for operational AI:
Observe -> Understand -> Act
or
Detect -> Predict -> Allocate
That is much deeper than chatbot demos.
________________________________________
My observation on Allocate result
The allocator behaved logically:
•	highest priorities assigned first 
•	capacities respected 
•	some tasks left unassigned 
•	utilization shown 
This is real scheduling behavior.
________________________________________
One interesting insight
Worker W003 remained unused because of the greedy strategy and capacity constraints.
That is actually educational.
It shows:
A working allocator is not necessarily an optimal allocator.
Which leads to future versions:
•	smarter search 
•	global optimization 
•	travel cost 
•	fairness 
•	load balancing 
Excellent teaching point.

Natural next steps (optional): 
Add AI at the edges
Use OpenAI to:
•	ingest messy reports into structured events 
•	explain why an alert happened 
•	summarize simulation results 
•	justify allocations 
Add a simple UI
FastAPI or Streamlit dashboard.
Add linked workflow
Detect issue → Predict impact → Allocate response.


26.0618