# How analyze-patterns.mjs Identifies Trends in Rejection Reasons and Application Success

> Discover how analyze-patterns.mjs identifies trends in rejection reasons and application success by parsing data, normalizing statuses, and computing aggregates to highlight frequent rejections and offer correlations.

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

---

**`analyze-patterns.mjs` parses the master application tracker, normalizes status values against a canonical schema, and computes statistical aggregates to surface which rejection reasons occur most frequently and which role keywords correlate with offers.**

The `analyze-patterns.mjs` script is the pattern-analysis engine of the Career-Ops repository. It transforms raw application history into actionable intelligence by quantifying why jobs are rejected and what factors drive successful outcomes. This lightweight JavaScript module operates without external dependencies, making it portable across any environment where the Career-Ops repository is cloned.

## Data Ingestion from the Master Tracker

The script begins by reading [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md), which stores the complete application history in a markdown table format.

**`analyze-patterns.mjs`** splits the markdown table and converts each row into a structured record containing fields for **date**, **company**, **role**, **status**, **score**, and **notes**. This parsing step creates a machine-readable dataset from the human-friendly tracker, enabling downstream statistical operations.

```javascript
// Conceptual flow of the ingestion phase
const records = parseTracker('data/applications.md');
// Each record: { date, company, role, status, score, notes }

```

## Signal Extraction and Status Normalization

Once ingested, the script normalizes status values against the canonical state list defined in **[`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml)**. This ensures that custom status text (such as "Declined" or "Pass") maps to standard tokens like `Rejected`, `Discarded`, `Offer`, or `Applied`.

For **rejection reason extraction**, the script inspects the `notes` column whenever the normalized status is `Rejected` or `Discarded`. It identifies keywords such as **"salary"**, **"culture"**, or **"lack-of-experience"** and stores these as `rejectionReason` attributes. Similarly, it marks records with `Offer` status or high scores as **success signals**, capturing the role and company context for each positive outcome.

## Statistical Aggregation and Trend Calculation

The core analysis logic builds two primary frequency maps:

- **`rejectionCounts[reason]`** – Tally of rejections grouped by extracted reason
- **`successCounts[roleKeyword]`** – Count of offers grouped by role keyword (derived from job title parsing)

The script then calculates percentages relative to total applications and ranks the **top-N items** by frequency. It also groups data by dimensions such as **company**, **role category**, **keyword**, and **time window** (weekly or monthly) to surface temporal trends.

```javascript
// Aggregation logic within analyze-patterns.mjs
const trends = [
  { type: 'RejectionReason', label: 'salary-mis-match', count: 14, percentage: 28 },
  { type: 'SuccessKeyword', label: 'machine-learning', count: 5, percentage: 12 }
];

```

## JSON Output Generation

The computed aggregates are serialized to **[`output/patterns/patterns.json`](https://github.com/santifer/career-ops/blob/main/output/patterns/patterns.json)**. This JSON payload contains a list of trend objects, each specifying the type (rejection or success), label, count, and percentage.

Other Career-Ops modules—including the follow-up cadence script—consume this file to suggest improvements. For example, if **salary-mis-match** appears in 28% of rejections, the system might recommend researching market rates before the next application wave.

```javascript
import fs from 'fs';

const trends = JSON.parse(fs.readFileSync('output/patterns/patterns.json', 'utf8'));

for (const t of trends) {
  console.log(`${t.type}: ${t.label} → ${t.percentage}% (${t.count} occurrences)`);
}

```

## Running the Analysis

Execute the script from the repository root to regenerate trend statistics:

```bash
node analyze-patterns.mjs

```

This command reads the current state of [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) and produces fresh aggregates in `output/patterns/`. Because the script is **pure JavaScript** with no external dependencies, it runs on any Node.js environment without additional package installation.

## Summary

- **`analyze-patterns.mjs`** serves as the statistical engine for the Career-Ops repository, converting application history into quantified trend data.
- The script reads from **[`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md)**, normalizes statuses using **[`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml)**, and extracts rejection reasons from the notes field.
- It computes **frequency maps** for rejection reasons and success keywords, calculating percentages and ranking top-N trends.
- Results are exported to **[`output/patterns/patterns.json`](https://github.com/santifer/career-ops/blob/main/output/patterns/patterns.json)** for consumption by other tools or manual review.
- The architecture is **stateless and portable**, allowing re-execution at any time to track how patterns evolve as new applications are added.

## Frequently Asked Questions

### How does analyze-patterns.mjs handle inconsistent status labels?

The script references **[`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml)** to map any custom status text to canonical tokens such as `Rejected`, `Offer`, or `Applied`. This normalization ensures that variations like "Declined" or "Not Moving Forward" are counted consistently under the `Rejected` category.

### What specific rejection keywords does the script detect?

The extraction logic scans the `notes` column for terms such as **"salary"**, **"culture"**, and **"lack-of-experience"**. These keywords are stored as `rejectionReason` values and aggregated in the `rejectionCounts` map to calculate frequency percentages.

### Where are the trend statistics stored after analysis?

The script writes a JSON file to **[`output/patterns/patterns.json`](https://github.com/santifer/career-ops/blob/main/output/patterns/patterns.json)**. This file contains trend objects with type, label, count, and percentage fields, which can be imported by visualization tools or other Career-Ops automation scripts.

### Can I run analyze-patterns.mjs without installing dependencies?

Yes. The script is implemented in **pure JavaScript** with no external dependencies beyond Node.js itself. Simply run `node analyze-patterns.mjs` from the repository root after cloning `santifer/career-ops`.