# Troubleshooting

This guide covers common issues you may encounter when integrating COI Review, along with recommended solutions.

## Error status
When a certificate ends up in `error` status, processing failed before evaluation could complete. There are three common causes:

| Cause                    | Description                                                                                           |
|--------------------------|-------------------------------------------------------------------------------------------------------|
| AI parsing failure        | The AI couldn't extract any meaningful data from the PDF (blank, corrupted, or unreadable)            |
| Processing exception      | An unexpected error occurred during processing                                                        |
| Retries exhausted         | Transient errors (rate limits, timeouts) persisted after multiple retry attempts                      |

### What to do

```python
def handle_certificate_status(certificate):
    if certificate['status'] == 'error':
        # Log for investigation
        log_error(f"Certificate {certificate['id']} failed processing")

# Notify contractor to re-upload
        request_new_certificate(certificate['contractor'])
```

There is no automatic retry mechanism you can trigger. The recommended approach is to request a new certificate from the contractor.

## Processing timeouts
Certificate processing typically completes within 1-2 minutes. However, under certain conditions it can take longer:

| Timeout            | Duration                   |
|--------------------|----------------------------|
| AI processing       | 5 minutes per attempt      |
| Overall job        | 10 minutes per attempt     |
| Maximum with retries| ~2 hours (worst case)     |

Transient failures (rate limits, server errors, timeouts) are retried up to 3 times with exponential backoff (30s, 60s, 120s delays).

### Recommended handling

```python
def check_stuck_certificates():
    """Run periodically to catch certificates that may be stuck"""
    two_hours_ago = time.time() - (2 * 60 * 60)

# Find certificates still processing after 2 hours
    stuck = Certificate.query.filter(
        Certificate.status.in_(['pending', 'processing']),
        Certificate.created < two_hours_ago
    ).all()

for cert in stuck:
        # Alert your team or request re-upload
        handle_stuck_certificate(cert)
```

## File issues
### Rejected uploads

| Issue        | Error                        | Solution                                                    |
|--------------|------------------------------|-------------------------------------------------------------|
| Wrong format | File must be PDF            | Only PDF files are accepted. Convert images, Word docs, or scanned TIFFs to PDF first. |
| Too large    | File exceeds 15 MB limit    | Compress the PDF or request a smaller file from the contractor.                 |
| Invalid PDF  | File is not a valid PDF     | The file may be corrupted or have an incorrect extension. Request a new copy.  |

### Processing failures due to file quality

| Issue                    | Result        | Solution                                                            |
|--------------------------|---------------|---------------------------------------------------------------------|
| Blank or corrupted PDF   | `error` status| Request a valid PDF from the contractor                             |
| Poor scan quality        | Rules fail due to unparseable data | Request a clearer scan or a digitally-generated certificate  |
| Important data on page 3+| Rules fail (data not extracted) | Request a certificate with all coverage details on pages 1-2   |

Page limit
Only the first 2 pages of each PDF are analyzed. Most standard ACORD forms include all relevant information on page 1, but custom certificates may vary.

## Date parsing issues
The AI recognizes only three date formats:

| Format         | Example          |
|----------------|------------------|
| `MM/DD/YYYY`   | 01/15/2025       |
| `MM/DD/YY`     | 01/15/25         |
| `YYYY-MM-DD`   | 2025-01-15       |

Certificates using other formats (e.g., `15 Jan 2025`, `January 15, 2025`, `DD/MM/YYYY`) will have dates parsed as `null`, causing expiration-related rules to fail.

### Symptoms
- Policy expiration rules fail even though the certificate isn't expired
- `parsed_certificate_json` shows `null` for date fields

### Solution
Request a certificate with dates in a supported format, or have your compliance team manually review and approve the expiration rules.

## Missing or unparseable fields
When the AI cannot extract a field that a rule depends on, the rule fails. This is by design—certificates are flagged for human review rather than auto-approved when data is uncertain.

| Scenario                              | Behavior                                                |
|---------------------------------------|--------------------------------------------------------|
| Amount field missing or unparseable   | Treated as $0, rule fails                             |
| Required field is null or empty       | `is_not_none` check fails                              |
| Date field missing or unparseable     | Treated as null, rule fails                            |
| Boolean field missing                  | Treated as false, rule fails                           |

### Debugging
Fetch the full review results to see what was extracted:

```bash
curl "https://api.1099policy.com/api/v1/files/certificates/{id}?expand[]=review_results.full" \
  -u YOUR_SECRET_KEY:
```

Compare `parsed_certificate_json` with the original PDF to identify parsing issues.

## Multiple certificates per contractor
Each certificate upload creates an independent record. The system does not deduplicate or lock based on contractor.

