<reasoning_content>
*   Task: Refactor a job queue into three composable classes: `Job`, `RetryPolicy`, and `JobQueue`.
    *    Language: Python.
    *    Constraints:
        *   Use `dataclasses` for `Job`.
        *   `RetryPolicy` handles retries with backoff (logic-only, no actual sleep).
        *   `JobQueue` orchestrates `Job` using `RetryPolicy`.
        *   `JobQueue` must support priority (highest first) and FIFO for same priority.
        *   Use only standard library.
        *   Output: Only Python code, no explanations.

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

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

    *   `RetryPolicy` class:
        *   `__init__(self, max_attempts: int = 4)`
        *   `run(self, fn: Callable, data) -> tuple[bool, int]`
        *   Logic: Try `fn(data)`. If exception, retry up to `max_attempts`. Return `(success, count)`.
        *   Note: "Do NOT actually sleep".

    *   `JobQueue` class:
        *   `add_job(self, job_id: str, data: dict, priority: int = 0) -> None`
        *   `process_job(self, job_id: str, processor: Callable) -> bool`
        *   `get_next_job(self) -> tuple[str, dict] | None`
        *   Internal storage: Need to sort by priority (desc) and then entry order (FIFO).
        *   Priority order: Highest priority first.
        *   Composition: `process_job` must use `RetryPolicy`.

    *   *Priority Queue implementation:*
        Standard library `heapq` is the best way to handle priority queues.
        `heapq` is a min-heap. To get highest priority first, we can use negative priority.
        To handle FIFO for same priority, we need a counter.
        Entry: `(-priority, counter, job_id, data)`.

    *   *RetryPolicy logic:*
        ```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 = 0
                while attempts < self.max_attempts:
                    try:
                        fn(data)
                        return True, attempts + 1
                    except Exception:
                        attempts += 1
                return False, attempts
        ```

    *   *JobQueue logic:*
        Need to store jobs. If `process_job` is called, it should find the job, run it via `RetryPolicy`, and then remove it? Wait, `process_job` usually implies it's being executed now.
        `get_next_job` returns the highest priority.
        The prompt says:
        `add_job(self, job_id: str, data: dict, priority: int = 0) -> None`
        `process_job(self, job_id: str, processor: Callable) -> bool`
        `get_next_job(self) -> tuple[str, dict] | None`

        Wait, if `process_job` takes a `job_id`, how does it know which job it is? It needs to find it in the internal storage.
        If `get_next_job` is called, does it remove the job? Usually, yes.

        Let's look at the requirements again.
        `JobQueue` must use `RetryPolicy`.
        So `process_job` finds the job with `job_id`, gets its `data`, and calls `retry_policy.run(processor, data)`.

        Let's refine the storage:
        A dictionary `self.jobs = {}` to store `Job` objects by `id`.
        To handle "highest priority" + "FIFO", I'll maintain a list of IDs sorted by `(-priority, sequence_number)`. Or just keep a list of `Job` objects and sort/pop?
        Actually, `heapq` is cleaner.

    *   *Refined Class Definitions:*
        `Job` dataclass.
        `RetryPolicy` class.
        `JobQueue` class.

    *   *Implementation Details:*
        `JobQueue` needs a `RetryPolicy` instance.
        `JobQueue.add_job`: create `Job`, store it, add to heap.
        `JobQueue.get_next_job`: pop from heap.
        `JobQueue.process_job`: find job in dictionary, run via `RetryPolicy`. If successful, maybe remove from dictionary? The prompt doesn't specify deletion, but usually, a processed job is removed. However, the signature `process_job(self, job_id: str, ...)` suggests it's looking up a specific ID.

    Wait, if `process_job` uses the `job_id`, it implies the job might still be in the queue or in a "processed" list.
    The prompt says "orchestrates Jobs using RetryPolicy".
    "process_job ... must USE RetryPolicy".

    Let's structure `JobQueue`:
    ```python
    import heapq
    from dataclasses import dataclass
    from typing import Callable, Dict, List, Tuple, Optional

    @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: Dict[str, Job] = {}
            self.heap: List[tuple[int, int, str]] = [] # (-priority, sequence, id)
            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
            heapq.heappush(self.heap, (-priority, self.counter, job_id))
            self.counter += 1

        def get_next_job(self) -> Optional[tuple[str, dict]] :
            if not self.heap:
                return None
            _, _, job_id = heapq.heappop(self.heap)
            job = self.jobs.get(job_id)
            return (job.id, job.data) if job else None

        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
    ```

    One detail: "Highest-priority job's (id, data); FIFO order within the same priority."
    Using a counter in a min-heap with negative priority accomplishes this correctly.

    Another detail: `process_job` signature is `process_job(self, job_id: str, processor: Callable) -> bool`.
    I should make sure `JobQueue` has the `RetryPolicy` instance.

    Let's double check requirements:
    1. `Job` dataclass: `id`, `data`, `priority`, `retries`. (Yes)
    2. `RetryPolicy`: `__init__`, `run`. Returns `(success, attempts_made)`. No sleep. (Yes)
    3. `JobQueue`: `add_job`, `process_job` (must use `RetryPolicy`), `get_next_job`. FIFO order. (Yes)
    4. Standard library only. (Yes)
    5. Python code only. (Yes)

    Wait, `JobQueue` doesn't explicitly say it has a `RetryPolicy` instance, but "orchestrates Jobs using RetryPolicy" and "must USE RetryPolicy (compose, don't reimplement)".
    I'll instantiate `RetryPolicy` inside `JobQueue` or as a property.

    Let's refine `JobQueue.process_job`:
    It needs to access the `RetryPolicy`. Since it says "composable", having it as an attribute is standard.

    Wait, `Job` has a `retries` field. Should `RetryPolicy` update `job.retries`?
    The `run` method returns `(success, attempts_made)`.
    The `JobQueue.process_job` method doesn't specify updating the object, but since the `Job` dataclass has a `retries` field, it's good practice to update it if the prompt implies it.
    However, the prompt says `RetryPolicy` should return the tuple and `JobQueue` must use it. It doesn't explicitly say "update the dataclass field". I'll stick to the specific signatures provided.

    One edge case: `get_next_job` returns `tuple[str, dict] | None`.
    I'll use `Optional` or the `|` syntax if Python version allows (Prompt asks for Python code, I'll use modern syntax where safe or standard). The prompt used `tuple[bool, int]` style which suggests Python 3.9+ type hinting style. I will use `|` or `Optional`? The prompt used `tuple[str, dict] | None` which is Python 3.10+ syntax.

    Actually, looking at the prompt's `RetryPolicy.run(self, fn: Callable, data) -> tuple[bool, int]`, it uses `tuple[...]`.
    I'll use `Optional` just to be safe or stick to the requested syntax.
    The prompt asks for `tuple[str, dict] | None`. I will use that.

    Double check `Job` dataclass fields: `id`, `data`, `priority`, `retries`.
    `RetryPolicy` init: `max_attempts: int = 4`.
    `JobQueue.add_job` signature: `(self, job_id: str, data: dict, priority: int = 0) -> None`.

    One logic check: `process_job` receives a `job_id`. Should it remove the job from the queue after processing? The instructions don't say to remove it, but usually, `process_job` implies completion. However, if I remove it, `get_next_job` might return it? No, `get_next_job` is "get next", `process_job` is "execute this specific one".
    If I don't remove it, and `process_job` fails, it stays in the `jobs` dict.
    If I don't remove it from the heap, it stays there.
    Since `process_job` takes an ID, it doesn't necessarily mean it was the "next" job.

    Wait, if `get_next_job` is called, it should probably remove the job from the "available" pool.
    If `process_job` is called, it executes it.
    I will assume `get_next_job` removes it from the priority heap.

    Let's write the code.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, List, Tuple, Optional
    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 = 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: Dict[str, Job] = {}
            self.priority_heap: List[tuple[int, int, str]] = []
            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 with heapq (min-heap)
            # Counter ensures FIFO for same priority
            heapq.heappush(self.priority_heap, (-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, attempts = self.retry_policy.run(processor, job.data)
            job.retries = attempts
            return success

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

    One small fix: `process_job` might be expected to remove the job from the dictionary? The instructions don't specify. I'll leave it in the dictionary so that `process_job` can be called independently of `get_next_job`. If `get_next_job` removes it from the heap, it's "popped" from the queue but still exists in `self.jobs`. This seems like a reasonable interpretation of a "queue" that supports both "get next" and "process specific ID".

    Wait, if `get_next_job` removes it from the heap, and `process_job` is called on that same ID, it works.
    If `process_job` is called on an ID that hasn't been popped yet, it also works.
    This is the most robust way to handle the two distinct methods.

    Final check on types:
    `tuple[bool, int]` - Checked.
    `tuple[str, dict] | None` - Checked.
    `Callable` - Checked.

    Wait, `JobQueue` should be "composable". Does that imply passing `RetryPolicy` to the constructor?
    "Refactor the job queue into three composable classes... orchestrates Jobs using RetryPolicy".
    Usually, composition means `JobQueue` has a `RetryPolicy` instance. I'll put it in `__init__`.

    Actually, to be more "composable" (Dependency Injection style), I could pass it in, but the prompt says "Define these three classes" with specific `__init__` signatures for `RetryPolicy` (which doesn't mention `JobQueue` dependency) and `JobQueue` (which doesn't mention `RetryPolicy` dependency in signature).
    I will stick to the signatures provided.

    One more look at `JobQueue.process_job`:
    `process_job(self, job_id: str, processor: Callable) -> bool`
    The requirement says "must USE RetryPolicy (compose, don't reimplement retry logic)".
    So `JobQueue` must have an instance of `RetryPolicy`. I'll initialize it in `__init__`.

    Refining `JobQueue.get_next_job`:
    "return the highest-priority job's (id, data); FIFO order within the same priority."
    Yes, `(-priority, counter, job_id)` handles this perfectly.

    One final check: `RetryPolicy.run` signature: `run(self, fn: Callable, data)`.
    The prompt's signature is `run(self, fn: Callable, data) -> tuple[bool, int]`.
    My code: `def run(self, fn: Callable, data: dict) -> tuple[bool, int]:`.
    I'll remove the `dict` hint for `data` in `run` to match the prompt exactly if needed, but `data: dict` is safer. I'll match the prompt's `data` (no type hint).

    Wait, prompt says:
    `run(self, fn: Callable, data) -> tuple[bool, int]`
    I will use `data` without type hint there.

    Final code check:
    - `Job` dataclass? Yes.
    - `RetryPolicy` with `max_attempts`? Yes.
    - `JobQueue` with `add`, `process`, `get_next`? Yes.
    - `process_job` uses `RetryPolicy`? Yes.
    - Standard library only? Yes.
    - No explanation? Yes.
</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) -> 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: dict[str, Job] = {}
        self.heap: list[tuple[int, int, str]] = []
        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 with heapq (min-heap)
        # Counter ensures FIFO order for items with the same priority
        heapq.heappush(self.heap, (-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, attempts = self.retry_policy.run(processor, job.data)
        job.retries = attempts
        return success

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