---
title: "Smart Warehouse Inventory Simulation"
author: "Vera Karakoleva"
date: "16/10/2026"
output:
  word_document: default
---
**Module:** Advanced Statistical Programming

**Module Code:** ACCA7022

**Assessment:** Data Manipulation

**Student Number:** 2232348

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

## 1. SIMULATION CONFIGURATION

This project generates simulated warehouse data for 28 days, 5 zones and 6 product categories. Loops are used to generate the data, calculate production summaries and identify peak production days. Conditional statements are used to select records and detect unusual production values.
```{r}
# Define warehouse dimensions
    TotalDays <- 28
    TotalZones <- 5
    TotalCategories <- 6

# Define production parameters
    BaseUnits <- 100
    TimePerUnit <- 0.85
    
# Set a seed to generate the same random results each time
    set.seed(123)
    
# Calculate the total number of records
    TotalRows <- TotalDays * TotalZones * TotalCategories

```

## 2. Part A: Generate Warehouse Data

```{r}

# Create the dataframe with the required columns
warehouse_data <- data.frame(
  Day = integer(TotalRows),
  Zone = integer(TotalRows),
  Category = integer(TotalRows),
  UnitsProcessed = integer(TotalRows),
  ProcessingTime = numeric(TotalRows)
)

# Start the row counter
RowIndex <- 1

# Generate one record for each day, zone and category
for (day in 1:TotalDays) {

  for (zone in 1:TotalZones) {

    for (category in 1:TotalCategories) {

      # Generate a random value between -12 and +12
      RandomVariation <- sample(-12:12, 1)

      # Calculate the number of processed units
      UnitsProcessed <- BaseUnits +
        (zone * 8) +
        (category * 5) +
        RandomVariation

      # Calculate processing time in minutes
      ProcessingTime <- UnitsProcessed * TimePerUnit

      # Store the generated values
      warehouse_data$Day[RowIndex] <- day
      warehouse_data$Zone[RowIndex] <- zone
      warehouse_data$Category[RowIndex] <- category
      warehouse_data$UnitsProcessed[RowIndex] <- UnitsProcessed
      warehouse_data$ProcessingTime[RowIndex] <- ProcessingTime

      # Move to the next row
      RowIndex <- RowIndex + 1
    }
  }
}

# Display the first 10 rows
print(head(warehouse_data, 10))

# Display the dataframe dimensions
cat("Total Rows:", nrow(warehouse_data), "\n")
cat("Total Columns:", ncol(warehouse_data), "\n")
```
## 3. PART B: TOTAL UNITS PROCESSED PER ZONE
```{r}
# Create a dataframe to store zone totals
zone_totals <- data.frame(
  Zone = integer(TotalZones),
  TotalUnits = integer(TotalZones)
)

# Calculate total production for each zone
for (zone in 1:TotalZones) {

  # Start the total at zero for each zone
  ZoneTotal <- 0

  # Check all warehouse records
  for (i in seq_len(nrow(warehouse_data))) {

    if (warehouse_data$Zone[i] == zone) {

      # Add units processed in the current zone
      ZoneTotal <- ZoneTotal +
        warehouse_data$UnitsProcessed[i]
    }
  }

  # Store the result for this zone
  zone_totals$Zone[zone] <- zone
  zone_totals$TotalUnits[zone] <- ZoneTotal
}

# Display the completed zone summary
print(zone_totals, row.names = FALSE)
```

## 4. PART C: AVERAGE PROCESSING TIME PER CATEGORY

