Skill: wuminmin/retail-trade-report-generator (version 1.0.0)

# Retail Trade Weekly Report Generator - Skill Documentation ## Overview This skill processes multiple weekly sales report Excel files to generate a consolidated Retail Trade Weekly Report with week-over-week (WoW) comparisons across different channels (DRP, DXS, License Store) and product types (Mobile Prepaid/Postpaid, FWA 4G/5G). ## Purpose - Consolidate 12 Excel files (6 current week + 6 previous week) into a single comprehensive weekly report - Calculate Average Daily Acquisition (ADA) metrics by region and channel - Compute Week-over-Week (WoW) performance indicators - Generate formatted Excel output with charts and color-coded performance indicators ## Input Requirements ### Required Files (12 total) **Current Week (6 files):** 1. `DRP_Channel_Sales_Report_DRP_M_DD-M_DD.xlsx` 2. `DRP_Special_SIM_Monitor_Report_Daily_TECNO_M_DD-M_DD.xlsx` 3. `License_Store_Performance_Monitor_Report_LS_M_DD-M_DD.xlsx` 4. `DXS_Acquisition_Report_Mobile_Prepaid_M_DD-M_DD.xlsx` 5. `DXS_Acquisition_Report_Mobile_Postpaid_M_DD-M_DD.xlsx` 6. `DXS_Acquisition_Report_FWA_M_DD-M_DD.xlsx` **Previous Week (6 files with earlier dates):** Same file types with earlier date ranges in filename ### Store Mapping CSV File containing Store Name to Region mapping with aliases support: ```csv Store Name,Region,Aliases SM Megamall,NCR,"Megamall|SM Mega|MEGAMALL" ... ``` ## Data Processing Logic ### 1. File Identification - Extract date ranges from filenames (format: `M_DD-M_DD`) - Auto-group into current week vs previous week based on date comparison - Validate all 12 required files are present ### 2. Data Extraction Rules #### DRP Channel Sales Report - **Header rows:** Skip rows 0-7 - **Data rows:** Start from row 8 - **Region field:** Column 0 (AREA) - **Key columns:** - Column 1: MOBILE POSTPAID > TOTAL ACTIVATION - Column 5: MOBILE PREPAID > TOTAL ACTIVATION - Column 6: Double Data_Sum - Column 9: 4G WiFi 980 SIM_Sum (FWA 4G) - Column 10: Unli 5G WIFI 100Mbps Starter SIM_Sum (FWA 5G) - Column 11: 5G WiFi 4990 SIM_Sum (FWA 5G) #### DRP TECNO Report - **Header rows:** Skip rows 0-6 - **Data rows:** Start from row 7 - **Region field:** Column 0 (Activation Area) - **Key columns:** - Column 1: CARMON Activation (CAMON 40) - Column 2: POVA Activation (POVA 7) - Column 3: Total Activation (TECNO ADA = CAMON 40 + POVA 7) #### License Store Report - **Header rows:** Skip rows 0-7 - **Data rows:** Start from row 8 - **Store field:** Column 0 (Store Name) - **Requires mapping to Region** - **Key columns:** - Column 1: Mobile Prepaid - Column 3: Mobile Postpaid - Column 29 (AD): DITO Home Prepaid 4G WiFi 980 SIM (FWA 4G) - Need to find: Unli 5G WIFI 100Mbps Starter SIM (FWA 5G) - Need to find: 5G WiFi 4990 SIM (FWA 5G) #### DXS Mobile Prepaid Report - **Header rows:** Skip rows 0-7 - **Data rows:** Start from row 8 - **Store field:** Column 0 (DXS Name) - **Requires mapping to Region** - **Key column:** - Column 4: Total #### DXS Mobile Postpaid Report - **Header rows:** Skip rows 0-7 - **Data rows:** Start from row 8 - **Store field:** Column 0 (DXS Name) - **Requires mapping to Region** - **Key column:** - Column 12: Total #### DXS FWA Report - **Header rows:** Skip rows 0-7 - **Data rows:** Start from row 8 - **Store field:** Column 0 (DXS Name) - **Requires mapping to Region** - **Key columns:** - Column 1: DITO Home Prepaid 4G WiFi 980 (FWA 4G) - Column 18: Total - **FWA 5G calculation:** Total (Col 18) - 4G (Col 1) ### 3. Store Name to Region Mapping ```python # Build mapping dictionary from CSV store_mapping = {} for row in mapping_csv: main_name = row['Store Name'] region = row['Region'] aliases = row['Aliases'].split('|') if row['Aliases'] else [] # Add main name and all aliases to mapping store_mapping[main_name.upper()] = region for alias in aliases: store_mapping[alias.strip().upper()] = region # Apply fuzzy matching for unmatched stores def map_store_to_region(store_name): # Exact match (case-insensitive) if store_name.upper() in store_mapping: return store_mapping[store_name.upper()] # Fuzzy match using substring search for key in store_mapping: if key in store_name.upper() or store_name.upper() in key: return store_mapping[key] # Default to "Others" if no match return "Others" ``` ### 4. Regional Aggregation **Standard Regions:** NCR, SLZ, NLZ, CLZ, EVIS, WVIS, MIN, Others For each product type and region: ```python # DRP data: Direct mapping (already by region) DRP_ADA = drp_data[region][product_column] # DXS data: Aggregate stores by region DXS_ADA = sum(dxs_data[store][product_column] for store in dxs_data if map_store_to_region(store) == region) # LS data: Aggregate stores by region LS_ADA = sum(ls_data[store][product_column] for store in ls_data if map_store_to_region(store) == region) # Total for region RT_Total_ADA = DRP_ADA + DXS_ADA + LS_ADA ``` ### 5. WoW Calculation ```python WoW = (current_week_value - previous_week_value) / previous_week_value # Formatting rules: # - Display as percentage (e.g., "21%", "-13%") # - Round to nearest integer # - Handle division by zero: display "-" if previous_week_value == 0 # - Handle cases where current = 0 and previous > 0: show "-100%" ``` ### 6. Special Calculations #### FWA 5G Components ```python # DRP FWA 5G DRP_FWA_5G = Column_10 + Column_11 # DXS FWA 5G DXS_FWA_5G = Total - Column_1_4G # LS FWA 5G LS_FWA_5G = Unli_5G_WIFI_100Mbps + WiFi_4990_SIM ``` #### TECNO ADA ```python TECNO_ADA = CAMON_40 + POVA_7 ``` ## Output Format ### Excel Structure **Single Sheet:** "Weekly Report" **Sections:** 1. Report Header (Rows 1-2) - Title: "Retail Trade Weekly Report" - Date ranges: "Last Week: [dates] | This Week: [dates]" 2. Channel Summary (Rows 4-9) - Columns: Channel | Program name | This Week ADA | WoW | MoM - Rows: DRP BAU, DRP TECNO, License Store, DXS, RT Total 3. Mobile Prepaid by Region (Rows 11-21) - Columns: Region | RT Total ADA | WoW | DXS ADA | WoW | LS ADA | WoW | DRP ADA | WoW - Rows: 8 regions + Total 4. DRP Prepaid Program (Rows 23-33) - Columns: Region | Double Data ADA | WoW | TECNO ADA | WoW | CAMON 40 | WoW | POVA 7 | WoW - Rows: 8 regions + Total 5. Mobile Postpaid by Region (Rows 35-45) - Same structure as Mobile Prepaid 6. FWA 4G by Region (Rows 47-57) - Same structure as Mobile Prepaid 7. FWA 5G by Region (Rows 59-69) - Same structure as Mobile Prepaid ### Formatting Rules #### Number Formatting - ADA values: Integer format with thousand separators (e.g., "1,876") - WoW percentages: Integer percentage (e.g., "21%", "-13%") - Small ADA values (< 10): Show 1 decimal place (e.g., "0.6", "2.9") #### Color Coding - **WoW Positive (>0%):** Green text (#008000) - **WoW Negative (<0%):** Red text (#FF0000) - **WoW Zero (0%):** Black text - **WoW N/A ("-"):** Gray text (#808080) #### Cell Styling - **Headers:** Bold, centered, light gray background (#F0F0F0) - **Region names:** Bold - **Total rows:** Bold, light blue background (#E6F2FF) - **Borders:** Thin borders around all data cells ### Charts **Chart 1: Channel Performance Comparison** - Type: Clustered Column Chart - Data: This Week ADA by Channel (DRP BAU, DRP TECNO, License Store, DXS) - Position: Right side of Channel Summary section - Size: 6 columns wide x 15 rows tall **Chart 2: Regional Mobile Prepaid Distribution** - Type: Stacked Column Chart - Data: DRP ADA, DXS ADA, LS ADA by Region - Position: Right side of Mobile Prepaid section - Size: 6 columns wide x 15 rows tall **Chart 3: WoW Trend - Top 3 Regions** - Type: Line Chart with Markers - Data: WoW % for top 3 regions by RT Total ADA - Position: Below main tables - Size: 12 columns wide x 12 rows tall ## Error Handling ### Missing Files ```python if len(current_week_files) != 6: raise ValueError(f"Expected 6 current week files, found {len(current_week_files)}") if len(previous_week_files) != 6: raise ValueError(f"Expected 6 previous week files, found {len(previous_week_files)}") ``` ### Unmapped Stores ```python unmapped_stores = [] for store in all_stores: if map_store_to_region(store) == "Others": # Log warning but continue processing unmapped_stores.append(store) if unmapped_stores: print(f"Warning: {len(unmapped_stores)} stores mapped to 'Others' region") ``` ### Data Quality Checks ```python # Check for negative values if any_value < 0: print(f"Warning: Negative value found in {file}:{column}") # Check for missing regions expected_regions = {"NCR", "SLZ", "NLZ", "CLZ", "EVIS", "WVIS", "MIN", "Others"} missing_regions = expected_regions - set(actual_regions) if missing_regions: print(f"Warning: Missing regions: {missing_regions}") ``` ## Implementation Notes ### Python Libraries ```python import pandas as pd import openpyxl from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.chart import BarChart, LineChart, Reference import re from datetime import datetime ``` ### Key Functions #### 1. File Parser ```python def extract_date_from_filename(filename): """Extract date range from filename like 'Report_1_11-1_17.xlsx'""" pattern = r'_(\d+)_(\d+)-(\d+)_(\d+)\.xlsx' match = re.search(pattern, filename) if match: start_month, start_day, end_month, end_day = match.groups() return (int(start_month), int(start_day), int(end_month), int(end_day)) return None def identify_file_type(filename): """Identify file type from filename""" if 'DRP_Channel_Sales' in filename: return 'DRP' elif 'TECNO' in filename: return 'TECNO' elif 'License_Store' in filename: return 'LS' elif 'Mobile_Prepaid' in filename: return 'DXS_Prepaid' elif 'Mobile_Postpaid' in filename: return 'DXS_Postpaid' elif 'FWA' in filename: return 'DXS_FWA' return 'Unknown' ``` #### 2. Data Extractor ```python def extract_drp_data(filepath): """Extract DRP channel sales data""" df = pd.read_excel(filepath, sheet_name='Sheet0', header=None) # Find data start row (usually row 8) data_start = 8 # Extract by region regions_data = {} for idx in range(data_start, len(df)): region = df.iloc[idx, 0] if pd.isna(region) or region == 'Total': continue regions_data[region] = { 'mobile_postpaid': df.iloc[idx, 1], 'mobile_prepaid': df.iloc[idx, 5], 'double_data': df.iloc[idx, 6], 'fwa_4g': df.iloc[idx, 9], 'fwa_5g': df.iloc[idx, 10] + df.iloc[idx, 11] } return regions_data def extract_dxs_data(filepath, product_type): """Extract DXS acquisition data""" df = pd.read_excel(filepath, sheet_name='Sheet1', header=None) # Determine column based on product type if product_type == 'prepaid': value_col = 4 elif product_type == 'postpaid': value_col = 12 elif product_type == 'fwa': return extract_dxs_fwa_data(df) stores_data = {} for idx in range(8, len(df)): store = df.iloc[idx, 0] if pd.isna(store) or store in ['Grand Total', '-']: continue value = df.iloc[idx, value_col] if pd.notna(value): stores_data[store] = value return stores_data def extract_dxs_fwa_data(df): """Extract FWA data with 4G/5G split""" stores_data = {} for idx in range(8, len(df)): store = df.iloc[idx, 0] if pd.isna(store) or store in ['Grand Total', '-']: continue fwa_4g = df.iloc[idx, 1] if pd.notna(df.iloc[idx, 1]) else 0 total = df.iloc[idx, 18] if pd.notna(df.iloc[idx, 18]) else 0 fwa_5g = total - fwa_4g stores_data[store] = { 'fwa_4g': fwa_4g, 'fwa_5g': fwa_5g } return stores_data ``` #### 3. Region Aggregator ```python def aggregate_by_region(stores_data, mapping_dict, regions): """Aggregate store data by region""" regional_totals = {region: 0 for region in regions} for store, value in stores_data.items(): region = map_store_to_region(store, mapping_dict) if isinstance(value, dict): # Handle nested data (e.g., FWA with 4G/5G) for key in value: if key not in regional_totals: regional_totals[key] = {region: 0 for region in regions} regional_totals[key][region] += value[key] else: regional_totals[region] += value return regional_totals ``` #### 4. WoW Calculator ```python def calculate_wow(current, previous): """Calculate week-over-week percentage change""" if previous == 0 or pd.isna(previous): return "-" if current == 0 or pd.isna(current): return "-100%" wow = ((current - previous) / previous) * 100 return f"{int(round(wow))}%" ``` #### 5. Excel Formatter ```python def apply_formatting(ws, start_row, start_col, end_row, end_col): """Apply formatting to Excel worksheet""" # Define styles header_fill = PatternFill(start_color="F0F0F0", end_color="F0F0F0", fill_type="solid") total_fill = PatternFill(start_color="E6F2FF", end_color="E6F2FF", fill_type="solid") green_font = Font(color="008000") red_font = Font(color="FF0000") gray_font = Font(color="808080") bold_font = Font(bold=True) thin_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) # Apply to cells for row in ws.iter_rows(min_row=start_row, max_row=end_row, min_col=start_col, max_col=end_col): for cell in row: cell.border = thin_border # Color code WoW values if isinstance(cell.value, str) and '%' in cell.value: try: pct_value = int(cell.value.replace('%', '')) if pct_value > 0: cell.font = green_font elif pct_value < 0: cell.font = red_font except: if cell.value == '-': cell.font = gray_font def add_chart(ws, chart_type, data_range, position, title): """Add chart to worksheet""" if chart_type == 'column': chart = BarChart() elif chart_type == 'line': chart = LineChart() chart.title = title chart.style = 10 chart.height = 10 chart.width = 15 data = Reference(ws, min_col=data_range[0], min_row=data_range[1], max_col=data_range[2], max_row=data_range[3]) chart.add_data(data, titles_from_data=True) ws.add_chart(chart, position) ``` ## Usage Example ```python from retail_trade_report_skill import generate_weekly_report # Input files directory input_dir = "/mnt/user-data/uploads/" # Store mapping CSV mapping_file = "/mnt/user-data/uploads/store_mapping.csv" # Generate report output_file = generate_weekly_report( input_dir=input_dir, mapping_csv=mapping_file, output_path="/mnt/user-data/outputs/Retail_Trade_Weekly_Report.xlsx" ) print(f"Report generated: {output_file}") ``` ## Validation Checklist Before finalizing output: - [ ] All 12 input files identified and processed - [ ] Date ranges correctly extracted and displayed - [ ] All stores mapped to regions (log unmapped as "Others") - [ ] All WoW calculations completed - [ ] No negative ADA values (except in error logs) - [ ] All formulas validated against sample data - [ ] Charts render correctly - [ ] Color coding applied to all WoW cells - [ ] Total rows sum correctly - [ ] Output file opens without errors ## Performance Considerations - Expected processing time: 10-30 seconds for 12 files - Memory usage: ~50-100 MB - Large file handling: Files up to 10MB each supported - Concurrent processing: Process files in parallel where possible ## Troubleshooting ### Common Issues **Issue:** "File not found" error - **Solution:** Verify all 12 files are uploaded and filenames match expected pattern **Issue:** Store name not mapping to region - **Solution:** Check mapping CSV for typos, add aliases for common variations **Issue:** WoW showing "N/A" for all values - **Solution:** Verify previous week files are correctly identified (earlier dates) **Issue:** Charts not displaying - **Solution:** Check openpyxl version >= 3.0, verify chart data ranges **Issue:** Negative ADA values - **Solution:** Check source data for errors, verify column indices ## Version History - **v1.0** (2026-02-02): Initial skill creation - Support for 12-file weekly report generation - WoW calculations with color coding - Store-to-region mapping with aliases - Three chart types for visualization

