static getSummaries(networkRecords, tbtImpactTasks, cpuMultiplier, entityClassification) {
    /** @type {Map<string, Summary>} */
    const byURL = new Map();
    /** @type {Map<LH.Artifacts.Entity, Summary>} */
    const byEntity = new Map();
    const defaultSummary = {mainThreadTime: 0, blockingTime: 0, transferSize: 0, tbtImpact: 0};

    for (const request of networkRecords) {
      const urlSummary = byURL.get(request.url) || {...defaultSummary};
      urlSummary.transferSize += request.transferSize;
      byURL.set(request.url, urlSummary);
    }

    const jsURLs = getJavaScriptURLs(networkRecords);

    for (const task of tbtImpactTasks) {
      const attributableURL = getAttributableURLForTask(task, jsURLs);

      const urlSummary = byURL.get(attributableURL) || {...defaultSummary};
      const taskDuration = task.selfTime * cpuMultiplier;
      // The amount of time spent on main thread is the sum of all durations.
      urlSummary.mainThreadTime += taskDuration;
      // Blocking time is the amount of time spent on the main thread *over* 50ms.
      // This value is interpolated because not all tasks attributed to this URL are at the top level.
      //
      // Note that this is not totally equivalent to the TBT definition since it fails to account for
      // the FCP&TTI bounds of TBT.
      urlSummary.blockingTime += task.selfBlockingTime;
      // TBT impact is similar to blocking time, but it accounts for the FCP&TTI bounds of TBT.
      urlSummary.tbtImpact += task.selfTbtImpact;
      byURL.set(attributableURL, urlSummary);
    }

    // Map each URL's stat to a particular entity.
    /** @type {Map<LH.Artifacts.Entity, string[]>} */
    const urls = new Map();
    for (const [url, urlSummary] of byURL.entries()) {
      const entity = entityClassification.entityByUrl.get(url);
      if (!entity) {
        byURL.delete(url);
        continue;
      }

      const entitySummary = byEntity.get(entity) || {...defaultSummary};
      entitySummary.transferSize += urlSummary.transferSize;
      entitySummary.mainThreadTime += urlSummary.mainThreadTime;
      entitySummary.blockingTime += urlSummary.blockingTime;
      entitySummary.tbtImpact += urlSummary.tbtImpact;
      byEntity.set(entity, entitySummary);

      const entityURLs = urls.get(entity) || [];
      entityURLs.push(url);
      urls.set(entity, entityURLs);
    }

    return {byURL, byEntity, urls};
  }
@param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@param {Array<LH.Artifacts.TBTImpactTask>} tbtImpactTasks
@param {number} cpuMultiplier
@param {LH.Artifacts.EntityClassification} entityClassification
@return {SummaryMaps}