(
rows: { email: string; createdAt: Date }[],
)
| 501 | } |
| 502 | |
| 503 | function findCreationClusters( |
| 504 | rows: { email: string; createdAt: Date }[], |
| 505 | ): CreationCluster[] { |
| 506 | const sorted = [...rows].sort( |
| 507 | (a, b) => a.createdAt.getTime() - b.createdAt.getTime(), |
| 508 | ) |
| 509 | // Greedy non-overlapping sweep: walk the sorted list, and whenever the next |
| 510 | // account is within the window of the current cluster's first member, add |
| 511 | // it. Emit clusters that reach the minimum size. |
| 512 | const clusters: CreationCluster[] = [] |
| 513 | let i = 0 |
| 514 | while (i < sorted.length) { |
| 515 | let j = i + 1 |
| 516 | while ( |
| 517 | j < sorted.length && |
| 518 | sorted[j].createdAt.getTime() - sorted[i].createdAt.getTime() <= |
| 519 | CREATION_CLUSTER_WINDOW_MS |
| 520 | ) { |
| 521 | j++ |
| 522 | } |
| 523 | if (j - i >= CREATION_CLUSTER_MIN_SIZE) { |
| 524 | clusters.push({ |
| 525 | windowStart: sorted[i].createdAt, |
| 526 | windowEnd: sorted[j - 1].createdAt, |
| 527 | emails: sorted.slice(i, j).map((m) => m.email), |
| 528 | }) |
| 529 | i = j |
| 530 | } else { |
| 531 | i++ |
| 532 | } |
| 533 | } |
| 534 | return clusters |
| 535 | } |
| 536 | |
| 537 | export function formatSweepReport(report: SweepReport): { |
| 538 | subject: string |
no outgoing calls
no test coverage detected