<reasoning_content>
*   Target: Refactor a job queue into three composable classes in `solution.py`.
    *   Requirement 1: `Job` dataclass (`id`, `data`, `priority`, `retries`).
    *   Requirement 2: `RetryPolicy` class.
        *   `__init__(max_attempts=4)`.
        *   `run(fn, data)`: calls `fn(data)`, retries on exception, returns `(success, attempts_made)`. No `time.sleep`.
    *   Requirement 3: `JobQueue` class.
        *   `add_job(job_id, data, priority)`.
        *   `process_job(job_id, processor)`: Use `RetryPolicy`.
        *   `get_next_job()`: Highest priority (FIFO for ties).
    *   Constraints: Standard library only, no explanations, specific file name `solution.py`.

    *   `from dataclasses import dataclass`
    *   `from typing import Callable, Tuple, Dict, List, Optional`
    *   `from collections import deque` (Actually, `heapq` is better for priority, but I need to manage FIFO within priorities).

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

    *   `JobQueue`:
        *   Internal storage: I need a way to store jobs by ID and a priority queue.
        *   `heapq` is the standard way to handle priorities. However, standard `heapq` handles the smallest value as highest priority. Usually, "highest priority" means higher numbers are higher priority. I should use negative priority for `heapq` or handle it correctly.
        *   FIFO within same priority: `heapq` needs a counter to break ties.
        *   Requirement: `process_job(self, job_id: str, processor: Callable) -> bool`. This implies the `JobQueue` stores jobs and we need to find the specific job by ID to process it. Or does it? The prompt says `process_job(self, job_id: str, ...)`. This suggests looking up a job by ID.
        *   Actually, `get_next_job` returns the highest priority job.
        *   Wait, `process_job` takes a `job_id`. Does it remove it? Usually, processing a job involves retrieving it, running it, and removing it.
        *   Let's re-read: `process_job(self, job_id: str, processor: Callable) -> bool`.
        *   Need a dictionary to store jobs by ID: `self.jobs = {}`.
        *   Need a priority queue for `get_next_job`.
        *   Wait, `get_next_job` returns the highest priority job. If I have multiple jobs, how does `process_job` work if it only takes an ID?
        *   Let's look at the signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
        *   Maybe the `JobQueue` holds a map of `id -> Job`.
        *   `get_next_job`: return the highest priority job.
        *   Let's implement `JobQueue` with a dictionary for lookups and a list for the priority queue.

    *   Priority: "Highest priority" usually means larger numbers. `heapq` is a min-heap. So I'll store `(-priority, sequence_number, job_id)`.
    *   `add_job`:
        1. Create `Job` object.
        2. Put in dict.
        3. Push to heap.
    *   `process_job`:
        1. Get job from dict by ID.
        2. Use `RetryPolicy.run`.
        3. Return result.
    *   `get_next_job`:
        1. Pop from heap.
        2. Return `(id, data)`.

    *Correction on `JobQueue` logic*:
    If `process_job` is called with an ID, but `get_next_job` also exists, there might be a conflict in state management. Usually, `get_next_job` removes the job from the queue. If I remove it, `process_job` (if called later) might fail if it relies on the ID being in the active list.
    Actually, `process_job` says "orchestrates Jobs".
    If I look at the method signature `process_job(self, job_id: str, processor: Callable) -> bool`, it implies the job is already known.

    Let's refine `JobQueue`:
    ```python
    class JobQueue:
        def __init__(self):
            self.jobs = {} # id -> Job
            self.priority_queue = [] # List for heapq
            self.counter = 0 # Tie breaker
            self.retry_policy = RetryPolicy()

        def add_job(self, job_id: str, data: dict, priority: int = 0):
            job = Job(id=job_id, data=data, priority=priority)
            self.jobs[job_id] = job
            # heapq is min-heap, so negate priority for "highest priority"
            import heapq
            heapq.heappush(self.priority_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:
            import heapq
            if not self.priority_queue:
                return None
            neg_pri, count, job_id = heapq.heappop(self.priority_queue)
            job = self.jobs.get(job_id)
            if not job: return None
            return (job.id, job.data)
    ```

    *Self-Correction on `JobQueue` usage*:
    If `get_next_job` removes from the heap, then `process_job` might be expected to be used *after* getting the job or independently.
    If I use `heapq.heappop`, the job ID is removed from the priority queue.
    If `process_job` is called with an ID, it still exists in `self.jobs`.
    Let's keep it simple. The prompt doesn't say `process_job` removes it, but `get_next_job` usually implies consumption.

    Wait, I should check if `RetryPolicy` needs to be instantiated inside `JobQueue` or passed in. "orchestrates Jobs using RetryPolicy" -> Instance inside.

    Refining `RetryPolicy.run`:
    "retries on any exception until success or max_attempts reached"
    ```python
    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
    ```
    Note: `attempts_made` is `attempts + 1` on success. If it fails after 4 tries, `attempts` was 0, 1, 2, 3, 4?
    Wait.
    Attempt 1: `attempts` is 0. `fn` fails. `attempts` becomes 1.
    Attempt 2: `attempts` is 1. `fn` fails. `attempts` becomes 2.
    Attempt 3: `attempts` is 2. `fn` fails. `attempts` becomes 3.
    Attempt 4: `attempts` is 3. `fn` fails. `attempts` becomes 4.
    Loop ends. Returns `False, 4`.
    If success on first try: `attempts` is 0. `fn` succeeds. Returns `True, 1`.
    This logic works.

    *Refining `JobQueue`*:
    Imports should be at the top.

    ```python
    import heapq
    from dataclasses import dataclass
    from typing import Callable, Tuple, Dict, List, 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_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: Dict[str, Job] = {}
            self.priority_queue: 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
            heapq.heappush(self.priority_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.priority_queue:
                return None
            neg_priority, _, job_id = heapq.heappop(self.priority_queue)
            job = self.jobs.get(job_id)
            if not job:
                return None
            return (job.id, job.data)
    ```

    Wait, one detail: "FIFO order within the same priority".
    My use of `self.counter` ensures FIFO because `heapq` will compare the second element of the tuple if the first elements (negated priorities) are equal.

    One more check: `process_job` return type `bool`.
    `RetryPolicy.run` returns `(bool, int)`.
    `process_job` returns `bool`. Correct.

    Is `retries` in `Job` dataclass used?
    The prompt says `retries: int = 0` in the dataclass.
    However, `RetryPolicy` handles the logic. Does `JobQueue` need to update `job.retries`?
    The instructions don't explicitly say to update the dataclass's `retries` field, but `RetryPolicy` takes `max_attempts`.
    Actually, `RetryPolicy` is independent of the `Job` object's `retries` field based on the signatures provided. `RetryPolicy.run` only takes `fn` and `data`.
    So `retries` in `Job` might just be a field for metadata or future use. I will leave it as defined.

    Final check on `RetryPolicy.run`:
    - `attempts_made` starts at 0.
    - loop runs while `attempts_made < max_attempts`.
    - if success: `return True, attempts_made + 1`.
    - if exception: `attempts_made += 1`.
    - loop ends: `return False, attempts_made`.
    Example: `max_attempts` = 2.
    Iter 1: `attempts_made` = 0. Exception -> `attempts_made` = 1.
    Iter 2: `attempts_made` = 1. Exception -> `attempts_made` = 2.
    Loop ends. Returns `False, 2`. Correct.

    One detail: `JobQueue.get_next_job` returns `tuple[str, dict] | None`.
    I'll use `Optional[Tuple[str, dict]]` or `Tuple[str, dict] | None` (Python 3.10+). The prompt uses `|` style in the signature `tuple[str, dict] | None`, so I should stick to that or ensure compatibility. Actually, I'll just use the syntax provided in the prompt.

    Wait, `process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
    If `get_next_job` pops the job from the heap, `process_job` still works because `self.jobs` dictionary still has it.

    Wait, I should double check if `JobQueue` should remove the job from `self.jobs` in `get_next_job`. Usually, popping from a priority queue means it's being processed. If I don't remove it from the dictionary, it stays "alive" in the queue. But `get_next_job` already removed it from the heap. So it's no longer "next" in the queue. This is consistent.

    One minor thing: "Write only the Python code, no explanations."

    ```python
    from dataclasses import dataclass
    from typing import Callable, Tuple, Dict, List, 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_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: Dict[str, Job] = {}
            self.priority_queue: 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
            heapq.heappush(self.priority_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.priority_queue:
                return None
            neg_priority, _, job_id = heapq.heappop(self.priority_queue)
            job = self.jobs.get(job_id)
            if not job:
                return None
            return (job.id, job.data)
    ```
    Wait, the prompt signature for `RetryPolicy.run` is `run(self, fn: Callable, data)`. It doesn't specify the type of `data`. I'll keep it generic or `dict` as per `Job`. The prompt's `Job` says `data: dict`.

    Final check on imports: `heapq` and `dataclass` and `Callable` etc are all standard library.

    One detail: `get_next_job` return type: `tuple[str, dict] | None`.
    I will ensure I use that exact syntax.

    Wait, `RetryPolicy.run` should return `tuple[bool, int]`.
    The prompt signature: `run(self, fn: Callable, data) -> tuple[bool, int]`.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Tuple, Dict, List
    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: Dict[str, Job] = {}
            self.priority_queue: 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
            heapq.heappush(self.priority_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.priority_queue:
                return None
            _, _, job_id = heapq.heappop(self.priority_queue)
            job = self.jobs.get(job_id)
            if not job:
                return None
            return (job.id, job.data)
    ```
    The `Tuple` vs `tuple` thing. Python 3.9+ supports `tuple[...]`. I'll use `tuple` to match the prompt style where applicable. Actually, I'll just use the provided signatures.

    Prompt signatures:
    1. `Job`: `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`
    2. `RetryPolicy`: `__init__(self, max_attempts: int = 4)`, `run(self, fn: Callable, data) -> tuple[bool, int]`
    3. `JobQueue`: `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, `RetryPolicy.run` takes `data`. Does not specify `dict`. I will use `any` or just `data`.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable, Tuple, Dict, List
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: Dict[str, Job] = {}
        self.priority_queue: 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 using heapq min-heap
        # counter ensures FIFO order for equal priorities
        heapq.heappush(self.priority_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.priority_queue:
            return None
        _, _, job_id = heapq.heappop(self.priority_queue)
        job = self.jobs.get(job_id)
        if not job:
            return None
        return (job.id, job.data)
```
</content>