changes: Branch Metrics calculation update

This commit is contained in:
2025-10-24 18:07:48 -06:00
parent ab69486306
commit 24d7ca08f6
4 changed files with 438 additions and 31 deletions

View File

@@ -0,0 +1,353 @@
-- =============================================================================
-- Vista: splash_wifi_scanning_metrics_by_network
-- Descripción: Pre-calcula métricas de visitantes agregadas por NetworkId
-- Autor: Sistema
-- Fecha: 2025-10-24
-- Optimización: Eliminados JOINs innecesarios (datos ya en scanning_report_daily_full)
-- =============================================================================
-- Vista Simple: Agregación por Network (sin filtro de fechas)
-- OPTIMIZADA: Sin JOINs, usa solo scanning_report_daily_full que ya tiene NetworkName y OrganizationName
CREATE OR REPLACE VIEW public.splash_wifi_scanning_metrics_by_network AS
SELECT
s."NetworkId",
MAX(s."NetworkName") AS "NetworkName", -- Usa MAX para agregación (todos son iguales por NetworkId)
MAX(s."OrganizationName") AS "OrganizationName",
-- Rango de fechas
MIN(s."DetectionDate") AS "FirstDetection",
MAX(s."DetectionDate") AS "LastDetection",
-- Métricas de visitantes (exactas al método CalculateVisitorMetrics)
COUNT(*) AS "TotalPersons",
COUNT(*) FILTER (WHERE s."PersonType" = 'Visitor') AS "Visitors",
COUNT(*) FILTER (WHERE s."PersonType" != 'Visitor') AS "NonVisitors",
-- VisitorRate: Porcentaje de visitantes
CASE
WHEN COUNT(*) > 0
THEN ROUND((COUNT(*) FILTER (WHERE s."PersonType" = 'Visitor')::numeric / COUNT(*)::numeric * 100), 1)
ELSE 0
END AS "VisitorRate",
-- AverageDurationMinutes
CASE
WHEN COUNT(*) > 0
THEN ROUND(AVG(s."DurationInMinutes")::numeric, 0)::integer
ELSE 0
END AS "AverageDurationMinutes",
-- Métricas adicionales útiles
MIN(s."DurationInMinutes") AS "MinDurationMinutes",
MAX(s."DurationInMinutes") AS "MaxDurationMinutes",
COUNT(DISTINCT s."MacAddress") AS "UniqueDevices",
COUNT(DISTINCT CASE WHEN s."IsRegisteredUser" THEN s."MacAddress" END) AS "RegisteredUsers",
-- Distribución por categoría de presencia
COUNT(*) FILTER (WHERE s."PresenceCategory" IS NOT NULL) AS "CategorizedDetections",
-- Métricas de señal
ROUND(AVG(s."AverageRssi")::numeric, 0)::integer AS "AvgSignalStrength"
FROM public.scanning_report_daily_full s
GROUP BY s."NetworkId"
ORDER BY s."NetworkId";
-- =============================================================================
-- Vista Materializada: splash_wifi_scanning_metrics_daily
-- Descripción: Métricas agregadas por NetworkId y Fecha (para mejor performance)
-- Uso: Consultas con filtros de fecha frecuentes en dashboards
-- Mantenimiento: Refrescar diariamente con REFRESH MATERIALIZED VIEW
-- Optimización: Eliminados JOINs innecesarios (datos ya en scanning_report_daily_full)
-- =============================================================================
CREATE MATERIALIZED VIEW IF NOT EXISTS public.splash_wifi_scanning_metrics_daily AS
SELECT
s."NetworkId",
MAX(s."NetworkName") AS "NetworkName", -- Usa MAX para agregación
MAX(s."OrganizationName") AS "OrganizationName",
s."DetectionDate"::date AS "Date",
MAX(s."Year") AS "Year",
MAX(s."MonthNumber") AS "MonthNumber",
MAX(s."MonthName") AS "MonthName",
MAX(s."WeekNumber") AS "WeekNumber",
MAX(s."DayOfWeek") AS "DayOfWeek",
MAX(s."DayName") AS "DayName",
-- Métricas exactas de CalculateVisitorMetrics
COUNT(*) AS "TotalPersons",
COUNT(*) FILTER (WHERE s."PersonType" = 'Visitor') AS "Visitors",
COUNT(*) FILTER (WHERE s."PersonType" != 'Visitor') AS "NonVisitors",
-- VisitorRate: Porcentaje de visitantes
CASE
WHEN COUNT(*) > 0
THEN ROUND((COUNT(*) FILTER (WHERE s."PersonType" = 'Visitor')::numeric / COUNT(*)::numeric * 100), 1)
ELSE 0
END AS "VisitorRate",
-- AverageDurationMinutes
CASE
WHEN COUNT(*) > 0
THEN ROUND(AVG(s."DurationInMinutes")::numeric, 0)::integer
ELSE 0
END AS "AverageDurationMinutes",
-- Métricas adicionales
MIN(s."DurationInMinutes") AS "MinDurationMinutes",
MAX(s."DurationInMinutes") AS "MaxDurationMinutes",
COUNT(DISTINCT s."MacAddress") AS "UniqueDevices",
COUNT(DISTINCT CASE WHEN s."IsRegisteredUser" THEN s."MacAddress" END) AS "RegisteredUsers",
-- Métricas de señal
ROUND(AVG(s."AverageRssi")::numeric, 0)::integer AS "AvgSignalStrength",
MIN(s."MinimumRssi") AS "MinRssi",
MAX(s."MaximumRssi") AS "MaxRssi"
FROM public.scanning_report_daily_full s
GROUP BY
s."NetworkId",
s."DetectionDate"::date;
-- =============================================================================
-- Índices para Vista Materializada
-- =============================================================================
-- Índices para mejorar performance en consultas con filtros
CREATE INDEX IF NOT EXISTS idx_scanning_metrics_daily_date
ON public.splash_wifi_scanning_metrics_daily("Date", "NetworkId");
CREATE INDEX IF NOT EXISTS idx_scanning_metrics_daily_network
ON public.splash_wifi_scanning_metrics_daily("NetworkId", "Date");
CREATE INDEX IF NOT EXISTS idx_scanning_metrics_daily_year_month
ON public.splash_wifi_scanning_metrics_daily("Year", "MonthNumber", "NetworkId");
-- =============================================================================
-- CRÍTICO: Índices en la tabla/vista base scanning_report_daily_full
-- NOTA: Estos índices son ESENCIALES para mejorar performance
-- Si scanning_report_daily_full es una VISTA, crear MATERIALIZED VIEW con índices
-- Si es una TABLA, estos índices mejorarán drasticamente las consultas
-- =============================================================================
-- Índice compuesto para filtros de fecha y network (más usado)
CREATE INDEX IF NOT EXISTS idx_scanning_daily_networkid_date
ON public.scanning_report_daily_full("NetworkId", "DetectionDate");
-- Índice solo por fecha (para queries que filtran todas las networks)
CREATE INDEX IF NOT EXISTS idx_scanning_daily_date
ON public.scanning_report_daily_full("DetectionDate");
-- Índice por PersonType para filtros específicos
CREATE INDEX IF NOT EXISTS idx_scanning_daily_persontype
ON public.scanning_report_daily_full("PersonType");
-- Índice compuesto para queries complejas (NetworkId, Date, PersonType)
CREATE INDEX IF NOT EXISTS idx_scanning_daily_composite
ON public.scanning_report_daily_full("NetworkId", "DetectionDate", "PersonType");
-- =============================================================================
-- Comandos de Mantenimiento
-- =============================================================================
-- Refrescar la vista materializada (ejecutar diariamente o según necesidad)
-- REFRESH MATERIALIZED VIEW public.splash_wifi_scanning_metrics_daily;
-- Refrescar sin bloquear lecturas (requiere índice único, PostgreSQL 9.4+)
-- REFRESH MATERIALIZED VIEW CONCURRENTLY public.splash_wifi_scanning_metrics_daily;
-- Eliminar vista materializada (si necesitas recrearla)
-- DROP MATERIALIZED VIEW IF EXISTS public.splash_wifi_scanning_metrics_daily;
-- =============================================================================
-- Ejemplos de Uso
-- =============================================================================
-- =============================================================================
-- CONSULTA OPTIMIZADA: Equivalente a tu consulta original pero MUCHO MÁS RÁPIDA
-- Esta consulta debería ser más rápida que C# porque:
-- 1. No hace JOINs innecesarios
-- 2. Usa índices apropiados
-- 3. Filtra ANTES de agregar
-- =============================================================================
-- Consulta directa optimizada (sin vista pre-agregada)
-- Usa esta para consultas ad-hoc con filtros específicos
-- DEBE tardar MENOS de 7.63 segundos con índices apropiados
/*
SELECT
"NetworkId",
MAX("NetworkName") AS "NetworkName",
MAX("OrganizationName") AS "OrganizationName",
-- Métricas exactas de CalculateVisitorMetrics
COUNT(*) AS "TotalPersons",
COUNT(*) FILTER (WHERE "PersonType" = 'Visitor') AS "Visitors",
CASE
WHEN COUNT(*) > 0
THEN ROUND((COUNT(*) FILTER (WHERE "PersonType" = 'Visitor')::numeric / COUNT(*)::numeric * 100), 1)
ELSE 0
END AS "VisitorRate",
CASE
WHEN COUNT(*) > 0
THEN ROUND(AVG("DurationInMinutes")::numeric, 0)::integer
ELSE 0
END AS "AverageDurationMinutes"
FROM public.scanning_report_daily_full
WHERE "NetworkId" IN (17, 372, 82, 93, 95)
AND "DetectionDate" >= '2025-10-18'
AND "DetectionDate" <= '2025-10-25'
GROUP BY "NetworkId"
ORDER BY "NetworkId";
*/
-- =============================================================================
-- DIAGNÓSTICO DE PERFORMANCE
-- =============================================================================
-- Ver plan de ejecución de la consulta (busca "Seq Scan" - malo, "Index Scan" - bueno)
/*
EXPLAIN ANALYZE
SELECT
"NetworkId",
COUNT(*) AS "TotalPersons",
COUNT(*) FILTER (WHERE "PersonType" = 'Visitor') AS "Visitors"
FROM public.scanning_report_daily_full
WHERE "NetworkId" IN (17, 372, 82, 93, 95)
AND "DetectionDate" >= '2025-10-18'
AND "DetectionDate" <= '2025-10-25'
GROUP BY "NetworkId";
*/
-- Ver estadísticas de índices utilizados
/*
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
WHERE tablename = 'scanning_report_daily_full'
ORDER BY idx_scan DESC;
*/
-- Ver tamaño de la tabla/vista
/*
SELECT
pg_size_pretty(pg_total_relation_size('scanning_report_daily_full')) AS total_size,
pg_size_pretty(pg_relation_size('scanning_report_daily_full')) AS table_size,
pg_size_pretty(pg_indexes_size('scanning_report_daily_full')) AS indexes_size;
*/
-- =============================================================================
-- Ejemplos de Uso con Vistas Pre-agregadas
-- =============================================================================
-- Ejemplo 1: Obtener métricas de un network específico (sin filtro de fecha)
/*
SELECT
"NetworkName",
"TotalPersons",
"Visitors",
"VisitorRate",
"AverageDurationMinutes"
FROM public.splash_wifi_scanning_metrics_by_network
WHERE "NetworkId" = 123;
*/
-- Ejemplo 2: Obtener métricas por rango de fechas (USA LA VISTA MATERIALIZADA)
/*
SELECT
"NetworkName",
"Date",
"TotalPersons",
"Visitors",
"VisitorRate",
"AverageDurationMinutes"
FROM public.splash_wifi_scanning_metrics_daily
WHERE "NetworkId" = 123
AND "Date" BETWEEN '2025-01-01' AND '2025-01-31'
ORDER BY "Date";
*/
-- Ejemplo 3: Agregar métricas de múltiples días en un período (RECOMENDADO)
/*
SELECT
"NetworkId",
"NetworkName",
SUM("TotalPersons") AS "TotalPersons",
SUM("Visitors") AS "Visitors",
CASE
WHEN SUM("TotalPersons") > 0
THEN ROUND((SUM("Visitors")::numeric / SUM("TotalPersons")::numeric * 100), 1)
ELSE 0
END AS "VisitorRate",
ROUND(AVG("AverageDurationMinutes")::numeric, 0)::integer AS "AverageDurationMinutes"
FROM public.splash_wifi_scanning_metrics_daily
WHERE "Date" BETWEEN '2025-10-18' AND '2025-10-25'
AND "NetworkId" IN (17, 372, 82, 93, 95)
GROUP BY "NetworkId", "NetworkName";
*/
-- Ejemplo 4: Top 10 Networks por VisitorRate en el último mes
/*
SELECT
"NetworkName",
SUM("TotalPersons") AS "TotalPersons",
SUM("Visitors") AS "Visitors",
CASE
WHEN SUM("TotalPersons") > 0
THEN ROUND((SUM("Visitors")::numeric / SUM("TotalPersons")::numeric * 100), 1)
ELSE 0
END AS "VisitorRate"
FROM public.splash_wifi_scanning_metrics_daily
WHERE "Date" >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY "NetworkId", "NetworkName"
ORDER BY "VisitorRate" DESC
LIMIT 10;
*/
-- =============================================================================
-- Permisos
-- =============================================================================
ALTER TABLE public.splash_wifi_scanning_metrics_by_network OWNER TO mysql;
ALTER TABLE public.splash_wifi_scanning_metrics_daily OWNER TO mysql;
-- =============================================================================
-- RECOMENDACIONES DE OPTIMIZACIÓN
-- =============================================================================
/*
Si la consulta sigue siendo lenta después de crear los índices:
1. VERIFICAR si scanning_report_daily_full es una VISTA o TABLA:
- Si es VISTA: Considera convertirla a MATERIALIZED VIEW
- Si es TABLA: Los índices deberían ayudar significativamente
2. ANALIZAR estadísticas de la tabla:
ANALYZE public.scanning_report_daily_full;
3. VACUUM si la tabla tiene muchos registros eliminados:
VACUUM ANALYZE public.scanning_report_daily_full;
4. CONSIDERAR particionamiento por fecha si la tabla es muy grande (>10M registros):
CREATE TABLE scanning_report_daily_full_2025_01 PARTITION OF scanning_report_daily_full
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
5. MONITOREAR queries lentas:
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
WHERE query LIKE '%scanning_report_daily_full%'
ORDER BY mean_exec_time DESC
LIMIT 10;
6. AJUSTAR parámetros de PostgreSQL si es necesario:
- shared_buffers (25% de RAM)
- effective_cache_size (50-75% de RAM)
- work_mem (para sorts y agregaciones grandes)
- maintenance_work_mem (para CREATE INDEX)
*/

