## Class: WPDAI_Data_Warehouse

The `WPDAI_Data_Warehouse` class is the centralized data layer for Alpha Insights. It does not fetch data itself; instead it orchestrates fetching via registered data sources (see `wpd_alpha_insights_register_data_sources` and `WPDAI_Custom_Data_Source_Registry`). You request entities by name; the warehouse delegates to the appropriate source, stores results in `$data`, and deduplicates so each source runs only once per warehouse instance.

### Overview

This class:

- Provides a single entry point for loading data: `fetch_data( array( 'orders', 'expenses', 'store_profit', ... ) )`
- Stores all fetched data internally in a structured `$data` array keyed by entity name
- Applies date ranges and filters (from the constructor filter) to all operations
- Organizes each entity's data into: totals, categorized_data, data_table, data_by_date, total_db_records, execution_time, memory_usage
- Builds date containers for time-series charting (`get_data_by_date_range_container()`, etc.)
- Exposes filter and date helpers: `get_filter()`, `get_data_filter()`, `get_date_from()`, `get_date_to()`
- Tracks execution time and memory per entity (added automatically when sources return)
- Supports per-entity data table limits and entity-specific filters via `data_filters` and `data_table_limit`

#### Location

File: `includes/classes/WPDAI_Data_Warehouse.php`

### Data Structure

Fetched data is stored in the protected `$data` array. Entity keys (e.g. `orders`, `products`, `expenses`, `store_profit`, `analytics`) are created when `fetch_data()` is called for those entities. Built-in entities are provided by registered data sources (sales, expenses, store profit, subscriptions, campaigns, analytics). Custom data sources can add more entity names.

Special top-level keys:

- `total_db_records` (int) – Aggregate record count across all entities, updated when data is set
- `anonymous_queries` (array) – Arbitrary query results keyed by query name

Per-entity structure (each key under an entity name):

    array(
        'totals'              => array(),   // Aggregate metrics
        'categorized_data'    => array(),   // Data grouped by categories
        'data_table'          => array(),   // Tabular data for display
        'data_by_date'        => array(),   // Time-series data keyed by date
        'total_db_records'    => 0,         // Records fetched for this entity
        'execution_time'      => 0.0,       // Seconds (set by warehouse when source returns)
        'memory_usage'        => 0         // Bytes (set by warehouse when source returns)
    )

### Main Public Methods

#### Constructor

##### __construct( $filter = array() )

Purpose: Initialize the warehouse with filter options. Date containers are built automatically from the filter.

Parameters:

- `$filter` (array) – Filter configuration (date range, data_filters, data_table_limit, etc.). See Filter Configuration below.

Example:

    $filter = array(
        'date_from' => '2024-01-01',
        'date_to'   => '2024-01-31',
        'data_filters' => array(
            'orders' => array(
                'order_status' => array( 'wc-completed', 'wc-processing' )
            )
        )
    );
    $warehouse = new WPDAI_Data_Warehouse( $filter );

#### Data Fetching

##### fetch_data( array $entity_names )

Purpose: Single entry point to load data for the requested entities. Each entity is served by a registered data source (built-in or custom). Each source is invoked only once per warehouse instance; requesting both `orders` and `products` (from the same source) results in one fetch.

Parameters:

- `$entity_names` (array) – List of entity names to fetch (e.g. `array( 'orders', 'products', 'expenses', 'store_profit' )`)

Returns: Array keyed by entity name; each value is that entity's data structure (totals, data_by_date, etc.). Only includes entities that were requested and are available in `$data`.

Example:

    $warehouse = new WPDAI_Data_Warehouse( array(
        'date_from' => '2024-01-01',
        'date_to'   => '2024-01-31'
    ) );
    $warehouse->fetch_data( array( 'orders', 'expenses', 'store_profit' ) );
    $order_totals = $warehouse->get_data( 'orders', 'totals' );
    echo $order_totals['total_order_revenue_ex_tax'];

##### fetch_custom_data_source( $entity_name )

Purpose: Backward-compatible wrapper to fetch a single custom entity. Delegates to `fetch_data( array( $entity_name ) )`.

Parameters:

- `$entity_name` (string) – The entity name of the custom data source

Returns: The fetched entity data array, or false if the entity is not registered or on failure.

#### Filter Management

##### update_filter( $key, $value )

Purpose: Update a single filter value after initialization.

Parameters: `$key` (string), `$value` (mixed)

Returns: Boolean (true on success, false on failure)

##### get_filter( $key = null, $default = null )

Purpose: Get a filter value or all filters.

Parameters:

- `$key` (string|null) – Specific filter key, or null to get all filters
- `$default` (mixed|null) – Default if key is not set

Returns: Mixed (filter value, full filter array, or default)

##### get_data_filter( $entity, $key )

