diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..e32cc49 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,54 @@ +kind: pipeline +type: docker +name: vendor-report-cicd + +trigger: + branch: + - main + - deployment-ready + event: + - push + +steps: + - name: build-image + image: docker:24-cli + volumes: + - name: dockersock + path: /var/run/docker.sock + environment: + DOCKER_HOST: unix:///var/run/docker.sock + DOCKER_BUILDKIT: 1 + commands: + - echo "Building vendor-report Docker image..." + - docker build -t registry.lci.ge/taskboard/vendor-report-api:${DRONE_COMMIT_SHA:0:8} . + - echo "Tagging image as latest..." + - docker tag registry.lci.ge/taskboard/vendor-report-api:${DRONE_COMMIT_SHA:0:8} registry.lci.ge/taskboard/vendor-report-api:latest + - echo "Vendor-report Docker image built and tagged successfully" + when: + event: + - push + + - name: push-image + image: docker:24-cli + volumes: + - name: dockersock + path: /var/run/docker.sock + environment: + DOCKER_HOST: unix:///var/run/docker.sock + commands: + - echo "Pushing vendor-report image to registry..." + - docker push registry.lci.ge/taskboard/vendor-report-api:${DRONE_COMMIT_SHA:0:8} + - docker push registry.lci.ge/taskboard/vendor-report-api:latest + - echo "Vendor-report image pushed to registry" + - echo "Cleaning up local images to save space..." + - docker rmi registry.lci.ge/taskboard/vendor-report-api:${DRONE_COMMIT_SHA:0:8} || true + - docker rmi registry.lci.ge/taskboard/vendor-report-api:latest || true + when: + event: + - push + +volumes: +- name: dockersock + host: + path: /var/run/docker.sock + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0fff3f1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# Python API Server for Vendor Report Generator +FROM python:3.11-slim + +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements first for better caching +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application files +COPY . . + +# Create directories for reports and output +RUN mkdir -p /app/reports /app/output + +# Expose port (internal only, not exposed in docker-compose) +EXPOSE 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +# Run API server (uses environment variables for configuration) +CMD ["python", "api_server.py"] + diff --git a/QUICK_START.md b/QUICK_START.md new file mode 100644 index 0000000..c45e4db --- /dev/null +++ b/QUICK_START.md @@ -0,0 +1,163 @@ +# Quick Start Guide: SharePoint Integration & Scheduling + +This guide will help you quickly set up SharePoint integration and automated report generation. + +## Quick Setup (5 minutes) + +### 1. Install Dependencies + +```bash +pip install -r requirements.txt +``` + +### 2. Create Configuration + +```bash +cp config.yaml.template config.yaml +``` + +### 3. Configure SharePoint + +Edit `config.yaml`: + +```yaml +sharepoint: + enabled: true + site_url: "https://yourcompany.sharepoint.com/sites/YourSite" + folder_path: "/Shared Documents/Reports" # Path to your Excel files + use_app_authentication: true + client_id: "your-azure-ad-client-id" + client_secret: "your-azure-ad-client-secret" +``` + +**To get Azure AD credentials:** +1. Go to Azure Portal → App registrations +2. Create new registration or use existing +3. Create a client secret +4. Grant SharePoint API permissions: `Sites.Read.All` +5. Copy Client ID and Client Secret to config + +### 4. Choose Your Deployment Method + +#### Option A: Scheduled Reports (Recommended) + +Edit `config.yaml`: +```yaml +scheduler: + enabled: true + schedule_type: "cron" + cron_expression: "0 8 * * *" # 8 AM daily + timezone: "America/New_York" +``` + +Start scheduler: +```bash +python scheduler.py +``` + +#### Option B: On-Demand via API + +Edit `config.yaml`: +```yaml +api: + enabled: true + port: 8080 + api_key: "your-secret-key" # Optional but recommended +``` + +Start API server: +```bash +python api_server.py +``` + +Generate report: +```bash +curl -X POST http://localhost:8080/api/generate \ + -H "X-API-Key: your-secret-key" \ + -H "Content-Type: application/json \ + -d '{"download_from_sharepoint": true}' +``` + +## How It Works + +1. **SharePoint Download**: Downloads latest Excel files from SharePoint folder +2. **Report Generation**: Processes Excel files and generates reports +3. **Output**: Creates `output/report.json` and `output/report.html` + +## Testing + +### Test SharePoint Connection + +```bash +python sharepoint_downloader.py +``` + +This will download files from SharePoint to the `reports/` directory. + +### Test Report Generation + +```bash +python report_generator.py +``` + +This will generate reports from files in the `reports/` directory. + +## Deployment Options + +### As a Service (Linux) + +```bash +# Create systemd service +sudo nano /etc/systemd/system/vendor-report.service + +# Add: +[Unit] +Description=Vendor Report Scheduler +After=network.target + +[Service] +Type=simple +User=your-user +WorkingDirectory=/path/to/vendor_report +ExecStart=/usr/bin/python3 /path/to/vendor_report/scheduler.py +Restart=always + +[Install] +WantedBy=multi-user.target + +# Enable and start +sudo systemctl enable vendor-report +sudo systemctl start vendor-report +``` + +### Docker (Coming Soon) + +The application can be containerized for easy deployment. + +## Troubleshooting + +### SharePoint Authentication Fails + +- Verify Azure AD app has correct permissions +- Check client ID and secret are correct +- Ensure SharePoint site URL is correct (include `/sites/SiteName`) + +### Files Not Downloading + +- Check folder path is correct (use SharePoint's "Copy path" feature) +- Verify app has read permissions +- Check file pattern matches your Excel files + +### Scheduler Not Running + +- Check timezone is correct +- Verify cron expression format +- Check logs for errors + +## Next Steps + +- Set up monitoring/alerting for failed reports +- Configure webhook notifications +- Set up automated email delivery of reports +- Integrate with other systems via API + diff --git a/README.md b/README.md index e01f089..e3915d9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A Python tool that generates comprehensive vendor punchlist reports from Excel files. The tool processes Excel data, normalizes vendor information, calculates metrics, and generates both JSON and interactive HTML reports. +> **📘 For Taskboard Integration**: See [TASKBOARD_INTEGRATION_CONTEXT.md](./TASKBOARD_INTEGRATION_CONTEXT.md) for detailed context and integration possibilities. + ## Features - **Direct Excel Processing**: Reads Excel files directly using pandas @@ -11,6 +13,9 @@ A Python tool that generates comprehensive vendor punchlist reports from Excel f - **Oldest Unaddressed Items**: Identifies and highlights the oldest 3 unaddressed items per vendor - **Interactive HTML Reports**: Generates searchable, filterable HTML reports with tabs and filters - **JSON Export**: Exports structured JSON data for further processing +- **SharePoint Integration**: Automatically download Excel files from SharePoint +- **Scheduled Generation**: Automatically generate reports on a schedule (interval or cron) +- **Web API**: REST API for on-demand report generation ## Requirements @@ -221,6 +226,12 @@ vendor_report/ ├── html_generator.py # HTML report generation ├── models.py # Pydantic data models ├── excel_to_text.py # Utility for Excel to text conversion +├── sharepoint_downloader.py # SharePoint file downloader +├── scheduler.py # Scheduled report generation +├── api_server.py # REST API for on-demand reports +├── web_ui.py # Web UI for easy access +├── config.py # Configuration management +├── config.yaml.template # Configuration template ├── requirements.txt # Python dependencies ├── reports/ # Directory for input Excel files ├── output/ # Directory for generated reports @@ -256,6 +267,268 @@ pip install -r requirements.txt The tool uses **Baltimore/Eastern timezone (America/New_York)** for all date calculations. This ensures consistent 24-hour window calculations regardless of where the script is run. All dates are stored as timezone-aware datetime objects. +## SharePoint Integration + +The application can automatically download Excel files from SharePoint before generating reports. This is useful when your source data is stored in SharePoint. + +### Setup SharePoint Integration + +1. **Create a configuration file**: + ```bash + cp config.yaml.template config.yaml + ``` + +2. **Edit `config.yaml`** and configure SharePoint settings: + ```yaml + sharepoint: + enabled: true + site_url: "https://yourcompany.sharepoint.com/sites/YourSite" + folder_path: "/Shared Documents/Reports" + local_dir: "reports" + use_app_authentication: true # Recommended for automation + client_id: "your-azure-ad-client-id" + client_secret: "your-azure-ad-client-secret" + ``` + +3. **Authentication Options**: + + **Option A: App Authentication (Recommended)** + - Register an app in Azure AD + - Grant SharePoint permissions (Sites.Read.All or Sites.ReadWrite.All) + - Use `client_id` and `client_secret` in config + - Set `use_app_authentication: true` + + **Option B: User Authentication** + - Use your SharePoint username and password + - Set `username` and `password` in config + - Set `use_app_authentication: false` + +4. **Test SharePoint download**: + ```bash + python sharepoint_downloader.py + ``` + +### Manual SharePoint Download + +Download files from SharePoint without generating a report: +```bash +python sharepoint_downloader.py +``` + +## Scheduled Report Generation + +The application can automatically generate reports on a schedule, optionally downloading from SharePoint first. + +### Setup Scheduling + +1. **Edit `config.yaml`**: + ```yaml + scheduler: + enabled: true + schedule_type: "interval" # or "cron" + interval_hours: 24 # Generate every 24 hours + # OR use cron expression: + # cron_expression: "0 8 * * *" # 8 AM daily + timezone: "America/New_York" + ``` + +2. **Start the scheduler**: + ```bash + python scheduler.py + ``` + + The scheduler will run continuously and generate reports according to your schedule. + +3. **Schedule Types**: + - **interval**: Generate report every N hours + - **cron**: Use cron expression for precise scheduling (e.g., "0 8 * * *" for 8 AM daily) + - **once**: Run once immediately (for testing) + +### Running Scheduler as a Service + +**Linux (systemd)**: +```bash +# Create service file: /etc/systemd/system/vendor-report-scheduler.service +[Unit] +Description=Vendor Report Scheduler +After=network.target + +[Service] +Type=simple +User=your-user +WorkingDirectory=/path/to/vendor_report +ExecStart=/usr/bin/python3 /path/to/vendor_report/scheduler.py +Restart=always + +[Install] +WantedBy=multi-user.target + +# Enable and start +sudo systemctl enable vendor-report-scheduler +sudo systemctl start vendor-report-scheduler +``` + +**Windows (Task Scheduler)**: +- Create a scheduled task that runs `python scheduler.py` at startup or on a schedule + +## Web UI & On-Demand Report Generation + +The application includes both a **Web UI** and a **REST API** for generating reports on demand. + +### Web UI (Recommended for Easy Access) + +A simple, user-friendly web interface for generating reports without using the terminal. + +1. **Start the Web UI server**: + ```bash + python web_ui.py + ``` + +2. **Open in browser**: + ``` + http://localhost:8080 + ``` + +3. **Features**: + - One-click report generation + - Download from SharePoint & generate (single button) + - View generated reports + - View service status + - View configuration + - No terminal knowledge required! + +### REST API + +The application also includes a REST API for integration with other systems or manual triggers. + +### Setup API Server + +1. **Edit `config.yaml`**: + ```yaml + api: + enabled: true + host: "0.0.0.0" + port: 8080 + api_key: "your-secret-api-key" # Optional, for authentication + ``` + +2. **Start the Web UI** (recommended): + ```bash + python web_ui.py + ``` + + Then open `http://localhost:8080` in your browser. + + **OR start the API server** (for programmatic access): + ```bash + python api_server.py + ``` + +3. **Generate report via API**: + ```bash + # Without authentication + curl -X POST http://localhost:8080/api/generate \ + -H "Content-Type: application/json" \ + -d '{"download_from_sharepoint": true}' + + # With API key authentication + curl -X POST http://localhost:8080/api/generate \ + -H "Content-Type: application/json" \ + -H "X-API-Key: your-secret-api-key" \ + -d '{"download_from_sharepoint": true}' + ``` + +### API Endpoints + +- **POST `/api/generate`**: Generate report on demand + - Request body (optional): + ```json + { + "download_from_sharepoint": true, + "reports_dir": "reports", + "output_file": "output/report.json" + } + ``` + +- **GET `/api/status`**: Get service status and configuration + +- **GET `/health`**: Health check endpoint + +### Example: Integration with Webhook + +You can trigger report generation from SharePoint webhooks, Power Automate, or any HTTP client: + +```python +import requests + +response = requests.post( + 'http://your-server:8080/api/generate', + json={'download_from_sharepoint': True}, + headers={'X-API-Key': 'your-api-key'} +) +print(response.json()) +``` + +## Configuration + +The application uses a YAML configuration file (`config.yaml`) for all settings. You can also use environment variables: + +### Environment Variables + +```bash +# SharePoint +export SHAREPOINT_ENABLED=true +export SHAREPOINT_SITE_URL="https://yourcompany.sharepoint.com/sites/YourSite" +export SHAREPOINT_FOLDER_PATH="/Shared Documents/Reports" +export SHAREPOINT_CLIENT_ID="your-client-id" +export SHAREPOINT_CLIENT_SECRET="your-client-secret" +export SHAREPOINT_USE_APP_AUTH=true + +# Scheduler +export SCHEDULER_ENABLED=true +export SCHEDULER_INTERVAL_HOURS=24 + +# API +export API_ENABLED=true +export API_PORT=8080 +export API_KEY="your-api-key" +``` + +## Complete Workflow Example + +Here's a complete example setup for automated SharePoint → Report generation: + +1. **Setup configuration** (`config.yaml`): + ```yaml + sharepoint: + enabled: true + site_url: "https://company.sharepoint.com/sites/Reports" + folder_path: "/Shared Documents/Vendor Reports" + use_app_authentication: true + client_id: "your-client-id" + client_secret: "your-client-secret" + + scheduler: + enabled: true + schedule_type: "cron" + cron_expression: "0 8 * * *" # 8 AM daily + timezone: "America/New_York" + + report: + output_dir: "output" + reports_dir: "reports" + ``` + +2. **Start scheduler**: + ```bash + python scheduler.py + ``` + +3. **The scheduler will**: + - Download latest Excel files from SharePoint at 8 AM daily + - Generate reports automatically + - Save to `output/report.json` and `output/report.html` + ## License [Add your license information here] diff --git a/SHAREPOINT_SETUP.md b/SHAREPOINT_SETUP.md new file mode 100644 index 0000000..7084998 --- /dev/null +++ b/SHAREPOINT_SETUP.md @@ -0,0 +1,127 @@ +# SharePoint Configuration Guide + +This guide will help you get the configuration values needed to connect to SharePoint. + +## Quick Answer: Where to Get Configuration Values + +### 1. SharePoint Site URL +- Go to your SharePoint site in a browser +- Copy the URL from the address bar +- Example: `https://yourcompany.sharepoint.com/sites/YourSiteName` +- **Important**: Include `/sites/SiteName` if it's a subsite + +### 2. Folder Path +- Navigate to the folder containing your Excel files in SharePoint +- Right-click the folder → "Copy path" or "Details" +- Example: `/Shared Documents/Reports` or `/sites/YourSite/Shared Documents/Vendor Reports` +- **Tip**: In SharePoint, go to the folder, click "..." menu → "Copy link" and extract the path + +### 3. Azure AD App Credentials (Recommended Method) + +#### Step 1: Register App in Azure AD +1. Go to [Azure Portal](https://portal.azure.com) +2. Navigate to **Azure Active Directory** → **App registrations** +3. Click **New registration** +4. Name it (e.g., "Vendor Report Generator") +5. Select **Accounts in this organizational directory only** +6. Click **Register** + +#### Step 2: Create Client Secret +1. In your app, go to **Certificates & secrets** +2. Click **New client secret** +3. Add description (e.g., "Vendor Report Secret") +4. Choose expiration (recommend 24 months) +5. Click **Add** +6. **IMPORTANT**: Copy the **Value** immediately (you won't see it again!) + - This is your `client_secret` + +#### Step 3: Get Client ID +1. In your app, go to **Overview** +2. Copy the **Application (client) ID** + - This is your `client_id` + +#### Step 4: Grant SharePoint Permissions +1. In your app, go to **API permissions** +2. Click **Add a permission** +3. Select **SharePoint** +4. Choose **Application permissions** (not Delegated) +5. Select **Sites.Read.All** (or Sites.ReadWrite.All if you need write access) +6. Click **Add permissions** +7. Click **Grant admin consent** (important!) +8. Wait for status to show "Granted for [Your Organization]" + +### 4. Alternative: User Credentials (Less Secure) +If you can't use app authentication: +- `username`: Your SharePoint/Office 365 email +- `password`: Your password (not recommended for automation) + +## Complete Configuration Example + +Once you have all values, add them to `config.yaml`: + +```yaml +sharepoint: + enabled: true + site_url: "https://yourcompany.sharepoint.com/sites/YourSite" + folder_path: "/Shared Documents/Reports" # Path to your Excel files folder + local_dir: "reports" # Where to save downloaded files + use_app_authentication: true # Use app auth (recommended) + client_id: "12345678-1234-1234-1234-123456789abc" # From Azure AD + client_secret: "your-secret-value-here" # From Azure AD (the Value, not Secret ID!) + file_pattern: "*.xlsx" # Only download Excel files + overwrite: true # Overwrite existing files +``` + +## Testing Your Configuration + +1. **Test SharePoint connection**: + ```bash + python sharepoint_downloader.py + ``` + +2. **Or use the Web UI**: + - Start: `python web_ui.py` + - Open: `http://localhost:8080` + - Click "Update Data from SharePoint" + - Check for errors + +## Common Issues + +### "SharePoint authentication failed" +- **Check**: Client ID and secret are correct +- **Check**: App has been granted admin consent +- **Check**: Permissions are "Application permissions" (not Delegated) + +### "Folder not found" +- **Check**: Folder path is correct (case-sensitive) +- **Tip**: Use SharePoint's "Copy path" feature +- **Check**: Path starts with `/` (e.g., `/Shared Documents/...`) + +### "No files downloaded" +- **Check**: Folder contains Excel files (`.xlsx` or `.xls`) +- **Check**: File pattern matches your files +- **Check**: You have read permissions to the folder + +### "Access denied" +- **Check**: App has `Sites.Read.All` permission +- **Check**: Admin consent has been granted +- **Check**: App is registered in the same tenant as SharePoint + +## Security Best Practices + +1. **Use App Authentication** (not user credentials) +2. **Store secrets securely**: + - Use environment variables in production + - Never commit `config.yaml` with secrets to git + - Use a secrets manager for production +3. **Limit permissions**: Only grant `Sites.Read.All` (not write access unless needed) +4. **Rotate secrets**: Update client secrets regularly + +## Getting Help + +If you're stuck: +1. Check the terminal/console for detailed error messages +2. Verify each configuration value step by step +3. Test with a simple folder first (one Excel file) +4. Check Azure AD app status in Azure Portal + diff --git a/TASKBOARD_INTEGRATION_CONTEXT.md b/TASKBOARD_INTEGRATION_CONTEXT.md new file mode 100644 index 0000000..6e94561 --- /dev/null +++ b/TASKBOARD_INTEGRATION_CONTEXT.md @@ -0,0 +1,470 @@ +# Vendor Report Generator - Taskboard Integration Context + +## 🎯 Goal & Purpose + +The **Vendor Report Generator** is a Python-based tool designed to automate the generation of comprehensive vendor punchlist reports from Excel files stored in SharePoint. The goal is to: + +1. **Automate Report Generation**: Eliminate manual Excel processing and report creation +2. **Centralize Data**: Pull vendor punchlist data directly from SharePoint +3. **Provide Insights**: Generate actionable reports with metrics, priorities, and status tracking +4. **Enable Integration**: Make reports accessible within Taskboard for team collaboration + +### Business Value + +- **Time Savings**: Automates hours of manual report generation +- **Accuracy**: Consistent data normalization and calculation +- **Visibility**: Real-time vendor status tracking and metrics +- **Accessibility**: Web-based interface for non-technical users +- **Integration Ready**: Can be embedded as a tool/widget in Taskboard + +--- + +## 📋 Application Overview + +### What It Does + +The application processes Excel files containing vendor punchlist items and generates: +- **Interactive HTML Reports**: Searchable, filterable web reports with vendor tabs, status filters, and priority grouping +- **JSON Data**: Structured data for further processing or API integration +- **Metrics**: Per-vendor statistics (total items, closed/open counts, 24-hour updates, oldest unaddressed items) + +### Key Features + +1. **Excel Processing**: Direct pandas-based reading (no manual conversion needed) +2. **Data Normalization**: Automatically handles vendor name variations, status inconsistencies, priority classifications +3. **24-Hour Tracking**: Identifies items added, closed, or changed to monitor status in the last 24 hours (Baltimore/Eastern timezone) +4. **Priority Classification**: Groups items by Very High, High, Medium, Low priorities +5. **Oldest Items**: Highlights the oldest 3 unaddressed items per vendor +6. **SharePoint Integration**: Automatically downloads Excel files from SharePoint +7. **Scheduled Generation**: Can run automatically on a schedule +8. **Web UI**: User-friendly interface for generating reports +9. **REST API**: Programmatic access for integration + +--- + +## 🏗️ Architecture & Components + +### Core Components + +``` +vendor_report/ +├── report_generator.py # Main entry point - orchestrates report generation +├── data_preprocessor.py # Excel parsing, normalization, data cleaning +├── html_generator.py # Generates interactive HTML reports +├── models.py # Pydantic data models for validation +├── sharepoint_downloader.py # SharePoint file downloader +├── scheduler.py # Scheduled report generation +├── api_server.py # REST API server +├── web_ui.py # Web UI server (Flask-based) +├── config.py # Configuration management +└── config.yaml # Configuration file +``` + +### Data Flow + +``` +SharePoint Excel Files + ↓ +[SharePoint Downloader] → Local reports/ directory + ↓ +[Data Preprocessor] → Normalize vendors, statuses, priorities, parse dates + ↓ +[Report Generator] → Calculate metrics, group by vendor, identify updates + ↓ +[HTML Generator] → Generate interactive report.html + ↓ +[Output] → output/report.json + output/report.html +``` + +### Processing Pipeline + +1. **Input**: Excel files with columns: + - Punchlist Name, Vendor, Priority, Description, Date Identified, Status Updates, Issue Image, Status, Date Completed + +2. **Preprocessing**: + - Parse Excel files using pandas + - Normalize vendor names (handle case variations, combined vendors) + - Normalize statuses (Complete, Monitor, Incomplete) + - Classify priorities (Very High, High, Medium, Low) + - Parse dates (multiple formats supported) + - Calculate 24-hour windows (Baltimore/Eastern timezone) + - Calculate item age (days since identified) + +3. **Report Generation**: + - Group items by vendor + - Calculate metrics per vendor (total, closed, open, monitor counts) + - Identify 24-hour updates (added, closed, changed to monitor) + - Find oldest 3 unaddressed items per vendor + - Group by priority levels + - Generate JSON structure + - Generate HTML report + +4. **Output**: + - `output/report.json`: Structured JSON data + - `output/report.html`: Interactive HTML report + - `output/preprocessed_data.txt`: Debug/preview data + +--- + +## 🔧 Technical Details + +### Dependencies + +```python +# Core +pandas>=2.0.0 # Excel processing +openpyxl>=3.0.0 # Excel file reading +pydantic>=2.0.0 # Data validation + +# Optional: SharePoint +Office365-REST-Python-Client>=2.3.0 # SharePoint API + +# Optional: Scheduling +apscheduler>=3.10.0 # Task scheduling + +# Optional: Web UI/API +flask>=2.3.0 # Web framework +flask-cors>=4.0.0 # CORS support + +# Configuration +pyyaml>=6.0 # YAML config parsing +``` + +### Configuration + +Configuration is managed via `config.yaml`: + +```yaml +sharepoint: + enabled: true/false + site_url: "https://company.sharepoint.com/sites/SiteName" + folder_path: "/Shared Documents/Reports" + use_app_authentication: true # Azure AD app auth (recommended) + client_id: "azure-ad-client-id" + client_secret: "azure-ad-client-secret" + +scheduler: + enabled: true/false + schedule_type: "interval" | "cron" | "once" + interval_hours: 24 + cron_expression: "0 8 * * *" # 8 AM daily + +api: + enabled: true/false + port: 8080 + api_key: "optional-api-key" + +report: + output_dir: "output" + reports_dir: "reports" +``` + +### API Endpoints + +**Web UI Server** (`web_ui.py`): +- `GET /` - Web UI interface +- `POST /api/generate` - Generate report +- `POST /api/update-sharepoint` - Download files from SharePoint +- `GET /api/status` - Service status +- `GET /api/reports` - List generated reports +- `GET /api/config` - Configuration (safe, no secrets) +- `GET /reports/` - Serve report files + +**API Server** (`api_server.py`): +- `POST /api/generate` - Generate report (programmatic) +- `GET /api/status` - Service status +- `GET /health` - Health check + +### Data Models + +**PunchlistItem**: +- punchlist_name, description, priority, date_identified, date_completed +- status, status_updates, issue_image, age_days + +**VendorMetrics**: +- vendor_name, total_items, closed_count, open_count, monitor_count +- updates_24h (added, closed, changed_to_monitor) +- oldest_unaddressed (top 3) +- very_high_priority_items, high_priority_items + +**FullReport**: +- report_generated_at, vendors[], summary{} + +--- + +## 🔗 Taskboard Integration Possibilities + +### Option 1: Embedded Widget/Page + +Create a new page in Taskboard (`/vendor-reports`) that: +- Uses Taskboard's authentication (already authenticated users) +- Embeds the generated HTML report in an iframe or renders it directly +- Provides a button to trigger report generation +- Shows report history/list + +**Implementation**: +```typescript +// taskboard/src/app/(dashboard)/vendor-reports/page.tsx +// - Call Python API server to generate reports +// - Display generated HTML reports +// - Use Taskboard's UI components for consistency +``` + +### Option 2: API Integration + +Create Taskboard API routes that proxy to the Python API: +- `POST /api/vendor-reports/generate` → Calls Python `POST /api/generate` +- `GET /api/vendor-reports/list` → Calls Python `GET /api/reports` +- `GET /api/vendor-reports/status` → Calls Python `GET /api/status` + +**Benefits**: +- Single authentication system (Taskboard) +- Consistent API patterns +- Can add Taskboard-specific features (notifications, task linking) + +### Option 3: Background Service + +Run the Python scheduler as a background service that: +- Generates reports on schedule +- Saves reports to a shared location +- Taskboard displays the latest report +- Can trigger notifications when reports are updated + +### Option 4: Task Integration + +Link reports to Taskboard tasks: +- Create tasks for vendors with unaddressed items +- Link report generation to project/task completion +- Use report metrics in task dashboards + +--- + +## 🚀 Usage Examples + +### Command Line + +```bash +# Generate report from local files +python report_generator.py + +# Generate with custom directories +python report_generator.py --reports-dir /path/to/excel --output /path/to/output.json +``` + +### Web UI + +```bash +# Start web UI server +python web_ui.py + +# Open browser: http://localhost:8080 +# Click "Update Data from SharePoint" → "Generate Report" +``` + +### API + +```bash +# Generate report via API +curl -X POST http://localhost:8080/api/generate \ + -H "Content-Type: application/json" \ + -d '{"download_from_sharepoint": false}' + +# Update from SharePoint +curl -X POST http://localhost:8080/api/update-sharepoint +``` + +### Scheduled + +```bash +# Start scheduler (runs continuously) +python scheduler.py + +# Configured via config.yaml: +# scheduler: +# enabled: true +# schedule_type: "cron" +# cron_expression: "0 8 * * *" # 8 AM daily +``` + +### Programmatic (Python) + +```python +from report_generator import generate_report + +# Generate report +report_data = generate_report( + reports_dir="reports", + output_file="output/report.json", + verbose=True +) + +# Access data +vendors = report_data['vendors'] +summary = report_data['summary'] +``` + +--- + +## 📊 Report Structure + +### JSON Report Format + +```json +{ + "report_generated_at": "2025-11-06T16:00:00", + "vendors": [ + { + "vendor_name": "VendorName", + "total_items": 10, + "closed_count": 5, + "open_count": 3, + "monitor_count": 2, + "updates_24h": { + "added": [...], + "closed": [...], + "changed_to_monitor": [...] + }, + "oldest_unaddressed": [...], + "very_high_priority_items": [...], + "high_priority_items": [...], + "closed_items": [...], + "monitor_items": [...], + "open_items": [...] + } + ], + "summary": { + "total_vendors": 5, + "total_items": 50, + "total_closed": 25, + "total_open": 15, + "total_monitor": 10 + } +} +``` + +### HTML Report Features + +- **Summary Cards**: Overview statistics +- **Vendor Tabs**: Quick navigation between vendors +- **Status Tabs**: Filter by All, Yesterday's Updates, Oldest Unaddressed, Closed, Monitor, Open +- **Search & Filters**: Search by name/description, filter by vendor/status/priority +- **Quick Filters**: Show only vendors with updates or oldest items +- **Responsive Design**: Works on desktop and mobile + +--- + +## 🔐 Authentication & Security + +### Current State + +- **Web UI**: Optional API key authentication +- **SharePoint**: Azure AD app authentication (recommended) or user credentials +- **No User Management**: Standalone application + +### Taskboard Integration Benefits + +- **Leverage Existing Auth**: Use Taskboard's Authentik/Microsoft Entra ID authentication +- **Role-Based Access**: Control who can generate/view reports +- **Audit Trail**: Track who generated reports (via Taskboard user system) +- **Secure Storage**: Use Taskboard's file storage for reports + +--- + +## 📝 Integration Checklist + +### Phase 1: Basic Integration +- [ ] Set up Python API server as background service +- [ ] Create Taskboard API route that proxies to Python API +- [ ] Create Taskboard page to display reports +- [ ] Add "Generate Report" button in Taskboard UI + +### Phase 2: Enhanced Integration +- [ ] Use Taskboard authentication for report access +- [ ] Store report metadata in Taskboard database +- [ ] Add report history/versioning +- [ ] Link reports to projects/tasks + +### Phase 3: Advanced Features +- [ ] Scheduled report generation via Taskboard +- [ ] Notifications when reports are generated +- [ ] Dashboard widgets showing report metrics +- [ ] Export reports to Taskboard tasks/boards + +--- + +## 🛠️ Development Notes + +### Running Locally + +```bash +# Setup +cd vendor_report +python -m venv venv +source venv/bin/activate # Windows: venv\Scripts\activate +pip install -r requirements.txt + +# Configure +cp config.yaml.template config.yaml +# Edit config.yaml with SharePoint credentials + +# Run Web UI +python web_ui.py +# Open http://localhost:8080 +``` + +### Deployment Considerations + +- **Python Environment**: Requires Python 3.8+ +- **Dependencies**: Install via pip +- **Configuration**: Store secrets securely (environment variables or vault) +- **Port**: Default 8080 (configurable) +- **File Storage**: Reports saved to `output/` directory +- **SharePoint**: Requires Azure AD app registration + +### Error Handling + +- Graceful handling of missing Excel files +- SharePoint connection errors logged +- Invalid data formats handled +- User-friendly error messages in Web UI + +--- + +## 📚 Additional Resources + +- **SharePoint Setup**: See `SHAREPOINT_SETUP.md` for detailed Azure AD configuration +- **Quick Start**: See `QUICK_START.md` for 5-minute setup guide +- **Full Documentation**: See `README.md` for complete usage guide + +--- + +## 💡 Integration Ideas for Taskboard + +1. **Vendor Dashboard**: Show vendor metrics as cards/widgets +2. **Report History**: Track when reports were generated, by whom +3. **Task Creation**: Auto-create tasks for vendors with oldest unaddressed items +4. **Notifications**: Alert project managers when reports are generated +5. **Export to Tasks**: Convert report items to Taskboard tasks +6. **Project Linking**: Associate reports with Taskboard projects +7. **Scheduled Reports**: Use Taskboard's scheduling to trigger reports +8. **Role-Based Views**: Different report views for different user roles + +--- + +## 🔄 Current Status + +- ✅ Core functionality complete +- ✅ SharePoint integration working +- ✅ Web UI functional +- ✅ API endpoints available +- ✅ Scheduled generation supported +- ⏳ Taskboard integration pending +- ⏳ Authentication integration pending +- ⏳ Database storage pending + +--- + +**Last Updated**: November 6, 2025 +**Version**: 1.0 +**Status**: Production Ready (Standalone), Integration Ready (Taskboard) + diff --git a/api_server.py b/api_server.py new file mode 100644 index 0000000..a2da95a --- /dev/null +++ b/api_server.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +""" +Web API Server for On-Demand Report Generation + +Provides REST API endpoints to trigger report generation on demand. +""" + +import logging +from pathlib import Path +from typing import Optional +import json + +try: + from flask import Flask, jsonify, request + from flask_cors import CORS + FLASK_AVAILABLE = True +except ImportError: + FLASK_AVAILABLE = False + logging.warning("Flask not installed. API server features disabled.") + +from config import load_config +from report_generator import generate_report +from sharepoint_downloader import download_from_sharepoint + +logger = logging.getLogger(__name__) + +app = None +config = None + + +def create_app(config_path: Optional[str] = None): + """Create and configure Flask app.""" + global app, config + + if not FLASK_AVAILABLE: + raise ImportError( + "Flask is required for API server. " + "Install it with: pip install flask flask-cors" + ) + + app = Flask(__name__) + CORS(app) # Enable CORS for all routes + + config = load_config(config_path) + api_config = config.get('api', {}) + sharepoint_config = config.get('sharepoint', {}) + report_config = config.get('report', {}) + + # Store config in app context + app.config['API_KEY'] = api_config.get('api_key') + app.config['SHAREPOINT_CONFIG'] = sharepoint_config + app.config['REPORT_CONFIG'] = report_config + + @app.route('/health', methods=['GET']) + def health(): + """Health check endpoint.""" + return jsonify({ + 'status': 'healthy', + 'service': 'vendor-report-generator' + }) + + @app.route('/api/generate', methods=['POST']) + def generate_report_endpoint(): + """ + Generate report on demand. + + Request body (optional): + { + "download_from_sharepoint": true, + "reports_dir": "reports", + "output_file": "output/report.json" + } + """ + # Check API key if configured + api_key = app.config.get('API_KEY') + if api_key: + provided_key = request.headers.get('X-API-Key') or request.json.get('api_key') if request.json else None + if provided_key != api_key: + return jsonify({'error': 'Invalid API key'}), 401 + + try: + request_data = request.json or {} + download_from_sp = request_data.get('download_from_sharepoint', False) + + # Download from SharePoint if requested + if download_from_sp: + sp_config = app.config['SHAREPOINT_CONFIG'] + if not sp_config.get('enabled'): + return jsonify({ + 'error': 'SharePoint is not enabled in configuration' + }), 400 + + logger.info("Downloading files from SharePoint...") + try: + downloaded = download_from_sharepoint( + site_url=sp_config['site_url'], + folder_path=sp_config.get('folder_path'), + file_path=sp_config.get('file_path'), + local_dir=sp_config.get('local_dir', 'reports'), + username=sp_config.get('username'), + password=sp_config.get('password'), + client_id=sp_config.get('client_id'), + client_secret=sp_config.get('client_secret'), + use_app_authentication=sp_config.get('use_app_authentication', False), + file_pattern=sp_config.get('file_pattern'), + overwrite=sp_config.get('overwrite', True) + ) + logger.info(f"Downloaded {len(downloaded)} file(s) from SharePoint") + except Exception as e: + logger.error(f"Failed to download from SharePoint: {e}") + return jsonify({ + 'error': f'SharePoint download failed: {str(e)}' + }), 500 + + # Generate report + report_config = app.config['REPORT_CONFIG'] + reports_dir = request_data.get('reports_dir', report_config.get('reports_dir', 'reports')) + output_file = request_data.get('output_file', + str(Path(report_config.get('output_dir', 'output')) / 'report.json')) + + logger.info(f"Generating report from {reports_dir}...") + report_data = generate_report( + reports_dir=reports_dir, + output_file=output_file, + verbose=False # Don't print to console in API mode + ) + + if report_data: + return jsonify({ + 'status': 'success', + 'message': 'Report generated successfully', + 'output_file': output_file, + 'summary': report_data.get('summary', {}), + 'vendors_count': len(report_data.get('vendors', [])) + }) + else: + return jsonify({ + 'error': 'Report generation failed' + }), 500 + + except Exception as e: + logger.error(f"Error generating report: {e}", exc_info=True) + return jsonify({ + 'error': f'Report generation failed: {str(e)}' + }), 500 + + @app.route('/api/status', methods=['GET']) + def status(): + """Get service status and configuration.""" + return jsonify({ + 'status': 'running', + 'sharepoint_enabled': app.config['SHAREPOINT_CONFIG'].get('enabled', False), + 'reports_dir': app.config['REPORT_CONFIG'].get('reports_dir', 'reports'), + 'output_dir': app.config['REPORT_CONFIG'].get('output_dir', 'output') + }) + + @app.route('/api/report/json', methods=['GET']) + def get_report_json(): + """Get latest report JSON file.""" + try: + report_config = app.config['REPORT_CONFIG'] + output_dir = Path(report_config.get('output_dir', 'output')) + report_file = output_dir / 'report.json' + + if not report_file.exists(): + return jsonify({'error': 'Report not found. Generate a report first.'}), 404 + + with open(report_file, 'r', encoding='utf-8') as f: + report_data = json.load(f) + + return jsonify(report_data) + except Exception as e: + logger.error(f"Error reading report JSON: {e}", exc_info=True) + return jsonify({'error': f'Failed to read report: {str(e)}'}), 500 + + @app.route('/api/report/html', methods=['GET']) + def get_report_html(): + """Get latest report HTML file.""" + try: + from flask import send_from_directory + + report_config = app.config['REPORT_CONFIG'] + output_dir = Path(report_config.get('output_dir', 'output')) + html_file = output_dir / 'report.html' + + if not html_file.exists(): + return jsonify({'error': 'Report HTML not found. Generate a report first.'}), 404 + + return send_from_directory(str(output_dir), 'report.html', mimetype='text/html') + except Exception as e: + logger.error(f"Error reading report HTML: {e}", exc_info=True) + return jsonify({'error': f'Failed to read report HTML: {str(e)}'}), 500 + + return app + + +def run_server(config_path: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None): + """Run the API server.""" + app = create_app(config_path) + + api_config = config.get('api', {}) + server_host = host or api_config.get('host', '0.0.0.0') + server_port = port or api_config.get('port', 8080) + + logger.info(f"Starting API server on {server_host}:{server_port}") + app.run(host=server_host, port=server_port, debug=False) + + +if __name__ == "__main__": + import sys + + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + + config_path = sys.argv[1] if len(sys.argv) > 1 else None + + # Check if API is enabled + config = load_config(config_path) + if not config.get('api', {}).get('enabled', False): + logger.warning("API is disabled in configuration. Set api.enabled=true to enable.") + logger.info("Starting API server anyway (for testing)...") + + run_server(config_path=config_path) + diff --git a/config.py b/config.py new file mode 100644 index 0000000..6e8fb69 --- /dev/null +++ b/config.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +""" +Configuration Management + +Loads configuration from YAML file or environment variables. +""" + +import os +import yaml +import logging +from pathlib import Path +from typing import Dict, Optional, Any + +try: + from dotenv import load_dotenv + DOTENV_AVAILABLE = True +except ImportError: + DOTENV_AVAILABLE = False + +DEFAULT_CONFIG = { + 'sharepoint': { + 'enabled': False, + 'site_url': '', + 'folder_path': '/Shared Documents/Reports', + 'file_path': None, # Use folder_path for multiple files, file_path for single file + 'local_dir': 'reports', + 'username': None, + 'password': None, + 'client_id': None, + 'client_secret': None, + 'use_app_authentication': False, + 'file_pattern': '*.xlsx', + 'overwrite': True + }, + 'scheduler': { + 'enabled': False, + 'schedule_type': 'interval', # 'interval', 'cron', or 'once' + 'interval_hours': 24, # For interval type + 'cron_expression': '0 8 * * *', # For cron type (8 AM daily) + 'timezone': 'America/New_York' + }, + 'api': { + 'enabled': False, + 'host': '0.0.0.0', + 'port': 8080, + 'api_key': None # Optional API key for authentication + }, + 'report': { + 'output_dir': 'output', + 'reports_dir': 'reports' + } +} + + +def load_config(config_path: Optional[str] = None) -> Dict[str, Any]: + """ + Load configuration from YAML file or environment variables. + + Args: + config_path: Path to config.yaml file (default: config.yaml in current directory) + + Returns: + Configuration dictionary + """ + # Load .env file if available (from current directory or parent taskboard directory) + if DOTENV_AVAILABLE: + # Try loading from vendor_report/.env first + env_file = Path(__file__).parent / ".env" + if not env_file.exists(): + # Try loading from parent taskboard/.env + parent_env = Path(__file__).parent.parent / "taskboard" / ".env" + if parent_env.exists(): + env_file = parent_env + if env_file.exists(): + load_dotenv(env_file) + logging.info(f"Loaded environment variables from {env_file}") + + if config_path is None: + config_path = Path(__file__).parent / "config.yaml" + else: + config_path = Path(config_path) + + config = DEFAULT_CONFIG.copy() + + # Load from YAML file if exists + if config_path.exists(): + try: + with open(config_path, 'r') as f: + file_config = yaml.safe_load(f) or {} + # Deep merge with defaults + config = _deep_merge(config, file_config) + except Exception as e: + print(f"Warning: Failed to load config from {config_path}: {e}") + + # Override with environment variables + config = _load_from_env(config) + + return config + + +def _deep_merge(base: Dict, override: Dict) -> Dict: + """Deep merge two dictionaries.""" + result = base.copy() + for key, value in override.items(): + if key in result and isinstance(result[key], dict) and isinstance(value, dict): + result[key] = _deep_merge(result[key], value) + else: + result[key] = value + return result + + +def _load_from_env(config: Dict) -> Dict: + """Load configuration from environment variables.""" + # SharePoint settings + if os.getenv('SHAREPOINT_ENABLED'): + config['sharepoint']['enabled'] = os.getenv('SHAREPOINT_ENABLED').lower() == 'true' + if os.getenv('SHAREPOINT_SITE_URL'): + config['sharepoint']['site_url'] = os.getenv('SHAREPOINT_SITE_URL') + if os.getenv('SHAREPOINT_FOLDER_PATH'): + config['sharepoint']['folder_path'] = os.getenv('SHAREPOINT_FOLDER_PATH') + if os.getenv('SHAREPOINT_USERNAME'): + config['sharepoint']['username'] = os.getenv('SHAREPOINT_USERNAME') + if os.getenv('SHAREPOINT_PASSWORD'): + config['sharepoint']['password'] = os.getenv('SHAREPOINT_PASSWORD') + # Check for SHAREPOINT_CLIENT_ID first, fallback to AZURE_AD_CLIENT_ID + if os.getenv('SHAREPOINT_CLIENT_ID'): + config['sharepoint']['client_id'] = os.getenv('SHAREPOINT_CLIENT_ID') + elif os.getenv('AZURE_AD_CLIENT_ID'): + config['sharepoint']['client_id'] = os.getenv('AZURE_AD_CLIENT_ID') + + # Check for SHAREPOINT_CLIENT_SECRET first, fallback to AZURE_AD_CLIENT_SECRET + if os.getenv('SHAREPOINT_CLIENT_SECRET'): + config['sharepoint']['client_secret'] = os.getenv('SHAREPOINT_CLIENT_SECRET') + elif os.getenv('AZURE_AD_CLIENT_SECRET'): + config['sharepoint']['client_secret'] = os.getenv('AZURE_AD_CLIENT_SECRET') + + if os.getenv('SHAREPOINT_USE_APP_AUTH'): + config['sharepoint']['use_app_authentication'] = os.getenv('SHAREPOINT_USE_APP_AUTH').lower() == 'true' + elif os.getenv('SHAREPOINT_USE_APP_AUTH') is None and os.getenv('AZURE_AD_CLIENT_ID'): + # If Azure AD credentials are present, default to app auth + config['sharepoint']['use_app_authentication'] = True + + # Scheduler settings + if os.getenv('SCHEDULER_ENABLED'): + config['scheduler']['enabled'] = os.getenv('SCHEDULER_ENABLED').lower() == 'true' + if os.getenv('SCHEDULER_INTERVAL_HOURS'): + config['scheduler']['interval_hours'] = int(os.getenv('SCHEDULER_INTERVAL_HOURS')) + if os.getenv('SCHEDULER_CRON'): + config['scheduler']['cron_expression'] = os.getenv('SCHEDULER_CRON') + + # API settings + if os.getenv('API_ENABLED'): + config['api']['enabled'] = os.getenv('API_ENABLED').lower() == 'true' + if os.getenv('API_PORT'): + config['api']['port'] = int(os.getenv('API_PORT')) + if os.getenv('API_HOST'): + config['api']['host'] = os.getenv('API_HOST') + if os.getenv('API_KEY'): + config['api']['api_key'] = os.getenv('API_KEY') + + # Report settings + if os.getenv('REPORT_OUTPUT_DIR'): + config['report']['output_dir'] = os.getenv('REPORT_OUTPUT_DIR') + if os.getenv('REPORT_REPORTS_DIR'): + config['report']['reports_dir'] = os.getenv('REPORT_REPORTS_DIR') + + return config + + +def save_config_template(config_path: Optional[str] = None) -> None: + """Save a template configuration file.""" + if config_path is None: + config_path = Path(__file__).parent / "config.yaml.template" + else: + config_path = Path(config_path) + + template = """# Vendor Report Generator Configuration + +# SharePoint Integration +sharepoint: + enabled: false # Set to true to enable SharePoint downloads + site_url: "https://yourcompany.sharepoint.com/sites/YourSite" + folder_path: "/Shared Documents/Reports" # Path to folder containing Excel files + # file_path: "/Shared Documents/Reports/file.xlsx" # Alternative: single file path + local_dir: "reports" # Local directory to save downloaded files + username: null # Username for user authentication (leave null if using app auth) + password: null # Password for user authentication (leave null if using app auth) + client_id: null # Azure AD app client ID (for app authentication) + client_secret: null # Azure AD app client secret (for app authentication) + use_app_authentication: false # Set to true to use app authentication (recommended) + file_pattern: "*.xlsx" # Pattern to filter files + overwrite: true # Whether to overwrite existing files + +# Scheduler Configuration +scheduler: + enabled: false # Set to true to enable scheduled report generation + schedule_type: "interval" # Options: "interval", "cron", or "once" + interval_hours: 24 # For interval type: generate report every N hours + cron_expression: "0 8 * * *" # For cron type: generate at 8 AM daily (cron format) + timezone: "America/New_York" # Timezone for scheduling + +# API Configuration (for on-demand report generation) +api: + enabled: false # Set to true to enable web API + host: "0.0.0.0" # Host to bind API server + port: 8080 # Port for API server + api_key: null # Optional API key for authentication (set to enable auth) + +# Report Settings +report: + output_dir: "output" # Directory for generated reports + reports_dir: "reports" # Directory containing Excel files +""" + + with open(config_path, 'w') as f: + f.write(template) + + print(f"Configuration template saved to: {config_path}") + diff --git a/config.yaml.template b/config.yaml.template new file mode 100644 index 0000000..a0b9d8c --- /dev/null +++ b/config.yaml.template @@ -0,0 +1,37 @@ +# Vendor Report Generator Configuration + +# SharePoint Integration +sharepoint: + enabled: false # Set to true to enable SharePoint downloads + site_url: "https://yourcompany.sharepoint.com/sites/YourSite" + folder_path: "/Shared Documents/Reports" # Path to folder containing Excel files + # file_path: "/Shared Documents/Reports/file.xlsx" # Alternative: single file path + local_dir: "reports" # Local directory to save downloaded files + username: null # Username for user authentication (leave null if using app auth) + password: null # Password for user authentication (leave null if using app auth) + client_id: null # Azure AD app client ID (for app authentication) + client_secret: null # Azure AD app client secret (for app authentication) + use_app_authentication: false # Set to true to use app authentication (recommended) + file_pattern: "*.xlsx" # Pattern to filter files + overwrite: true # Whether to overwrite existing files + +# Scheduler Configuration +scheduler: + enabled: false # Set to true to enable scheduled report generation + schedule_type: "interval" # Options: "interval", "cron", or "once" + interval_hours: 24 # For interval type: generate report every N hours + cron_expression: "0 8 * * *" # For cron type: generate at 8 AM daily (cron format) + timezone: "America/New_York" # Timezone for scheduling + +# API Configuration (for on-demand report generation) +api: + enabled: false # Set to true to enable web API + host: "0.0.0.0" # Host to bind API server + port: 8080 # Port for API server + api_key: null # Optional API key for authentication (set to enable auth) + +# Report Settings +report: + output_dir: "output" # Directory for generated reports + reports_dir: "reports" # Directory containing Excel files + diff --git a/output/preprocessed_data.txt b/output/preprocessed_data.txt index d96ea03..829c09b 100644 --- a/output/preprocessed_data.txt +++ b/output/preprocessed_data.txt @@ -1,6 +1,6 @@ PREPROCESSED EXCEL DATA ================================================================================ -Current Date (Baltimore/Eastern): 2025-11-05 10:08:52 EST +Current Date (Baltimore/Eastern): 2025-11-06 11:37:45 EST Total Items: 162 VENDOR: Amazon @@ -25,15 +25,9 @@ Total Items: 74 Open: 3 Monitor: 4 -RECENT UPDATES (Yesterday's Date): - ADDED: Estops are getting damaged on the UL lane | 2025-11-04 00:00:00 | Incomplete - ADDED: Raise the fill height ob the DTC's approx 2 " | 2025-11-04 00:00:00 | Incomplete - CLOSED: SCADA performance issue | 2025-11-04 00:00:00 | Complete - CLOSED: gap control at non con sorter. | 2025-11-04 00:00:00 | Complete - OLDEST UNADDRESSED (Top 3): - Estops are getting damaged on the UL lane | Age: 1 days | 2025-11-04 00:00:00 | Incomplete - Raise the fill height ob the DTC's approx 2 " | Age: 1 days | 2025-11-04 00:00:00 | Incomplete + Estops are getting damaged on the UL lane | Age: 2 days | 2025-11-04 00:00:00 | Incomplete + Raise the fill height ob the DTC's approx 2 " | Age: 2 days | 2025-11-04 00:00:00 | Incomplete 3:1 merge code update | Age: None days | | Incomplete VERY HIGH PRIORITY (6 items): @@ -309,15 +303,10 @@ Total Items: 25 Open: 4 Monitor: 1 -RECENT UPDATES (Yesterday's Date): - ADDED: ) There is a catchpoint of bent metal that is sticking out from the tail assembly on PS10-1 where it transitions to PS11-1. This is catching polys during operation. Jesse is going to look into making proper modifications to eliminate this. | 2025-11-04 00:00:00 | Incomplete - ADDED: 2) When product from PS10-1 is flowing towards PS11-1, there is no snowplow and instead the slide just dead ends with a corner of sidepan. I’ve asked Jesse to look into fabricating a UHMW piece that could bridge this corner to push products down onto the belt. | 2025-11-04 00:00:00 | Incomplete - ADDED: 3) The black UHMW strip under the belt which transitions the belt from slider bed to tail roller is too sharp and is shaving the bottom side of the belt. Jesse and his team are going to look into pulling this uhmw strip out, properly chamfering it and then re-installing. | 2025-11-04 00:00:00 | Incomplete - OLDEST UNADDRESSED (Top 3): - NCS1-1 aligner belt failed | Age: 4 days | 2025-11-01 00:00:00 | Incomplete - ) There is a catchpoint of bent metal that is sticking out from the tail assembly on PS10-1 where it transitions to PS11-1. This is catching polys during operation. Jesse is going to look into making proper modifications to eliminate this. | Age: 1 days | 2025-11-04 00:00:00 | Incomplete - 2) When product from PS10-1 is flowing towards PS11-1, there is no snowplow and instead the slide just dead ends with a corner of sidepan. I’ve asked Jesse to look into fabricating a UHMW piece that could bridge this corner to push products down onto the belt. | Age: 1 days | 2025-11-04 00:00:00 | Incomplete + NCS1-1 aligner belt failed | Age: 5 days | 2025-11-01 00:00:00 | Incomplete + ) There is a catchpoint of bent metal that is sticking out from the tail assembly on PS10-1 where it transitions to PS11-1. This is catching polys during operation. Jesse is going to look into making proper modifications to eliminate this. | Age: 2 days | 2025-11-04 00:00:00 | Incomplete + 2) When product from PS10-1 is flowing towards PS11-1, there is no snowplow and instead the slide just dead ends with a corner of sidepan. I’ve asked Jesse to look into fabricating a UHMW piece that could bridge this corner to push products down onto the belt. | Age: 2 days | 2025-11-04 00:00:00 | Incomplete VERY HIGH PRIORITY (4 items): Flow turn Belt Replacement | Complete | 10/10/25 @@ -420,7 +409,7 @@ Total Items: 5 Monitor: 1 OLDEST UNADDRESSED (Top 3): - Add DHL label to Scan tunnel valid message | Age: 9 days | 2025-10-27 00:00:00 | Incomplete + Add DHL label to Scan tunnel valid message | Age: 10 days | 2025-10-27 00:00:00 | Incomplete VERY HIGH PRIORITY (1 items): Add DHL label to Scan tunnel valid message | Incomplete | 2025-10-27 00:00:00 diff --git a/output/report.html b/output/report.html index 3c72cf5..fbc972f 100644 --- a/output/report.html +++ b/output/report.html @@ -397,8 +397,8 @@ } .badge-danger { - background: #fee2e2; - color: #991b1b; + background: #dc2626; + color: white; } .badge-secondary { @@ -587,7 +587,7 @@

