<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, default 0), `retries` (int, default 0).
    *   Class 2: `RetryPolicy`. Methods: `__init__(max_attempts=4)`, `run(fn: Callable, data) -> tuple[bool, int]`. Behavior: Call `fn(data)`, retry on exceptions until success or max attempts. No actual sleeping.
    *   Class 3: `JobQueue`. Methods: `add_job(job_id, data, priority)`, `process_job(job_id, processor)`, `get_next_job()`. Behavior: `process_job` must use `RetryPolicy`. `get_next_job` returns highest priority (FIFO within same priority).
    *   Constraint: Standard library only. No external packages.
    *   Output: Only Python code, 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`:
        Need to store jobs. Since it's priority + FIFO, a `heapq` or a sorted list/dictionary approach is needed.
        `heapq` provides a min-heap. Priority is usually "higher is better", but `heapq` is min-heap.
        If `priority` 10 is higher than 0, I should store negative priority.
        FIFO requirement: Use a counter to break ties in `heapq`.

        ```python
        import heapq
        from typing import Optional

        class JobQueue:
            def __init__(self):
                self.jobs = [] # Min-heap
                self.job_map = {} # Map ID to Job object for easy retrieval?
                # Or just store them in a way to retrieve by ID.
                # Actually, the requirement for get_next_job says "return the highest-priority job".
                # Usually, JobQueue stores jobs, and process_job looks them up?
                # Wait, "process_job(self, job_id: str, ...)" implies we need to find the job by ID.
                # "get_next_job()" implies getting from a pool.
                self.counter = 0 # To ensure FIFO for same priority

            def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
                job = Job(id=job_id, data=data, priority=priority)
                # To handle priority (higher is better) and FIFO (lower counter is better):
                # heap elements: (-priority, counter, job)
                heapq.heappush(self.jobs, (-priority, self.counter, job))
                self.job_map[job_id] = job
                self.counter += 1

            def get_next_job(self) -> tuple[str, dict] | None:
                if not self.jobs:
                    return None
                _, _, job = heapq.heappop(self.jobs)
                # If the job was already processed/removed from map, handle it?
                # The prompt doesn't specify deleting from map during process_job,
                # but usually jobs are removed once "gotten" or "processed".
                # Let's assume get_next_job removes it from the queue.
                return job.id, job.data

            def process_job(self, job_id: str, processor: Callable) -> bool:
                # Need to find the job by id.
                # If I pop it in get_next_job, how do I find it in process_job?
                # Usually process_job refers to a job still in the queue or a specific one.
                # The prompt doesn't specify if get_next_job is the only way to access jobs.
                # Let's keep a dictionary of all jobs added.
                job = self.job_map.get(job_id)
                if not job:
                    return False
                policy = RetryPolicy()
                success, _ = policy.run(processor, job.data)
                return success
        ```

    *   *Wait*, if `get_next_job` pops from the heap, `process_job` won't find it in the heap?
    *   Actually, usually `process_job` takes an ID. This suggests the `JobQueue` holds a collection of jobs.
    *   If `get_next_job` is called, the job is removed from the "active" queue.
    *   If `process_job` is called with a specific `job_id`, it probably refers to a job that exists in the system.
    *   However, the prompt says "orchestrates Jobs".
    *   Standard behavior: `add_job` puts it in a pool. `get_next_job` takes the "best" one out of the pool. `process_job` takes a specific job (perhaps one already retrieved? or still in the pool?).
    *   Usually, `process_job` would be used for a job that was retrieved or is being targeted specifically.
    *   Let's refine `JobQueue` to keep a map of all jobs added so far so `process_job` can find it, and a heap for `get_next_job`.

    *   *Refinement on `Job` dataclass*:
        `id`, `data`, `priority`, `retries`.

    *   *Refinement on `RetryPolicy`*:
        `run(fn, data)`. Correct.

    *   *Refinement on `JobQueue`*:
        `add_job`: Add to `self.jobs` (heap) and `self.job_map` (dict).
        `process_job`: Look up in `self.job_map`. Use `RetryPolicy`.
        `get_next_job`: Pop from `self.jobs`. If it's been popped once, it's "done" for the `get_next_job` logic? Or should I remove it from `job_map`?
        Actually, `get_next_job` usually implies "pop". If I pop it, it shouldn't be available in `get_next_job` again. But `process_job` might be called on a job ID that was just "gotten".
        Actually, the prompt doesn't say `get_next_job` removes it from the map. It just says return it. To be safe and fulfill "FIFO", I will pop from heap.

    *   *Priority logic*:
        `priority` 10 > 0. `heapq` is min-heap. Use `-priority`.
        FIFO: `counter` incrementing.

    ```python
    from dataclasses import dataclass
    from typing import Callable, 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_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._heap = []
            self._job_map = {}
            self._counter = 0

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            job = Job(id=job_id, data=data, priority=priority)
            self._job_map[job_id] = job
            # Higher priority number = higher priority. Heap is min-heap.
            # Use negative priority to simulate max-heap.
            heapq.heappush(self._heap, (-priority, self._counter, job))
            self._counter += 1

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

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

    - `Job`: dataclass with fields `id`, `data`, `priority`, `retries`. (Check)
    - `RetryPolicy`: `__init__(max_attempts)`, `run(fn, data)` retries on exception, no sleep, returns `(success, attempts_made)`. (Check)
    - `JobQueue`:
        - `add_job`: (Check)
        - `process_job`: Uses `RetryPolicy`. (Check)
        - `get_next_job`: highest priority (negative for min-heap), FIFO (counter). (Check)
    - Standard library only. (Check)
    - No explanations. (Check)

    *Small correction*: The prompt says `RetryPolicy` takes `fn` and `data`. My code does `fn(data)`.
    The `process_job` method must use `RetryPolicy`.

    One detail: `Job` has `retries` field. Should `RetryPolicy` update it? The prompt doesn't say to update the `Job` object's `retries` attribute, just to return `(success, attempts_made)`. I'll stick to the prompt literally.

    One detail: `get_next_job` returns `tuple[str, dict] | None`. Python 3.10+ syntax is fine.

    Wait, the prompt says: `RetryPolicy` retries on *any* exception.

    Let's double check `RetryPolicy.run`.
    `run(self, fn: Callable, data) -> tuple[bool, int]`
    It says `data` (no type hint provided in the signature of the prompt for `data`, but `Job.data` is `dict`). I'll use `dict` or leave it generic.

    One small thing: Should `process_job` remove the job from the map? Usually, yes, but the prompt doesn't specify. I'll leave it as is.

    Wait, in `process_job`, I should probably make sure `RetryPolicy` is instantiated or used. The prompt says "orchestrates Jobs using RetryPolicy".

    ```python
    from dataclasses import dataclass
    from typing import Callable, 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_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._heap = []
            self._jobs = {}
            self._counter = 0

        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 is a min-heap. To get highest priority (max value), 
            # we use negative priority. Counter ensures FIFO for same priority.
            heapq.heappush(self._heap, (-priority, self._counter, job))
            self._counter += 1

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

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

    Wait, the prompt says `get_next_job` should return the highest priority.
    If `priority` is 10 and 5, 10 is higher.
    Min-heap: `(-10, counter1, job1)` vs `(-5, counter2, job2)`.
    `-10` is smaller than `-5`, so `job1` (priority 10) comes out first. Correct.
    If priorities are same: `(-10, 1, job1)` vs `(-10, 2, job2)`.
    `1` is smaller than `2`, so `job1` comes out first. Correct.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable, 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_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._heap = []
        self._job_registry = {}
        self._counter = 0

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

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

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