Purpose: Get the applied data filter for a given entity and key (from `data_filters[ $entity ][ $key ]`). Useful inside custom data sources to read entity-specific filters.

Parameters:

- `$entity` (string) – Entity name (e.g. 'orders', 'products')
- `$key` (string) – Filter key (e.g. 'order_status', 'product_category')

Returns: The sanitized filter value, or false if not set or empty.

#### Data Retrieval

##### get_data( $data_type = false, $data_key = false )

Purpose: Retrieve stored data from the warehouse.

Parameters:

- `$data_type` (string|false) – Entity name (e.g. 'orders', 'expenses', 'store_profit'), or false
- `$data_key` (string|false) – Key within the entity (e.g. 'totals', 'data_by_date'), or false

Returns: Array of data, or false if not found. With no args, returns all `$data`; with only `$data_type`, returns that entity's full structure.

Examples:

    $all_data = $warehouse->get_data();
    $orders_data = $warehouse->get_data( 'orders' );
    $orders_totals = $warehouse->get_data( 'orders', 'totals' );
    $orders_by_date = $warehouse->get_data( 'orders', 'data_by_date' );

##### set_data( $data_type, $data )

Purpose: Set or merge data for an entity (or for `anonymous_queries`). Used internally when data sources return; can be used for custom queries.

Parameters: `$data_type` (string), `$data` (array). For entities, `$data` typically has keys totals, categorized_data, data_table, data_by_date, total_db_records.

Returns: void (sets `$this->data[ $data_type ]` by merging each key; updates aggregate `total_db_records`)

#### Date & Time

##### get_date_from( $format = 'Y-m-d' )

Purpose: Start date for the current filter.

Returns: Formatted date string.

##### get_date_to( $format = 'Y-m-d' )

Purpose: End date for the current filter.

Returns: Formatted date string.

##### get_n_days_range()

Purpose: Number of days between date_from and date_to (inclusive).

Returns: Integer.

##### get_date_range_array( $first, $last, $step = '+1 day', $output_format = 'Y-m-d' )

Purpose: Generate an array of dates between two dates.

Parameters: `$first`, `$last` (strings), `$step` (e.g. '+1 day', '+1 month'), `$output_format` (PHP date format)

Returns: Array of date strings.

##### get_data_by_date_range_container()

Purpose: Empty array with date keys for the current range and `date_format_display`. Use this to initialize `data_by_date` metrics so charts have aligned dates. For `date_format_display` = 'minute', the structure differs (minute indices).

Returns: Array with date keys and 0 values.

##### get_data_by_date_containers()

Purpose: Full date containers including metadata (n_days_period, date_format, date_from, date_to, date_range_container, calculations_by_day, calculations_by_time).

Returns: Array as above.

##### get_data_by_day_container()

Purpose: Empty array keyed by day of week (Mon–Sun).

Returns: Associative array.

##### get_data_by_hour_container()

Purpose: Empty array keyed by hour (12am–11pm).

Returns: Associative array.

#### Utility

##### get_errors()

Purpose: All errors recorded during the request.

Returns: Array of error messages.

##### get_execution_time()

Purpose: Execution time per entity and total (from stored `execution_time` on each entity).

Returns: Array (e.g. `array( 'orders' => 0.5, 'total' => 0.5 )`).

##### get_total_db_records()

Purpose: Aggregate database record count across all entities.

Returns: Integer.

##### get_store_currency()

Purpose: Store currency code from WooCommerce (e.g. 'USD', 'GBP').

Returns: String or false.

##### get_data_table_limit( $entity = null )

Purpose: Row limit for data_table for an entity. Comes from filter `data_table_limit[ $entity ]` if set; otherwise default (500).

Parameters: `$entity` (string|null) – Entity name, or null for default limit.

Returns: Integer (0 = no table data, PHP_INT_MAX for unlimited when filter is 0 or -1).

##### determine_traffic_source( $referral_url, $query_parameters = null )

Purpose: Derive traffic source from a referral URL (and optional query params).

Parameters: `$referral_url` (string), `$query_parameters` (array|null)

Returns: String (e.g. 'google', 'facebook', 'direct').

##### get_analytics_event_count()

Purpose: Total analytics event count for current filters. Delegates to the registered analytics data source if it implements the method.

Returns: int|false.

##### get_analytics_session_count()

Purpose: Total session count for current filters. Delegates to the registered analytics data source if it implements the method.

Returns: int|false.

### Filter Configuration