Vendor Punchlist Report

- Generated: 2025-11-05 19:08:52 | + Generated: 2025-11-06 20:37:46 | Total Vendors: 16 | Total Items: 162
@@ -664,6 +664,10 @@

9

Open

+
+

9

+

Incomplete

+
@@ -695,6 +699,10 @@
0
Open
+
+
0
+
Incomplete
+
@@ -706,6 +714,7 @@ +
@@ -809,6 +818,15 @@
+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -833,17 +851,22 @@
3
Open
+
+
3
+
Incomplete
+
- + +
@@ -2262,115 +2285,12 @@ KK 11/4 - 0 gap errors since logic update, closing this item

-
Yesterday's Updates (4)
-
- - - -
- -
-
-
Items Added Yesterday (2)
-
    - -
  • -
    -
    Estops are getting damaged on the UL lane
    -
    - Incomplete - (2) High - -
    -
    -
    -

    Description: UL16-1, UL15-3, UL10-2 (both sides) UL8-1 , UL7-3, UL6-1 protect or relocate devices

    -

    Date Identified: 2025-11-04

    - - - -
    -
  • - -
  • -
    -
    Raise the fill height ob the DTC's approx 2 "
    -
    - Incomplete - (2) High - -
    -
    -
    - -

    Date Identified: 2025-11-04

    - - - -
    -
  • - -
