@@ -340,12 +340,17 @@ class ImpressionShareReportRow(BaseModel):
340340 """A row in an impression share report.
341341
342342 Impression share shows how often your ads appeared compared
343- to the total available impressions.
343+ to the total available impressions. Values are percentage ranges.
344344
345345 Attributes:
346- metadata: Information about the entity.
347- impression_share: Your share of impressions (0.0 to 1.0).
346+ metadata: Information about the entity (keyword, ad group, campaign).
347+ impression_share: Your share of impressions as a percentage range.
348+ low_impression_share: Low end of impression share range.
349+ high_impression_share: High end of impression share range.
348350 rank: Your rank compared to competitors.
351+ low_rank: Low end of rank range.
352+ high_rank: High end of rank range.
353+ search_popularity: Popularity of the search term (0-5 scale).
349354 """
350355
351356 model_config = ConfigDict (populate_by_name = True , extra = "ignore" )
@@ -357,3 +362,130 @@ class ImpressionShareReportRow(BaseModel):
357362 rank : int | None = None
358363 low_rank : int | None = Field (default = None , alias = "lowRank" )
359364 high_rank : int | None = Field (default = None , alias = "highRank" )
365+ search_popularity : int | None = Field (default = None , alias = "searchPopularity" )
366+
367+
368+ class ImpressionShareDateRange (StrEnum ):
369+ """Date range options for weekly impression share reports."""
370+
371+ LAST_WEEK = "LAST_WEEK"
372+ LAST_2_WEEKS = "LAST_2_WEEKS"
373+ LAST_4_WEEKS = "LAST_4_WEEKS"
374+
375+
376+ class ImpressionShareReportStatus (StrEnum ):
377+ """Status of an impression share report."""
378+
379+ QUEUED = "QUEUED"
380+ RUNNING = "RUNNING"
381+ COMPLETED = "COMPLETED"
382+ FAILED = "FAILED"
383+
384+
385+ class ImpressionShareReportRequest (BaseModel ):
386+ """Request model for creating an impression share report.
387+
388+ Impression share reports are async - you create a report request,
389+ then poll for results.
390+
391+ Example:
392+ Create a daily impression share report::
393+
394+ request = ImpressionShareReportRequest(
395+ name="my_report",
396+ start_time=date(2024, 1, 1),
397+ end_time=date(2024, 1, 31),
398+ granularity=GranularityType.DAILY,
399+ )
400+ """
401+
402+ model_config = ConfigDict (populate_by_name = True , extra = "ignore" )
403+
404+ name : str = "impression_share_report"
405+ start_time : date = Field (alias = "startTime" )
406+ end_time : date = Field (alias = "endTime" )
407+ granularity : GranularityType = GranularityType .DAILY
408+ date_range : ImpressionShareDateRange | None = Field (default = None , alias = "dateRange" )
409+ selector : dict [str , Any ] | None = None
410+ return_records_with_no_metrics : bool = Field (default = True , alias = "returnRecordsWithNoMetrics" )
411+ return_row_totals : bool = Field (default = False , alias = "returnRowTotals" )
412+ return_grand_totals : bool = Field (default = False , alias = "returnGrandTotals" )
413+
414+
415+ class ImpressionShareReport (BaseModel ):
416+ """An impression share report with metadata and rows.
417+
418+ Attributes:
419+ id: The report ID.
420+ name: The report name.
421+ state: Current state of the report (QUEUED, RUNNING, COMPLETED, FAILED).
422+ start_time: Report start date.
423+ end_time: Report end date.
424+ granularity: Time granularity (DAILY or WEEKLY).
425+ row: List of report rows with impression share data.
426+ """
427+
428+ model_config = ConfigDict (populate_by_name = True , extra = "ignore" )
429+
430+ id : int
431+ name : str | None = None
432+ state : ImpressionShareReportStatus | str = Field (alias = "state" )
433+ start_time : str | None = Field (default = None , alias = "startTime" )
434+ end_time : str | None = Field (default = None , alias = "endTime" )
435+ granularity : GranularityType | str | None = None
436+ date_range : str | None = Field (default = None , alias = "dateRange" )
437+ row : list [ImpressionShareReportRow ] = Field (default_factory = list )
438+ grand_totals : GrandTotals | None = Field (default = None , alias = "grandTotals" )
439+
440+ @property
441+ def is_complete (self ) -> bool :
442+ """Check if the report has finished generating."""
443+ return self .state == ImpressionShareReportStatus .COMPLETED
444+
445+ @property
446+ def is_failed (self ) -> bool :
447+ """Check if the report generation failed."""
448+ return self .state == ImpressionShareReportStatus .FAILED
449+
450+ def to_dataframe (self ) -> "pd.DataFrame" :
451+ """Convert impression share report to a pandas DataFrame.
452+
453+ Returns:
454+ A DataFrame with impression share data.
455+
456+ Raises:
457+ ImportError: If pandas is not installed.
458+ """
459+ try :
460+ import pandas as pd
461+ except ImportError :
462+ raise ImportError (
463+ "pandas is required for to_dataframe(). "
464+ "Install with: pip install asa-api-client[pandas]"
465+ ) from None
466+
467+ rows_data : list [dict [str , Any ]] = []
468+
469+ for row in self .row :
470+ row_data : dict [str , Any ] = {}
471+
472+ if row .metadata :
473+ meta_dict = row .metadata .model_dump (by_alias = False , exclude_none = True )
474+ if meta_dict .get ("bid_amount" ):
475+ row_data ["bid" ] = meta_dict ["bid_amount" ].get ("amount" )
476+ row_data ["currency" ] = meta_dict ["bid_amount" ].get ("currency" )
477+ del meta_dict ["bid_amount" ]
478+ row_data .update (meta_dict )
479+
480+ # Add impression share fields
481+ row_data ["impression_share" ] = row .impression_share
482+ row_data ["low_impression_share" ] = row .low_impression_share
483+ row_data ["high_impression_share" ] = row .high_impression_share
484+ row_data ["rank" ] = row .rank
485+ row_data ["low_rank" ] = row .low_rank
486+ row_data ["high_rank" ] = row .high_rank
487+ row_data ["search_popularity" ] = row .search_popularity
488+
489+ rows_data .append (row_data )
490+
491+ return pd .DataFrame (rows_data )
0 commit comments