path: README_CN.md
content: # Retail Trade Weekly Report Generator - 使用指南

## 📁 文件清单

1. **retail_trade_weekly_report_SKILL.md** - 完整的Skill文档（技术规格）
2. **retail_trade_report_generator.py** - Python实现代码
3. **store_mapping.csv** - Store到Region的映射表
4. **README_CN.md** - 本使用指南

## 🚀 快速开始

### 1. 准备输入文件

确保你有**12个Excel文件**（6个本周 + 6个上周）：

**本周文件示例（1月11-17日）：**
- `DRP_Channel_Sales_Report_DRP_1_11-1_17.xlsx`
- `DRP_Special_SIM_Monitor_Report_Daily_TECNO_1_11-1_17.xlsx`
- `License_Store_Performance_Monitor_Report_LS_1_11-1_17.xlsx`
- `DXS_Acquisition_Report_Mobile_Prepaid_1_11-1_17.xlsx`
- `DXS_Acquisition_Report_Mobile_Postpaid_1_11-1_17.xlsx`
- `DXS_Acquisition_Report_FWA_1_11-1_17.xlsx`

**上周文件示例（1月4-10日）：**
- `DRP_Channel_Sales_Report_DRP_1_04-1_10.xlsx`
- `DRP_Special_SIM_Monitor_Report_Daily_TECNO_1_04-1_10.xlsx`
- `License_Store_Performance_Monitor_Report_LS_1_04-1_10.xlsx`
- `DXS_Acquisition_Report_Mobile_Prepaid_1_04-1_10.xlsx`
- `DXS_Acquisition_Report_Mobile_Postpaid_1_04-1_10.xlsx`
- `DXS_Acquisition_Report_FWA_1_04-1_10.xlsx`

