# shiftops-backend Full Instructions

## ShiftOps Backend Purpose

Lead generation grader tool that analyzes businesses using Google Places API data, calculates ShiftOps scores (0-100), and generates recommendations.

## Core Files

**API Endpoints:**

- `shiftops.php` – Main API endpoint (6084 lines, contains ShiftOpsAnalyzer class)
- `shiftops-cost-calculator.php` – Cost savings calculations (922 lines)
- `shiftops-competitive-analyzer.php` – Market positioning (910 lines)
- `shiftops-recommendations-engine.php` – Recommendation generation (947 lines)
- `shiftops-user-explanations.php` – Score explanations (126 lines)
- `shiftops-customer-matcher.php` – Customer matching (918 lines, lazy loading, multi-strategy)
- `shiftops-hubspot.php` – HubSpot lead submission (325 lines)
- `shiftops-nps.php` – NPS survey submission (238 lines)
- `shiftops-hubspot-customers.php` – Customer list sync (547 lines)
- `shiftops-health-check.php` – Diagnostic endpoint (123 lines)
- `shiftops-test-matching.php` – Test matching endpoint (172 lines)

**Data/Config:**

- `v2/data/industry_benchmarks.php` – Industry-specific benchmarks
- `v2/config/shiftops-customers.php` – Configuration constants

---

## Scoring Algorithm (Unique to ShiftOps)

### Pillar Scores (0-20 each)

**5 Pillars:**

1. `scheduling_efficiency` – Digital maturity + operational complexity
2. `absence_stability` – Review sentiment + seasonal factors
3. `time_tracking_hygiene` – Hours complexity analysis
4. `compliance_docs` – Industry-specific requirements
5. `payroll_readiness` – DATEV integration indicators

**Total Score:** Sum of 5 pillar scores = 0-100

### Calculation Process

1. **Calculate raw pillar scores** (floats, 0.00-20.00)
   - **Restaurant-specific reductions:** Digital maturity, review count impact, and operational complexity scoring are reduced for restaurants to prevent score inflation
2. **Sum raw scores**
3. **Apply data completeness multiplier** (0.60-1.0 based on available data)
   - **Customer multiplier:** Minimum 0.95 for customers (vs 0.60-1.0 for non-customers)
4. **Apply customer cap** (max 95 for existing Ordio customers)
5. **Round total score** (integer 0-100)
6. **Adjust pillar scores proportionally** to match rounded total
7. **Assign grade** (A+, A, A-, B+, B, B-, C+, C, C-, D+, D, D-, F based on total)

**For complete scoring documentation, see `docs/systems/shiftops/SHIFTOPS_SCORING_SYSTEM.md`**

### Customer Boost

**For existing Ordio customers:**

- Baseline bonus: +3 points per pillar (added before percentage boost)
- Percentage boost: 25-35% per pillar
  - Scheduling Efficiency: +35%
  - Time Tracking Hygiene: +35%
  - Compliance Docs: +30%
  - Absence Stability: +25%
  - Payroll Readiness: +30%
- Maximum scores: 18-20 per pillar
- Total score cap: 95 (intentional ceiling to maintain credibility)
- Customer multiplier: Minimum 0.95 (vs 0.60-1.0 for non-customers) to preserve boost visibility

**Rationale:** Customers shouldn't score 100 (implies no improvement needed), but should score high (reflects good practices). Higher multipliers ensure boost is not overly diluted.

### ROI Calculation

**Formula:**

- Use **annual** values (not monthly): `$annualSavings = $monthlySavings * 12`
- Calculate net benefit: `$netBenefit = $annualSavings - $ordioAnnualCost`
- ROI percentage: `($netBenefit / $ordioAnnualCost) * 100`
- **Cap at 500%** to prevent unrealistic values: `min(500, max(-100, $roiPercentage))`

**Code Locations:**

- `v2/api/shiftops.php` line ~3020 (`calculateROIPercentage()` method)
- `v2/api/shiftops-cost-calculator.php` line ~680 (`calculateROI()` method)

### Compliance Industry Baselines

**Industry-specific bonuses (reduced November 2025 to balance with other pillars):**

- Healthcare (hospital, pharmacy, doctor): +9 points (intentionally high - strict requirements)
- Hospitality (restaurant, cafe, bar): +5 points (reduced from +7)
- Retail (store, shopping_mall): +5 points (reduced from +7)
- General: +6 points

**Code Location:** `v2/api/shiftops.php` line ~3750 (`calculateComplianceScoreRaw()`)

---

## Data Flow (Unique to ShiftOps)

### Essential Mode

