late 0.0.1340

API reference for Zernio. Authenticate with a Bearer API key. Base URL: https://zernio.com/api Versioning and deprecation: all endpoints are versioned in the URL path (current version: /v1). Breaking changes only ship in a new path version; existing versions keep working. Deprecated operations are marked 'deprecated: true' in this spec and announced in the changelog (https://zernio.com/changelog) before removal. Errors: every 4xx/5xx response is application/json with a machine-readable 'code' and a human-readable 'error' message (see the ErrorResponse schema). Request ids: responses carry an X-Request-Id header with the id we log the request under. Quote it when reporting a problem. A valid x-request-id you send is reused as that id.
Documentation
/*
 * Zernio API
 *
 * API reference for Zernio. Authenticate with a Bearer API key. Base URL: https://zernio.com/api  Versioning and deprecation: all endpoints are versioned in the URL path (current version: /v1). Breaking changes only ship in a new path version; existing versions keep working. Deprecated operations are marked 'deprecated: true' in this spec and announced in the changelog (https://zernio.com/changelog) before removal.  Errors: every 4xx/5xx response is application/json with a machine-readable 'code' and a human-readable 'error' message (see the ErrorResponse schema).  Request ids: responses carry an X-Request-Id header with the id we log the request under. Quote it when reporting a problem. A valid x-request-id you send is reused as that id.
 *
 * The version of the OpenAPI document: 1.522.1
 * Contact: support@zernio.com
 * Generated by: https://openapi-generator.tech
 */

use crate::models;
use serde::{Deserialize, Serialize};