Supported keys in the constructor (and via `update_filter`):

    array(
        'cache'                      => true,        // Use cache (default true)
        'date_preset'                 => 'last_30_days',  // Preset name; overrides date_from/date_to
        'date_from'                   => '2024-01-01',   // Start (Y-m-d)
        'date_to'                     => '2024-01-31',   // End (Y-m-d)
        'date_format_display'         => 'day',       // day, month, quarter, year, minute
        'minutes_ago'                 => 30,         // When date_format_display is 'minute', 1-60
        'date_format_string'          => 'Y-m-d',    // Set internally from date containers
        'data_filters'                => array(      // Per-entity filters (get_data_filter( $entity, $key ))
            'orders'   => array(
                'order_status' => array( 'wc-completed', 'wc-processing' ),
                'billing_email' => 'customer@example.com',
                'traffic_source' => array( 'facebook', 'google' ),
                'device_type' => array( 'mobile', 'desktop' ),
                'order_ids' => array( 123, 456 )
            ),
            'products' => array(
                'products' => array( 100, 101 ),
                'product_category' => array( 15, 22 ),
                'product_tag' => array( 5, 8 )
            ),
            'customers' => array( 'billing_country' => array( 'US', 'GB' ) ),
            'expenses'  => array( 'expense_category' => array( 10, 11 ) ),
            'website_traffic' => array( 'traffic_source' => array( 'google', 'direct' ) )
        ),
        'data_table_limit'            => array(      // Per-entity row limit for data_table
            'orders' => 500,                         // int; 0 or -1 = unlimited
            'products' => false                     // false = no table for this entity
        ),
        'comparison_date_selection'    => '...',
        'comparison_date_from'         => '...',
        'comparison_date_to'           => '...',
        'campaign_id'                 => 123,
        'campaign_date_override'      => true
    )

`date_preset` values (when set, `date_from` and `date_to` are derived): today, yesterday, this_week, this_month, last_month, month_to_date, this_year, last_year, last_7_days, last_30_days, last_90_days, ytd, all_time.

### Complete Usage Example

    // Initialize with filters
    $warehouse = new WPDAI_Data_Warehouse( array(
        'date_from' => '2024-01-01',
        'date_to'   => '2024-01-31',
        'data_filters' => array(
            'orders' => array(
                'order_status' => array( 'wc-completed', 'wc-processing' ),
                'traffic_source' => array( 'facebook' )
            ),
            'products' => array( 'product_category' => array( 15, 22 ) )
        )
    ) );

    // Load data (single entry point; no fetch_sales_data() / fetch_expense_data() etc.)
    $warehouse->fetch_data( array( 'orders', 'expenses', 'store_profit' ) );

    // Get totals
    $order_totals = $warehouse->get_data( 'orders', 'totals' );
    $expense_totals = $warehouse->get_data( 'expenses', 'totals' );
    $profit_totals = $warehouse->get_data( 'store_profit', 'totals' );

    // Time-series and categorized
    $revenue_by_date = $warehouse->get_data( 'orders', 'data_by_date' );
    $products_by_category = $warehouse->get_data( 'products', 'categorized_data' );
    $product_table = $warehouse->get_data( 'products', 'data_table' );

    // Performance
    $execution_time = $warehouse->get_execution_time();
    $total_records = $warehouse->get_total_db_records();

    echo "Total Revenue: " . $order_totals['total_order_revenue_ex_tax'] . "\n";
    echo "Store Profit: " . $profit_totals['total_store_profit'] . "\n";
    echo "Records: " . $total_records . ", Time: " . $execution_time['total'] . "s\n";

### Notes

- Data is loaded only via `fetch_data( array( ... ) )`. There are no public methods like `fetch_sales_data()` or `fetch_expense_data()`; all entities are provided by data sources registered with `WPDAI_Custom_Data_Source_Registry`.
- The warehouse adds `execution_time` and `memory_usage` to each entity after the source's `fetch_data( $data_warehouse )` returns. Data sources should not return these keys.
- Each data source is run at most once per warehouse instance (`fetched_custom_sources` tracks by source object id).
- Used by `WPDAI_Report_Builder` and `WPDAI_Reporting_API` for report and REST data.
- Date ranges respect the store timezone. `date_format_display` 'minute' uses `minutes_ago` (1–60) and yields a different date container shape.
- Campaign and analytics entities require the corresponding tracking/setup. Subscriptions require WooCommerce Subscriptions.

### Related Classes & Docs

- `WPDAI_Custom_Data_Source_Registry` – Registry of data sources; `has()` / `get()` by entity name
- `WPDAI_Custom_Data_Source_Base` – Base class for custom data sources; implement `fetch_data( WPDAI_Data_Warehouse $data_warehouse )`
- `WPDAI_Report_Builder` – Uses the warehouse to fetch data for React reports
- `WPDAI_Order_Calculator` – Order profit calculations
- `WPDAI_Reporting_API` – REST API that uses the warehouse for data endpoints
- Custom Data Sources (developers docs) – Data Warehouse API Reference and fetch_data() method