# How the Follow-Up Cadence Calculator Determines Optimal Contact Timing in Career-Ops

> Learn how the follow-up cadence calculator optimizes contact timing by analyzing application statuses and historical data. Configure custom schedules for your career-ops pipeline.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: deep-dive
- Published: 2026-06-13

---

**The follow-up cadence calculator analyzes application statuses and historical outreach data to compute personalized contact schedules based on configurable rules for different pipeline stages.**

The follow-up cadence calculator in the `santifer/career-ops` repository automates the scheduling of job-search communications by calculating exactly when to send the next message based on where an application sits in the hiring pipeline. By parsing your application tracker and follow-up history, the system eliminates guesswork and ensures consistent, appropriately timed outreach. This article explains the algorithmic logic behind the timing calculations, referencing the actual implementation in `followup-cadence.mjs`.

## Configuration Strategy for Contact Timing

The calculator builds its timing recommendations from a layered configuration system that merges defaults with user preferences and runtime overrides.

### Default Cadence Policies

The `DEFAULT_CADENCE` object (lines 34-42 in `followup-cadence.mjs`) defines the baseline timing rules:

- **applied_first**: Days to wait after initial application before the first follow-up
- **applied_subsequent**: Interval between follow-ups while status remains "applied"
- **applied_max_followups**: Maximum number of follow-ups before marking the candidate as "cold"
- **responded_initial**: Days to wait after receiving a response before following up
- **responded_subsequent**: Interval between follow-ups after initial response
- **interview_thankyou**: Days after an interview to send a thank-you note

### User Profile Overrides

The system supports persistent customization through [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml). The `PROFILE_CADENCE_KEYS` mapping (lines 44-51) translates profile YAML keys to cadence fields, while the `loadProfileCadence` helper (lines 58-73) validates these values as positive integers and merges them with defaults. This allows users to maintain permanent preferences for aggressive or conservative outreach strategies.

### CLI Temporary Overrides

For one-off adjustments, the `resolveCadenceConfig` function (lines 75-80) processes command-line flags such as `--applied-days`, which temporarily replaces `applied_first` without modifying stored configuration. The final merged configuration is stored in the `CADENCE` object (line 82).

## Processing Application Data

Before calculating dates, the system normalizes raw tracker data into a consistent format.

### Status Normalization

Application statuses from [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) may appear with variant spellings or formatting. The `normalizeStatus` function (lines 99-105) maps these variations to canonical values (`applied`, `responded`, `interview`, etc.). Only statuses listed in `ACTIONABLE_STATUSES` (line 99) proceed to cadence calculations; others are filtered out.

### Date Arithmetic Utilities

To avoid external dependencies, the calculator implements pure JavaScript date helpers:

- **`parseDate`** (lines 112-115): Validates ISO-date strings
- **`daysBetween`** (lines 117-119): Computes whole-day differences between dates
- **`addDays`** (lines 121-124): Adds days to a date and returns an ISO-formatted string

These utilities enable precise calendar arithmetic for determining intervals between application events and scheduled follow-ups.

## Core Calculation Logic

The heart of the system lies in two functions that determine *when* to contact and *how urgent* that contact is.

### The computeNextFollowupDate Algorithm

The `computeNextFollowupDate` function (lines 176-233) implements status-specific branching logic:

**For "applied" status:**
- If `applied_max_followups` has been reached, returns `null` (candidate is "cold")
- If no prior follow-ups exist, schedules the first contact after `applied_first` days
- For subsequent follow-ups, adds `applied_subsequent` days to the most recent follow-up date

**For "responded" status:**
- Schedules the first follow-up after `responded_initial` days
- Sets subsequent intervals using `responded_subsequent`

**For "interview" status:**
- Schedules a single thank-you follow-up after `interview_thankyou` days

The function returns an ISO-date string or `null` when no further contact is recommended.

### Urgency Classification

The `computeUrgency` function (lines 198-214) categorizes each pending contact using the same temporal logic but adds priority labels:

- **Cold**: Maximum follow-ups reached
- **Overdue**: Time since last action exceeds the configured cadence interval
- **Urgent**: Early-response scenarios requiring immediate attention
- **Waiting**: Within normal cadence parameters

