From 3c0b9c3c64d8f92a4cb9384b8e2a528ab69b4b0e Mon Sep 17 00:00:00 2001 From: Christopher Luft Date: Tue, 9 Dec 2025 17:47:39 -0800 Subject: [PATCH 01/11] Rebuild reporting skill with template-based architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace monolithic reporting skill with clean template-driven design - Add billing-report.json template defining input/output schema - Create billing-collector agent for parallel multi-org data collection - Skill now calls MCP tools directly (like detection-engineering pattern) - Single-org: direct MCP calls from skill - Multi-org: parallel billing-collector agents Templates define contracts, skill handles orchestration, agents handle data collection. Never fabricates data - all values from API responses. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../lc-essentials/agents/billing-collector.md | 153 ++ .../lc-essentials/skills/reporting/SKILL.md | 1536 +++-------------- .../reporting/templates/billing-report.json | 206 +++ 3 files changed, 558 insertions(+), 1337 deletions(-) create mode 100644 marketplace/plugins/lc-essentials/agents/billing-collector.md create mode 100644 marketplace/plugins/lc-essentials/skills/reporting/templates/billing-report.json diff --git a/marketplace/plugins/lc-essentials/agents/billing-collector.md b/marketplace/plugins/lc-essentials/agents/billing-collector.md new file mode 100644 index 00000000..7e126446 --- /dev/null +++ b/marketplace/plugins/lc-essentials/agents/billing-collector.md @@ -0,0 +1,153 @@ +--- +name: billing-collector +description: Collect billing data for a SINGLE LimaCharlie organization. Designed to be spawned in parallel (one instance per org) by the reporting skill. Returns structured JSON matching the billing-report template schema. +model: haiku +allowed-tools: + - mcp__plugin_lc-essentials_limacharlie__lc_call_tool + - Bash +--- + +# Billing Collector Agent + +You collect billing/invoice data for a SINGLE LimaCharlie organization and return structured JSON. + +## Input Format + +You will receive a prompt containing: +- **Organization name**: Human-readable name +- **OID**: Organization UUID +- **Year**: Invoice year (integer) +- **Month**: Invoice month (1-12) + +Example prompt: +``` +Collect billing data for org 'Acme Corp' (OID: c7e8f940-1234-5678-abcd-1234567890ab) +Year: 2025, Month: 11 +Return JSON matching billing-report template schema +``` + +## Workflow + +### Step 1: Extract Parameters + +Parse the prompt to extract: +- `org_name`: Organization name +- `oid`: Organization UUID +- `year`: Invoice year +- `month`: Invoice month + +### Step 2: Fetch Invoice Data + +Call the MCP tool directly: + +``` +tool: get_org_invoice_url +parameters: + oid: [extracted-oid] + year: [extracted-year] + month: [extracted-month] + format: simple_json +``` + +### Step 3: Handle Response + +**On Success:** +The API returns: +```json +{ + "lines": [ + { "description": "...", "amount": 400000, "quantity": 1 }, + { "description": "...", "amount": 150000, "quantity": 2500 } + ], + "total": 550000 +} +``` + +Transform to output schema: +1. Convert `total` from cents to dollars: `total / 100` +2. Convert each line item `amount` from cents to dollars +3. Map `lines` to `skus` array + +**On Error:** +- **403 Forbidden**: Return status="error", error_message="Insufficient permissions - requires admin/owner role" +- **404 Not Found**: Return status="no_invoice", error_message="No invoice exists for this period" +- **Other errors**: Return status="error" with error details + +### Step 4: Optionally Fetch Invoice URL + +If data retrieval succeeded, also get the PDF URL: + +``` +tool: get_org_invoice_url +parameters: + oid: [extracted-oid] + year: [extracted-year] + month: [extracted-month] +``` + +(No format parameter = returns PDF URL) + +## Output Schema + +Return ONLY valid JSON matching this structure: + +```json +{ + "name": "Acme Corp", + "oid": "c7e8f940-1234-5678-abcd-1234567890ab", + "status": "success", + "cost": 5500.00, + "currency": "usd", + "skus": [ + { + "description": "Enterprise Plan - Base", + "amount": 4000.00, + "quantity": 1 + }, + { + "description": "Additional Sensors", + "amount": 1500.00, + "quantity": 2500 + } + ], + "invoice_url": "https://pay.stripe.com/invoice/..." +} +``` + +**On Error:** +```json +{ + "name": "Acme Corp", + "oid": "c7e8f940-1234-5678-abcd-1234567890ab", + "status": "error", + "error_message": "403 Forbidden - Insufficient permissions" +} +``` + +**No Invoice:** +```json +{ + "name": "Acme Corp", + "oid": "c7e8f940-1234-5678-abcd-1234567890ab", + "status": "no_invoice", + "error_message": "No invoice exists for November 2025" +} +``` + +## Critical Rules + +### NEVER Fabricate Data +- Only return data from the API response +- If a field is missing, omit it or return null +- NEVER estimate, guess, or calculate values not in the response + +### Currency Conversion +- API returns amounts in **cents** +- ALWAYS divide by 100 for dollars +- Example: `400000` cents = `4000.00` dollars + +### Return Format +- Return ONLY the JSON object +- No markdown formatting +- No explanatory text before or after +- Must be valid, parseable JSON diff --git a/marketplace/plugins/lc-essentials/skills/reporting/SKILL.md b/marketplace/plugins/lc-essentials/skills/reporting/SKILL.md index 34cc3a9e..e273805d 100644 --- a/marketplace/plugins/lc-essentials/skills/reporting/SKILL.md +++ b/marketplace/plugins/lc-essentials/skills/reporting/SKILL.md @@ -1,1454 +1,316 @@ --- name: reporting -description: Generate comprehensive multi-tenant security and operational reports from LimaCharlie. Provides billing summaries, usage roll-ups, detection trends, sensor health monitoring, and configuration audits across multiple organizations. Supports both per-tenant detailed breakdowns and cross-tenant aggregated roll-ups. Built with strict data accuracy guardrails to prevent fabricated metrics. Supports partial report generation when some organizations fail, with transparent error documentation. Time windows always displayed, detection limits clearly flagged, zero cost calculations. +description: Generate template-based reports from LimaCharlie. Uses JSON templates to define report structure and calls MCP tools directly for data collection. Supports billing reports with single-tenant or multi-tenant scope. Never fabricates data. allowed-tools: - - Skill - - Task + - mcp__plugin_lc-essentials_limacharlie__lc_call_tool - Read - - Write - Bash + - Task --- -# LimaCharlie Reporting Skill - -> **Prerequisites**: Run `/init-lc` to load LimaCharlie guidelines into your CLAUDE.md. - -## Overview - -This skill enables AI-assisted generation of comprehensive security and operational reports across LimaCharlie organizations. It provides structured access to billing data, usage statistics, detection summaries, sensor health, and configuration audits. Supports both per-tenant detailed reports and cross-tenant aggregated roll-ups. - -**Core Philosophy**: Accuracy over completeness. This skill prioritizes data accuracy with strict guardrails that make fabricated metrics impossible. Reports clearly document what data is available, what failed, and what limits were applied. +# LimaCharlie Reporting -## Purpose +Template-driven reporting system for LimaCharlie data. Each report type has a JSON template defining inputs, outputs, and data sources. -- Generate multi-tenant reports across 50+ organizations -- Provide billing and usage summaries for customer invoicing -- Analyze security detection trends across customer base -- Monitor sensor health and deployment status -- Audit organizational configurations -- Track operational metrics for capacity planning -- Support partial report generation with clear error documentation - -## When to Use This Skill - -Use this skill when you need to: +--- -### Multi-Tenant MSSP Reports -- **"Generate monthly report for all my customers"** - Comprehensive overview across all organizations -- **"Billing summary for November 2025"** - Usage and billing data for invoicing period -- **"Show me customer health dashboard"** - Sensor status and detection trends across clients -- **"Which customers had the most detections this month?"** - Security activity ranking -- **"Export usage data for all organizations"** - Bulk data extraction for analysis +## Available Report Templates -### Single Organization Deep Dives -- **"Detailed report for Client ABC"** - Complete organizational analysis -- **"Security posture for organization XYZ"** - Detection and rule effectiveness -- **"Sensor health for customer PDQ"** - Endpoint deployment and status +| Template | Description | Scope | +|----------|-------------|-------| +| `billing-report` | Invoice-focused billing data | single / all | -### Billing and Usage Analysis -- **"Usage trends across all customers"** - Comparative analysis for capacity planning -- **"Which orgs are using the most data?"** - Resource consumption identification -- **"Show subscription status for all clients"** - Billing health check +Templates are located in `skills/reporting/templates/`. -### Operational Monitoring -- **"How many sensors are offline across all orgs?"** - Fleet health monitoring -- **"Detection volume trends this month"** - Security activity patterns -- **"Which customers need attention?"** - Issue identification and prioritization +--- -## Critical Prerequisites +## Billing Report -### Authentication -Ensure you are authenticated to LimaCharlie with access to target organizations: -- User must have permissions across multiple organizations (MSSP/partner account) -- Billing data requires admin/owner role per organization -- Usage statistics accessible with standard read permissions +Generate invoice-based billing reports for one or all organizations. -### Understanding Organization IDs (OIDs) -**⚠️ CRITICAL**: Organization ID (OID) is a **UUID** (like `c7e8f940-1234-5678-abcd-1234567890ab`), **NOT** the organization name. +### Required Input -- Use `list-user-orgs` function to get OID from organization name -- All API calls require the UUID, not the friendly name -- OIDs are permanent identifiers (names can change) +| Parameter | Type | Description | +|-----------|------|-------------| +| `year` | integer | Invoice year (e.g., 2025) | +| `month` | integer | Invoice month (1-12) | -### Time Range Requirements +### Optional Input -**⚠️ MANDATORY: Prompt User for Time Range** +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `scope` | string | `all` | `single` = one org, `all` = all accessible orgs | +| `oid` | uuid | - | Organization ID (required when scope=single) | +| `format` | string | `markdown` | Output format: `json` or `markdown` | -Before generating any report that requires detection or event data, you MUST ask the user to confirm or specify the time range using the `AskUserQuestion` tool: +### Usage Examples +**All organizations:** ``` -AskUserQuestion( - questions=[{ - "question": "What time range should I use for this report?", - "header": "Time Range", - "options": [ - {"label": "Last 24 hours", "description": "Most recent day of data"}, - {"label": "Last 7 days", "description": "Past week of activity"}, - {"label": "Last 30 days", "description": "Past month of activity"}, - {"label": "Custom range", "description": "I'll specify exact dates"} - ], - "multiSelect": false - }] -) +"Generate billing report for November 2025" +"Billing summary for all orgs, month 11 year 2025" ``` -If user selects "Custom range", follow up to get specific start/end dates. - -**Core Requirements:** -- All reports MUST specify explicit time ranges -- Time windows MUST be displayed in every report section -- NEVER assume a default time range without user confirmation -- Maximum recommended range: 90 days (API limitations) - -**⚠️ CRITICAL: Dynamic Timestamp Calculation** - -**NEVER use hardcoded epoch values from examples or documentation!** - -ALWAYS calculate timestamps dynamically using bash before making API calls: - -```bash -# Get current Unix timestamp -NOW=$(date +%s) - -# Calculate relative time ranges -HOURS_24_AGO=$((NOW - 86400)) # 24 hours = 86400 seconds -DAYS_7_AGO=$((NOW - 604800)) # 7 days = 604800 seconds -DAYS_30_AGO=$((NOW - 2592000)) # 30 days = 2592000 seconds -DAYS_90_AGO=$((NOW - 7776000)) # 90 days = 7776000 seconds - -# For specific date ranges (user-provided) -START=$(date -d "2025-11-01 00:00:00 UTC" +%s) -END=$(date -d "2025-11-30 23:59:59 UTC" +%s) - -# Display human-readable for confirmation -echo "Time range: $(date -d @$START) to $(date -d @$END)" +**Single organization:** ``` - -**Why This Matters:** -- The detection API (`get_historic_detections`) uses Unix epoch timestamps in SECONDS -- Using stale or example timestamps (like those in documentation) returns NO DATA -- The API only returns detections within the specified time window -- Incorrect timestamps = empty results = incorrect reports - -**Validation Before API Call:** -```bash -# Verify timestamps are reasonable -if [ $START -gt $END ]; then - echo "ERROR: Start time is after end time" - exit 1 -fi - -if [ $END -gt $NOW ]; then - echo "WARNING: End time is in the future, using current time" - END=$NOW -fi +"Billing report for Acme Corp, November 2025" +"Invoice details for org c7e8f940-..., month 11, year 2025" ``` -## Data Accuracy Guardrails - -### Principle 1: NEVER Fabricate Data - -**Absolute Rules:** -- ❌ NEVER estimate, infer, or extrapolate data not in API responses -- ❌ NEVER calculate costs (no pricing data in API) -- ❌ NEVER guess at missing fields -- ❌ NEVER assume "standard" values -- ❌ NEVER substitute placeholder data for errors - -**Always:** -- βœ… Show "N/A" or "Data unavailable" for missing fields -- βœ… Display explicit warnings when limits reached -- βœ… Document all errors and failures prominently -- βœ… State "Retrieved X of potentially more" when truncated -- βœ… Link to invoice URLs for billing details - -### Principle 2: Detection Limit Handling - -**Default Limit**: 5,000 detections per organization - -**Required Workflow:** -``` -1. Query with limit=5000 -2. Track retrieved_count -3. Check: limit_reached = (retrieved_count >= 5000) -4. If limit_reached: - ⚠️ DISPLAY PROMINENT WARNING - "DETECTION LIMIT REACHED - Retrieved: 5,000 detections - Actual count: May be significantly higher - - For complete data: - - Narrow time range - - Query specific date ranges - - Filter by category or sensor" -``` - -**Never Say:** -- ❌ "Total detections: 5,000" (implies this is complete) -- ❌ "Approximately 5,000 detections" (ambiguous) - -**Always Say:** -- βœ… "Retrieved 5,000 detections (limit reached - actual count may be higher)" -- βœ… "Detection sample: First 5,000 of potentially more" - -### Principle 3: Pricing and Billing - -**Absolute Rule: ZERO Cost Calculations** - -**What You CAN Show:** -- βœ… Usage metrics: events, data output (GB), evaluations, peak sensors -- βœ… Billing metadata: plan name, status, next billing date -- βœ… Invoice links: get-org-invoice-url for actual costs +--- -**What You CANNOT Do:** -- ❌ Calculate costs based on usage -- ❌ Estimate bills -- ❌ Multiply usage by assumed rates -- ❌ Project future costs -- ❌ Compare plan pricing +## Workflow: Single Organization -**Even if user provides rates:** -- ❌ Don't perform calculations -- βœ… Show usage metrics -- βœ… Provide invoice link -- βœ… State: "For billing details, see invoice: [URL]" +When `scope=single` or user specifies a single org: -### Principle 4: Time Window Display +### Step 1: Resolve OID -**MANDATORY in Every Report:** +If user provides org name instead of OID: ``` -Header (always visible): - Generated: 2025-11-20 14:45:30 UTC - Time Window: 2025-11-01 00:00:00 UTC to 2025-11-30 23:59:59 UTC (30 days) - Organizations: 45 of 50 processed successfully - -Per Section: - ── Usage Statistics ── - Data Retrieved: 2025-11-20 14:45:35 UTC - Coverage Period: Nov 1-30, 2025 (30 days) - Source: get-usage-stats API - Data Freshness: Daily updates (24hr delay typical) +tool: list_user_orgs +parameters: {} ``` -### Principle 5: Error Transparency +Match org name to get OID. -**Partial Reports Are Acceptable:** -- Generate reports even if some organizations fail -- Clearly document which organizations failed and why -- Never silently skip failed organizations -- Provide actionable remediation steps +### Step 2: Fetch Invoice Data -**Error Documentation Template:** ``` -⚠️ FAILED ORGANIZATIONS (3 of 50) - -Client ABC (oid: c7e8f940-...) - Status: ❌ Failed - Error: 403 Forbidden - Endpoint: get-billing-details - Reason: Insufficient permissions - Impact: Billing data unavailable - Action: Grant billing:read permission - Timestamp: 2025-11-20 14:32:15 UTC +tool: get_org_invoice_url +parameters: + oid: [uuid] + year: [year] + month: [month] + format: simple_json ``` -## Available Data Sources - -### 1. Multi-Tenant Discovery -**Function**: `list-user-orgs` -- **Endpoint**: GET /v1/user/orgs -- **OID Required**: NO (user-level operation) -- **Returns**: List of accessible organizations with OIDs and names -- **Usage**: Starting point for all multi-tenant operations -- **Data Freshness**: Real-time - -**Response Structure:** +**Response:** ```json { - "orgs": [ - { - "oid": "c7e8f940-1234-5678-abcd-1234567890ab", - "name": "Client ABC Production", - "role": "owner" - }, - { - "oid": "c7e8f940-5678-1234-dcba-0987654321ab", - "name": "Client XYZ Security", - "role": "admin" - } + "lines": [ + { "description": "Enterprise Plan", "amount": 400000, "quantity": 1 }, + { "description": "Sensors (2500)", "amount": 150000, "quantity": 2500 } ], - "total": 2 -} -``` - -**Validation:** -- Check `orgs` array exists and not empty -- Verify each OID is valid UUID format -- Confirm role is "owner", "admin", or "user" - -### 2. Organization Metadata -**Function**: `get-org-info` -- **Endpoint**: GET /v1/orgs/{oid} -- **OID Required**: YES -- **Returns**: Organization details, creation date, settings -- **Data Freshness**: Real-time - -**Response Structure:** -```json -{ - "oid": "c7e8f940-...", - "name": "Client ABC", - "created": 1672531200, - "creator": "user@example.com" -} -``` - -### 3. Usage Statistics -**Function**: `get-usage-stats` -- **Endpoint**: GET /v1/usage/{oid} -- **OID Required**: YES -- **Returns**: Daily usage metrics (~90 days historical) -- **Data Freshness**: Daily aggregation, 24-hour delay typical - -**Response Structure:** -```json -{ - "usage": { - "2025-11-06": { - "sensor_events": 131206, - "output_bytes_tx": 500123456, - "replay_num_evals": 435847, - "peak_sensors": 4 - }, - "2025-11-07": { - "sensor_events": 145821, - "output_bytes_tx": 523456789, - "replay_num_evals": 478932, - "peak_sensors": 4 - } - } + "total": 550000 } ``` -**Field Definitions:** -- `sensor_events`: Total events ingested from sensors -- `output_bytes_tx`: Data transmitted to outputs (in bytes) -- `replay_num_evals`: D&R rule evaluations performed -- `peak_sensors`: Maximum concurrent sensors online - -**Critical Notes:** -- API returns ~90 days of data -- **MUST filter to requested time range** - don't use all 90 days -- Dates are in YYYY-MM-DD format -- `output_bytes_tx` is in BYTES - convert to GB: divide by 1,073,741,824 -- ALWAYS show both: "450 GB (483,183,820,800 bytes)" +### Step 3: Fetch Invoice URL (Optional) -**Aggregation Rules:** ``` -For time range Nov 1-30, 2025: - 1. Filter usage dict to only dates in range - 2. Sum daily values: - total_events = sum(usage[date]['sensor_events'] for date in range) - 3. Document calculation: - "Total Events: 1,250,432,100 - Calculation: Sum of daily sensor_events from Nov 1-30, 2025 - Source: get-usage-stats" -``` - -### 4. Billing Details -**Function**: `get-billing-details` -- **Endpoint**: GET /orgs/{oid}/details (billing endpoint) -- **OID Required**: YES -- **Permissions**: Requires admin/owner role -- **Returns**: Subscription info, payment status, billing contact -- **Data Freshness**: Updated on changes, ~1hr delay - -**Response Structure:** -```json -{ - "plan": "enterprise", - "status": "active", - "billing_email": "billing@example.com", - "payment_method": "card", - "last_four": "4242", - "next_billing_date": 1672531200, - "auto_renew": true -} +tool: get_org_invoice_url +parameters: + oid: [uuid] + year: [year] + month: [month] ``` -**Common Error**: 403 Forbidden (insufficient permissions) -- Expected for user-role accounts -- Document in failures section -- Continue with partial data +Returns PDF download URL. -### 5. Invoice URLs -**Function**: `get-org-invoice-url` -- **Endpoint**: GET /orgs/{oid}/invoice -- **OID Required**: YES -- **Returns**: Direct URL to organization's invoice -- **Usage**: For actual billing amounts (no cost calculations in reports) +### Step 4: Transform Data -**Response Structure:** -```json -{ - "url": "https://billing.limacharlie.io/invoice/..." -} -``` - -**Usage in Reports:** -``` -For billing details and charges: -β†’ View Invoice: https://billing.limacharlie.io/invoice/... -``` - -### 6. Sensor Inventory -**Function**: `list-sensors` -- **Endpoint**: GET /v1/sensors/{oid} -- **OID Required**: YES -- **Returns**: All sensors with metadata -- **Data Freshness**: Real-time snapshot -- **Large Result Handling**: May return `resource_link` if >100KB +1. **Convert amounts from cents to dollars**: `amount / 100` +2. **Map to output schema**: -**Response Structure (normal):** ```json { - "sensors": { - "sensor-id-1": { - "sid": "sensor-id-1", - "hostname": "SERVER01", - "plat": 268435456, - "arch": 1, - "enroll": "2024-01-15T10:30:00Z", - "alive": "2024-11-20 14:22:13", - "int_ip": "10.0.1.50", - "ext_ip": "203.0.113.45", - "oid": "c7e8f940-..." - } + "metadata": { + "generated_at": "2025-12-09T10:30:00Z", + "period": "November 2025", + "scope": "single" + }, + "data": { + "tenants": [ + { + "name": "Acme Corp", + "oid": "c7e8f940-...", + "status": "success", + "cost": 5500.00, + "currency": "usd", + "skus": [ + { "description": "Enterprise Plan", "amount": 4000.00, "quantity": 1 }, + { "description": "Sensors (2500)", "amount": 1500.00, "quantity": 2500 } + ], + "invoice_url": "https://..." + } + ] }, - "continuation_token": "" + "warnings": [], + "errors": [] } ``` -**Large Result Handling:** -If API returns `resource_link` instead of inline data: +### Step 5: Output Report -```json -{ - "success": true, - "resource_link": "https://storage.googleapis.com/...", - "resource_size": 234329, - "reason": "results too large, see resource_link" -} -``` +Format as JSON or Markdown based on user preference. -**REQUIRED Workflow for resource_link:** -```bash -# Step 1: Download and analyze schema (MANDATORY - do not skip) -bash ./marketplace/plugins/lc-essentials/scripts/analyze-lc-result.sh "https://storage.googleapis.com/..." +--- -# Output shows schema and file path: -# (stdout) {"sensors": {"sensor-id": {"sid": "string", "hostname": "string", ...}}} -# (stderr) ---FILE_PATH--- -# (stderr) /tmp/lc-result-1234567890.json +## Workflow: All Organizations -# Step 2: Review schema output BEFORE writing jq queries +When `scope=all` or user asks for "all orgs": -# Step 3: Extract only needed fields with jq -jq '.sensors | length' /tmp/lc-result-1234567890.json # Count -jq '.sensors | to_entries | .[].value.hostname' /tmp/lc-result-1234567890.json | head -20 # Sample hostnames +### Step 1: Get Organization List -# Step 4: Clean up -rm /tmp/lc-result-1234567890.json ``` - -**NEVER:** -- Assume JSON structure without analyzing schema -- Write jq queries before seeing schema output -- Skip the analyze-lc-result.sh step -- Load entire large file into context - -**Field Validation - CRITICAL:** - -**CORRECT Fields to Use:** -- βœ… `alive`: "2025-11-20 14:22:13" (datetime string for last seen) -- βœ… `plat`: Platform code (int or string) -- βœ… `hostname`: Sensor hostname -- βœ… `sid`: Sensor ID -- βœ… `int_ip`: Internal IP address -- βœ… `ext_ip`: External IP address - -**INCORRECT Fields (Common Mistakes):** -- ❌ `last_seen`: Often 0 or missing - DO NOT USE -- ❌ Use `alive` field instead for offline detection - -**Offline Sensor Detection:** -```python -# Parse alive field (datetime string format: "YYYY-MM-DD HH:MM:SS") -from datetime import datetime, timezone - -alive_str = sensor_info.get('alive', '') -if alive_str: - # Parse: "2025-10-01 17:08:10" - alive_dt = datetime.strptime(alive_str, '%Y-%m-%d %H:%M:%S') - alive_dt = alive_dt.replace(tzinfo=timezone.utc) - last_seen_timestamp = alive_dt.timestamp() - - hours_offline = (current_time - last_seen_timestamp) / 3600 - - # Categorize with explicit thresholds: - if hours_offline < 24: - category = "Recently offline (< 24 hours)" - elif hours_offline < 168: # 7 days - category = "Offline short term (1-7 days)" - elif hours_offline < 720: # 30 days - category = "Offline medium term (7-30 days)" - else: - category = "Offline long term (30+ days)" +tool: list_user_orgs +parameters: {} ``` -**Platform Code Translation:** +### Step 2: Spawn Parallel Collectors -Traditional OS platforms (strings): -- "windows" β†’ Windows -- "linux" β†’ Linux -- "macos" β†’ macOS -- "chrome" β†’ Chrome OS +For EACH organization, spawn a `billing-collector` agent IN PARALLEL using a single message with multiple Task calls: -Numeric platform codes (extensions/adapters): -- Use two-pass pattern analysis -- Collect hostname samples -- Match patterns: ext-, test-, slack-, office365- -- ALWAYS show sample hostnames in report - -**Example:** -``` -Platform: LimaCharlie Extensions (code: 2415919104) -Sample hostnames: ext-strelka-01, ext-hayabusa-02, ext-secureannex-01 -Sensor count: 30 ``` +Task(subagent_type="lc-essentials:billing-collector", prompt=" + Collect billing data for org 'Acme Corp' (OID: uuid-1) + Year: 2025, Month: 11 + Return JSON matching billing-report template schema +") -### 7. Online Sensors -**Function**: `get-online-sensors` -- **Endpoint**: GET /v1/sensors/online/{oid} -- **OID Required**: YES -- **Returns**: List of currently online sensor IDs -- **Data Freshness**: Real-time +Task(subagent_type="lc-essentials:billing-collector", prompt=" + Collect billing data for org 'Beta Inc' (OID: uuid-2) + Year: 2025, Month: 11 + Return JSON matching billing-report template schema +") -**Response Structure:** -```json -{ - "sensors": [ - "sensor-id-1", - "sensor-id-2", - "sensor-id-3" - ] -} +// ... all orgs in ONE message for parallel execution ``` -**Usage:** -```python -# Convert to set for O(1) lookup -online_sids = set(response['sensors']) +### Step 3: Aggregate Results -# Check if sensor is online -is_online = sensor_id in online_sids +Collect JSON responses from all agents: -# Calculate offline count -total_sensors = 2500 -online_count = len(online_sids) -offline_count = total_sensors - online_count -``` +1. **Combine tenant data** into `data.tenants` array +2. **Separate errors** into `errors` array +3. **Calculate rollup**: + - `total_cost`: Sum of all successful tenant costs + - `total_tenants`: Count of all tenants + - `successful_count`: Tenants with status=success + - `failed_count`: Tenants with status=error or no_invoice + +### Step 4: Output Report -### 8. Historic Detections -**Function**: `get-historic-detections` -- **Endpoint**: GET /v1/insight/{oid}/detections -- **OID Required**: YES -- **Returns**: Security detections within time range -- **Data Freshness**: Near real-time (5-minute delay typical) -- **Default Limit**: 5,000 per query - -**Query Parameters:** -- `start`: Unix epoch timestamp (seconds) -- `end`: Unix epoch timestamp (seconds) -- `limit`: Maximum detections to retrieve (default: 5000) -- `sid`: Filter by sensor ID (optional) -- `cat`: Filter by category (optional) - -**Response Structure:** +**JSON Output:** ```json { - "detects": [ + "metadata": { + "generated_at": "2025-12-09T10:30:00Z", + "period": "November 2025", + "scope": "all", + "tenant_count": 5 + }, + "data": { + "tenants": [...], + "rollup": { + "total_cost": 25500.00, + "total_tenants": 5, + "successful_count": 4, + "failed_count": 1 + } + }, + "warnings": [], + "errors": [ { - "detect_id": "detect-uuid-123", - "cat": "suspicious_process", - "source_rule": "general.encoded-powershell", - "namespace": "general", - "ts": 1732108934567, - "sid": "sensor-xyz-123", - "detect": { - "event": { - "TIMESTAMP": 1732108934567, - "COMMAND_LINE": "powershell.exe -encodedCommand ...", - "FILE_PATH": "C:\\Windows\\System32\\..." - }, - "routing": { - "sid": "sensor-xyz-123", - "hostname": "SERVER01" - } - } + "org_name": "Failed Corp", + "oid": "...", + "error_message": "403 Forbidden - Insufficient permissions" } - ], - "next_cursor": "" -} -``` - -**Field Validation - CRITICAL:** - -**CORRECT Fields:** -- βœ… `source_rule`: "namespace.rule-name" (actual rule identifier) -- βœ… `cat`: Category name -- βœ… `ts`: Timestamp (MAY be seconds or milliseconds - normalize!) -- βœ… `sid`: Sensor ID (may be "N/A" for some detections) -- βœ… `detect_id`: Unique detection identifier - -**INCORRECT Fields (Common Mistakes):** -- ❌ `rule_name`: Doesn't exist - use `source_rule` instead -- ❌ `severity`: NOT in detection records (only in D&R rule config) - -**Timestamp Normalization (MANDATORY):** -```python -ts = detection.get('ts', 0) - -# Check magnitude to determine units -if ts > 10000000000: - # Milliseconds - convert to seconds - ts = ts / 1000 - -# Sanity check result -if ts < 1577836800: # Before 2020-01-01 - # Invalid timestamp - display = "Invalid timestamp" -elif ts > time.time() + 86400: # More than 1 day in future - # Invalid timestamp - display = "Invalid timestamp" -else: - # Valid - format for display - display = datetime.fromtimestamp(ts, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC') -``` - -**Detection Limit Tracking (MANDATORY):** -```python -retrieved_count = 0 -for detection in detections: - retrieved_count += 1 - -limit_reached = (retrieved_count >= query_limit) - -if limit_reached: - # MUST display prominent warning - warning = f""" - ⚠️ DETECTION LIMIT REACHED - Retrieved: {retrieved_count:,} detections - Actual count: May be significantly higher - - This organization has more detections than retrieved. - For complete data: - - Narrow time range (currently: {days} days) - - Query specific date ranges separately - - Filter by category or sensor - """ -``` - -### 9. D&R Rules Inventory -**Function**: `list-dr-general-rules` -- **Endpoint**: GET /v1/rules/{oid}?namespace=general -- **OID Required**: YES -- **Returns**: Custom D&R rules in general namespace -- **Data Freshness**: Real-time - -**Response Structure:** -```json -{ - "custom-rule-1": { - "name": "custom-rule-1", - "namespace": "general", - "detect": { - "event": "NEW_PROCESS", - "op": "contains", - "path": "event/COMMAND_LINE", - "value": "powershell" - }, - "respond": [ - { - "action": "report", - "name": "suspicious_powershell" - } - ], - "is_enabled": true - } + ] } ``` -**Usage:** -- Count total rules: Object.keys(response).length -- Count enabled: Filter where is_enabled === true -- List detection types: Extract event types from detect blocks - -### 10. Outputs Configuration -**Function**: `list-outputs` -- **Endpoint**: GET /v1/outputs/{oid} -- **OID Required**: YES -- **Returns**: Configured data outputs (SIEM, storage, webhooks) -- **Data Freshness**: Real-time - -**Usage in Reports:** -- Count total outputs -- List destination types -- Note any disabled outputs - -## Workflow Patterns - -### Architecture Overview - -This skill uses a **parallel subagent architecture** for efficient multi-tenant data collection: - -``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ reporting (this skill) β”‚ -β”‚ β”œβ”€ Phase 1: Discovery (list orgs via limacharlie-call) β”‚ -β”‚ β”œβ”€ Phase 2: Time range validation β”‚ -β”‚ β”œβ”€ Phase 3: Spawn parallel agents ────────────────────┐ β”‚ -β”‚ β”œβ”€ Phase 4: Aggregate results β”‚ β”‚ -β”‚ └─ Phase 5: Generate report β”‚ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”˜ - β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β”‚ Spawns ONE agent per organization (in parallel) - β”‚ - β–Ό -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚org- β”‚ β”‚org- β”‚ β”‚org- β”‚ β”‚org- β”‚ -β”‚reporter β”‚ β”‚reporter β”‚ β”‚reporter β”‚ β”‚reporter β”‚ -β”‚ Org 1 β”‚ β”‚ Org 2 β”‚ β”‚ Org 3 β”‚ β”‚ Org N β”‚ -β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ - β”‚ β”‚ β”‚ β”‚ - β”‚ Each agent collects ALL data for its org: - β”‚ - org info, usage, billing, sensors, - β”‚ - detections, rules, outputs - β”‚ β”‚ β”‚ β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β–Ό - Structured JSON results - returned to parent skill -``` - -**Benefits of this architecture:** -- True parallelism across organizations -- Each agent handles its own error recovery -- Reduced context usage in main skill -- Scalable to 50+ organizations +**Markdown Output:** +```markdown +## Billing Report - November 2025 -### Pattern 1: Multi-Tenant MSSP Comprehensive Report +**Generated:** 2025-12-09 10:30:00 UTC +**Scope:** All Organizations (5 total) -**User Request Examples:** -- "Generate monthly MSSP report for all my customers" -- "Show me comprehensive overview across all organizations" -- "Create security and billing summary for November 2025" +### Summary -**Step-by-Step Execution:** +| Metric | Value | +|--------|-------| +| Total Cost | $25,500.00 | +| Successful | 4 | +| Failed | 1 | -``` -β”Œβ”€ PHASE 1: DISCOVERY ──────────────────────────────┐ -β”‚ 1. Use limacharlie-call skill to get org list: β”‚ -β”‚ Function: list-user-orgs (no OID required) β”‚ -β”‚ β”‚ -β”‚ 2. Validation: β”‚ -β”‚ βœ“ Check orgs array exists and not empty β”‚ -β”‚ βœ“ Validate each OID is UUID format β”‚ -β”‚ βœ“ Count total organizations β”‚ -β”‚ β”‚ -β”‚ 3. User Confirmation (if >20 orgs): β”‚ -β”‚ "Found 50 organizations. Generate report for β”‚ -β”‚ all 50? This may take a few minutes." β”‚ -β”‚ β”‚ -β”‚ Options to present: β”‚ -β”‚ - Yes, process all 50 β”‚ -β”‚ - No, let me filter first β”‚ -β”‚ - Show me the organization list β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - -β”Œβ”€ PHASE 2: TIME RANGE - ASK USER & CALCULATE ──────┐ -β”‚ β”‚ -β”‚ 4. ⚠️ MANDATORY: Ask user for time range: β”‚ -β”‚ β”‚ -β”‚ Use AskUserQuestion tool: β”‚ -β”‚ - "Last 24 hours" β”‚ -β”‚ - "Last 7 days" β”‚ -β”‚ - "Last 30 days" β”‚ -β”‚ - "Custom range" β”‚ -β”‚ β”‚ -β”‚ If "Custom range", ask for specific dates. β”‚ -β”‚ NEVER assume or default without asking! β”‚ -β”‚ β”‚ -β”‚ 5. ⚠️ CRITICAL: Calculate timestamps dynamically: β”‚ -β”‚ β”‚ -β”‚ ```bash β”‚ -β”‚ NOW=$(date +%s) β”‚ -β”‚ # Based on user selection: β”‚ -β”‚ # 24h: START=$((NOW - 86400)) β”‚ -β”‚ # 7d: START=$((NOW - 604800)) β”‚ -β”‚ # 30d: START=$((NOW - 2592000)) β”‚ -β”‚ END=$NOW β”‚ -β”‚ ``` β”‚ -β”‚ β”‚ -β”‚ NEVER use hardcoded epoch values! β”‚ -β”‚ Stale timestamps = NO DATA returned! β”‚ -β”‚ β”‚ -β”‚ 6. Validation Checks: β”‚ -β”‚ βœ“ Start timestamp < End timestamp β”‚ -β”‚ βœ“ End timestamp <= Current time β”‚ -β”‚ βœ“ Range is reasonable (<= 90 days) β”‚ -β”‚ βœ“ Timestamps are Unix epoch in SECONDS β”‚ -β”‚ β”‚ -β”‚ 7. Display for user confirmation: β”‚ -β”‚ "Time Range: β”‚ -β”‚ - Start: [calculated date] UTC β”‚ -β”‚ - End: [calculated date] UTC β”‚ -β”‚ - Duration: X days β”‚ -β”‚ - Unix: [start_epoch] to [end_epoch]" β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - -β”Œβ”€ PHASE 3: SPAWN PARALLEL AGENTS ──────────────────┐ -β”‚ 7. Spawn org-reporter agents IN PARALLEL: β”‚ -β”‚ β”‚ -β”‚ CRITICAL: Send ALL Task calls in a SINGLE β”‚ -β”‚ message to achieve true parallelism: β”‚ -β”‚ β”‚ -β”‚ Task( β”‚ -β”‚ subagent_type="lc-essentials:org-reporter", -β”‚ model="haiku", β”‚ -β”‚ prompt="Collect reporting data for org β”‚ -β”‚ 'Client ABC' (OID: uuid-1) β”‚ -β”‚ Time Range: β”‚ -β”‚ - Start: 1730419200 β”‚ -β”‚ - End: 1733011199 β”‚ -β”‚ Detection Limit: 5000" β”‚ -β”‚ ) β”‚ -β”‚ Task( β”‚ -β”‚ subagent_type="lc-essentials:org-reporter", -β”‚ model="haiku", β”‚ -β”‚ prompt="Collect reporting data for org β”‚ -β”‚ 'Client XYZ' (OID: uuid-2)..." β”‚ -β”‚ ) β”‚ -β”‚ ... (one Task per organization) β”‚ -β”‚ β”‚ -β”‚ 8. Each agent returns structured JSON: β”‚ -β”‚ { β”‚ -β”‚ "org_name": "...", β”‚ -β”‚ "oid": "...", β”‚ -β”‚ "status": "success|partial|failed", β”‚ -β”‚ "data": { usage, billing, sensors, ... }, β”‚ -β”‚ "errors": [...], β”‚ -β”‚ "warnings": [...] β”‚ -β”‚ } β”‚ -β”‚ β”‚ -β”‚ 9. Wait for all agents to complete β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - -β”Œβ”€ PHASE 4: AGGREGATE RESULTS ──────────────────────┐ -β”‚ 10. Categorize agent results: β”‚ -β”‚ success_orgs = [] (status == "success") β”‚ -β”‚ partial_orgs = [] (status == "partial") β”‚ -β”‚ failed_orgs = [] (status == "failed") β”‚ -β”‚ β”‚ -β”‚ 11. Multi-Org Aggregation: β”‚ -β”‚ Aggregate across SUCCESS + PARTIAL orgs: β”‚ -β”‚ - Sum total_events (from usage) β”‚ -β”‚ - Sum total_output_bytes (convert to GB) β”‚ -β”‚ - Sum total_evaluations β”‚ -β”‚ - Sum peak_sensors β”‚ -β”‚ - Count total sensors β”‚ -β”‚ - Count total detections (track limits) β”‚ -β”‚ β”‚ -β”‚ Document for each aggregate: β”‚ -β”‚ - Formula used β”‚ -β”‚ - Orgs included count β”‚ -β”‚ - Orgs excluded (and why) β”‚ -β”‚ - Time range covered β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - -β”Œβ”€ PHASE 5: REPORT GENERATION ──────────────────────┐ -β”‚ 17. Report Structure: β”‚ -β”‚ β”‚ -β”‚ A. HEADER (mandatory metadata) β”‚ -β”‚ ═══════════════════════════════════════ β”‚ -β”‚ MSSP Comprehensive Report β”‚ -β”‚ Generated: 2025-11-20 14:45:30 UTC β”‚ -β”‚ Time Window: Nov 1-30, 2025 (30 days) β”‚ -β”‚ Organizations: 45 of 50 successful β”‚ -β”‚ ═══════════════════════════════════════ β”‚ -β”‚ β”‚ -β”‚ B. EXECUTIVE SUMMARY β”‚ -β”‚ - High-level metrics (successful orgs) β”‚ -β”‚ - Critical warnings and alerts β”‚ -β”‚ - Failed organization count β”‚ -β”‚ - Detection limit warnings β”‚ -β”‚ β”‚ -β”‚ C. AGGREGATE METRICS β”‚ -β”‚ Total Across 45 Organizations β”‚ -β”‚ (Excluded: 5 orgs - see failures section) β”‚ -β”‚ β”‚ -β”‚ - Total Sensor Events: 1,250,432,100 β”‚ -β”‚ Calculation: Sum of daily sensor_events β”‚ -β”‚ from Nov 1-30, 2025 β”‚ -β”‚ β”‚ -β”‚ - Total Data Output: 3,847 GB β”‚ -β”‚ (4,128,394,752,000 bytes) β”‚ -β”‚ Calculation: Sum of daily output_bytes_txβ”‚ -β”‚ Γ· 1,073,741,824 β”‚ -β”‚ β”‚ -β”‚ - Peak Sensors: 12,450 β”‚ -β”‚ Calculation: Sum of max peak_sensors β”‚ -β”‚ β”‚ -β”‚ D. PER-ORGANIZATION DETAILS β”‚ -β”‚ For each successful organization: β”‚ -β”‚ β”‚ -β”‚ ── Client ABC ───────────────────────── β”‚ -β”‚ OID: c7e8f940-1234-5678-abcd-... β”‚ -β”‚ Data Retrieved: 2025-11-20 14:45:35 UTC β”‚ -β”‚ β”‚ -β”‚ Usage Statistics (Nov 1-30, 2025): β”‚ -β”‚ - Sensor Events: 42,150,000 β”‚ -β”‚ - Data Output: 125 GB (134,217,728,000 B)β”‚ -β”‚ - D&R Evaluations: 1,200,450 β”‚ -β”‚ - Peak Sensors: 250 β”‚ -β”‚ β”‚ -β”‚ Sensor Inventory: β”‚ -β”‚ - Total Sensors: 250 β”‚ -β”‚ - Online: 245 (98%) β”‚ -β”‚ - Offline: 5 (2%) β”‚ -β”‚ - Platforms: Windows (150), Linux (100) β”‚ -β”‚ β”‚ -β”‚ Detection Summary: β”‚ -β”‚ Retrieved: 5,000 detections β”‚ -β”‚ ⚠️ LIMIT REACHED - actual count higher β”‚ -β”‚ Top Categories: β”‚ -β”‚ - suspicious_process: 1,250 β”‚ -β”‚ - network_threat: 890 β”‚ -β”‚ - malware: 450 β”‚ -β”‚ β”‚ -β”‚ Billing Status: β”‚ -β”‚ - Plan: Enterprise β”‚ -β”‚ - Status: Active βœ“ β”‚ -β”‚ - Next Billing: Dec 1, 2025 β”‚ -β”‚ - Invoice: [URL] β”‚ -β”‚ β”‚ -β”‚ E. FAILED ORGANIZATIONS SECTION β”‚ -β”‚ ⚠️ FAILED ORGANIZATIONS (5 of 50) β”‚ -β”‚ β”‚ -β”‚ Client XYZ (oid: c7e8f940-...) β”‚ -β”‚ Status: ❌ Failed β”‚ -β”‚ Error: 403 Forbidden β”‚ -β”‚ Endpoint: get-billing-details β”‚ -β”‚ Reason: Insufficient billing permissions β”‚ -β”‚ Impact: Billing data unavailable β”‚ -β”‚ Available: Usage stats, sensor inventory β”‚ -β”‚ Action: Grant billing:read permission β”‚ -β”‚ Timestamp: 2025-11-20 14:32:15 UTC β”‚ -β”‚ β”‚ -β”‚ F. DETECTION LIMIT WARNINGS β”‚ -β”‚ ⚠️ Organizations at Detection Limit: β”‚ -β”‚ β”‚ -β”‚ 15 of 45 organizations exceeded the 5,000 β”‚ -β”‚ detection limit. Actual counts are higher. β”‚ -β”‚ β”‚ -β”‚ Organizations affected: β”‚ -β”‚ - Client A: 5,000 retrieved ⚠️ β”‚ -β”‚ - Client B: 5,000 retrieved ⚠️ β”‚ -β”‚ - [... 13 more] β”‚ -β”‚ β”‚ -β”‚ Recommendation: For complete detection β”‚ -β”‚ data, narrow time ranges or query specific β”‚ -β”‚ date ranges for these organizations. β”‚ -β”‚ β”‚ -β”‚ G. METHODOLOGY SECTION β”‚ -β”‚ Data Sources: β”‚ -β”‚ - list-user-orgs: Organization discovery β”‚ -β”‚ - get-usage-stats: Daily metrics β”‚ -β”‚ - get-billing-details: Subscription info β”‚ -β”‚ - list-sensors: Endpoint inventory β”‚ -β”‚ - get-online-sensors: Real-time status β”‚ -β”‚ - get-historic-detections: Security data β”‚ -β”‚ - list-dr-general-rules: Custom rules β”‚ -β”‚ β”‚ -β”‚ Query Parameters: β”‚ -β”‚ - Detection limit: 5,000 per org β”‚ -β”‚ - Time range: Nov 1-30, 2025 β”‚ -β”‚ - Date filtering: Applied to usage stats β”‚ -β”‚ β”‚ -β”‚ Calculations: β”‚ -β”‚ - Bytes to GB: value Γ· 1,073,741,824 β”‚ -β”‚ - Aggregations: Sum across successful β”‚ -β”‚ organizations only β”‚ -β”‚ - Timestamps: Normalized from mixed β”‚ -β”‚ seconds/milliseconds format β”‚ -β”‚ β”‚ -β”‚ Data Freshness: β”‚ -β”‚ - Usage stats: Daily (24hr delay) β”‚ -β”‚ - Detections: Near real-time (5min) β”‚ -β”‚ - Sensor status: Real-time β”‚ -β”‚ - Billing: Updated on changes (~1hr) β”‚ -β”‚ β”‚ -β”‚ H. FOOTER β”‚ -β”‚ ═══════════════════════════════════════ β”‚ -β”‚ Report completed: 2025-11-20 14:50:15 UTC β”‚ -β”‚ Execution time: 4 minutes 45 seconds β”‚ -β”‚ β”‚ -β”‚ For questions or issues: β”‚ -β”‚ Contact: support@limacharlie.io β”‚ -β”‚ β”‚ -β”‚ Disclaimer: Usage metrics shown are from β”‚ -β”‚ LimaCharlie APIs. For billing and pricing, β”‚ -β”‚ refer to individual organization invoices. β”‚ -β”‚ ═══════════════════════════════════════ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - -Progress Reporting During Execution: - Display progress as orgs are processed: - - "Generating MSSP Report for 50 Organizations... - - [1/50] Client ABC... βœ“ Success (2.3s) - [2/50] Client XYZ... βœ“ Success (1.8s) - [3/50] Client PDQ... ⚠️ Billing permission denied - [4/50] Client RST... βœ“ Success (2.1s) - [5/50] Client MNO... ❌ Failed: 500 Server Error - ... - [50/50] Client ZZZ... βœ“ Success (1.9s) - - Collection Complete: - βœ“ Successful: 45 organizations - ⚠️ Partial: 2 organizations (some data unavailable) - ❌ Failed: 3 organizations - - Generating report structure..." -``` - -### Pattern 2: Billing-Focused Summary Report - -**User Request Examples:** -- "Billing summary for all customers this month" -- "Show me subscription status across organizations" -- "Usage report for invoicing" - -**Workflow:** -``` -1. Use limacharlie-call skill: list-user-orgs - -2. Spawn org-reporter agents in parallel (same as Pattern 1) - - Agents collect ALL data (billing focus is in report generation) - -3. Aggregate results focusing on billing data: - - Extract only: usage, billing, invoice_url from each agent result - - Skip: detections, rules, detailed sensor data - -4. Generate Billing-Focused Report: - - Usage metrics per org (NO cost calculations) - - Subscription status - - Invoice links - - Billing permission issues flagged -``` - -### Pattern 3: Single Organization Deep Dive - -**User Request Examples:** -- "Detailed report for Client ABC" -- "Complete security analysis for organization XYZ" -- "Show me everything for [org name]" - -**Workflow:** -``` -1. Use limacharlie-call skill: list-user-orgs - - Filter to find OID for the specified org name - -2. Spawn ONE org-reporter agent for that organization: - Task( - subagent_type="lc-essentials:org-reporter", - model="haiku", - prompt="Collect reporting data for org 'Client ABC' (OID: uuid) - Time Range: [start] to [end] - Detection Limit: 5000" - ) - -3. Generate Detailed Single-Org Report: - - Full usage breakdown - - Complete sensor inventory with platforms - - Detection breakdown by category - - All D&R rules listed - - Output configurations - - Any errors or warnings from collection -``` - -## Validation Checkpoints - -AI must validate at these critical points: - -### Before Spawning Agents -``` -βœ“ Organization list retrieved successfully -βœ“ Each OID is valid UUID format (not org name) -βœ“ USER WAS ASKED to confirm time range (MANDATORY) -βœ“ Timestamps calculated DYNAMICALLY using $(date +%s) -βœ“ Timestamps are reasonable (start < end, not future) -βœ“ Limit values are positive integers -βœ“ Date ranges <= 90 days (warn if larger) -βœ“ User confirmed processing for large org counts (>20) -βœ“ Time range displayed to user before proceeding -``` - -### After Agent Results -``` -βœ“ Each agent returned valid JSON structure -βœ“ Check agent status: "success", "partial", or "failed" -βœ“ Required data fields present in successful results -βœ“ Errors array populated for any failures -βœ“ Warnings array checked for detection limits, etc. -``` - -### Before Calculations -``` -βœ“ Data types correct (numbers are numbers) -βœ“ Values within expected ranges -βœ“ No division by zero -βœ“ No negative values where impossible -βœ“ Timestamps pass sanity checks -``` - -### Before Presenting Data -``` -βœ“ All numbers formatted with thousand separators -βœ“ Units clearly labeled (GB, bytes, count, etc.) -βœ“ Timestamps in consistent format (UTC) -βœ“ Warnings included where required -βœ“ Metadata sections complete -``` +### Per-Tenant Breakdown -## Common Mistakes to Avoid +| Tenant | Status | Cost | Line Items | +|--------|--------|------|------------| +| Acme Corp | success | $5,500.00 | 2 | +| Beta Inc | success | $8,000.00 | 3 | +| Gamma LLC | success | $7,000.00 | 2 | +| Delta Co | success | $5,000.00 | 2 | +| Failed Corp | error | - | - | -Based on previous implementation learnings: +### Errors -### ❌ Detection Data Mistakes +| Tenant | Error | +|--------|-------| +| Failed Corp | 403 Forbidden - Insufficient permissions | ``` -WRONG: detection['rule_name'] -RIGHT: detection.get('source_rule', detection.get('cat', 'unknown')) -WRONG: detection['severity'] -RIGHT: Severity not in detection records - only in D&R rule config -``` - -### ❌ Timestamp Mistakes -``` -WRONG: Using hardcoded epoch values from documentation examples -RIGHT: ALWAYS calculate dynamically: NOW=$(date +%s); START=$((NOW - 604800)) - -WRONG: Assuming a default time range without asking user -RIGHT: Use AskUserQuestion to confirm time range before querying - -WRONG: Using timestamps from skill examples (e.g., 1730419200) -RIGHT: Calculate current time and subtract: $(($(date +%s) - 86400)) - -WRONG: Assuming all timestamps are seconds -RIGHT: Check magnitude, normalize if > 10000000000 (milliseconds) - -WRONG: Ignoring invalid timestamps -RIGHT: Validate range (2020-01-01 to now+1day), flag invalid - -WRONG: Not confirming calculated timestamps with user -RIGHT: Display "Time range: [date] to [date]" before running queries -``` - -### ❌ Sensor Status Mistakes -``` -WRONG: last_seen field -RIGHT: alive field (datetime string format) - -WRONG: Estimating offline duration -RIGHT: Parse alive timestamp, calculate exact hours/days -``` - -### ❌ Platform Mistakes -``` -WRONG: Showing raw numeric codes without context -RIGHT: Pattern analysis + sample hostnames - -Example: - WRONG: "Platform: 2415919104 (30 sensors)" - RIGHT: "Platform: LimaCharlie Extensions (code: 2415919104) - Sample hostnames: ext-strelka-01, ext-hayabusa-02 - Sensor count: 30" -``` - -### ❌ Usage Stats Mistakes -``` -WRONG: Using all 90 days from API without filtering -RIGHT: Filter to requested time range only - -WRONG: Showing only GB -RIGHT: Show both - "450 GB (483,183,820,800 bytes)" - -WRONG: Calculating costs -RIGHT: Show usage only, link to invoice for costs -``` - -### ❌ Multi-Org Aggregation Mistakes -``` -WRONG: Including failed orgs in totals -RIGHT: Sum successful orgs only, document exclusions - -WRONG: "Total detections: 75,000" -RIGHT: "Total retrieved: 75,000 (15 orgs hit 5K limit - actual higher)" -``` - -## Error Handling Reference - -### Error Severity Levels - -**CRITICAL** (Stop entire report): -- User authentication failure -- list-user-orgs fails (can't discover orgs) -- No organizations accessible +--- -**HIGH** (Skip org, document prominently): -- 403 Forbidden on critical endpoints -- 500 Internal Server Error (after retry) -- Invalid OID format +## Data Integrity Rules -**MEDIUM** (Use fallback, note in report): -- Sensor list unavailable -- Detection query timeout -- Empty result sets +### NEVER Fabricate Data -**LOW** (Note in methodology): -- resource_link handling (normal for large results) -- Pagination required -- Data freshness >24hr +- Only report data returned by APIs +- Missing data = `null` or `"N/A"` +- Failed calls = documented in `errors` array +- NEVER estimate, infer, or guess values -### Error Response Template +### Currency Conversion -```json -{ - "org_oid": "c7e8f940-...", - "org_name": "Client ABC", - "endpoint": "get-billing-details", - "error_type": "403 Forbidden", - "error_message": "Insufficient permissions", - "timestamp": "2025-11-20T14:45:30Z", - "impact": "Billing details unavailable", - "remediation": "Grant billing:read permission", - "severity": "HIGH", - "retry_attempted": false, - "partial_data_available": true, - "partial_data_sections": ["usage", "sensors", "detections"] -} -``` +API returns amounts in **cents**. Always convert: -## Report Quality Checklist - -Before presenting any report, verify: - -### βœ“ Header Completeness -- [ ] Generation timestamp (UTC) -- [ ] Time window start/end (UTC) -- [ ] Duration in days -- [ ] Success/failure org counts - -### βœ“ Data Accuracy -- [ ] All timestamps normalized -- [ ] Units clearly labeled -- [ ] No cost calculations -- [ ] Detection limits flagged -- [ ] Aggregations documented - -### βœ“ Error Transparency -- [ ] Failed orgs listed with details -- [ ] Error codes and messages shown -- [ ] Impact statements clear -- [ ] Remediation actions provided - -### βœ“ Methodology Documentation -- [ ] API endpoints listed -- [ ] Query parameters documented -- [ ] Calculation formulas shown -- [ ] Data freshness stated -- [ ] Exclusions explained - -### βœ“ Warnings Present -- [ ] Detection limit warnings -- [ ] Partial data notices -- [ ] Permission issues flagged -- [ ] Data freshness alerts - -## Example Output Snippets - -### Executive Summary Example ``` -═══════════════════════════════════════════════════════════ -MSSP Comprehensive Report - November 2025 - -Generated: 2025-11-20 14:45:30 UTC -Time Window: 2025-11-01 00:00:00 UTC to 2025-11-30 23:59:59 UTC -Duration: 30 days -Organizations Processed: 45 of 50 (90% success rate) -═══════════════════════════════════════════════════════════ - -EXECUTIVE SUMMARY - -Fleet Overview: - β€’ Total Sensors: 12,450 (across 45 successful organizations) - β€’ Online: 11,823 (95%) - β€’ Offline: 627 (5%) - -Security Activity: - β€’ Detections Retrieved: 127,450 - β€’ ⚠️ 15 organizations hit detection limit (actual counts higher) - β€’ Top Categories: suspicious_process (32%), network_threat (28%) - -Usage Metrics (45 organizations): - β€’ Total Events: 1,250,432,100 - β€’ Data Output: 3,847 GB - β€’ D&R Evaluations: 45,230,890 - -Issues Requiring Attention: - ⚠️ 5 organizations failed (see Failures section) - ⚠️ 15 organizations exceeded detection limits - ⚠️ 627 sensors offline (5% of fleet) +amount_dollars = amount_cents / 100 ``` -### Failed Organization Example -``` -⚠️ FAILED ORGANIZATIONS (5 of 50) - -───────────────────────────────────────────────────────── -Organization: Client XYZ Security Operations -OID: c7e8f940-aaaa-bbbb-cccc-ddddeeeeffffggg -Status: ❌ Partial Failure -───────────────────────────────────────────────────────── - -Failed Endpoint: get-billing-details - Error Code: 403 Forbidden - Error Message: Insufficient permissions to access billing data - Timestamp: 2025-11-20 14:32:15 UTC - -Impact: - βœ— Billing details unavailable - βœ— Subscription status unknown - βœ— Invoice link unavailable - βœ— Next billing date unknown - -Available Data: - βœ“ Organization metadata - βœ“ Usage statistics (Nov 1-30, 2025) - βœ“ Sensor inventory (125 sensors) - βœ“ Detection summary (1,847 detections) - βœ“ D&R rules configuration - -Action Required: - Grant the following permission to this organization: - β€’ Permission: billing:read - β€’ Scope: Organization level - β€’ Required Role: Admin or Owner - - Without this permission, billing data will remain unavailable - in future reports for this organization. - -Data Inclusion: - βœ“ Usage metrics INCLUDED in aggregate totals - βœ— Billing status EXCLUDED from billing summaries -───────────────────────────────────────────────────────── -``` +Example: `550000` cents β†’ `$5,500.00` -### Detection Limit Warning Example -``` -⚠️ DETECTION LIMIT WARNINGS - -The following organizations exceeded the 5,000 detection retrieval -limit. Actual detection counts are higher than shown. - -Organizations Affected (15 of 45): - - Client ABC Corp - Retrieved: 5,000 detections ⚠️ LIMIT REACHED - Time Range: Nov 1-30, 2025 (30 days) - Actual Count: Unknown (exceeds 5,000) - Recommendation: Query in 7-day increments for complete data - - Client XYZ Industries - Retrieved: 5,000 detections ⚠️ LIMIT REACHED - Time Range: Nov 1-30, 2025 (30 days) - Actual Count: Unknown (exceeds 5,000) - Recommendation: Query in 7-day increments for complete data - - [... 13 more organizations] +### Error Handling -Impact on Report: - β€’ Detection counts shown are MINIMUM values - β€’ Actual totals across all organizations are higher - β€’ Category distributions may be skewed (sample bias) - β€’ Aggregate detection count is underreported +| Error | Status | Action | +|-------|--------|--------| +| 403 Forbidden | `error` | "Insufficient permissions - requires admin/owner role" | +| 404 Not Found | `no_invoice` | "No invoice exists for this period" | +| Other | `error` | Include error details | -Recommendations: - 1. For complete data, narrow time ranges: - - Instead of 30 days, query 7-day periods - - Aggregate results manually +Partial success is valid - report what succeeded, document what failed. - 2. Filter by category for targeted analysis: - - Query specific categories separately - - Combine results for complete picture - - 3. Consider increasing limit (max: 50,000): - - Higher limits increase API response time - - May hit other infrastructure limits -``` - -## Support and Troubleshooting +--- -### Common Issues +## Output Formats -**Issue**: "Too many organizations to process" -- **Solution**: Filter to subset, or process in batches -- **Workaround**: Generate multiple reports for org groups +### JSON -**Issue**: "Detection limit hit for all orgs" -- **Solution**: Narrow time range (30 days β†’ 7 days) -- **Workaround**: Query by week, aggregate manually +Raw structured data matching template schema. Use for: +- Programmatic consumption +- Piping to other tools +- Further processing -**Issue**: "Billing permission errors" -- **Solution**: Grant billing:read to organizations -- **Workaround**: Generate usage-only reports +### Markdown -**Issue**: "Large sensor list timing out" -- **Solution**: Use resource_link pattern with analyze script -- **Workaround**: Extract summary only (count, platforms) +Formatted tables for terminal display. Use for: +- Human review +- Quick summaries +- Copy/paste to reports -### Getting Help +--- -For issues with this skill: -1. Verify authentication and permissions -2. Check API endpoint availability -3. Validate time ranges and parameters -4. Review error messages for specific guidance -5. Contact LimaCharlie support: support@limacharlie.io +## Template Reference -## Skill Maintenance +Template file: `skills/reporting/templates/billing-report.json` -This skill should be updated when: -- New LimaCharlie API endpoints become available -- Data structure changes in API responses -- New reporting requirements emerge -- Additional guardrails needed based on issues +The template defines: +- **Input schema**: Required and optional parameters +- **Output schema**: Expected JSON structure +- **Data sources**: Which API calls populate each field -Last Updated: 2025-11-20 -Version: 1.0.0 +Read the template for full schema details. diff --git a/marketplace/plugins/lc-essentials/skills/reporting/templates/billing-report.json b/marketplace/plugins/lc-essentials/skills/reporting/templates/billing-report.json new file mode 100644 index 00000000..838b7b37 --- /dev/null +++ b/marketplace/plugins/lc-essentials/skills/reporting/templates/billing-report.json @@ -0,0 +1,206 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "name": "billing-report", + "description": "Invoice-focused billing report for single tenant or all tenants", + "version": "1.0", + "input": { + "required": { + "year": { + "type": "integer", + "description": "Invoice year (e.g., 2025)", + "minimum": 2020, + "maximum": 2100 + }, + "month": { + "type": "integer", + "description": "Invoice month (1-12)", + "minimum": 1, + "maximum": 12 + } + }, + "optional": { + "scope": { + "type": "string", + "enum": ["single", "all"], + "default": "all", + "description": "single = one org (requires oid), all = all accessible orgs" + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID (required when scope=single)" + }, + "format": { + "type": "string", + "enum": ["json", "markdown"], + "default": "markdown", + "description": "Output format" + } + } + }, + "output": { + "type": "object", + "required": ["metadata", "data"], + "properties": { + "metadata": { + "type": "object", + "required": ["generated_at", "period", "scope"], + "properties": { + "generated_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when report was generated" + }, + "period": { + "type": "string", + "description": "Human-readable billing period (e.g., 'November 2025')" + }, + "scope": { + "type": "string", + "enum": ["single", "all"], + "description": "Report scope" + }, + "tenant_count": { + "type": "integer", + "description": "Number of tenants in report (when scope=all)" + } + } + }, + "data": { + "type": "object", + "required": ["tenants"], + "properties": { + "tenants": { + "type": "array", + "description": "Per-tenant billing data", + "items": { + "type": "object", + "required": ["name", "oid", "status"], + "properties": { + "name": { + "type": "string", + "description": "Organization name" + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID" + }, + "status": { + "type": "string", + "enum": ["success", "error", "no_invoice"], + "description": "Data collection status" + }, + "cost": { + "type": "number", + "description": "Total invoice amount in dollars (converted from cents)" + }, + "currency": { + "type": "string", + "default": "usd", + "description": "Currency code" + }, + "skus": { + "type": "array", + "description": "Invoice line items", + "items": { + "type": "object", + "required": ["description", "amount"], + "properties": { + "description": { + "type": "string", + "description": "Line item description" + }, + "amount": { + "type": "number", + "description": "Amount in dollars" + }, + "quantity": { + "type": ["string", "number"], + "description": "Quantity or unit" + } + } + } + }, + "invoice_url": { + "type": "string", + "format": "uri", + "description": "PDF download URL (expires)" + }, + "error_message": { + "type": "string", + "description": "Error details if status=error" + } + } + } + }, + "rollup": { + "type": "object", + "description": "Aggregate totals (only when scope=all)", + "properties": { + "total_cost": { + "type": "number", + "description": "Sum of all tenant costs in dollars" + }, + "total_tenants": { + "type": "integer", + "description": "Total number of tenants processed" + }, + "successful_count": { + "type": "integer", + "description": "Tenants with successful data retrieval" + }, + "failed_count": { + "type": "integer", + "description": "Tenants that failed or had no invoice" + } + } + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Warning messages" + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "org_name": { + "type": "string" + }, + "oid": { + "type": "string" + }, + "error_message": { + "type": "string" + } + } + }, + "description": "Detailed error information per failed org" + } + } + }, + "data_sources": { + "description": "API calls used to populate this template (documentation only)", + "primary": { + "function": "get_org_invoice_url", + "parameters": { + "oid": "from input or list_user_orgs", + "year": "from input", + "month": "from input", + "format": "simple_json" + }, + "notes": "Amounts returned in cents - divide by 100 for dollars" + }, + "supporting": { + "function": "list_user_orgs", + "parameters": {}, + "notes": "Used to get OID list when scope=all" + } + } +} From 8ab6adacb00520d4b06183e3d49a5df1891d0d55 Mon Sep 17 00:00:00 2001 From: Christopher Luft Date: Tue, 9 Dec 2025 22:02:08 -0800 Subject: [PATCH 02/11] Simplify reporting templates to billing-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove MSSP Executive Summary, Customer Health Dashboard, and Detection Analytics templates - Keep only Monthly Billing Report as the single template option - Delete unused schemas: mssp-report.json, custom-report.json, security-overview.json - Delete unused HTML templates: mssp-dashboard.html.j2, custom-report.html.j2, security-overview.html.j2 - Enhance billing-summary.json schema with explicit examples for all variables - Add comprehensive field descriptions and example values throughout schema πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../commands/reporting-templates.md | 153 +++--- .../schemas/billing-summary.json | 183 ++++++- .../graphic-output/schemas/custom-report.json | 256 --------- .../graphic-output/schemas/mssp-report.json | 311 ----------- .../schemas/security-overview.json | 179 ------ .../templates/reports/custom-report.html.j2 | 394 ------------- .../templates/reports/mssp-dashboard.html.j2 | 506 ----------------- .../reports/security-overview.html.j2 | 518 ------------------ 8 files changed, 219 insertions(+), 2281 deletions(-) delete mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/schemas/custom-report.json delete mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/schemas/mssp-report.json delete mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/schemas/security-overview.json delete mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/custom-report.html.j2 delete mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/mssp-dashboard.html.j2 delete mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/security-overview.html.j2 diff --git a/marketplace/plugins/lc-essentials/commands/reporting-templates.md b/marketplace/plugins/lc-essentials/commands/reporting-templates.md index b28c2f4a..f5029f0f 100644 --- a/marketplace/plugins/lc-essentials/commands/reporting-templates.md +++ b/marketplace/plugins/lc-essentials/commands/reporting-templates.md @@ -16,10 +16,7 @@ AskUserQuestion( "question": "Which report template would you like to generate?", "header": "Template", "options": [ - {"label": "MSSP Executive Summary", "description": "High-level metrics across all customers: sensor counts, detection volumes, SLA status"}, - {"label": "Customer Health Dashboard", "description": "Per-org health scores, offline sensors, detection trends with drill-down"}, - {"label": "Monthly Billing Report", "description": "Usage statistics and billing data for customer invoicing"}, - {"label": "Detection Analytics", "description": "Security activity breakdown: top categories, trends, alert volumes by org"} + {"label": "Monthly Billing Report", "description": "Usage statistics and billing data for customer invoicing with roll-up totals"} ], "multiSelect": false }] @@ -28,89 +25,53 @@ AskUserQuestion( ## Template Definitions -### Template 1: MSSP Executive Summary +### Template: Monthly Billing Report -**Purpose**: High-level overview for MSSP leadership - quick health check across all customers. +**Purpose**: Comprehensive billing data with roll-up totals and per-tenant SKU breakdown for customer invoicing. **Data Collection** (reporting skill): - List all organizations -- Per-org: sensor count (online/offline), detection count (7 days), SLA status -- Aggregate totals across fleet +- Per-org: + - Invoice line items via `get_org_invoice_url` with `format: "simple_json"` + - SKU names, quantities, and amounts (converted from cents to dollars) + - Subscription status and billing details + - Sensor counts for context +- Aggregate roll-up: + - Total cost across all tenants + - Total sensors across all tenants + - Average cost per sensor (blended rate) **Visualization** (graphic-output skill): -- Summary cards: Total sensors, Total detections, Fleet coverage %, Orgs passing SLA -- Pie chart: Platform distribution (Windows/Linux/macOS) -- Bar chart: Top 10 orgs by detection volume -- Table: All orgs with health indicators (green/yellow/red) - -**Time Range**: Default last 7 days, prompt user to confirm. - -**Output**: `/tmp/mssp-executive-summary-{date}.html` - ---- - -### Template 2: Customer Health Dashboard - -**Purpose**: Operational dashboard for SOC teams - identify customers needing attention. - -**Data Collection** (reporting skill): -- List all organizations -- Per-org: sensor inventory with online/offline status, detection counts by category -- Identify: offline sensors >24h, orgs below SLA, detection spikes - -**Visualization** (graphic-output skill): -- Health gauge: Fleet-wide coverage percentage -- Heatmap: Org health matrix (rows=orgs, columns=metrics, color=status) -- Bar chart: Offline sensors by org -- Line chart: Detection trend (daily, last 7 days) if data available -- Alert list: Orgs requiring immediate attention - -**Time Range**: Default last 7 days, prompt user to confirm. - -**Output**: `/tmp/customer-health-dashboard-{date}.html` - ---- - -### Template 3: Monthly Billing Report - -**Purpose**: Usage data for customer invoicing and capacity planning. - -**Data Collection** (reporting skill): -- List all organizations -- Per-org: billing details, usage stats (events, outputs, storage) -- Aggregate: total usage, month-over-month comparison if available - -**Visualization** (graphic-output skill): -- Summary cards: Total events, Total output bytes, Active sensors -- Bar chart: Usage by organization (top consumers) -- Table: Full org breakdown with usage columns -- Pie chart: Usage distribution by org size tier - -**Time Range**: Prompt user for billing period (default: previous calendar month). +- **Executive Summary Roll-Up Cards**: + - Total Monthly Billing (all tenants combined) + - Total Sensors (all tenants) + - Average Cost/Sensor (blended rate) + - Active Tenant Count +- **Distribution Charts**: + - Pie chart: Cost distribution by tenant + - Pie chart: Sensor distribution by tenant +- **Per-Tenant Breakdown Table**: + - Organization name + - Region + - Sensor count + - Monthly cost + - Cost per sensor + - Percentage of total + - Status (active/draft/no usage) +- **Detailed SKU Breakdown by Tenant**: + - Expandable cards for each tenant + - Each SKU line item with name, quantity, amount + - Progress bar showing percentage of total cost +- **Cost by Category** (if SKUs can be categorized): + - Bar chart of spending by SKU category + +**Time Range**: Prompt user for billing period (year and month). This is passed to `get_org_invoice_url` to retrieve the correct invoice. **Output**: `/tmp/billing-report-{month}-{year}.html` ---- - -### Template 4: Detection Analytics +**Template Used**: `billing-summary` (uses billing-summary.html.j2) -**Purpose**: Security activity analysis for threat intelligence and tuning. - -**Data Collection** (reporting skill): -- List all organizations -- Per-org: detections by category, top detection rules triggered -- Aggregate: fleet-wide detection categories, rule effectiveness - -**Visualization** (graphic-output skill): -- Summary cards: Total detections, Unique categories, Orgs with alerts -- Pie chart: Detection categories (top 10) -- Bar chart: Detections by organization -- Table: Top triggered rules with counts -- Warning banner: Detection limits reached (if applicable) - -**Time Range**: Default last 30 days, prompt user to confirm. - -**Output**: `/tmp/detection-analytics-{date}.html` +**JSON Schema**: `billing-summary.json` --- @@ -118,11 +79,30 @@ AskUserQuestion( Once the user selects a template: -1. **Confirm Time Range**: Use `AskUserQuestion` to confirm or customize the time period +1. **Confirm Billing Period**: Use `AskUserQuestion` to get the billing period + ``` + AskUserQuestion( + questions=[{ + "question": "Which billing period should I generate the report for?", + "header": "Period", + "options": [ + {"label": "Previous month", "description": "Most recent completed billing cycle"}, + {"label": "Current month", "description": "Current billing period (may be incomplete)"}, + {"label": "Specific month", "description": "I'll specify the year and month"} + ], + "multiSelect": false + }] + ) + ``` 2. **Confirm Scope**: Ask if they want all orgs or a specific subset 3. **Collect Data**: Spawn `org-reporter` agents in parallel to collect data from each organization -4. **Generate HTML**: Spawn `html-renderer` agent to create the visualization dashboard -5. **Open in Browser**: Automatically open the generated HTML file using `xdg-open` or serve via local HTTP server + - Include billing period (year, month) in the prompt so agents call `get_org_invoice_url` with `format: "simple_json"` +4. **Aggregate Results**: + - Calculate roll-up totals (total cost, total sensors, avg cost/sensor) + - Structure data per the `billing-summary.json` schema +5. **Generate HTML**: Spawn `html-renderer` agent to create the visualization dashboard + - Use template `billing-summary` +6. **Open in Browser**: Automatically open the generated HTML file **Browser Launch Command:** ```bash @@ -137,15 +117,4 @@ xdg-open http://localhost:8765/{report-file}.html ## Example Conversation Flow ``` -User: /lc-essentials:reporting-templates - -Assistant: [Presents template menu] - -User: MSSP Executive Summary - -Assistant: [Confirms time range, collects data via reporting skill, generates HTML via graphic-output skill] - -Assistant: Your MSSP Executive Summary is ready! Opening in browser... - -[Browser opens with the report at http://localhost:8765/mssp-executive-summary-2025-12-06.html] -``` +User: /lc-essentials:reporting-templates \ No newline at end of file diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/billing-summary.json b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/billing-summary.json index 929f7a5b..9f4b2d1d 100644 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/billing-summary.json +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/billing-summary.json @@ -7,71 +7,120 @@ "properties": { "metadata": { "type": "object", + "description": "Report metadata - when and how data was collected", "required": ["generated_at", "period"], "properties": { "generated_at": { "type": "string", - "description": "ISO 8601 timestamp when data was collected" + "description": "ISO 8601 timestamp when data was collected", + "examples": ["2025-12-10T14:32:45Z"] }, "period": { "type": "string", - "description": "Billing period (e.g., 'November 2025')" + "description": "Billing period in human-readable format", + "examples": ["November 2025", "December 2025"] }, "tenant_count": { "type": "integer", - "description": "Number of tenants with billing data" + "description": "Number of tenants with billing data retrieved", + "examples": [24, 50, 100] } } }, "data": { "type": "object", + "description": "Report data containing rollup totals and per-tenant details", "required": ["rollup", "tenants"], "properties": { "rollup": { "type": "object", - "description": "Aggregate totals across all tenants", + "description": "Aggregate totals across all tenants - displayed in summary cards at top of report", "required": ["total_cost", "total_sensors"], "properties": { "total_cost": { "type": "number", - "description": "Total monthly cost across all tenants" + "description": "Total monthly cost in USD across all tenants (sum of all tenant costs)", + "examples": [12500.00, 45000.00, 125000.00] }, "total_sensors": { "type": "integer", - "description": "Total sensors across all tenants" + "description": "Total sensor count across all tenants (sum of all tenant sensors)", + "examples": [203, 1500, 5000] }, "avg_cost_per_sensor": { "type": "number", - "description": "Average cost per sensor (blended rate)" + "description": "Average cost per sensor (total_cost / total_sensors) - blended rate", + "examples": [61.58, 30.00, 25.00] } } }, "tenants": { "type": "array", - "description": "Per-tenant billing details", + "description": "Per-tenant billing details - each item represents one organization", "items": { "type": "object", "required": ["name", "oid"], "properties": { - "name": { "type": "string" }, - "oid": { "type": "string", "format": "uuid" }, - "region": { "type": "string" }, - "sensors": { "type": "integer" }, - "cost": { "type": "number" }, + "name": { + "type": "string", + "description": "Organization display name", + "examples": ["Acme Corp", "TPS Reporting Solutions", "Client ABC Production"] + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID (UUID)", + "examples": ["cdadbaa4-265d-4549-9686-990731ee4091"] + }, + "region": { + "type": "string", + "description": "Geographic region or data center location (optional)", + "examples": ["us-east", "eu-west", "apac"] + }, + "sensors": { + "type": "integer", + "description": "Total sensor count for this tenant", + "examples": [21, 50, 250] + }, + "cost": { + "type": "number", + "description": "Total monthly cost in USD for this tenant", + "examples": [1250.00, 5000.00, 25000.00] + }, "status": { "type": "string", - "enum": ["active", "draft", "no_usage", "error"] + "description": "Billing status for this tenant", + "enum": ["active", "draft", "no_usage", "error"], + "examples": ["active"] }, "skus": { "type": "array", - "description": "SKU line items", + "description": "Individual SKU line items from the invoice", "items": { "type": "object", "required": ["name", "amount"], "properties": { - "name": { "type": "string" }, - "amount": { "type": "number" }, - "quantity": { "type": ["string", "number"] } + "name": { + "type": "string", + "description": "SKU name/description from invoice", + "examples": [ + "Sensor License - Windows", + "Sensor License - Linux", + "Data Output (GB)", + "D&R Rule Evaluations", + "Artifact Storage (GB)" + ] + }, + "amount": { + "type": "number", + "description": "Cost in USD for this SKU (converted from cents: API value / 100)", + "examples": [1000.00, 250.00, 500.00] + }, + "quantity": { + "type": ["string", "number"], + "description": "Quantity or usage amount for this SKU", + "examples": [21, "50 GB", "1,000,000 evals", 100] + } } } } @@ -80,13 +129,21 @@ }, "categories": { "type": "array", - "description": "Cost breakdown by category (optional)", + "description": "Cost breakdown by category for chart visualization (optional - aggregate SKUs by type)", "items": { "type": "object", "required": ["name", "amount"], "properties": { - "name": { "type": "string" }, - "amount": { "type": "number" } + "name": { + "type": "string", + "description": "Category name (grouped SKU type)", + "examples": ["Sensor Licenses", "Data Output", "Storage", "Evaluations"] + }, + "amount": { + "type": "number", + "description": "Total cost in USD for this category across all tenants", + "examples": [8500.00, 4000.00, 1500.00] + } } } } @@ -94,17 +151,93 @@ }, "warnings": { "type": "array", - "items": { "type": "string" } + "description": "Data collection warnings - displayed in yellow alert box", + "items": { + "type": "string" + }, + "examples": [ + ["12 organizations missing billing.ctrl permission", "Usage stats unavailable for 5 orgs"] + ] }, "errors": { "type": "array", + "description": "Per-organization errors - displayed in collapsible error section", "items": { "type": "object", "properties": { - "org_name": { "type": "string" }, - "error_message": { "type": "string" } + "org_name": { + "type": "string", + "description": "Name of the organization that failed", + "examples": ["Client XYZ", "Test Org"] + }, + "oid": { + "type": "string", + "description": "Organization ID (UUID)", + "examples": ["abc123-def456-..."] + }, + "error_message": { + "type": "string", + "description": "Error description", + "examples": ["Permission denied: missing billing.ctrl", "API timeout", "Invalid response"] + } } } } - } + }, + "examples": [ + { + "metadata": { + "generated_at": "2025-12-10T14:32:45Z", + "period": "November 2025", + "tenant_count": 24 + }, + "data": { + "rollup": { + "total_cost": 12500.00, + "total_sensors": 203, + "avg_cost_per_sensor": 61.58 + }, + "tenants": [ + { + "name": "TPS Reporting Solutions", + "oid": "aac9c41d-e0a3-4e7e-88b8-33936ab93238", + "region": "us-east", + "sensors": 21, + "cost": 1250.00, + "status": "active", + "skus": [ + {"name": "Sensor License - Windows", "amount": 800.00, "quantity": 16}, + {"name": "Sensor License - Linux", "amount": 250.00, "quantity": 5}, + {"name": "Data Output (GB)", "amount": 200.00, "quantity": "40 GB"} + ] + }, + { + "name": "lc-infrastructure", + "oid": "efedcf87-318c-4c15-9f52-482606491223", + "sensors": 17, + "cost": 850.00, + "status": "active", + "skus": [ + {"name": "Sensor License - Linux", "amount": 850.00, "quantity": 17} + ] + } + ], + "categories": [ + {"name": "Sensor Licenses", "amount": 10000.00}, + {"name": "Data Output", "amount": 2000.00}, + {"name": "Storage", "amount": 500.00} + ] + }, + "warnings": [ + "12 organizations returned permission denied for billing data" + ], + "errors": [ + { + "org_name": "github-integration-test", + "oid": "ddcc6630-20d1-4fd6-b16c-245c58a6d58e", + "error_message": "Permission denied: missing billing.ctrl permission" + } + ] + } + ] } diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/custom-report.json b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/custom-report.json deleted file mode 100644 index 12f41bfa..00000000 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/custom-report.json +++ /dev/null @@ -1,256 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Custom Report Data", - "description": "Schema for component-based custom reports. Build flexible reports by specifying which components to include.", - "type": "object", - "required": ["metadata", "components"], - "properties": { - "metadata": { - "type": "object", - "properties": { - "title": { "type": "string", "description": "Report title" }, - "subtitle": { "type": "string", "description": "Report subtitle" }, - "generated_at": { "type": "string", "description": "Generation timestamp" } - } - }, - "components": { - "type": "array", - "description": "Array of components to render in order", - "items": { - "oneOf": [ - { "$ref": "#/$defs/summary_cards" }, - { "$ref": "#/$defs/chart" }, - { "$ref": "#/$defs/table" }, - { "$ref": "#/$defs/alert_banner" }, - { "$ref": "#/$defs/text_section" }, - { "$ref": "#/$defs/two_column" }, - { "$ref": "#/$defs/metric_grid" }, - { "$ref": "#/$defs/platform_table" }, - { "$ref": "#/$defs/divider" } - ] - } - }, - "warnings": { - "type": "array", - "items": { "type": "string" } - } - }, - "$defs": { - "summary_cards": { - "type": "object", - "required": ["type", "cards"], - "properties": { - "type": { "const": "summary_cards" }, - "title": { "type": "string" }, - "cards": { - "type": "array", - "items": { - "type": "object", - "required": ["label"], - "properties": { - "label": { "type": "string" }, - "value": { "type": ["string", "number"] }, - "subvalue": { "type": "string" }, - "warning": { "type": "string" }, - "icon": { - "type": "string", - "enum": ["sensors", "alerts", "rules", "users", "money", "check", "shield", "activity"] - }, - "icon_color": { - "type": "string", - "enum": ["blue", "green", "amber", "purple", "red"] - }, - "value_color": { "type": "string" }, - "format": { - "type": "string", - "enum": ["currency", "percent", "number"] - } - } - } - } - } - }, - "chart": { - "type": "object", - "required": ["type", "chart_type", "data"], - "properties": { - "type": { "const": "chart" }, - "title": { "type": "string" }, - "subtitle": { "type": "string" }, - "chart_type": { - "type": "string", - "enum": ["bar", "pie", "doughnut", "line"] - }, - "data": { - "type": "array", - "items": { - "type": "object", - "required": ["label", "value"], - "properties": { - "label": { "type": "string" }, - "value": { "type": "number" } - } - } - }, - "colors": { "type": "array", "items": { "type": "string" } }, - "horizontal": { "type": "boolean", "default": false }, - "show_legend": { "type": "boolean", "default": true }, - "legend_position": { "type": "string", "enum": ["top", "bottom", "left", "right"] }, - "height": { "type": "integer" }, - "dataset_label": { "type": "string" }, - "line_color": { "type": "string" }, - "fill": { "type": "boolean" }, - "empty_message": { "type": "string" } - } - }, - "table": { - "type": "object", - "required": ["type", "rows"], - "properties": { - "type": { "const": "table" }, - "title": { "type": "string" }, - "columns": { - "type": "array", - "items": { - "type": "object", - "required": ["label"], - "properties": { - "label": { "type": "string" }, - "align": { "type": "string", "enum": ["left", "right", "center"] } - } - } - }, - "rows": { - "type": "array", - "items": { - "type": "array", - "items": { - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { - "type": "object", - "properties": { - "value": { "type": ["string", "number"] }, - "align": { "type": "string" }, - "color": { "type": "string" }, - "badge": { "type": "string", "enum": ["success", "partial", "failed"] }, - "type": { "type": "string" } - } - } - ] - } - } - }, - "footer": { "type": "array" }, - "sortable": { "type": "boolean", "default": false }, - "empty_message": { "type": "string" } - } - }, - "alert_banner": { - "type": "object", - "required": ["type", "message"], - "properties": { - "type": { "const": "alert_banner" }, - "title": { "type": "string" }, - "message": { "type": "string" }, - "severity": { - "type": "string", - "enum": ["info", "warning", "danger", "success"], - "default": "info" - } - } - }, - "text_section": { - "type": "object", - "required": ["type", "content"], - "properties": { - "type": { "const": "text_section" }, - "title": { "type": "string" }, - "content": { "type": "string", "description": "HTML content" } - } - }, - "two_column": { - "type": "object", - "required": ["type", "columns"], - "properties": { - "type": { "const": "two_column" }, - "title": { "type": "string" }, - "columns": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "items": { - "type": "object", - "properties": { - "title": { "type": "string" }, - "chart_type": { "type": "string", "enum": ["pie", "doughnut", "bar"] }, - "data": { - "type": "array", - "items": { - "type": "object", - "required": ["label", "value"], - "properties": { - "label": { "type": "string" }, - "value": { "type": "number" } - } - } - }, - "colors": { "type": "array", "items": { "type": "string" } } - } - } - } - } - }, - "metric_grid": { - "type": "object", - "required": ["type", "metrics"], - "properties": { - "type": { "const": "metric_grid" }, - "title": { "type": "string" }, - "metrics": { - "type": "array", - "items": { - "type": "object", - "required": ["label", "value"], - "properties": { - "label": { "type": "string" }, - "value": { "type": ["string", "number"] }, - "color": { "type": "string" } - } - } - } - } - }, - "platform_table": { - "type": "object", - "required": ["type", "platforms"], - "properties": { - "type": { "const": "platform_table" }, - "title": { "type": "string" }, - "show_detections": { "type": "boolean", "default": false }, - "platforms": { - "type": "array", - "items": { - "type": "object", - "required": ["name", "sensors"], - "properties": { - "name": { "type": "string" }, - "sensors": { "type": "integer" }, - "sensors_percent": { "type": "number" }, - "detections": { "type": "integer" }, - "detections_percent": { "type": "number" } - } - } - } - } - }, - "divider": { - "type": "object", - "required": ["type"], - "properties": { - "type": { "const": "divider" } - } - } - } -} diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/mssp-report.json b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/mssp-report.json deleted file mode 100644 index 18f2c83f..00000000 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/mssp-report.json +++ /dev/null @@ -1,311 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MSSP Dashboard Report Data", - "description": "Schema for data input to the mssp-dashboard template. All values must come from actual API responses - no fabrication allowed.", - "type": "object", - "required": ["metadata", "data"], - "properties": { - "metadata": { - "type": "object", - "description": "Report metadata - when and how data was collected", - "required": ["generated_at", "time_window", "organizations"], - "properties": { - "generated_at": { - "type": "string", - "description": "ISO 8601 timestamp when data was collected", - "format": "date-time", - "examples": ["2025-11-27T14:32:45Z"] - }, - "time_window": { - "type": "object", - "description": "Time range covered by the report", - "required": ["start", "end", "start_display", "end_display"], - "properties": { - "start": { - "type": "integer", - "description": "Start timestamp in Unix epoch seconds" - }, - "end": { - "type": "integer", - "description": "End timestamp in Unix epoch seconds" - }, - "start_display": { - "type": "string", - "description": "Human-readable start time", - "examples": ["2025-11-01 00:00:00 UTC"] - }, - "end_display": { - "type": "string", - "description": "Human-readable end time", - "examples": ["2025-11-30 23:59:59 UTC"] - }, - "days": { - "type": "integer", - "description": "Number of days in the time window" - } - } - }, - "organizations": { - "type": "object", - "description": "Summary of organizations processed", - "required": ["total", "successful"], - "properties": { - "total": { - "type": "integer", - "description": "Total number of organizations attempted" - }, - "successful": { - "type": "integer", - "description": "Number of organizations successfully processed" - }, - "failed": { - "type": "integer", - "description": "Number of organizations that failed" - }, - "success_rate": { - "type": "number", - "description": "Percentage of successful organizations", - "minimum": 0, - "maximum": 100 - } - } - } - } - }, - "data": { - "type": "object", - "description": "Report data - all values from actual API responses", - "required": ["aggregate", "organizations"], - "properties": { - "aggregate": { - "type": "object", - "description": "Aggregated metrics across all successful organizations", - "required": ["sensors", "detections", "rules"], - "properties": { - "sensors": { - "type": "object", - "description": "Sensor metrics", - "required": ["total", "online"], - "properties": { - "total": { - "type": "integer", - "description": "Total number of sensors" - }, - "online": { - "type": "integer", - "description": "Number of online sensors" - }, - "offline": { - "type": "integer", - "description": "Number of offline sensors" - }, - "health_percent": { - "type": "number", - "description": "Percentage of sensors online" - }, - "platforms": { - "type": "object", - "description": "Sensor count by platform", - "additionalProperties": { - "type": "integer" - }, - "examples": [{"windows": 1823, "linux": 712, "macos": 247}] - } - } - }, - "detections": { - "type": "object", - "description": "Detection metrics", - "required": ["retrieved", "top_categories"], - "properties": { - "retrieved": { - "type": "integer", - "description": "Total detections retrieved (may be limited)" - }, - "limit_reached": { - "type": "boolean", - "description": "Whether any organization hit the detection limit" - }, - "orgs_at_limit": { - "type": "integer", - "description": "Number of organizations that hit detection limit" - }, - "top_categories": { - "type": "array", - "description": "Top detection categories with counts", - "items": { - "type": "object", - "required": ["label", "value"], - "properties": { - "label": { - "type": "string", - "description": "Category name" - }, - "value": { - "type": "integer", - "description": "Detection count" - } - } - } - }, - "daily_trend": { - "type": "array", - "description": "Daily detection counts (optional)", - "items": { - "type": "object", - "required": ["date", "value"], - "properties": { - "date": { - "type": "string", - "description": "Date in YYYY-MM-DD format" - }, - "value": { - "type": "integer", - "description": "Detection count for that day" - } - } - } - } - } - }, - "rules": { - "type": "object", - "description": "D&R rule metrics", - "required": ["total", "enabled"], - "properties": { - "total": { - "type": "integer", - "description": "Total number of D&R rules" - }, - "enabled": { - "type": "integer", - "description": "Number of enabled rules" - } - } - }, - "usage": { - "type": "object", - "description": "Usage metrics (optional)", - "properties": { - "total_events": { - "type": "integer", - "description": "Total sensor events" - }, - "total_output_bytes": { - "type": "integer", - "description": "Total bytes output" - }, - "total_output_gb": { - "type": "number", - "description": "Total GB output" - }, - "total_evaluations": { - "type": "integer", - "description": "Total D&R evaluations" - } - } - } - } - }, - "organizations": { - "type": "array", - "description": "Per-organization data", - "items": { - "type": "object", - "required": ["name", "oid", "status"], - "properties": { - "name": { - "type": "string", - "description": "Organization name" - }, - "oid": { - "type": "string", - "description": "Organization ID (UUID)", - "format": "uuid" - }, - "status": { - "type": "string", - "description": "Data collection status", - "enum": ["success", "partial", "failed"] - }, - "sensors_total": { - "type": "integer", - "description": "Total sensors in this org" - }, - "sensors_online": { - "type": "integer", - "description": "Online sensors in this org" - }, - "health": { - "type": "number", - "description": "Health percentage (0-100)" - }, - "detections": { - "type": "integer", - "description": "Detection count for this org" - }, - "detection_limit_reached": { - "type": "boolean", - "description": "Whether this org hit the detection limit" - }, - "rules_total": { - "type": "integer", - "description": "Total rules in this org" - }, - "rules_enabled": { - "type": "integer", - "description": "Enabled rules in this org" - } - } - } - } - } - }, - "warnings": { - "type": "array", - "description": "Warnings from data collection - ALL must be displayed", - "items": { - "type": "string" - } - }, - "errors": { - "type": "array", - "description": "Errors from data collection - ALL must be displayed", - "items": { - "type": "object", - "properties": { - "org_name": { - "type": "string", - "description": "Organization name" - }, - "oid": { - "type": "string", - "description": "Organization ID" - }, - "error_code": { - "type": ["integer", "string"], - "description": "Error code" - }, - "error_message": { - "type": "string", - "description": "Error message" - }, - "remediation": { - "type": "string", - "description": "Suggested remediation action" - } - } - } - }, - "source_skill": { - "type": "string", - "description": "Skill that generated the data", - "default": "reporting" - } - }, - "$defs": { - "data_accuracy_note": { - "description": "DATA ACCURACY GUARDRAILS: All values in this schema must come from actual API responses. The graphic-output skill will NEVER fabricate, estimate, or interpolate data. Missing fields will show as 'N/A' in the output." - } - } -} diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/security-overview.json b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/security-overview.json deleted file mode 100644 index f44e941c..00000000 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/security-overview.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Security Overview Report Data", - "description": "Schema for data input to the security-overview template. Single-tenant comprehensive security report. All values must come from actual API responses - no fabrication allowed.", - "type": "object", - "required": ["metadata", "data"], - "properties": { - "metadata": { - "type": "object", - "description": "Report metadata - when and how data was collected", - "required": ["generated_at", "time_window", "org_name", "oid"], - "properties": { - "generated_at": { - "type": "string", - "description": "ISO 8601 timestamp when data was collected", - "format": "date-time" - }, - "time_window": { - "type": "object", - "required": ["start", "end", "start_display", "end_display"], - "properties": { - "start": { "type": "integer" }, - "end": { "type": "integer" }, - "start_display": { "type": "string" }, - "end_display": { "type": "string" }, - "days": { "type": "integer" } - } - }, - "org_name": { - "type": "string", - "description": "Organization name" - }, - "oid": { - "type": "string", - "description": "Organization ID (UUID)", - "format": "uuid" - } - } - }, - "data": { - "type": "object", - "description": "Report data - all values from actual API responses", - "required": ["summary"], - "properties": { - "summary": { - "type": "object", - "description": "Executive summary metrics", - "required": ["sensors_total", "detections_total", "rules_total"], - "properties": { - "sensors_total": { "type": "integer" }, - "sensors_online": { "type": "integer" }, - "detections_total": { "type": "integer" }, - "detections_24h": { "type": "integer" }, - "rules_total": { "type": "integer" }, - "rules_enabled": { "type": "integer" }, - "mitre_coverage": { "type": "number", "minimum": 0, "maximum": 100 } - } - }, - "active_threats": { - "type": "array", - "description": "Active threat alerts (optional)", - "items": { - "type": "object", - "properties": { - "title": { "type": "string" }, - "description": { "type": "string" }, - "attacker_ip": { "type": "string" }, - "start_time": { "type": "string" }, - "severity": { "type": "string", "enum": ["critical", "high", "medium", "low"] } - } - } - }, - "platforms": { - "type": "array", - "description": "Platform breakdown with sensors and detections", - "items": { - "type": "object", - "required": ["name", "sensors"], - "properties": { - "name": { "type": "string" }, - "sensors": { "type": "integer" }, - "sensors_percent": { "type": "number" }, - "detections": { "type": "integer" }, - "detections_percent": { "type": "number" }, - "badge_class": { "type": "string" } - } - } - }, - "hourly_detections": { - "type": "array", - "description": "Detection timeline data (optional)", - "items": { - "type": "object", - "required": ["hour", "count"], - "properties": { - "hour": { "type": "string" }, - "count": { "type": "integer" } - } - } - }, - "top_detections": { - "type": "array", - "description": "Top detection categories", - "items": { - "type": "object", - "required": ["category", "count"], - "properties": { - "category": { "type": "string" }, - "count": { "type": "integer" }, - "trend": { "type": "string", "enum": ["up", "down", "stable"] } - } - } - }, - "mitre_techniques": { - "type": "array", - "description": "MITRE ATT&CK technique coverage (optional)", - "items": { - "type": "object", - "required": ["id", "name"], - "properties": { - "id": { "type": "string", "pattern": "^T[0-9]{4}(\\.[0-9]{3})?$" }, - "name": { "type": "string" }, - "tactic": { "type": "string" }, - "detection_count": { "type": "integer" } - } - } - }, - "rules": { - "type": "object", - "description": "D&R rule inventory", - "properties": { - "general": { "type": "integer" }, - "managed": { "type": "integer" }, - "service": { "type": "integer" }, - "enabled": { "type": "integer" }, - "disabled": { "type": "integer" } - } - }, - "attack_analysis": { - "type": "object", - "description": "Attack pattern analysis (optional)", - "properties": { - "primary_vector": { "type": "string" }, - "top_attacker_ips": { - "type": "array", - "items": { "type": "string" } - }, - "targeted_assets": { - "type": "array", - "items": { "type": "string" } - } - } - } - } - }, - "warnings": { - "type": "array", - "description": "Warnings from data collection - ALL must be displayed", - "items": { "type": "string" } - }, - "errors": { - "type": "array", - "description": "Errors from data collection", - "items": { - "type": "object", - "properties": { - "error_code": { "type": ["integer", "string"] }, - "error_message": { "type": "string" }, - "remediation": { "type": "string" } - } - } - } - }, - "$defs": { - "data_accuracy_note": { - "description": "DATA ACCURACY GUARDRAILS: All values must come from actual API responses. Missing fields show as 'N/A'." - } - } -} diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/custom-report.html.j2 b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/custom-report.html.j2 deleted file mode 100644 index 4d4862dc..00000000 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/custom-report.html.j2 +++ /dev/null @@ -1,394 +0,0 @@ -{# - Custom Report Template - Component-Based Composition - - Allows users to build flexible reports by specifying which components to include. - Each component in the `components` array is rendered in order. - - DATA ACCURACY GUARDRAILS: - - This template ONLY displays data provided in the context - - Missing data shows "N/A" or "Data unavailable" - - No data is fabricated, estimated, or interpolated - - SUPPORTED COMPONENTS: - - summary_cards: Grid of metric cards - - chart: Pie, bar, line, doughnut charts - - table: Data tables with optional sorting - - alert_banner: Warning/info/success banners - - text_section: Markdown-style text content - - divider: Visual separator - - two_column: Side-by-side layout for charts/content -#} -{% extends 'base.html.j2' %} - -{% block title %}{{ metadata.title | default('Custom Report') }}{% endblock %} - -{% block report_title %}{{ metadata.title | default('Custom Report') }}{% endblock %} - -{% block report_subtitle %} -{% if metadata.subtitle %}{{ metadata.subtitle }}{% endif %} -{% endblock %} - -{% block content %} -
- - {# Render each component in order #} - {% for component in components %} - - {# ================================================================ - SUMMARY CARDS - Grid of metric cards - ================================================================ #} - {% if component.type == 'summary_cards' %} -
- {% if component.title %}

{{ component.title }}

{% endif %} -
- {% for card in component.cards %} -
- {% if card.icon %} -
- {% if card.icon == 'sensors' %} - - - - {% elif card.icon == 'alerts' %} - - - - - {% elif card.icon == 'rules' %} - - - - - {% elif card.icon == 'users' %} - - - - - {% elif card.icon == 'money' %} - - - - {% elif card.icon == 'check' %} - - - - {% elif card.icon == 'shield' %} - - - - {% elif card.icon == 'activity' %} - - - - {% else %} - - - - {% endif %} -
- {% endif %} -
- - {% if card.value is defined %} - {% if card.format == 'currency' %}${% endif %}{{ card.value | format_number }}{% if card.format == 'percent' %}%{% endif %} - {% else %} - N/A - {% endif %} - - {{ card.label | default('Metric') }} - {% if card.subvalue %}{{ card.subvalue }}{% endif %} - {% if card.warning %}
{{ card.warning }}
{% endif %} -
-
- {% endfor %} -
-
- - {# ================================================================ - CHART - Various chart types - ================================================================ #} - {% elif component.type == 'chart' %} -
- {% if component.title %}

{{ component.title }}

{% endif %} -
- {% if component.subtitle %}

{{ component.subtitle }}

{% endif %} - {% if component.data and component.data | length > 0 %} - - - {% else %} -
- πŸ“Š -

{{ component.empty_message | default('No data available for this chart') }}

-
- {% endif %} -
-
- - {# ================================================================ - TWO COLUMN - Side-by-side charts - ================================================================ #} - {% elif component.type == 'two_column' %} -
- {% if component.title %}

{{ component.title }}

{% endif %} -
- {% for col in component.columns %} -
- {% if col.title %}

{{ col.title }}

{% endif %} - {% if col.data and col.data | length > 0 %} - - - {% else %} -
- πŸ“Š -

No data available

-
- {% endif %} -
- {% endfor %} -
-
- - {# ================================================================ - TABLE - Data tables - ================================================================ #} - {% elif component.type == 'table' %} -
- {% if component.title %}

{{ component.title }}

{% endif %} - {% if component.rows and component.rows | length > 0 %} -
- - {% if component.columns %} - - - {% for col in component.columns %} - - {% endfor %} - - - {% endif %} - - {% for row in component.rows %} - - {% for cell in row %} - - {% endfor %} - - {% endfor %} - - {% if component.footer %} - - - {% for cell in component.footer %} - - {% endfor %} - - - {% endif %} -
{{ col.label }}
- {% if cell.badge %} - {{ cell.value }} - {% elif cell.value is defined %} - {{ cell.value }} - {% else %} - {{ cell }} - {% endif %} -
- {{ cell.value | default(cell) }} -
-
- {% else %} -
- πŸ“‹ -

{{ component.empty_message | default('No data available') }}

-
- {% endif %} -
- - {# ================================================================ - ALERT BANNER - Warning/info/success banners - ================================================================ #} - {% elif component.type == 'alert_banner' %} -
-
- - {% if component.severity == 'danger' %}🚨 - {% elif component.severity == 'warning' %}⚠️ - {% elif component.severity == 'success' %}βœ… - {% else %}ℹ️{% endif %} - -
- {% if component.title %}{{ component.title }}{% endif %} - {{ component.message }} -
-
-
- - {# ================================================================ - TEXT SECTION - Markdown-style content - ================================================================ #} - {% elif component.type == 'text_section' %} -
- {% if component.title %}

{{ component.title }}

{% endif %} -
- {{ component.content | safe }} -
-
- - {# ================================================================ - DIVIDER - Visual separator - ================================================================ #} - {% elif component.type == 'divider' %} -
- - {# ================================================================ - METRIC GRID - Compact metrics grid - ================================================================ #} - {% elif component.type == 'metric_grid' %} -
- {% if component.title %}

{{ component.title }}

{% endif %} -
- {% for metric in component.metrics %} -
-
- {{ metric.value | default('N/A') }} -
-
- {{ metric.label }} -
-
- {% endfor %} -
-
- - {# ================================================================ - PLATFORM TABLE - Special platform breakdown table - ================================================================ #} - {% elif component.type == 'platform_table' %} -
- {% if component.title %}

{{ component.title }}

{% endif %} - {% if component.platforms and component.platforms | length > 0 %} - - - - - - - {% if component.show_detections %}{% endif %} - - - - {% for p in component.platforms %} - - - - - {% if component.show_detections %} - - - {% endif %} - - {% endfor %} - -
PlatformSensors%DetectionsDet %
{{ p.name }}{{ p.sensors | format_number }}{{ p.sensors_percent | default(0) | round(1) }}%{{ p.detections | default(0) | format_number }}{{ p.detections_percent | default(0) | round(1) }}%
- {% else %} -
- πŸ“Š -

No platform data available

-
- {% endif %} -
- - {% endif %} - {% endfor %} - - {# ================================================================ - WARNINGS (if any) - ================================================================ #} - {% if warnings and warnings | length > 0 %} -
-

⚠️ Data Notes & Warnings

-
- {% for warning in warnings %} -
- ⚠️ - {{ warning }} -
- {% endfor %} -
-
- {% endif %} - -
-{% endblock %} - -{% block extra_scripts %} - -{% endblock %} diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/mssp-dashboard.html.j2 b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/mssp-dashboard.html.j2 deleted file mode 100644 index 28062acb..00000000 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/mssp-dashboard.html.j2 +++ /dev/null @@ -1,506 +0,0 @@ -{# - MSSP Dashboard Template - - Multi-tenant overview with aggregate metrics, charts, and organization table. - - DATA ACCURACY GUARDRAILS: - - This template ONLY displays data provided in the context - - Missing data shows "N/A" or "Data unavailable" - - All warnings are displayed in a dedicated section - - No data is fabricated, estimated, or interpolated -#} -{% extends 'base.html.j2' %} - -{% block title %}MSSP Security Dashboard{% endblock %} - -{% block report_title %}MSSP Security Dashboard{% endblock %} - -{% block content %} -
- - {# ================================================================ - EXECUTIVE SUMMARY CARDS - GUARDRAIL: All values come directly from data.aggregate - ================================================================ #} -
-

Executive Summary

- -
- {# Sensors Card #} -
-
- - - - -
-
- - {% if data.aggregate.sensors.total is defined %} - {{ data.aggregate.sensors.total | format_number }} - {% else %} - N/A - {% endif %} - - Total Sensors - {% if data.aggregate.sensors.online is defined and data.aggregate.sensors.total is defined %} - - {{ data.aggregate.sensors.online | format_number }} online - ({{ ((data.aggregate.sensors.online / data.aggregate.sensors.total) * 100) | round(1) }}%) - - {% endif %} -
-
- - {# Detections Card #} -
-
- - - - - -
-
- - {% if data.aggregate.detections.retrieved is defined %} - {{ data.aggregate.detections.retrieved | format_number }} - {% else %} - N/A - {% endif %} - - Detections Retrieved - {% if data.aggregate.detections.limit_reached %} -
- Limit reached on {{ data.aggregate.detections.orgs_at_limit | default('some') }} orgs - actual count higher -
- {% endif %} -
-
- - {# Rules Card #} -
-
- - - - - - -
-
- - {% if data.aggregate.rules.total is defined %} - {{ data.aggregate.rules.total | format_number }} - {% else %} - N/A - {% endif %} - - D&R Rules - {% if data.aggregate.rules.enabled is defined %} - - {{ data.aggregate.rules.enabled | format_number }} enabled - - {% endif %} -
-
- - {# Organizations Card #} -
-
- - - - - -
-
- - {% if metadata.organizations.successful is defined %} - {{ metadata.organizations.successful }} - {% else %} - N/A - {% endif %} - - Organizations - {% if metadata.organizations.total is defined %} - - of {{ metadata.organizations.total }} - {% if metadata.organizations.success_rate is defined %} - ({{ metadata.organizations.success_rate }}% success) - {% endif %} - - {% endif %} -
-
-
-
- - {# ================================================================ - FLEET HEALTH CHARTS - GUARDRAIL: Charts only render if data exists - ================================================================ #} -
-

Fleet Overview

- -
- {# Platform Distribution Pie Chart #} -
-

Platform Distribution

- {% if data.aggregate.sensors.platforms and data.aggregate.sensors.platforms | length > 0 %} -
- -
-
- - {% else %} -
- πŸ“Š -

Platform data not available

-
- {% endif %} -
- - {# Organization Health Bar Chart #} -
-

Organization Health

- {% if data.organizations and data.organizations | length > 0 %} -
- -
- - {% if data.organizations | length > 10 %} -

- Showing top 10 of {{ data.organizations | length }} organizations -

- {% endif %} - {% else %} -
- πŸ“Š -

Organization health data not available

-
- {% endif %} -
-
-
- - {# ================================================================ - DETECTION ANALYTICS - GUARDRAIL: Shows "unavailable" message if data missing - ================================================================ #} -
-

Detection Analytics

- - {% if data.aggregate.detections.limit_reached %} -
- ⚠️ - Detection limit reached on {{ data.aggregate.detections.orgs_at_limit | default('some') }} organizations. - Actual detection counts are higher than shown. -
- {% endif %} - -
- {# Top Detection Categories Bar Chart #} -
-

Top Detection Categories

- {% if data.aggregate.detections.top_categories and data.aggregate.detections.top_categories | length > 0 %} -
- -
- - {% else %} -
- πŸ“Š -

Detection category data not available

-
- {% endif %} -
- - {# Detection Trend Line Chart #} -
-

Detection Volume Trend

- {% if data.aggregate.detections.daily_trend and data.aggregate.detections.daily_trend | length > 1 %} -
- - -
- - {% elif data.aggregate.detections.daily_trend and data.aggregate.detections.daily_trend | length == 1 %} -
- πŸ“Š -

Insufficient data for trend visualization

-

- Single data point: {{ data.aggregate.detections.daily_trend[0].date }} = - {{ data.aggregate.detections.daily_trend[0].value | format_number }} -

-
- {% else %} -
- πŸ“Š -

Trend data not available

-

- Daily detection counts were not provided in the source data. -

-
- {% endif %} -
-
-
- - {# ================================================================ - ORGANIZATION DETAILS TABLE - GUARDRAIL: Shows N/A for missing cell values - ================================================================ #} -
-

Organization Details

- - {% if data.organizations and data.organizations | length > 0 %} -
- - - - - - - - - - - - - - {% for org in data.organizations %} - - - - - - - - - {% endfor %} - - - - - - - - - - - -
Organization summary table
OrganizationSensorsOnlineHealthDetectionsStatus
{{ org.name | default('N/A') }} - {{ org.sensors_total | format_number if org.sensors_total is defined else 'N/A' }} - - {{ org.sensors_online | format_number if org.sensors_online is defined else 'N/A' }} - - {% if org.health is defined %} -
- {{ org.health | round(1) }}% -
- {% else %} - N/A - {% endif %} -
- {{ org.detections | format_number if org.detections is defined else 'N/A' }} - {% if org.detection_limit_reached %}⚠️{% endif %} - - {% if org.status == 'success' %} - Success - {% elif org.status == 'partial' %} - Partial - {% elif org.status == 'failed' %} - Failed - {% else %} - {{ org.status | default('N/A') }} - {% endif %} -
Total ({{ data.organizations | length }} orgs) - {{ data.aggregate.sensors.total | format_number if data.aggregate.sensors.total is defined else 'N/A' }} - - {{ data.aggregate.sensors.online | format_number if data.aggregate.sensors.online is defined else 'N/A' }} - - {% if data.aggregate.sensors.health_percent is defined %} - {{ data.aggregate.sensors.health_percent | round(1) }}% - {% elif data.aggregate.sensors.online is defined and data.aggregate.sensors.total is defined and data.aggregate.sensors.total > 0 %} - {{ ((data.aggregate.sensors.online / data.aggregate.sensors.total) * 100) | round(1) }}% - {% else %} - N/A - {% endif %} - - {{ data.aggregate.detections.retrieved | format_number if data.aggregate.detections.retrieved is defined else 'N/A' }} -
-
- {% else %} -
- πŸ“‹ -

No organization data available

-
- {% endif %} -
- - {# ================================================================ - WARNINGS AND ERRORS SECTION - GUARDRAIL: ALL warnings from source data MUST be displayed here - ================================================================ #} - {% if warnings or errors %} -
-

⚠️ Data Limitations & Warnings

- - {% if warnings and warnings | length > 0 %} -
-

Warnings

- {% for warning in warnings %} -
- ⚠️ - {{ warning }} -
- {% endfor %} -
- {% endif %} - - {% if errors and errors | length > 0 %} -
-

Failed Organizations ({{ errors | length }})

- {% for error in errors %} -
- {{ error.org_name | default(error.org | default('Unknown')) }} - {% if error.error_code %} - {{ error.error_code }} - {% endif %} -

{{ error.error_message | default(error.error | default('Unknown error')) }}

- {% if error.remediation %} -

Action: {{ error.remediation }}

- {% endif %} -
- {% endfor %} -
- {% endif %} -
- {% endif %} - - {# ================================================================ - USAGE METRICS (if available) - GUARDRAIL: Only shown if data exists, no fabrication - ================================================================ #} - {% if data.aggregate.usage %} -
-

Usage Metrics

- -
- {% if data.aggregate.usage.total_events is defined %} -
-
- {{ data.aggregate.usage.total_events | format_number }} - Total Events -
-
- {% endif %} - - {% if data.aggregate.usage.total_output_bytes is defined %} -
-
- {{ data.aggregate.usage.total_output_bytes | format_bytes }} - Data Output - ({{ data.aggregate.usage.total_output_bytes | format_number }} bytes) -
-
- {% endif %} - - {% if data.aggregate.usage.total_evaluations is defined %} -
-
- {{ data.aggregate.usage.total_evaluations | format_number }} - D&R Evaluations -
-
- {% endif %} -
- -

- Note: Usage metrics are from LimaCharlie APIs. No cost calculations are performed. - For billing details, refer to individual organization invoices. -

-
- {% endif %} - -
-{% endblock %} - -{% block extra_scripts %} - -{% endblock %} diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/security-overview.html.j2 b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/security-overview.html.j2 deleted file mode 100644 index bdebd0e6..00000000 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/security-overview.html.j2 +++ /dev/null @@ -1,518 +0,0 @@ -{# - Security Overview Report Template - - Comprehensive single-org security report with: - - Active threat alerts - - Executive summary cards - - Platform breakdown with detection counts - - Detection timeline charts - - MITRE ATT&CK coverage - - Detection rules inventory - - Attack analysis details - - Top detections table - - DATA ACCURACY GUARDRAILS: - - This template ONLY displays data provided in the context - - Missing data shows "N/A" or "Data unavailable" - - All warnings are displayed in a dedicated section - - No data is fabricated, estimated, or interpolated -#} -{% extends 'base.html.j2' %} - -{% block title %}{{ data.org_name | default('Organization') }} - Security Overview{% endblock %} - -{% block report_title %}Comprehensive Security Report{% endblock %} - -{% block content %} -
- - {# ================================================================ - ACTIVE THREAT ALERT BANNER (if threats detected) - GUARDRAIL: Only shown if data.active_threats exists - ================================================================ #} - {% if data.active_threats and data.active_threats | length > 0 %} - {% for threat in data.active_threats %} -
-
- - - - {{ threat.title | default('Active Threat Detected') }} -
-
- {% if threat.attacker_ip %} -
- Attacker IP -
{{ threat.attacker_ip }}
-
- {% endif %} - {% if threat.target_host %} -
- Target -
{{ threat.target_host }}{% if threat.target_ip %} ({{ threat.target_ip }}){% endif %}
-
- {% endif %} - {% if threat.attack_type %} -
- Attack Type - {{ threat.attack_type }} -
- {% endif %} - {% if threat.target_account %} -
- Target Account - {{ threat.target_account }} -
- {% endif %} -
-
- {% endfor %} - {% endif %} - - {# ================================================================ - EXECUTIVE SUMMARY CARDS - GUARDRAIL: All values come directly from data.summary - ================================================================ #} -
-

Executive Summary

- -
- {# Total Sensors Card #} -
-
- - - - -
-
- - {% if data.summary.sensors_total is defined %} - {{ data.summary.sensors_total | format_number }} - {% else %} - N/A - {% endif %} - - Total Sensors - {% if data.summary.sensors_online is defined %} - {{ data.summary.sensors_online | format_number }} online - {% endif %} -
-
- - {# Detection Rules Card #} -
-
- - - - - - -
-
- - {% if data.summary.rules_total is defined %} - {{ data.summary.rules_total | format_number }} - {% else %} - N/A - {% endif %} - - Detection Rules - Active D&R rules -
-
- - {# Detections Today Card #} -
-
- - - - - -
-
- - {% if data.summary.detections_total is defined %} - {{ data.summary.detections_total | format_number }} - {% else %} - N/A - {% endif %} - - Detections - {% if data.summary.detections_timeframe %} - {{ data.summary.detections_timeframe }} - {% endif %} -
-
- - {# MITRE Techniques Card #} -
-
- - - -
-
- - {% if data.summary.mitre_techniques is defined %} - {{ data.summary.mitre_techniques | format_number }} - {% else %} - N/A - {% endif %} - - MITRE Techniques - Coverage mapped -
-
- - {# Active Threats Card #} -
-
- - - - - -
-
- - {% if data.active_threats is defined %} - {{ data.active_threats | length }} - {% else %} - 0 - {% endif %} - - Active Threats - {% if data.active_threats and data.active_threats | length > 0 %} - {{ data.active_threats[0].attack_type | default('Requires attention') }} - {% endif %} -
-
-
-
- - {# ================================================================ - PLATFORM DISTRIBUTION WITH DETECTIONS - GUARDRAIL: Only shows data if platforms exists - ================================================================ #} -
-

Fleet Overview

- -
- {# Platform Distribution Chart #} -
-

Platform Distribution

-

{{ data.summary.sensors_total | default('N/A') }} sensors across platforms

- {% if data.platforms and data.platforms | length > 0 %} -
- -
- - {% else %} -
- πŸ“Š -

Platform data not available

-
- {% endif %} -
- - {# Platform Breakdown Table with Detections #} -
-

Platform Breakdown

-

Sensors and detections by operating system

- {% if data.platforms and data.platforms | length > 0 %} -
- - - - - - - - - - - - {% set total_sensors = data.summary.sensors_total | default(1) %} - {% set total_detections = data.summary.detections_total | default(1) %} - {% for platform in data.platforms %} - - - - - - - - {% endfor %} - - - - - - - - - - -
PlatformSensors%DetectionsDet %
- - {{ platform.name }} - - {{ platform.sensors | default(0) | format_number }} - {{ ((platform.sensors | default(0) / total_sensors) * 100) | round(1) }}% - - {{ platform.detections | default(0) | format_number }} - - {{ ((platform.detections | default(0) / total_detections) * 100) | round(1) }}% -
Total{{ total_sensors | format_number }}100%{{ total_detections | format_number }}100%
-
- {% if data.platform_note %} -

- * {{ data.platform_note }} -

- {% endif %} - {% else %} -
- πŸ“‹ -

Platform breakdown not available

-
- {% endif %} -
-
-
- - {# ================================================================ - DETECTION TIMELINE - GUARDRAIL: Only shows if hourly_detections exists - ================================================================ #} - {% if data.hourly_detections and data.hourly_detections | length > 1 %} -
-

Detection Timeline

- -
-

Detections Over Time (Hourly)

-

{{ metadata.time_window.start_display | default('N/A') }} to {{ metadata.time_window.end_display | default('N/A') }}

-
- -
-
- -
- {% endif %} - - {# ================================================================ - MITRE ATT&CK COVERAGE - GUARDRAIL: Only shows if mitre_techniques exists - ================================================================ #} - {% if data.mitre_techniques and data.mitre_techniques | length > 0 %} -
-

MITRE ATT&CK Coverage

-

- Detection rules mapped to MITRE ATT&CK framework techniques -

-
- {% for technique in data.mitre_techniques %} -
{{ technique.id }} - {{ technique.name }}
- {% endfor %} -
-
- {% endif %} - - {# ================================================================ - DETECTION RULES INVENTORY - GUARDRAIL: Only shows if rules data exists - ================================================================ #} - {% if data.rules %} -
-

Detection Rules Inventory

- -
-
-
- {{ data.rules.total | default('N/A') | format_number }} - Total Rules -
-
- {% for category in data.rules.categories[:4] %} -
-
- {{ category.count | default('N/A') | format_number }} - {{ category.name }} -
-
- {% endfor %} -
- - {% if data.rules.category_badges and data.rules.category_badges | length > 0 %} -

Rule Categories

-
- {% for badge in data.rules.category_badges %} - {{ badge.name }} - {% endfor %} -
- {% endif %} -
- {% endif %} - - {# ================================================================ - ATTACK ANALYSIS DETAILS - GUARDRAIL: Only shows if attack_analysis exists - ================================================================ #} - {% if data.attack_analysis %} -
-

Attack Analysis: {{ data.attack_analysis.title | default('Threat Details') }}

- - {% if data.attack_analysis.summary %} -
-

Summary: {{ data.attack_analysis.summary }}

-
- {% endif %} - -
- - - - - - - - - {% for attr in data.attack_analysis.attributes %} - - - - - {% endfor %} - -
AttributeValue
{{ attr.name }} - {% if attr.type == 'ip' %} - {{ attr.value }} - {% elif attr.type == 'code' %} - {{ attr.value }} - {% elif attr.type == 'badge' %} - {{ attr.value }} - {% else %} - {{ attr.value }} - {% endif %} -
-
-
- {% endif %} - - {# ================================================================ - TOP DETECTIONS TABLE - GUARDRAIL: Only shows if top_detections exists - ================================================================ #} - {% if data.top_detections and data.top_detections | length > 0 %} -
-

Top Detections by Volume

- -
- - - - - - - - - - - - - {% set total = data.summary.detections_total | default(1) %} - {% for detection in data.top_detections %} - - - - - - - - - {% endfor %} - - - - - - - - -
#CategorySource HostEvent TypeCount%
{{ loop.index }} - - {{ detection.category }} - - {{ detection.host | default('N/A') }}{{ detection.event_type | default('N/A') }}{{ detection.count | format_number }} - {{ ((detection.count / total) * 100) | round(1) }}% -
Total (Top {{ data.top_detections | length }}) - {% set sum = data.top_detections | sum(attribute='count') %} - {{ sum | format_number }} - {{ ((sum / total) * 100) | round(1) }}%
-
-
- {% endif %} - - {# ================================================================ - WARNINGS AND ERRORS - GUARDRAIL: ALL warnings from source data MUST be displayed - ================================================================ #} - {% if warnings or errors %} -
-

Data Limitations & Warnings

- - {% if warnings and warnings | length > 0 %} -
- {% for warning in warnings %} -
- ⚠️ - {{ warning }} -
- {% endfor %} -
- {% endif %} - - {% if errors and errors | length > 0 %} -
- {% for error in errors %} -
- {{ error.component | default('Error') }} - {% if error.code %}{{ error.code }}{% endif %} -

{{ error.message | default('Unknown error') }}

-
- {% endfor %} -
- {% endif %} -
- {% endif %} - -
-{% endblock %} - -{% block extra_scripts %} - -{% endblock %} From 1262cadbdf2fed24231ccc26faef3b2a15255c13 Mon Sep 17 00:00:00 2001 From: Christopher Luft Date: Wed, 10 Dec 2025 13:51:30 -0800 Subject: [PATCH 03/11] Add MSSP executive and detection analytics report templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New JSON templates for the reporting skill: - mssp-executive-report.json: Fleet health overview with sensor status, detections, and per-org health metrics - detection-analytics-report.json: Detection volume, categories, trends, and rule performance across tenants πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../templates/detection-analytics-report.json | 319 ++++++++++++++++++ .../templates/mssp-executive-report.json | 300 ++++++++++++++++ 2 files changed, 619 insertions(+) create mode 100644 marketplace/plugins/lc-essentials/skills/reporting/templates/detection-analytics-report.json create mode 100644 marketplace/plugins/lc-essentials/skills/reporting/templates/mssp-executive-report.json diff --git a/marketplace/plugins/lc-essentials/skills/reporting/templates/detection-analytics-report.json b/marketplace/plugins/lc-essentials/skills/reporting/templates/detection-analytics-report.json new file mode 100644 index 00000000..e4e474fd --- /dev/null +++ b/marketplace/plugins/lc-essentials/skills/reporting/templates/detection-analytics-report.json @@ -0,0 +1,319 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "name": "detection-analytics-report", + "description": "Detection volume, categories, trends, and rule performance across tenants", + "version": "1.0", + "input": { + "required": { + "days": { + "type": "integer", + "description": "Number of days to analyze", + "enum": [7, 14, 30], + "default": 7 + } + }, + "optional": { + "scope": { + "type": "string", + "enum": ["single", "all"], + "default": "all", + "description": "single = one org (requires oid), all = all accessible orgs" + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID (required when scope=single)" + }, + "format": { + "type": "string", + "enum": ["json", "markdown"], + "default": "markdown", + "description": "Output format" + } + } + }, + "output": { + "type": "object", + "required": ["metadata", "data"], + "properties": { + "metadata": { + "type": "object", + "required": ["generated_at", "time_window", "scope"], + "properties": { + "generated_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when report was generated" + }, + "time_window": { + "type": "object", + "required": ["start", "end", "start_display", "end_display", "days"], + "properties": { + "start": { + "type": "integer", + "description": "Start timestamp in Unix epoch seconds" + }, + "end": { + "type": "integer", + "description": "End timestamp in Unix epoch seconds" + }, + "start_display": { + "type": "string", + "description": "Human-readable start time" + }, + "end_display": { + "type": "string", + "description": "Human-readable end time" + }, + "days": { + "type": "integer", + "description": "Number of days in the time window" + } + } + }, + "scope": { + "type": "string", + "enum": ["single", "all"], + "description": "Report scope" + }, + "tenant_count": { + "type": "integer", + "description": "Number of tenants in report (when scope=all)" + } + } + }, + "data": { + "type": "object", + "required": ["tenants"], + "properties": { + "tenants": { + "type": "array", + "description": "Per-tenant detection data", + "items": { + "type": "object", + "required": ["name", "oid", "status"], + "properties": { + "name": { + "type": "string", + "description": "Organization name" + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID" + }, + "status": { + "type": "string", + "enum": ["success", "error", "no_detections"], + "description": "Data collection status" + }, + "detection_count": { + "type": "integer", + "description": "Total detections in time window" + }, + "limit_reached": { + "type": "boolean", + "description": "Whether the 5000 detection limit was hit" + }, + "categories": { + "type": "array", + "description": "Detection breakdown by category/rule", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Category or rule name (from source_rule or cat field)" + }, + "count": { + "type": "integer", + "description": "Number of detections in this category" + }, + "percent": { + "type": "number", + "description": "Percentage of tenant's total detections" + } + } + } + }, + "severities": { + "type": "object", + "description": "Detection breakdown by severity", + "properties": { + "critical": { + "type": "integer", + "description": "Critical severity count" + }, + "high": { + "type": "integer", + "description": "High severity count" + }, + "medium": { + "type": "integer", + "description": "Medium severity count" + }, + "low": { + "type": "integer", + "description": "Low severity count" + }, + "info": { + "type": "integer", + "description": "Informational severity count" + } + } + }, + "top_hosts": { + "type": "array", + "description": "Hosts with most detections", + "items": { + "type": "object", + "required": ["hostname", "count"], + "properties": { + "hostname": { + "type": "string", + "description": "Host name" + }, + "sid": { + "type": "string", + "description": "Sensor ID" + }, + "count": { + "type": "integer", + "description": "Detection count for this host" + } + } + } + }, + "error_message": { + "type": "string", + "description": "Error details if status=error" + } + } + } + }, + "rollup": { + "type": "object", + "description": "Aggregate totals across all tenants", + "properties": { + "total_detections": { + "type": "integer", + "description": "Sum of all detections across tenants" + }, + "total_tenants": { + "type": "integer", + "description": "Total number of tenants processed" + }, + "tenants_with_detections": { + "type": "integer", + "description": "Tenants that had at least one detection" + }, + "tenants_at_limit": { + "type": "integer", + "description": "Tenants that hit the 5000 detection limit" + }, + "successful_count": { + "type": "integer", + "description": "Tenants with successful data retrieval" + }, + "failed_count": { + "type": "integer", + "description": "Tenants that failed data retrieval" + } + } + }, + "top_categories": { + "type": "array", + "description": "Top detection categories across all tenants", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Category or rule name" + }, + "count": { + "type": "integer", + "description": "Total count across all tenants" + }, + "tenant_count": { + "type": "integer", + "description": "Number of tenants with this detection type" + } + } + } + }, + "top_tenants": { + "type": "array", + "description": "Tenants ranked by detection volume", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Tenant name" + }, + "oid": { + "type": "string", + "description": "Organization ID" + }, + "count": { + "type": "integer", + "description": "Detection count" + }, + "limit_reached": { + "type": "boolean", + "description": "Whether limit was hit" + } + } + } + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Warning messages" + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "org_name": { + "type": "string" + }, + "oid": { + "type": "string" + }, + "error_message": { + "type": "string" + } + } + }, + "description": "Detailed error information per failed org" + } + } + }, + "data_sources": { + "description": "API calls used to populate this template (documentation only)", + "primary": { + "function": "get_historic_detections", + "parameters": { + "oid": "from input or list_user_orgs", + "start": "calculated: now - (days * 86400)", + "end": "calculated: now" + }, + "notes": "Returns up to 5000 detections per org. Extract source_rule or cat for categories, severity from detect field, hostname from routing." + }, + "supporting": { + "function": "list_user_orgs", + "parameters": {}, + "notes": "Used to get OID list when scope=all" + } + } +} diff --git a/marketplace/plugins/lc-essentials/skills/reporting/templates/mssp-executive-report.json b/marketplace/plugins/lc-essentials/skills/reporting/templates/mssp-executive-report.json new file mode 100644 index 00000000..c7835952 --- /dev/null +++ b/marketplace/plugins/lc-essentials/skills/reporting/templates/mssp-executive-report.json @@ -0,0 +1,300 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "name": "mssp-executive-report", + "description": "High-level fleet health overview for MSSP leadership - sensor status, detections, and per-org health", + "version": "1.0", + "input": { + "required": {}, + "optional": { + "days": { + "type": "integer", + "description": "Number of days for detection time window", + "enum": [7, 14, 30], + "default": 7 + }, + "scope": { + "type": "string", + "enum": ["single", "all"], + "default": "all", + "description": "single = one org (requires oid), all = all accessible orgs" + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID (required when scope=single)" + }, + "format": { + "type": "string", + "enum": ["json", "markdown"], + "default": "markdown", + "description": "Output format" + } + } + }, + "output": { + "type": "object", + "required": ["metadata", "data"], + "properties": { + "metadata": { + "type": "object", + "required": ["generated_at", "time_window", "organizations"], + "properties": { + "generated_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when report was generated" + }, + "time_window": { + "type": "object", + "required": ["start", "end", "start_display", "end_display", "days"], + "properties": { + "start": { + "type": "integer", + "description": "Start timestamp in Unix epoch seconds" + }, + "end": { + "type": "integer", + "description": "End timestamp in Unix epoch seconds" + }, + "start_display": { + "type": "string", + "description": "Human-readable start time" + }, + "end_display": { + "type": "string", + "description": "Human-readable end time" + }, + "days": { + "type": "integer", + "description": "Number of days in the time window" + } + } + }, + "organizations": { + "type": "object", + "required": ["total", "successful"], + "properties": { + "total": { + "type": "integer", + "description": "Total number of organizations attempted" + }, + "successful": { + "type": "integer", + "description": "Number of organizations successfully processed" + }, + "failed": { + "type": "integer", + "description": "Number of organizations that failed data collection" + }, + "success_rate": { + "type": "number", + "description": "Percentage of organizations successfully processed" + } + } + } + } + }, + "data": { + "type": "object", + "required": ["rollup", "organizations"], + "properties": { + "rollup": { + "type": "object", + "required": ["total_sensors", "online_sensors"], + "properties": { + "total_sensors": { + "type": "integer", + "description": "Total sensor count across all organizations" + }, + "online_sensors": { + "type": "integer", + "description": "Number of sensors currently online" + }, + "offline_sensors": { + "type": "integer", + "description": "Number of sensors currently offline (total - online)" + }, + "health_percent": { + "type": "number", + "description": "Fleet-wide health percentage (online/total * 100)" + }, + "total_detections": { + "type": "integer", + "description": "Total detections across all organizations in time window" + }, + "detection_limit_reached": { + "type": "boolean", + "description": "Whether any organization hit the 5000 detection limit" + }, + "orgs_at_limit": { + "type": "integer", + "description": "Number of organizations that hit the detection limit" + } + } + }, + "platforms": { + "type": "array", + "description": "Sensor count breakdown by platform", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Platform name (Windows, Linux, macOS, Chrome, USP Adapter, etc.)" + }, + "count": { + "type": "integer", + "description": "Number of sensors on this platform" + }, + "percent": { + "type": "number", + "description": "Percentage of total sensors" + } + } + } + }, + "organizations": { + "type": "array", + "description": "Per-organization health details", + "items": { + "type": "object", + "required": ["name", "oid", "status"], + "properties": { + "name": { + "type": "string", + "description": "Organization display name" + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID" + }, + "sensors_total": { + "type": "integer", + "description": "Total sensor count for this organization" + }, + "sensors_online": { + "type": "integer", + "description": "Online sensor count" + }, + "sensors_offline": { + "type": "integer", + "description": "Offline sensor count" + }, + "health": { + "type": "number", + "description": "Health percentage (0-100)" + }, + "detections": { + "type": ["integer", "null"], + "description": "Detection count in time window (null if data unavailable)" + }, + "detection_limit_reached": { + "type": "boolean", + "description": "Whether this org hit the 5000 detection limit" + }, + "status": { + "type": "string", + "enum": ["healthy", "warning", "critical", "error"], + "description": "Health status: healthy (>=90%), warning (70-89%), critical (<70%), error (no data)" + } + } + } + }, + "top_categories": { + "type": "array", + "description": "Top detection categories across all organizations", + "items": { + "type": "object", + "required": ["label", "value"], + "properties": { + "label": { + "type": "string", + "description": "Detection category name (from source_rule or cat field)" + }, + "value": { + "type": "integer", + "description": "Total detection count for this category" + } + } + } + }, + "top_orgs_by_detections": { + "type": "array", + "description": "Top organizations by detection volume", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string", + "description": "Organization name" + }, + "value": { + "type": "integer", + "description": "Detection count" + }, + "limit_reached": { + "type": "boolean", + "description": "Whether this org hit the detection limit" + } + } + } + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Warning messages" + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "org_name": { + "type": "string" + }, + "oid": { + "type": "string" + }, + "error_message": { + "type": "string" + } + } + }, + "description": "Detailed error information per failed org" + } + } + }, + "data_sources": { + "description": "API calls used to populate this template (documentation only)", + "primary": [ + { + "function": "list_sensors", + "parameters": { + "oid": "from input or list_user_orgs" + }, + "notes": "Returns sensor list with hostname, plat, last_seen. Online status determined by last_seen timestamp." + }, + { + "function": "get_historic_detections", + "parameters": { + "oid": "from input or list_user_orgs", + "start": "calculated from days input", + "end": "current time in seconds" + }, + "notes": "Returns detections in time window. Limit of 5000 per org. Requires insight.det.get permission." + } + ], + "supporting": { + "function": "list_user_orgs", + "parameters": {}, + "notes": "Used to get OID list when scope=all" + } + } +} From deaceec5994745977d128e9eb6996aa08a3a9727 Mon Sep 17 00:00:00 2001 From: Christopher Luft Date: Wed, 10 Dec 2025 15:03:33 -0800 Subject: [PATCH 04/11] Add customer health report template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Combines sensor coverage, detection activity, and health scoring into a single customer success tracking report. Includes: - Fleet-wide health rollup with online/offline sensor counts - Per-customer health status (healthy/warning/critical/inactive) - Detection severity breakdown and top categories - Attention items highlighting issues requiring follow-up πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../templates/customer-health-report.json | 459 ++++++++++++++++++ 1 file changed, 459 insertions(+) create mode 100644 marketplace/plugins/lc-essentials/skills/reporting/templates/customer-health-report.json diff --git a/marketplace/plugins/lc-essentials/skills/reporting/templates/customer-health-report.json b/marketplace/plugins/lc-essentials/skills/reporting/templates/customer-health-report.json new file mode 100644 index 00000000..22b3b15c --- /dev/null +++ b/marketplace/plugins/lc-essentials/skills/reporting/templates/customer-health-report.json @@ -0,0 +1,459 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "name": "customer-health-report", + "description": "Comprehensive customer health report combining sensor coverage, detection activity, and health metrics for customer success tracking", + "version": "1.0", + "input": { + "required": {}, + "optional": { + "days": { + "type": "integer", + "description": "Number of days for detection and activity time window", + "enum": [7, 14, 30], + "default": 30 + }, + "scope": { + "type": "string", + "enum": ["single", "all"], + "default": "all", + "description": "single = one org (requires oid), all = all accessible orgs" + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID (required when scope=single)" + }, + "format": { + "type": "string", + "enum": ["json", "markdown"], + "default": "markdown", + "description": "Output format" + } + } + }, + "output": { + "type": "object", + "required": ["metadata", "data"], + "properties": { + "metadata": { + "type": "object", + "required": ["generated_at", "time_window", "organizations"], + "properties": { + "generated_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when report was generated" + }, + "time_window": { + "type": "object", + "required": ["start", "end", "start_display", "end_display", "days"], + "properties": { + "start": { + "type": "integer", + "description": "Start timestamp in Unix epoch seconds" + }, + "end": { + "type": "integer", + "description": "End timestamp in Unix epoch seconds" + }, + "start_display": { + "type": "string", + "description": "Human-readable start time" + }, + "end_display": { + "type": "string", + "description": "Human-readable end time" + }, + "days": { + "type": "integer", + "description": "Number of days in the time window" + } + } + }, + "organizations": { + "type": "object", + "required": ["total", "successful"], + "properties": { + "total": { + "type": "integer", + "description": "Total number of organizations attempted" + }, + "successful": { + "type": "integer", + "description": "Number of organizations successfully processed" + }, + "failed": { + "type": "integer", + "description": "Number of organizations that failed data collection" + }, + "success_rate": { + "type": "number", + "description": "Percentage of organizations successfully processed" + } + } + } + } + }, + "data": { + "type": "object", + "required": ["rollup", "customers"], + "properties": { + "rollup": { + "type": "object", + "description": "Fleet-wide summary metrics", + "required": ["total_customers", "total_sensors"], + "properties": { + "total_customers": { + "type": "integer", + "description": "Total number of customer organizations" + }, + "total_sensors": { + "type": "integer", + "description": "Total sensor count across all customers" + }, + "online_sensors": { + "type": "integer", + "description": "Number of sensors currently online" + }, + "offline_sensors": { + "type": "integer", + "description": "Number of sensors currently offline" + }, + "fleet_health_percent": { + "type": "number", + "description": "Fleet-wide health percentage (online/total * 100)" + }, + "total_detections": { + "type": "integer", + "description": "Total detections across all customers in time window" + }, + "customers_healthy": { + "type": "integer", + "description": "Number of customers with healthy status (>=90% sensors online)" + }, + "customers_warning": { + "type": "integer", + "description": "Number of customers with warning status (70-89% online)" + }, + "customers_critical": { + "type": "integer", + "description": "Number of customers with critical status (<70% online)" + }, + "customers_inactive": { + "type": "integer", + "description": "Number of customers with no sensors or no activity" + } + } + }, + "health_distribution": { + "type": "object", + "description": "Distribution of customer health statuses", + "properties": { + "healthy": { + "type": "number", + "description": "Percentage of customers with healthy status" + }, + "warning": { + "type": "number", + "description": "Percentage of customers with warning status" + }, + "critical": { + "type": "number", + "description": "Percentage of customers with critical status" + }, + "inactive": { + "type": "number", + "description": "Percentage of customers with inactive status" + } + } + }, + "platforms": { + "type": "array", + "description": "Sensor count breakdown by platform across all customers", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Platform name (Windows, Linux, macOS, Chrome, USP Adapter, etc.)" + }, + "count": { + "type": "integer", + "description": "Number of sensors on this platform" + }, + "percent": { + "type": "number", + "description": "Percentage of total sensors" + } + } + } + }, + "customers": { + "type": "array", + "description": "Per-customer health details", + "items": { + "type": "object", + "required": ["name", "oid", "health_status"], + "properties": { + "name": { + "type": "string", + "description": "Customer organization name" + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID" + }, + "health_status": { + "type": "string", + "enum": ["healthy", "warning", "critical", "inactive", "error"], + "description": "Overall health status: healthy (>=90%), warning (70-89%), critical (<70%), inactive (no sensors), error (data unavailable)" + }, + "health_score": { + "type": "number", + "description": "Numeric health score (0-100) based on sensor uptime" + }, + "sensors": { + "type": "object", + "description": "Sensor metrics for this customer", + "properties": { + "total": { + "type": "integer", + "description": "Total sensor count" + }, + "online": { + "type": "integer", + "description": "Online sensor count" + }, + "offline": { + "type": "integer", + "description": "Offline sensor count" + }, + "offline_24h": { + "type": "integer", + "description": "Sensors offline for less than 24 hours" + }, + "offline_7d": { + "type": "integer", + "description": "Sensors offline for 1-7 days" + }, + "offline_30d": { + "type": "integer", + "description": "Sensors offline for more than 7 days" + } + } + }, + "detections": { + "type": "object", + "description": "Detection metrics for this customer", + "properties": { + "total": { + "type": ["integer", "null"], + "description": "Total detections in time window (null if unavailable)" + }, + "limit_reached": { + "type": "boolean", + "description": "Whether the 5000 detection limit was hit" + }, + "severities": { + "type": "object", + "description": "Breakdown by severity level", + "properties": { + "critical": { + "type": "integer" + }, + "high": { + "type": "integer" + }, + "medium": { + "type": "integer" + }, + "low": { + "type": "integer" + }, + "info": { + "type": "integer" + } + } + }, + "top_categories": { + "type": "array", + "description": "Top 5 detection categories for this customer", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + } + } + }, + "platforms": { + "type": "array", + "description": "Sensor breakdown by platform for this customer", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + }, + "last_activity": { + "type": "string", + "format": "date-time", + "description": "Timestamp of most recent sensor activity" + }, + "attention_items": { + "type": "array", + "description": "Items requiring attention for this customer", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["offline_sensors", "high_severity_detections", "stale_sensors", "no_recent_detections", "detection_limit"], + "description": "Type of attention item" + }, + "severity": { + "type": "string", + "enum": ["info", "warning", "critical"], + "description": "Severity of the attention item" + }, + "message": { + "type": "string", + "description": "Human-readable description" + }, + "count": { + "type": "integer", + "description": "Count associated with this item (e.g., number of offline sensors)" + } + } + } + } + } + } + }, + "attention_summary": { + "type": "object", + "description": "Summary of items requiring attention across all customers", + "properties": { + "critical_items": { + "type": "integer", + "description": "Total critical attention items" + }, + "warning_items": { + "type": "integer", + "description": "Total warning attention items" + }, + "customers_needing_attention": { + "type": "array", + "description": "List of customers with critical issues", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "oid": { + "type": "string" + }, + "issues": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "top_detection_categories": { + "type": "array", + "description": "Top detection categories across all customers", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Detection category name" + }, + "count": { + "type": "integer", + "description": "Total count across all customers" + }, + "customer_count": { + "type": "integer", + "description": "Number of customers with this detection type" + } + } + } + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Warning messages" + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "org_name": { + "type": "string" + }, + "oid": { + "type": "string" + }, + "error_message": { + "type": "string" + } + } + }, + "description": "Detailed error information per failed customer" + } + } + }, + "data_sources": { + "description": "API calls used to populate this template (documentation only)", + "primary": [ + { + "function": "list_sensors", + "parameters": { + "oid": "from input or list_user_orgs" + }, + "notes": "Returns sensor list with hostname, plat, last_seen. Online status determined by last_seen timestamp. Used for sensor health metrics." + }, + { + "function": "get_historic_detections", + "parameters": { + "oid": "from input or list_user_orgs", + "start": "calculated from days input", + "end": "current time in seconds" + }, + "notes": "Returns detections in time window. Limit of 5000 per org. Extract severity, categories for breakdown. Requires insight.det.get permission." + } + ], + "supporting": { + "function": "list_user_orgs", + "parameters": {}, + "notes": "Used to get OID list when scope=all" + } + } +} From 5dd703cbe438a8e0c318926c39a328fbf93fd06e Mon Sep 17 00:00:00 2001 From: Christopher Luft Date: Wed, 10 Dec 2025 20:32:40 -0800 Subject: [PATCH 05/11] Add visualization templates to graphic-output for all report types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 3 new schemas: fleet-health, detection-analytics, customer-health - Add 3 new Jinja2 templates for visualizing report data - Update billing-summary schema/template to match reporting skill output - Update SKILL.md with new template documentation - Update reporting-templates.md with execution flows for all 4 reports Templates use schema-based decoupling - no direct dependency on reporting skill. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../commands/reporting-templates.md | 184 ++++- .../skills/graphic-output/SKILL.md | 203 +++++- .../schemas/billing-summary.json | 167 +++-- .../schemas/customer-health.json | 545 +++++++++++++++ .../schemas/detection-analytics.json | 392 +++++++++++ .../graphic-output/schemas/fleet-health.json | 343 +++++++++ .../templates/reports/billing-summary.html.j2 | 114 ++- .../templates/reports/customer-health.html.j2 | 654 ++++++++++++++++++ .../reports/detection-analytics.html.j2 | 492 +++++++++++++ .../templates/reports/fleet-health.html.j2 | 495 +++++++++++++ 10 files changed, 3418 insertions(+), 171 deletions(-) create mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/schemas/customer-health.json create mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/schemas/detection-analytics.json create mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/schemas/fleet-health.json create mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/customer-health.html.j2 create mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/detection-analytics.html.j2 create mode 100644 marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/fleet-health.html.j2 diff --git a/marketplace/plugins/lc-essentials/commands/reporting-templates.md b/marketplace/plugins/lc-essentials/commands/reporting-templates.md index f5029f0f..b4c43e8b 100644 --- a/marketplace/plugins/lc-essentials/commands/reporting-templates.md +++ b/marketplace/plugins/lc-essentials/commands/reporting-templates.md @@ -16,7 +16,10 @@ AskUserQuestion( "question": "Which report template would you like to generate?", "header": "Template", "options": [ - {"label": "Monthly Billing Report", "description": "Usage statistics and billing data for customer invoicing with roll-up totals"} + {"label": "Monthly Billing Report", "description": "Usage statistics and billing data for customer invoicing with roll-up totals"}, + {"label": "MSSP Executive Report", "description": "High-level fleet health overview with sensor status and detection activity"}, + {"label": "Detection Analytics Report", "description": "Detection volume, categories, severity breakdown across tenants"}, + {"label": "Customer Health Report", "description": "Comprehensive customer health with sensor coverage and attention items"} ], "multiSelect": false }] @@ -75,10 +78,145 @@ AskUserQuestion( --- +### Template: MSSP Executive Report + +**Purpose**: High-level fleet health overview for MSSP leadership - sensor status, detection activity, and per-org health. + +**Data Collection** (reporting skill): +- Use template: `mssp-executive-report.json` +- List all organizations +- Per-org: + - Sensor inventory via `list_sensors` + - Detection counts via `get_historic_detections` (last N days) + - Calculate online/offline counts from last_seen timestamps +- Aggregate roll-up: + - Total sensors (online/offline) + - Fleet health percentage + - Total detections + - Detection limit flags + +**Visualization** (graphic-output skill): +- **Summary Cards**: + - Total Sensors + - Online Sensors + - Fleet Health % + - Total Detections +- **Distribution Charts**: + - Platform distribution donut chart + - Organization health bar chart +- **Top Categories**: Bar chart of top detection categories +- **Per-Organization Table**: + - Organization name, sensors, online, offline, health %, detections, status + - LIMIT badge for orgs at detection limit + +**Time Range**: Prompt user for time window (7, 14, or 30 days). + +**Output**: `/tmp/fleet-health-{days}d.html` + +**Template Used**: `fleet-health` (uses fleet-health.html.j2) + +**JSON Schema**: `fleet-health.json` + +--- + +### Template: Detection Analytics Report + +**Purpose**: Detection volume, categories, severity breakdown, and trends across tenants for security analysis. + +**Data Collection** (reporting skill): +- Use template: `detection-analytics-report.json` +- List all organizations +- Per-org: + - Detection data via `get_historic_detections` (last N days) + - Extract categories from source_rule or cat field + - Extract severity from detection metadata + - Identify top hosts by detection count +- Aggregate roll-up: + - Total detections across all tenants + - Severity breakdown (critical/high/medium/low/info) + - Top categories across all tenants + - Detection limit tracking + +**Visualization** (graphic-output skill): +- **Summary Cards**: + - Total Detections + - Tenants with Detections + - Tenants at Limit + - Critical Severity Count +- **Severity Distribution**: Stacked bar showing severity breakdown +- **Top Categories**: Horizontal bar chart +- **Top Tenants**: Horizontal bar chart by detection volume +- **Per-Tenant Table**: + - Tenant name, detection count, critical, high, medium counts + - Category pills, LIMIT badges + - Status column + +**Time Range**: Prompt user for time window (7, 14, or 30 days). + +**Output**: `/tmp/detection-analytics-{days}d.html` + +**Template Used**: `detection-analytics` (uses detection-analytics.html.j2) + +**JSON Schema**: `detection-analytics.json` + +--- + +### Template: Customer Health Report + +**Purpose**: Comprehensive customer health combining sensor coverage, detection activity, and health metrics for customer success tracking. + +**Data Collection** (reporting skill): +- Use template: `customer-health-report.json` +- List all organizations +- Per-org: + - Sensor inventory via `list_sensors` + - Detection data via `get_historic_detections` (last N days) + - Calculate health score (online/total * 100) + - Classify health status (healthy β‰₯90%, warning 70-89%, critical <70%) + - Identify attention items: + - Offline sensors + - Stale sensors (offline > 7 days) + - High severity detections + - Detection limit reached +- Aggregate roll-up: + - Health distribution (healthy/warning/critical/inactive counts) + - Fleet-wide health percentage + - Attention summary + +**Visualization** (graphic-output skill): +- **Attention Banner**: Critical/warning items summary +- **Summary Cards**: + - Total Customers + - Total Sensors (online count) + - Fleet Health % + - Total Detections +- **Health Distribution**: Grid showing healthy/warning/critical/inactive counts +- **Charts**: + - Health distribution donut chart + - Platform distribution donut chart + - Top detection categories bar chart +- **Per-Customer Table**: + - Customer name, health score, sensors, online, offline, detections + - Attention badges (critical/warning) + - Health status +- **Customers Needing Attention**: Expanded cards for flagged customers + +**Time Range**: Prompt user for time window (7, 14, or 30 days). + +**Output**: `/tmp/customer-health-{days}d.html` + +**Template Used**: `customer-health` (uses customer-health.html.j2) + +**JSON Schema**: `customer-health.json` + +--- + ## Execution Flow Once the user selects a template: +### For Monthly Billing Report: + 1. **Confirm Billing Period**: Use `AskUserQuestion` to get the billing period ``` AskUserQuestion( @@ -95,15 +233,45 @@ Once the user selects a template: ) ``` 2. **Confirm Scope**: Ask if they want all orgs or a specific subset -3. **Collect Data**: Spawn `org-reporter` agents in parallel to collect data from each organization - - Include billing period (year, month) in the prompt so agents call `get_org_invoice_url` with `format: "simple_json"` -4. **Aggregate Results**: - - Calculate roll-up totals (total cost, total sensors, avg cost/sensor) - - Structure data per the `billing-summary.json` schema -5. **Generate HTML**: Spawn `html-renderer` agent to create the visualization dashboard - - Use template `billing-summary` +3. **Collect Data**: Spawn `org-reporter` agents in parallel to collect billing data +4. **Aggregate Results**: Calculate roll-up totals per `billing-summary.json` schema +5. **Generate HTML**: Spawn `html-renderer` with template `billing-summary` 6. **Open in Browser**: Automatically open the generated HTML file +### For MSSP Executive, Detection Analytics, or Customer Health Reports: + +1. **Confirm Time Window**: Use `AskUserQuestion` to get the time window + ``` + AskUserQuestion( + questions=[{ + "question": "What time window should the report cover?", + "header": "Time Window", + "options": [ + {"label": "Last 7 days", "description": "Recent activity overview"}, + {"label": "Last 14 days", "description": "Two-week analysis"}, + {"label": "Last 30 days", "description": "Monthly comprehensive view (Recommended)"} + ], + "multiSelect": false + }] + ) + ``` +2. **Confirm Scope**: Ask if they want all orgs or a specific subset +3. **Calculate Timestamps**: Use bash to get accurate Unix timestamps + ```bash + date +%s # Current time (end) + date -d '7 days ago' +%s # 7 days ago (start) + ``` +4. **Collect Data**: Spawn parallel agents (one per org) to collect: + - For MSSP Executive: sensors + detections + - For Detection Analytics: detections with categories/severity + - For Customer Health: sensors + detections + health metrics +5. **Aggregate Results**: Structure data per the appropriate schema: + - MSSP Executive β†’ `fleet-health.json` + - Detection Analytics β†’ `detection-analytics.json` + - Customer Health β†’ `customer-health.json` +6. **Generate HTML**: Spawn `html-renderer` with the appropriate template +7. **Open in Browser**: Automatically open the generated HTML file + **Browser Launch Command:** ```bash # Option 1: Direct file open diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/SKILL.md b/marketplace/plugins/lc-essentials/skills/graphic-output/SKILL.md index 20bf65a3..176b721b 100644 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/SKILL.md +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/SKILL.md @@ -192,9 +192,12 @@ This skill: | Template | Description | Best For | |----------|-------------|----------| +| `billing-summary` | Multi-tenant billing roll-up with per-tenant breakdown, SKU details, cost distribution charts | Billing/cost analysis | +| `fleet-health` | Sensor health, platform distribution, detection activity across organizations | MSSP fleet monitoring | +| `detection-analytics` | Detection volume, categories, severity breakdown, and trends across tenants | Detection analysis | +| `customer-health` | Comprehensive customer health combining sensor coverage, detection activity, and health metrics | Customer success tracking | | `mssp-dashboard` | Multi-tenant overview with aggregate metrics, org table, charts | MSSP/multi-org reports | | `security-overview` | Single-org comprehensive security report with platform breakdown, MITRE coverage, detection timeline, rules inventory | Individual org deep-dive | -| `billing-summary` | Multi-tenant billing roll-up with per-tenant breakdown, SKU details, cost distribution charts | Billing/cost analysis | | `custom-report` | **Component-based flexible reports** - build any report by specifying which components to include | Ad-hoc/custom reports | **Note:** Use `custom-report` when none of the predefined templates fit your needs. It supports composable components that can be arranged in any order. @@ -281,11 +284,13 @@ If integrating with the reporting skill, the data structure should include: Choose the appropriate template based on data type: -- **Multi-org data** β†’ `mssp-dashboard` -- **Single org data** β†’ `org-detail` -- **Health-focused** β†’ `sensor-health` -- **Detection-focused** β†’ `detection-summary` - **Billing/usage** β†’ `billing-summary` +- **Fleet health (sensors/detections)** β†’ `fleet-health` +- **Detection analysis** β†’ `detection-analytics` +- **Customer success** β†’ `customer-health` +- **Multi-org data** β†’ `mssp-dashboard` +- **Single org data** β†’ `security-overview` +- **Custom/flexible** β†’ `custom-report` ### Step 3: Spawn HTML Renderer @@ -480,6 +485,184 @@ errors # Array of error objects - All `warnings` displayed prominently - No data fabrication under any circumstances +### Fleet Health (`fleet-health`) + +**Purpose:** Sensor health, platform distribution, and detection activity across multiple organizations for MSSP fleet monitoring. + +**Visualizations:** +- Summary cards (total sensors, online sensors, fleet health %, total detections) +- Platform distribution donut chart +- Organization health horizontal bar chart +- Top detection categories bar chart +- Top organizations by detection volume +- Per-organization sortable table with health status +- Detection limit warnings prominently displayed +- Warnings and errors section + +**Required Data Fields:** +``` +metadata.generated_at # When data was collected +metadata.time_window.start_display # Time range start +metadata.time_window.end_display # Time range end +metadata.time_window.days # Number of days in window + +data.rollup.total_sensors # Total sensor count +data.rollup.online_sensors # Online sensor count +``` + +**Optional Data Fields (shown if present, "N/A" if missing):** +``` +metadata.organizations.total # Total org count +metadata.organizations.successful # Successful org count +metadata.organizations.success_rate # Success percentage + +data.rollup.offline_sensors # Offline sensor count +data.rollup.health_percent # Fleet health percentage +data.rollup.total_detections # Total detections +data.rollup.detection_limit_reached # Boolean limit flag +data.rollup.orgs_at_limit # Count of orgs at limit + +data.platforms[] # Platform breakdown array +data.organizations[] # Per-org details array +data.top_categories[] # Top detection categories +data.top_orgs_by_detections[] # Top orgs by detection volume + +warnings # Array of warning strings +errors # Array of error objects +``` + +**Guardrail Behavior:** +- If `health_percent` missing but online/total provided: Calculates as online/total * 100 +- If `platforms` empty: Shows "Platform data not available" +- Detection limit warning banner shown if `detection_limit_reached` is true +- All orgs at limit marked with "LIMIT" badge in table + +### Detection Analytics (`detection-analytics`) + +**Purpose:** Detection volume, categories, severity breakdown, and trends across multiple tenants for security analysis. + +**Visualizations:** +- Summary cards (total detections, tenants with detections, tenants at limit, critical severity count) +- Aggregate severity distribution bar (critical/high/medium/low/info) +- Top detection categories bar chart +- Top tenants by detection volume +- Per-tenant table with severity breakdown and categories +- Detection limit warnings prominently displayed + +**Required Data Fields:** +``` +metadata.generated_at # When data was collected +metadata.time_window.start_display # Time range start +metadata.time_window.end_display # Time range end +metadata.time_window.days # Number of days in window + +data.tenants[] # Per-tenant detection data +data.tenants[].name # Tenant name +data.tenants[].oid # Tenant OID +data.tenants[].status # success/error/no_detections +``` + +**Optional Data Fields (shown if present, "N/A" if missing):** +``` +metadata.scope # single/all +metadata.tenant_count # Total tenant count + +data.tenants[].detection_count # Detection count +data.tenants[].limit_reached # Boolean limit flag +data.tenants[].categories[] # Category breakdown +data.tenants[].severities # Severity breakdown object +data.tenants[].top_hosts[] # Top hosts by detections + +data.rollup.total_detections # Aggregate detection count +data.rollup.total_tenants # Total tenants +data.rollup.tenants_with_detections # Tenants with detections +data.rollup.tenants_at_limit # Tenants at limit +data.rollup.severities # Aggregate severity breakdown + +data.top_categories[] # Top categories across all +data.top_tenants[] # Top tenants by volume + +warnings # Array of warning strings +errors # Array of error objects +``` + +**Guardrail Behavior:** +- Detection limit warning banner shown if any tenant at limit +- Tenants at limit marked with "LIMIT" badge +- Severity bars only shown if severity data available +- Critical severity count highlighted in red + +### Customer Health (`customer-health`) + +**Purpose:** Comprehensive customer health combining sensor coverage, detection activity, and health metrics for customer success tracking. + +**Visualizations:** +- Attention required banner (critical/warning items summary) +- Summary cards (customers, sensors, fleet health, detections) +- Customer health distribution grid (healthy/warning/critical/inactive counts) +- Health distribution donut chart +- Platform distribution donut chart +- Top detection categories bar chart +- Per-customer table with health score, sensors, detections, attention items +- Customers needing attention section with expanded cards +- Warnings and errors section + +**Required Data Fields:** +``` +metadata.generated_at # When data was collected +metadata.time_window.start_display # Time range start +metadata.time_window.end_display # Time range end +metadata.time_window.days # Number of days in window + +data.rollup.total_customers # Total customer count +data.rollup.total_sensors # Total sensor count + +data.customers[] # Per-customer details +data.customers[].name # Customer name +data.customers[].oid # Customer OID +data.customers[].health_status # healthy/warning/critical/inactive/error +``` + +**Optional Data Fields (shown if present, "N/A" if missing):** +``` +metadata.organizations.total # Total org count +metadata.organizations.successful # Successful org count +metadata.organizations.success_rate # Success percentage + +data.rollup.online_sensors # Online sensors +data.rollup.offline_sensors # Offline sensors +data.rollup.fleet_health_percent # Fleet health % +data.rollup.total_detections # Total detections +data.rollup.customers_healthy # Healthy customer count +data.rollup.customers_warning # Warning customer count +data.rollup.customers_critical # Critical customer count +data.rollup.customers_inactive # Inactive customer count + +data.health_distribution # Health distribution percentages +data.platforms[] # Platform breakdown +data.customers[].health_score # Numeric health score +data.customers[].sensors # Sensor metrics object +data.customers[].detections # Detection metrics object +data.customers[].attention_items[] # Attention items array + +data.attention_summary # Overall attention summary +data.attention_summary.critical_items # Critical item count +data.attention_summary.warning_items # Warning item count +data.attention_summary.customers_needing_attention[] + +data.top_detection_categories[] # Top categories across all + +warnings # Array of warning strings +errors # Array of error objects +``` + +**Guardrail Behavior:** +- Attention banner shown if any critical or warning items exist +- Customers needing attention highlighted with colored border +- Health scores color-coded (green β‰₯90%, amber 70-89%, red <70%) +- Rows with attention items have amber background +- All attention badges shown per customer + ## Chart Behavior with Missing Data ### Pie Chart @@ -628,16 +811,22 @@ skills/graphic-output/ β”œβ”€β”€ templates/ β”‚ β”œβ”€β”€ base.html.j2 # Base template with CSS, Chart.js utilities β”‚ └── reports/ +β”‚ β”œβ”€β”€ billing-summary.html.j2 # Multi-tenant billing report +β”‚ β”œβ”€β”€ fleet-health.html.j2 # Fleet health dashboard +β”‚ β”œβ”€β”€ detection-analytics.html.j2 # Detection analytics dashboard +β”‚ β”œβ”€β”€ customer-health.html.j2 # Customer health dashboard β”‚ β”œβ”€β”€ mssp-dashboard.html.j2 # Multi-tenant MSSP dashboard β”‚ β”œβ”€β”€ security-overview.html.j2 # Single-org security report -β”‚ β”œβ”€β”€ billing-summary.html.j2 # Multi-tenant billing report β”‚ └── custom-report.html.j2 # Component-based flexible reports β”œβ”€β”€ static/ β”‚ └── js/ β”‚ └── lc-charts.js # Chart.js utility functions └── schemas/ + β”œβ”€β”€ billing-summary.json # Schema for billing-summary data + β”œβ”€β”€ fleet-health.json # Schema for fleet-health data + β”œβ”€β”€ detection-analytics.json # Schema for detection-analytics data + β”œβ”€β”€ customer-health.json # Schema for customer-health data β”œβ”€β”€ mssp-report.json # Schema for mssp-dashboard data β”œβ”€β”€ security-overview.json # Schema for security-overview data - β”œβ”€β”€ billing-summary.json # Schema for billing-summary data └── custom-report.json # Schema for custom-report components ``` diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/billing-summary.json b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/billing-summary.json index 9f4b2d1d..f1d9f485 100644 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/billing-summary.json +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/billing-summary.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Multi-Tenant Billing Summary Data", - "description": "Schema for data input to the billing-summary template. All values must come from actual LimaCharlie billing API responses - no fabrication allowed.", + "title": "Billing Summary Visualization Data", + "description": "Schema for data input to the billing-summary template. Accepts output from the reporting skill's billing-report template. All values must come from actual LimaCharlie billing API responses - no fabrication allowed.", "type": "object", "required": ["metadata", "data"], "properties": { @@ -12,6 +12,7 @@ "properties": { "generated_at": { "type": "string", + "format": "date-time", "description": "ISO 8601 timestamp when data was collected", "examples": ["2025-12-10T14:32:45Z"] }, @@ -20,6 +21,11 @@ "description": "Billing period in human-readable format", "examples": ["November 2025", "December 2025"] }, + "scope": { + "type": "string", + "enum": ["single", "all"], + "description": "Report scope" + }, "tenant_count": { "type": "integer", "description": "Number of tenants with billing data retrieved", @@ -29,42 +35,20 @@ }, "data": { "type": "object", - "description": "Report data containing rollup totals and per-tenant details", - "required": ["rollup", "tenants"], + "description": "Report data containing per-tenant details and optional rollup", + "required": ["tenants"], "properties": { - "rollup": { - "type": "object", - "description": "Aggregate totals across all tenants - displayed in summary cards at top of report", - "required": ["total_cost", "total_sensors"], - "properties": { - "total_cost": { - "type": "number", - "description": "Total monthly cost in USD across all tenants (sum of all tenant costs)", - "examples": [12500.00, 45000.00, 125000.00] - }, - "total_sensors": { - "type": "integer", - "description": "Total sensor count across all tenants (sum of all tenant sensors)", - "examples": [203, 1500, 5000] - }, - "avg_cost_per_sensor": { - "type": "number", - "description": "Average cost per sensor (total_cost / total_sensors) - blended rate", - "examples": [61.58, 30.00, 25.00] - } - } - }, "tenants": { "type": "array", "description": "Per-tenant billing details - each item represents one organization", "items": { "type": "object", - "required": ["name", "oid"], + "required": ["name", "oid", "status"], "properties": { "name": { "type": "string", "description": "Organization display name", - "examples": ["Acme Corp", "TPS Reporting Solutions", "Client ABC Production"] + "examples": ["Acme Corp", "TPS Reporting Solutions"] }, "oid": { "type": "string", @@ -72,48 +56,41 @@ "description": "Organization ID (UUID)", "examples": ["cdadbaa4-265d-4549-9686-990731ee4091"] }, - "region": { + "status": { "type": "string", - "description": "Geographic region or data center location (optional)", - "examples": ["us-east", "eu-west", "apac"] - }, - "sensors": { - "type": "integer", - "description": "Total sensor count for this tenant", - "examples": [21, 50, 250] + "description": "Data collection status", + "enum": ["success", "error", "no_invoice"], + "examples": ["success"] }, "cost": { "type": "number", - "description": "Total monthly cost in USD for this tenant", + "description": "Total invoice amount in dollars (converted from cents)", "examples": [1250.00, 5000.00, 25000.00] }, - "status": { + "currency": { "type": "string", - "description": "Billing status for this tenant", - "enum": ["active", "draft", "no_usage", "error"], - "examples": ["active"] + "default": "usd", + "description": "Currency code" }, "skus": { "type": "array", - "description": "Individual SKU line items from the invoice", + "description": "Invoice line items", "items": { "type": "object", - "required": ["name", "amount"], + "required": ["description", "amount"], "properties": { - "name": { + "description": { "type": "string", - "description": "SKU name/description from invoice", + "description": "Line item description (SKU name)", "examples": [ "Sensor License - Windows", "Sensor License - Linux", - "Data Output (GB)", - "D&R Rule Evaluations", - "Artifact Storage (GB)" + "Data Output (GB)" ] }, "amount": { "type": "number", - "description": "Cost in USD for this SKU (converted from cents: API value / 100)", + "description": "Amount in dollars", "examples": [1000.00, 250.00, 500.00] }, "quantity": { @@ -123,27 +100,40 @@ } } } + }, + "invoice_url": { + "type": "string", + "format": "uri", + "description": "PDF download URL (expires)" + }, + "error_message": { + "type": "string", + "description": "Error details if status=error" } } } }, - "categories": { - "type": "array", - "description": "Cost breakdown by category for chart visualization (optional - aggregate SKUs by type)", - "items": { - "type": "object", - "required": ["name", "amount"], - "properties": { - "name": { - "type": "string", - "description": "Category name (grouped SKU type)", - "examples": ["Sensor Licenses", "Data Output", "Storage", "Evaluations"] - }, - "amount": { - "type": "number", - "description": "Total cost in USD for this category across all tenants", - "examples": [8500.00, 4000.00, 1500.00] - } + "rollup": { + "type": "object", + "description": "Aggregate totals across all tenants (optional, only when scope=all)", + "properties": { + "total_cost": { + "type": "number", + "description": "Sum of all tenant costs in dollars", + "examples": [12500.00, 45000.00] + }, + "total_tenants": { + "type": "integer", + "description": "Total number of tenants processed", + "examples": [24, 50] + }, + "successful_count": { + "type": "integer", + "description": "Tenants with successful data retrieval" + }, + "failed_count": { + "type": "integer", + "description": "Tenants that failed or had no invoice" } } } @@ -178,7 +168,7 @@ "error_message": { "type": "string", "description": "Error description", - "examples": ["Permission denied: missing billing.ctrl", "API timeout", "Invalid response"] + "examples": ["Permission denied: missing billing.ctrl", "API timeout"] } } } @@ -189,51 +179,52 @@ "metadata": { "generated_at": "2025-12-10T14:32:45Z", "period": "November 2025", + "scope": "all", "tenant_count": 24 }, "data": { - "rollup": { - "total_cost": 12500.00, - "total_sensors": 203, - "avg_cost_per_sensor": 61.58 - }, "tenants": [ { "name": "TPS Reporting Solutions", "oid": "aac9c41d-e0a3-4e7e-88b8-33936ab93238", - "region": "us-east", - "sensors": 21, + "status": "success", "cost": 1250.00, - "status": "active", + "currency": "usd", "skus": [ - {"name": "Sensor License - Windows", "amount": 800.00, "quantity": 16}, - {"name": "Sensor License - Linux", "amount": 250.00, "quantity": 5}, - {"name": "Data Output (GB)", "amount": 200.00, "quantity": "40 GB"} + {"description": "Sensor License - Windows", "amount": 800.00, "quantity": 16}, + {"description": "Sensor License - Linux", "amount": 250.00, "quantity": 5}, + {"description": "Data Output (GB)", "amount": 200.00, "quantity": "40 GB"} ] }, { "name": "lc-infrastructure", "oid": "efedcf87-318c-4c15-9f52-482606491223", - "sensors": 17, + "status": "success", "cost": 850.00, - "status": "active", "skus": [ - {"name": "Sensor License - Linux", "amount": 850.00, "quantity": 17} + {"description": "Sensor License - Linux", "amount": 850.00, "quantity": 17} ] + }, + { + "name": "Test Org", + "oid": "ddcc6630-20d1-4fd6-b16c-245c58a6d58e", + "status": "error", + "error_message": "Permission denied: missing billing.ctrl" } ], - "categories": [ - {"name": "Sensor Licenses", "amount": 10000.00}, - {"name": "Data Output", "amount": 2000.00}, - {"name": "Storage", "amount": 500.00} - ] + "rollup": { + "total_cost": 2100.00, + "total_tenants": 3, + "successful_count": 2, + "failed_count": 1 + } }, "warnings": [ - "12 organizations returned permission denied for billing data" + "1 organization returned permission denied for billing data" ], "errors": [ { - "org_name": "github-integration-test", + "org_name": "Test Org", "oid": "ddcc6630-20d1-4fd6-b16c-245c58a6d58e", "error_message": "Permission denied: missing billing.ctrl permission" } diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/customer-health.json b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/customer-health.json new file mode 100644 index 00000000..2a6dbe0c --- /dev/null +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/customer-health.json @@ -0,0 +1,545 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Customer Health Dashboard Data", + "description": "Schema for data input to the customer-health template. Visualizes comprehensive customer health combining sensor coverage, detection activity, and health metrics for customer success tracking. All values must come from actual LimaCharlie API responses - no fabrication allowed.", + "type": "object", + "required": ["metadata", "data"], + "properties": { + "metadata": { + "type": "object", + "description": "Report metadata - when and how data was collected", + "required": ["generated_at", "time_window"], + "properties": { + "generated_at": { + "type": "string", + "description": "ISO 8601 timestamp when data was collected", + "examples": ["2025-12-10T14:32:45Z"] + }, + "time_window": { + "type": "object", + "description": "Time range for detection and activity data", + "required": ["start_display", "end_display", "days"], + "properties": { + "start": { + "type": "integer", + "description": "Start timestamp in Unix epoch seconds" + }, + "end": { + "type": "integer", + "description": "End timestamp in Unix epoch seconds" + }, + "start_display": { + "type": "string", + "description": "Human-readable start time", + "examples": ["2025-11-10 00:00:00 UTC"] + }, + "end_display": { + "type": "string", + "description": "Human-readable end time", + "examples": ["2025-12-10 23:59:59 UTC"] + }, + "days": { + "type": "integer", + "description": "Number of days in the time window", + "examples": [7, 14, 30] + } + } + }, + "organizations": { + "type": "object", + "description": "Organization processing summary", + "properties": { + "total": { + "type": "integer", + "description": "Total number of organizations attempted" + }, + "successful": { + "type": "integer", + "description": "Number of organizations successfully processed" + }, + "failed": { + "type": "integer", + "description": "Number of organizations that failed data collection" + }, + "success_rate": { + "type": "number", + "description": "Percentage of organizations successfully processed" + } + } + } + } + }, + "data": { + "type": "object", + "description": "Report data containing customer health metrics", + "required": ["rollup", "customers"], + "properties": { + "rollup": { + "type": "object", + "description": "Fleet-wide summary metrics - displayed in summary cards", + "required": ["total_customers", "total_sensors"], + "properties": { + "total_customers": { + "type": "integer", + "description": "Total number of customer organizations" + }, + "total_sensors": { + "type": "integer", + "description": "Total sensor count across all customers" + }, + "online_sensors": { + "type": "integer", + "description": "Number of sensors currently online" + }, + "offline_sensors": { + "type": "integer", + "description": "Number of sensors currently offline" + }, + "fleet_health_percent": { + "type": "number", + "description": "Fleet-wide health percentage (online/total * 100)" + }, + "total_detections": { + "type": "integer", + "description": "Total detections across all customers in time window" + }, + "customers_healthy": { + "type": "integer", + "description": "Number of customers with healthy status (>=90% sensors online)" + }, + "customers_warning": { + "type": "integer", + "description": "Number of customers with warning status (70-89% online)" + }, + "customers_critical": { + "type": "integer", + "description": "Number of customers with critical status (<70% online)" + }, + "customers_inactive": { + "type": "integer", + "description": "Number of customers with no sensors or no activity" + } + } + }, + "health_distribution": { + "type": "object", + "description": "Distribution of customer health statuses - displayed as donut chart", + "properties": { + "healthy": { + "type": "number", + "description": "Percentage of customers with healthy status" + }, + "warning": { + "type": "number", + "description": "Percentage of customers with warning status" + }, + "critical": { + "type": "number", + "description": "Percentage of customers with critical status" + }, + "inactive": { + "type": "number", + "description": "Percentage of customers with inactive status" + } + } + }, + "platforms": { + "type": "array", + "description": "Sensor count breakdown by platform across all customers", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Platform name (Windows, Linux, macOS, Chrome, USP Adapter, etc.)" + }, + "count": { + "type": "integer", + "description": "Number of sensors on this platform" + }, + "percent": { + "type": "number", + "description": "Percentage of total sensors" + } + } + } + }, + "customers": { + "type": "array", + "description": "Per-customer health details - displayed in sortable table with expandable details", + "items": { + "type": "object", + "required": ["name", "oid", "health_status"], + "properties": { + "name": { + "type": "string", + "description": "Customer organization name" + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID" + }, + "health_status": { + "type": "string", + "enum": ["healthy", "warning", "critical", "inactive", "error"], + "description": "Overall health status" + }, + "health_score": { + "type": "number", + "description": "Numeric health score (0-100) based on sensor uptime" + }, + "sensors": { + "type": "object", + "description": "Sensor metrics for this customer", + "properties": { + "total": { + "type": "integer", + "description": "Total sensor count" + }, + "online": { + "type": "integer", + "description": "Online sensor count" + }, + "offline": { + "type": "integer", + "description": "Offline sensor count" + }, + "offline_24h": { + "type": "integer", + "description": "Sensors offline for less than 24 hours" + }, + "offline_7d": { + "type": "integer", + "description": "Sensors offline for 1-7 days" + }, + "offline_30d": { + "type": "integer", + "description": "Sensors offline for more than 7 days" + } + } + }, + "detections": { + "type": "object", + "description": "Detection metrics for this customer", + "properties": { + "total": { + "type": ["integer", "null"], + "description": "Total detections in time window (null if unavailable)" + }, + "limit_reached": { + "type": "boolean", + "description": "Whether the 5000 detection limit was hit" + }, + "severities": { + "type": "object", + "description": "Breakdown by severity level", + "properties": { + "critical": {"type": "integer"}, + "high": {"type": "integer"}, + "medium": {"type": "integer"}, + "low": {"type": "integer"}, + "info": {"type": "integer"} + } + }, + "top_categories": { + "type": "array", + "description": "Top 5 detection categories for this customer", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "count": {"type": "integer"} + } + } + } + } + }, + "platforms": { + "type": "array", + "description": "Sensor breakdown by platform for this customer", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "count": {"type": "integer"} + } + } + }, + "last_activity": { + "type": "string", + "format": "date-time", + "description": "Timestamp of most recent sensor activity" + }, + "attention_items": { + "type": "array", + "description": "Items requiring attention for this customer - displayed as badges/alerts", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["offline_sensors", "high_severity_detections", "stale_sensors", "no_recent_detections", "detection_limit"], + "description": "Type of attention item" + }, + "severity": { + "type": "string", + "enum": ["info", "warning", "critical"], + "description": "Severity of the attention item" + }, + "message": { + "type": "string", + "description": "Human-readable description" + }, + "count": { + "type": "integer", + "description": "Count associated with this item" + } + } + } + } + } + } + }, + "attention_summary": { + "type": "object", + "description": "Summary of items requiring attention across all customers - displayed as alert section", + "properties": { + "critical_items": { + "type": "integer", + "description": "Total critical attention items" + }, + "warning_items": { + "type": "integer", + "description": "Total warning attention items" + }, + "customers_needing_attention": { + "type": "array", + "description": "List of customers with critical issues", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "oid": {"type": "string"}, + "issues": { + "type": "array", + "items": {"type": "string"} + } + } + } + } + } + }, + "top_detection_categories": { + "type": "array", + "description": "Top detection categories across all customers - displayed as bar chart", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Detection category name" + }, + "count": { + "type": "integer", + "description": "Total count across all customers" + }, + "customer_count": { + "type": "integer", + "description": "Number of customers with this detection type" + } + } + } + } + } + }, + "warnings": { + "type": "array", + "description": "Data collection warnings - displayed prominently in alert box", + "items": { + "type": "string" + }, + "examples": [ + ["Detection data limited to 5000 per organization", "3 customers have sensors offline > 30 days"] + ] + }, + "errors": { + "type": "array", + "description": "Per-customer errors - displayed in collapsible error section", + "items": { + "type": "object", + "properties": { + "org_name": { + "type": "string", + "description": "Name of the organization that failed" + }, + "oid": { + "type": "string", + "description": "Organization ID (UUID)" + }, + "error_message": { + "type": "string", + "description": "Error description" + } + } + } + } + }, + "examples": [ + { + "metadata": { + "generated_at": "2025-12-10T14:32:45Z", + "time_window": { + "start": 1731196800, + "end": 1733875199, + "start_display": "2025-11-10 00:00:00 UTC", + "end_display": "2025-12-10 23:59:59 UTC", + "days": 30 + }, + "organizations": { + "total": 24, + "successful": 22, + "failed": 2, + "success_rate": 91.7 + } + }, + "data": { + "rollup": { + "total_customers": 24, + "total_sensors": 1500, + "online_sensors": 1350, + "offline_sensors": 150, + "fleet_health_percent": 90.0, + "total_detections": 45000, + "customers_healthy": 18, + "customers_warning": 4, + "customers_critical": 1, + "customers_inactive": 1 + }, + "health_distribution": { + "healthy": 75.0, + "warning": 16.7, + "critical": 4.2, + "inactive": 4.2 + }, + "platforms": [ + {"name": "Windows", "count": 850, "percent": 56.7}, + {"name": "Linux", "count": 400, "percent": 26.7}, + {"name": "macOS", "count": 150, "percent": 10.0}, + {"name": "USP Adapter", "count": 100, "percent": 6.6} + ], + "customers": [ + { + "name": "TPS Reporting Solutions", + "oid": "aac9c41d-e0a3-4e7e-88b8-33936ab93238", + "health_status": "healthy", + "health_score": 94.7, + "sensors": { + "total": 150, + "online": 142, + "offline": 8, + "offline_24h": 5, + "offline_7d": 2, + "offline_30d": 1 + }, + "detections": { + "total": 1250, + "limit_reached": false, + "severities": { + "critical": 5, + "high": 45, + "medium": 200, + "low": 500, + "info": 500 + }, + "top_categories": [ + {"name": "SENSITIVE_PROCESS_ACCESS", "count": 500}, + {"name": "NEW_PROCESS", "count": 350} + ] + }, + "platforms": [ + {"name": "Windows", "count": 100}, + {"name": "Linux", "count": 50} + ], + "last_activity": "2025-12-10T14:30:00Z", + "attention_items": [] + }, + { + "name": "Acme Corp", + "oid": "cdadbaa4-265d-4549-9686-990731ee4091", + "health_status": "warning", + "health_score": 75.0, + "sensors": { + "total": 200, + "online": 150, + "offline": 50, + "offline_24h": 10, + "offline_7d": 25, + "offline_30d": 15 + }, + "detections": { + "total": 5000, + "limit_reached": true, + "severities": { + "critical": 25, + "high": 175, + "medium": 800, + "low": 2000, + "info": 2000 + } + }, + "attention_items": [ + { + "type": "offline_sensors", + "severity": "warning", + "message": "50 sensors offline", + "count": 50 + }, + { + "type": "stale_sensors", + "severity": "critical", + "message": "15 sensors offline > 30 days", + "count": 15 + }, + { + "type": "detection_limit", + "severity": "warning", + "message": "Hit 5000 detection limit", + "count": 5000 + } + ] + } + ], + "attention_summary": { + "critical_items": 3, + "warning_items": 8, + "customers_needing_attention": [ + { + "name": "Acme Corp", + "oid": "cdadbaa4-265d-4549-9686-990731ee4091", + "issues": ["50 sensors offline", "15 stale sensors", "Detection limit reached"] + } + ] + }, + "top_detection_categories": [ + {"name": "NEW_PROCESS", "count": 15000, "customer_count": 22}, + {"name": "SENSITIVE_PROCESS_ACCESS", "count": 8500, "customer_count": 18}, + {"name": "DNS_REQUEST", "count": 6000, "customer_count": 15} + ] + }, + "warnings": [ + "Detection data limited to 5000 per organization", + "3 customers have sensors offline > 30 days" + ], + "errors": [ + { + "org_name": "Test Org", + "oid": "ddcc6630-20d1-4fd6-b16c-245c58a6d58e", + "error_message": "Permission denied: missing sensor.list permission" + } + ] + } + ] +} diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/detection-analytics.json b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/detection-analytics.json new file mode 100644 index 00000000..d63d1b6a --- /dev/null +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/detection-analytics.json @@ -0,0 +1,392 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Detection Analytics Dashboard Data", + "description": "Schema for data input to the detection-analytics template. Visualizes detection volume, categories, severity breakdown, and trends across tenants. All values must come from actual LimaCharlie API responses - no fabrication allowed.", + "type": "object", + "required": ["metadata", "data"], + "properties": { + "metadata": { + "type": "object", + "description": "Report metadata - when and how data was collected", + "required": ["generated_at", "time_window"], + "properties": { + "generated_at": { + "type": "string", + "description": "ISO 8601 timestamp when data was collected", + "examples": ["2025-12-10T14:32:45Z"] + }, + "time_window": { + "type": "object", + "description": "Time range for detection data", + "required": ["start_display", "end_display", "days"], + "properties": { + "start": { + "type": "integer", + "description": "Start timestamp in Unix epoch seconds" + }, + "end": { + "type": "integer", + "description": "End timestamp in Unix epoch seconds" + }, + "start_display": { + "type": "string", + "description": "Human-readable start time", + "examples": ["2025-12-03 00:00:00 UTC"] + }, + "end_display": { + "type": "string", + "description": "Human-readable end time", + "examples": ["2025-12-10 23:59:59 UTC"] + }, + "days": { + "type": "integer", + "description": "Number of days in the time window", + "examples": [7, 14, 30] + } + } + }, + "scope": { + "type": "string", + "enum": ["single", "all"], + "description": "Report scope" + }, + "tenant_count": { + "type": "integer", + "description": "Number of tenants in report (when scope=all)" + } + } + }, + "data": { + "type": "object", + "description": "Report data containing detection analytics", + "required": ["tenants"], + "properties": { + "tenants": { + "type": "array", + "description": "Per-tenant detection data - displayed in sortable table with expandable details", + "items": { + "type": "object", + "required": ["name", "oid", "status"], + "properties": { + "name": { + "type": "string", + "description": "Organization name" + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID" + }, + "status": { + "type": "string", + "enum": ["success", "error", "no_detections"], + "description": "Data collection status" + }, + "detection_count": { + "type": "integer", + "description": "Total detections in time window" + }, + "limit_reached": { + "type": "boolean", + "description": "Whether the 5000 detection limit was hit" + }, + "categories": { + "type": "array", + "description": "Detection breakdown by category/rule", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Category or rule name" + }, + "count": { + "type": "integer", + "description": "Number of detections in this category" + }, + "percent": { + "type": "number", + "description": "Percentage of tenant's total detections" + } + } + } + }, + "severities": { + "type": "object", + "description": "Detection breakdown by severity - displayed as stacked bar", + "properties": { + "critical": { + "type": "integer", + "description": "Critical severity count" + }, + "high": { + "type": "integer", + "description": "High severity count" + }, + "medium": { + "type": "integer", + "description": "Medium severity count" + }, + "low": { + "type": "integer", + "description": "Low severity count" + }, + "info": { + "type": "integer", + "description": "Informational severity count" + } + } + }, + "top_hosts": { + "type": "array", + "description": "Hosts with most detections", + "items": { + "type": "object", + "required": ["hostname", "count"], + "properties": { + "hostname": { + "type": "string", + "description": "Host name" + }, + "sid": { + "type": "string", + "description": "Sensor ID" + }, + "count": { + "type": "integer", + "description": "Detection count for this host" + } + } + } + }, + "error_message": { + "type": "string", + "description": "Error details if status=error" + } + } + } + }, + "rollup": { + "type": "object", + "description": "Aggregate totals across all tenants - displayed in summary cards", + "properties": { + "total_detections": { + "type": "integer", + "description": "Sum of all detections across tenants" + }, + "total_tenants": { + "type": "integer", + "description": "Total number of tenants processed" + }, + "tenants_with_detections": { + "type": "integer", + "description": "Tenants that had at least one detection" + }, + "tenants_at_limit": { + "type": "integer", + "description": "Tenants that hit the 5000 detection limit" + }, + "successful_count": { + "type": "integer", + "description": "Tenants with successful data retrieval" + }, + "failed_count": { + "type": "integer", + "description": "Tenants that failed data retrieval" + }, + "severities": { + "type": "object", + "description": "Aggregate severity breakdown across all tenants", + "properties": { + "critical": {"type": "integer"}, + "high": {"type": "integer"}, + "medium": {"type": "integer"}, + "low": {"type": "integer"}, + "info": {"type": "integer"} + } + } + } + }, + "top_categories": { + "type": "array", + "description": "Top detection categories across all tenants - displayed as bar chart", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Category or rule name" + }, + "count": { + "type": "integer", + "description": "Total count across all tenants" + }, + "tenant_count": { + "type": "integer", + "description": "Number of tenants with this detection type" + } + } + } + }, + "top_tenants": { + "type": "array", + "description": "Tenants ranked by detection volume - displayed as horizontal bar chart", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Tenant name" + }, + "oid": { + "type": "string", + "description": "Organization ID" + }, + "count": { + "type": "integer", + "description": "Detection count" + }, + "limit_reached": { + "type": "boolean", + "description": "Whether limit was hit" + } + } + } + } + } + }, + "warnings": { + "type": "array", + "description": "Data collection warnings - displayed prominently in alert box", + "items": { + "type": "string" + }, + "examples": [ + ["5 tenants hit the 5000 detection limit", "Severity data unavailable for 3 tenants"] + ] + }, + "errors": { + "type": "array", + "description": "Per-tenant errors - displayed in collapsible error section", + "items": { + "type": "object", + "properties": { + "org_name": { + "type": "string", + "description": "Name of the organization that failed" + }, + "oid": { + "type": "string", + "description": "Organization ID (UUID)" + }, + "error_message": { + "type": "string", + "description": "Error description" + } + } + } + } + }, + "examples": [ + { + "metadata": { + "generated_at": "2025-12-10T14:32:45Z", + "time_window": { + "start": 1733270400, + "end": 1733875199, + "start_display": "2025-12-03 00:00:00 UTC", + "end_display": "2025-12-10 23:59:59 UTC", + "days": 7 + }, + "scope": "all", + "tenant_count": 24 + }, + "data": { + "tenants": [ + { + "name": "TPS Reporting Solutions", + "oid": "aac9c41d-e0a3-4e7e-88b8-33936ab93238", + "status": "success", + "detection_count": 1250, + "limit_reached": false, + "categories": [ + {"name": "SENSITIVE_PROCESS_ACCESS", "count": 500, "percent": 40.0}, + {"name": "NEW_PROCESS", "count": 350, "percent": 28.0}, + {"name": "NETWORK_CONNECTIONS", "count": 200, "percent": 16.0} + ], + "severities": { + "critical": 5, + "high": 45, + "medium": 200, + "low": 500, + "info": 500 + }, + "top_hosts": [ + {"hostname": "WORKSTATION-01", "sid": "abc123", "count": 150}, + {"hostname": "SERVER-DC01", "sid": "def456", "count": 120} + ] + }, + { + "name": "Acme Corp", + "oid": "cdadbaa4-265d-4549-9686-990731ee4091", + "status": "success", + "detection_count": 5000, + "limit_reached": true, + "categories": [ + {"name": "NEW_PROCESS", "count": 2500, "percent": 50.0}, + {"name": "DNS_REQUEST", "count": 1500, "percent": 30.0} + ], + "severities": { + "critical": 25, + "high": 175, + "medium": 800, + "low": 2000, + "info": 2000 + } + } + ], + "rollup": { + "total_detections": 45000, + "total_tenants": 24, + "tenants_with_detections": 20, + "tenants_at_limit": 5, + "successful_count": 22, + "failed_count": 2, + "severities": { + "critical": 150, + "high": 1200, + "medium": 8000, + "low": 18000, + "info": 17650 + } + }, + "top_categories": [ + {"name": "NEW_PROCESS", "count": 15000, "tenant_count": 22}, + {"name": "SENSITIVE_PROCESS_ACCESS", "count": 8500, "tenant_count": 18}, + {"name": "DNS_REQUEST", "count": 6000, "tenant_count": 15}, + {"name": "NETWORK_CONNECTIONS", "count": 5500, "tenant_count": 20}, + {"name": "FILE_CREATE", "count": 4000, "tenant_count": 19} + ], + "top_tenants": [ + {"name": "GlobalTech Industries", "oid": "abc123", "count": 5000, "limit_reached": true}, + {"name": "Acme Corp", "oid": "def456", "count": 5000, "limit_reached": true}, + {"name": "Nexus Systems", "oid": "ghi789", "count": 4200, "limit_reached": false} + ] + }, + "warnings": [ + "5 tenants hit the 5000 detection limit - actual counts may be higher", + "Severity data based on detection metadata where available" + ], + "errors": [ + { + "org_name": "Test Org", + "oid": "ddcc6630-20d1-4fd6-b16c-245c58a6d58e", + "error_message": "Permission denied: missing insight.det.get permission" + } + ] + } + ] +} diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/fleet-health.json b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/fleet-health.json new file mode 100644 index 00000000..4d432637 --- /dev/null +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/schemas/fleet-health.json @@ -0,0 +1,343 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Fleet Health Dashboard Data", + "description": "Schema for data input to the fleet-health template. Visualizes sensor health, platform distribution, and detection activity across multiple organizations. All values must come from actual LimaCharlie API responses - no fabrication allowed.", + "type": "object", + "required": ["metadata", "data"], + "properties": { + "metadata": { + "type": "object", + "description": "Report metadata - when and how data was collected", + "required": ["generated_at", "time_window"], + "properties": { + "generated_at": { + "type": "string", + "description": "ISO 8601 timestamp when data was collected", + "examples": ["2025-12-10T14:32:45Z"] + }, + "time_window": { + "type": "object", + "description": "Time range for detection data", + "required": ["start_display", "end_display", "days"], + "properties": { + "start": { + "type": "integer", + "description": "Start timestamp in Unix epoch seconds" + }, + "end": { + "type": "integer", + "description": "End timestamp in Unix epoch seconds" + }, + "start_display": { + "type": "string", + "description": "Human-readable start time", + "examples": ["2025-12-03 00:00:00 UTC"] + }, + "end_display": { + "type": "string", + "description": "Human-readable end time", + "examples": ["2025-12-10 23:59:59 UTC"] + }, + "days": { + "type": "integer", + "description": "Number of days in the time window", + "examples": [7, 14, 30] + } + } + }, + "organizations": { + "type": "object", + "description": "Organization processing summary", + "properties": { + "total": { + "type": "integer", + "description": "Total number of organizations attempted" + }, + "successful": { + "type": "integer", + "description": "Number of organizations successfully processed" + }, + "failed": { + "type": "integer", + "description": "Number of organizations that failed data collection" + }, + "success_rate": { + "type": "number", + "description": "Percentage of organizations successfully processed" + } + } + } + } + }, + "data": { + "type": "object", + "description": "Report data containing rollup totals and per-organization details", + "required": ["rollup"], + "properties": { + "rollup": { + "type": "object", + "description": "Aggregate totals across all organizations - displayed in summary cards", + "required": ["total_sensors", "online_sensors"], + "properties": { + "total_sensors": { + "type": "integer", + "description": "Total sensor count across all organizations", + "examples": [1500, 5000] + }, + "online_sensors": { + "type": "integer", + "description": "Number of sensors currently online", + "examples": [1350, 4500] + }, + "offline_sensors": { + "type": "integer", + "description": "Number of sensors currently offline (total - online)", + "examples": [150, 500] + }, + "health_percent": { + "type": "number", + "description": "Fleet-wide health percentage (online/total * 100)", + "examples": [90.0, 95.5] + }, + "total_detections": { + "type": "integer", + "description": "Total detections across all organizations in time window", + "examples": [12500, 50000] + }, + "detection_limit_reached": { + "type": "boolean", + "description": "Whether any organization hit the 5000 detection limit" + }, + "orgs_at_limit": { + "type": "integer", + "description": "Number of organizations that hit the detection limit" + } + } + }, + "platforms": { + "type": "array", + "description": "Sensor count breakdown by platform - displayed as donut chart", + "items": { + "type": "object", + "required": ["name", "count"], + "properties": { + "name": { + "type": "string", + "description": "Platform name (Windows, Linux, macOS, Chrome, USP Adapter, etc.)", + "examples": ["Windows", "Linux", "macOS"] + }, + "count": { + "type": "integer", + "description": "Number of sensors on this platform" + }, + "percent": { + "type": "number", + "description": "Percentage of total sensors" + } + } + } + }, + "organizations": { + "type": "array", + "description": "Per-organization health details - displayed in sortable table", + "items": { + "type": "object", + "required": ["name", "oid"], + "properties": { + "name": { + "type": "string", + "description": "Organization display name", + "examples": ["Acme Corp", "TPS Reporting Solutions"] + }, + "oid": { + "type": "string", + "format": "uuid", + "description": "Organization ID" + }, + "sensors_total": { + "type": "integer", + "description": "Total sensor count for this organization" + }, + "sensors_online": { + "type": "integer", + "description": "Online sensor count" + }, + "sensors_offline": { + "type": "integer", + "description": "Offline sensor count" + }, + "health": { + "type": "number", + "description": "Health percentage (0-100)" + }, + "detections": { + "type": ["integer", "null"], + "description": "Detection count in time window (null if data unavailable)" + }, + "detection_limit_reached": { + "type": "boolean", + "description": "Whether this org hit the 5000 detection limit" + }, + "status": { + "type": "string", + "enum": ["healthy", "warning", "critical", "error"], + "description": "Health status: healthy (>=90%), warning (70-89%), critical (<70%), error (no data)" + } + } + } + }, + "top_categories": { + "type": "array", + "description": "Top detection categories across all organizations - displayed as bar chart", + "items": { + "type": "object", + "required": ["label", "value"], + "properties": { + "label": { + "type": "string", + "description": "Detection category name (from source_rule or cat field)" + }, + "value": { + "type": "integer", + "description": "Total detection count for this category" + } + } + } + }, + "top_orgs_by_detections": { + "type": "array", + "description": "Top organizations by detection volume - displayed as horizontal bar chart", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string", + "description": "Organization name" + }, + "value": { + "type": "integer", + "description": "Detection count" + }, + "limit_reached": { + "type": "boolean", + "description": "Whether this org hit the detection limit" + } + } + } + } + } + }, + "warnings": { + "type": "array", + "description": "Data collection warnings - displayed in yellow alert box", + "items": { + "type": "string" + }, + "examples": [ + ["4 organizations hit the 5000 detection limit", "Detection counts are approximate for 2 orgs"] + ] + }, + "errors": { + "type": "array", + "description": "Per-organization errors - displayed in collapsible error section", + "items": { + "type": "object", + "properties": { + "org_name": { + "type": "string", + "description": "Name of the organization that failed" + }, + "oid": { + "type": "string", + "description": "Organization ID (UUID)" + }, + "error_message": { + "type": "string", + "description": "Error description" + } + } + } + } + }, + "examples": [ + { + "metadata": { + "generated_at": "2025-12-10T14:32:45Z", + "time_window": { + "start": 1733270400, + "end": 1733875199, + "start_display": "2025-12-03 00:00:00 UTC", + "end_display": "2025-12-10 23:59:59 UTC", + "days": 7 + }, + "organizations": { + "total": 24, + "successful": 22, + "failed": 2, + "success_rate": 91.7 + } + }, + "data": { + "rollup": { + "total_sensors": 1500, + "online_sensors": 1350, + "offline_sensors": 150, + "health_percent": 90.0, + "total_detections": 12500, + "detection_limit_reached": true, + "orgs_at_limit": 3 + }, + "platforms": [ + {"name": "Windows", "count": 850, "percent": 56.7}, + {"name": "Linux", "count": 400, "percent": 26.7}, + {"name": "macOS", "count": 150, "percent": 10.0}, + {"name": "USP Adapter", "count": 100, "percent": 6.6} + ], + "organizations": [ + { + "name": "TPS Reporting Solutions", + "oid": "aac9c41d-e0a3-4e7e-88b8-33936ab93238", + "sensors_total": 150, + "sensors_online": 142, + "sensors_offline": 8, + "health": 94.7, + "detections": 1250, + "detection_limit_reached": false, + "status": "healthy" + }, + { + "name": "Acme Corp", + "oid": "cdadbaa4-265d-4549-9686-990731ee4091", + "sensors_total": 200, + "sensors_online": 150, + "sensors_offline": 50, + "health": 75.0, + "detections": 5000, + "detection_limit_reached": true, + "status": "warning" + } + ], + "top_categories": [ + {"label": "SENSITIVE_PROCESS_ACCESS", "value": 3500}, + {"label": "NEW_PROCESS", "value": 2800}, + {"label": "NETWORK_CONNECTIONS", "value": 1500} + ], + "top_orgs_by_detections": [ + {"name": "Acme Corp", "value": 5000, "limit_reached": true}, + {"name": "GlobalTech", "value": 3200, "limit_reached": false} + ] + }, + "warnings": [ + "3 organizations hit the 5000 detection limit - actual counts may be higher" + ], + "errors": [ + { + "org_name": "Test Org", + "oid": "ddcc6630-20d1-4fd6-b16c-245c58a6d58e", + "error_message": "Permission denied: missing sensor.list permission" + } + ] + } + ] +} diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/billing-summary.html.j2 b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/billing-summary.html.j2 index 834b095f..113e3954 100644 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/billing-summary.html.j2 +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/billing-summary.html.j2 @@ -122,7 +122,7 @@
- {% if data.rollup.total_cost is defined %} + {% if data.rollup and data.rollup.total_cost is defined %} ${{ data.rollup.total_cost | format_number }} {% else %} N/A @@ -136,44 +136,41 @@
- - + +
- {% if data.rollup.total_sensors is defined %} - {{ data.rollup.total_sensors | format_number }} + {% if data.rollup.successful_count is defined %} + {{ data.rollup.successful_count }} {% else %} - N/A + {{ data.tenants | selectattr('status', 'equalto', 'success') | list | length }} {% endif %} - Total Sensors - Across {{ data.tenants | length }} organizations + Successful + Organizations with billing data
-
+
- - - - + + +
- {% if data.rollup.avg_cost_per_sensor is defined %} - ${{ data.rollup.avg_cost_per_sensor | round(2) }} - {% elif data.rollup.total_cost is defined and data.rollup.total_sensors is defined and data.rollup.total_sensors > 0 %} - ${{ (data.rollup.total_cost / data.rollup.total_sensors) | round(2) }} + {% if data.rollup.failed_count is defined %} + {{ data.rollup.failed_count }} {% else %} - N/A + {{ data.tenants | rejectattr('status', 'equalto', 'success') | list | length }} {% endif %} - Avg Cost/Sensor - Blended rate + Failed/No Invoice + Organizations without data
@@ -199,15 +196,15 @@ ================================================================ #} {% if data.tenants and data.tenants | length > 0 %}
-

Cost & Sensor Distribution

+

Cost Distribution

Cost Distribution by Tenant

-

Sensor Distribution by Tenant

- +

Status Breakdown

+
@@ -225,10 +222,7 @@ Organization - Region - Sensors Monthly Cost - Cost/Sensor % of Total Status @@ -237,36 +231,23 @@ {% for tenant in data.tenants %} {{ tenant.name | default('N/A') }} - - {% if tenant.region %} - {{ tenant.region }} - {% else %} - N/A - {% endif %} - - {{ tenant.sensors | default(0) | format_number }} ${{ tenant.cost | default(0) | format_number }} - {% if tenant.sensors and tenant.sensors > 0 and tenant.cost %} - ${{ (tenant.cost / tenant.sensors) | round(2) }} - {% else %} - - - {% endif %} - - - {% if data.rollup.total_cost and data.rollup.total_cost > 0 and tenant.cost %} + {% if data.rollup and data.rollup.total_cost and data.rollup.total_cost > 0 and tenant.cost %} {{ ((tenant.cost / data.rollup.total_cost) * 100) | round(1) }}% {% else %} - 0% + - {% endif %} - {% if tenant.status == 'active' %} - Active - {% elif tenant.status == 'draft' %} - Draft - {% elif tenant.cost == 0 or tenant.sensors == 0 %} - No Usage + {% if tenant.status == 'success' %} + Success + {% elif tenant.status == 'error' %} + Error + {% elif tenant.status == 'no_invoice' %} + No Invoice + {% elif tenant.cost == 0 %} + No Usage {% else %} {{ tenant.status | default('N/A') }} {% endif %} @@ -277,15 +258,8 @@ Total ({{ data.tenants | length }} tenants) - - {{ data.rollup.total_sensors | default(0) | format_number }} - ${{ data.rollup.total_cost | default(0) | format_number }} - - {% if data.rollup.avg_cost_per_sensor is defined %} - ${{ data.rollup.avg_cost_per_sensor | round(2) }} - {% endif %} - - 100% + ${{ (data.rollup.total_cost if data.rollup and data.rollup.total_cost else 0) | format_number }} + {% if data.rollup and data.rollup.total_cost %}100%{% else %}-{% endif %} @@ -312,7 +286,7 @@
{{ tenant.name }}
- {{ tenant.sensors | default(0) }} sensors | {{ tenant.region | default('Unknown') }} region + {{ tenant.skus | length if tenant.skus else 0 }} line items | {{ tenant.currency | default('usd') | upper }}
@@ -323,13 +297,13 @@ {% if tenant.skus and tenant.skus | length > 0 %} {% for sku in tenant.skus %}
- {{ sku.name }}{% if sku.quantity %} ({{ sku.quantity }}){% endif %} + {{ sku.description }}{% if sku.quantity %} ({{ sku.quantity }}){% endif %} ${{ sku.amount | format_number }}
{% endfor %} {% elif tenant.cost == 0 %}
- No active usage - subscription active but 0 sensors deployed + No billable usage this period
{% else %}
@@ -338,7 +312,7 @@ {% endif %}
-
+
{% endfor %} @@ -424,14 +398,18 @@ } }); - // Sensor Distribution Doughnut Chart - new Chart(document.getElementById('sensorDistChart'), { + // Status Distribution Doughnut Chart + {# Count tenants by status #} + {% set success_count = data.tenants | selectattr('status', 'equalto', 'success') | list | length %} + {% set error_count = data.tenants | selectattr('status', 'equalto', 'error') | list | length %} + {% set no_invoice_count = data.tenants | selectattr('status', 'equalto', 'no_invoice') | list | length %} + new Chart(document.getElementById('statusDistChart'), { type: 'doughnut', data: { - labels: [{% for t in data.tenants %}'{{ t.name | truncate(20) }} ({{ t.sensors | default(0) }})'{% if not loop.last %}, {% endif %}{% endfor %}], + labels: ['Success ({{ success_count }})', 'Error ({{ error_count }})', 'No Invoice ({{ no_invoice_count }})'], datasets: [{ - data: [{% for t in data.tenants %}{{ t.sensors | default(0) }}{% if not loop.last %}, {% endif %}{% endfor %}], - backgroundColor: ['#0ea5e9', '#22c55e', '#8b5cf6', '#f59e0b', '#ef4444', '#14b8a6'], + data: [{{ success_count }}, {{ error_count }}, {{ no_invoice_count }}], + backgroundColor: ['#22c55e', '#ef4444', '#94a3b8'], borderWidth: 0 }] }, @@ -441,7 +419,7 @@ plugins: { legend: { position: 'bottom' }, tooltip: { - callbacks: { label: (ctx) => ` ${ctx.raw} sensors` } + callbacks: { label: (ctx) => ` ${ctx.raw} organizations` } } } } diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/customer-health.html.j2 b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/customer-health.html.j2 new file mode 100644 index 00000000..7b87b582 --- /dev/null +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/customer-health.html.j2 @@ -0,0 +1,654 @@ +{# + Customer Health Dashboard Template + + Displays comprehensive customer health combining sensor coverage, detection activity, + and health metrics for customer success tracking. + + DATA ACCURACY GUARDRAILS: + - This template ONLY displays data provided in the context + - Missing data shows "N/A" or "Data unavailable" + - All warnings and attention items are displayed prominently + - No data is fabricated, estimated, or interpolated + - Health scores calculated only from provided sensor counts +#} +{% extends 'base.html.j2' %} + +{% block title %}Customer Health Report - {{ metadata.time_window.days | default(30) }} Day Overview{% endblock %} + +{% block extra_styles %} + +{% endblock %} + +{% block content %} +
+ + {# ================================================================ + REPORT HEADER + ================================================================ #} +
+ +
+

Customer Health Report

+
+
Time Window
{{ metadata.time_window.days | default(30) }} days
+
Generated
{{ metadata.generated_at | default('N/A') }}
+
Customers
+ {% if metadata.organizations %} + {{ metadata.organizations.successful | default(0) }} of {{ metadata.organizations.total | default(0) }} + {% else %} + {{ data.customers | length if data.customers else 'N/A' }} + {% endif %} +
+
+
+
+ + {# ================================================================ + ATTENTION REQUIRED BANNER + ================================================================ #} + {% if data.attention_summary and (data.attention_summary.critical_items or data.attention_summary.warning_items) %} +
+
+ 🚨 Attention Required +
+
+ {% if data.attention_summary.critical_items %} +
+ {{ data.attention_summary.critical_items }} critical issues +
+ {% endif %} + {% if data.attention_summary.warning_items %} +
+ {{ data.attention_summary.warning_items }} warnings +
+ {% endif %} + {% if data.attention_summary.customers_needing_attention %} +
+ {{ data.attention_summary.customers_needing_attention | length }} customers need attention +
+ {% endif %} +
+
+ {% endif %} + + {# ================================================================ + EXECUTIVE SUMMARY + ================================================================ #} +
+

Executive Summary

+ +
+
+
+ + + + + +
+
+ + {% if data.rollup and data.rollup.total_customers is defined %} + {{ data.rollup.total_customers }} + {% else %} + {{ data.customers | length if data.customers else 'N/A' }} + {% endif %} + + Total Customers + Active organizations +
+
+ +
+
+ + + + +
+
+ + {% if data.rollup and data.rollup.total_sensors is defined %} + {{ data.rollup.total_sensors }} + {% else %} + N/A + {% endif %} + + Total Sensors + + {{ data.rollup.online_sensors | default(0) if data.rollup else 0 }} online + +
+
+ +
+
+ + + +
+
+ + {% if data.rollup and data.rollup.fleet_health_percent is defined %} + {{ data.rollup.fleet_health_percent | round(1) }}% + {% else %} + N/A + {% endif %} + + Fleet Health + Average across all customers +
+
+ +
+
+ + + + + +
+
+ + {% if data.rollup and data.rollup.total_detections is defined %} + {{ data.rollup.total_detections }} + {% else %} + N/A + {% endif %} + + Total Detections + Last {{ metadata.time_window.days | default(30) }} days +
+
+
+ + {# Customer Health Distribution #} + {% if data.rollup and (data.rollup.customers_healthy is defined or data.rollup.customers_warning is defined) %} +
+
+ {{ data.rollup.customers_healthy | default(0) }} + Healthy + {% if data.health_distribution and data.health_distribution.healthy is defined %} + {{ data.health_distribution.healthy | round(1) }}% + {% endif %} +
+
+ {{ data.rollup.customers_warning | default(0) }} + Warning + {% if data.health_distribution and data.health_distribution.warning is defined %} + {{ data.health_distribution.warning | round(1) }}% + {% endif %} +
+
+ {{ data.rollup.customers_critical | default(0) }} + Critical + {% if data.health_distribution and data.health_distribution.critical is defined %} + {{ data.health_distribution.critical | round(1) }}% + {% endif %} +
+
+ {{ data.rollup.customers_inactive | default(0) }} + Inactive + {% if data.health_distribution and data.health_distribution.inactive is defined %} + {{ data.health_distribution.inactive | round(1) }}% + {% endif %} +
+
+ {% endif %} +
+ + {# ================================================================ + DISTRIBUTION CHARTS + ================================================================ #} +
+

Health & Platform Distribution

+
+
+

Customer Health Distribution

+

Breakdown by health status

+ {% if data.health_distribution %} +
+ +
+ {% else %} +
+ 📊 +

Health distribution data not available

+
+ {% endif %} +
+ +
+

Platform Distribution

+

Sensors by operating system

+ {% if data.platforms and data.platforms | length > 0 %} +
+ +
+ {% else %} +
+ 📊 +

Platform data not available

+
+ {% endif %} +
+
+
+ + {# ================================================================ + TOP DETECTION CATEGORIES + ================================================================ #} + {% if data.top_detection_categories and data.top_detection_categories | length > 0 %} +
+

Top Detection Categories

+
+
+ +
+
+
+ {% endif %} + + {# ================================================================ + CUSTOMER TABLE + ================================================================ #} +
+

Customer Details

+ + {% if data.customers and data.customers | length > 0 %} +
+ + + + + + + + + + + + + + + {% for customer in data.customers | sort(attribute='health_score', reverse=true) if customer.health_score is defined else data.customers %} + 0 %}style="background: #fffbeb;"{% endif %}> + + + + + + + + + + {% endfor %} + + + + + + + + + + + + + +
CustomerHealthSensorsOnlineOfflineDetectionsAttentionStatus
{{ customer.name | default('N/A') }} + {% if customer.health_score is defined %} + + {{ customer.health_score | round(1) }}% + + {% else %} + N/A + {% endif %} + {{ customer.sensors.total | default(0) if customer.sensors else 0 }}{{ customer.sensors.online | default(0) if customer.sensors else 0 }}{{ customer.sensors.offline | default(0) if customer.sensors else 0 }} + {% if customer.detections and customer.detections.total is not none %} + {{ customer.detections.total }} + {% if customer.detections.limit_reached %} + LIMIT + {% endif %} + {% else %} + N/A + {% endif %} + + {% if customer.attention_items and customer.attention_items | length > 0 %} + {% set critical_count = customer.attention_items | selectattr('severity', 'equalto', 'critical') | list | length %} + {% set warning_count = customer.attention_items | selectattr('severity', 'equalto', 'warning') | list | length %} + {% if critical_count > 0 %} + {{ critical_count }} critical + {% endif %} + {% if warning_count > 0 %} + {{ warning_count }} warning + {% endif %} + {% else %} + - + {% endif %} + + {% if customer.health_status == 'healthy' %} + Healthy + {% elif customer.health_status == 'warning' %} + Warning + {% elif customer.health_status == 'critical' %} + Critical + {% elif customer.health_status == 'inactive' %} + Inactive + {% else %} + {{ customer.health_status | default('Unknown') }} + {% endif %} +
Total ({{ data.customers | length }} customers) + {% if data.rollup and data.rollup.fleet_health_percent is defined %} + {{ data.rollup.fleet_health_percent | round(1) }}% + {% endif %} + {{ data.rollup.total_sensors | default(0) if data.rollup else 0 }}{{ data.rollup.online_sensors | default(0) if data.rollup else 0 }}{{ data.rollup.offline_sensors | default(0) if data.rollup else 0 }}{{ data.rollup.total_detections | default(0) if data.rollup else 0 }}
+
+ {% else %} +
+ 📋 +

No customer data available

+
+ {% endif %} +
+ + {# ================================================================ + CUSTOMERS NEEDING ATTENTION (Expanded Cards) + ================================================================ #} + {% if data.attention_summary and data.attention_summary.customers_needing_attention and data.attention_summary.customers_needing_attention | length > 0 %} +
+

⚠ Customers Needing Attention

+ + {% for cust in data.attention_summary.customers_needing_attention %} +
+
+
{{ cust.name }}
+
+ {% if cust.issues %} +
+ {% for issue in cust.issues %} + + {{ issue }} + + {% endfor %} +
+ {% endif %} +
+ {% endfor %} +
+ {% endif %} + + {# ================================================================ + WARNINGS AND ERRORS + ================================================================ #} + {% if warnings or errors %} +
+

Data Limitations & Warnings

+ + {% if warnings %} +
+ {% for warning in warnings %} +
+ + {{ warning }} +
+ {% endfor %} +
+ {% endif %} + + {% if errors %} +
+

Customers With Errors

+ {% for error in errors %} +
+ {{ error.org_name | default('Unknown') }} +

{{ error.error_message | default('Data collection failed') }}

+
+ {% endfor %} +
+ {% endif %} +
+ {% endif %} + + {# ================================================================ + DATA PROVENANCE FOOTER + ================================================================ #} +
+

Data Provenance

+
+
+
Generated
+
{{ metadata.generated_at | default('N/A') }}
+
+
+
Time Window
+
{{ metadata.time_window.start_display | default('N/A') }} to {{ metadata.time_window.end_display | default('N/A') }}
+
+
+
Customers
+
+ {% if metadata.organizations %} + {{ metadata.organizations.successful | default(0) }} of {{ metadata.organizations.total | default(0) }} + ({{ metadata.organizations.success_rate | default(0) | round(1) }}% success) + {% else %} + {{ data.customers | length if data.customers else 'N/A' }} + {% endif %} +
+
+
+
Data Source
+
LimaCharlie API
+
+
+
+

Data Accuracy: All values shown are from actual API responses. + Health scores are calculated as online/total sensors per customer. + No data has been estimated, interpolated, or fabricated.

+
+
+ +
+{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/detection-analytics.html.j2 b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/detection-analytics.html.j2 new file mode 100644 index 00000000..486008a9 --- /dev/null +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/detection-analytics.html.j2 @@ -0,0 +1,492 @@ +{# + Detection Analytics Dashboard Template + + Displays detection volume, categories, severity breakdown, and trends across tenants. + + DATA ACCURACY GUARDRAILS: + - This template ONLY displays data provided in the context + - Missing data shows "N/A" or "Data unavailable" + - All warnings are displayed in a dedicated section + - No data is fabricated, estimated, or interpolated + - Detection limit warnings are prominently displayed +#} +{% extends 'base.html.j2' %} + +{% block title %}Detection Analytics - {{ metadata.time_window.days | default(7) }} Day Analysis{% endblock %} + +{% block extra_styles %} + +{% endblock %} + +{% block content %} +
+ + {# ================================================================ + REPORT HEADER + ================================================================ #} +
+ +
+

Detection Analytics

+
+
Time Window
{{ metadata.time_window.days | default(7) }} days
+
Generated
{{ metadata.generated_at | default('N/A') }}
+
Tenants
{{ metadata.tenant_count | default(data.tenants | length if data.tenants else 'N/A') }}
+
+
+
+ + {# ================================================================ + DETECTION LIMIT WARNING (if applicable) + ================================================================ #} + {% if data.rollup and data.rollup.tenants_at_limit and data.rollup.tenants_at_limit > 0 %} +
+
+ Detection Limit Reached +
+

+ {{ data.rollup.tenants_at_limit }} tenant(s) hit the 5,000 detection limit. + Actual detection counts may be higher than shown. Tenants at limit are marked with a badge. +

+
+ {% endif %} + + {# ================================================================ + EXECUTIVE SUMMARY + GUARDRAIL: All values come directly from data.rollup + ================================================================ #} +
+

Executive Summary

+ +
+
+
+ + + + + +
+
+ + {% if data.rollup and data.rollup.total_detections is defined %} + {{ data.rollup.total_detections | default(0) }} + {% else %} + N/A + {% endif %} + + Total Detections + Last {{ metadata.time_window.days | default(7) }} days +
+
+ +
+
+ + + + + +
+
+ + {% if data.rollup and data.rollup.tenants_with_detections is defined %} + {{ data.rollup.tenants_with_detections }} + {% else %} + N/A + {% endif %} + + Tenants with Detections + + of {{ data.rollup.total_tenants | default(data.tenants | length if data.tenants else 'N/A') }} total + +
+
+ +
+
+ + + + + +
+
+ + {% if data.rollup and data.rollup.tenants_at_limit is defined %} + {{ data.rollup.tenants_at_limit }} + {% else %} + 0 + {% endif %} + + Tenants at Limit + Hit 5,000 detection cap +
+
+ +
+
+ + + + + +
+
+ + {% if data.rollup and data.rollup.severities and data.rollup.severities.critical is defined %} + {{ data.rollup.severities.critical }} + {% else %} + N/A + {% endif %} + + Critical Severity + Requires attention +
+
+
+ + {# Severity Breakdown Bar #} + {% if data.rollup and data.rollup.severities %} + {% set sev = data.rollup.severities %} + {% set total_sev = (sev.critical | default(0)) + (sev.high | default(0)) + (sev.medium | default(0)) + (sev.low | default(0)) + (sev.info | default(0)) %} + {% if total_sev > 0 %} +
+

Severity Distribution (All Tenants)

+
+ {% if sev.critical and sev.critical > 0 %} +
{{ sev.critical }}
+ {% endif %} + {% if sev.high and sev.high > 0 %} +
{{ sev.high }}
+ {% endif %} + {% if sev.medium and sev.medium > 0 %} +
{{ sev.medium }}
+ {% endif %} + {% if sev.low and sev.low > 0 %} +
{{ sev.low }}
+ {% endif %} + {% if sev.info and sev.info > 0 %} +
{{ sev.info }}
+ {% endif %} +
+
+
Critical ({{ sev.critical | default(0) }})
+
High ({{ sev.high | default(0) }})
+
Medium ({{ sev.medium | default(0) }})
+
Low ({{ sev.low | default(0) }})
+
Info ({{ sev.info | default(0) }})
+
+
+ {% endif %} + {% endif %} +
+ + {# ================================================================ + TOP DETECTION CATEGORIES + ================================================================ #} + {% if data.top_categories and data.top_categories | length > 0 %} +
+

Top Detection Categories

+

Most common detection types across all tenants

+
+
+ +
+
+
+ {% endif %} + + {# ================================================================ + TOP TENANTS BY DETECTION VOLUME + ================================================================ #} + {% if data.top_tenants and data.top_tenants | length > 0 %} +
+

Top Tenants by Detection Volume

+

Organizations with highest detection counts

+
+
+ +
+
+
+ {% endif %} + + {# ================================================================ + PER-TENANT TABLE + ================================================================ #} +
+

Tenant Detection Details

+ + {% if data.tenants and data.tenants | length > 0 %} +
+ + + + + + + + + + + + + + {% for tenant in data.tenants %} + + + + + + + + + + {% endfor %} + + + + + + + + + + + + +
TenantDetectionsCriticalHighMediumTop CategoriesStatus
{{ tenant.name | default('N/A') }} + {{ tenant.detection_count | default(0) }} + {% if tenant.limit_reached %} + LIMIT + {% endif %} + 0 %}style="color: var(--color-danger); font-weight: 600;"{% endif %}> + {{ tenant.severities.critical | default(0) if tenant.severities else 'N/A' }} + 0 %}style="color: #ea580c; font-weight: 600;"{% endif %}> + {{ tenant.severities.high | default(0) if tenant.severities else 'N/A' }} + + {{ tenant.severities.medium | default(0) if tenant.severities else 'N/A' }} + + {% if tenant.categories and tenant.categories | length > 0 %} + {% for cat in tenant.categories[:3] %} + {{ cat.name | truncate(20) }}{{ cat.count }} + {% endfor %} + {% else %} + N/A + {% endif %} + + {% if tenant.status == 'success' %} + Success + {% elif tenant.status == 'no_detections' %} + No Detections + {% elif tenant.status == 'error' %} + Error + {% else %} + {{ tenant.status | default('N/A') }} + {% endif %} +
Total ({{ data.tenants | length }} tenants){{ data.rollup.total_detections | default(0) if data.rollup else 0 }}{{ data.rollup.severities.critical | default(0) if data.rollup and data.rollup.severities else 0 }}{{ data.rollup.severities.high | default(0) if data.rollup and data.rollup.severities else 0 }}{{ data.rollup.severities.medium | default(0) if data.rollup and data.rollup.severities else 0 }}
+
+ {% else %} +
+ 📋 +

No tenant detection data available

+
+ {% endif %} +
+ + {# ================================================================ + WARNINGS AND ERRORS + ================================================================ #} + {% if warnings or errors %} +
+

Data Limitations & Warnings

+ + {% if warnings %} +
+ {% for warning in warnings %} +
+ + {{ warning }} +
+ {% endfor %} +
+ {% endif %} + + {% if errors %} +
+

Tenants With Errors

+ {% for error in errors %} +
+ {{ error.org_name | default('Unknown') }} +

{{ error.error_message | default('Data collection failed') }}

+
+ {% endfor %} +
+ {% endif %} +
+ {% endif %} + + {# ================================================================ + DATA PROVENANCE FOOTER + ================================================================ #} +
+

Data Provenance

+
+
+
Generated
+
{{ metadata.generated_at | default('N/A') }}
+
+
+
Time Window
+
{{ metadata.time_window.start_display | default('N/A') }} to {{ metadata.time_window.end_display | default('N/A') }}
+
+
+
Tenants
+
+ {% if data.rollup %} + {{ data.rollup.successful_count | default(0) }} successful, {{ data.rollup.failed_count | default(0) }} failed + {% else %} + {{ data.tenants | length if data.tenants else 'N/A' }} + {% endif %} +
+
+
+
Data Source
+
LimaCharlie Detection API
+
+
+
+

Data Accuracy: All detection counts are from actual API responses. + Organizations that hit the 5,000 detection limit are flagged. + No data has been estimated, interpolated, or fabricated.

+
+
+ +
+{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/fleet-health.html.j2 b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/fleet-health.html.j2 new file mode 100644 index 00000000..dff3212b --- /dev/null +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/fleet-health.html.j2 @@ -0,0 +1,495 @@ +{# + Fleet Health Dashboard Template + + Displays sensor health, platform distribution, and detection activity across organizations. + + DATA ACCURACY GUARDRAILS: + - This template ONLY displays data provided in the context + - Missing data shows "N/A" or "Data unavailable" + - All warnings are displayed in a dedicated section + - No data is fabricated, estimated, or interpolated + - Health percentages calculated only from provided online/total counts +#} +{% extends 'base.html.j2' %} + +{% block title %}Fleet Health Dashboard - {{ metadata.time_window.days | default(7) }} Day Overview{% endblock %} + +{% block extra_styles %} + +{% endblock %} + +{% block content %} +
+ + {# ================================================================ + REPORT HEADER + ================================================================ #} +
+ +
+

Fleet Health Dashboard

+
+
Time Window
{{ metadata.time_window.days | default(7) }} days
+
Generated
{{ metadata.generated_at | default('N/A') }}
+
Organizations
+ {% if metadata.organizations %} + {{ metadata.organizations.successful | default(0) }} of {{ metadata.organizations.total | default(0) }} + {% else %} + {{ data.organizations | length if data.organizations else 'N/A' }} + {% endif %} +
+
+
+
+ + {# ================================================================ + DETECTION LIMIT WARNING (if applicable) + ================================================================ #} + {% if data.rollup.detection_limit_reached %} +
+
+ Detection Limit Reached +
+

+ {{ data.rollup.orgs_at_limit | default('Some') }} organization(s) hit the 5,000 detection limit. + Actual detection counts may be higher than shown. +

+
+ {% endif %} + + {# ================================================================ + EXECUTIVE SUMMARY + GUARDRAIL: All values come directly from data.rollup + ================================================================ #} +
+

Executive Summary

+ +
+
+
+ + + + +
+
+ + {% if data.rollup.total_sensors is defined %} + {{ data.rollup.total_sensors | default(0) }} + {% else %} + N/A + {% endif %} + + Total Sensors + Across all organizations +
+
+ +
+
+ + + + +
+
+ + {% if data.rollup.online_sensors is defined %} + {{ data.rollup.online_sensors | default(0) }} + {% else %} + N/A + {% endif %} + + Online Sensors + Currently reporting +
+
+ +
+
+ + + +
+
+ + {% if data.rollup.health_percent is defined %} + {{ data.rollup.health_percent | round(1) }}% + {% elif data.rollup.online_sensors is defined and data.rollup.total_sensors is defined and data.rollup.total_sensors > 0 %} + {{ ((data.rollup.online_sensors / data.rollup.total_sensors) * 100) | round(1) }}% + {% else %} + N/A + {% endif %} + + Fleet Health + Online / Total +
+
+ +
+
+ + + + + +
+
+ + {% if data.rollup.total_detections is defined %} + {{ data.rollup.total_detections | default(0) }} + {% else %} + N/A + {% endif %} + + Total Detections + Last {{ metadata.time_window.days | default(7) }} days + {% if data.rollup.detection_limit_reached %} +
+ ⚠ {{ data.rollup.orgs_at_limit | default('Some') }} org(s) at limit +
+ {% endif %} +
+
+
+
+ + {# ================================================================ + DISTRIBUTION CHARTS + ================================================================ #} +
+

Platform & Organization Distribution

+
+
+

Platform Distribution

+

Sensors by operating system

+ {% if data.platforms and data.platforms | length > 0 %} +
+ +
+ {% else %} +
+ 📊 +

Platform data not available

+
+ {% endif %} +
+ +
+

Organization Health

+

Health status by organization

+ {% if data.organizations and data.organizations | length > 0 %} +
+ +
+ {% else %} +
+ 📊 +

Organization data not available

+
+ {% endif %} +
+
+
+ + {# ================================================================ + TOP DETECTION CATEGORIES + ================================================================ #} + {% if data.top_categories and data.top_categories | length > 0 %} +
+

Top Detection Categories

+
+
+ +
+
+
+ {% endif %} + + {# ================================================================ + TOP ORGANIZATIONS BY DETECTIONS + ================================================================ #} + {% if data.top_orgs_by_detections and data.top_orgs_by_detections | length > 0 %} +
+

Top Organizations by Detection Volume

+
+
+ +
+
+
+ {% endif %} + + {# ================================================================ + PER-ORGANIZATION TABLE + ================================================================ #} +
+

Organization Details

+ + {% if data.organizations and data.organizations | length > 0 %} +
+ + + + + + + + + + + + + + {% for org in data.organizations %} + + + + + + + + + + {% endfor %} + + + + + + + + + + + + +
OrganizationSensorsOnlineOfflineHealthDetectionsStatus
{{ org.name | default('N/A') }}{{ org.sensors_total | default(0) }}{{ org.sensors_online | default(0) }}{{ org.sensors_offline | default(0) }} + {% if org.health is defined %} + + {{ org.health | round(1) }}% + + {% else %} + N/A + {% endif %} + + {% if org.detections is not none %} + {{ org.detections }} + {% if org.detection_limit_reached %} + LIMIT + {% endif %} + {% else %} + N/A + {% endif %} + + {% if org.status == 'healthy' %} + Healthy + {% elif org.status == 'warning' %} + Warning + {% elif org.status == 'critical' %} + Critical + {% else %} + {{ org.status | default('Unknown') }} + {% endif %} +
Total ({{ data.organizations | length }} orgs){{ data.rollup.total_sensors | default(0) }}{{ data.rollup.online_sensors | default(0) }}{{ data.rollup.offline_sensors | default(0) }} + {% if data.rollup.health_percent is defined %} + {{ data.rollup.health_percent | round(1) }}% + {% endif %} + {{ data.rollup.total_detections | default(0) }}
+
+ {% else %} +
+ 📋 +

No organization data available

+
+ {% endif %} +
+ + {# ================================================================ + WARNINGS AND ERRORS + ================================================================ #} + {% if warnings or errors %} +
+

Data Limitations & Warnings

+ + {% if warnings %} +
+ {% for warning in warnings %} +
+ + {{ warning }} +
+ {% endfor %} +
+ {% endif %} + + {% if errors %} +
+

Organizations With Errors

+ {% for error in errors %} +
+ {{ error.org_name | default('Unknown') }} +

{{ error.error_message | default('Data collection failed') }}

+
+ {% endfor %} +
+ {% endif %} +
+ {% endif %} + + {# ================================================================ + DATA PROVENANCE FOOTER + ================================================================ #} +
+

Data Provenance

+
+
+
Generated
+
{{ metadata.generated_at | default('N/A') }}
+
+
+
Time Window
+
{{ metadata.time_window.start_display | default('N/A') }} to {{ metadata.time_window.end_display | default('N/A') }}
+
+
+
Organizations
+
+ {% if metadata.organizations %} + {{ metadata.organizations.successful | default(0) }} of {{ metadata.organizations.total | default(0) }} + ({{ metadata.organizations.success_rate | default(0) | round(1) }}% success) + {% else %} + {{ data.organizations | length if data.organizations else 'N/A' }} + {% endif %} +
+
+
+
Data Source
+
LimaCharlie API
+
+
+
+

Data Accuracy: All values shown are from actual API responses. + No data has been estimated, interpolated, or fabricated. + Health percentages calculated as online/total sensors.

+
+
+ +
+{% endblock %} + +{% block extra_scripts %} + +{% endblock %} From 01ca146198106e43b40c350a175d2eff5f4c85a6 Mon Sep 17 00:00:00 2001 From: Christopher Luft Date: Fri, 12 Dec 2025 08:49:44 -0800 Subject: [PATCH 06/11] Fix reporting command to require time period selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add explicit 3-step wizard flow with mandatory markers to prevent the model from skipping the time period question and assuming defaults. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../commands/reporting-templates.md | 288 ------------------ .../lc-essentials/commands/reporting.md | 139 +++++++++ 2 files changed, 139 insertions(+), 288 deletions(-) delete mode 100644 marketplace/plugins/lc-essentials/commands/reporting-templates.md create mode 100644 marketplace/plugins/lc-essentials/commands/reporting.md diff --git a/marketplace/plugins/lc-essentials/commands/reporting-templates.md b/marketplace/plugins/lc-essentials/commands/reporting-templates.md deleted file mode 100644 index b4c43e8b..00000000 --- a/marketplace/plugins/lc-essentials/commands/reporting-templates.md +++ /dev/null @@ -1,288 +0,0 @@ ---- -description: Pre-defined report templates combining data collection and visualization. Usage: /lc-essentials:reporting-templates ---- - -# Reporting Templates - -Present the user with a menu of pre-defined report templates. Each template combines the `reporting` skill (data collection) with the `graphic-output` skill (HTML visualization). - -## Instructions - -Use the `AskUserQuestion` tool to present the template menu: - -``` -AskUserQuestion( - questions=[{ - "question": "Which report template would you like to generate?", - "header": "Template", - "options": [ - {"label": "Monthly Billing Report", "description": "Usage statistics and billing data for customer invoicing with roll-up totals"}, - {"label": "MSSP Executive Report", "description": "High-level fleet health overview with sensor status and detection activity"}, - {"label": "Detection Analytics Report", "description": "Detection volume, categories, severity breakdown across tenants"}, - {"label": "Customer Health Report", "description": "Comprehensive customer health with sensor coverage and attention items"} - ], - "multiSelect": false - }] -) -``` - -## Template Definitions - -### Template: Monthly Billing Report - -**Purpose**: Comprehensive billing data with roll-up totals and per-tenant SKU breakdown for customer invoicing. - -**Data Collection** (reporting skill): -- List all organizations -- Per-org: - - Invoice line items via `get_org_invoice_url` with `format: "simple_json"` - - SKU names, quantities, and amounts (converted from cents to dollars) - - Subscription status and billing details - - Sensor counts for context -- Aggregate roll-up: - - Total cost across all tenants - - Total sensors across all tenants - - Average cost per sensor (blended rate) - -**Visualization** (graphic-output skill): -- **Executive Summary Roll-Up Cards**: - - Total Monthly Billing (all tenants combined) - - Total Sensors (all tenants) - - Average Cost/Sensor (blended rate) - - Active Tenant Count -- **Distribution Charts**: - - Pie chart: Cost distribution by tenant - - Pie chart: Sensor distribution by tenant -- **Per-Tenant Breakdown Table**: - - Organization name - - Region - - Sensor count - - Monthly cost - - Cost per sensor - - Percentage of total - - Status (active/draft/no usage) -- **Detailed SKU Breakdown by Tenant**: - - Expandable cards for each tenant - - Each SKU line item with name, quantity, amount - - Progress bar showing percentage of total cost -- **Cost by Category** (if SKUs can be categorized): - - Bar chart of spending by SKU category - -**Time Range**: Prompt user for billing period (year and month). This is passed to `get_org_invoice_url` to retrieve the correct invoice. - -**Output**: `/tmp/billing-report-{month}-{year}.html` - -**Template Used**: `billing-summary` (uses billing-summary.html.j2) - -**JSON Schema**: `billing-summary.json` - ---- - -### Template: MSSP Executive Report - -**Purpose**: High-level fleet health overview for MSSP leadership - sensor status, detection activity, and per-org health. - -**Data Collection** (reporting skill): -- Use template: `mssp-executive-report.json` -- List all organizations -- Per-org: - - Sensor inventory via `list_sensors` - - Detection counts via `get_historic_detections` (last N days) - - Calculate online/offline counts from last_seen timestamps -- Aggregate roll-up: - - Total sensors (online/offline) - - Fleet health percentage - - Total detections - - Detection limit flags - -**Visualization** (graphic-output skill): -- **Summary Cards**: - - Total Sensors - - Online Sensors - - Fleet Health % - - Total Detections -- **Distribution Charts**: - - Platform distribution donut chart - - Organization health bar chart -- **Top Categories**: Bar chart of top detection categories -- **Per-Organization Table**: - - Organization name, sensors, online, offline, health %, detections, status - - LIMIT badge for orgs at detection limit - -**Time Range**: Prompt user for time window (7, 14, or 30 days). - -**Output**: `/tmp/fleet-health-{days}d.html` - -**Template Used**: `fleet-health` (uses fleet-health.html.j2) - -**JSON Schema**: `fleet-health.json` - ---- - -### Template: Detection Analytics Report - -**Purpose**: Detection volume, categories, severity breakdown, and trends across tenants for security analysis. - -**Data Collection** (reporting skill): -- Use template: `detection-analytics-report.json` -- List all organizations -- Per-org: - - Detection data via `get_historic_detections` (last N days) - - Extract categories from source_rule or cat field - - Extract severity from detection metadata - - Identify top hosts by detection count -- Aggregate roll-up: - - Total detections across all tenants - - Severity breakdown (critical/high/medium/low/info) - - Top categories across all tenants - - Detection limit tracking - -**Visualization** (graphic-output skill): -- **Summary Cards**: - - Total Detections - - Tenants with Detections - - Tenants at Limit - - Critical Severity Count -- **Severity Distribution**: Stacked bar showing severity breakdown -- **Top Categories**: Horizontal bar chart -- **Top Tenants**: Horizontal bar chart by detection volume -- **Per-Tenant Table**: - - Tenant name, detection count, critical, high, medium counts - - Category pills, LIMIT badges - - Status column - -**Time Range**: Prompt user for time window (7, 14, or 30 days). - -**Output**: `/tmp/detection-analytics-{days}d.html` - -**Template Used**: `detection-analytics` (uses detection-analytics.html.j2) - -**JSON Schema**: `detection-analytics.json` - ---- - -### Template: Customer Health Report - -**Purpose**: Comprehensive customer health combining sensor coverage, detection activity, and health metrics for customer success tracking. - -**Data Collection** (reporting skill): -- Use template: `customer-health-report.json` -- List all organizations -- Per-org: - - Sensor inventory via `list_sensors` - - Detection data via `get_historic_detections` (last N days) - - Calculate health score (online/total * 100) - - Classify health status (healthy β‰₯90%, warning 70-89%, critical <70%) - - Identify attention items: - - Offline sensors - - Stale sensors (offline > 7 days) - - High severity detections - - Detection limit reached -- Aggregate roll-up: - - Health distribution (healthy/warning/critical/inactive counts) - - Fleet-wide health percentage - - Attention summary - -**Visualization** (graphic-output skill): -- **Attention Banner**: Critical/warning items summary -- **Summary Cards**: - - Total Customers - - Total Sensors (online count) - - Fleet Health % - - Total Detections -- **Health Distribution**: Grid showing healthy/warning/critical/inactive counts -- **Charts**: - - Health distribution donut chart - - Platform distribution donut chart - - Top detection categories bar chart -- **Per-Customer Table**: - - Customer name, health score, sensors, online, offline, detections - - Attention badges (critical/warning) - - Health status -- **Customers Needing Attention**: Expanded cards for flagged customers - -**Time Range**: Prompt user for time window (7, 14, or 30 days). - -**Output**: `/tmp/customer-health-{days}d.html` - -**Template Used**: `customer-health` (uses customer-health.html.j2) - -**JSON Schema**: `customer-health.json` - ---- - -## Execution Flow - -Once the user selects a template: - -### For Monthly Billing Report: - -1. **Confirm Billing Period**: Use `AskUserQuestion` to get the billing period - ``` - AskUserQuestion( - questions=[{ - "question": "Which billing period should I generate the report for?", - "header": "Period", - "options": [ - {"label": "Previous month", "description": "Most recent completed billing cycle"}, - {"label": "Current month", "description": "Current billing period (may be incomplete)"}, - {"label": "Specific month", "description": "I'll specify the year and month"} - ], - "multiSelect": false - }] - ) - ``` -2. **Confirm Scope**: Ask if they want all orgs or a specific subset -3. **Collect Data**: Spawn `org-reporter` agents in parallel to collect billing data -4. **Aggregate Results**: Calculate roll-up totals per `billing-summary.json` schema -5. **Generate HTML**: Spawn `html-renderer` with template `billing-summary` -6. **Open in Browser**: Automatically open the generated HTML file - -### For MSSP Executive, Detection Analytics, or Customer Health Reports: - -1. **Confirm Time Window**: Use `AskUserQuestion` to get the time window - ``` - AskUserQuestion( - questions=[{ - "question": "What time window should the report cover?", - "header": "Time Window", - "options": [ - {"label": "Last 7 days", "description": "Recent activity overview"}, - {"label": "Last 14 days", "description": "Two-week analysis"}, - {"label": "Last 30 days", "description": "Monthly comprehensive view (Recommended)"} - ], - "multiSelect": false - }] - ) - ``` -2. **Confirm Scope**: Ask if they want all orgs or a specific subset -3. **Calculate Timestamps**: Use bash to get accurate Unix timestamps - ```bash - date +%s # Current time (end) - date -d '7 days ago' +%s # 7 days ago (start) - ``` -4. **Collect Data**: Spawn parallel agents (one per org) to collect: - - For MSSP Executive: sensors + detections - - For Detection Analytics: detections with categories/severity - - For Customer Health: sensors + detections + health metrics -5. **Aggregate Results**: Structure data per the appropriate schema: - - MSSP Executive β†’ `fleet-health.json` - - Detection Analytics β†’ `detection-analytics.json` - - Customer Health β†’ `customer-health.json` -6. **Generate HTML**: Spawn `html-renderer` with the appropriate template -7. **Open in Browser**: Automatically open the generated HTML file - -**Browser Launch Command:** -```bash -# Option 1: Direct file open -xdg-open /tmp/{report-file}.html - -# Option 2: HTTP server (if direct open fails) -cd /tmp && python3 -m http.server 8765 & -xdg-open http://localhost:8765/{report-file}.html -``` - -## Example Conversation Flow - -``` -User: /lc-essentials:reporting-templates \ No newline at end of file diff --git a/marketplace/plugins/lc-essentials/commands/reporting.md b/marketplace/plugins/lc-essentials/commands/reporting.md new file mode 100644 index 00000000..50e702cb --- /dev/null +++ b/marketplace/plugins/lc-essentials/commands/reporting.md @@ -0,0 +1,139 @@ +--- +description: Generate reports from LimaCharlie data with visualization. Usage: /lc-essentials:reporting +--- + +# Report Generation Wizard + +You MUST complete all 3 steps in order before generating any report: +1. **Report Type** - Ask which report to generate +2. **Time Period** - Ask which period (NEVER assume a default) +3. **Scope** - Ask which organizations to include + +Do NOT skip any step. Do NOT assume defaults for time period. + +--- + +## Step 1: Report Type (MANDATORY) + +IMMEDIATELY call AskUserQuestion - do NOT output any text first: + +questions=[{"question": "Which report would you like to generate?", "header": "Report", "options": [{"label": "Billing Summary", "description": "Monthly billing with roll-up totals and per-tenant SKU breakdown (Recommended)"}, {"label": "Fleet Health", "description": "Sensor status, platform distribution, and detection activity"}, {"label": "Detection Analytics", "description": "Detection volume, severity breakdown, and category analysis"}, {"label": "Customer Health", "description": "Health scores with sensor coverage and attention items"}], "multiSelect": false}] + +## Step 2: Time Period (MANDATORY - DO NOT SKIP) + +After Step 1 response, you MUST call AskUserQuestion for time period. Never assume a default period: + +questions=[{"question": "What time period should the report cover?", "header": "Period", "options": [{"label": "Previous month", "description": "Most recent completed month (Recommended for billing)"}, {"label": "Current month", "description": "Current month to date (may be incomplete)"}, {"label": "Custom date range", "description": "I'll specify the year and month"}], "multiSelect": false}] + +IMPORTANT: You must wait for the user to select a time period before proceeding. Do NOT assume "current billing period" or any default. + +If "Custom date range" is selected, call AskUserQuestion with year and month questions: + +questions=[{"question": "Which year?", "header": "Year", "options": [{"label": "2025", "description": "Current year"}, {"label": "2024", "description": "Previous year"}], "multiSelect": false}, {"question": "Which month?", "header": "Month", "options": [{"label": "January", "description": "Month 1"}, {"label": "February", "description": "Month 2"}, {"label": "March", "description": "Month 3"}, {"label": "April", "description": "Month 4"}, {"label": "May", "description": "Month 5"}, {"label": "June", "description": "Month 6"}, {"label": "July", "description": "Month 7"}, {"label": "August", "description": "Month 8"}, {"label": "September", "description": "Month 9"}, {"label": "October", "description": "Month 10"}, {"label": "November", "description": "Month 11"}, {"label": "December", "description": "Month 12"}], "multiSelect": false}] + +## Step 3: Scope (MANDATORY) + +After Step 2 response (and custom date if applicable), call AskUserQuestion for scope: + +questions=[{"question": "Which organizations should be included?", "header": "Scope", "options": [{"label": "All organizations", "description": "Generate report for all accessible orgs (Recommended)"}, {"label": "Specific organizations", "description": "I'll select which orgs to include"}], "multiSelect": false}] + +If "Specific organizations" is selected: +1. Load the lc-essentials:limacharlie-call skill +2. Call list_user_orgs to get available organizations +3. Present a multi-select AskUserQuestion with the org names and OIDs + +After all selections are complete, display this summary: + +## Report Configuration + +| Setting | Value | +|---------|-------| +| **Report** | {selected_report} | +| **Period** | {selected_period} | +| **Scope** | {scope_description} | + +Then execute the report based on the selected type: + +## Billing Summary Report Execution + +1. Calculate billing period using bash: + - Previous month: YEAR=$(date -d "last month" +%Y) MONTH=$(date -d "last month" +%-m) + - Current month: YEAR=$(date +%Y) MONTH=$(date +%-m) + +2. Load the lc-essentials:limacharlie-call skill + +3. Call list_user_orgs to get all organizations + +4. For each organization, call get_org_invoice_url with parameters: + - oid: the organization's OID + - year: the billing year + - month: the billing month + - format: "simple_json" + +5. Aggregate results into billing-summary.json schema: + - report_metadata: title, generated_at, period (month_name, year, display) + - summary: total_billing, total_sensors, avg_cost_per_sensor, active_tenants + - tenants: array with name, oid, region, sensors, monthly_cost, cost_per_sensor, percentage_of_total, status, line_items + +6. Load the lc-essentials:graphic-output skill and render using template "billing-summary" + +7. Open the generated HTML file with: xdg-open /tmp/billing-report-{month}-{year}.html + +## Fleet Health Report Execution + +1. Calculate timestamps using bash: + - END_TS=$(date +%s) + - START_TS based on selected period + +2. Load the lc-essentials:limacharlie-call skill + +3. Call list_user_orgs + +4. For each organization: + - Call list_sensors to get sensor inventory + - Call get_historic_detections with start/end timestamps + - Calculate online/offline from last_seen timestamps + +5. Aggregate into fleet-health.json schema + +6. Load lc-essentials:graphic-output skill and render using template "fleet-health" + +7. Open: xdg-open /tmp/fleet-health-{days}d.html + +## Detection Analytics Report Execution + +1. Calculate timestamps using bash + +2. Load lc-essentials:limacharlie-call skill + +3. Call list_user_orgs + +4. For each organization: + - Call get_historic_detections + - Extract categories and severity from detection metadata + +5. Aggregate into detection-analytics.json schema + +6. Load lc-essentials:graphic-output skill and render using template "detection-analytics" + +7. Open: xdg-open /tmp/detection-analytics-{days}d.html + +## Customer Health Report Execution + +1. Calculate timestamps using bash + +2. Load lc-essentials:limacharlie-call skill + +3. Call list_user_orgs + +4. For each organization: + - Call list_sensors + - Call get_historic_detections + - Calculate health score (online/total * 100) + - Classify: healthy >= 90%, warning 70-89%, critical < 70% + +5. Aggregate into customer-health.json schema + +6. Load lc-essentials:graphic-output skill and render using template "customer-health" + +7. Open: xdg-open /tmp/customer-health-{days}d.html From fa5e88028f0d9047dbad952756488766c97377e0 Mon Sep 17 00:00:00 2001 From: Christopher Luft Date: Sat, 20 Dec 2025 23:38:03 -0800 Subject: [PATCH 07/11] Refactor billing-summary template: unified expandable org view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: The billing report had redundant sections - a summary table showing per-org costs AND a separate expandable card section showing SKU details. Users saw the same organization data twice. Solution: Consolidated into a single "Per-Organization Breakdown" section with expandable cards that show both summary info and SKU details: - All 25 orgs displayed in one list (sorted by cost, highest first) - Card header shows: org name, status badge, line item count, % of total - Click to expand reveals SKU breakdown with amounts - Visual distinction by status: - Success: normal styling - Permission Denied: red left border, grayed out - No Invoice: gray left border, grayed out - Summary footer with total org count and combined amount Also updated render-html.py validation to expect data.tenants and data.rollup instead of deprecated data.usage structure. Tested with live billing data from 25 LimaCharlie organizations. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../lc-essentials/scripts/render-html.py | 3 +- .../templates/reports/billing-summary.html.j2 | 407 ++++++++++-------- 2 files changed, 240 insertions(+), 170 deletions(-) diff --git a/marketplace/plugins/lc-essentials/scripts/render-html.py b/marketplace/plugins/lc-essentials/scripts/render-html.py index c20efa17..65685f1c 100644 --- a/marketplace/plugins/lc-essentials/scripts/render-html.py +++ b/marketplace/plugins/lc-essentials/scripts/render-html.py @@ -73,7 +73,8 @@ def validate_no_fabrication(data: dict, template: str) -> list: ], 'billing-summary': [ 'metadata.generated_at', - 'data.usage', + 'data.tenants', + 'data.rollup', ], } diff --git a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/billing-summary.html.j2 b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/billing-summary.html.j2 index 113e3954..ea90897a 100644 --- a/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/billing-summary.html.j2 +++ b/marketplace/plugins/lc-essentials/skills/graphic-output/templates/reports/billing-summary.html.j2 @@ -36,43 +36,75 @@ .meta dd { font-weight: 600; margin-top: 0.25rem; } .cost-highlight { font-size: 1.125rem; font-weight: 700; color: var(--color-success); } + + /* Expandable tenant cards */ .tenant-card { - padding: 1.25rem; background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-lg); - margin-bottom: 1rem; + margin-bottom: 0.75rem; + overflow: hidden; } .tenant-header { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 1rem; - padding-bottom: 0.75rem; - border-bottom: 1px solid var(--color-border); + padding: 1rem 1.25rem; + cursor: pointer; + user-select: none; + transition: background 0.15s ease; + } + .tenant-header:hover { + background: var(--color-bg); + } + .tenant-header-left { + display: flex; + align-items: center; + gap: 0.75rem; } - .tenant-name { font-size: 1.125rem; font-weight: 600; } - .tenant-cost { font-size: 1.5rem; font-weight: 700; color: var(--color-success); } + .tenant-expand-icon { + width: 20px; + height: 20px; + color: var(--color-text-muted); + transition: transform 0.2s ease; + } + .tenant-card.expanded .tenant-expand-icon { + transform: rotate(90deg); + } + .tenant-name { font-size: 1rem; font-weight: 600; } + .tenant-meta { font-size: 0.8rem; color: var(--color-text-muted); } + .tenant-cost { font-size: 1.25rem; font-weight: 700; color: var(--color-success); } + .tenant-cost.zero { color: var(--color-text-muted); } + + .tenant-details { + display: none; + padding: 0 1.25rem 1.25rem 1.25rem; + border-top: 1px solid var(--color-border); + } + .tenant-card.expanded .tenant-details { + display: block; + } + .sku-row { display: flex; justify-content: space-between; padding: 0.5rem 0; - border-bottom: 1px solid #f8fafc; + border-bottom: 1px solid #f1f5f9; font-size: 0.875rem; } .sku-row:last-child { border-bottom: none; } .sku-name { color: var(--color-text-muted); } .sku-amount { font-weight: 500; } .progress-bar { - height: 8px; + height: 6px; background: var(--color-border); - border-radius: 4px; + border-radius: 3px; overflow: hidden; - margin-top: 0.5rem; + margin-top: 0.75rem; } .progress-fill { height: 100%; - border-radius: 4px; + border-radius: 3px; transition: width 0.5s ease; } .badge-region { @@ -82,6 +114,26 @@ font-size: 0.75rem; font-weight: 600; } + + /* Expand all button */ + .expand-controls { + display: flex; + justify-content: flex-end; + margin-bottom: 1rem; + gap: 0.5rem; + } + .expand-btn { + padding: 0.5rem 1rem; + font-size: 0.8rem; + background: var(--color-bg); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.15s ease; + } + .expand-btn:hover { + background: var(--color-border); + } {% endblock %} @@ -107,11 +159,17 @@ {# ================================================================ - EXECUTIVE SUMMARY ROLL-UP - GUARDRAIL: All values come directly from data.rollup + EXECUTIVE SUMMARY - MOVED TO TOP ================================================================ #} + {% set success_count = data.tenants | selectattr('status', 'equalto', 'success') | list | length %} + {% set error_count = data.tenants | selectattr('status', 'equalto', 'error') | list | length %} + {% set no_invoice_count = data.tenants | selectattr('status', 'equalto', 'no_invoice') | list | length %} + {% set total_tenants = data.tenants | length %} + {% set failed_count = error_count + no_invoice_count %} + {% set completeness_pct = ((success_count / total_tenants) * 100) | round(0) | int if total_tenants > 0 else 0 %} +
-

Executive Summary (Roll-Up)

+

Executive Summary

@@ -128,8 +186,8 @@ N/A {% endif %} - Total Monthly Billing - All tenants combined + Total Billed Amount + {{ success_count }} organization{{ 's' if success_count != 1 else '' }} with successful invoices
@@ -141,15 +199,9 @@
- - {% if data.rollup.successful_count is defined %} - {{ data.rollup.successful_count }} - {% else %} - {{ data.tenants | selectattr('status', 'equalto', 'success') | list | length }} - {% endif %} - - Successful - Organizations with billing data + {{ success_count }} + Organizations with Invoices + {{ completeness_pct }}% of total organizations
@@ -162,48 +214,40 @@
- - {% if data.rollup.failed_count is defined %} - {{ data.rollup.failed_count }} - {% else %} - {{ data.tenants | rejectattr('status', 'equalto', 'success') | list | length }} - {% endif %} - - Failed/No Invoice - Organizations without data + {{ failed_count }} + Organizations without Invoices + {{ no_invoice_count }} no invoice, {{ error_count }} permission denied
- - - +
- {{ data.tenants | length }} - Active Tenants - With billing data + {{ completeness_pct }}% + Data Completeness + {{ success_count }} of {{ total_tenants }} organizations successfully retrieved
{# ================================================================ - DISTRIBUTION CHARTS + DISTRIBUTION CHARTS - IMMEDIATELY AFTER SUMMARY ================================================================ #} {% if data.tenants and data.tenants | length > 0 %}
-

Cost Distribution

+

Distribution Overview

-

Cost Distribution by Tenant

+

Cost Distribution by Organization

-

Status Breakdown

+

Data Retrieval Status

@@ -211,111 +255,146 @@ {% endif %} {# ================================================================ - PER-TENANT BREAKDOWN TABLE + WARNINGS AND ERRORS - SHOW EARLY IF PRESENT ================================================================ #} -
-

Per-Tenant Breakdown

- - {% if data.tenants and data.tenants | length > 0 %} -
- - - - - - - - - - - {% for tenant in data.tenants %} - - - - - - - {% endfor %} - - - - - - - - - -
OrganizationMonthly Cost% of TotalStatus
{{ tenant.name | default('N/A') }}${{ tenant.cost | default(0) | format_number }} - {% if data.rollup and data.rollup.total_cost and data.rollup.total_cost > 0 and tenant.cost %} - {{ ((tenant.cost / data.rollup.total_cost) * 100) | round(1) }}% - {% else %} - - - {% endif %} - - {% if tenant.status == 'success' %} - Success - {% elif tenant.status == 'error' %} - Error - {% elif tenant.status == 'no_invoice' %} - No Invoice - {% elif tenant.cost == 0 %} - No Usage - {% else %} - {{ tenant.status | default('N/A') }} - {% endif %} -
Total ({{ data.tenants | length }} tenants)${{ (data.rollup.total_cost if data.rollup and data.rollup.total_cost else 0) | format_number }}{% if data.rollup and data.rollup.total_cost %}100%{% else %}-{% endif %}
-
- {% else %} -
- πŸ“‹ -

No tenant billing data available

+ {% if warnings or errors %} +
+

Data Limitations & Warnings

+ + {% if warnings %} +
+ {% for warning in warnings %} +
+ ⚠️ + {{ warning }} +
+ {% endfor %}
{% endif %} + + {% if errors %} +
+ + {{ errors | length }} organization{{ 's' if errors | length != 1 else '' }} without billing access (click to expand) + +
+ {% for error in errors %} +
+ {{ error.org_name | default('Unknown') }} +

{{ error.error_message | default('Permission denied') }}

+
+ {% endfor %} +
+
+ {% endif %}
+ {% endif %} {# ================================================================ - DETAILED SKU BREAKDOWN BY TENANT + PER-TENANT BREAKDOWN - UNIFIED EXPANDABLE VIEW ================================================================ #} {% if data.tenants and data.tenants | length > 0 %}
-

Detailed SKU Breakdown by Tenant

- - {% for tenant in data.tenants %} -
-
-
-
{{ tenant.name }}
-
- {{ tenant.skus | length if tenant.skus else 0 }} line items | {{ tenant.currency | default('usd') | upper }} +

Per-Organization Breakdown

+ +
+ + +
+ + {# Sort tenants: successful with costs first, then successful with $0, then errors #} + {% set sorted_tenants = data.tenants | sort(attribute='cost', reverse=true) %} + + {% for tenant in sorted_tenants %} +
+
+
+ + + +
+
+ {{ tenant.name }} + {% if tenant.status == 'error' %} + Permission Denied + {% elif tenant.status == 'no_invoice' %} + No Invoice + {% endif %} +
+
+ {% if tenant.status == 'success' %} + {{ tenant.skus | length if tenant.skus else 0 }} line items + {% if data.rollup and data.rollup.total_cost and data.rollup.total_cost > 0 and tenant.cost %} + | {{ ((tenant.cost / data.rollup.total_cost) * 100) | round(1) }}% of total + {% endif %} + {% elif tenant.status == 'error' %} + Missing billing.ctrl permission + {% else %} + No active billing this period + {% endif %} +
-
- ${{ tenant.cost | default(0) | format_number }} +
+ {% if tenant.status == 'success' %} + ${{ tenant.cost | default(0) | format_number }} + {% else %} + β€” + {% endif %}
- {% if tenant.skus and tenant.skus | length > 0 %} - {% for sku in tenant.skus %} -
- {{ sku.description }}{% if sku.quantity %} ({{ sku.quantity }}){% endif %} - ${{ sku.amount | format_number }} +
+ {% if tenant.status == 'error' %} +
+ ⚠️ Cannot retrieve billing data - missing billing.ctrl permission
- {% endfor %} - {% elif tenant.cost == 0 %} -
- No billable usage this period -
- {% else %} -
- SKU details not available -
- {% endif %} + {% elif tenant.status == 'no_invoice' %} +
+ No invoice found for this billing period +
+ {% elif tenant.skus and tenant.skus | length > 0 %} + {% for sku in tenant.skus %} +
+ {{ sku.description }}{% if sku.quantity %} ({{ sku.quantity }}){% endif %} + ${{ sku.amount | format_number }} +
+ {% endfor %} -
-
+
+
+
+ {% elif tenant.cost == 0 %} +
+ No billable usage this period +
+ {% else %} +
+ SKU details not available +
+ {% endif %}
{% endfor %} + + {# Summary footer #} +
+ {{ data.tenants | length }} organizations total + + Total: ${{ (data.rollup.total_cost if data.rollup and data.rollup.total_cost else 0) | format_number }} + +
+
+ {% else %} +
+

Per-Organization Breakdown

+
+ πŸ“‹ +

No tenant billing data available

+
{% endif %} @@ -331,38 +410,6 @@
{% endif %} - {# ================================================================ - WARNINGS AND ERRORS - ================================================================ #} - {% if warnings or errors %} -
-

Data Limitations & Warnings

- - {% if warnings %} -
- {% for warning in warnings %} -
- ⚠️ - {{ warning }} -
- {% endfor %} -
- {% endif %} - - {% if errors %} -
-

Organizations Without Billing Access

- {% for error in errors %} -
- {{ error.org_name | default('Unknown') }} -

{{ error.error_message | default('Permission denied') }}

-
- {% endfor %} -
- {% endif %} -
- {% endif %} -