### 2. 运行脚本

```python
from retail_trade_report_generator import generate_weekly_report

# 设置路径
input_dir = "/path/to/your/excel/files/"
mapping_csv = "/path/to/store_mapping.csv"
output_path = "/path/to/output/Retail_Trade_Weekly_Report.xlsx"

# 生成报告
report_path = generate_weekly_report(input_dir, mapping_csv, output_path)
print(f"报告已生成：{report_path}")
```

### 3. 查看输出

生成的Excel文件包含5个主要部分：
- Mobile Prepaid（按区域）
- DRP Prepaid Program（按区域）
- Mobile Postpaid（按区域）
- FWA 4G（按区域）
- FWA 5G（按区域）

每个部分都包含：
- 本周ADA数据
- WoW（周环比）
- 按渠道拆分（DRP、DXS、LS）
- 颜色标注（绿色=增长，红色=下降）

## 📊 输出示例

### Mobile Prepaid 表格结构
```
Region | RT Total ADA | WoW  | DXS ADA | WoW  | LS ADA | WoW  | DRP ADA | WoW
--------|-------------|------|---------|------|--------|------|---------|-----
NCR    | 337         | 21%  | 163     | 16%  | 8      | 51%  | 166     | 25%
SLZ    | 401         | 6%   | 64      | -13% | 28     | -17% | 310     | 14%
...
Total  | 1,876       | 6%   | 508     | -7%  | 227    | 18%  | 1,141   | 11%
```

## 🔧 自定义配置

### 修改Store映射

编辑 `store_mapping.csv` 文件：

```csv
Store Name,Region,Aliases
New Store Name,NCR,"Alias1|Alias2|Alias3"
```

**注意事项：**
- Aliases用 `|` 分隔
- 大小写不敏感（自动转换）
- 支持模糊匹配

### 调整区域列表

如需修改区域，编辑 `retail_trade_report_generator.py` 中的：

```python
REGIONS = ['NCR', 'SLZ', 'NLZ', 'CLZ', 'EVIS', 'WVIS', 'MIN', 'Others']
```

## 📋 数据映射关系