The `urgencyOrder` map (line 302) sorts these classifications to prioritize your daily outreach tasks.

## Full Analysis Pipeline

The `analyze` function orchestrates the complete workflow:

1. **Parse Inputs**: Reads [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) via `parseTracker` (lines 28-44) and [`data/follow-ups.md`](https://github.com/santifer/career-ops/blob/main/data/follow-ups.md) via `parseFollowups` (lines 48-68)
2. **Group Events**: Associates follow-up entries with application IDs
3. **Calculate Metrics**: For each actionable application, computes days since application and days since last follow-up
4. **Extract Contacts**: Uses `extractContacts` (lines 73-86) to pull email addresses from application notes
5. **Determine Timing**: Invokes `computeUrgency` and `computeNextFollowupDate` for each entry
6. **Sort by Priority**: Orders results using the `urgencyOrder` ranking
7. **Return Payload**: Outputs JSON (lines 310-323) containing `nextFollowupDate`, `urgency` level, and effective configuration

## Implementation Examples

Run the calculator from the command line to see a human-readable dashboard:

```bash
node followup-cadence.mjs --summary

```

This outputs a formatted table showing application numbers, companies, days elapsed, follow-up counts, next scheduled dates, urgency levels, and contact information.

Import the module programmatically to integrate with other tools:

```javascript
import { analyze } from './followup-cadence.mjs';

(async () => {
  const result = analyze();
  console.log('Next contact for app #1:', result.entries[0].nextFollowupDate);
  console.log('Urgency level:', result.entries[0].urgency);
})();

```

Override the first-follow-up interval temporarily for faster outreach:

```bash
node followup-cadence.mjs --applied-days 3 --summary

```

This command replaces the `applied_first` value with 3 days, causing earlier reminders for new applications without changing your default configuration.

## Summary

- The **follow-up cadence calculator** in `followup-cadence.mjs` combines default policies, user profile settings, and CLI flags to determine optimal contact timing
- **Status-specific rules** govern intervals for "applied," "responded," and "interview" stages, with maximum follow-up limits preventing over-communication
- **Urgency classification** automatically prioritizes contacts as cold, overdue, urgent, or waiting
- **Pure JavaScript date utilities** handle all calendar arithmetic without external dependencies
- The system reads from [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) and [`data/follow-ups.md`](https://github.com/santifer/career-ops/blob/main/data/follow-ups.md), outputting structured JSON via the `analyze` function

## Frequently Asked Questions

### How does the calculator handle different application statuses?

The calculator treats each status according to distinct rules defined in `DEFAULT_CADENCE`. For "applied" statuses, it tracks the number of follow-ups against `applied_max_followups` and spaces them using `applied_first` and `applied_subsequent`. For "responded" statuses, it uses `responded_initial` and `responded_subsequent` intervals. Interview statuses trigger a single thank-you reminder after `interview_thankyou` days. The `normalizeStatus` function ensures consistent status mapping before these calculations apply.

### Can I customize the timing intervals?

Yes, customization occurs at two levels. Persistent changes belong in [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) under the `followup_cadence` section, which the `loadProfileCadence` function validates and merges with defaults. Temporary adjustments use CLI flags like `--applied-days`, which `resolveCadenceConfig` processes at runtime. Both methods override the `DEFAULT_CADENCE` values defined in lines 34-42.

### What happens when I reach the maximum number of follow-ups?

When the follow-up count for an "applied" status meets or exceeds `applied_max_followups`, the `computeNextFollowupDate` function returns `null` and `computeUrgency` labels the entry as **cold**. This prevents automated suggestions for further contact, effectively archiving the application unless the status changes to "responded" or "interview," which reset the cadence logic with their own intervals.

### How does the tool determine if a contact is overdue?

The `computeUrgency` function compares the days elapsed since the last action (application or follow-up) against the configured cadence interval for that status. If the elapsed time exceeds the expected interval (such as `applied_subsequent` or `responded_subsequent`), the entry receives the **overdue** label. This calculation runs for every actionable entry in the pipeline, allowing the system to surface priority contacts in the daily summary.