| Scenario                                                                  | Behavior                                     |
|---------------------------------------------------------------------------|----------------------------------------------|
| New certificate uploaded while one is processing                          | Both process independently, both produce results |
| Multiple approved certificates exist                                       | Both remain valid; your application decides which to use|
| Contractor uploads corrected certificate                                   | Original certificate remains (with its status); new one evaluated separately |

### Determining the current certificate

```python
def get_current_certificate(contractor_id):
    response = requests.get(
        'https://api.1099policy.com/api/v1/files/certificates',
        auth=('YOUR_SECRET_KEY', ''),
        params={'contractor': contractor_id}
    )
    certificates = response.json()['data']

# Option 1: Most recent approved certificate
    approved = [c for c in certificates if c['status'] == 'approved']
    if approved:
        return max(approved, key=lambda x: x['created'])

# Option 2: Most recent non-error certificate
    valid = [c for c in certificates if c['status'] != 'error']
    if valid:
        return max(valid, key=lambda x: x['created'])

return None
```

## Expired certificates
Certificates are evaluated at upload time. A certificate that was `approved` may later have an expired policy.

### Proactive expiration tracking

```python
from datetime import datetime, timedelta

def check_expiring_certificates():
    """Run daily to identify certificates expiring soon"""

response = requests.get(
        'https://api.1099policy.com/api/v1/files/certificates',
        auth=('YOUR_SECRET_KEY', ''),
        params={'status': 'approved', 'limit': 100}
    )

for cert in response.json()['data']:
        # Fetch full details to get expiration date
        details = fetch_full_results(cert['id'])

if details['review_results']:
            parsed = details['review_results'].get('parsed_certificate_json', {})

# Check GL expiration (adjust path for other coverage types)
            gl = parsed.get('coverages', {}).get('commercial_general_liability', {})
            expiration = gl.get('policy_expiration_date')

if expiration and is_expiring_soon(expiration, days=30):
                notify_contractor_to_renew(cert['contractor'])

def is_expiring_soon(date_string, days=30):
    """Check if date is within the specified number of days"""
    for fmt in ['%m/%d/%Y', '%m/%d/%y', '%Y-%m-%d']:
        try:
            expiration = datetime.strptime(date_string, fmt)
            return expiration <= datetime.now() + timedelta(days=days)
        except ValueError:
            continue
    return False  # Couldn't parse date
```

## Split limits vs. Combined Single Limit (Auto Liability)
For automobile liability, if you require split limits (e.g., Bodily Injury per Person ≥ $100K) but the certificate only shows a Combined Single Limit (CSL), the system automatically checks whether the CSL meets the required amount.

| Your rule                                    | Certificate shows        | Result                           |
|----------------------------------------------|--------------------------|----------------------------------|
| BI per Person ≥ $100,000                    | CSL of $500,000          | Pass (CSL meets requirement)     |
| BI per Person ≥ $100,000                    | CSL of $50,000           | Fail (CSL below requirement)     |
| BI per Person ≥ $100,000                    | BI per Person of $250,000| Pass (exact field matches)      |

This prevents false failures for certificates that use CSL formatting.

## Workers' Compensation only requirements
When your insurance requirement only includes Workers' Compensation coverage (no GL, Auto, etc.), the system automatically skips validation of additional insured names in the description of operations. This is because additional insured endorsements are not applicable to WC-only policies.

No action needed—this is handled automatically.

## Voided audit results
In rare cases, audit results may be voided by your compliance team (e.g., if a certificate was evaluated against an incorrect requirement). Voided results:

- Are excluded from the API response entirely
- Don't appear in `audit_results`
- Aren't counted in `summary` totals

You don't need to handle voided results in your integration—they're filtered out before the response reaches you.

## Troubleshooting checklist
When investigating certificate issues:

| Check                         | How                                                                              |
|-------------------------------|---------------------------------------------------------------------------------|
| Certificate status             | `GET /api/v1/files/certificates/{id}` — check `status` field                   |
| What was extracted            | Add `?expand[]=review_results.full` — examine `parsed_certificate_json`        |
| Which rules failed            | Look at `audit_results` where `result: "fail"`                               |
| Original file                | Download from `pdf_url` and compare with parsed data                          |
| File requirements              | Confirm PDF format, under 15 MB, coverage info on pages 1-2                   |
| Date formats                  | Check certificate uses MM/DD/YYYY, MM/DD/YY, or YYYY-MM-DD                     |

## Getting help
If you've investigated an issue and need assistance:

- **Dashboard**: Review certificates and audit results visually
- **API Reference**: [docs.1099policy.com](https://docs.1099policy.com/)
- **Support**: support@1099policy.com — include the certificate ID (`cert_...`) in your request