### Mobile Prepaid
| 指标 | 来源文件 | 字段 |
|-----|---------|-----|
| DRP ADA | DRP_Channel_Sales_Report | Column 5: MOBILE PREPAID > TOTAL ACTIVATION |
| DXS ADA | DXS_Acquisition_Report_Mobile_Prepaid | Column 4: Total（按Store汇总到Region）|
| LS ADA | License_Store_Performance_Monitor_Report | Column 1: Mobile Prepaid（按Store汇总到Region）|

### Mobile Postpaid
| 指标 | 来源文件 | 字段 |
|-----|---------|-----|
| DRP ADA | DRP_Channel_Sales_Report | Column 1: MOBILE POSTPAID > TOTAL ACTIVATION |
| DXS ADA | DXS_Acquisition_Report_Mobile_Postpaid | Column 12: Total |
| LS ADA | License_Store_Performance_Monitor_Report | Column 3: Mobile Postpaid |

### FWA 4G
| 指标 | 来源文件 | 字段 |
|-----|---------|-----|
| DRP ADA | DRP_Channel_Sales_Report | Column 9: 4G WiFi 980 SIM_Sum |
| DXS ADA | DXS_Acquisition_Report_FWA | Column 1: DITO Home Prepaid 4G WiFi 980 |
| LS ADA | License_Store_Performance_Monitor_Report | Column 29 (AD): DITO Home Prepaid 4G WiFi 980 SIM |

### FWA 5G
| 指标 | 来源文件 | 字段 |
|-----|---------|-----|
| DRP ADA | DRP_Channel_Sales_Report | Column 10 + Column 11（求和）|
| DXS ADA | DXS_Acquisition_Report_FWA | Total - 4G列 |
| LS ADA | License_Store_Performance_Monitor_Report | Unli 5G + WiFi 4990（求和）|

### DRP Prepaid Program
| 指标 | 来源文件 | 字段 |
|-----|---------|-----|
| Double Data ADA | DRP_Channel_Sales_Report | Column 6: Double Data_Sum |
| CAMON 40 | DRP_Special_SIM_Monitor_Report_Daily_TECNO | Column 1: CARMON Activation |
| POVA 7 | DRP_Special_SIM_Monitor_Report_Daily_TECNO | Column 2: POVA Activation |
| TECNO ADA | 计算字段 | CAMON 40 + POVA 7 |

## ⚙️ WoW计算公式

```python
WoW = (本周值 - 上周值) / 上周值 × 100%

# 特殊情况：
# - 上周值 = 0：显示 "-"
# - 本周值 = 0 且 上周值 > 0：显示 "-100%"
# - 格式化为整数百分比（如 "21%"、"-13%"）
```

## 🎨 颜色编码

- **绿色（粗体）**: WoW > 0%（增长）
- **红色（粗体）**: WoW < 0%（下降）
- **黑色**: WoW = 0%（持平）
- **灰色**: WoW = "-"（无法计算）

## ❗ 常见问题

### Q1: "Expected 6 current week files, found X"
**A:** 检查文件命名和日期格式，确保有完整的6个文件，且日期能被正确识别。

### Q2: Store没有匹配到Region
**A:** 检查 `store_mapping.csv`，添加缺失的Store或其别名。未匹配的Store会自动归类到 "Others"。

### Q3: WoW显示异常
**A:** 检查上周和本周文件的日期范围，确保上周日期早于本周。

### Q4: 某个Region的数据为0
**A:** 检查原始Excel文件中该Region是否有数据，或Store映射是否正确。

### Q5: 数值精度问题
**A:** 小于10的值显示1位小数，大于等于10的值显示整数。

## 📞 技术支持

如需修改计算逻辑或添加新功能，请参考：
- `retail_trade_weekly_report_SKILL.md` - 完整技术文档
- `retail_trade_report_generator.py` - 源代码（有详细注释）

## 📝 更新日志

### v1.0 (2026-02-02)
- ✅ 初始版本发布
- ✅ 支持12个Excel文件自动识别和分组
- ✅ WoW计算和颜色标注
- ✅ Store到Region映射（支持别名和模糊匹配）
- ✅ 5个产品类型的完整报表
- ✅ 自动格式化和边框样式

## 🔮 未来功能（可选）

- [ ] 添加图表（柱状图、折线图）
- [ ] 支持导出为PDF
- [ ] 自动发送邮件报告
- [ ] 历史趋势分析（多周对比）
- [ ] 数据异常检测和预警

---

**如有任何问题或建议，请联系技术团队。**


path: scripts/retail_trade_report_generator.py
content: """
Retail Trade Weekly Report Generator
Processes 12 Excel files (6 current week + 6 previous week) to generate consolidated weekly report
"""

import pandas as pd
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.chart import BarChart, LineChart, Reference
from openpyxl.utils import get_column_letter
import re
import os
from datetime import datetime
from collections import defaultdict


