# How the Follow-Up Cadence Calculator Determines Optimal Follow-Up Timing

> Discover how the follow-up cadence calculator optimizes your job search by analyzing your application tracker to pinpoint the best time for your next follow-up.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: how-to-guide
- Published: 2026-06-12

---

**The follow-up cadence calculator reads your job application tracker and follow-up log, normalizes each status against a configurable rule set, and computes both an urgency label and the exact calendar date for the next follow-up.**

The `santifer/career-ops` repository provides a data-driven follow-up cadence calculator that removes guesswork from job-search outreach. Implemented in `followup-cadence.mjs`, the tool ingests raw application data, applies user-defined or default timing thresholds, and surfaces precisely when each follow-up should be sent. By comparing elapsed days against these best-practice intervals, the calculator ensures you maintain consistent, optimal timing without relying on ad-hoc reminders.

## Configuration and Profile Overrides

The effective timing rules are built from three layered sources inside `followup-cadence.mjs`.

### Default Cadence Constants

At lines 35–42, the module declares **`DEFAULT_CADENCE`**, a hard-coded object that encodes best-practice intervals. These defaults specify how many days should pass before the first follow-up, between subsequent touchpoints, and the maximum number of attempts for each application stage.

### Merging Profile and CLI Overrides

User preferences live in [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) under the `followup_cadence` key. The `loadProfileCadence()` function (lines 58–73) reads those keys and merges them over the defaults. If you invoke the script with `--applied-days <N>`, the `resolveCadenceConfig()` function (lines 75–80) applies that CLI flag as a final override for the `applied_first` interval. The resulting effective configuration is stored in the **`CADENCE`** constant at line 82 and drives every downstream timing decision.

## Data Ingestion and Status Normalization

Before computing dates, the calculator transforms raw markdown logs into structured data.

### Parsing Tracker and Follow-Up Logs