/// AdDailyMetrics : One day of metrics: the same fields as `AdMetrics` plus the `date` they apply to. Returned inside a node's `daily[]` when `GET /v1/ads/tree` is called with `timeIncrement=1`.  Rate metrics (ctr/cpc/cpm/costPerConversion/roas/videoAvgTimeWatchedActions) are recomputed per day from that day's sums, so summing the additive fields across a node's `daily[]` reproduces its aggregated `metrics` total.  Exceptions:  - `reach`: on Meta and TikTok the aggregated total is de-duplicated across the range, so daily reach does not sum to it. - `videoAvgTimeWatchedActions`: do NOT sum or plain-average it across days. The range value is the play-weighted average of the daily values.
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct AdDailyMetrics {
    #[serde(rename = "spend", skip_serializing_if = "Option::is_none")]
    pub spend: Option<f64>,
    #[serde(rename = "impressions", skip_serializing_if = "Option::is_none")]
    pub impressions: Option<i32>,
    /// Unique people reached in the requested date range.  - **Meta (facebook/instagram) and TikTok:** the platform's own de-duplicated reach for the exact range, fetched live and cached up to ~1 hour (may lag recent delivery). - **Meta and TikTok fallback:** on a transient platform error the value temporarily falls back to a sum of per-day reach, which overcounts people reached on multiple days or by multiple child ads. - **Not additive (Meta and TikTok):** because reach is de-duplicated, neither daily values nor child nodes sum to the range total. - **LinkedIn:** the campaign's `approximateMemberReach`, de-duplicated by LinkedIn per day and summed over the range here, so it overcounts members reached on more than one day. LinkedIn withholds it below its audience privacy threshold, where it reads 0. - **Google, X, Pinterest and OpenAI:** 0 (reach not synced).  Frequency (impressions / reach) is only meaningful for Meta and TikTok.
    #[serde(rename = "reach", skip_serializing_if = "Option::is_none")]
    pub reach: Option<i32>,
    #[serde(rename = "clicks", skip_serializing_if = "Option::is_none")]
    pub clicks: Option<i32>,
    /// Click-through rate (%)
    #[serde(rename = "ctr", skip_serializing_if = "Option::is_none")]
    pub ctr: Option<f64>,
    /// Cost per click
    #[serde(rename = "cpc", skip_serializing_if = "Option::is_none")]
    pub cpc: Option<f64>,
    /// Cost per 1000 impressions
    #[serde(rename = "cpm", skip_serializing_if = "Option::is_none")]
    pub cpm: Option<f64>,
    #[serde(rename = "engagement", skip_serializing_if = "Option::is_none")]
    pub engagement: Option<i32>,
    /// Count of conversion events over the requested date range. FRACTIONAL: attribution splits one conversion across touchpoints and Google also reports modeled conversions, so values like 0.347 are normal. 0 for non-conversion campaigns or when no events have fired.  - Meta: the ad set's Ads Manager Results, derived per ad set (see below). - Google: the account's tracked conversions. - X and LinkedIn: their reported website/lead conversions (added 2026-07). - Reddit: key conversions (`key_conversion_total_count`, added 2026-10-23; older days read 0).  Meta uses the optimization goal's result: LEAD_GENERATION and QUALITY_LEAD = leads, CONVERSATIONS = messaging conversations started, APP_INSTALLS = installs, LANDING_PAGE_VIEWS = landing page views, LINK_CLICKS = link clicks, THRUPLAY = ThruPlays. REACH, IMPRESSIONS, POST_ENGAGEMENT and PAGE_LIKES report 0.  Otherwise Meta uses the promoted_object event: custom_conversion_id when set, else custom_event_type, else PURCHASE.
    #[serde(rename = "conversions", skip_serializing_if = "Option::is_none")]
    pub conversions: Option<f64>,
    /// All conversions, including actions excluded from the Conversions column (Google metrics.all_conversions). 0 on platforms without the concept.
    #[serde(rename = "allConversions", skip_serializing_if = "Option::is_none")]
    pub all_conversions: Option<f64>,
    /// Derived spend / conversions in the same currency as spend. 0 when conversions is 0.
    #[serde(rename = "costPerConversion", skip_serializing_if = "Option::is_none")]
    pub cost_per_conversion: Option<f64>,
    /// Per-action-type counts summed over the date range, keyed by the platform's action-type names. Keys differ by platform, so branch on the ad's platform when interpreting them. Empty object when no actions are reported.  - Meta: raw Insights action_type keys (link_click, offsite_conversion.fb_pixel_purchase, onsite_conversion.lead_grouped, ...), covering both engagement and conversion events. - Meta newer standard events (Schedule, SubmitApplication, Contact, StartTrial, Subscribe and the like) come only from Meta's `conversions` insight field, under keys such as schedule_total, schedule_website or offsite_schedule_add_<dataset>. - Since 2026-11-03 every `conversions` key that `actions` lacks is added here, rounded per key, and its `conversion_values` value to `actionValues`. A key both fields report keeps its `actions` value. - Days synced before 2026-11-03 lack those keys until re-fetched. Each sync restates the last 30 days. - TikTok: conversion events plus the paid-engagement family (follow, post_reaction for paid likes, comment, share). Follow is how FOLLOWERS-goal campaigns report their result. - TikTok website (pixel) events: complete_payment (website purchase, counted in `conversions` by SHOPPING-optimised campaigns), place_order, initiate_checkout, add_to_cart, view_content, lead (website form submissions plus TikTok instant-form leads). - TikTok place_order is Place an Order, the website purchase of pixels set up with the legacy ON_WEB_ORDER event. It is absent from days synced before this metric was added until the ad is re-synced. - TikTok app events: purchase, view_content (added to the website count). - TikTok history: website add_to_cart, initiate_checkout, complete_payment and lead were missing before 2026-10-08 (TikTok rejected the metric names we requested). Each sync re-fetches only the last 7 days, so older days lack them until the ad is re-synced from scratch. - X: conversion types (purchase, sign_up, site_visit, download, custom). - LinkedIn: conversion types (post_click, post_view, lead_gen) plus `video_view`. It carries LinkedIn's `videoViews` under the key every other platform uses, so it feeds `engagementBreakdown.videoViews` too. It is an engagement, NOT a conversion, and is excluded from `conversions`. - Google: returns {} (its per-action names aren't synced per ad).
    #[serde(rename = "actions", skip_serializing_if = "Option::is_none")]
    pub actions: Option<std::collections::HashMap<String, i32>>,
    /// Monetary mirror of `actions`, from Meta's Insights `action_values[]` array. Meta and TikTok; other platforms return {}. Same history note as `actions`.  Same keying as `actions`: values are the revenue attributed to each action_type, in ad-account native currency (same unit as `spend`; see the campaign node's `currency` field). Use it to compute revenue-per-event (e.g. avg purchase value).  TikTok: the value TikTok attributes to each key of `actions`, present only when the advertiser sends a value with the event. Keys: purchase and view_content from app events; complete_payment, initiate_checkout, add_to_cart, view_content and lead from the website pixel.  initiate_checkout and add_to_cart values are basket values, not revenue. Revenue is complete_payment or place_order (website) and purchase (app).
    #[serde(rename = "actionValues", skip_serializing_if = "Option::is_none")]
    pub action_values: Option<std::collections::HashMap<String, f64>>,
    /// Convenience sum of purchase-type action values, in ad-account native currency. 0 when the campaign has no purchase event configured.  Meta: picked from `actionValues` for the ad set promoted_object event (custom conversion, custom_event_type, else PURCHASE). It matches `conversions` for conversion-optimized ad sets. When the optimization goal reports a different result (LINK_CLICKS, LANDING_PAGE_VIEWS, ...) it stays on the promoted event, so ROAS keeps its purchase basis.  - TikTok: app purchase value plus website complete-payment value (from 2026-10-08), or the Place an Order value when that is larger. They are never summed, since a pixel can fire both for one sale. Days synced before each was added carry the app value only until the ad is re-synced. - Google: `conversions_value` of every primary conversion action. - Pinterest: checkout value. - LinkedIn: `conversionValueInLocalCurrency`, the advertiser-defined value of every conversion (days synced before 2026-10-23 report 0). - Reddit: `conversion_purchase_total_value` (same history note). - Other platforms report 0.
    #[serde(rename = "purchaseValue", skip_serializing_if = "Option::is_none")]
    pub purchase_value: Option<f64>,
    /// Return on ad spend, derived as `purchaseValue / spend`. 0 when `spend` is 0. Equivalent to Meta's `purchase_roas` under default attribution. At ad-set and campaign levels this is recomputed from summed purchaseValue + spend (NOT averaged across children) so it's mathematically correct at every rollup level.
    #[serde(rename = "roas", skip_serializing_if = "Option::is_none")]
    pub roas: Option<f64>,
    /// Derived `spend / actions[type]` for every action type with a non-zero count, in ad-account native currency. Same keys as `actions`.  - Rounded to 4 decimals, because cheap actions cost well under a cent. - Recomputed from summed spend + counts at every rollup level. - Empty object when spend is 0 or no actions are reported.
    #[serde(rename = "costPerAction", skip_serializing_if = "Option::is_none")]
    pub cost_per_action: Option<std::collections::HashMap<String, f64>>,
    /// Clicks leading off Meta's surfaces to the advertiser's destination. Meta-only; other platforms report 0.
    #[serde(rename = "outboundClicks", skip_serializing_if = "Option::is_none")]
    pub outbound_clicks: Option<i32>,
    /// Derived `outboundClicks / impressions * 100`, recomputed from sums at every rollup level.
    #[serde(rename = "outboundClicksCtr", skip_serializing_if = "Option::is_none")]
    pub outbound_clicks_ctr: Option<f64>,
    /// In-session link clicks. Differs from the attributed `link_click` count in `actions`/`engagementBreakdown.linkClicks`, which uses the attribution window. Meta-only.
    #[serde(rename = "inlineLinkClicks", skip_serializing_if = "Option::is_none")]
    pub inline_link_clicks: Option<i32>,
    /// Derived `inlineLinkClicks / impressions * 100`, recomputed from sums at every rollup level.
    #[serde(rename = "inlineLinkClickCtr", skip_serializing_if = "Option::is_none")]
    pub inline_link_click_ctr: Option<f64>,
    /// People who clicked at least once. NOT additive: summed across days/children it overcounts people who clicked on multiple days or ads, so treat rollups as an upper bound (same caveat as `reach`). Meta-only.
    #[serde(rename = "uniqueClicks", skip_serializing_if = "Option::is_none")]
    pub unique_clicks: Option<i32>,
    /// Derived `uniqueClicks / impressions * 100` (NOT Meta's reach-based unique_ctr). Inherits the non-additivity caveat of `uniqueClicks`.
    #[serde(rename = "uniqueCtr", skip_serializing_if = "Option::is_none")]
    pub unique_ctr: Option<f64>,
    /// Number of times the video started playing, summed over the date range and across children at ad-set/campaign level. 0 for non-video ads.  Sources: Meta `video_play_actions`, TikTok `video_play_actions`, X `video_content_starts`, Reddit `video_started`, Pinterest `TOTAL_VIDEO_P0_COMBINED`.  **X, Reddit and Pinterest history**: added 2026-10-23, and their syncs only re-fetch recent days, so older days read 0 on every X, Reddit and Pinterest video field.
    #[serde(rename = "videoPlayActions", skip_serializing_if = "Option::is_none")]
    pub video_play_actions: Option<i32>,
    /// Views of at least 30 seconds (or to the end, for shorter videos). Sources: Meta `video_30_sec_watched_actions`, Google `video_trueview_views` (a TrueView view: 30 seconds, the whole video if shorter, or an interaction; Google history note: added 2026-10-23, older days read 0).
    #[serde(
        rename = "video30SecWatchedActions",
        skip_serializing_if = "Option::is_none"
    )]
    pub video30_sec_watched_actions: Option<i32>,
    /// ThruPlays (watched to completion, or at least 15 seconds). Sources: Meta `video_thruplay_watched_actions` (Meta only).
    #[serde(
        rename = "videoThruplayWatchedActions",
        skip_serializing_if = "Option::is_none"
    )]
    pub video_thruplay_watched_actions: Option<i32>,
    /// Views reaching 25% of the video's length. With the other percentile fields, powers hook/hold/drop-off analysis (e.g. hook rate = videoP25WatchedActions / videoPlayActions). Sources: Meta `video_p25_watched_actions`, TikTok `video_views_p25`, X `video_views_25`, Reddit `video_watched_25_percent`, Google `video_quartile_p25_rate` x impressions, Pinterest `TOTAL_VIDEO_P25_COMBINED`.
    #[serde(
        rename = "videoP25WatchedActions",
        skip_serializing_if = "Option::is_none"
    )]
    pub video_p25_watched_actions: Option<i32>,
    /// Views reaching 50% of the video's length. Sources: Meta `video_p50_watched_actions`, TikTok `video_views_p50`, X `video_views_50`, Reddit `video_watched_50_percent`, Google `video_quartile_p50_rate` x impressions, Pinterest `TOTAL_VIDEO_P50_COMBINED`.
    #[serde(
        rename = "videoP50WatchedActions",
        skip_serializing_if = "Option::is_none"
    )]
    pub video_p50_watched_actions: Option<i32>,
    /// Views reaching 75% of the video's length. Sources: Meta `video_p75_watched_actions`, TikTok `video_views_p75`, X `video_views_75`, Reddit `video_watched_75_percent`, Google `video_quartile_p75_rate` x impressions, Pinterest `TOTAL_VIDEO_P75_COMBINED`.
    #[serde(
        rename = "videoP75WatchedActions",
        skip_serializing_if = "Option::is_none"
    )]
    pub video_p75_watched_actions: Option<i32>,
    /// Views reaching 95% of the video's length. Sources: Meta `video_p95_watched_actions`, Pinterest `TOTAL_VIDEO_P95_COMBINED`.
    #[serde(
        rename = "videoP95WatchedActions",
        skip_serializing_if = "Option::is_none"
    )]
    pub video_p95_watched_actions: Option<i32>,
    /// Views reaching 100% of the video's length. Sources: Meta `video_p100_watched_actions`, TikTok `video_views_p100`, X `video_views_100`, Reddit `video_watched_100_percent`, Google `video_quartile_p100_rate` x impressions, Pinterest `TOTAL_VIDEO_P100_COMPLETE`.
    #[serde(
        rename = "videoP100WatchedActions",
        skip_serializing_if = "Option::is_none"
    )]
    pub video_p100_watched_actions: Option<i32>,
    /// Plays of at least 2 seconds, replays excluded. Hook rate = video2SecWatchedActions / impressions. Sources: TikTok `video_watched_2s` (TikTok only; Meta's closest field, 2-second continuous plays, is not synced). TikTok history note: added 2026-10, and each sync re-fetches only the last 7 days, so older days read 0.
    #[serde(
        rename = "video2SecWatchedActions",
        skip_serializing_if = "Option::is_none"
    )]
    pub video2_sec_watched_actions: Option<i32>,
    /// Plays of at least 6 seconds, replays excluded. Hold rate = video6SecWatchedActions / video2SecWatchedActions. Sources: TikTok `video_watched_6s` (TikTok only). Same history note as `video2SecWatchedActions`.
    #[serde(
        rename = "video6SecWatchedActions",
        skip_serializing_if = "Option::is_none"
    )]
    pub video6_sec_watched_actions: Option<i32>,
    /// TikTok's 6-second focused views: plays of at least 6 seconds (or to the end, for shorter videos) or with an interaction in the first 6 seconds, so it is at least `video6SecWatchedActions`. Sources: TikTok `engaged_view` (TikTok only). Same history note as `video2SecWatchedActions`.
    #[serde(
        rename = "video6SecFocusedViews",
        skip_serializing_if = "Option::is_none"
    )]
    pub video6_sec_focused_views: Option<i32>,
    /// Average seconds watched per play. Aggregated over date ranges and across children as a play-weighted average (total watch time / total plays), never a plain average of averages. Sources: Meta `video_avg_time_watched_actions`, TikTok `average_video_play`.
    #[serde(
        rename = "videoAvgTimeWatchedActions",
        skip_serializing_if = "Option::is_none"
    )]
    pub video_avg_time_watched_actions: Option<f64>,
    /// Derived `spend / videoThruplayWatchedActions`, in ad-account native currency. Rounded to 4 decimals rather than the usual 2 because a ThruPlay routinely costs well under a cent. 0 when the ad has no ThruPlays (ThruPlay is Meta-only).
    #[serde(rename = "costPerThruplay", skip_serializing_if = "Option::is_none")]
    pub cost_per_thruplay: Option<f64>,
    #[serde(rename = "funnel", skip_serializing_if = "Option::is_none")]
    pub funnel: Option<Box<models::AdFunnelCounts>>,
    #[serde(
        rename = "engagementBreakdown",
        skip_serializing_if = "Option::is_none"
    )]
    pub engagement_breakdown: Option<Box<models::AdEngagementCounts>>,
    /// Present on individual ads only, not on campaign aggregations
    #[serde(rename = "lastSyncedAt", skip_serializing_if = "Option::is_none")]
    pub last_synced_at: Option<String>,
    /// Calendar day (YYYY-MM-DD) these metrics apply to.
    #[serde(rename = "date", skip_serializing_if = "Option::is_none")]
    pub date: Option<String>,
}