-
-
- -
-
-
Items Closed Yesterday (2)
-
    - -
  • -
    -
    SCADA performance issue
    -
    - Complete - (2) High - -
    -
    -
    -

    Description: report export crashed system

    - -

    Date Completed: 2025-11-04

    -

    Status Updates: 11/1 RC - SCADA was updated to improve performance yesterday NIGHT. -KK 11/4 - Assuming this is related to SCADA crashing when trying to export large amounts of data (like a week of alarms). We addressed, just tested and it did not crash.

    - -
    -
  • - -
  • -
    -
    gap control at non con sorter.
    -
    - Complete - - -
    -
    -
    -

    Description: code change/ help with box tracking.

    -

    Date Identified: 2025-10-30

    -

    Date Completed: 2025-11-04

    -

    Status Updates: working on it today. /tomorrow -KK 11/4 - 0 gap errors since logic update, closing this item

    - -
    -
  • - -
-
-
- -
-
-
Items Changed to Monitor Yesterday (0)
-
    -
  • No items changed to monitor yesterday
  • -
-
-
+
Yesterday's Updates
+
    +
  • No updates from yesterday
  • +
- +
@@ -2385,7 +2305,7 @@ KK 11/4 - 0 gap errors since logic update, closing this item

Incomplete (2) High - 1 days + 2 days
@@ -2403,7 +2323,7 @@ KK 11/4 - 0 gap errors since logic update, closing this item

Incomplete (2) High - 1 days + 2 days
@@ -3819,6 +3739,69 @@ KK 11/30 - Fixed the "Unknown" S04 message, verified with AWCS data

+
+ + + +
+
+ +
+
+
Incomplete Items (3)
+
    + +
  • +
    +
    3:1 merge code update
    +
    + Incomplete + (2) High + +
    +
    +
    +

    Description: mcm02 by monday 11/4. mcm01 ul 1-3 done. mcm01 complete.

    + + +

    Status Updates: 11/3 - no update... Igor at BNA8

    + +
    +
  • + +
  • +
    +
    Estops are getting damaged on the UL lane
    +
    + Incomplete + (2) High + +
    +
    +
    +

    Description: UL16-1, UL15-3, UL10-2 (both sides) UL8-1 , UL7-3, UL6-1 protect or relocate devices

    +

    Date Identified: 2025-11-04

    + + + +
    +
  • + +
  • +
    +
    Raise the fill height ob the DTC's approx 2 "
    +
    + Incomplete + (2) High + +
    +
    +
    + +

    Date Identified: 2025-11-04

    + + +
  • @@ -3849,6 +3832,10 @@ KK 11/30 - Fixed the "Unknown" S04 message, verified with AWCS data

    0
    Open
+
+
0
+
Incomplete
+
@@ -3860,6 +3847,7 @@ KK 11/30 - Fixed the "Unknown" S04 message, verified with AWCS data

+
@@ -4044,6 +4032,15 @@ KK 10/19 - Done

+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -4068,6 +4065,10 @@ KK 10/19 - Done

0
Open
+
+
0
+
Incomplete
+
@@ -4079,6 +4080,7 @@ KK 10/19 - Done

+
@@ -4261,6 +4263,15 @@ KK 10/19 - Done

+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -4285,6 +4296,10 @@ KK 10/19 - Done

1
Open
+
+
1
+
Incomplete
+
@@ -4296,6 +4311,7 @@ KK 10/19 - Done

+
@@ -5576,6 +5592,33 @@ confirmed that it works

+