class RetailTradeReportGenerator:
    """Main class for generating retail trade weekly reports"""
    
    REGIONS = ['NCR', 'SLZ', 'NLZ', 'CLZ', 'EVIS', 'WVIS', 'MIN', 'Others']
    
    FILE_TYPES = {
        'DRP': 'DRP_Channel_Sales_Report_DRP',
        'TECNO': 'DRP_Special_SIM_Monitor_Report_Daily_TECNO',
        'LS': 'License_Store_Performance_Monitor_Report_LS',
        'DXS_Prepaid': 'DXS_Acquisition_Report_Mobile_Prepaid',
        'DXS_Postpaid': 'DXS_Acquisition_Report_Mobile_Postpaid',
        'DXS_FWA': 'DXS_Acquisition_Report_FWA'
    }
    
    def __init__(self, input_dir, mapping_csv_path):
        self.input_dir = input_dir
        self.mapping_csv_path = mapping_csv_path
        self.store_mapping = {}
        self.current_week_files = {}
        self.previous_week_files = {}
        self.current_week_data = {}
        self.previous_week_data = {}
        
    def load_store_mapping(self):
        """Load store to region mapping from CSV"""
        df = pd.read_csv(self.mapping_csv_path)
        
        for _, row in df.iterrows():
            store_name = str(row['Store Name']).strip()
            region = str(row['Region']).strip()
            aliases = str(row['Aliases']) if pd.notna(row['Aliases']) else ""
            
            # Add main name
            self.store_mapping[store_name.upper()] = region
            
            # Add aliases
            if aliases:
                for alias in aliases.split('|'):
                    alias = alias.strip()
                    if alias:
                        self.store_mapping[alias.upper()] = region
        
        print(f"Loaded {len(self.store_mapping)} store mappings")
    
    def map_store_to_region(self, store_name):
        """Map store name to region with fuzzy matching"""
        if pd.isna(store_name):
            return "Others"
        
        store_name = str(store_name).strip()
        store_upper = store_name.upper()
        
        # Exact match
        if store_upper in self.store_mapping:
            return self.store_mapping[store_upper]
        
        # Partial match
        for key, region in self.store_mapping.items():
            if key in store_upper or store_upper in key:
                return region
        
        # Default to Others
        return "Others"
    
    def extract_date_from_filename(self, filename):
        """Extract date range from filename"""
        pattern = r'_(\d+)_(\d+)-(\d+)_(\d+)'
        match = re.search(pattern, filename)
        if match:
            start_month, start_day, end_month, end_day = match.groups()
            # Use year 2026 for comparison
            start_date = datetime(2026, int(start_month), int(start_day))
            end_date = datetime(2026, int(end_month), int(end_day))
            return start_date, end_date, f"{start_month}/{start_day}-{end_month}/{end_day}"
        return None, None, None
    
    def identify_file_type(self, filename):
        """Identify file type from filename"""
        for file_type, pattern in self.FILE_TYPES.items():
            if pattern in filename:
                return file_type
        return None
    
    def identify_files(self):
        """Identify and group current week vs previous week files"""
        all_files = [f for f in os.listdir(self.input_dir) if f.endswith('.xlsx')]
        
        files_with_dates = []
        for filename in all_files:
            file_type = self.identify_file_type(filename)
            if file_type:
                start_date, end_date, date_str = self.extract_date_from_filename(filename)
                if start_date:
                    files_with_dates.append({
                        'filename': filename,
                        'filepath': os.path.join(self.input_dir, filename),
                        'type': file_type,
                        'start_date': start_date,
                        'end_date': end_date,
                        'date_str': date_str
                    })
        
        # Sort by date (most recent first)
        files_with_dates.sort(key=lambda x: x['end_date'], reverse=True)
        
        # Group by week (assuming 2 sets of 6 files)
        current_week_end = files_with_dates[0]['end_date']
        
        for file_info in files_with_dates:
            if file_info['end_date'] == current_week_end:
                self.current_week_files[file_info['type']] = file_info
            else:
                self.previous_week_files[file_info['type']] = file_info
        
        print(f"Current week files: {len(self.current_week_files)}")
        print(f"Previous week files: {len(self.previous_week_files)}")
        
        # Validate
        if len(self.current_week_files) != 6:
            raise ValueError(f"Expected 6 current week files, found {len(self.current_week_files)}")
        if len(self.previous_week_files) != 6:
            raise ValueError(f"Expected 6 previous week files, found {len(self.previous_week_files)}")
    
    def extract_drp_data(self, filepath):
        """Extract DRP channel sales data"""
        df = pd.read_excel(filepath, sheet_name='Sheet0', header=None)
        
        data = {}
        for idx in range(8, 16):  # Row 8-15 are data rows
            region = df.iloc[idx, 0]
            if pd.isna(region) or region == 'Total':
                continue
            
            data[region] = {
                'mobile_postpaid': self._safe_float(df.iloc[idx, 1]),
                'mobile_prepaid': self._safe_float(df.iloc[idx, 5]),
                'double_data': self._safe_float(df.iloc[idx, 6]),
                'fwa_4g': self._safe_float(df.iloc[idx, 9]),
                'fwa_5g': self._safe_float(df.iloc[idx, 10]) + self._safe_float(df.iloc[idx, 11])
            }
        
        return data
    
    def extract_tecno_data(self, filepath):
        """Extract TECNO activation data"""
        df = pd.read_excel(filepath, sheet_name='Sheet0', header=None)
        
        data = {}
        for idx in range(7, 15):  # Row 7-14 are data rows
            region = df.iloc[idx, 0]
            if pd.isna(region) or region == 'Total':
                continue
            
            data[region] = {
                'camon_40': self._safe_float(df.iloc[idx, 1]),
                'pova_7': self._safe_float(df.iloc[idx, 2]),
                'tecno_total': self._safe_float(df.iloc[idx, 3])
            }
        
        return data
    
    def extract_ls_data(self, filepath):
        """Extract License Store data"""
        df = pd.read_excel(filepath, sheet_name='Sheet0', header=None)
        
        stores_data = {
            'mobile_prepaid': {},
            'mobile_postpaid': {},
            'fwa_4g': {}
        }
        
        for idx in range(8, len(df)):
            store = df.iloc[idx, 0]
            if pd.isna(store):
                break
            
            stores_data['mobile_prepaid'][store] = self._safe_float(df.iloc[idx, 1])
            stores_data['mobile_postpaid'][store] = self._safe_float(df.iloc[idx, 3])
            stores_data['fwa_4g'][store] = self._safe_float(df.iloc[idx, 29])  # Column AD
        
        return stores_data
    
    def extract_dxs_prepaid_data(self, filepath):
        """Extract DXS Mobile Prepaid data"""
        df = pd.read_excel(filepath, sheet_name='Sheet1', header=None)
        
        stores_data = {}
        for idx in range(8, len(df)):
            store = df.iloc[idx, 0]
            if pd.isna(store) or store in ['Grand Total', '-']:
                continue
            
            value = self._safe_float(df.iloc[idx, 4])  # Column 4: Total
            if value > 0:
                stores_data[store] = value
        
        return stores_data
    
    def extract_dxs_postpaid_data(self, filepath):
        """Extract DXS Mobile Postpaid data"""
        df = pd.read_excel(filepath, sheet_name='Sheet1', header=None)
        
        stores_data = {}
        for idx in range(8, len(df)):
            store = df.iloc[idx, 0]
            if pd.isna(store) or store in ['Grand Total', '-']:
                continue
            
            value = self._safe_float(df.iloc[idx, 12])  # Column 12: Total
            if value > 0:
                stores_data[store] = value
        
        return stores_data
    
    def extract_dxs_fwa_data(self, filepath):
        """Extract DXS FWA data with 4G/5G split"""
        df = pd.read_excel(filepath, sheet_name='Sheet1', header=None)
        
        stores_data = {
            'fwa_4g': {},
            'fwa_5g': {}
        }
        
        for idx in range(8, len(df)):
            store = df.iloc[idx, 0]
            if pd.isna(store) or store in ['Grand Total', '-']:
                continue
            
            fwa_4g = self._safe_float(df.iloc[idx, 1])  # Column 1: 4G
            total = self._safe_float(df.iloc[idx, 18])  # Column 18: Total
            fwa_5g = total - fwa_4g
            
            if fwa_4g > 0:
                stores_data['fwa_4g'][store] = fwa_4g
            if fwa_5g > 0:
                stores_data['fwa_5g'][store] = fwa_5g
        
        return stores_data
    
    def aggregate_stores_by_region(self, stores_data):
        """Aggregate store-level data by region"""
        regional_data = {region: 0 for region in self.REGIONS}
        
        for store, value in stores_data.items():
            region = self.map_store_to_region(store)
            regional_data[region] += value
        
        return regional_data
    
    def process_all_files(self):
        """Process all files and aggregate data"""
        for week_label, file_dict in [('current', self.current_week_files), 
                                       ('previous', self.previous_week_files)]:
            week_data = {}
            
            # Process DRP
            if 'DRP' in file_dict:
                week_data['drp'] = self.extract_drp_data(file_dict['DRP']['filepath'])
            
            # Process TECNO
            if 'TECNO' in file_dict:
                week_data['tecno'] = self.extract_tecno_data(file_dict['TECNO']['filepath'])
            
            # Process License Store
            if 'LS' in file_dict:
                ls_raw = self.extract_ls_data(file_dict['LS']['filepath'])
                week_data['ls'] = {
                    'mobile_prepaid': self.aggregate_stores_by_region(ls_raw['mobile_prepaid']),
                    'mobile_postpaid': self.aggregate_stores_by_region(ls_raw['mobile_postpaid']),
                    'fwa_4g': self.aggregate_stores_by_region(ls_raw['fwa_4g'])
                }
            
            # Process DXS Prepaid
            if 'DXS_Prepaid' in file_dict:
                dxs_prepaid_raw = self.extract_dxs_prepaid_data(file_dict['DXS_Prepaid']['filepath'])
                week_data['dxs_prepaid'] = self.aggregate_stores_by_region(dxs_prepaid_raw)
            
            # Process DXS Postpaid
            if 'DXS_Postpaid' in file_dict:
                dxs_postpaid_raw = self.extract_dxs_postpaid_data(file_dict['DXS_Postpaid']['filepath'])
                week_data['dxs_postpaid'] = self.aggregate_stores_by_region(dxs_postpaid_raw)
            
            # Process DXS FWA
            if 'DXS_FWA' in file_dict:
                dxs_fwa_raw = self.extract_dxs_fwa_data(file_dict['DXS_FWA']['filepath'])
                week_data['dxs_fwa'] = {
                    'fwa_4g': self.aggregate_stores_by_region(dxs_fwa_raw['fwa_4g']),
                    'fwa_5g': self.aggregate_stores_by_region(dxs_fwa_raw['fwa_5g'])
                }
            
            if week_label == 'current':
                self.current_week_data = week_data
            else:
                self.previous_week_data = week_data
    
    def calculate_wow(self, current, previous):
        """Calculate week-over-week percentage"""
        if previous == 0 or pd.isna(previous):
            return "-"
        if current == 0 or pd.isna(current):
            return "-100%"
        
        wow = ((current - previous) / previous) * 100
        return f"{int(round(wow))}%"
    
    def _safe_float(self, value):
        """Safely convert value to float"""
        if pd.isna(value):
            return 0.0
        try:
            return float(value)
        except:
            return 0.0
    
    def generate_report(self, output_path):
        """Generate the final Excel report"""
        wb = openpyxl.Workbook()
        ws = wb.active
        ws.title = "Weekly Report"
        
        # Get date ranges
        current_date_str = self.current_week_files['DRP']['date_str']
        previous_date_str = self.previous_week_files['DRP']['date_str']
        
        # Write header
        row = 1
        ws.merge_cells(f'A{row}:I{row}')
        ws[f'A{row}'] = "Retail Trade Weekly Report"
        ws[f'A{row}'].font = Font(size=16, bold=True)
        ws[f'A{row}'].alignment = Alignment(horizontal='center')
        
        row += 1
        ws.merge_cells(f'A{row}:I{row}')
        ws[f'A{row}'] = f"Last Week: {previous_date_str} | This Week: {current_date_str}"
        ws[f'A{row}'].alignment = Alignment(horizontal='center')
        
        row += 2
        
        # Mobile Prepaid Section
        row = self._write_product_section(ws, row, "Mobile Prepaid", 'mobile_prepaid')
        
        row += 2
        
        # DRP Prepaid Program Section
        row = self._write_drp_program_section(ws, row)
        
        row += 2
        
        # Mobile Postpaid Section
        row = self._write_product_section(ws, row, "Mobile Postpaid", 'mobile_postpaid')
        
        row += 2
        
        # FWA 4G Section
        row = self._write_product_section(ws, row, "FWA 4G", 'fwa_4g')
        
        row += 2
        
        # FWA 5G Section
        row = self._write_product_section(ws, row, "FWA 5G", 'fwa_5g')
        
        # Apply formatting
        self._apply_formatting(ws)
        
        # Adjust column widths
        for col in range(1, 10):
            ws.column_dimensions[get_column_letter(col)].width = 15
        
        # Save
        wb.save(output_path)
        print(f"Report saved to: {output_path}")
        return output_path
    
    def _write_product_section(self, ws, start_row, product_name, product_key):
        """Write a product section (Mobile Prepaid/Postpaid/FWA)"""
        row = start_row
        
        # Section header
        ws.merge_cells(f'A{row}:I{row}')
        ws[f'A{row}'] = product_name
        ws[f'A{row}'].font = Font(size=14, bold=True)
        ws[f'A{row}'].fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid")
        row += 1
        
        # Column headers
        headers = ['Region', 'RT Total ADA', 'WoW', 'DXS ADA', 'WoW', 'LS ADA', 'WoW', 'DRP ADA', 'WoW']
        for col, header in enumerate(headers, 1):
            cell = ws.cell(row, col, header)
            cell.font = Font(bold=True)
            cell.fill = PatternFill(start_color="F0F0F0", end_color="F0F0F0", fill_type="solid")
            cell.alignment = Alignment(horizontal='center')
        row += 1
        
        # Data rows
        total_current = {'rt': 0, 'dxs': 0, 'ls': 0, 'drp': 0}
        total_previous = {'rt': 0, 'dxs': 0, 'ls': 0, 'drp': 0}
        
        for region in self.REGIONS:
            # Get current week data
            drp_current = self.current_week_data['drp'].get(region, {}).get(product_key, 0)
            dxs_current = self._get_dxs_value(self.current_week_data, product_key, region)
            ls_current = self._get_ls_value(self.current_week_data, product_key, region)
            rt_current = drp_current + dxs_current + ls_current
            
            # Get previous week data
            drp_previous = self.previous_week_data['drp'].get(region, {}).get(product_key, 0)
            dxs_previous = self._get_dxs_value(self.previous_week_data, product_key, region)
            ls_previous = self._get_ls_value(self.previous_week_data, product_key, region)
            rt_previous = drp_previous + dxs_previous + ls_previous
            
            # Update totals
            total_current['rt'] += rt_current
            total_current['dxs'] += dxs_current
            total_current['ls'] += ls_current
            total_current['drp'] += drp_current
            total_previous['rt'] += rt_previous
            total_previous['dxs'] += dxs_previous
            total_previous['ls'] += ls_previous
            total_previous['drp'] += drp_previous
            
            # Write row
            ws.cell(row, 1, region).font = Font(bold=True)
            ws.cell(row, 2, self._format_number(rt_current))
            ws.cell(row, 3, self.calculate_wow(rt_current, rt_previous))
            ws.cell(row, 4, self._format_number(dxs_current))
            ws.cell(row, 5, self.calculate_wow(dxs_current, dxs_previous))
            ws.cell(row, 6, self._format_number(ls_current))
            ws.cell(row, 7, self.calculate_wow(ls_current, ls_previous))
            ws.cell(row, 8, self._format_number(drp_current))
            ws.cell(row, 9, self.calculate_wow(drp_current, drp_previous))
            
            # Apply WoW coloring
            self._color_wow_cell(ws.cell(row, 3))
            self._color_wow_cell(ws.cell(row, 5))
            self._color_wow_cell(ws.cell(row, 7))
            self._color_wow_cell(ws.cell(row, 9))
            
            row += 1
        
        # Total row
        ws.cell(row, 1, "Total").font = Font(bold=True)
        ws.cell(row, 2, self._format_number(total_current['rt']))
        ws.cell(row, 3, self.calculate_wow(total_current['rt'], total_previous['rt']))
        ws.cell(row, 4, self._format_number(total_current['dxs']))
        ws.cell(row, 5, self.calculate_wow(total_current['dxs'], total_previous['dxs']))
        ws.cell(row, 6, self._format_number(total_current['ls']))
        ws.cell(row, 7, self.calculate_wow(total_current['ls'], total_previous['ls']))
        ws.cell(row, 8, self._format_number(total_current['drp']))
        ws.cell(row, 9, self.calculate_wow(total_current['drp'], total_previous['drp']))
        
        # Color total row
        for col in range(1, 10):
            ws.cell(row, col).fill = PatternFill(start_color="E6F2FF", end_color="E6F2FF", fill_type="solid")
            ws.cell(row, col).font = Font(bold=True)
        
        self._color_wow_cell(ws.cell(row, 3))
        self._color_wow_cell(ws.cell(row, 5))
        self._color_wow_cell(ws.cell(row, 7))
        self._color_wow_cell(ws.cell(row, 9))
        
        return row + 1
    
    def _write_drp_program_section(self, ws, start_row):
        """Write DRP Prepaid Program section"""
        row = start_row
        
        # Section header
        ws.merge_cells(f'A{row}:I{row}')
        ws[f'A{row}'] = "DRP Prepaid Program"
        ws[f'A{row}'].font = Font(size=14, bold=True)
        ws[f'A{row}'].fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid")
        row += 1
        
        # Column headers
        headers = ['Region', 'Double Data ADA', 'WoW', 'TECNO ADA', 'WoW', 'CAMON 40', 'WoW', 'POVA 7', 'WoW']
        for col, header in enumerate(headers, 1):
            cell = ws.cell(row, col, header)
            cell.font = Font(bold=True)
            cell.fill = PatternFill(start_color="F0F0F0", end_color="F0F0F0", fill_type="solid")
            cell.alignment = Alignment(horizontal='center')
        row += 1
        
        # Data rows
        total_current = {'double': 0, 'tecno': 0, 'camon': 0, 'pova': 0}
        total_previous = {'double': 0, 'tecno': 0, 'camon': 0, 'pova': 0}
        
        for region in self.REGIONS:
            # Current week
            double_current = self.current_week_data['drp'].get(region, {}).get('double_data', 0)
            camon_current = self.current_week_data['tecno'].get(region, {}).get('camon_40', 0)
            pova_current = self.current_week_data['tecno'].get(region, {}).get('pova_7', 0)
            tecno_current = camon_current + pova_current
            
            # Previous week
            double_previous = self.previous_week_data['drp'].get(region, {}).get('double_data', 0)
            camon_previous = self.previous_week_data['tecno'].get(region, {}).get('camon_40', 0)
            pova_previous = self.previous_week_data['tecno'].get(region, {}).get('pova_7', 0)
            tecno_previous = camon_previous + pova_previous
            
            # Update totals
            total_current['double'] += double_current
            total_current['tecno'] += tecno_current
            total_current['camon'] += camon_current
            total_current['pova'] += pova_current
            total_previous['double'] += double_previous
            total_previous['tecno'] += tecno_previous
            total_previous['camon'] += camon_previous
            total_previous['pova'] += pova_previous
            
            # Write row
            ws.cell(row, 1, region).font = Font(bold=True)
            ws.cell(row, 2, self._format_number(double_current))
            ws.cell(row, 3, self.calculate_wow(double_current, double_previous))
            ws.cell(row, 4, self._format_number(tecno_current))
            ws.cell(row, 5, self.calculate_wow(tecno_current, tecno_previous))
            ws.cell(row, 6, self._format_number(camon_current))
            ws.cell(row, 7, self.calculate_wow(camon_current, camon_previous))
            ws.cell(row, 8, self._format_number(pova_current))
            ws.cell(row, 9, self.calculate_wow(pova_current, pova_previous))
            
            # Apply coloring
            for col in [3, 5, 7, 9]:
                self._color_wow_cell(ws.cell(row, col))
            
            row += 1
        
        # Total row
        ws.cell(row, 1, "Total").font = Font(bold=True)
        ws.cell(row, 2, self._format_number(total_current['double']))
        ws.cell(row, 3, self.calculate_wow(total_current['double'], total_previous['double']))
        ws.cell(row, 4, self._format_number(total_current['tecno']))
        ws.cell(row, 5, self.calculate_wow(total_current['tecno'], total_previous['tecno']))
        ws.cell(row, 6, self._format_number(total_current['camon']))
        ws.cell(row, 7, self.calculate_wow(total_current['camon'], total_previous['camon']))
        ws.cell(row, 8, self._format_number(total_current['pova']))
        ws.cell(row, 9, self.calculate_wow(total_current['pova'], total_previous['pova']))
        
        # Color total row
        for col in range(1, 10):
            ws.cell(row, col).fill = PatternFill(start_color="E6F2FF", end_color="E6F2FF", fill_type="solid")
            ws.cell(row, col).font = Font(bold=True)
        
        for col in [3, 5, 7, 9]:
            self._color_wow_cell(ws.cell(row, col))
        
        return row + 1
    
    def _get_dxs_value(self, week_data, product_key, region):
        """Get DXS value for a product and region"""
        if product_key == 'mobile_prepaid':
            return week_data.get('dxs_prepaid', {}).get(region, 0)
        elif product_key == 'mobile_postpaid':
            return week_data.get('dxs_postpaid', {}).get(region, 0)
        elif product_key == 'fwa_4g':
            return week_data.get('dxs_fwa', {}).get('fwa_4g', {}).get(region, 0)
        elif product_key == 'fwa_5g':
            return week_data.get('dxs_fwa', {}).get('fwa_5g', {}).get(region, 0)
        return 0
    
    def _get_ls_value(self, week_data, product_key, region):
        """Get LS value for a product and region"""
        return week_data.get('ls', {}).get(product_key, {}).get(region, 0)
    
    def _format_number(self, value):
        """Format number for display"""
        if value < 10 and value > 0:
            return round(value, 1)
        return int(round(value))
    
    def _color_wow_cell(self, cell):
        """Apply color coding to WoW cell"""
        value = str(cell.value)
        if value == "-":
            cell.font = Font(color="808080")  # Gray
        elif "%" in value:
            try:
                pct = int(value.replace("%", ""))
                if pct > 0:
                    cell.font = Font(color="008000", bold=True)  # Green
                elif pct < 0:
                    cell.font = Font(color="FF0000", bold=True)  # Red
            except:
                pass
    
    def _apply_formatting(self, ws):
        """Apply borders to all cells"""
        thin_border = Border(
            left=Side(style='thin'),
            right=Side(style='thin'),
            top=Side(style='thin'),
            bottom=Side(style='thin')
        )
        
        for row in ws.iter_rows():
            for cell in row:
                if cell.value is not None:
                    cell.border = thin_border


