from typing import Union, Dict, Any from marshmallow import fields, Schema, EXCLUDE, validate from charts.constants.chart_names import CHART_DEFINITION_KEY_TO_CHART_NAME_MAPPING from charts.constants.common import CHART_EXPECTED_UPDATE_FREQUENCY from charts.constants.track.charts import DEFAULT_TRACK_IN_CHART_ORDER_BY from charts.schemas.fields.chart_name import ChartNameMixin from charts.schemas.request_schema import RequestSchema def _check_entry_bluedot(data: Dict[str, Any]) -> bool: """ Check if this is the first time ever track entered a chart Args: data: Dict containing track in chart information Returns: Boolean value """ return data["track_in_chart_previous_position"] is None def _check_reentry_bluedot(data: Dict[str, Any]) -> bool: """ Check if the track re-entered a particular chart Frequency update for charts should be: 1 day for weekly 7 days for weekly If the count of days between records update is greater than expected it means that track was not present in a chart and re-entered it because we could calculate this value between two records Args: data: Dict containing track in chart information Returns: Boolean value """ days_between_records_update = data["days_between_records_update"] if not days_between_records_update: return False return days_between_records_update > CHART_EXPECTED_UPDATE_FREQUENCY[data["chart_frequency"]] def _get_bluedot(data: Dict[str, Any]) -> bool: """Calculate is this track is a new entry or re-entry""" return _check_entry_bluedot(data) or _check_reentry_bluedot(data) class TrackCharts: """Track Charts Request & Response schemas to use with flask_apispec @use_kwargs & @marhal_with decorators""" class Request(RequestSchema): isrc = fields.String(required=True) platforms = fields.List(fields.String(required=True), required=True,) current_appearances = fields.Boolean(load_default=True) order_by = fields.String( load_default=DEFAULT_TRACK_IN_CHART_ORDER_BY, validate=validate.OneOf(["current_position", "country_name"]), ) class Response(Schema, ChartNameMixin): """Chart Track response schema with additional calculations and data manipulations""" class Meta: unknown = EXCLUDE class NestedCountryItem(Schema): chart_country_code = fields.String(data_key="countryCode") addedDate = fields.Method("track_last_added_date") track_in_chart_streams = fields.Integer(data_key="streamsNumber", allow_none=True) track_in_chart_most_recent_position = fields.Integer(data_key="currentPosition") positionChange = fields.Method("position_change") newlyEntered = fields.Method("blue_dot") def position_change(self, data, *args, **kwargs) -> Union[int, None]: """Position change is a difference between current and previous position in a chart Is calculated for the charts that has two consecutive records of update by the chart frequency Is not calculated for entry or reentry. 1. Positive result - that track moved up in a chart 2. Negative - track moved down """ current_position = data["track_in_chart_most_recent_position"] previous_position = data["track_in_chart_previous_position"] if not _get_bluedot(data) and previous_position and current_position: return previous_position - current_position def blue_dot(self, data, *args, **kwargs) -> bool: """Blue dot is shown for newly added or reentries of a track in a chart """ return _get_bluedot(data) def track_last_added_date(self, data, *args, **kwargs): """For newly entered last_added_date will be null""" if _get_bluedot(data): return data["latest_chart_date"].isoformat() track_last_added = data["last_date_track_was_added_to_chart"] if track_last_added: return track_last_added.isoformat() platform = fields.String() latest_chart_date = fields.String(data_key="chartDate") chartCount = fields.Method("chart_count") countries = fields.List(fields.Nested(NestedCountryItem())) def chart_count(self, data, *args, **kwargs) -> int: """Count the number of a charts for a track""" return len(data["countries"]) def _get_chart_name(self, data, *args, **kwargs): """Get a proper chart name""" return CHART_DEFINITION_KEY_TO_CHART_NAME_MAPPING[data["definition_key_mapping"]]