Format: text/x-python
⬇ Download File (4.7 KB)
   1  import json
   2  import io
   3  import boto3
   4  import pandas as pd
   5  from datetime import datetime, timedelta
   6  from logger_setup import setup_logger
   7  
   8  logger = setup_logger("recall_sampler/data_writer")
   9  
  10  class DataWriter:
  11      def __init__(self, output_config: dict, customer_name: str):
  12          self.output_config = output_config
  13          self.customer_name = customer_name
  14          self.bucket = output_config.get("bucket")
  15          self.prefix = output_config.get("prefix").rstrip("/")
  16          self.file_format = output_config.get("file_format", "csv").lower()
  17          self.s3_client = boto3.client('s3')
  18  
  19          self.audit_log_key = f"{self.prefix}/customer={self.customer_name}/sample_audit_log.csv"
  20  
  21      def load_audit_log(self) -> pd.DataFrame:
  22          """Load audit log sample_audit_log.csv from S3 if exists."""
  23          try:
  24              response = self.s3_client.get_object(Bucket=self.bucket, Key=self.audit_log_key)
  25              audit_log = pd.read_csv(io.BytesIO(response['Body'].read()))
  26              audit_log['sampled_on'] = pd.to_datetime(audit_log['sampled_on'])
  27              return audit_log
  28          except self.s3_client.exceptions.NoSuchKey:
  29              # No audit log exists yet
  30              return pd.DataFrame(columns=["base_sku", "sampled_on", "sampling_strategy_type"])
  31          except Exception as e:
  32              raise RuntimeError(f"Failed to load audit log: {str(e)}")
  33  
  34      def exclude_already_sampled(self, candidates_df: pd.DataFrame, audit_log_df: pd.DataFrame) -> pd.DataFrame:
  35          """Exclude base_skus already sampled within last 1 year."""
  36          if audit_log_df.empty:
  37              return candidates_df
  38  
  39          one_year_ago = datetime.now() - timedelta(days=365)
  40          recent_samples = audit_log_df[audit_log_df['sampled_on'] >= one_year_ago]
  41          sampled_skus = set(recent_samples['base_sku'].tolist())
  42  
  43          filtered_df = candidates_df[~candidates_df['base_sku'].isin(sampled_skus)]
  44          return filtered_df
  45  
  46      def write_samples(self, sampled_df: pd.DataFrame, metadata: dict):
  47          """Write sampled data and metadata to S3 partitioned by customer/year/month."""
  48          now = datetime.now()
  49          year = now.strftime("%Y")
  50          month = now.strftime("%m")
  51  
  52          output_prefix = f"{self.prefix}/customer={self.customer_name}/year={year}/month={month}"
  53  
  54          # 1. Write sampled data
  55          sample_key = f"{output_prefix}/sampled_recall_audit.{self.file_format}"
  56          if self.file_format == "csv":
  57              csv_buffer = io.StringIO()
  58              sampled_df.to_csv(csv_buffer, index=False)
  59              self.s3_client.put_object(Bucket=self.bucket, Key=sample_key, Body=csv_buffer.getvalue())
  60          elif self.file_format == "parquet":
  61              parquet_buffer = io.BytesIO()
  62              sampled_df.to_parquet(parquet_buffer, index=False)
  63              self.s3_client.put_object(Bucket=self.bucket, Key=sample_key, Body=parquet_buffer.getvalue())
  64          else:
  65              raise ValueError(f"Unsupported file format: {self.file_format}")
  66  
  67          # 2. Write metadata.json
  68          metadata_key = f"{output_prefix}/metadata.json"
  69          metadata_bytes = json.dumps(metadata, indent=4).encode('utf-8')
  70          self.s3_client.put_object(Bucket=self.bucket, Key=metadata_key, Body=metadata_bytes)
  71  
  72          print(f"[SUCCESS] Sampled data written to S3: s3://{self.bucket}/{sample_key}")
  73          print(f"[SUCCESS] Metadata written to S3: s3://{self.bucket}/{metadata_key}")
  74  
  75      def update_audit_log(self, sampled_skus: list, sampling_strategy_type: str):
  76          """Update or create sample_audit_log.csv in S3."""
  77          now = datetime.now().strftime("%Y-%m-%d")
  78  
  79          new_entries = pd.DataFrame({
  80              "base_sku": sampled_skus,
  81              "sampled_on": now,
  82              "sampling_strategy_type": sampling_strategy_type
  83          })
  84  
  85          try:
  86              # Load existing
  87              response = self.s3_client.get_object(Bucket=self.bucket, Key=self.audit_log_key)
  88              existing_audit_log = pd.read_csv(io.BytesIO(response['Body'].read()))
  89              updated_audit_log = pd.concat([existing_audit_log, new_entries], ignore_index=True)
  90          except self.s3_client.exceptions.NoSuchKey:
  91              # No existing audit log, create new
  92              updated_audit_log = new_entries
  93          except Exception as e:
  94              raise RuntimeError(f"Failed to read existing audit log for update: {str(e)}")
  95  
  96          # Save updated audit log
  97          csv_buffer = io.StringIO()
  98          updated_audit_log.to_csv(csv_buffer, index=False)
  99          self.s3_client.put_object(Bucket=self.bucket, Key=self.audit_log_key, Body=csv_buffer.getvalue())
 100  
 101          print(f"[SUCCESS] Audit log updated at S3: s3://{self.bucket}/{self.audit_log_key}")
 102