def generate_weekly_report(input_dir, mapping_csv, output_path):
    """
    Main function to generate retail trade weekly report
    
    Args:
        input_dir: Directory containing input Excel files
        mapping_csv: Path to store mapping CSV file
        output_path: Output Excel file path
    
    Returns:
        Path to generated report
    """
    generator = RetailTradeReportGenerator(input_dir, mapping_csv)
    
    print("Step 1: Loading store mapping...")
    generator.load_store_mapping()
    
    print("Step 2: Identifying files...")
    generator.identify_files()
    
    print("Step 3: Processing all files...")
    generator.process_all_files()
    
    print("Step 4: Generating report...")
    return generator.generate_report(output_path)


if __name__ == "__main__":
    # Example usage
    input_dir = "/mnt/user-data/uploads/"
    mapping_csv = "/mnt/user-data/uploads/store_mapping.csv"
    output_path = "/mnt/user-data/outputs/Retail_Trade_Weekly_Report.xlsx"
    
    result = generate_weekly_report(input_dir, mapping_csv, output_path)
    print(f"✓ Report generated: {result}")


path: references/store_mapping.csv
content: Store Name,Region,Aliases
SM Megamall,NCR,"Megamall|SM Mega|MEGAMALL"
SM North EDSA,NCR,"SM North|North EDSA|SM NORTH"
SM Manila,NCR,"Manila|SM MLA"
SM Southmall,NCR,"Southmall|South Mall|SM SOUTH"
Greenhills,NCR,"Greenhills Shopping Center|GH"
Glorietta,NCR,"Glorietta Mall|GLLO"
Robinsons Galleria Ortigas,NCR,"Galleria Ortigas|Rob Galleria|GALLERIA ORTIGAS"
SM Valenzuela,NCR,"Valenzuela|SM VAL"
Gateway,NCR,"Gateway Mall|Araneta Gateway"
J & M COMPUTER SOFTWARE TRADING,NCR,"J&M|J & M|LS"
REG'S FONE GADGETS AND ACCESSORIES SHOP- LS,NCR,"REG'S FONE|REGS FONE|LS"
REG'S FONE GADGETS AND ACCESSORIES SHOP_LSKiosk,NCR,""
SM Bacoor,SLZ,"Bacoor|SM BACOOR"
Sta. Lucia East Grand Mall,SLZ,"Sta Lucia|STA LUCIA|East Grand Mall"
Robinsons Place Lipa,SLZ,"Rob Lipa|Robinsons Lipa|LIPA"
Robinsons Galleria South,SLZ,"Galleria South|Rob Galleria South|GALLERIA SOUTH"
Pacific Mall Lucena,SLZ,"Lucena|Pacific Lucena|PACIFIC LUCENA"
Target Mall Sta Rosa,SLZ,"Sta Rosa|Target Sta Rosa|STA ROSA"
LOPEZBILLS E-LOAD AND SIMCARDS TRADING,SLZ,"LOPEZBILLS|LOPEZ BILLS"
SM Marilao,CLZ,"Marilao|SM MARILAO"
SM Mega Center Cabanatuan,CLZ,"Cabanatuan|Mega Cabanatuan|SM CABANATUAN"
Marquee Mall,CLZ,"Marquee|Angeles|MARQUEE ANGELES"
Robinsons Townsville Cabanatuan,CLZ,"Townsville|Rob Cabanatuan|CABANATUAN"
YANA'S GADGETS ENTERPRISES,CLZ,"YANA'S|YANA GADGETS|INLINE MARIA AURORA"
J.P CONCEPCION CELLSHOPPE CORP.-LS,CLZ,"JP CONCEPCION|CONCEPCION|LS"
"GOLD KARSON MARKETING, INC.",CLZ,"GOLD KARSON|KARSON"
ALPHARUS ENTERPRISES_LS,CLZ,"ALPHARUS|ALPHARUS LS"
SM Baguio,NLZ,"Baguio|SM BAGUIO"
PROGRESSIVE WIRELESS COMMUNICATION-LS,NLZ,"PROGRESSIVE|PROGRESSIVE LS"
Robinsons Galleria Cebu,EVIS,"Galleria Cebu|Rob Cebu|GALLERIA CEBU"
SM City Cebu,EVIS,"Cebu|SM CEBU"
Alturas Mall Bohol,EVIS,"Bohol|Alturas Bohol|ALTURAS"
Robinsons Tacloban,EVIS,"Tacloban|Rob Tacloban|TACLOBAN"
JY CELLPHONE AND ACCESSORIES-LS-INLINE,EVIS,"JY CELLPHONE|JY|LS-INPPE CORP-LS"
RGG BAYAD..EXPRESS BUSINESS CENTER- LS,EVIS,"RCG BAYAD|RCG|LS"
JM TECH CELLULAR ACCESSORIES- LS,EVIS,"JM TECH|JMTECH|LS"
"JMAGS SHOP, INC.-LS",EVIS,"JMAGS|JMAGS SHOP|LS"
KAJAKA COMMUNICATION AND GADGETS INC_LS,EVIS,"KAJAKA|KAJAKA COMM|LS"
Ayala Capitol Central,WVIS,"Capitol Central|Bacolod Capitol|BACOLOD"
SM Iloilo,WVIS,"Iloilo|SM ILOILO"
SUPREME TELECOM OPC,WVIS,"SUPREME|SUPREME TELECOM"
Ayala Centrio CDO,MIN,"Centrio|CDO Centrio|CENTRIO CDO"
SM Ecoland,MIN,"Ecoland|Davao Ecoland|SM ECOLAND"
SM Lanang,MIN,"Lanang|Davao Lanang|SM LANANG"
SM Gensan,MIN,"Gensan|General Santos|SM GENSAN"
Gaisano Mall of Tagum,MIN,"Tagum|Gaisano Tagum|TAGUM"
Robinsons Butuan,MIN,"Butuan|Rob Butuan|BUTUAN"
Robinsons Iligan,MIN,"Iligan|Rob Iligan|ILIGAN"
KAREN AND JAY MARKETING_KIOSK,MIN,"KAREN JAY|KAREN AND JAY|KIOSK"
DIGILIFE GADGETS & DEVICES-LS,MIN,"DIGILIFE|DIGI LIFE|LS"
Udenna Tower,Others,"UDENNA|Udenna"
AVICOM ENTERPRISES-LS,SLZ,"AVICOM|AVICOM LS"


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)