diff --git a/PULSE_DATABASE_SKILL.md b/PULSE_DATABASE_SKILL.md index dbf516d..4b99e28 100644 --- a/PULSE_DATABASE_SKILL.md +++ b/PULSE_DATABASE_SKILL.md @@ -18,6 +18,7 @@ |---|---|---|---| | **Autotask PSA** | tickets, time_entries, companies, contacts, resources, configuration_items, contracts, projects, tasks, ticket_notes | 152K tickets, 156K time entries, 7K CIs | Service desk, billing, contracts, clients | | **Datto RMM** | datto_rmm_alerts, datto_rmm_devices, datto_rmm_sites | 21K alerts, 3.6K devices | Remote monitoring & management | +| **Auvik** | auvik_tenants, auvik_tenant_mappings | 17 tenants, 15 mappings | Network management — device inventory fetched live from API; tenant/company links stored in DB | | **SentinelOne** | s1_agents, s1_threats, s1_sites | 2.8K agents, 4.1K threats | Endpoint security | | **Veeam** | veeam_organizations, veeam_backup_jobs, veeam_backup_agents, veeam_alarms, veeam_protected_workloads, veeam_repositories, veeam_backup_servers | ~2.5K total | Backup & disaster recovery | | **IT Glue** | itg_organizations, itg_configurations, itg_passwords, itg_flexible_assets, itg_contacts, itg_documents, itg_expirations, itg_domains, itg_locations | 14.7K configs | IT documentation | @@ -196,21 +197,75 @@ Line items on contracts. - `company_id` → `companies.id` - `service_name` (text), `unit_price`, `quantity` -### projects (~291 rows, 36 columns) +### projects (~314 rows, 36 columns) + +Planned engagements for client work — distinct from reactive service tickets. A project has a defined scope, timeline, and budget. Think of projects as the planned/scheduled side of the business (switch deployments, onboarding, infrastructure upgrades) while tickets are the reactive/support side. **Key columns:** -- `id`, `company_id`, `project_name`, `status`, `type` -- `project_lead_resource_id` → `resources.id` -- `start_date_time`, `end_date_time`, `actual_hours`, `estimated_time` +- `id` (bigint PK) — Autotask project ID +- `company_id` (bigint) — FK to `companies.id` +- `project_name` (varchar) — display name +- `project_number` (varchar) — unique identifier string +- `description` (text) +- `status` (int) — project status code +- `type` (int) — project type +- `project_lead_resource_id` (bigint) — FK to `resources.id` +- `owner_resource_id` (bigint) — FK to `resources.id` +- `start_date_time`, `end_date_time` (timestamp) — scheduled window +- `completed_date_time` (timestamp) +- `estimated_time` (numeric) — estimated hours +- `actual_hours` (numeric) — hours logged so far +- `estimated_sale_cost`, `labor_estimated_revenue` (numeric) — financial estimates +- `completed_percentage` (numeric) — 0–100 +- `last_activity_date_time` (timestamp) +- `is_deleted` (boolean) -### tasks (~4.3K rows, 34 columns) +**Relationship to tickets:** A ticket can optionally be linked to a project via `tickets.project_id`. This is typically used when a ticket was generated as part of project work (e.g. a change request within a project). -Tasks on tickets or projects. +### tasks (~5K rows, 34 columns) -- `ticket_id` → `tickets.id` -- `project_id` → `projects.id` -- `assigned_resource_id` → `resources.id` -- `status`, `priority`, `estimated_hours`, `remaining_hours` +Tasks are discrete units of work that live **inside** a project or a ticket. They are NOT standalone items — every task belongs to either a project (`project_id`) or a ticket (`ticket_id`), not both simultaneously. + +**Key difference from tickets:** +- **Tickets** = reactive service desk items. Created by clients, RMM alerts, email, or portal. Queued and triaged. +- **Tasks** = planned steps within a project (or checklist items on a ticket). Created by technicians and project managers. Have structured phases and scheduling. + +**Real-world examples of tasks:** +- A "Switch Deployment" project might have tasks: `Switch [Config] - ynghynswp27`, `Switch [Build] - ynghynswp27`, `Switch [Deploy] - ynghynswp27` +- Each task represents a discrete phase of work, assigned to a resource with estimated hours + +**Key columns:** +- `id` (bigint PK) — Autotask task ID +- `title` (varchar) — task name +- `description` (text) +- `project_id` (bigint) — FK to `projects.id` (if this is a project task) +- `ticket_id` (bigint) — FK to `tickets.id` (if this is a ticket sub-task) +- `phase_id` (bigint) — project phase grouping (project tasks only) +- `assigned_resource_id` (bigint) — FK to `resources.id` +- `creator_resource_id` (bigint) — FK to `resources.id` +- `status` (int), `priority` (int) +- `task_type` (int) +- `task_number` (varchar) — human-readable number within the project +- `estimated_hours` (numeric), `remaining_hours` (numeric) +- `start_date_time`, `end_date_time` (timestamp) — scheduled window +- `completed_date_time` (timestamp) +- `task_is_billable` (boolean) +- `is_deleted` (boolean) + +**Time entries on tasks:** `time_entries.task_id` links work logs to specific tasks. Task time entries also carry `project_id` and `company_id` for quick aggregation without joins. + +**Finding project tasks vs ticket tasks:** +```sql +-- Project tasks only +SELECT t.*, p.project_name FROM tasks t +JOIN projects p ON p.id = t.project_id +WHERE t.project_id IS NOT NULL AND t.is_deleted IS NOT TRUE; + +-- Ticket sub-tasks only +SELECT t.*, tk.title AS ticket_title FROM tasks t +JOIN tickets tk ON tk.id = t.ticket_id +WHERE t.ticket_id IS NOT NULL AND t.is_deleted IS NOT TRUE; +``` ### billing_items (~96K rows) @@ -415,7 +470,58 @@ Join: `issue_types.value = tickets.issue_type` --- -## 4. SentinelOne +## 4. Auvik Network Management + +Auvik is the network device management platform used to monitor and manage client network infrastructure (switches, routers, firewalls, APs, etc.). It organizes clients as **tenants** — one tenant per client organization. + +**Important:** Auvik **device inventory is NOT stored in the Pulse database.** Device data (hostnames, IPs, device types, configs, firmware) is fetched **live from the Auvik API** at query time. Only the tenant list and their mappings to Autotask companies are persisted in the DB. + +### auvik_tenants (~17 rows) + +The list of Auvik tenants (client organizations) known to this Pulse instance. + +**Key columns:** +- `id` (int PK) — internal DB ID +- `tenant_id` (varchar) — Auvik's unique tenant identifier (numeric string, e.g. `"197643669426944326"`) +- `tenant_name` (varchar) — short name / domain prefix (e.g. `"seubert"`, `"wulfconsulting"`) +- `domain_prefix` (varchar) — same as tenant_name; used in Auvik URLs +- `device_count` (int) — last known device count from sync +- `last_sync_at` (timestamptz) — when this tenant was last synced +- `created_at`, `updated_at` (timestamptz) + +**Sample tenants:** vollmer, poha, premierautomation, wulfconsulting, vorteqcoilfinishers, seubert, hynesindustries, greco, therla, and ~8 others. + +### auvik_tenant_mappings (~15 rows) + +Maps each Auvik tenant to its corresponding Autotask company. This is the cross-platform link between network management and the PSA. + +**Key columns:** +- `id` (int PK) +- `auvik_tenant_id` (varchar) — Auvik tenant ID, matches `auvik_tenants.tenant_id` +- `auvik_tenant_name` (varchar) — short name +- `autotask_company_id` (int) — FK to `companies.id` +- `autotask_company_name` (varchar) — denormalized for convenience +- `created_at`, `updated_at` (timestamptz) + +**Join pattern — Auvik tenant → Autotask company:** +```sql +SELECT atm.auvik_tenant_name, c.company_name, c.id AS autotask_company_id +FROM auvik_tenant_mappings atm +JOIN companies c ON c.id = atm.autotask_company_id; +``` + +**What Auvik tracks (via live API, not DB):** +- Network device inventory: switches, routers, firewalls, access points, printers, UPS units +- Device details: hostname, IP addresses, MAC addresses, model, firmware version, vendor +- Online/offline status and last seen time +- Device configurations (running config snapshots — see `/api/auvik/device-config` endpoint) +- Network topology and interface relationships + +**Auvik device naming convention:** Hostnames in Auvik follow the pattern `{client_prefix}{device_type}{sequence}`, e.g. `ynghynswp27` = `ynghy` (client) + `nsw` (network switch) + `p27` (port count / unit). These hostnames also appear in Autotask ticket notes and task titles when work is performed on the device. + +--- + +## 6. SentinelOne ### s1_agents (~2.8K rows, 42 columns) @@ -452,7 +558,7 @@ Maps S1 sites to Autotask companies for cross-referencing. --- -## 5. Veeam Backup +## 7. Veeam Backup ### veeam_organizations (~17 columns) @@ -481,7 +587,7 @@ Supporting tables for backup infrastructure. --- -## 6. IT Glue Documentation +## 8. IT Glue Documentation ### itg_organizations (~330 rows) @@ -516,7 +622,7 @@ Supporting IT documentation tables. --- -## 7. Engagement & Communications +## 9. Engagement & Communications ### graph_users @@ -562,7 +668,7 @@ Weekly/monthly aggregates of M365 activity per user. --- -## 8. QuickBooks Online (Accounting) +## 10. QuickBooks Online (Accounting) ### qbo_invoices (~6.9K rows) @@ -616,7 +722,7 @@ Periodic financial reports stored as JSON. --- -## 9. Zabbix NMS +## 11. Zabbix NMS ### zabbix_wan_hosts (~75 rows) @@ -643,7 +749,7 @@ Zabbix alert events. --- -## 10. Authentication (Better Auth + Entra ID) +## 12. Authentication (Better Auth + Entra ID) ### "user" table (~3 rows) @@ -676,7 +782,7 @@ OAuth provider links (Microsoft Entra ID). --- -## 11. Common Join Patterns +## 13. Common Join Patterns ```sql -- Ticket with company, resource, and status label @@ -740,11 +846,53 @@ FROM zabbix_events ze JOIN zabbix_wan_hosts zwh ON zwh.host_id = ze.host_id LEFT JOIN companies c ON c.id = zwh.company_id WHERE ze.value = 1 ORDER BY ze.clock DESC; + +-- Project with all its tasks and hours logged +SELECT p.project_name, p.status, p.actual_hours, + t.title AS task_title, t.status AS task_status, + t.estimated_hours, t.remaining_hours, + r.first_name || ' ' || r.last_name AS assigned_to +FROM projects p +LEFT JOIN tasks t ON t.project_id = p.id AND t.is_deleted IS NOT TRUE +LEFT JOIN resources r ON r.id = t.assigned_resource_id +WHERE p.id = $1 AND p.is_deleted IS NOT TRUE; + +-- All time entries for a project (direct + via tasks) +SELECT te.entry_date, te.hours_worked, te.notes, + r.first_name || ' ' || r.last_name AS technician, + t.title AS task_title, + te.billable +FROM time_entries te +JOIN resources r ON r.id = te.resource_id +LEFT JOIN tasks t ON t.id = te.task_id +WHERE te.project_id = $1 AND te.is_deleted IS NOT TRUE +ORDER BY te.entry_date; + +-- Hours by technician across all projects this month +SELECT r.first_name || ' ' || r.last_name AS technician, + SUM(te.hours_worked) AS total_hours, + SUM(CASE WHEN te.billable THEN te.hours_worked ELSE 0 END) AS billable_hours +FROM time_entries te +JOIN resources r ON r.id = te.resource_id +WHERE te.project_id IS NOT NULL + AND te.entry_date >= DATE_TRUNC('month', NOW()) + AND te.is_deleted IS NOT TRUE +GROUP BY 1 ORDER BY 2 DESC; + +-- Auvik tenant → Autotask company → open tickets +SELECT atm.auvik_tenant_name, c.company_name, + COUNT(tk.id) AS open_tickets +FROM auvik_tenant_mappings atm +JOIN companies c ON c.id = atm.autotask_company_id +LEFT JOIN tickets tk ON tk.company_id = c.id + AND tk.status NOT IN (5) -- not Complete + AND tk.is_deleted IS NOT TRUE +GROUP BY 1, 2 ORDER BY 3 DESC; ``` --- -## 12. Important Notes +## 14. Important Notes 1. **Soft deletes:** Most Autotask tables have `is_deleted` (boolean) and `deleted_at`. Always add `WHERE is_deleted IS NOT TRUE` unless you want deleted records. @@ -760,7 +908,15 @@ WHERE ze.value = 1 ORDER BY ze.clock DESC; - RMM → Autotask: `datto_rmm_sites.autotask_company_id = companies.id` - IT Glue → Autotask: `itg_organizations.psa_id::bigint = companies.id` - S1 → Autotask: via `s1_company_mappings` + - Auvik → Autotask: `auvik_tenant_mappings.autotask_company_id = companies.id` - Zoom/Teams → Contacts: `matched_contact_id` / `matched_company_id` columns - Config Items → RMM: `configuration_items.reference_number` sometimes matches RMM device UIDs + - Auvik hostnames → Autotask tickets/tasks: search `ticket_notes.description` and `tasks.title` for the device hostname (e.g. `ynghynswp27`) to find all work done on a network device -7. **Row counts** (as of March 17, 2026): tickets 152K, time_entries 156K, billing_items 96K, ticket_notes 42K, teams_meetings 14.4K, itg_configurations 14.7K, configuration_items 7K, companies 246, contacts 4.2K, datto_rmm_devices 3.6K, datto_rmm_alerts 21K, s1_agents 2.8K, s1_threats 4.1K, zoom_calls 2.8K, qbo_invoices 6.9K, qbo_transactions 12.1K, qbo_payments 4.5K, qbo_deposits 1.8K, qbo_reports 36, zabbix_wan_hosts 75, zabbix_events 231. +7. **Tickets vs Projects vs Tasks — when to use which:** + - Query `tickets` when asking about: support requests, break/fix, helpdesk, RMM alerts, client-reported issues, SLA performance, response times, queue volumes + - Query `projects` when asking about: planned work, deployments, onboarding, infrastructure upgrades, project status, project hours/budget + - Query `tasks` when asking about: individual steps within a project, task completion rates, who is assigned to what within a project, task-level time tracking. Tasks have both `project_id` (project tasks) and `ticket_id` (ticket sub-tasks) — check which is populated. + - Query `time_entries` for actual hours worked — it spans all three: `ticket_id`, `task_id`, and `project_id` can all be non-null depending on context + +8. **Row counts** (as of March 24, 2026): tickets 152K, time_entries 156K, billing_items 96K, ticket_notes 42K, teams_meetings 14.4K, itg_configurations 14.7K, configuration_items 7K, tasks 5K, projects 314, companies 246, contacts 4.2K, datto_rmm_devices 3.6K, datto_rmm_alerts 21K, s1_agents 2.8K, s1_threats 4.1K, auvik_tenants 17, auvik_tenant_mappings 15, zoom_calls 2.8K, qbo_invoices 6.9K, qbo_transactions 12.1K, qbo_payments 4.5K, qbo_deposits 1.8K, qbo_reports 36, zabbix_wan_hosts 75, zabbix_events 231.