impl AdDailyMetrics {
    /// One day of metrics: the same fields as `AdMetrics` plus the `date` they apply to. Returned inside a node's `daily[]` when `GET /v1/ads/tree` is called with `timeIncrement=1`.  Rate metrics (ctr/cpc/cpm/costPerConversion/roas/videoAvgTimeWatchedActions) are recomputed per day from that day's sums, so summing the additive fields across a node's `daily[]` reproduces its aggregated `metrics` total.  Exceptions:  - `reach`: on Meta and TikTok the aggregated total is de-duplicated across the range, so daily reach does not sum to it. - `videoAvgTimeWatchedActions`: do NOT sum or plain-average it across days. The range value is the play-weighted average of the daily values.
    pub fn new() -> AdDailyMetrics {
        AdDailyMetrics {
            spend: None,
            impressions: None,
            reach: None,
            clicks: None,
            ctr: None,
            cpc: None,
            cpm: None,
            engagement: None,
            conversions: None,
            all_conversions: None,
            cost_per_conversion: None,
            actions: None,
            action_values: None,
            purchase_value: None,
            roas: None,
            cost_per_action: None,
            outbound_clicks: None,
            outbound_clicks_ctr: None,
            inline_link_clicks: None,
            inline_link_click_ctr: None,
            unique_clicks: None,
            unique_ctr: None,
            video_play_actions: None,
            video30_sec_watched_actions: None,
            video_thruplay_watched_actions: None,
            video_p25_watched_actions: None,
            video_p50_watched_actions: None,
            video_p75_watched_actions: None,
            video_p95_watched_actions: None,
            video_p100_watched_actions: None,
            video2_sec_watched_actions: None,
            video6_sec_watched_actions: None,
            video6_sec_focused_views: None,
            video_avg_time_watched_actions: None,
            cost_per_thruplay: None,
            funnel: None,
            engagement_breakdown: None,
            last_synced_at: None,
            date: None,
        }
    }
}