1. User searches business → Google Places autocomplete
2. POST to `/v2/api/shiftops.php` with `mode='essential'`
3. Returns minimal data for loading screen (name, address, preliminary score)
4. Stores in localStorage: `shiftops_report_data`

### Enhanced Mode

1. Background POST to `/v2/api/shiftops.php` with `mode='enhanced'`
2. Returns full analysis (detailed scores, recommendations, cost savings, competitive analysis)
3. Stores in localStorage: `shiftops_enhanced_data`

### Report Display

1. Load from localStorage
2. Display gated content (blurred until email submitted)
3. Progressive rendering (priority sections first)

### HubSpot Submission

1. User submits email form on `/shiftops-report` page
2. Frontend extracts `hubspotutk` cookie and includes in submission
3. POST to `/v2/api/shiftops-hubspot.php` with:
   - Contact info (email, firstname, lastname)
   - HubSpot cookie (`hubspotutk`) for activity linking
   - Report data (business info, scores, cost savings)
   - UTM tracking parameters
   - **Original referrer** (preserved from initial page visit, see Referrer Preservation below)
4. Backend processes:
   - Extracts IP address (handles proxy headers: X-Forwarded-For, X-Real-IP)
   - Extracts UTM parameters from input or cookies (with cookie fallback)
   - Extracts hsa\_\* parameters (Google Ads) from input or cookies
   - Extracts UTM/hsa\_\* parameters from page_url if not already set
   - Resolves lead source attribution via `ordio_resolve_attribution()` (includes hsa_src, hsa_kw)
   - Generates description overview from report data
   - Generates calculation_data JSON structure
5. Submits to HubSpot form (Portal ID: 145133546, Form ID: `41d07332-6697-4daa-b27e-dd60515f9c0f`)
6. Context object includes:
   - `hutk`: HubSpot tracking cookie (links to existing contacts)
   - `ipAddress`: Client IP address (required for form analytics)
   - `pageUri`: Actual page URL (from input or referrer)
   - `pageName`: "ShiftOps Report"
7. Fields submitted:
   - `sign_up_type__c`: "Tools Page"
   - `content`: "ShiftOps"
   - `tool_type`: "shiftops"
   - `description`: Clean text overview (business name, scores, savings)
   - `calculation_data`: JSON-encoded report summary
   - UTM fields: `source__c`, `utm_medium__c`, `utm_campaign__c`, `utm_term__c`, `content__c`, `gclid__c`, `leadsource` (only sent if values exist)
8. Logs rich data to `logs/shiftops-leads-full.log` and submission details to `logs/shiftops-hubspot.log`
9. Logs attribution resolution inputs/outputs for debugging (referrer source, UTM values, resolved leadSource)
10. **Consent debugging:** Logs `hubspotConsent`, `hubspot_form_consent_in_payload`, and `hubspot_status` (sent/skipped) at DEBUG/INFO level. Use `php v2/scripts/dev-helpers/debug-hubspot-contact.php <email>` to query HubSpot for contact and form activity.

### Referrer Preservation (Critical for Attribution)

**Problem:** When users navigate from `/shiftops` to `/shiftops-report` via redirect, `document.referrer` becomes same-site (`localhost:8003/shiftops`), causing attribution to default to "Direct Traffic" and losing the original traffic source.

**Solution:** Store original referrer before same-site redirect and use it for attribution resolution.

**Implementation:**

1. **Before redirect** (`v2/pages/shiftops.php` line ~3846):

   - Check if current referrer exists and is NOT same-site
   - Store in localStorage as `ordio_original_referrer` with timestamp (30-day expiration)
   - Preserve existing original referrer if current referrer is same-site

2. **On report page** (`v2/pages/shiftops-report.php`):

   - `utmTracker.getUTMDataForAPI()` automatically uses original referrer if current referrer is same-site
   - Fallback logic also checks localStorage if `utmTracker` not available

3. **Backend attribution** (`v2/api/shiftops-hubspot.php`):
   - Receives original referrer from frontend
   - Logs referrer source (original vs same-site) for debugging
   - Passes to `ordio_resolve_attribution()` for proper lead source resolution

**localStorage Key:** `ordio_original_referrer`

- Format: `{referrer: string, timestamp: number, page_url: string}`
- Expiration: 30 days
- Used by: `utm-tracking.js`, `shiftops-report.php` fallback

**Troubleshooting:**

- Check browser console logs for "📌 Original referrer stored" or "📌 Using original referrer"
- Check `logs/shiftops-hubspot.log` for "Attribution input" and "Attribution resolved" entries
- Verify `ordio_original_referrer` exists in localStorage (DevTools → Application → Local Storage)

