# How the Pattern Analysis Script Identifies Rejection Patterns and Improves Targeting

> Learn how the pattern analysis script identifies rejection patterns by parsing application data. Get actionable recommendations to refine your job search and improve targeting.

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

---

**The `analyze-patterns.mjs` script parses [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) and individual markdown reports to classify outcomes as positive, negative, or self-filtered, extracts hard-stop blockers like geo-restrictions and stack-mismatches, and generates quantifiable recommendations to tighten targeting filters and optimize your job search strategy.**

The **santifer/career-ops** repository includes a sophisticated pattern analysis tool that transforms raw application history into data-driven targeting improvements. By processing your tracker file and generated reports, the script identifies exactly which rejection patterns waste your evaluation effort and proposes concrete configuration changes to improve your hit rate.

## How the Script Ingests and Normalizes Application Data

The analysis pipeline begins by loading the central tracker and individual report files.

### Parsing the Tracker and Report Files

The `parseTracker` function (lines 52-70) reads [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) and converts each line into a structured object containing the report link, score, and status. For every entry, `parseReport` (lines 73-106) reads the linked markdown file to extract the *Machine Summary* block, table fields (archetype, seniority, remote policy, team size), the scoring table, and the "gaps" table listing hard-stop and soft-gap observations.

### Classifying Outcomes and Enriching Metadata

Raw status strings are cleaned via `normalizeStatus` and mapped to canonical definitions from [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml). The `classifyOutcome` function (lines 75-81) then buckets each application into **positive**, **negative**, **self_filtered**, or **pending** outcomes.

Each entry is enriched with calculated buckets. The `classifyRemote` function categorizes remote policies into `global remote`, `regional remote`, `hybrid/onsite`, or `geo-restricted`, while `classifyCompanySize` groups companies into startup, scale-up, or enterprise tiers.

## Detecting Rejection Blockers from Gap Analysis

The core rejection-pattern detection happens in the `extractBlockerType` function (lines 36-45), which examines every gap extracted from the reports.

### Categorizing Hard-Stop Blockers

For each gap, the script determines if it represents a **geo-restriction**, **stack-mismatch**, **seniority-mismatch**, **onsite-requirement**, or **other** blocker. Soft gaps marked as `nice` or `soft` are deliberately ignored to focus only on deal-breakers. A `blockerCounts` frequency map tallies each blocker type across the entire enriched dataset (lines 38-46), revealing which rejection reasons dominate your pipeline.

### Extracting Tech-Stack Gaps

For negative and self-filtered outcomes, the script performs additional tech-stack extraction (lines 100-114) to identify specific technologies mentioned in gap descriptions. This surfaces whether missing Python, JavaScript, Go, or other skills are systematically blocking your applications.

## Generating Data-Driven Targeting Recommendations

Based on the aggregated blocker analysis, the script derives actionable recommendations with specific thresholds.

### Geo-Restriction and Stack-Mismatch Thresholds

If **geo-restriction** blockers appear in 20% or more of applications, the script recommends tightening location filters in [`portals.yml`](https://github.com/santifer/career-ops/blob/main/portals.yml) (lines 24-32). Similarly, when **stack-mismatch** blockers hit 15% or higher, it lists the top missing technologies and suggests adding negative filters to your search configuration (lines 34-43).

### Score Thresholds and Archetype Optimization

The script calculates the lowest score among all positive outcomes to propose a minimum viable score threshold before generating PDFs (lines 45-52). It also identifies the best-performing archetype and remote-policy combinations, advising you to double down on high-conversion categories while eliminating policies showing 0% conversion rates (lines 54-71).

## Running the Pattern Analysis

Execute the script from the repository root to generate the analysis.

Run a full JSON analysis to get machine-readable output:

```bash
node analyze-patterns.mjs

```

Generate a human-readable summary table:

```bash
node analyze-patterns.mjs --summary

```

Verify the Machine Summary parser integrity:

```bash
node analyze-patterns.mjs --self-test

```

The default JSON output includes metadata, funnel statistics, blocker analysis with percentages, and specific recommendations targeting your [`portals.yml`](https://github.com/santifer/career-ops/blob/main/portals.yml) configuration.

## Summary

- The `parseTracker` and `parseReport` functions ingest data from [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) and individual markdown reports to build a structured dataset.
- `extractBlockerType` classifies hard-stop rejection reasons into geo-restrictions, stack-mismatches, and other blockers while ignoring soft gaps.
- Recommendation thresholds trigger at 20% for geo-restrictions and 15% for stack-mismatches, proposing specific filters for [`portals.yml`](https://github.com/santifer/career-ops/blob/main/portals.yml).
- The script calculates minimum viable score thresholds from positive outcomes and identifies high-converting archetypes to optimize your targeting strategy.

## Frequently Asked Questions

### How does the script distinguish between soft preferences and hard rejection blockers?

The `extractBlockerType` function (lines 36-45) examines gap classifications from the report parser. It explicitly filters out gaps tagged as `nice` or `soft`, processing only hard-stop observations that represent actual rejection reasons such as geo-restrictions or mandatory onsite requirements.

### What specific thresholds trigger the targeting recommendations?

The script implements quantified thresholds: **geo-restriction** recommendations trigger when 20% or more of applications hit location limits, while **stack-mismatch** advice appears at 15% frequency. These thresholds ensure recommendations reflect statistically significant patterns rather than isolated incidents.

### Can I run this analysis if I haven't generated individual markdown reports yet?

No. The `parseReport` function requires the individual markdown reports referenced in [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) to extract the Machine Summary blocks and gap tables. Without these generated reports, the script cannot perform blocker classification or outcome analysis.

### How does the script determine the minimum score threshold for future applications?

The script analyzes the score distribution of all **positive** outcomes (successful applications) and identifies the minimum score in that cohort (lines 45-52). It then recommends setting your minimum viable score slightly above this floor to avoid wasting effort on applications unlikely to convert, based on your historical success data.