Status Updates: 11/3 - Working with Autstand conveyor ready flag as disabled ... will be addressed by full lane signal (fluidload only). Still need to do similar fix on bypass but need to evaluate when to trigger full lane (vs lane not available0

+ +
+ + + + + + +
+
+
Incomplete Items (1)
+
    + +
  • +
    +
    Lane Not Available Metric too high
    +
    + Incomplete + (1) Very Hgh + +
    +
    +
    + + +

    Status Updates: 11/3 - Working with Autstand conveyor ready flag as disabled ... will be addressed by full lane signal (fluidload only). Still need to do similar fix on bypass but need to evaluate when to trigger full lane (vs lane not available0

    @@ -5608,6 +5651,10 @@ confirmed that it works

    0
    Open
+
+
0
+
Incomplete
+
@@ -5619,6 +5666,7 @@ confirmed that it works

+
@@ -5841,6 +5889,15 @@ https://t.corp.amazon.com/V1969041198
+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -5865,17 +5922,22 @@ https://t.corp.amazon.com/V1969041198
4
Open
+
+
4
+
Incomplete
+
- + +
@@ -6376,95 +6438,12 @@ https://t.corp.amazon.com/V1969041198
-
Yesterday's Updates (3)
-
- - - -
- -
-
-
Items Added Yesterday (3)
-
    - -
  • -
    -
    ) There is a catchpoint of bent metal that is sticking out from the tail assembly on PS10-1 where it transitions to PS11-1. This is catching polys during operation. Jesse is going to look into making proper modifications to eliminate this.
    -
    - Incomplete - (2) High - -
    -
    -
    - -

    Date Identified: 2025-11-04

    - -

    Status Updates: 11/4 - Wes Matthews reviewed with Jesse

    - -
    -
  • - -
  • -
    -
    2) When product from PS10-1 is flowing towards PS11-1, there is no snowplow and instead the slide just dead ends with a corner of sidepan. I’ve asked Jesse to look into fabricating a UHMW piece that could bridge this corner to push products down onto the belt.
    -
    - Incomplete - (2) High - -
    -
    -
    - -

    Date Identified: 2025-11-04

    - -

    Status Updates: 11/4 Wes Matthews reviewed with Jesse

    - -
    -
  • - -
  • -
    -
    3) The black UHMW strip under the belt which transitions the belt from slider bed to tail roller is too sharp and is shaving the bottom side of the belt. Jesse and his team are going to look into pulling this uhmw strip out, properly chamfering it and then re-installing.
    -
    - Incomplete - (2) High - -
    -
    -
    - -

    Date Identified: 2025-11-04

    - -

    Status Updates: 11/4 Wes Matthews reviewed with Jesse

    - -
    -
  • - -
-
-
- -
-
-
Items Closed Yesterday (0)
-
    -
  • No items closed yesterday
  • -
-
-
- -
-
-
Items Changed to Monitor Yesterday (0)
-
    -
  • No items changed to monitor yesterday
  • -
-
-
+
Yesterday's Updates
+
    +
  • No updates from yesterday
  • +
- +
@@ -6479,7 +6458,7 @@ https://t.corp.amazon.com/V1969041198
Incomplete (1) Very High - 4 days + 5 days
@@ -6497,7 +6476,7 @@ https://t.corp.amazon.com/V1969041198
Incomplete (2) High - 1 days + 2 days
@@ -6515,7 +6494,7 @@ https://t.corp.amazon.com/V1969041198
Incomplete (2) High - 1 days + 2 days
@@ -7015,6 +6994,87 @@ https://t.corp.amazon.com/V1969041198
+ +
+
+
Incomplete Items (4)
+
    + +
  • +
    +
    NCS1-1 aligner belt failed
    +
    + Incomplete + (1) Very High + +
    +
    +
    +

    Description: Belt failed prior to flow splitter. Replaced with belt from noncon 2 (using only non con 1) until belt is delivered.

    +

    Date Identified: 2025-11-01

    + +

    Status Updates: 11/3 - still waiting on belt to be delivered

    + +
    +
  • + +
  • +
    +
    ) There is a catchpoint of bent metal that is sticking out from the tail assembly on PS10-1 where it transitions to PS11-1. This is catching polys during operation. Jesse is going to look into making proper modifications to eliminate this.
    +
    + Incomplete + (2) High + +
    +
    +
    + +

    Date Identified: 2025-11-04

    + +

    Status Updates: 11/4 - Wes Matthews reviewed with Jesse

    + +
    +
  • + +
  • +
    +
    2) When product from PS10-1 is flowing towards PS11-1, there is no snowplow and instead the slide just dead ends with a corner of sidepan. I’ve asked Jesse to look into fabricating a UHMW piece that could bridge this corner to push products down onto the belt.
    +
    + Incomplete + (2) High + +
    +
    +
    + +

    Date Identified: 2025-11-04

    + +

    Status Updates: 11/4 Wes Matthews reviewed with Jesse

    + +
    +
  • + +
  • +
    +
    3) The black UHMW strip under the belt which transitions the belt from slider bed to tail roller is too sharp and is shaving the bottom side of the belt. Jesse and his team are going to look into pulling this uhmw strip out, properly chamfering it and then re-installing.
    +
    + Incomplete + (2) High + +
    +
    +
    + +

    Date Identified: 2025-11-04

    + +

    Status Updates: 11/4 Wes Matthews reviewed with Jesse

    + +
    +
  • + +
+
+
@@ -7039,6 +7099,10 @@ https://t.corp.amazon.com/V1969041198
0
Open
+
+
0
+
Incomplete
+
@@ -7050,6 +7114,7 @@ https://t.corp.amazon.com/V1969041198 +
@@ -7153,6 +7218,15 @@ https://t.corp.amazon.com/V1969041198
+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -7177,6 +7251,10 @@ https://t.corp.amazon.com/V1969041198
0
Open
+
+
0
+
Incomplete
+
@@ -7188,6 +7266,7 @@ https://t.corp.amazon.com/V1969041198 +
@@ -7291,6 +7370,15 @@ https://t.corp.amazon.com/V1969041198
+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -7315,6 +7403,10 @@ https://t.corp.amazon.com/V1969041198
0
Open
+
+
0
+
Incomplete
+
@@ -7326,6 +7418,7 @@ https://t.corp.amazon.com/V1969041198 +
@@ -7429,6 +7522,15 @@ https://t.corp.amazon.com/V1969041198
+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -7453,6 +7555,10 @@ https://t.corp.amazon.com/V1969041198
1
Open
+
+
1
+
Incomplete
+
@@ -7464,6 +7570,7 @@ https://t.corp.amazon.com/V1969041198 +
@@ -7618,7 +7725,7 @@ https://t.corp.amazon.com/V1969041198
Incomplete (1) Very High - 9 days + 10 days
@@ -7746,6 +7853,33 @@ https://t.corp.amazon.com/V1969041198
+ + + + + + + +
+
+
Incomplete Items (1)
+
    + +
  • +
    +
    Add DHL label to Scan tunnel valid message
    +
    + Incomplete + (1) Very High + +
    +
    +
    +

    Description: DHL label was not in orignal spec. Need to be able to identify that label and filter out other barcodes.

    +

    Date Identified: 2025-10-27

    + + +
  • @@ -7776,6 +7910,10 @@ https://t.corp.amazon.com/V1969041198
0
Open
+
+
0
+
Incomplete
+
@@ -7787,6 +7925,7 @@ https://t.corp.amazon.com/V1969041198 +
@@ -7890,6 +8029,15 @@ https://t.corp.amazon.com/V1969041198
+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -7914,6 +8062,10 @@ https://t.corp.amazon.com/V1969041198
0
Open
+
+
0
+
Incomplete
+
@@ -7925,6 +8077,7 @@ https://t.corp.amazon.com/V1969041198 +
@@ -8071,6 +8224,15 @@ https://t.corp.amazon.com/V1969041198
+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -8095,6 +8257,10 @@ https://t.corp.amazon.com/V1969041198
0
Open
+
+
0
+
Incomplete
+
@@ -8106,6 +8272,7 @@ https://t.corp.amazon.com/V1969041198 +
@@ -8209,6 +8376,15 @@ https://t.corp.amazon.com/V1969041198
+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -8233,6 +8409,10 @@ https://t.corp.amazon.com/V1969041198
0
Open
+
+
0
+
Incomplete
+
@@ -8244,6 +8424,7 @@ https://t.corp.amazon.com/V1969041198 +
@@ -8570,6 +8751,15 @@ https://t.corp.amazon.com/V1969041198
+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
@@ -8594,6 +8784,10 @@ https://t.corp.amazon.com/V1969041198
0
Open
+
+
0
+
Incomplete
+
@@ -8605,6 +8799,7 @@ https://t.corp.amazon.com/V1969041198 +
@@ -8708,6 +8903,15 @@ https://t.corp.amazon.com/V1969041198
+ +
+
+
Incomplete Items (0)
+
    +
  • No incomplete items
  • +
