late 0.0.1055

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.104.0
 * Contact: support@zernio.com
 * Generated by: https://openapi-generator.tech
 */

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

#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct PostAnalytics {
    #[serde(rename = "impressions", skip_serializing_if = "Option::is_none")]
    pub impressions: Option<i32>,
    #[serde(rename = "reach", skip_serializing_if = "Option::is_none")]
    pub reach: Option<i32>,
    #[serde(rename = "likes", skip_serializing_if = "Option::is_none")]
    pub likes: Option<i32>,
    #[serde(rename = "comments", skip_serializing_if = "Option::is_none")]
    pub comments: Option<i32>,
    #[serde(rename = "shares", skip_serializing_if = "Option::is_none")]
    pub shares: Option<i32>,
    /// Number of saves/bookmarks (Instagram, Pinterest, X)
    #[serde(rename = "saves", skip_serializing_if = "Option::is_none")]
    pub saves: Option<i32>,
    /// Link clicks where the platform reports them. Always 0 on Facebook Page stories: Meta exposes no link-click metric for stories.
    #[serde(rename = "clicks", skip_serializing_if = "Option::is_none")]
    pub clicks: Option<i32>,
    #[serde(rename = "views", skip_serializing_if = "Option::is_none")]
    pub views: Option<i32>,
    /// Instagram feed posts and stories only: organic accounts that started following from this post. Null on Instagram Reels and non-Reels video, where Meta does not expose this metric for the media. 0 for other platforms.
    #[serde(
        rename = "follows",
        default,
        with = "::serde_with::rust::double_option",
        skip_serializing_if = "Option::is_none"
    )]
    pub follows: Option<Option<i32>>,
    /// Instagram Reels only: average watch time per play, in milliseconds. 0 for non-Reels media and other platforms.
    #[serde(
        rename = "igReelsAvgWatchTime",
        skip_serializing_if = "Option::is_none"
    )]
    pub ig_reels_avg_watch_time: Option<i32>,
    /// Instagram Reels only: total watch time including replays, in milliseconds. 0 for non-Reels media and other platforms.
    #[serde(
        rename = "igReelsVideoViewTotalTime",
        skip_serializing_if = "Option::is_none"
    )]
    pub ig_reels_video_view_total_time: Option<i32>,
    /// Instagram Reels only: percentage (0-100) of initial views that skipped the reel within its first 3 seconds, as reported by Meta. Meta labels the metric estimated and in development, so it can move between syncs. 0 for non-Reels media and other platforms. When a post is published to several accounts, the aggregate is weighted by views.
    #[serde(rename = "reelsSkipRate", skip_serializing_if = "Option::is_none")]
    pub reels_skip_rate: Option<f64>,
    /// TikTok accounts connected through the TikTok for Business app only: share of viewers who watched the video to the end, 0 to 1, as TikTok reports it (T+24-48h, only for posts active in the last 7 days). 0 for other platforms. When a post is published to several accounts, the aggregate is weighted by views.
    #[serde(rename = "completionRate", skip_serializing_if = "Option::is_none")]
    pub completion_rate: Option<f64>,
    /// TikTok accounts connected through the TikTok for Business app only: profile views from users who reached the profile through this post (T+24-48h). 0 for other platforms.
    #[serde(rename = "profileViews", skip_serializing_if = "Option::is_none")]
    pub profile_views: Option<i32>,
    /// TikTok accounts connected through the TikTok for Business app only: clicks on the profile website link attributed to this post (T+24-48h). Also counted inside `clicks`, which sums every profile-link type (website, phone, email, address, app download). 0 for other platforms.
    #[serde(rename = "websiteClicks", skip_serializing_if = "Option::is_none")]
    pub website_clicks: Option<i32>,
    /// TikTok accounts connected through the TikTok for Business app only: share of views by surface, as fractions 0 to 1 (T+24-48h, only for posts active in the last 7 days). Keys: `forYou`, `follow`, `search`, `personalProfile`, `sound`, `directMessage`, `other`; a surface TikTok adds later appears under a camelCase key derived from its name. Empty object when TikTok reports nothing, and for other platforms. When a post is published to several accounts, each share is weighted by views.
    #[serde(rename = "impressionSources", skip_serializing_if = "Option::is_none")]
    pub impression_sources: Option<std::collections::HashMap<String, f64>>,
    /// TikTok accounts connected through the TikTok for Business app only: two viewer splits as fractions 0 to 1 (T+24-48h). Each pair sums to 1 when present, `follower` + `nonFollower` and `newViewer` + `returnViewer`; TikTok can report one pair without the other. Empty object when TikTok reports nothing, and for other platforms. Views-weighted across accounts like impressionSources.
    #[serde(rename = "audienceTypes", skip_serializing_if = "Option::is_none")]
    pub audience_types: Option<std::collections::HashMap<String, f64>>,
    /// TikTok accounts connected through the TikTok for Business app only: share of views by viewer country as fractions 0 to 1, keyed by upper-case ISO-3166 alpha-2 code (T+24-48h, only for posts active in the last 7 days). At most 20 country keys plus `other`: the catch-all bucket TikTok sends, any country below 0.001 and anything past the twentieth all sum into `other`, so the values still add up to 1. Empty object when TikTok reports nothing, and for other platforms. Views-weighted across accounts like impressionSources.
    #[serde(rename = "audienceCountries", skip_serializing_if = "Option::is_none")]
    pub audience_countries: Option<std::collections::HashMap<String, f64>>,
    /// Instagram accounts connected with Facebook Login only: reposts of the media by other users, minus deleted reposts, on feed posts, reels and stories. Meta does not expose this metric for accounts connected with Instagram Login, so those always report 0. 0 for other platforms, including Threads, where reposts are counted in shares instead.
    #[serde(rename = "reposts", skip_serializing_if = "Option::is_none")]
    pub reposts: Option<i32>,
    /// Video length in seconds. Currently Instagram Reels only; combine with igReelsAvgWatchTime (ms) to estimate retention. Null when unknown (other platforms, non-video media, or when Instagram does not expose the media URL, e.g. reels with copyrighted audio).
    #[serde(
        rename = "videoDurationSeconds",
        default,
        with = "::serde_with::rust::double_option",
        skip_serializing_if = "Option::is_none"
    )]
    pub video_duration_seconds: Option<Option<i32>>,
    /// Percentage, rounded to 2 decimals: (likes + comments + shares + saves) / (impressions or reach or views) * 100. Clicks and follows are never counted. The denominator is the FIRST of impressions, reach, views that is non-zero, so it is not the same basis on every post: a post with impressions divides by impressions, one without falls back to reach, then to views. If you need a single consistent basis (e.g. interactions / reach), compute it from the raw fields above. When this object is the post-level roll-up of a cross-posted post, the denominator is the SUM of the denominators resolved per platform entry: an entry reporting none of impressions, reach or views is excluded from both sides of the ratio. Each platformAnalytics entry below keeps its own single denominator. The engagementRate on the LinkedIn account endpoints is a different formula.
    #[serde(rename = "engagementRate", skip_serializing_if = "Option::is_none")]
    pub engagement_rate: Option<f64>,
    #[serde(rename = "lastUpdated", skip_serializing_if = "Option::is_none")]
    pub last_updated: Option<String>,
}

impl PostAnalytics {
    pub fn new() -> PostAnalytics {
        PostAnalytics {
            impressions: None,
            reach: None,
            likes: None,
            comments: None,
            shares: None,
            saves: None,
            clicks: None,
            views: None,
            follows: None,
            ig_reels_avg_watch_time: None,
            ig_reels_video_view_total_time: None,
            reels_skip_rate: None,
            completion_rate: None,
            profile_views: None,
            website_clicks: None,
            impression_sources: None,
            audience_types: None,
            audience_countries: None,
            reposts: None,
            video_duration_seconds: None,
            engagement_rate: None,
            last_updated: None,
        }
    }
}