<reasoning_content>
*   Target: Refactor a job queue into three composable classes in `solution.py`.
    *   Class 1: `Job` (dataclass). Fields: `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.
    *   Class 2: `RetryPolicy` (retry logic).
        *   `__init__(self, max_attempts: int = 4)`.
        *   `run(self, fn: Callable, data) -> tuple[bool, int]`.
        *   Retries on exception until success or `max_attempts`.
        *   *Requirement:* Do NOT actually sleep.
        *   Return type: `(success, attempts_made)`.
    *   Class 3: `JobQueue` (orchestrator).
        *   `add_job(self, job_id: str, data: dict, priority: int = 0) -> None`.
        *   `process_job(self, job_id: str, processor: Callable) -> bool`.
        *   *Requirement:* Must use `RetryPolicy` (composition).
        *   `get_next_job(self) -> tuple[str, dict] | None`.
        *   Priority logic: Highest priority first, FIFO within same priority.
    *   Constraints: Standard library only. No explanations.

    *   `Job`:
        ```python
        from dataclasses import dataclass
        from typing import Callable

        @dataclass
        class Job:
            id: str
            data: dict
            priority: int = 0
            retries: int = 0
        ```

    *   `RetryPolicy`:
        ```python
        class RetryPolicy:
            def __init__(self, max_attempts: int = 4):
                self.max_attempts = max_attempts

            def run(self, fn: Callable, data: dict) -> tuple[bool, int]:
                attempts_made = 0
                while attempts_made < self.max_attempts:
                    try:
                        fn(data)
                        return True, attempts_made + 1
                    except Exception:
                        attempts_made += 1
                return False, attempts_made
        ```

    *   `JobQueue`:
        *   Needs to store jobs. A list of jobs or a priority queue.
        *   FIFO within priority: `heapq` is good for priority, but standard `heapq` is Min-Heap. High priority usually means larger number, but for `heapq`, we need to be careful. If high priority = higher number, use negative.
        *   Actually, `get_next_job` needs to return highest priority.
        *   If we use a list and sort, or just keep it sorted.
        *   Let's use a list of Jobs and sort it or maintain it sorted.
        *   Wait, `heapq` is best for "highest priority".
        *   To handle FIFO within same priority with `heapq`, we can use an incrementing counter.
        *   Requirement: `get_next_job` returns `tuple[str, dict] | None`.

    *   *Refining `JobQueue`:*
        ```python
        import heapq
        from dataclasses import field

        class JobQueue:
            def __init__(self):
                self.jobs = [] # List of tuples: (-priority, entry_count, job_id, data)
                self.counter = 0
                self.retry_policy = RetryPolicy()
                self.jobs_map = {} # To find job by id for process_job?

            # Actually, the prompt says:
            # add_job(job_id, data, priority)
            # process_job(job_id, processor)
            # get_next_job() -> (id, data) or None

            # If I have jobs in a heap, how do I "process_job" by ID?
            # Usually, "process_job" removes it or marks it.
            # The prompt doesn't say "remove" for process_job, but usually it implies execution.
            # Let's look at the signature: process_job(self, job_id: str, processor: Callable) -> bool
        ```

        *Re-reading `JobQueue` requirements:*
        1. `add_job`: adds to queue.
        2. `process_job`: must use `RetryPolicy`.
        3. `get_next_job`: highest priority, then FIFO.

        Wait, if `process_job` takes an ID, and `get_next_job` takes nothing, the queue needs to store jobs such that they can be accessed by ID *and* sorted by priority.

        Let's store jobs in a dictionary `self.jobs = {}`.
        And a list for the priority queue.

        Actually, the simplest way to handle "Highest priority, FIFO" is a list of `(priority, insertion_order, job_id)`.
        But `process_job` needs the data.

        Let's refine `JobQueue` structure:
        `self.jobs = {}` (mapping `id` -> `Job`)
        `self.queue = []` (heap of `(-priority, count, job_id))`)

        ```python
        import heapq
        from dataclasses import dataclass
        from typing import Callable

        @dataclass
        class Job:
            id: str
            data: dict
            priority: int = 0
            retries: int = 0

        class RetryPolicy:
            def __init__(self, max_attempts: int = 4):
                self.max_attempts = max_attempts

            def run(self, fn: Callable, data: dict) -> tuple[bool, int]:
                attempts = 0
                while attempts < self.max_attempts:
                    try:
                        fn(data)
                        return True, attempts + 1
                    except Exception:
                        attempts += 1
                return False, attempts

        class JobQueue:
            def __init__(self):
                self.jobs = {} # id -> Job
                self.queue = [] # heap
                self.counter = 0
                self.retry_policy = RetryPolicy()

            def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
                job = Job(id=job_id, data=data, priority=priority)
                self.jobs[job_id] = job
                # Use negative priority for max-heap behavior using heapq (min-heap)
                heapq.heappush(self.queue, (-priority, self.counter, job_id))
                self.counter += 1

            def process_job(self, job_id: str, processor: Callable) -> bool:
                job = self.jobs.get(job_id)
                if not job:
                    return False
                success, _ = self.retry_policy.run(processor, job.data)
                return success

            def get_next_job(self) -> tuple[str, dict] | None:
                if not self.queue:
                    return None
                neg_priority, count, job_id = heapq.heappop(self.queue)
                job = self.jobs.get(job_id)
                if job:
                    return (job.id, job.data)
                return None
        ```

    *   `Job` dataclass: Correct.
    *   `RetryPolicy`: `run` returns `(success, attempts_made)`. Correct. `max_attempts` check. No sleep. Correct.
    *   `JobQueue`:
        *   `add_job`: Correct.
        *   `process_job`: Uses `RetryPolicy`. Correct.
        *   `get_next_job`: Highest priority (negative value in heap), FIFO (counter). Correct.

    Wait, if `process_job` is called, should it remove from the queue? The prompt doesn't specify. Usually, `process_job` implies execution, but `get_next_job` implies fetching.
    If I call `get_next_job`, it pops from heap.
    If I call `process_job`, it just runs it.
    The prompt says `process_job` takes a `job_id`.
    If `get_next_job` pops it, `process_job` might fail if it's already popped.
    However, the prompt doesn't say "remove it" in `process_job`.
    Actually, usually, in these coding challenges, `get_next_job` is the way to consume it. If `process_job` is called, it's just an action on a specific ID.
    I will keep it simple: `process_job` doesn't remove from the queue/dict because the prompt doesn't say to. But `get_next_job` pops it from the priority heap.

    Wait, if `get_next_job` pops it from the priority queue, then `process_job` might be called on a job that is no longer "next". That's fine.

    One detail: `process_job` return type is `bool`. `RetryPolicy.run` returns `(success, attempts_made)`.
    `process_job` needs to return `bool`.
    `success, _ = self.retry_policy.run(processor, job.data)`
    `return success`. Correct.

    One more check: "Highest-priority job's (id, data); FIFO order within the same priority."
    Heap logic: `(-priority, counter, job_id)`.
    If priorities are equal, `counter` determines order (smaller counter = earlier insertion).
    This perfectly satisfies FIFO for same priority.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable
import heapq

@dataclass
class Job:
    id: str
    data: dict
    priority: int = 0
    retries: int = 0

class RetryPolicy:
    def __init__(self, max_attempts: int = 4):
        self.max_attempts = max_attempts

    def run(self, fn: Callable, data: dict) -> tuple[bool, int]:
        attempts_made = 0
        while attempts_made < self.max_attempts:
            try:
                fn(data)
                return True, attempts_made + 1
            except Exception:
                attempts_made += 1
        return False, attempts_made

class JobQueue:
    def __init__(self):
        self.jobs = {}
        self.queue = []
        self.counter = 0
        self.retry_policy = RetryPolicy()

    def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
        job = Job(id=job_id, data=data, priority=priority)
        self.jobs[job_id] = job
        # Use negative priority to simulate max-heap with heapq (min-heap)
        # Counter ensures FIFO order for jobs with the same priority
        heapq.heappush(self.queue, (-priority, self.counter, job_id))
        self.counter += 1

    def process_job(self, job_id: str, processor: Callable) -> bool:
        job = self.jobs.get(job_id)
        if not job:
            return False
        
        success, _ = self.retry_policy.run(processor, job.data)
        return success

    def get_next_job(self) -> tuple[str, dict] | None:
        if not self.queue:
            return None
        
        _, _, job_id = heapq.heappop(self.queue)
        job = self.jobs.get(job_id)
        if job:
            return (job.id, job.data)
        return None
```
</content>