---

## Caching Strategy (Unique to ShiftOps)

### Cache Key Generation

Based on: `place_id`, `name`, `rating`, `user_ratings_total`, `types`, `mode`

Format: `shiftops_analysis_{md5_hash}.json`

Location: `v2/cache/shiftops/`

### Cache TTL

- **1 hour (3600 seconds)**
- Automatic expiration on read (check file timestamp)
- Manual cleanup on cache miss (delete old files)

### Cache Benefits

- Reduces Google Places API calls (cost savings)
- Faster response for repeated searches
- Reduces server load

---

## Customer Matching (Unique to ShiftOps)

### Matching Strategies (In Order of Confidence)

1. **Domain match** (95% confidence)

   - Extract domain from Google Places website
   - Match against customer database domains

2. **Exact name match** (100% confidence)

   - Normalize names (lowercase, remove special characters)
   - Exact string match

3. **Prefix match** (75-90% confidence)

   - Match first N characters of name
   - Adjust confidence based on prefix length

4. **Fuzzy match** (75-95% confidence)

   - Levenshtein distance algorithm
   - Confidence decreases with distance

5. **Address match** (+5% boost)
   - If name matches AND address matches, boost confidence

### Matching Thresholds

- **Exact match:** 100%
- **Domain match:** 95%
- **Fuzzy match:** 85% minimum
- **Prefix match:** 75% minimum

**Below threshold:** Treat as non-customer

---

## Team Size Estimation (ShiftOps-Specific)

### Validation Bounds Requirements

**All implementations must use same validation bounds:**

- **Restaurant:** Min 5, Max 25 (capped at 25)
- **Cafe:** Min 3, Max 15 (capped at 15)
- **Bar:** Min 3, Max 25 (capped at 25)
- **Store:** Min 2, Max 30 (capped at 30)
- **Hospital:** Min 8, Max 60 (capped at 60)
- **Pharmacy:** Min 3, Max 20 (capped at 20)
- **General:** Min 3, Max 30 (capped at 30)

### Volume Factor Caps

- **Restaurants with 2000+ reviews:** 4.0x cap (reduced from 6.0x to prevent unrealistic inflation)
  - Note: "2000" refers to review count threshold, not a year
- **Other businesses:** 6.0x cap
- **All businesses:** Minimum 0.3x

### Safety Checks

- **Restaurant cap:** Additional validation after location multipliers ensures restaurants never exceed 25 employees
- **Applied in all implementations:** Prevents location multipliers from inflating values unrealistically

### Code Locations

- PHP Backend: `v2/api/shiftops-cost-calculator.php` line 110 (`estimateTeamSize()`)
- PHP API: `v2/api/shiftops.php` line 2047 (`estimateTeamSize()`)

### Consistency Requirements

- All implementations must use same formulas, bounds, and caps
- Changes must be made in all 4 locations (PHP backend, PHP API, JavaScript report, JavaScript loading)
- Validation bounds must be applied after location multipliers
- Safety checks must be applied after validation bounds

### Documentation

See `docs/systems/shiftops/TEAM_ESTIMATION_CHANGELOG.md` for complete changelog and `docs/systems/shiftops/TEAM_ESTIMATION_QUICK_REFERENCE.md` for quick reference.

## Validation Checklist (ShiftOps-Specific)

Beyond global validation:

