# How Does AAA Resolve Item Levels? A Technical Deep Dive into Azeroth Auction Assassin

> Discover how Azeroth Auction Assassin resolves item levels using static data or a dynamic bonus ID resolver. Learn about its configuration for Classic and Retail WoW.

- Repository: [FF14 Advanced Market Search/azerothauctionassassin](https://github.com/ff14-advanced-market-search/azerothauctionassassin)
- Tags: deep-dive
- Published: 2026-03-01

---

**Azeroth Auction Assassin (AAA) resolves item levels using either static data from [`ilvl_items.json`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/ilvl_items.json) for Classic content or a dynamic bonus ID resolver for modern Retail WoW, controlled by the `USE_POST_MIDNIGHT_ILVL` configuration flag.**

Azeroth Auction Assassin (AAA) is an open-source auction house sniper for World of Warcraft that requires precise item level calculation to filter deals accurately. Understanding how AAA resolves item levels is crucial for configuring the tool correctly across different WoW expansions and data formats. The system employs two distinct resolution paths depending on whether you are scanning Classic-era content or modern Retail auctions.

## The Two Modes of Item Level Resolution

AAA determines which resolution strategy to use based on the `USE_POST_MIDNIGHT_ILVL` boolean flag stored in [`mega_data.json`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/mega_data.json) or passed as an environment variable【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/mega_data_setup.py#L49-L51】.

### Pre-Midnight Static Mode (Classic Data)

When `USE_POST_MIDNIGHT_ILVL` is set to `false` (the default for Classic data), AAA uses a static lookup approach. The application downloads [`ilvl_items.json`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/ilvl_items.json) during startup and stores the values in `MegaData.base_ilvls`. During auction scanning, the item level is retrieved directly from `DESIRED_ILVL_ITEMS["base_ilvls"][item_id]`【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/mega_alerts.py#L418-L425】.

This mode optionally applies **bonus-specific additive item levels** (`ilvl_addition`) extracted from the static [`bonuses.json`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/bonuses.json) dataset (see `get_bonus_id_sets`).

### Post-Midnight Dynamic Mode (Retail Data)

When `USE_POST_MIDNIGHT_ILVL` is `true` (required for modern Retail WoW after the "midnight" patch), AAA executes a full-featured resolver that interprets **bonus IDs**, **drop-level curves**, and **era-specific scaling** using live Raidbots data. The core algorithm lives in [`utils/ilvl_resolver.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/utils/ilvl_resolver.py) within the `resolve_post_midnight_ilvl` function【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L77-L89】.

## How the Post-Midnight Resolver Works

The dynamic resolver processes item levels through several sophisticated stages to handle Retail WoW's complex scaling systems.

### Initialization and Data Loading

When `USE_POST_MIDNIGHT_ILVL` is enabled, the `MegaData` class in [`utils/mega_data_setup.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/utils/mega_data_setup.py) fetches and caches four critical datasets from Raidbots on initialization:

```python
self.bonuses_by_id = get_bonus_ids()["bonuses_by_id"]          # bonus definitions

self.equippable_items = get_raidbots_equippable_items()      # base item-level per item

self.item_curves = get_raidbots_item_curves()                # level-curve tables

self.item_squish_era = get_raidbots_item_squish_era()        # era definitions

```

These objects are stored in memory and passed to the resolver for each auction evaluation.

### The Resolution Algorithm

The `resolve_post_midnight_ilvl` function executes a multi-stage algorithm:

1. **Base Level Retrieval**: Fetches the base item level from `equippable_items` via `_get_base_item_level`, handling both string and integer keys. If missing, the base defaults to `0`【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L10-L31】.

2. **Bonus ID Parsing**: Iterates over `bonus_lists` to extract four operation types from `bonuses_by_id`:
   - `itemLevel` → **set-level** operation (priority-based)
   - `levelOffset` → **additive offset**
   - `levelOffsetSecondary` → extra additive offset applied after all eras
   - `dropLevelCurve` → **curve-based offset** using the optional `drop_level` argument (the required player level)

   The collected operations are grouped by **squish era** to allow era-specific handling【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L100-L132】.

3. **Legacy Offset Handling**: Sums any plain `level` fields in bonuses into `legacy_level_offset` as a fallback when no other operations exist【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L134-L137】.

4. **Early Exit**: If no operations were collected, returns the base level plus any legacy offset【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L144-L148】.

### Era-Based Processing

For modern Retail items, the resolver walks through the list of eras supplied by `item_squish_era` (parsed from [`item-squish-era.json`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/item-squish-era.json)). For each era it:

- **Applies Global Curves**: Optionally applies a global curve (`curveId`) via `_apply_curve` using linear interpolation between points【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L34-L74】.

- **Selects Best Set-Level**: Chooses the best set-level operation for that era (lowest priority) and overwrites `item_level`【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L68-L72】.

- **Applies Drop-Level Curve**: If a `drop_level` is present, scales the required level through the curve and adds any static offset【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L73-L80】.

- **Aggregates Level Offsets**: Sums all `levelOffset` values belonging to the era【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L81-L85】.

- **Applies Secondary Offsets**: After era processing, sums every `levelOffsetSecondary` onto the final `item_level`【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L86-L88】.

The function returns the integer `item_level`, or `None` if the base could not be determined and no operations existed【/cache/repos/github.com/ff14-advanced-market-search/azerothauctionassassin/main/utils/ilvl_resolver.py#L89-L90】.

## Integration with the Alert Pipeline

In [`mega_alerts.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/mega_alerts.py), the application selects the appropriate resolution path for each auction:

```python
if mega_data.USE_POST_MIDNIGHT_ILVL:
    ilvl = resolve_post_midnight_ilvl(
        auction["item"]["id"],
        auction["item"]["bonus_lists"],
        required_lvl,
        mega_data.bonuses_by_id,
        mega_data.equippable_items,
        mega_data.item_curves,
        mega_data.item_squish_era,
    )
else:
    # static path (base_ilvl + additive bonuses)

    …

```

If the resolver returns `None`—which occurs when the base item level cannot be determined from `equippable_items` and no valid bonus operations exist—the auction is discarded by the alert pipeline. This prevents false positives from malformed auction data or unknown items that cannot be accurately priced.

## Code Examples

### Direct Resolution Using the API

You can resolve item levels programmatically using the post-midnight resolver:

```python
from utils.ilvl_resolver import resolve_post_midnight_ilvl
from utils.mega_data_setup import MegaData

# Initialise MegaData (loads static & Raidbots data)

mega = MegaData()

item_id = 19019                # Example: "Vicious Gladiator's Greatsword"

bonus_lists = [5275, 6228]     # Bonus IDs from the auction

required_lvl = 60              # Player level required to use the item

ilvl = resolve_post_midnight_ilvl(
    item_id,
    bonus_lists,
    required_lvl,
    mega.bonuses_by_id,
    mega.equippable_items,
    mega.item_curves,
    mega.item_squish_era,
)

print(f"Resolved ilvl: {ilvl}")

```

### Using the Built-in Alert Filter

For standard operation, allow the main application to handle resolution automatically:

```python
from main import AzerothAuctionAssassin  # entry point for the whole app

# Run the scanner; the engine will automatically pick the correct ilvl mode

# based on the `USE_POST_MIDNIGHT_ILVL` flag in mega_data.json.

AzerothAuctionAssassin().run()

```

## Key Files and Their Roles

| File | Role | Link |
|------|------|------|
| [`utils/ilvl_resolver.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/utils/ilvl_resolver.py) | Core algorithm that interprets bonus IDs, curves, and eras to compute a post-midnight ilvl | [utils/ilvl_resolver.py](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/utils/ilvl_resolver.py) |
| [`utils/mega_data_setup.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/utils/mega_data_setup.py) | Loads configuration, decides whether to use the dynamic resolver, and fetches the Raidbots static datasets | [utils/mega_data_setup.py](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/utils/mega_data_setup.py) |
| [`mega_alerts.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/mega_alerts.py) | Where the ilvl is actually requested for each auction (chooses static vs dynamic path) | [mega_alerts.py](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/mega_alerts.py) |
| [`utils/api_requests.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/utils/api_requests.py) | Provides the static [`ilvl_items.json`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/ilvl_items.json) data for the pre-midnight path | [utils/api_requests.py](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/utils/api_requests.py) |
| [`StaticData/ilvl_items.json`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/StaticData/ilvl_items.json) | Cached static ilvl values for Classic and early-retail items (used when `USE_POST_MIDNIGHT_ILVL` is false) | [StaticData/ilvl_items.json](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/StaticData/ilvl_items.json) |
| [`README.md`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/README.md) | High-level description of the project's purpose and configuration options (including `USE_POST_MIDNIGHT_ILVL`) | [README.md](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/README.md) |

## Summary

- **AAA uses two distinct resolution strategies**: static lookup from [`ilvl_items.json`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/ilvl_items.json) for Classic data, and dynamic calculation using bonus IDs and curves for modern Retail WoW.
- **The `USE_POST_MIDNIGHT_ILVL` flag** controls which mode is active, with the dynamic resolver fetching live data from Raidbots on initialization.
- **The post-midnight resolver** in [`utils/ilvl_resolver.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/utils/ilvl_resolver.py) processes bonus lists through era-specific operations including set-level overrides, drop-level curves, and additive offsets to compute final item levels.
- **Failed resolutions return `None`**, causing the alert pipeline in [`mega_alerts.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/mega_alerts.py) to discard auctions for unknown items or malformed data.

## Frequently Asked Questions

### What is the difference between pre-midnight and post-midnight item level resolution?

Pre-midnight resolution uses static data from [`ilvl_items.json`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/ilvl_items.json) downloaded during startup, suitable for Classic WoW where item levels are fixed values stored in `MegaData.base_ilvls`. Post-midnight resolution uses a dynamic algorithm in [`utils/ilvl_resolver.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/utils/ilvl_resolver.py) that interprets bonus IDs, drop-level curves, and era-specific scaling from live Raidbots data, which is required for modern Retail WoW after the "midnight" patch introduced complex item scaling systems.

### How does AAA handle bonus IDs when calculating item levels?

The post-midnight resolver iterates over each bonus ID in the auction's `bonus_lists` and extracts four operation types from the `bonuses_by_id` dataset: `itemLevel` (set-level operations with priority handling), `levelOffset` (additive bonuses), `levelOffsetSecondary` (post-era additive bonuses), and `dropLevelCurve` (scaling curves based on required player level). These operations are grouped by squish era and applied sequentially through era-based processing to determine the final item level.

### What happens if the item level resolver returns None?

If `resolve_post_midnight_ilvl` returns `None`—which occurs when the base item level cannot be determined from `equippable_items` and no valid bonus operations exist—the alert pipeline in [`mega_alerts.py`](https://github.com/ff14-advanced-market-search/azerothauctionassassin/blob/main/mega_alerts.py) discards the auction. This prevents false positives from malformed auction data or unknown items that cannot be accurately priced, ensuring only verifiable items trigger alerts.

### Where does AAA get its data for the dynamic resolver?

When `USE_POST_MIDNIGHT_ILVL` is enabled, the `MegaData` class fetches four critical datasets from Raidbots during initialization: `bonuses_by_id` (bonus definitions), `equippable_items` (base item levels per item), `item_curves` (level-curve tables), and `item_squish_era` (era definitions). These are cached in the `MegaData` instance and passed to `resolve_post_midnight_ilvl` for real-time calculation of item levels based on current game data.