```{r}
# Create a dataframe to store category averages
category_average_time <- data.frame(
  Category = integer(TotalCategories),
  AverageProcessingTime = numeric(TotalCategories)
)

# Calculate average processing time for each category
for (category in 1:TotalCategories) {

  # Start the total time and record count at zero
  TotalProcessingTime <- 0
  RecordCount <- 0

  # Check all warehouse records
  for (i in seq_len(nrow(warehouse_data))) {

    if (warehouse_data$Category[i] == category) {

      # Add the processing time
      TotalProcessingTime <- TotalProcessingTime +
        warehouse_data$ProcessingTime[i]

      # Count the matching record
      RecordCount <- RecordCount + 1
    }
  }

  # Calculate the average processing time
  AverageProcessingTime <- TotalProcessingTime / RecordCount

  # Store the result for this category
  category_average_time$Category[category] <- category
  category_average_time$AverageProcessingTime[category] <-
    AverageProcessingTime
}

# Display the averages rounded to two decimal places
print(
  data.frame(
    Category = category_average_time$Category,
    AverageProcessingTime = round(
      category_average_time$AverageProcessingTime, 2
    )
  ),
  row.names = FALSE
)

```
## 5. PART D: IDENTIFY PROCESSING ANOMALIES
Anomalies are identified using total daily production across all six
categories. Each daily total is compared with the zone's average daily
production over 28 days. A difference greater than 15% is marked as
an anomaly. The result is then linked to the corresponding category
records.
```{r}

# Create one record for each day and zone
DailyRecords <- TotalDays * TotalZones

daily_zone_totals <- data.frame(
  Zone = integer(DailyRecords),
  Day = integer(DailyRecords),
  TotalUnitsProcessed = integer(DailyRecords),
  DailyZoneAverage = numeric(DailyRecords),
  Anomaly = logical(DailyRecords)
)

# Calculate daily production and identify anomalies
RowIndex <- 1

for(zone in 1:TotalZones){

  # Average daily production over 28 days
  ZoneAverage <- zone_totals$TotalUnits[zone] / TotalDays

  for(day in 1:TotalDays){

    DailyTotal <- 0

    # Sum production across all six categories
    for(i in seq_len(nrow(warehouse_data))){

      if(warehouse_data$Zone[i] == zone &&
         warehouse_data$Day[i] == day){

        DailyTotal <- DailyTotal +
          warehouse_data$UnitsProcessed[i]

      }
    }

    # Store the daily zone results
    daily_zone_totals$Zone[RowIndex] <- zone
    daily_zone_totals$Day[RowIndex] <- day
    daily_zone_totals$TotalUnitsProcessed[RowIndex] <- DailyTotal
    daily_zone_totals$DailyZoneAverage[RowIndex] <- ZoneAverage

    # Identify production outside the 15% limits
      if(DailyTotal > ZoneAverage * 1.15 ||
         DailyTotal < ZoneAverage * 0.85){

         daily_zone_totals$Anomaly[RowIndex] <- TRUE

    } else {

      daily_zone_totals$Anomaly[RowIndex] <- FALSE

    }

    RowIndex <- RowIndex + 1
  }
}


  # Display the first 10 daily zone results
    print(
    head(daily_zone_totals, 10),
    row.names = FALSE
)
# Create the required anomaly dataframe
processing_anomalies <- data.frame(
  Day = integer(TotalRows),
  Zone = integer(TotalRows),
  Category = integer(TotalRows),
  UnitsProcessed = integer(TotalRows),
  DailyZoneAverage = numeric(TotalRows),
  Anomaly = logical(TotalRows)
)

# Link each category record to its daily zone result
for(i in seq_len(nrow(warehouse_data))){

  day <- warehouse_data$Day[i]
  zone <- warehouse_data$Zone[i]

  DailyRow <- (zone - 1) * TotalDays + day

  processing_anomalies$Day[i] <- day
  processing_anomalies$Zone[i] <- zone
  processing_anomalies$Category[i] <- warehouse_data$Category[i]
  processing_anomalies$UnitsProcessed[i] <-
    warehouse_data$UnitsProcessed[i]

  processing_anomalies$DailyZoneAverage[i] <-
    daily_zone_totals$DailyZoneAverage[DailyRow]

  processing_anomalies$Anomaly[i] <-
    daily_zone_totals$Anomaly[DailyRow]

}

# Display the first 10 detected anomalies
AnomalyRows <- integer(10)
AnomalyCount <- 0

for(i in seq_len(nrow(processing_anomalies))){

  if(processing_anomalies$Anomaly[i]){

    AnomalyCount <- AnomalyCount + 1
    AnomalyRows[AnomalyCount] <- i

    if(AnomalyCount == 10){
      break
    }
  }
}

if(AnomalyCount > 0){

  print(processing_anomalies[
    AnomalyRows[1:AnomalyCount], ],
    row.names = FALSE
  )

} else {

  print("No anomalies detected.")

}

```

## 6. PART E: IDENTIFY PEAK PRODUCTION DAYS 
```{r} 
# Create a dataframe to store the peak day for each zone
peak_zone_days <- data.frame(
  Zone = integer(TotalZones),
  Day = integer(TotalZones),
  TotalUnits = integer(TotalZones)
)

# Find the day with the highest production in each zone
for (zone in 1:TotalZones) {

  # Start the maximum and peak day at zero
  MaxUnits <- 0
  PeakDay <- 0

  # Check all daily production records
  for (i in seq_len(nrow(daily_zone_totals))) {

    if (daily_zone_totals$Zone[i] == zone) {

      # Update the maximum when higher production is found
      if (daily_zone_totals$TotalUnitsProcessed[i] > MaxUnits) {

        MaxUnits <- daily_zone_totals$TotalUnitsProcessed[i]
        PeakDay <- daily_zone_totals$Day[i]
      }
    }
  }

  # Store the peak production result for this zone
  peak_zone_days$Zone[zone] <- zone
  peak_zone_days$Day[zone] <- PeakDay
  peak_zone_days$TotalUnits[zone] <- MaxUnits
}

# Display the completed peak production summary
print(peak_zone_days, row.names = FALSE)
```

# Validation
```{r}
# Check the number of records in the main dataframes
print(nrow(warehouse_data) == TotalDays * TotalZones * TotalCategories)
print(nrow(zone_totals) == TotalZones)
print(nrow(category_average_time) == TotalCategories)
print(nrow(daily_zone_totals) == TotalDays * TotalZones)
print(nrow(processing_anomalies) == TotalDays * TotalZones * TotalCategories)
print(nrow(peak_zone_days) == TotalZones)
```