Applications are read from [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) by `parseTracker()` (lines 27–44), which turns each pipe-separated row into an object containing the application number, date, company, role, and current status. Follow-ups are read from [`data/follow-ups.md`](https://github.com/santifer/career-ops/blob/main/data/follow-ups.md) by `parseFollowups()` (lines 47–69) and grouped by application number (lines 45–49) so each application can be evaluated alongside its full outreach history.

### Canonical Status Mapping

Raw status strings such as "aplicado" or "Respondido" are mapped to canonical identifiers (`applied`, `responded`, `interview`) by `normalizeStatus()` (lines 101–105) using the **`ALIASES`** lookup table (lines 84–96). Only statuses listed in **`ACTIONABLE_STATUSES`** (line 99) are processed further; everything else is filtered out of the timing logic.

## How Urgency and Next Dates Are Computed

The core logic lives in two pure functions: **`computeUrgency()`** (lines 98–115) and **`computeNextFollowupDate()`** (lines 117–133). Both are exported at lines 97 and 118, making them available for unit tests or programmatic use. Each canonical status follows its own rule set.

### Applied Status Rules

For applications in the `applied` stage:

- The first follow-up is due `applied_first` days after the submission date.
- If no follow-up exists and `daysSinceApp` has crossed that threshold, the entry is marked **overdue**.
- After the first follow-up, each subsequent touchpoint waits `applied_subsequent` days. If `daysSinceLastFollowup` exceeds that value, the entry is also **overdue**.
- If `followupCount` meets or exceeds `applied_max_followups`, the entry is marked **cold** rather than overdue.
- `computeNextFollowupDate()` returns `addDays(appDate, applied_first)` when no follow-up exists, otherwise `addDays(lastFollowupDate, applied_subsequent)`.

### Responded Status Rules

For applications with a `responded` status:

- The entry is labeled **urgent** when `daysSinceApp` is still less than `responded_initial`.
- It becomes **overdue** once `daysSinceApp` reaches or exceeds `responded_subsequent`.
- The next follow-up date is computed as `addDays(lastFollowupDate || appDate, responded_subsequent)`.

### Interview Status Rules

For applications in the `interview` stage:

- The calculator expects a thank-you follow-up within `interview_thankyou` days of the application date.
- If `daysSinceApp` is greater than or equal to that threshold, the entry is **overdue**.
- The next date is always `addDays(appDate, interview_thankyou)`.

## Aggregating Results into Actionable Output

The `analyze()` function (lines 135–222) orchestrates the full pipeline. It iterates over every actionable application, normalizes its status, gathers the associated follow-ups, and calls `computeUrgency()` and `computeNextFollowupDate()`. It also extracts contact email addresses from application notes via `extractContacts()` (lines 73–86).

The final payload assembled at lines 219–222 contains three top-level keys:

- `metadata` — counts of total, actionable, overdue, urgent, waiting, and cold entries.
- `entries` — one object per active application with fields such as `daysSinceApplication`, `followupCount`, `urgency`, `nextFollowupDate`, and `contacts`.
- `cadenceConfig` — the effective timing parameters used for the run.

By default, the CLI prints this JSON. Supplying the `--summary` flag triggers `printSummary()` (lines 274–340), which renders a human-readable table instead.

## Running the Calculator

### CLI Commands

You can execute the module directly from the terminal:

```bash

# JSON output (default)

node followup-cadence.mjs

# Human-readable summary dashboard

node followup-cadence.mjs --summary

# Show only overdue/urgent entries

node followup-cadence.mjs --overdue-only

# Override the first-follow-up interval to 10 days

node followup-cadence.mjs --applied-days 10

```

To inspect specific fields from the JSON output, pipe it through `jq`:

```bash
node followup-cadence.mjs | jq '.entries[] | {company, status, urgency, nextFollowupDate}'

```

### Programmatic API

Import the core functions to build custom workflows:

```javascript
import { computeUrgency, computeNextFollowupDate, resolveCadenceConfig } from './followup-cadence.mjs';

// Load the merged cadence (defaults + profile.yml + CLI flags)
const cadence = resolveCadenceConfig();

const status = 'applied';
const appDate = '2024-06-01';
const lastFollowup = null;
const followupCount = 0;

// Determine urgency after 15 days with no follow-up
const urgency = computeUrgency(status, 15, null, followupCount);
console.log('Urgency:', urgency); // → 'overdue'

// Get the recommended next follow-up date
const nextDate = computeNextFollowupDate(status, appDate, lastFollowup, followupCount);
console.log('Next follow-up should be on:', nextDate); // → '2024-06-08'

```

## Summary

- The `followup-cadence.mjs` script in `santifer/career-ops` computes optimal follow-up timing by comparing elapsed days against configurable cadence thresholds.
- Defaults are defined in `DEFAULT_CADENCE` (lines 35–42), overridden by [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml), and further customized via CLI flags like `--applied-days`.
- `parseTracker()` and `parseFollowups()` ingest [`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), while `normalizeStatus()` maps raw strings to canonical identifiers using `ALIASES`.
- `computeUrgency()` and `computeNextFollowupDate()` are pure, exported functions that label each application as **urgent**, **overdue**, **waiting**, or **cold**, and return the exact next follow-up date.
- The `analyze()` function aggregates everything into a JSON payload with metadata, per-entry details, and the active `cadenceConfig`.

## Frequently Asked Questions

### How does the follow-up cadence calculator define each urgency level?

The calculator uses `computeUrgency()` (lines 98–115) to compare elapsed days and follow-up counts against the effective `CADENCE` rules. An entry is **urgent** when it requires immediate attention, such as a fresh reply that needs a rapid response. It is **overdue** when the elapsed time since the last follow-up or application date has crossed the configured threshold, **cold** when the maximum number of follow-ups has been reached without a reply, and **waiting** when the recommended interval has not yet passed.

### Can I customize the follow-up intervals without editing the source code?

Yes. The `loadProfileCadence()` function (lines 58–73) reads the `followup_cadence` key from [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) and merges those values over `DEFAULT_CADENCE`. You can also use the `--applied-days <N>` CLI flag, which `resolveCadenceConfig()` (lines 75–80) applies as a final override for the first-follow-up interval.

### What input files does the calculator read to determine optimal follow-up timing?

The script reads [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md), parsed by `parseTracker()` (lines 27–44), and [`data/follow-ups.md`](https://github.com/santifer/career-ops/blob/main/data/follow-ups.md), parsed by `parseFollowups()` (lines 47–69). It also references [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml) for the canonical list of status identifiers and [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) for user-level cadence overrides.

### Is it possible to use the follow-up cadence logic in my own Node.js scripts?

Yes. Both `computeUrgency()` and `computeNextFollowupDate()` are exported at lines 97 and 118, respectively, so you can import them directly from `followup-cadence.mjs`. You can also import `resolveCadenceConfig()` to load the same merged cadence configuration that the CLI uses.