- [ ] Scoring algorithm produces valid scores (0-100, integer)
- [ ] Pillar scores sum to total score (within rounding tolerance)
- [ ] Customer boost applied correctly (max 95 total)
- [ ] Data completeness multiplier applied correctly (0.60-1.0, see `docs/systems/shiftops/SHIFTOPS_SCORING_SYSTEM.md`)
- [ ] Scoring calculations match documentation (verify all factors, baselines, boosts)
- [ ] Grade assignment correct (A+ through F, see grade ranges in scoring docs)
- [ ] Team size estimation uses correct validation bounds (restaurant: 25, cafe: 15, etc.)
- [ ] Volume factor cap applied correctly (4.0x for restaurants with 2000+ reviews)
- [ ] Safety checks applied after location multipliers (restaurant cap at 25)
- [ ] Caching works (1-hour TTL, correct cache key)
- [ ] Customer matching identifies known customers (test with known names/domains)
- [ ] HubSpot submission logs to `logs/shiftops-leads-full.log` and `logs/shiftops-hubspot.log`
- [ ] HubSpot integration submits to correct form ID (lead: `41d07332-6697-4daa-b27e-dd60515f9c0f`, NPS: `804459f7-c18d-4da6-8a0b-a81f44bb8275`)
- [ ] HubSpot context includes `hutk` cookie (from input or `$_COOKIE['hubspotutk']`)
- [ ] HubSpot context includes `ipAddress` (from `$_SERVER['REMOTE_ADDR']` with proxy header handling)
- [ ] HubSpot submission includes `sign_up_type__c = "Tools Page"` (not "ShiftOps Grader")
- [ ] HubSpot submission includes `content = "ShiftOps"`
- [ ] HubSpot submission includes `description` field with clean overview
- [ ] HubSpot submission includes `calculation_data` field with JSON-encoded report summary
- [ ] HubSpot submission includes all UTM tracking fields (`source__c`, `utm_medium__c`, `utm_campaign__c`, `utm_term__c`, `content__c`, `gclid__c`, `leadsource`) - only sent if values exist (no empty fields)
- [ ] Frontend extracts and sends `hubspotutk` cookie in form submission
- [ ] Frontend includes cost savings data (`team_size_estimate`, `monthly_labor_cost`, `total_monthly_savings`, `total_annual_savings`) for description/calculation_data
- [ ] Original referrer preserved before redirect from `/shiftops` to `/shiftops-report` (stored in localStorage as `ordio_original_referrer`)
- [ ] Attribution uses original referrer (not same-site referrer) for lead source resolution
- [ ] Attribution logs show correct referrer source and resolved leadSource (check `logs/shiftops-hubspot.log`)
- [ ] NPS endpoint validates score (0-10 integer) and calculates category correctly
- [ ] NPS endpoint logs to `logs/shiftops-nps.log`
- [ ] Error handling robust (Google API failures, missing data)
- [ ] No console errors in JavaScript (frontend)

---

## Common ShiftOps Backend Pitfalls

### Scoring Calculation Errors

❌ **BAD:**

- Pillar scores don't sum to total
- Customer boost exceeds 95 cap
- Scores outside 0-100 range

✅ **GOOD:**

- Test with multiple scenarios (non-customer, customer, edge cases)
- Verify pillar scores sum matches total (within rounding tolerance)
- Ensure customer cap applied

### Cache Key Collisions

❌ **BAD:** Different businesses generate same cache key

✅ **GOOD:** Include enough data in cache key to ensure uniqueness (place_id, name, rating, types)

### Customer Matching False Positives

❌ **BAD:** Non-customer matched as customer due to loose matching

✅ **GOOD:** Use confidence thresholds, require 85%+ match, verify with multiple strategies

### Missing Error Handling

❌ **BAD:** Google API failure crashes script

✅ **GOOD:** Try-catch blocks, fallback to cached data, log errors, return user-friendly messages

---

## Reference Documentation

For detailed ShiftOps workflows:

- `docs/systems/shiftops/SHIFTOPS_ARCHITECTURE.md` – System architecture (updated with current file sizes, detailed scoring algorithm)
- `docs/systems/shiftops/SHIFTOPS_API_DOCUMENTATION.md` – API documentation
- `docs/systems/shiftops/SHIFTOPS_DATA_STRUCTURES.md` – Complete data structures reference (NEW)
- `docs/systems/shiftops/SHIFTOPS_COMPONENTS.md` – All classes and methods documentation (NEW)
- `docs/systems/shiftops/SHIFTOPS_TROUBLESHOOTING.md` – Troubleshooting guide (updated with latest fixes)
- `docs/systems/shiftops/SHIFTOPS_QUICK_REFERENCE.md` – Quick reference guide
- `docs/systems/shiftops/SHIFTOPS_DEVELOPMENT_CHECKLIST.md` – Development checklist
- `docs/systems/shiftops/SHIFTOPS_SCORING_SYSTEM.md` – Complete scoring system documentation (NEW)
- `docs/systems/shiftops/SHIFTOPS_SCORING_QUICK_REFERENCE.md` – Scoring quick reference (NEW)
## Related Documentation

See [docs/ai/RULE_TO_DOC_MAPPING.md](../../docs/ai/RULE_TO_DOC_MAPPING.md) for complete mapping.

**Key Documentation:**

- [docs/systems/shiftops/README.md](../../docs/systems/shiftops/README.md) - `docs/systems/shiftops/README.md` - ShiftOps system index
- [docs/systems/shiftops/](../../docs/systems/shiftops/) - `docs/systems/shiftops/` - All ShiftOps documentation files
- [docs/guides/PAGE_TYPE_GUIDES.md](../../docs/guides/PAGE_TYPE_GUIDES.md) - `docs/guides/PAGE_TYPE_GUIDES.md` - ShiftOps workflow patterns