View File

@@ -356,15 +356,15 @@ namespace SplashPage.Splash
{
var networkId = networkGroup.Key;
var networkConnections = networkGroup.ToList();
var networkScanning = scanning.Where(s => s.NetworkId == networkId).ToList();
var networkScanning = scanning.Where(s => s.NetworkId == networkId);
// ✅ Create domain entity with business logic
var branchMetric = new BranchMetric(networkId, networkConnections.FirstOrDefault()?.NetworkName ?? "Unknown");
// ✅ Apply domain rules
branchMetric.CalculateVisitorMetrics(networkScanning);
//branchMetric.CalculateVisitorMetrics(networkScanning);
branchMetric.CalculateConnectionMetrics(networkConnections);
branchMetric.CalculateConversionRate(networkConnections, networkScanning);
branchMetric.CalculateConversionRate(networkConnections, new List<SplashWifiScanningReportDto>());
// ✅ Business validation
ValidateBranchMetric(branchMetric);
@@ -521,11 +521,11 @@ namespace SplashPage.Splash
{
public int NetworkId { get; private set; }
public string NetworkName { get; private set; }
public int TotalVisits { get; private set; }
public int TotalPersons { get; private set; }
public int Visitors { get; private set; }
public double VisitorRate { get; private set; }
public int AverageDurationMinutes { get; private set; }
public int TotalVisits { get; set; }
public int TotalPersons { get; set; }
public int Visitors { get; set; }
public double VisitorRate { get; set; }
public int AverageDurationMinutes { get; set; }
public BranchMetric(int networkId, string networkName)
{
@@ -534,11 +534,10 @@ namespace SplashPage.Splash
}
// ✅ Domain logic for visitor metrics
public void CalculateVisitorMetrics(List<SplashWifiScanningReportDto> scanning)
public void CalculateVisitorMetrics(IQueryable<SplashWifiScanningReport> scanning)
{
TotalPersons = scanning.Select(s => s.MacAddress).Distinct().Count();
Visitors = scanning.Where(s => s.PersonType == "Visitor")
.Select(s => s.MacAddress).Distinct().Count();
TotalPersons = scanning.Count();
Visitors = scanning.Where(s => s.PersonType == "Visitor").Count();
VisitorRate = TotalPersons > 0 ? Math.Round((double)Visitors / TotalPersons * 100, 1) : 0;
AverageDurationMinutes = scanning.Any() ? (int)scanning.Average(s => s.DurationInMinutes) : 0;
@@ -547,7 +546,7 @@ namespace SplashPage.Splash
// ✅ Domain logic for connection metrics
public void CalculateConnectionMetrics(List<SplashWifiConnectionReportDto> connections)
{
TotalVisits = connections.Select(c => c.DeviceMac).Distinct().Count();
TotalVisits = connections.Count();
}
// ✅ Domain logic for conversion rate

View File

@@ -32,7 +32,7 @@ namespace SplashPage.Splash
throw new System.NotImplementedException();
}
private async Task<List<SplashWifiConnectionReportUnique>> ExecuteQuery(IQueryable<SplashWifiConnectionReportUnique> query)
private async Task<List<SplashWifiConnectionReportUnique>> ExecuteQuery(IQueryable<SplashWifiConnectionReportUnique> query)
=> await query
.OrderByDescending(x => x.ConnectionDate)
.ToListAsync();
@@ -130,10 +130,10 @@ namespace SplashPage.Splash
var scanningTask = await _scanningService.GetAllEntitiesAsync(scanningFilter);
//await Task.WhenAll(connectionTask, scanningTask);
var connections = connectionTask;
var scanning = scanningTask;
// ✅ Apply domain logic
var branchMetrics = CalculateBranchMetrics(connections.MapToEntityDtos(), scanning, input);
@@ -150,30 +150,50 @@ namespace SplashPage.Splash
IQueryable<SplashWifiScanningReport> scanning,
PagedWifiConnectionReportRequestDto query)
{
var results = new List<BranchMetric>();
//var results = new List<BranchMetric>();
// ✅ Group by network and apply domain rules
var networkGroups = connections.GroupBy(c => c.NetworkId).ToList();
foreach (var networkGroup in networkGroups)
var results = scanning
.GroupBy(x => x.NetworkName)
.Select(x => new
{
var networkId = networkGroup.Key;
var networkConnections = networkGroup.ToList();
var networkScanning = scanning.Where(s => s.NetworkId == networkId).ToList().MapToEntityDtos();
NetworkName = x.Key,
TotalPersons = x.Count(),
Visitors = x.Where(s => s.PersonType == "Visitor").Count(),
})
.Select(x => new BranchMetric(1, x.NetworkName)
{
TotalPersons = x.TotalPersons,
Visitors = x.Visitors,
// ✅ Create domain entity with business logic
var branchMetric = new BranchMetric(networkId, networkConnections.FirstOrDefault()?.NetworkName ?? "Unknown");
VisitorRate = x.TotalPersons > 0 ? Math.Round((double)x.Visitors / x.TotalPersons * 100, 1) : 0,
AverageDurationMinutes = scanning.Any() ? (int)scanning.Average(s => s.DurationInMinutes) : 0,
}).ToList();
// ✅ Apply domain rules
branchMetric.CalculateVisitorMetrics(networkScanning);
branchMetric.CalculateConnectionMetrics(networkConnections);
//branchMetric.CalculateConversionRate(networkConnections, networkScanning);
// ✅ Business validation
//ValidateBranchMetric(branchMetric);
results.ForEach(x => x.TotalVisits = connections.Count(c => c.NetworkId == x.NetworkId));
results.Add(branchMetric);
}
//foreach (var networkGroup in networkGroups)
//{
// var networkId = networkGroup.Key;
// var networkConnections = networkGroup.ToList();
// var networkScanning = scanning.Where(s => s.NetworkId == networkId);
// // ✅ Create domain entity with business logic
// var branchMetric = new BranchMetric(networkId, networkConnections.FirstOrDefault()?.NetworkName ?? "Unknown");
// // ✅ Apply domain rules
// branchMetric.CalculateVisitorMetrics(networkScanning);
// branchMetric.CalculateConnectionMetrics(networkConnections);
// //branchMetric.CalculateConversionRate(networkConnections, networkScanning);
// // ✅ Business validation
// //ValidateBranchMetric(branchMetric);
// results.Add(branchMetric);
//}
// ✅ Apply ranking rules based on query
return ApplyRankingRules(results, RankingType.ByTotalPersons);

View File

@@ -12,6 +12,7 @@
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@formkit/auto-animate": "^0.9.0",
"@hookform/resolvers": "^5.2.1",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
@@ -46,6 +47,7 @@
"@tailwindcss/postcss": "^4.0.0",
"@tanstack/react-query": "^5.90.2",
"@tanstack/react-table": "^8.21.2",
"@tanstack/react-virtual": "3.0.0-beta.68",
"@types/react-grid-layout": "^1.3.5",
"apexcharts": "^5.3.5",
"axios": "^1.12.2",
@@ -1211,6 +1213,12 @@
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@formkit/auto-animate": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.9.0.tgz",
"integrity": "sha512-VhP4zEAacXS3dfTpJpJ88QdLqMTcabMg0jwpOSxZ/VzfQVfl3GkZSCZThhGC5uhq/TxPHPzW0dzr4H9Bb1OgKA==",
"license": "MIT"
},
"node_modules/@gerrit0/mini-shiki": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.13.1.tgz",
@@ -6711,6 +6719,23 @@
"react-dom": ">=16.8"
}
},
"node_modules/@tanstack/react-virtual": {
"version": "3.0.0-beta.68",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.68.tgz",
"integrity": "sha512-YEFNCf+N3ZlNou2r4qnh+GscMe24foYEjTL05RS0ZHvah2RoUDPGuhnuedTv0z66dO2vIq6+Bl4TXatht5T7GQ==",
"license": "MIT",
"dependencies": {
"@tanstack/virtual-core": "3.0.0-beta.68"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@tanstack/table-core": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
@@ -6724,6 +6749,16 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/virtual-core": {
"version": "3.0.0-beta.68",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.68.tgz",
"integrity": "sha512-CnvsEJWK7cugigckt13AeY80FMzH+OMdEP0j0bS3/zjs44NiRe49x8FZC6R9suRXGMVMXtUHet0zbTp/Ec9Wfg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",