+
+
diff --git a/output/report.json b/output/report.json index cabbc23..bc88ee1 100644 --- a/output/report.json +++ b/output/report.json @@ -1,5 +1,5 @@ { - "report_generated_at": "2025-11-05T19:08:52.683813", + "report_generated_at": "2025-11-06T20:37:46.364471", "vendors": [ { "vendor_name": "Amazon", @@ -23,7 +23,7 @@ "status": "Complete", "status_updates": "10/15 - Jamari Randle changed NC Config to have two destinations, still waiting for a change to Crossbelt Config", "issue_image": null, - "age_days": 21 + "age_days": 22 } ], "high_priority_items": [], @@ -37,11 +37,13 @@ "status": "Complete", "status_updates": "10/15 - Jamari Randle changed NC Config to have two destinations, still waiting for a change to Crossbelt Config", "issue_image": null, - "age_days": 21 + "age_days": 22 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "Autstand", @@ -50,54 +52,8 @@ "open_count": 3, "monitor_count": 4, "updates_24h": { - "added": [ - { - "punchlist_name": "Estops are getting damaged on the UL lane", - "description": "UL16-1, UL15-3, UL10-2 (both sides) UL8-1 , UL7-3, UL6-1 protect or relocate devices", - "priority": "(2) High", - "date_identified": "2025-11-04 00:00:00", - "date_completed": null, - "status": "Incomplete", - "status_updates": null, - "issue_image": null, - "age_days": 1 - }, - { - "punchlist_name": "Raise the fill height ob the DTC's approx 2 \"", - "description": null, - "priority": "(2) High", - "date_identified": "2025-11-04 00:00:00", - "date_completed": null, - "status": "Incomplete", - "status_updates": null, - "issue_image": null, - "age_days": 1 - } - ], - "closed": [ - { - "punchlist_name": "SCADA performance issue", - "description": "report export crashed system", - "priority": "(2) High", - "date_identified": null, - "date_completed": "2025-11-04 00:00:00", - "status": "Complete", - "status_updates": "11/1 RC - SCADA was updated to improve performance yesterday NIGHT.\nKK 11/4 - Assuming this is related to SCADA crashing when trying to export large amounts of data (like a week of alarms). We addressed, just tested and it did not crash.", - "issue_image": null, - "age_days": null - }, - { - "punchlist_name": "gap control at non con sorter.", - "description": "code change/ help with box tracking.", - "priority": null, - "date_identified": "2025-10-30 00:00:00", - "date_completed": "2025-11-04 00:00:00", - "status": "Complete", - "status_updates": "working on it today. /tomorrow\nKK 11/4 - 0 gap errors since logic update, closing this item", - "issue_image": null, - "age_days": 6 - } - ], + "added": [], + "closed": [], "changed_to_monitor": [] }, "oldest_unaddressed": [ @@ -110,7 +66,7 @@ "status": "Incomplete", "status_updates": null, "issue_image": null, - "age_days": 1 + "age_days": 2 }, { "punchlist_name": "Raise the fill height ob the DTC's approx 2 \"", @@ -121,7 +77,7 @@ "status": "Incomplete", "status_updates": null, "issue_image": null, - "age_days": 1 + "age_days": 2 }, { "punchlist_name": "3:1 merge code update", @@ -145,7 +101,7 @@ "status": "Complete", "status_updates": "10/14 - Interim measure in place to force arm extension, Kevin working on updating logic at all VS fixed 10/14", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "PS Conveyor chute clearing Issues", @@ -156,7 +112,7 @@ "status": "Complete", "status_updates": "complete", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Replicate logic timers from semi VS-D to the rest of the semis", @@ -167,7 +123,7 @@ "status": "Monitor", "status_updates": "10/16 - fix applied monitor as VS become active", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Tipper timer", @@ -178,7 +134,7 @@ "status": "Complete", "status_updates": "10/16 - in progress... ECD : 10/20 (dtw needed) 10/19 - inprocess will be complete by EOD\nKK 10/19 - Fixed", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "SCADA Accurate Status Reads", @@ -189,7 +145,7 @@ "status": "Complete", "status_updates": "10/14 - All necessary information received from Beumer to execute change\n10/16 - closed", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "NC boxes are diverting to xbelt causing jams particullary at bypass curves", @@ -213,7 +169,7 @@ "status": "Complete", "status_updates": "10/14 - First problem solve line complete, second will be complete by EOD", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Jam Reset Button needed at end of NC Jackpots", @@ -224,7 +180,7 @@ "status": "Complete", "status_updates": "10/17 - buttons there, programimed, waiting on bracket", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Jam Reset buttons on Bulk divert platforms to be relocated", @@ -235,7 +191,7 @@ "status": "Complete", "status_updates": "10/17 - downtime window needed. 10/21 - to be scheduled", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Flow Splitter Verification", @@ -246,7 +202,7 @@ "status": "Complete", "status_updates": "10/17 -- adjusted still fine tuning. may reduce nc length..TBD", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Jam Reset NC End of Sorter", @@ -257,7 +213,7 @@ "status": "Complete", "status_updates": "duplicate", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "\"Destination Not Attempted\" Reason Code", @@ -268,7 +224,7 @@ "status": "Complete", "status_updates": "10/17 -- follow up\nKK 10/21 - Based on container research data from Sherri, this reason code is no longer being sent in our S04", "issue_image": "image.png", - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "PS lines stop packages at the top of the line, on the first station. It should push the packages to the last station Jackpot probem solve", @@ -279,7 +235,7 @@ "status": "Complete", "status_updates": "10/17 fixed", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Still seeing Lost Container / No Reads in NC Jackpot", @@ -290,7 +246,7 @@ "status": "Complete", "status_updates": "10/16- Reviewed logic/flow. Updated installed at noon. Will be monitoring\nKK 10/27 - Issue seems to be resolved", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "First chute orange lights keeps turning on without pakages blocking any sensors semi auto vs-d", @@ -301,7 +257,7 @@ "status": "Complete", "status_updates": "Fixed, realigned PE", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Flashing and steady blue light on a chute without any package present S014020, S014018", @@ -312,7 +268,7 @@ "status": "Complete", "status_updates": "Please ensure the cart is loaded centered in the chute, too far to the left or right, or skewed will result in the overhead sensor will read the frame of the cart and prematurely trigger full\n10/17 - fixed", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Flashing Green light on D2C , start button doesn't work S014041", @@ -323,7 +279,7 @@ "status": "Complete", "status_updates": "Fixed - bad button, has been replaced", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "non- con packages still has divert issues packages going straight to jackpot", @@ -334,7 +290,7 @@ "status": "Complete", "status_updates": "Temporary buttons in placed at the end of each sorter, brackets pending", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Scada Map not showing cameras", @@ -345,7 +301,7 @@ "status": "Complete", "status_updates": "SCADA is showing cameras, the live feeds don't work at the moment, pending AMZ response for configuration (I contacted Davo and JC from ops eng for this)\n10/17 fixed", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Scada Map: showing stopped but actually running C", @@ -356,7 +312,7 @@ "status": "Complete", "status_updates": "10/17 - need more info\nKK 10/18 - Need more info\nKK 10/21 - No clarification received\nKK 10/27 - No clarification received, closing this item", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "PS lines stop packages at the top of the line, on the first station. It should push the packages to the last station.", @@ -367,7 +323,7 @@ "status": "Complete", "status_updates": "Fixed - corrected flow control on problem solve rollers", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Still seeing Lost Container / No Reads in NC Jackpot as of 1044", @@ -378,7 +334,7 @@ "status": "Complete", "status_updates": "Flow/assignment issue - to be addressed by Sherri (AMZ) and MFO today 11/1\n10/17 monitor\nKK 10/27 - Issue seems to be resolved", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "206 - 207 max reach giving us false signals its green on skada but not operational", @@ -389,7 +345,7 @@ "status": "Complete", "status_updates": "Need further clarification 10/19 - completed\n10/19 KK - Done", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Take away line from shuttle tipper side pan is loose and causing jams/PE mis alignment", @@ -411,7 +367,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Need further clarification\nKK 10/21 - No clarification received - closed as duplicate (Row 120)", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Label auto inducts", @@ -422,7 +378,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Logic for Semi induct D is off very low throughput see video", @@ -433,7 +389,7 @@ "status": "Monitor", "status_updates": "KK 10/18 - Tweaking logic, continually adding optmizations", "issue_image": "see video in comments", - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "S012075 unavailable", @@ -444,7 +400,7 @@ "status": "Complete", "status_updates": "KK 10/19 - Done", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "VSD- S014020 BLINKING BLUE WHILE CHUTE IS EMPTY", @@ -455,7 +411,7 @@ "status": "Complete", "status_updates": "IH 10/18 - Confirmed functionality of the chute.", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "VSD- S014018 SOLID BLUE  WHILE CHUTE IS EMPTY", @@ -466,7 +422,7 @@ "status": "Complete", "status_updates": "IH 10/18 - Confirmed functionality of the chute.", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "D2C S014041 flashing green + activation button not illuminating", @@ -477,7 +433,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Problem Solve rollers do not correctly inch-and-store packages, causing line to jam prematurely", @@ -488,7 +444,7 @@ "status": "Complete", "status_updates": "KK 10/17 - PRS photoeyes have been re-aligned and tightened", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "SCADA: We need some type of refrence in SCADA. Suggest adding dock doors that correlate with their belt", @@ -499,7 +455,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Run-up is not enabled on Problem solve collection belts. They all stop when the photoeye at the rollers is blocked.", @@ -510,7 +466,7 @@ "status": "Complete", "status_updates": "KK 10/17 - PRS photoeyes have been re-aligned and tightened", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Potential PE issue at the problem solve rollers right when the chutes level off. 300 side.", @@ -521,7 +477,7 @@ "status": "Complete", "status_updates": "KK 10/17 - PRS photoeyes have been re-aligned and tightened", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "SCADA: scada camera view from main map wont allow user to pan camera angles when clicked onto", @@ -532,7 +488,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Adjusting the camera angle is not possible from SCADA. Can be done by accessing the camera directly with its IP address.", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "PS9-3CH2 - located by semi induct C. Blue light actived with no packages", @@ -543,7 +499,7 @@ "status": "Complete", "status_updates": "MC: Blue Light was NOT on when investigated, still went ahead to realign better PE/teach again (10/18/25)", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "semi induct c chute 2 Flase blue light PE needs adjusted", @@ -554,7 +510,7 @@ "status": "Complete", "status_updates": "MC: Blue Light was NOT on when investigated, still went ahead to realign better PE/teach again (10/18/25)", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "IBNC splitting sending packages to NC that are within the crossbelt MTBH", @@ -565,7 +521,7 @@ "status": "Complete", "status_updates": "KK 10/16 - Splitter logic adjusted", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "D2C Chutes are full blue lighting 8 inches short of the go cart full line", @@ -576,7 +532,7 @@ "status": "Complete", "status_updates": "KK 10/16 - Raised ALL D2C Full height 6-8\" above last setting", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "chute stays green even when chute is full", @@ -587,7 +543,7 @@ "status": "Complete", "status_updates": "IH 10/18 Resolved. IO Mapping issue.", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Non- con packages coming down to lanes", @@ -598,7 +554,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Noncon detection length reduced to 41\"", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Scada - label each line ex. UL13 , UL14 ect.", @@ -609,7 +565,7 @@ "status": "Complete", "status_updates": "IH 10/19 SCADA Updated", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "PS11-11CH6NC Intralox Sorter (S02)", @@ -620,7 +576,7 @@ "status": "Complete", "status_updates": "KK 10/27 - Need clarification. I don't see the relation between PS11-11 and the intralox sorter.", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Blue light for semi induct D is on when chute is empty", @@ -631,7 +587,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Realigned and trained photoeye", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Need 1 button to turn on ALL inducts. I am having to turn them on one by one", @@ -642,7 +598,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Our global start button is confirmed to start all autStand conveyors and Beumer CMC/SMC conveyors, only if they are not faulted.", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "One major issue and one minor issue with the non-con system:\nNo-reads are really frequent \nThe PLC is not reporting S04 message divert fails properly (no reads to jackpot, lost container, failed to divert, wrong buildings)", @@ -653,7 +609,7 @@ "status": "Monitor", "status_updates": "KK 10/18 - For the whole sort yesterday we had 13 No Reads out of 500 inducted (2.6%) we will see what can be done to reduce this.\nIH 10/19 Datalogic & PLC update conducted. Will continue to monitor.\nKK 11/30 - Fixed the \"Unknown\" S04 message, verified with AWCS data", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Non-Con running through main sorter causing system to shutdown; Non-con packages not diverting to Non-cons sorter taking up carriers on Sorter", @@ -664,7 +620,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Noncon detection length reduced to 41\"", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "DTW8/CLE5 Non-Con packages not diverting to chutes; running straight through jackpot", @@ -675,7 +631,7 @@ "status": "Complete", "status_updates": "KK 10/18 - MFO issue\nKK 10/19 - Not MFO, need to deep dive tracking/diverting/etc\nKK 10/20 - Identified potential issue with a photoeye missing incoming packages at scan tunnel induct, which manifest as unknown/unexpected packages and causing some packages to go off-the-end. We adjusted this sensor and are monitoring. Initial observations seem promising\nKK 10/27 - Resolved", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "SEMI-D uneven flow of packages to each chute", @@ -686,7 +642,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Logic continually being optmized (monitor)\nKK 10/27 - This is a duplicate item related to bulk induct shutes (row 6), I will close it", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "Clean up SCADA Active Jams to not show false jams ex BYBA-15 /BYBD-14", @@ -697,7 +653,7 @@ "status": "Complete", "status_updates": "KK 10/21 - Aware of a couple \"stuck\" alarms - still investigating why RS 10/30 - This issue seems to be fixed", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "Disable jam pe's that are to be removed (bulk chutes)", @@ -708,7 +664,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Disabled, pending physical removal", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "Evaluate jam logic /die back on bypasses.", @@ -719,7 +675,7 @@ "status": "Complete", "status_updates": "KK 10/19 - Verified that it is not being caused by runup logic, need to continue to monitor\nKK 10/27 - False jams seemed to have stopped being as frequent. There is still no runup logic causing this.", "issue_image": null, - "age_days": 17 + "age_days": 18 }, { "punchlist_name": "Encoder failure (4x) + 2 x", @@ -730,7 +686,7 @@ "status": "Complete", "status_updates": "10/23 ST soluton is changing the port... LT reseach with Rockwell 10/29 - Rockwell to send engineer to test system 10/30 ; 10/30 RC - Rockwell visit not happening today. TBD. 10/30 - looking to send \"capable engineer\" to address engineer. today another encoder failed UL18-10 (all merge stop - 20 min swapping) 11/3- Rockwell scheduled to be on site 11/6.", "issue_image": null, - "age_days": 26 + "age_days": 27 }, { "punchlist_name": "SCADA performance issue", @@ -774,7 +730,7 @@ "status": "Incomplete", "status_updates": null, "issue_image": null, - "age_days": 1 + "age_days": 2 }, { "punchlist_name": "Raise the fill height ob the DTC's approx 2 \"", @@ -785,7 +741,7 @@ "status": "Incomplete", "status_updates": null, "issue_image": null, - "age_days": 1 + "age_days": 2 } ], "closed_items": [ @@ -798,7 +754,7 @@ "status": "Complete", "status_updates": "10/14 - Interim measure in place to force arm extension, Kevin working on updating logic at all VS fixed 10/14", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "PS Conveyor chute clearing Issues", @@ -809,7 +765,7 @@ "status": "Complete", "status_updates": "complete", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Tipper timer", @@ -820,7 +776,7 @@ "status": "Complete", "status_updates": "10/16 - in progress... ECD : 10/20 (dtw needed) 10/19 - inprocess will be complete by EOD\nKK 10/19 - Fixed", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "SCADA Accurate Status Reads", @@ -831,7 +787,7 @@ "status": "Complete", "status_updates": "10/14 - All necessary information received from Beumer to execute change\n10/16 - closed", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Problem Solve dead rollers", @@ -842,7 +798,7 @@ "status": "Complete", "status_updates": "10/14 - First problem solve line complete, second will be complete by EOD", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Jam Reset Button needed at end of NC Jackpots", @@ -853,7 +809,7 @@ "status": "Complete", "status_updates": "10/17 - buttons there, programimed, waiting on bracket", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Jam Reset buttons on Bulk divert platforms to be relocated", @@ -864,7 +820,7 @@ "status": "Complete", "status_updates": "10/17 - downtime window needed. 10/21 - to be scheduled", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Flow Splitter Verification", @@ -875,7 +831,7 @@ "status": "Complete", "status_updates": "10/17 -- adjusted still fine tuning. may reduce nc length..TBD", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Semi-Auto Chute fullness PE Height", @@ -886,7 +842,7 @@ "status": "Complete", "status_updates": "10/17 - load logic was adjusted... monitor PE response.\nKK 10/27 - This is a duplicate item related to bulk induct shutes (row 6), I will close it", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Jam Cam visibility in SCADA", @@ -897,7 +853,7 @@ "status": "Complete", "status_updates": "complete", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Chute 14041 Enable Issues", @@ -908,7 +864,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "VS-D, S01400 Pallet Chute Beacons - False Fullness", @@ -919,7 +875,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Semi-Auto Jam Beacons", @@ -930,7 +886,7 @@ "status": "Complete", "status_updates": "10/19 PE functionality diabled.. hardware removal to be scheduled TBD\nKK 11/4 - hareware was removed on 10/28", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "NC Dim Data", @@ -941,7 +897,7 @@ "status": "Complete", "status_updates": "10/15 - Matt Specht fix mid sort\n10/17 - COMPLETE", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Jam Reset NC End of Sorter", @@ -952,7 +908,7 @@ "status": "Complete", "status_updates": "duplicate", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "PS11_11_JR3", @@ -963,7 +919,7 @@ "status": "Complete", "status_updates": "10/17 - in process\n10/18 KK - Done", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "\"Destination Not Attempted\" Reason Code", @@ -974,7 +930,7 @@ "status": "Complete", "status_updates": "10/17 -- follow up\nKK 10/21 - Based on container research data from Sherri, this reason code is no longer being sent in our S04", "issue_image": "image.png", - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "S013089 Blue Beacon", @@ -985,7 +941,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Done, beacon replaced", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "PS lines stop packages at the top of the line, on the first station. It should push the packages to the last station Jackpot probem solve", @@ -996,7 +952,7 @@ "status": "Complete", "status_updates": "10/17 fixed", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Still seeing Lost Container / No Reads in NC Jackpot", @@ -1007,7 +963,7 @@ "status": "Complete", "status_updates": "10/16- Reviewed logic/flow. Updated installed at noon. Will be monitoring\nKK 10/27 - Issue seems to be resolved", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "First chute orange lights keeps turning on without pakages blocking any sensors semi auto vs-d", @@ -1018,7 +974,7 @@ "status": "Complete", "status_updates": "Fixed, realigned PE", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Flashing and steady blue light on a chute without any package present S014020, S014018", @@ -1029,7 +985,7 @@ "status": "Complete", "status_updates": "Please ensure the cart is loaded centered in the chute, too far to the left or right, or skewed will result in the overhead sensor will read the frame of the cart and prematurely trigger full\n10/17 - fixed", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Flashing Green light on D2C , start button doesn't work S014041", @@ -1040,7 +996,7 @@ "status": "Complete", "status_updates": "Fixed - bad button, has been replaced", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "non- con packages still has divert issues packages going straight to jackpot", @@ -1051,7 +1007,7 @@ "status": "Complete", "status_updates": "Temporary buttons in placed at the end of each sorter, brackets pending", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Scada Map not showing cameras", @@ -1062,7 +1018,7 @@ "status": "Complete", "status_updates": "SCADA is showing cameras, the live feeds don't work at the moment, pending AMZ response for configuration (I contacted Davo and JC from ops eng for this)\n10/17 fixed", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Scada Map: showing stopped but actually running C", @@ -1073,7 +1029,7 @@ "status": "Complete", "status_updates": "10/17 - need more info\nKK 10/18 - Need more info\nKK 10/21 - No clarification received\nKK 10/27 - No clarification received, closing this item", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "PS lines stop packages at the top of the line, on the first station. It should push the packages to the last station.", @@ -1084,7 +1040,7 @@ "status": "Complete", "status_updates": "Fixed - corrected flow control on problem solve rollers", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Still seeing Lost Container / No Reads in NC Jackpot as of 1044", @@ -1095,7 +1051,7 @@ "status": "Complete", "status_updates": "Flow/assignment issue - to be addressed by Sherri (AMZ) and MFO today 11/1\n10/17 monitor\nKK 10/27 - Issue seems to be resolved", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "206 - 207 max reach giving us false signals its green on skada but not operational", @@ -1106,7 +1062,7 @@ "status": "Complete", "status_updates": "Need further clarification 10/19 - completed\n10/19 KK - Done", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "NC boxes are diverting to xbelt causing jams particullary at bypass curves", @@ -1150,7 +1106,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Need further clarification\nKK 10/21 - No clarification received - closed as duplicate (Row 120)", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Label auto inducts", @@ -1161,7 +1117,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "False blue lights", @@ -1172,7 +1128,7 @@ "status": "Complete", "status_updates": "duplicate", "issue_image": null, - "age_days": 11 + "age_days": 12 }, { "punchlist_name": "S012075 unavailable", @@ -1183,7 +1139,7 @@ "status": "Complete", "status_updates": "KK 10/19 - Done", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "VSD- S014020 BLINKING BLUE WHILE CHUTE IS EMPTY", @@ -1194,7 +1150,7 @@ "status": "Complete", "status_updates": "IH 10/18 - Confirmed functionality of the chute.", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "VSD- S014018 SOLID BLUE  WHILE CHUTE IS EMPTY", @@ -1205,7 +1161,7 @@ "status": "Complete", "status_updates": "IH 10/18 - Confirmed functionality of the chute.", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "D2C S014041 flashing green + activation button not illuminating", @@ -1216,7 +1172,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Problem Solve rollers do not correctly inch-and-store packages, causing line to jam prematurely", @@ -1227,7 +1183,7 @@ "status": "Complete", "status_updates": "KK 10/17 - PRS photoeyes have been re-aligned and tightened", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "SCADA: We need some type of refrence in SCADA. Suggest adding dock doors that correlate with their belt", @@ -1238,7 +1194,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "SCADA: Need a way to click onto jammed area on main page to see where the jam is located and how to get there", @@ -1249,7 +1205,7 @@ "status": "Complete", "status_updates": "10/17 - That exists. Sherri showed several team members an dRME how to do this", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Run-up is not enabled on Problem solve collection belts. They all stop when the photoeye at the rollers is blocked.", @@ -1260,7 +1216,7 @@ "status": "Complete", "status_updates": "KK 10/17 - PRS photoeyes have been re-aligned and tightened", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Potential PE issue at the problem solve rollers right when the chutes level off. 300 side.", @@ -1271,7 +1227,7 @@ "status": "Complete", "status_updates": "KK 10/17 - PRS photoeyes have been re-aligned and tightened", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "SCADA: scada camera view from main map wont allow user to pan camera angles when clicked onto", @@ -1282,7 +1238,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Adjusting the camera angle is not possible from SCADA. Can be done by accessing the camera directly with its IP address.", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "PS9-3CH2 - located by semi induct C. Blue light actived with no packages", @@ -1293,7 +1249,7 @@ "status": "Complete", "status_updates": "MC: Blue Light was NOT on when investigated, still went ahead to realign better PE/teach again (10/18/25)", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "semi induct c chute 2 Flase blue light PE needs adjusted", @@ -1304,7 +1260,7 @@ "status": "Complete", "status_updates": "MC: Blue Light was NOT on when investigated, still went ahead to realign better PE/teach again (10/18/25)", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Non con diverts going down - becon alluminating but belt still running", @@ -1315,7 +1271,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Need clarification\nKK 10/19 - Closed", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "IBNC splitting sending packages to NC that are within the crossbelt MTBH", @@ -1326,7 +1282,7 @@ "status": "Complete", "status_updates": "KK 10/16 - Splitter logic adjusted", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "D2C Chutes are full blue lighting 8 inches short of the go cart full line", @@ -1337,7 +1293,7 @@ "status": "Complete", "status_updates": "KK 10/16 - Raised ALL D2C Full height 6-8\" above last setting", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "chute stays green even when chute is full", @@ -1348,7 +1304,7 @@ "status": "Complete", "status_updates": "IH 10/18 Resolved. IO Mapping issue.", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Non- con packages coming down to lanes", @@ -1359,7 +1315,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Noncon detection length reduced to 41\"", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Scada - label each line ex. UL13 , UL14 ect.", @@ -1370,7 +1326,7 @@ "status": "Complete", "status_updates": "IH 10/19 SCADA Updated", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "PS11-11CH6NC Intralox Sorter (S02)", @@ -1381,7 +1337,7 @@ "status": "Complete", "status_updates": "KK 10/27 - Need clarification. I don't see the relation between PS11-11 and the intralox sorter.", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Blue light for semi induct D is on when chute is empty", @@ -1392,7 +1348,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Realigned and trained photoeye", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Need 1 button to turn on ALL inducts. I am having to turn them on one by one", @@ -1403,7 +1359,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Our global start button is confirmed to start all autStand conveyors and Beumer CMC/SMC conveyors, only if they are not faulted.", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Non-Con running through main sorter causing system to shutdown; Non-con packages not diverting to Non-cons sorter taking up carriers on Sorter", @@ -1414,7 +1370,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Noncon detection length reduced to 41\"", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "DTW8/CLE5 Non-Con packages not diverting to chutes; running straight through jackpot", @@ -1425,7 +1381,7 @@ "status": "Complete", "status_updates": "KK 10/18 - MFO issue\nKK 10/19 - Not MFO, need to deep dive tracking/diverting/etc\nKK 10/20 - Identified potential issue with a photoeye missing incoming packages at scan tunnel induct, which manifest as unknown/unexpected packages and causing some packages to go off-the-end. We adjusted this sensor and are monitoring. Initial observations seem promising\nKK 10/27 - Resolved", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "SEMI-D uneven flow of packages to each chute", @@ -1436,7 +1392,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Logic continually being optmized (monitor)\nKK 10/27 - This is a duplicate item related to bulk induct shutes (row 6), I will close it", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "Clean up SCADA Active Jams to not show false jams ex BYBA-15 /BYBD-14", @@ -1447,7 +1403,7 @@ "status": "Complete", "status_updates": "KK 10/21 - Aware of a couple \"stuck\" alarms - still investigating why RS 10/30 - This issue seems to be fixed", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "Disable jam pe's that are to be removed (bulk chutes)", @@ -1458,7 +1414,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Disabled, pending physical removal", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "Evaluate jam logic /die back on bypasses.", @@ -1469,7 +1425,7 @@ "status": "Complete", "status_updates": "KK 10/19 - Verified that it is not being caused by runup logic, need to continue to monitor\nKK 10/27 - False jams seemed to have stopped being as frequent. There is still no runup logic causing this.", "issue_image": null, - "age_days": 17 + "age_days": 18 }, { "punchlist_name": "Non Con flow issues -- missorts to S02207", @@ -1480,7 +1436,7 @@ "status": "Complete", "status_updates": "KK 10/27 - Resolved", "issue_image": null, - "age_days": 14 + "age_days": 15 }, { "punchlist_name": "Encoder failure (4x) + 2 x", @@ -1491,7 +1447,7 @@ "status": "Complete", "status_updates": "10/23 ST soluton is changing the port... LT reseach with Rockwell 10/29 - Rockwell to send engineer to test system 10/30 ; 10/30 RC - Rockwell visit not happening today. TBD. 10/30 - looking to send \"capable engineer\" to address engineer. today another encoder failed UL18-10 (all merge stop - 20 min swapping) 11/3- Rockwell scheduled to be on site 11/6.", "issue_image": null, - "age_days": 26 + "age_days": 27 }, { "punchlist_name": "SCADA performance issue", @@ -1524,7 +1480,7 @@ "status": "Complete", "status_updates": "working on it today. /tomorrow\nKK 11/4 - 0 gap errors since logic update, closing this item", "issue_image": null, - "age_days": 6 + "age_days": 7 } ], "monitor_items": [ @@ -1537,7 +1493,7 @@ "status": "Monitor", "status_updates": "10/16 - fix applied monitor as VS become active", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Logic for Semi induct D is off very low throughput see video", @@ -1548,7 +1504,7 @@ "status": "Monitor", "status_updates": "KK 10/18 - Tweaking logic, continually adding optmizations", "issue_image": "see video in comments", - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "One major issue and one minor issue with the non-con system:\nNo-reads are really frequent \nThe PLC is not reporting S04 message divert fails properly (no reads to jackpot, lost container, failed to divert, wrong buildings)", @@ -1559,7 +1515,7 @@ "status": "Monitor", "status_updates": "KK 10/18 - For the whole sort yesterday we had 13 No Reads out of 500 inducted (2.6%) we will see what can be done to reduce this.\nIH 10/19 Datalogic & PLC update conducted. Will continue to monitor.\nKK 11/30 - Fixed the \"Unknown\" S04 message, verified with AWCS data", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "DTC chutes on VS-B is randomly disabling", @@ -1570,7 +1526,7 @@ "status": "Monitor", "status_updates": "10/30 RC - Is this still an issue? Chute code was updated to add a debounce period for registering a cart removal. This should mitigate any false removals caused by jiffies triggering the sensor while falling into the cart.", "issue_image": null, - "age_days": 14 + "age_days": 15 } ], "open_items": [ @@ -1594,7 +1550,7 @@ "status": "Incomplete", "status_updates": null, "issue_image": null, - "age_days": 1 + "age_days": 2 }, { "punchlist_name": "Raise the fill height ob the DTC's approx 2 \"", @@ -1605,9 +1561,45 @@ "status": "Incomplete", "status_updates": null, "issue_image": null, - "age_days": 1 + "age_days": 2 } - ] + ], + "incomplete_items": [ + { + "punchlist_name": "3:1 merge code update", + "description": "mcm02 by monday 11/4. mcm01 ul 1-3 done. mcm01 complete.", + "priority": "(2) High", + "date_identified": null, + "date_completed": null, + "status": "Incomplete", + "status_updates": "11/3 - no update... Igor at BNA8", + "issue_image": null, + "age_days": null + }, + { + "punchlist_name": "Estops are getting damaged on the UL lane", + "description": "UL16-1, UL15-3, UL10-2 (both sides) UL8-1 , UL7-3, UL6-1 protect or relocate devices", + "priority": "(2) High", + "date_identified": "2025-11-04 00:00:00", + "date_completed": null, + "status": "Incomplete", + "status_updates": null, + "issue_image": null, + "age_days": 2 + }, + { + "punchlist_name": "Raise the fill height ob the DTC's approx 2 \"", + "description": null, + "priority": "(2) High", + "date_identified": "2025-11-04 00:00:00", + "date_completed": null, + "status": "Incomplete", + "status_updates": null, + "issue_image": null, + "age_days": 2 + } + ], + "incomplete_count": 3 }, { "vendor_name": "Autstand/Beumer", @@ -1633,7 +1625,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Need further clarification\nKK 10/19 - Done", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "pe missing prob solve ak chute", @@ -1644,7 +1636,7 @@ "status": "Complete", "status_updates": "Completed 10/29", "issue_image": null, - "age_days": 7 + "age_days": 8 } ], "monitor_items": [ @@ -1657,10 +1649,12 @@ "status": "Monitor", "status_updates": "10/30 RC - AutStand removed rate management logic that caused Conveyor Not Ready status in SCADA. AutStand to monitor whether bypass jams increase. Beumer to adjust to-bypass discharge rate if necessary.", "issue_image": null, - "age_days": 14 + "age_days": 15 } ], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "Autstand/DCS", @@ -1709,7 +1703,7 @@ "status": "Complete", "status_updates": "KK 10/17 - PRS photoeyes have been re-aligned and tightened", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Non-con packages with plastic securement strips are not to be ran on sorter. Packages with plastic securement strips gets caught in sorter causing it to shut down/ jam.", @@ -1720,11 +1714,13 @@ "status": "Complete", "status_updates": "KK 10/18 - Our system can not tell if a package has plastic securement strips", "issue_image": null, - "age_days": 18 + "age_days": 19 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "Beumer", @@ -1760,7 +1756,7 @@ "status": "Monitor", "status_updates": "10/14 - Bryan to update code tomorrow\n10/15 - Bypasses had to be manually turned on when crossbelt stopped\n10/16 - Fix applied, monitor", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Inductions are going directly on bellowsco", @@ -1771,7 +1767,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 14 + "age_days": 15 }, { "punchlist_name": "Carrier disabled to shut down the sorter", @@ -1782,7 +1778,7 @@ "status": "Complete", "status_updates": "30% disable threshold... confirm reenable", "issue_image": null, - "age_days": 14 + "age_days": 15 }, { "punchlist_name": "Missorts", @@ -1793,7 +1789,7 @@ "status": "Complete", "status_updates": "11/3 -- Missorts issue with chute trigger settings identified. All chutes evaluated and fixed on 10/30 will evaluate improvements with next Ops report", "issue_image": null, - "age_days": 16 + "age_days": 17 }, { "punchlist_name": "Packages are being inducted on occupied trays", @@ -1804,7 +1800,7 @@ "status": "Complete", "status_updates": "10/23 - Beumer updated PLC to prevent issue but did nto work. Additional logs were addeed to trace root cause of issue (10/24 @730am)", "issue_image": null, - "age_days": 14 + "age_days": 15 }, { "punchlist_name": "Lane Not Available Metric too high", @@ -1828,7 +1824,7 @@ "status": "Complete", "status_updates": "10/14 - Beumer to start painting after sort tonight at VS-D\nVS B C A complete... VS-d ECD 10/17 night\n\n10/18 confirm if compete", "issue_image": null, - "age_days": 30 + "age_days": 31 }, { "punchlist_name": "Auto-Induct Restart", @@ -1839,7 +1835,7 @@ "status": "Complete", "status_updates": "10/17 -- related to the global start? montor\nconfirmed that it works", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "False Jam ( Rest every 6 mins)", @@ -1850,7 +1846,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Reassigned to Beumer", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Induct 5B (Bypass induct C->) rejecting packages within MTBH", @@ -1861,7 +1857,7 @@ "status": "Monitor", "status_updates": "10/18 - will verify Will check calibration, waiting on tool", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Auto induct - D back fireing packages that can go - also demissionier is off", @@ -1872,7 +1868,7 @@ "status": "Complete", "status_updates": "10/18 will investigate", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Packages are being directed to the wrong end of the chutes, AA had to take jiffy and scan to D2C", @@ -1883,7 +1879,7 @@ "status": "Complete", "status_updates": "10/18 will investigate", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "BUEMER scada - bypass is showing "die back" in yellow and futher up the belt is showing green. There is a disconnect. Packages arent able to move through in "die back" mode but the belt after is showing green, which looks to be good but isnt", @@ -1894,7 +1890,7 @@ "status": "Complete", "status_updates": "10/18 - will investigate", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "false jam", @@ -1905,7 +1901,7 @@ "status": "Complete", "status_updates": "KK 10/19 - Reassigned to beumer, they control crossbelt chute jams", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "DBS sensors - heavier packages meeting weight requirement for main sorter triggering senors; causing sorter to shutdown", @@ -1916,7 +1912,7 @@ "status": "Complete", "status_updates": "10/18 - need update for ops on how DBS works", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "jiffies getting caught in belows causing sorter to shutdown", @@ -1927,7 +1923,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "At inducts evaluate why \"conveyable\" boxes are being rejected at induction", @@ -1938,7 +1934,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 17 + "age_days": 18 }, { "punchlist_name": "Estop not reporting on BGFusion/Scada", @@ -1949,7 +1945,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 15 + "age_days": 16 } ], "closed_items": [ @@ -1962,7 +1958,7 @@ "status": "Complete", "status_updates": "10/14 - Beumer to start painting after sort tonight at VS-D\nVS B C A complete... VS-d ECD 10/17 night\n\n10/18 confirm if compete", "issue_image": null, - "age_days": 30 + "age_days": 31 }, { "punchlist_name": "VS-D Auto-Induct Jams/Faults", @@ -1973,7 +1969,7 @@ "status": "Complete", "status_updates": "10/14 - Bryan is working on similar issue at SAT9, Deepak from Amazon is helping, Jody will send people to investigate", "issue_image": "fixed finger gaurds", - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Throughput Limit on SCADA", @@ -1984,7 +1980,7 @@ "status": "Complete", "status_updates": "10/17 was used for testing is now disabled", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "SCADA Accurate Status Reads", @@ -1995,7 +1991,7 @@ "status": "Complete", "status_updates": "10/14 - Autstand now has all of the program info from Beumer to connect statuses\n10/17 resolved", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Auto-Induct Restart", @@ -2006,7 +2002,7 @@ "status": "Complete", "status_updates": "10/17 -- related to the global start? montor\nconfirmed that it works", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Operations requested the Problem Solve chutes from A and C to be disabled since not staffed", @@ -2028,7 +2024,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Reassigned to Beumer", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "BYPASS ATOC Auto induct reoccuring flase Jam", @@ -2039,7 +2035,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Reassigned to Beumer", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Allocations are correct - wrong packages coming down", @@ -2050,7 +2046,7 @@ "status": "Complete", "status_updates": "10/18 will investigate", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Auto induct - D back fireing packages that can go - also demissionier is off", @@ -2061,7 +2057,7 @@ "status": "Complete", "status_updates": "10/18 will investigate", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Packages are being directed to the wrong end of the chutes, AA had to take jiffy and scan to D2C", @@ -2072,7 +2068,7 @@ "status": "Complete", "status_updates": "10/18 will investigate", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "BUEMER scada - bypass is showing "die back" in yellow and futher up the belt is showing green. There is a disconnect. Packages arent able to move through in "die back" mode but the belt after is showing green, which looks to be good but isnt", @@ -2083,7 +2079,7 @@ "status": "Complete", "status_updates": "10/18 - will investigate", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "false jam", @@ -2094,7 +2090,7 @@ "status": "Complete", "status_updates": "KK 10/19 - Reassigned to beumer, they control crossbelt chute jams", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "DBS sensors - heavier packages meeting weight requirement for main sorter triggering senors; causing sorter to shutdown", @@ -2105,7 +2101,7 @@ "status": "Complete", "status_updates": "10/18 - need update for ops on how DBS works", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "jiffies getting caught in belows causing sorter to shutdown", @@ -2116,7 +2112,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "Provide Bellow clearing procedures", @@ -2127,7 +2123,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Air Knife over shooting takeaway conveyor", @@ -2138,7 +2134,7 @@ "status": "Complete", "status_updates": "10/23 - Airknife out of alignment. Beumer adjusted but requested Silman to return to site to recalibrate", "issue_image": null, - "age_days": 17 + "age_days": 18 }, { "punchlist_name": "At inducts evaluate why \"conveyable\" boxes are being rejected at induction", @@ -2149,7 +2145,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 17 + "age_days": 18 }, { "punchlist_name": "Deep dive missorts (file shared)", @@ -2160,7 +2156,7 @@ "status": "Complete", "status_updates": "Incomplete", "issue_image": null, - "age_days": 14 + "age_days": 15 }, { "punchlist_name": "Aligner catch point on IND2C-s", @@ -2171,7 +2167,7 @@ "status": "Complete", "status_updates": "Incomplete", "issue_image": null, - "age_days": 14 + "age_days": 15 }, { "punchlist_name": "Issue with needing to reset all inducts (fault). when system starts. Need to automatically reset", @@ -2182,7 +2178,7 @@ "status": "Complete", "status_updates": "Incomplete", "issue_image": null, - "age_days": 14 + "age_days": 15 }, { "punchlist_name": "Inductions are going directly on bellowsco", @@ -2193,7 +2189,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 14 + "age_days": 15 }, { "punchlist_name": "Carrier disabled to shut down the sorter", @@ -2204,7 +2200,7 @@ "status": "Complete", "status_updates": "30% disable threshold... confirm reenable", "issue_image": null, - "age_days": 14 + "age_days": 15 }, { "punchlist_name": "Missorts", @@ -2215,7 +2211,7 @@ "status": "Complete", "status_updates": "11/3 -- Missorts issue with chute trigger settings identified. All chutes evaluated and fixed on 10/30 will evaluate improvements with next Ops report", "issue_image": null, - "age_days": 16 + "age_days": 17 }, { "punchlist_name": "Estop not reporting on BGFusion/Scada", @@ -2226,7 +2222,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 15 + "age_days": 16 }, { "punchlist_name": "Packages are being inducted on occupied trays", @@ -2237,7 +2233,7 @@ "status": "Complete", "status_updates": "10/23 - Beumer updated PLC to prevent issue but did nto work. Additional logs were addeed to trace root cause of issue (10/24 @730am)", "issue_image": null, - "age_days": 14 + "age_days": 15 } ], "monitor_items": [ @@ -2250,7 +2246,7 @@ "status": "Monitor", "status_updates": "10/14 - Bryan to update code tomorrow\n10/15 - Bypasses had to be manually turned on when crossbelt stopped\n10/16 - Fix applied, monitor", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Diverting onto Bellows from Bypass, adjust carrier aim", @@ -2261,7 +2257,7 @@ "status": "Monitor", "status_updates": "10/14 - Beumer is going to work on re-aiming some of these auto-inducts, retrain ops on induction angles", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Divert Points to Pallet Chutes", @@ -2272,7 +2268,7 @@ "status": "Monitor", "status_updates": "10/14 - Check the chutes that have missorts and jams at the top\n10/17 - chutes checked and made adjustments Monitor", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "IND2A-22 Carrier Induct Timing", @@ -2283,7 +2279,7 @@ "status": "Monitor", "status_updates": "10/17 - beumer to monitor", "issue_image": "IMG_4519.jpg", - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Induct 5B (Bypass induct C->) rejecting packages within MTBH", @@ -2294,7 +2290,7 @@ "status": "Monitor", "status_updates": "10/18 - will verify Will check calibration, waiting on tool", "issue_image": null, - "age_days": 20 + "age_days": 21 } ], "open_items": [ @@ -2309,7 +2305,21 @@ "issue_image": null, "age_days": null } - ] + ], + "incomplete_items": [ + { + "punchlist_name": "Lane Not Available Metric too high", + "description": null, + "priority": "(1) Very Hgh", + "date_identified": null, + "date_completed": null, + "status": "Incomplete", + "status_updates": "11/3 - Working with Autstand conveyor ready flag as disabled ... will be addressed by full lane signal (fluidload only). Still need to do similar fix on bypass but need to evaluate when to trigger full lane (vs lane not available0", + "issue_image": null, + "age_days": null + } + ], + "incomplete_count": 1 }, { "vendor_name": "Caljan", @@ -2334,7 +2344,7 @@ "status": "Complete", "status_updates": "Autstand jumped out the photoeyes to get them operational\n10/17 SS contacted Caljan... waiting on date for tech", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Maxx Reach at DD 332 not working\nhttps://t.corp.amazon.com/V1969041198", @@ -2345,7 +2355,7 @@ "status": "Complete", "status_updates": "10/17 Sherri contacted Caljan and arranged for RME to have remote tech support", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Max reach 119  PE at top of max reach needs to be adjusted  , also having to reset line at times for it to move", @@ -2356,7 +2366,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Reassigned to Caljan", "issue_image": null, - "age_days": 20 + "age_days": 21 } ], "closed_items": [ @@ -2369,7 +2379,7 @@ "status": "Complete", "status_updates": "Autstand jumped out the photoeyes to get them operational\n10/17 SS contacted Caljan... waiting on date for tech", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Maxx Reach at DD 332 not working\nhttps://t.corp.amazon.com/V1969041198", @@ -2380,7 +2390,7 @@ "status": "Complete", "status_updates": "10/17 Sherri contacted Caljan and arranged for RME to have remote tech support", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Max Reach 100% PE logic is backwards All 300 and 100 DD", @@ -2391,7 +2401,7 @@ "status": "Complete", "status_updates": "KK 10/18 - Reassigned to Caljan", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Max reach 119  PE at top of max reach needs to be adjusted  , also having to reset line at times for it to move", @@ -2402,11 +2412,13 @@ "status": "Complete", "status_updates": "KK 10/18 - Reassigned to Caljan", "issue_image": null, - "age_days": 20 + "age_days": 21 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "DCS", @@ -2415,41 +2427,7 @@ "open_count": 4, "monitor_count": 1, "updates_24h": { - "added": [ - { - "punchlist_name": ") There is a catchpoint of bent metal that is sticking out from the tail assembly on PS10-1 where it transitions to PS11-1. This is catching polys during operation. Jesse is going to look into making proper modifications to eliminate this.", - "description": null, - "priority": "(2) High", - "date_identified": "2025-11-04 00:00:00", - "date_completed": null, - "status": "Incomplete", - "status_updates": "11/4 - Wes Matthews reviewed with Jesse", - "issue_image": null, - "age_days": 1 - }, - { - "punchlist_name": "2) When product from PS10-1 is flowing towards PS11-1, there is no snowplow and instead the slide just dead ends with a corner of sidepan. I’ve asked Jesse to look into fabricating a UHMW piece that could bridge this corner to push products down onto the belt.", - "description": null, - "priority": "(2) High", - "date_identified": "2025-11-04 00:00:00", - "date_completed": null, - "status": "Incomplete", - "status_updates": "11/4 Wes Matthews reviewed with Jesse", - "issue_image": null, - "age_days": 1 - }, - { - "punchlist_name": "3) The black UHMW strip under the belt which transitions the belt from slider bed to tail roller is too sharp and is shaving the bottom side of the belt. Jesse and his team are going to look into pulling this uhmw strip out, properly chamfering it and then re-installing.", - "description": null, - "priority": "(2) High", - "date_identified": "2025-11-04 00:00:00", - "date_completed": null, - "status": "Incomplete", - "status_updates": "11/4 Wes Matthews reviewed with Jesse", - "issue_image": null, - "age_days": 1 - } - ], + "added": [], "closed": [], "changed_to_monitor": [] }, @@ -2463,7 +2441,7 @@ "status": "Incomplete", "status_updates": "11/3 - still waiting on belt to be delivered", "issue_image": null, - "age_days": 4 + "age_days": 5 }, { "punchlist_name": ") There is a catchpoint of bent metal that is sticking out from the tail assembly on PS10-1 where it transitions to PS11-1. This is catching polys during operation. Jesse is going to look into making proper modifications to eliminate this.", @@ -2474,7 +2452,7 @@ "status": "Incomplete", "status_updates": "11/4 - Wes Matthews reviewed with Jesse", "issue_image": null, - "age_days": 1 + "age_days": 2 }, { "punchlist_name": "2) When product from PS10-1 is flowing towards PS11-1, there is no snowplow and instead the slide just dead ends with a corner of sidepan. I’ve asked Jesse to look into fabricating a UHMW piece that could bridge this corner to push products down onto the belt.", @@ -2485,7 +2463,7 @@ "status": "Incomplete", "status_updates": "11/4 Wes Matthews reviewed with Jesse", "issue_image": null, - "age_days": 1 + "age_days": 2 } ], "very_high_priority_items": [ @@ -2498,7 +2476,7 @@ "status": "Complete", "status_updates": "10/29 final belt replacement scheduled for midnite tonight 11/3 - all belts have been replaced", "issue_image": null, - "age_days": 26 + "age_days": 27 }, { "punchlist_name": "UL7-17 Belt needs to be replaced (again) RME installation (ripped edges, lacing)", @@ -2509,7 +2487,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "UL21-24 Failed (belts and shaft)", @@ -2520,7 +2498,7 @@ "status": "Complete", "status_updates": "10/21/25 - repair completed", "issue_image": null, - "age_days": 16 + "age_days": 17 }, { "punchlist_name": "NCS1-1 aligner belt failed", @@ -2531,7 +2509,7 @@ "status": "Incomplete", "status_updates": "11/3 - still waiting on belt to be delivered", "issue_image": null, - "age_days": 4 + "age_days": 5 } ], "high_priority_items": [ @@ -2544,7 +2522,7 @@ "status": "Monitor", "status_updates": "10/17 -- investigated... no issues identified monitor\n11/3: Nothing Mechanical we can do to frim up the side guard it is stanless steel", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Semi-Auto Phantom Jams", @@ -2555,7 +2533,7 @@ "status": "Complete", "status_updates": "10/17 - DCS surveyed... on going.\n10/18 Belts being cauterized to eliminate the flaying . Will update with estimated completion date 10/19 -- in process\n10/22: Inbound Completed; Working on Bypass and PS conveyance", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "NC End of Sorter Chute", @@ -2566,7 +2544,7 @@ "status": "Complete", "status_updates": "10/17 --- Changed the PE location.. to trigger the MDR. monitor may need to add UHMW", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Catch point at DD118 fluid load", @@ -2599,7 +2577,7 @@ "status": "Complete", "status_updates": "No Oil Present at Chute (Fire Suppression Pipe is Leaking)", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "oil leak in NON CON near chute QAA-3CH", @@ -2610,7 +2588,7 @@ "status": "Complete", "status_updates": "No Oil Present at Chute", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "packages being re inducted into ps bottom belt - hits the belt - needs to be raised", @@ -2621,7 +2599,7 @@ "status": "Complete", "status_updates": "Cannot raise belt", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Divert arms are to high and are causing jiffys to get stuck under the arm when diverting", @@ -2632,7 +2610,7 @@ "status": "Complete", "status_updates": "10/18 - pending", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "PS chute lip is catching ps boxes", @@ -2643,7 +2621,7 @@ "status": "Complete", "status_updates": "Pending", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Maint jack for tippers", @@ -2654,7 +2632,7 @@ "status": "Complete", "status_updates": "10/17 - Confirmed that jack was not received/onsite here or MTN2. Asked DCS to confirm if ordered with the tippers.\n10/22: PO Pending for 1 Jack \n10/23: ETA Shipping Date of Tipper Jack is 10/31/2025", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Non Con Chute/Maint access. Need Latch upgrade", @@ -2665,7 +2643,7 @@ "status": "Complete", "status_updates": "10.29 SSI will be on site to evaluate /repair 10/30. 11/3 - SSI completed their evaluation, need to replace/rebuild over 40 latches", "issue_image": null, - "age_days": 26 + "age_days": 27 }, { "punchlist_name": "Motor falling on HSQ gappers.. 2x (3:1 merge)", @@ -2687,7 +2665,7 @@ "status": "Complete", "status_updates": "Pending Ship Date", "issue_image": null, - "age_days": 10 + "age_days": 11 }, { "punchlist_name": ") There is a catchpoint of bent metal that is sticking out from the tail assembly on PS10-1 where it transitions to PS11-1. This is catching polys during operation. Jesse is going to look into making proper modifications to eliminate this.", @@ -2698,7 +2676,7 @@ "status": "Incomplete", "status_updates": "11/4 - Wes Matthews reviewed with Jesse", "issue_image": null, - "age_days": 1 + "age_days": 2 }, { "punchlist_name": "2) When product from PS10-1 is flowing towards PS11-1, there is no snowplow and instead the slide just dead ends with a corner of sidepan. I’ve asked Jesse to look into fabricating a UHMW piece that could bridge this corner to push products down onto the belt.", @@ -2709,7 +2687,7 @@ "status": "Incomplete", "status_updates": "11/4 Wes Matthews reviewed with Jesse", "issue_image": null, - "age_days": 1 + "age_days": 2 }, { "punchlist_name": "3) The black UHMW strip under the belt which transitions the belt from slider bed to tail roller is too sharp and is shaving the bottom side of the belt. Jesse and his team are going to look into pulling this uhmw strip out, properly chamfering it and then re-installing.", @@ -2720,7 +2698,7 @@ "status": "Incomplete", "status_updates": "11/4 Wes Matthews reviewed with Jesse", "issue_image": null, - "age_days": 1 + "age_days": 2 } ], "closed_items": [ @@ -2733,7 +2711,7 @@ "status": "Complete", "status_updates": "10/29 final belt replacement scheduled for midnite tonight 11/3 - all belts have been replaced", "issue_image": null, - "age_days": 26 + "age_days": 27 }, { "punchlist_name": "Semi-Auto Phantom Jams", @@ -2744,7 +2722,7 @@ "status": "Complete", "status_updates": "10/17 - DCS surveyed... on going.\n10/18 Belts being cauterized to eliminate the flaying . Will update with estimated completion date 10/19 -- in process\n10/22: Inbound Completed; Working on Bypass and PS conveyance", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "NC End of Sorter Chute", @@ -2755,7 +2733,7 @@ "status": "Complete", "status_updates": "10/17 --- Changed the PE location.. to trigger the MDR. monitor may need to add UHMW", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "NC Sorter Aligner", @@ -2766,7 +2744,7 @@ "status": "Complete", "status_updates": "10/17 - fixed.", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Catch point at DD118 fluid load", @@ -2810,7 +2788,7 @@ "status": "Complete", "status_updates": "No Oil Present at Chute (Fire Suppression Pipe is Leaking)", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "oil leak in NON CON near chute QAA-3CH", @@ -2821,7 +2799,7 @@ "status": "Complete", "status_updates": "No Oil Present at Chute", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "packages being re inducted into ps bottom belt - hits the belt - needs to be raised", @@ -2832,7 +2810,7 @@ "status": "Complete", "status_updates": "Cannot raise belt", "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Divert arms are to high and are causing jiffys to get stuck under the arm when diverting", @@ -2843,7 +2821,7 @@ "status": "Complete", "status_updates": "10/18 - pending", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "PS chute lip is catching ps boxes", @@ -2854,7 +2832,7 @@ "status": "Complete", "status_updates": "Pending", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "Maint jack for tippers", @@ -2865,7 +2843,7 @@ "status": "Complete", "status_updates": "10/17 - Confirmed that jack was not received/onsite here or MTN2. Asked DCS to confirm if ordered with the tippers.\n10/22: PO Pending for 1 Jack \n10/23: ETA Shipping Date of Tipper Jack is 10/31/2025", "issue_image": null, - "age_days": 19 + "age_days": 20 }, { "punchlist_name": "UL4-15 damaged belt", @@ -2887,7 +2865,7 @@ "status": "Complete", "status_updates": "F14041", "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "UL7-17 Belt needs to be replaced (again) RME installation (ripped edges, lacing)", @@ -2898,7 +2876,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 18 + "age_days": 19 }, { "punchlist_name": "Non Con Chute/Maint access. Need Latch upgrade", @@ -2909,7 +2887,7 @@ "status": "Complete", "status_updates": "10.29 SSI will be on site to evaluate /repair 10/30. 11/3 - SSI completed their evaluation, need to replace/rebuild over 40 latches", "issue_image": null, - "age_days": 26 + "age_days": 27 }, { "punchlist_name": "UL21-24 Failed (belts and shaft)", @@ -2920,7 +2898,7 @@ "status": "Complete", "status_updates": "10/21/25 - repair completed", "issue_image": null, - "age_days": 16 + "age_days": 17 }, { "punchlist_name": "Motor falling on HSQ gappers.. 2x (3:1 merge)", @@ -2942,7 +2920,7 @@ "status": "Complete", "status_updates": "Pending Ship Date", "issue_image": null, - "age_days": 10 + "age_days": 11 } ], "monitor_items": [ @@ -2955,7 +2933,7 @@ "status": "Monitor", "status_updates": "10/17 -- investigated... no issues identified monitor\n11/3: Nothing Mechanical we can do to frim up the side guard it is stanless steel", "issue_image": null, - "age_days": 22 + "age_days": 23 } ], "open_items": [ @@ -2968,7 +2946,7 @@ "status": "Incomplete", "status_updates": "11/3 - still waiting on belt to be delivered", "issue_image": null, - "age_days": 4 + "age_days": 5 }, { "punchlist_name": ") There is a catchpoint of bent metal that is sticking out from the tail assembly on PS10-1 where it transitions to PS11-1. This is catching polys during operation. Jesse is going to look into making proper modifications to eliminate this.", @@ -2979,7 +2957,7 @@ "status": "Incomplete", "status_updates": "11/4 - Wes Matthews reviewed with Jesse", "issue_image": null, - "age_days": 1 + "age_days": 2 }, { "punchlist_name": "2) When product from PS10-1 is flowing towards PS11-1, there is no snowplow and instead the slide just dead ends with a corner of sidepan. I’ve asked Jesse to look into fabricating a UHMW piece that could bridge this corner to push products down onto the belt.", @@ -2990,7 +2968,7 @@ "status": "Incomplete", "status_updates": "11/4 Wes Matthews reviewed with Jesse", "issue_image": null, - "age_days": 1 + "age_days": 2 }, { "punchlist_name": "3) The black UHMW strip under the belt which transitions the belt from slider bed to tail roller is too sharp and is shaving the bottom side of the belt. Jesse and his team are going to look into pulling this uhmw strip out, properly chamfering it and then re-installing.", @@ -3001,9 +2979,56 @@ "status": "Incomplete", "status_updates": "11/4 Wes Matthews reviewed with Jesse", "issue_image": null, - "age_days": 1 + "age_days": 2 } - ] + ], + "incomplete_items": [ + { + "punchlist_name": "NCS1-1 aligner belt failed", + "description": "Belt failed prior to flow splitter. Replaced with belt from noncon 2 (using only non con 1) until belt is delivered.", + "priority": "(1) Very High", + "date_identified": "2025-11-01 00:00:00", + "date_completed": null, + "status": "Incomplete", + "status_updates": "11/3 - still waiting on belt to be delivered", + "issue_image": null, + "age_days": 5 + }, + { + "punchlist_name": ") There is a catchpoint of bent metal that is sticking out from the tail assembly on PS10-1 where it transitions to PS11-1. This is catching polys during operation. Jesse is going to look into making proper modifications to eliminate this.", + "description": null, + "priority": "(2) High", + "date_identified": "2025-11-04 00:00:00", + "date_completed": null, + "status": "Incomplete", + "status_updates": "11/4 - Wes Matthews reviewed with Jesse", + "issue_image": null, + "age_days": 2 + }, + { + "punchlist_name": "2) When product from PS10-1 is flowing towards PS11-1, there is no snowplow and instead the slide just dead ends with a corner of sidepan. I’ve asked Jesse to look into fabricating a UHMW piece that could bridge this corner to push products down onto the belt.", + "description": null, + "priority": "(2) High", + "date_identified": "2025-11-04 00:00:00", + "date_completed": null, + "status": "Incomplete", + "status_updates": "11/4 Wes Matthews reviewed with Jesse", + "issue_image": null, + "age_days": 2 + }, + { + "punchlist_name": "3) The black UHMW strip under the belt which transitions the belt from slider bed to tail roller is too sharp and is shaving the bottom side of the belt. Jesse and his team are going to look into pulling this uhmw strip out, properly chamfering it and then re-installing.", + "description": null, + "priority": "(2) High", + "date_identified": "2025-11-04 00:00:00", + "date_completed": null, + "status": "Incomplete", + "status_updates": "11/4 Wes Matthews reviewed with Jesse", + "issue_image": null, + "age_days": 2 + } + ], + "incomplete_count": 4 }, { "vendor_name": "DCS/Autstand", @@ -3029,11 +3054,13 @@ "status": "Complete", "status_updates": "KK 10/21 - Investigated the unit, did not see anything unusual. Unit is working fine", "issue_image": null, - "age_days": 20 + "age_days": 21 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "DCS/Flow-Turn", @@ -3058,7 +3085,7 @@ "status": "Complete", "status_updates": "AM 10/19 - DCS repaired this merge and replaced the 2 belts. This needs to be monitored", "issue_image": null, - "age_days": 18 + "age_days": 19 } ], "closed_items": [ @@ -3071,11 +3098,13 @@ "status": "Complete", "status_updates": "AM 10/19 - DCS repaired this merge and replaced the 2 belts. This needs to be monitored", "issue_image": null, - "age_days": 18 + "age_days": 19 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "DCS/RME", @@ -3101,11 +3130,13 @@ "status": "Complete", "status_updates": "AM 10/19 - RME replaced this belt", "issue_image": null, - "age_days": 18 + "age_days": 19 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "Datalogic", @@ -3128,7 +3159,7 @@ "status": "Incomplete", "status_updates": null, "issue_image": null, - "age_days": 9 + "age_days": 10 } ], "very_high_priority_items": [ @@ -3141,7 +3172,7 @@ "status": "Incomplete", "status_updates": null, "issue_image": null, - "age_days": 9 + "age_days": 10 } ], "high_priority_items": [ @@ -3154,7 +3185,7 @@ "status": "Complete", "status_updates": "10/14 - Datalogic prioritzing deep dive on these issues to identify root cause\n10/17 - in process sa01 ab still has issue. investigating 10/19= Modification today, monitor 11/3 - DAta logic is reconfiguring all scan tunnels. SCA01, SCA02 complete. SCA03 and SCA04 to be completed over night 11/3", "issue_image": null, - "age_days": 22 + "age_days": 23 } ], "closed_items": [ @@ -3167,7 +3198,7 @@ "status": "Complete", "status_updates": "10/14 - Datalogic prioritzing deep dive on these issues to identify root cause\n10/17 - in process sa01 ab still has issue. investigating 10/19= Modification today, monitor 11/3 - DAta logic is reconfiguring all scan tunnels. SCA01, SCA02 complete. SCA03 and SCA04 to be completed over night 11/3", "issue_image": null, - "age_days": 22 + "age_days": 23 }, { "punchlist_name": "Long-Term Error Tracking Solution", @@ -3178,7 +3209,7 @@ "status": "Complete", "status_updates": "10/17- DL is proposes to get 2D images. Jody to follow up", "issue_image": null, - "age_days": 28 + "age_days": 29 }, { "punchlist_name": "Pull stats for error codes from Datalogic", @@ -3189,7 +3220,7 @@ "status": "Complete", "status_updates": "10/17 -- Jody to follow up with DL", "issue_image": null, - "age_days": 22 + "age_days": 23 } ], "monitor_items": [ @@ -3202,7 +3233,7 @@ "status": "Monitor", "status_updates": "10/17 - S01 ad. trigger was moved. appears to be resolved. monitoring", "issue_image": null, - "age_days": 22 + "age_days": 23 } ], "open_items": [ @@ -3215,9 +3246,23 @@ "status": "Incomplete", "status_updates": null, "issue_image": null, - "age_days": 9 + "age_days": 10 } - ] + ], + "incomplete_items": [ + { + "punchlist_name": "Add DHL label to Scan tunnel valid message", + "description": "DHL label was not in orignal spec. Need to be able to identify that label and filter out other barcodes.", + "priority": "(1) Very High", + "date_identified": "2025-10-27 00:00:00", + "date_completed": null, + "status": "Incomplete", + "status_updates": null, + "issue_image": null, + "age_days": 10 + } + ], + "incomplete_count": 1 }, { "vendor_name": "FMH/Gorbel", @@ -3243,11 +3288,13 @@ "status": "Complete", "status_updates": "Gorbel and FMH on site for training/ support", "issue_image": null, - "age_days": 21 + "age_days": 22 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "Gorbel", @@ -3271,7 +3318,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 21 + "age_days": 22 } ], "high_priority_items": [ @@ -3284,7 +3331,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 21 + "age_days": 22 } ], "closed_items": [ @@ -3297,7 +3344,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "DD218-DESTUFF-IT not properly working packing not able to climb up", @@ -3308,11 +3355,13 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 21 + "age_days": 22 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "MFO (Amazon)", @@ -3338,11 +3387,13 @@ "status": "Complete", "status_updates": "10/18 -- complete confirmed", "issue_image": null, - "age_days": 22 + "age_days": 23 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "MISC", @@ -3367,7 +3418,7 @@ "status": "Complete", "status_updates": "duplicate", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Auto inducts / By passes do not start up with scata -", @@ -3378,7 +3429,7 @@ "status": "Complete", "status_updates": "resolved", "issue_image": null, - "age_days": 21 + "age_days": 22 } ], "closed_items": [ @@ -3391,7 +3442,7 @@ "status": "Complete", "status_updates": "duplicate", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "Auto inducts / By passes do not start up with scata -", @@ -3402,7 +3453,7 @@ "status": "Complete", "status_updates": "resolved", "issue_image": null, - "age_days": 21 + "age_days": 22 }, { "punchlist_name": "supply cabinet in NON Con", @@ -3413,7 +3464,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "drop zone banner in Non Con", @@ -3424,7 +3475,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "printer station at both ends in Non Con", @@ -3435,7 +3486,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "5s area for emtpy carts in Non Con", @@ -3446,7 +3497,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 20 + "age_days": 21 }, { "punchlist_name": "Fans in Non Con", @@ -3457,11 +3508,13 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 20 + "age_days": 21 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 }, { "vendor_name": "Startup (Amazon)", @@ -3486,7 +3539,7 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 22 + "age_days": 23 } ], "closed_items": [ @@ -3499,11 +3552,13 @@ "status": "Complete", "status_updates": null, "issue_image": null, - "age_days": 22 + "age_days": 23 } ], "monitor_items": [], - "open_items": [] + "open_items": [], + "incomplete_items": [], + "incomplete_count": 0 } ], "summary": { @@ -3511,6 +3566,7 @@ "total_items": 162, "total_closed": 141, "total_open": 9, - "total_monitor": 12 + "total_monitor": 12, + "total_incomplete": 9 } } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b9df622..6103834 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,17 @@ pandas>=2.0.0 openpyxl>=3.0.0 pydantic>=2.0.0 + +# Optional: SharePoint integration +Office365-REST-Python-Client>=2.3.0 + +# Optional: Scheduling +apscheduler>=3.10.0 + +# Optional: Web API +flask>=2.3.0 +flask-cors>=4.0.0 + +# Configuration +pyyaml>=6.0 +python-dotenv>=1.0.0 \ No newline at end of file diff --git a/run-local.ps1 b/run-local.ps1 new file mode 100644 index 0000000..34d8513 --- /dev/null +++ b/run-local.ps1 @@ -0,0 +1,28 @@ +# PowerShell script to run vendor-report API locally with environment variables + +# Set SharePoint Configuration +$env:SHAREPOINT_ENABLED = "true" +$env:SHAREPOINT_SITE_URL = "https://automationstandard.sharepoint.com/sites/2429ODF_AMZ_MTN6_25K" +$env:SHAREPOINT_FOLDER_PATH = "/Documents/General/Amazon Punchlist [EXTERNAL]" +$env:SHAREPOINT_CLIENT_ID = "5e00db88-ff96-4070-8270-e6c9ea9282f0" +$env:SHAREPOINT_CLIENT_SECRET = "tYY8Q~e6hrzNA5EsTcUtDfZ4q3vT-c134r7nkaM8" +$env:SHAREPOINT_USE_APP_AUTH = "true" + +# Set API Configuration +$env:API_ENABLED = "true" +$env:API_PORT = "8080" +$env:API_HOST = "0.0.0.0" + +# Set Report Configuration +$env:REPORT_OUTPUT_DIR = "output" +$env:REPORT_REPORTS_DIR = "reports" + +Write-Host "Starting vendor-report API with SharePoint configuration..." -ForegroundColor Green +Write-Host "SharePoint Site: $env:SHAREPOINT_SITE_URL" -ForegroundColor Cyan +Write-Host "Folder Path: $env:SHAREPOINT_FOLDER_PATH" -ForegroundColor Cyan +Write-Host "API will run on: http://localhost:8080" -ForegroundColor Cyan +Write-Host "" + +# Run the API +python api_server.py + diff --git a/scheduler.py b/scheduler.py new file mode 100644 index 0000000..01ead22 --- /dev/null +++ b/scheduler.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +""" +Report Scheduler + +Schedules automatic report generation with optional SharePoint downloads. +""" + +import logging +from datetime import datetime +from typing import Optional +from pathlib import Path + +try: + from apscheduler.schedulers.blocking import BlockingScheduler + from apscheduler.triggers.interval import IntervalTrigger + from apscheduler.triggers.cron import CronTrigger + from apscheduler.triggers.date import DateTrigger + SCHEDULER_AVAILABLE = True +except ImportError: + SCHEDULER_AVAILABLE = False + logging.warning("APScheduler not installed. Scheduling features disabled.") + +from config import load_config +from report_generator import generate_report +from sharepoint_downloader import download_from_sharepoint + +logger = logging.getLogger(__name__) + + +class ReportScheduler: + """Manages scheduled report generation.""" + + def __init__(self, config_path: Optional[str] = None): + """ + Initialize scheduler. + + Args: + config_path: Path to configuration file + """ + if not SCHEDULER_AVAILABLE: + raise ImportError( + "APScheduler is required for scheduling. " + "Install it with: pip install apscheduler" + ) + + self.config = load_config(config_path) + self.scheduler = BlockingScheduler(timezone=self.config['scheduler']['timezone']) + self.scheduler_config = self.config['scheduler'] + self.sharepoint_config = self.config.get('sharepoint', {}) + self.report_config = self.config.get('report', {}) + + def generate_report_job(self): + """Job function to generate report.""" + logger.info("=" * 70) + logger.info("SCHEDULED REPORT GENERATION") + logger.info("=" * 70) + logger.info(f"Started at: {datetime.now()}") + + try: + # Download from SharePoint if enabled + if self.sharepoint_config.get('enabled'): + logger.info("Downloading files from SharePoint...") + try: + downloaded = download_from_sharepoint( + site_url=self.sharepoint_config['site_url'], + folder_path=self.sharepoint_config.get('folder_path'), + file_path=self.sharepoint_config.get('file_path'), + local_dir=self.sharepoint_config.get('local_dir', 'reports'), + username=self.sharepoint_config.get('username'), + password=self.sharepoint_config.get('password'), + client_id=self.sharepoint_config.get('client_id'), + client_secret=self.sharepoint_config.get('client_secret'), + use_app_authentication=self.sharepoint_config.get('use_app_authentication', False), + file_pattern=self.sharepoint_config.get('file_pattern'), + overwrite=self.sharepoint_config.get('overwrite', True) + ) + logger.info(f"Downloaded {len(downloaded)} file(s) from SharePoint") + except Exception as e: + logger.error(f"Failed to download from SharePoint: {e}") + # Continue with report generation even if download fails + + # Generate report + logger.info("Generating report...") + reports_dir = self.report_config.get('reports_dir', 'reports') + output_file = Path(self.report_config.get('output_dir', 'output')) / 'report.json' + + report_data = generate_report( + reports_dir=reports_dir, + output_file=str(output_file), + verbose=True + ) + + if report_data: + logger.info("✓ Scheduled report generation completed successfully") + else: + logger.error("✗ Scheduled report generation failed") + + except Exception as e: + logger.error(f"Error in scheduled report generation: {e}", exc_info=True) + + def start(self): + """Start the scheduler.""" + if not self.scheduler_config.get('enabled'): + logger.warning("Scheduler is disabled in configuration") + return + + schedule_type = self.scheduler_config.get('schedule_type', 'interval') + + if schedule_type == 'interval': + # Schedule at regular intervals + interval_hours = self.scheduler_config.get('interval_hours', 24) + trigger = IntervalTrigger(hours=interval_hours) + logger.info(f"Scheduling reports every {interval_hours} hours") + + elif schedule_type == 'cron': + # Schedule using cron expression + cron_expression = self.scheduler_config.get('cron_expression', '0 8 * * *') + # Parse cron expression (format: "minute hour day month day_of_week") + parts = cron_expression.split() + if len(parts) == 5: + trigger = CronTrigger( + minute=parts[0], + hour=parts[1], + day=parts[2], + month=parts[3], + day_of_week=parts[4] + ) + else: + logger.error(f"Invalid cron expression: {cron_expression}") + return + logger.info(f"Scheduling reports with cron: {cron_expression}") + + elif schedule_type == 'once': + # Run once at a specific time + # For "once", you'd typically use DateTrigger, but for simplicity, + # we'll just run it immediately + logger.info("Running report generation once (immediately)") + self.generate_report_job() + return + + else: + logger.error(f"Unknown schedule type: {schedule_type}") + return + + # Add job to scheduler + self.scheduler.add_job( + self.generate_report_job, + trigger=trigger, + id='generate_report', + name='Generate Vendor Report', + replace_existing=True + ) + + logger.info("Scheduler started. Press Ctrl+C to stop.") + try: + self.scheduler.start() + except KeyboardInterrupt: + logger.info("Scheduler stopped by user") + + +if __name__ == "__main__": + import sys + + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + + config_path = sys.argv[1] if len(sys.argv) > 1 else None + + scheduler = ReportScheduler(config_path=config_path) + scheduler.start() + diff --git a/sharepoint_downloader.py b/sharepoint_downloader.py new file mode 100644 index 0000000..5715ceb --- /dev/null +++ b/sharepoint_downloader.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python3 +""" +SharePoint File Downloader + +Downloads Excel files from SharePoint to the local reports directory. +Supports both scheduled and on-demand downloads. +""" + +import os +from pathlib import Path +from typing import Optional, List +from datetime import datetime +import logging + +try: + from office365.sharepoint.client_context import ClientContext + from office365.runtime.auth.authentication_context import AuthenticationContext + from office365.runtime.auth.user_credential import UserCredential + from office365.runtime.auth.client_credential import ClientCredential + SHAREPOINT_AVAILABLE = True +except ImportError: + SHAREPOINT_AVAILABLE = False + logging.warning("office365-rest-python-client not installed. SharePoint features disabled.") + +logger = logging.getLogger(__name__) + + +class SharePointDownloader: + """Downloads files from SharePoint.""" + + def __init__( + self, + site_url: str, + username: Optional[str] = None, + password: Optional[str] = None, + client_id: Optional[str] = None, + client_secret: Optional[str] = None, + use_app_authentication: bool = False + ): + """ + Initialize SharePoint downloader. + + Args: + site_url: SharePoint site URL (e.g., "https://yourcompany.sharepoint.com/sites/YourSite") + username: Username for user authentication (if not using app authentication) + password: Password for user authentication (if not using app authentication) + client_id: Azure AD app client ID (for app authentication) + client_secret: Azure AD app client secret (for app authentication) + use_app_authentication: Whether to use app authentication (recommended for automation) + """ + if not SHAREPOINT_AVAILABLE: + raise ImportError( + "office365-rest-python-client is required for SharePoint integration. " + "Install it with: pip install Office365-REST-Python-Client" + ) + + self.site_url = site_url + self.username = username + self.password = password + self.client_id = client_id + self.client_secret = client_secret + self.use_app_authentication = use_app_authentication + self.ctx = None + + def authenticate(self) -> bool: + """Authenticate with SharePoint.""" + try: + if self.use_app_authentication and self.client_id and self.client_secret: + # App authentication (recommended for automation) + credentials = ClientCredential(self.client_id, self.client_secret) + self.ctx = ClientContext(self.site_url).with_credentials(credentials) + logger.info("Authenticated with SharePoint using app credentials") + elif self.username and self.password: + # User authentication + credentials = UserCredential(self.username, self.password) + self.ctx = ClientContext(self.site_url).with_credentials(credentials) + logger.info("Authenticated with SharePoint using user credentials") + else: + logger.error("No authentication credentials provided") + return False + + # Test connection + web = self.ctx.web + self.ctx.load(web) + self.ctx.execute_query() + logger.info(f"Successfully connected to SharePoint site: {web.properties['Title']}") + return True + + except Exception as e: + logger.error(f"SharePoint authentication failed: {e}") + return False + + def download_file( + self, + file_path: str, + local_path: str, + overwrite: bool = True + ) -> bool: + """ + Download a single file from SharePoint. + + Args: + file_path: Path to file in SharePoint (e.g., "/Shared Documents/Reports/file.xlsx") + local_path: Local path where file should be saved + overwrite: Whether to overwrite existing file + + Returns: + True if successful, False otherwise + """ + if not self.ctx: + if not self.authenticate(): + return False + + try: + local_file_path = Path(local_path) + local_file_path.parent.mkdir(parents=True, exist_ok=True) + + # Check if file exists and overwrite flag + if local_file_path.exists() and not overwrite: + logger.info(f"File already exists, skipping: {local_path}") + return True + + # Download file + with open(local_file_path, "wb") as local_file: + file = self.ctx.web.get_file_by_server_relative_url(file_path) + file.download(local_file) + self.ctx.execute_query() + + logger.info(f"Downloaded: {file_path} -> {local_path}") + return True + + except Exception as e: + logger.error(f"Failed to download {file_path}: {e}") + return False + + def download_files_from_folder( + self, + folder_path: str, + local_dir: str, + file_pattern: Optional[str] = None, + overwrite: bool = True + ) -> List[str]: + """ + Download all files from a SharePoint folder. + + Args: + folder_path: Path to folder in SharePoint (e.g., "/Shared Documents/Reports") + local_dir: Local directory where files should be saved + file_pattern: Optional pattern to filter files (e.g., "*.xlsx") + overwrite: Whether to overwrite existing files + + Returns: + List of successfully downloaded file paths + """ + if not self.ctx: + if not self.authenticate(): + return [] + + downloaded_files = [] + + try: + folder = self.ctx.web.get_folder_by_server_relative_url(folder_path) + files = folder.files + self.ctx.load(files) + self.ctx.execute_query() + + local_dir_path = Path(local_dir) + local_dir_path.mkdir(parents=True, exist_ok=True) + + for file in files: + file_name = file.properties["Name"] + + # Filter by pattern if provided + if file_pattern: + if not file_name.endswith(file_pattern.replace("*", "")): + continue + + # Only download Excel files + if not (file_name.endswith('.xlsx') or file_name.endswith('.xls')): + continue + + local_file_path = local_dir_path / file_name + + if self.download_file( + file.properties["ServerRelativeUrl"], + str(local_file_path), + overwrite=overwrite + ): + downloaded_files.append(str(local_file_path)) + + logger.info(f"Downloaded {len(downloaded_files)} files from {folder_path}") + return downloaded_files + + except Exception as e: + logger.error(f"Failed to download files from folder {folder_path}: {e}") + return downloaded_files + + +def download_from_sharepoint( + site_url: str, + file_path: Optional[str] = None, + folder_path: Optional[str] = None, + local_dir: str = "reports", + username: Optional[str] = None, + password: Optional[str] = None, + client_id: Optional[str] = None, + client_secret: Optional[str] = None, + use_app_authentication: bool = False, + file_pattern: Optional[str] = None, + overwrite: bool = True +) -> List[str]: + """ + Convenience function to download files from SharePoint. + + Args: + site_url: SharePoint site URL + file_path: Path to specific file (if downloading single file) + folder_path: Path to folder (if downloading all files from folder) + local_dir: Local directory to save files + username: Username for authentication + password: Password for authentication + client_id: Azure AD app client ID + client_secret: Azure AD app client secret + use_app_authentication: Use app authentication + file_pattern: Pattern to filter files (e.g., "*.xlsx") + overwrite: Whether to overwrite existing files + + Returns: + List of downloaded file paths + """ + downloader = SharePointDownloader( + site_url=site_url, + username=username, + password=password, + client_id=client_id, + client_secret=client_secret, + use_app_authentication=use_app_authentication + ) + + if file_path: + # Download single file + local_file_path = Path(local_dir) / Path(file_path).name + if downloader.download_file(file_path, str(local_file_path), overwrite=overwrite): + return [str(local_file_path)] + return [] + elif folder_path: + # Download all files from folder + return downloader.download_files_from_folder( + folder_path=folder_path, + local_dir=local_dir, + file_pattern=file_pattern, + overwrite=overwrite + ) + else: + logger.error("Either file_path or folder_path must be provided") + return [] + + +if __name__ == "__main__": + import sys + from config import load_config + + logging.basicConfig(level=logging.INFO) + + # Load configuration + config = load_config() + + if not config.get('sharepoint'): + print("SharePoint configuration not found in config.yaml") + sys.exit(1) + + sp_config = config['sharepoint'] + + # Download files + downloaded = download_from_sharepoint( + site_url=sp_config['site_url'], + folder_path=sp_config.get('folder_path'), + file_path=sp_config.get('file_path'), + local_dir=sp_config.get('local_dir', 'reports'), + username=sp_config.get('username'), + password=sp_config.get('password'), + client_id=sp_config.get('client_id'), + client_secret=sp_config.get('client_secret'), + use_app_authentication=sp_config.get('use_app_authentication', False), + file_pattern=sp_config.get('file_pattern'), + overwrite=True + ) + + print(f"Downloaded {len(downloaded)} file(s):") + for file in downloaded: + print(f" - {file}") + diff --git a/web_ui.py b/web_ui.py new file mode 100644 index 0000000..a463a75 --- /dev/null +++ b/web_ui.py @@ -0,0 +1,766 @@ +#!/usr/bin/env python3 +""" +Web UI for Vendor Report Generator + +Provides a simple web interface for generating reports, viewing status, and managing configuration. +""" + +import logging +import json +from pathlib import Path +from typing import Optional +from datetime import datetime + +try: + from flask import Flask, render_template_string, jsonify, request, send_from_directory, redirect, url_for + from flask_cors import CORS + FLASK_AVAILABLE = True +except ImportError: + FLASK_AVAILABLE = False + logging.warning("Flask not installed. Web UI features disabled.") + +from config import load_config +from report_generator import generate_report +from sharepoint_downloader import download_from_sharepoint + +logger = logging.getLogger(__name__) + +app = None +config = None + +# HTML Template for the Web UI +UI_TEMPLATE = """ + + + + + + Vendor Report Generator + + + +
+
+

📊 Vendor Report Generator

+

Generate comprehensive vendor punchlist reports from Excel files

+
+ +
+
+ + +
+

Update Data

+

Download the latest Excel files from SharePoint to update your local data.

+ +
+ +
+ +
+
+

Downloading files from SharePoint... This may take a moment.

+
+
+ + +
+

Generate Report

+

Generate a new report from Excel files in the local reports directory.

+ +
+ +
+ +
+
+

Generating report... This may take a moment.

+
+
+ + +
+

Service Status

+
+
+

Loading status...

+
+
+
+ + +
+

Generated Reports

+
+

Loading reports...

+
+
+ + +
+

Configuration

+
+

Loading configuration...

+
+
+
+
+ + + + +""" + + +def create_app(config_path: Optional[str] = None): + """Create and configure Flask app with Web UI.""" + global app, config + + if not FLASK_AVAILABLE: + raise ImportError( + "Flask is required for Web UI. " + "Install it with: pip install flask flask-cors" + ) + + app = Flask(__name__) + CORS(app) + + config = load_config(config_path) + api_config = config.get('api', {}) + sharepoint_config = config.get('sharepoint', {}) + report_config = config.get('report', {}) + + app.config['API_KEY'] = api_config.get('api_key') + app.config['SHAREPOINT_CONFIG'] = sharepoint_config + app.config['REPORT_CONFIG'] = report_config + + @app.route('/') + def index(): + """Main web UI page.""" + return render_template_string(UI_TEMPLATE) + + @app.route('/api/update-sharepoint', methods=['POST']) + def update_sharepoint_endpoint(): + """Download files from SharePoint.""" + api_key = app.config.get('API_KEY') + if api_key: + provided_key = request.headers.get('X-API-Key') or (request.json.get('api_key') if request.json else None) + if provided_key != api_key: + return jsonify({'error': 'Invalid API key'}), 401 + + try: + sp_config = app.config['SHAREPOINT_CONFIG'] + if not sp_config.get('enabled'): + return jsonify({'error': 'SharePoint is not enabled in configuration'}), 400 + + logger.info("Downloading files from SharePoint...") + try: + downloaded = download_from_sharepoint( + site_url=sp_config['site_url'], + folder_path=sp_config.get('folder_path'), + file_path=sp_config.get('file_path'), + local_dir=sp_config.get('local_dir', 'reports'), + username=sp_config.get('username'), + password=sp_config.get('password'), + client_id=sp_config.get('client_id'), + client_secret=sp_config.get('client_secret'), + use_app_authentication=sp_config.get('use_app_authentication', False), + file_pattern=sp_config.get('file_pattern'), + overwrite=sp_config.get('overwrite', True) + ) + logger.info(f"Downloaded {len(downloaded)} file(s) from SharePoint") + return jsonify({ + 'status': 'success', + 'message': f'Successfully downloaded {len(downloaded)} file(s) from SharePoint', + 'downloaded_count': len(downloaded), + 'files': downloaded + }) + except Exception as e: + logger.error(f"Failed to download from SharePoint: {e}", exc_info=True) + return jsonify({'error': f'SharePoint download failed: {str(e)}'}), 500 + + except Exception as e: + logger.error(f"Error updating from SharePoint: {e}", exc_info=True) + return jsonify({'error': f'Update failed: {str(e)}'}), 500 + + @app.route('/api/generate', methods=['POST']) + def generate_report_endpoint(): + """Generate report on demand.""" + api_key = app.config.get('API_KEY') + if api_key: + provided_key = request.headers.get('X-API-Key') or (request.json.get('api_key') if request.json else None) + if provided_key != api_key: + return jsonify({'error': 'Invalid API key'}), 401 + + try: + request_data = request.json or {} + + report_config = app.config['REPORT_CONFIG'] + reports_dir = request_data.get('reports_dir', report_config.get('reports_dir', 'reports')) + output_file = request_data.get('output_file', + str(Path(report_config.get('output_dir', 'output')) / 'report.json')) + + # Check if reports directory exists and has files + reports_path = Path(reports_dir) + if not reports_path.exists(): + return jsonify({'error': f'Reports directory not found: {reports_dir}'}), 400 + + excel_files = list(reports_path.glob('*.xlsx')) + list(reports_path.glob('*.xls')) + if not excel_files: + return jsonify({'error': f'No Excel files found in {reports_dir}. Please update data from SharePoint first.'}), 400 + + logger.info(f"Generating report from {reports_dir} ({len(excel_files)} Excel file(s))...") + report_data = generate_report( + reports_dir=reports_dir, + output_file=output_file, + verbose=False + ) + + if report_data and report_data.get('vendors'): + return jsonify({ + 'status': 'success', + 'message': 'Report generated successfully', + 'output_file': output_file, + 'summary': report_data.get('summary', {}), + 'vendors_count': len(report_data.get('vendors', [])) + }) + else: + return jsonify({'error': 'Report generation failed - no data processed'}), 500 + + except Exception as e: + logger.error(f"Error generating report: {e}", exc_info=True) + return jsonify({'error': f'Report generation failed: {str(e)}'}), 500 + + @app.route('/api/status', methods=['GET']) + def status(): + """Get service status.""" + return jsonify({ + 'status': 'running', + 'sharepoint_enabled': app.config['SHAREPOINT_CONFIG'].get('enabled', False), + 'reports_dir': app.config['REPORT_CONFIG'].get('reports_dir', 'reports'), + 'output_dir': app.config['REPORT_CONFIG'].get('output_dir', 'output') + }) + + @app.route('/api/reports', methods=['GET']) + def list_reports(): + """List generated reports.""" + output_dir = Path(app.config['REPORT_CONFIG'].get('output_dir', 'output')) + reports = [] + + if output_dir.exists(): + html_files = list(output_dir.glob('*.html')) + for html_file in html_files: + json_file = html_file.with_suffix('.json') + reports.append({ + 'name': html_file.name, + 'json_name': json_file.name if json_file.exists() else None, + 'json_exists': json_file.exists(), + 'generated_at': datetime.fromtimestamp(html_file.stat().st_mtime).strftime('%Y-%m-%d %H:%M:%S'), + 'size': f"{html_file.stat().st_size / 1024:.1f} KB" + }) + + # Sort by modification time (newest first) + reports.sort(key=lambda x: x['generated_at'], reverse=True) + + return jsonify({'reports': reports}) + + @app.route('/api/config', methods=['GET']) + def get_config(): + """Get configuration (safe, no secrets).""" + return jsonify({ + 'sharepoint_enabled': app.config['SHAREPOINT_CONFIG'].get('enabled', False), + 'sharepoint_site_url': app.config['SHAREPOINT_CONFIG'].get('site_url', 'Not configured'), + 'sharepoint_folder_path': app.config['SHAREPOINT_CONFIG'].get('folder_path', 'Not configured'), + 'reports_dir': app.config['REPORT_CONFIG'].get('reports_dir', 'reports'), + 'output_dir': app.config['REPORT_CONFIG'].get('output_dir', 'output') + }) + + @app.route('/reports/') + def serve_report(filename): + """Serve report files.""" + output_dir = Path(app.config['REPORT_CONFIG'].get('output_dir', 'output')) + return send_from_directory(str(output_dir), filename) + + @app.route('/health', methods=['GET']) + def health(): + """Health check.""" + return jsonify({'status': 'healthy', 'service': 'vendor-report-generator-ui'}) + + return app + + +def run_server(config_path: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None): + """Run the Web UI server.""" + app = create_app(config_path) + + api_config = config.get('api', {}) + server_host = host or api_config.get('host', '0.0.0.0') + server_port = port or api_config.get('port', 8080) + + logger.info(f"Starting Web UI server on http://{server_host}:{server_port}") + app.run(host=server_host, port=server_port, debug=False) + + +if __name__ == "__main__": + import sys + + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + + config_path = sys.argv[1] if len(sys.argv) > 1 else None + + config = load_config(config_path) + if not config.get('api', {}).get('enabled', False): + logger.warning("API is disabled in configuration, but starting Web UI anyway...") + + run_server(config_path=config_path) +