<reasoning_content>
*   Target: Refactor a job queue into three composable classes (`Job`, `RetryPolicy`, `JobQueue`) in `solution.py`.
    *   Constraints:
        *   `Job`: dataclass (`id`, `data`, `priority`, `retries`).
        *   `RetryPolicy`: handles retries with backoff (simulated, no sleep). `run` returns `(success, attempts_made)`.
        *   `JobQueue`: manages jobs. `add_job`, `process_job` (using `RetryPolicy`), `get_next_job` (priority queue logic: high priority first, FIFO for same priority).
    *    Requirements:
        *   Use `dataclass`.
        *   Use `Callable` from `typing`.
        *   Use only standard library.
        *   No sleep in `RetryPolicy`.
        *   No explanations, only code.

    *   `Job` dataclass:
        ```python
        from dataclasses import dataclass
        from typing import Callable, Dict, Tuple, Optional

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

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

            def run(self, fn: Callable[[Dict]], 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
        ```
        *Correction*: The prompt asks for "retry-with-backoff" but says "Do NOT actually sleep". Usually, backoff implies waiting, but since I can't sleep, it's just a retry loop.

    *   `JobQueue` class:
        *   Needs to store jobs. A list or a heap?
        *   `get_next_job`: Highest priority (highest number? usually priority is higher = more important, or lower = higher priority? Standard convention is usually higher number = higher priority, but `heapq` is min-heap).
        *   Let's assume higher priority integer = higher priority (e.g., 10 > 1).
        *   FIFO within same priority.
        *   To handle "Highest priority first" and "FIFO" with `heapq`, we need a sequence counter for tie-breaking.
        *   Wait, I can just use a list and sort, or use `heapq`. Since it's a queue, `heapq` is efficient.
        *   `heapq` is a min-heap. To make it a max-heap for priority, use negative priority.

        ```python
        import heapq

        class JobQueue:
            def __init__(self):
                self.jobs = [] # list of (priority, counter, job_id, data)
                self.counter = 0
                self.retry_policy = RetryPolicy()

            def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
                # priority is max heap (higher number = higher priority)
                # counter ensures FIFO for same priority
                heapq.heappush(self.jobs, (-priority, self.counter, job_id, data))
                self.counter += 1

            def process_job(self, job_id: str, processor: Callable[[dict]]) -> bool:
                # The prompt says process_job(job_id, processor).
                # But it doesn't say where to get the data for the processor.
                # Usually, you find the job by ID or pop it.
                # However, the prompt doesn't specify searching.
                # Let's look at the signature: process_job(self, job_id: str, processor: Callable) -> bool
                # I need to find the job with job_id.
                # But the dataclass has retries.
                # Let's assume I need to find the job in a storage.
                # But the JobQueue signature for add_job only takes id, data, priority.
                # I should store jobs in a dictionary for lookup by ID and a heap for ordering.
                pass
        ```

    *   *Re-reading Requirements*:
        1. `Job` dataclass.
        2. `RetryPolicy`.
        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, `process_job` takes a `job_id`. Where is the data? It must be stored internally.
        `get_next_job` returns `(id, data)`.
        If `get_next_job` removes it from the queue, then `process_job` probably acts on a job already retrieved? Or `process_job` finds it?
        Usually, `process_job` takes the ID, finds the job, executes the processor, and maybe updates the retries or removes it.

    *   *Storage Strategy*:
        To support `get_next_job` (priority order) and `process_job` (by ID), I need both a priority queue and a dictionary.

    *   *Priority interpretation*: "highest-priority job". Usually, Priority 10 is higher than Priority 1. `heapq` is min-heap. So I'll use `-priority`.

    *   *Logic for `process_job`*:
        It takes `job_id`.
        I need a way to map `job_id` to the `Job` object.
        If `process_job` is successful, should it remove the job? The prompt doesn't say. Usually, processing implies completion.

    *   *Revised `JobQueue` structure*:
        ```python
        class JobQueue:
            def __init__(self):
                self.jobs_map = {} # job_id -> Job
                self.pq = [] # heap of (-priority, counter, job_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_map[job_id] = job
                heapq.heappush(self.pq, (-priority, self.counter, job_id))
                self.counter += 1

            def process_job(self, job_id: str, processor: Callable) -> bool:
                job = self.jobs_map.get(job_id)
                if not job: return False
                success, _ = self.retry_policy.run(processor, job.data)
                if success:
                    del self.jobs_map[job_id]
                    # Note: Removing from heap is hard.
                    # Maybe I should only remove from map and let get_next_job handle it?
                    # Or maybe get_next_job is supposed to be the primary way to get jobs.
                return success

            def get_next_job(self) -> tuple[str, dict] | None:
                while self.pq:
                    neg_prio, count, jid = heapq.heappop(self.pq)
                    if jid in self.jobs_map:
                        job = self.jobs_map[jid]
                        return (job.id, job.data)
                return None
        ```

    Wait, if `process_job` deletes from `jobs_map`, `get_next_job` will skip it because of the `if jid in self.jobs_map` check. This works well.

    *   *Backoff Requirement*: "encapsulates retry-with-backoff... Do NOT actually sleep."
        This is slightly contradictory. Backoff usually involves time. If no sleep, it's just a loop. I'll just implement the loop.

    *   *Typing*:
        `Callable` needs types: `Callable[[dict], None]`. Or just `Callable`.
        The prompt says `fn: Callable`.

    *   `Job` dataclass: Correct.
    *   `RetryPolicy`:
        - `__init__`: `max_attempts: int = 4`. Correct.
        - `run(self, fn: Callable, data) -> tuple[bool, int]`: Correct.
        - No sleep. Correct.
    *   `JobQueue`:
        - `add_job`: Correct.
        - `process_job`: Correct (uses `RetryPolicy`).
        - `get_next_job`: Correct (highest priority, FIFO). Correct.
    *   Standard library only. Correct.

    One detail: `priority`. Does "highest priority" mean highest integer? Yes, that's standard for "priority". If priority 10 is higher than 1, then `heapq` with `-priority` is correct.

    Refining `process_job`:
    If `process_job` succeeds, should it be removed? The prompt doesn't say. However, if `get_next_job` is meant to be used to get the next work item to process, usually the user would call `get_next_job` then call `process_job`. But `process_job` takes an ID.

    Let's look at the requirements again.
    `process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
    If I process it, I should probably mark it as done.

    Actually, if the user calls `process_job` with an ID, they probably want to know if it succeeded. If it succeeded, it's done. If it failed after retries, it's also "done" (or stay in queue? usually retries are exhausted). I will remove it from the map regardless of success/failure because `RetryPolicy` exhausted its attempts. Or only if success?
    Actually, if `process_job` fails after all retries, the job is technically "failed".
    If I don't remove it from `jobs_map`, `get_next_job` will keep returning it.
    If I do remove it, it's gone.
    Standard behavior: a job is processed once.

    Wait, `Job` dataclass has a `retries` field.
    Should `RetryPolicy` update `job.retries`?
    The prompt says `RetryPolicy.run` returns `(success, attempts_made)`. It doesn't say it modifies the `Job` object.
    The `Job` dataclass has `retries`, but `RetryPolicy` doesn't seem to interact with the `Job` object directly, only the `data`.
    I'll stick to the signatures strictly.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, 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.jobs_map: Dict[str, Job] = {}
            self.pq: 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_map[job_id] = job
            heapq.heappush(self.pq, (-priority, self.counter, job_id))
            self.counter += 1

        def process_job(self, job_id: str, processor: Callable) -> bool:
            job = self.jobs_map.get(job_id)
            if not job:
                return False
            
            success, _ = self.retry_policy.run(processor, job.data)
            
            # If process_job is called, it's implied we are completing the attempt.
            # If success is true, definitely remove it.
            # If success is false, it means max_attempts were reached.
            # Usually, jobs that fail all retries are removed or moved to a DLQ.
            # I'll remove it from the active map to ensure it's not picked up again.
            if success:
                del self.jobs_map[job_id]
            else:
                # Even if failed, it shouldn't stay in the 'active' queue 
                # if it exhausted retries.
                del self.jobs_map[job_id]
                
            return success

        def get_next_job(self) -> Tuple[str, Dict] | None:
            while self.pq:
                neg_prio, count, jid = heapq.heappop(self.pq)
                if jid in self.jobs_map:
                    job = self.jobs_map[jid]
                    return (job.id, job.data)
            return None
    ```

    Wait, one small detail: `retries` field in `Job`.
    Should `process_job` update `job.retries`?
    The `RetryPolicy.run` returns `attempts_made`.
    Maybe I should update `job.retries = attempts_made`?
    The prompt doesn't explicitly say to update it, but it's a field in the dataclass.
    However, since `RetryPolicy` doesn't take the `Job` object, only `data`, I can't update it inside `RetryPolicy.run`.
    I will update it in `process_job` just in case.

    Actually, I'll keep it simple. If the prompt doesn't say update it, don't overcomplicate. But `retries` field existence suggests it should be used.
    If I update `job.retries` in `process_job`, it makes the dataclass useful.

    Wait, `process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
    If it fails, does it stay in the queue?
    In most systems, if a job fails all retries, it's removed.
    I'll remove it from `jobs_map` regardless of result (since `RetryPolicy` already exhausted attempts).

    Wait, `get_next_job` returns `tuple[str, dict] | None`.
    In Python 3.10+ `|` is fine. For older, `Optional[Tuple[str, Dict]]]`.
    The prompt uses `tuple[...] | None` which implies Python 3.10+.

    Final check on `RetryPolicy`:
    `run(self, fn: Callable, data) -> tuple[bool, int]`
    The signature in the prompt is `fn: Callable, data`. It doesn't specify the type of `data`.
    I'll use `Dict` or just leave it as is.

    One last thing: "retry-with-backoff".
    Even though "Do NOT actually sleep", usually backoff means `2**attempts`.
    Since I can't sleep, I could just acknowledge that backoff is logically skipped or just ignore the word "backoff" since sleep is forbidden.
    Wait, if I can't sleep, there is no way to do backoff in a synchronous loop except for some hypothetical side effect. I'll just do the retry loop.

    Actually, I should check if `Job` needs to be imported or used.
    Yes, `Job` is the dataclass.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, 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.jobs: Dict[str, Job] = {}
            self.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 with 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, attempts = self.retry_policy.run(processor, job.data)
            job.retries = attempts
            
            # Remove from active jobs if processed (regardless of success, 
            # as retries are exhausted)
            if job_id in self.jobs:
                del self.jobs[job_id]
                
            return success

        def get_next_job(self) -> Tuple[str, Dict] | None:
            while self.queue:
                neg_priority, _, job_id = heapq.heappop(self.queue)
                if job_id in self.jobs:
                    job = self.jobs[job_id]
                    return (job.id, job.data)
            return None
    ```

    One minor thing: `retries` in `Job` dataclass.
    Should I increment it? Or set it?
    The `RetryPolicy` returns `attempts_made`. Setting `job.retries = attempts` is reasonable.

    Wait, the prompt says:
    `process_job(self, job_id: str, processor: Callable) -> bool`
    If it's successful, return `True`.
    If it fails all retries, return `False`.

    One detail: `tuple[str, dict] | None`.
    If I use `typing.Tuple`, it's `Tuple[str, Dict]`.
    The prompt uses `tuple[...]` which is the built-in type. I'll use `Tuple` from `typing` for compatibility or just `tuple` if I assume 3.9+. I'll stick to `Tuple` to be safe or just use the prompt's literal notation. I'll use `Tuple` for the return type hint to be safe with older 3.x versions but the prompt uses `tuple[...] | None` so I will assume 3.10+.

    Let's double check `get_next_job`.
    "return the highest-priority job's (id, data); FIFO order within the same priority."
    By using a `counter` as the second element in the heap tuple:
    `heapq.heappush(self.queue, (-priority, self.counter, job_id))`
    When priorities are equal, it compares `self.counter`.
    Since `self.counter` increments for every new job, lower counter = earlier added = FIFO.
    This is perfect.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable, Dict, 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.jobs: Dict[str, Job] = {}
        self.pq: 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.pq, (-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
        
        # Remove from active jobs once processed (attempts exhausted)
        if job_id in self.jobs:
            del self.jobs[job_id]
            
        return success

    def get_next_job(self) -> Tuple[str, Dict] | None:
        while self.pq:
            _, _, job_id = heapq.heappop(self.pq)
            if job_id in self.jobs:
                job = self.jobs[job_id]
                return (job.id, job.data)
        return None
```
</content>