# How to Detect Geo-Mismatches Between Job Posting Location and JD Body Requirements

> Discover how to detect geo-mismatches in job postings by comparing location data. Ensure your job descriptions accurately reflect location requirements for better candidate targeting.

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

---

**To detect geo-mismatches in Career-Ops, compare the normalized `location` value from the provider's parsing function against location mentions extracted from the job description body, flagging cases where the portal metadata contradicts the JD text.**

Career-Ops is an open-source job application tracker that normalizes posting data from multiple providers. Detecting geo-mismatches between job posting location and JD body requirements ensures you do not waste time on roles that advertise "Remote" but mandate on-site attendance in the fine print.

## Understanding the Two Location Data Sources

Career-Ops stores geographic information in two distinct formats that must be reconciled to identify discrepancies.

### Portal-Provided Location Metadata

Each provider module normalizes raw portal data into a consistent `location` field. In `providers/workday.mjs`, the `parseWorkdayResponse` function cleans portal-specific formats—removing trailing whitespace, standardizing "Remote" tokens, and enforcing city-plus-country structures. Similar normalization occurs in `providers/workingnomads.mjs` and other provider modules, ensuring all ingested postings converge on the same location representation regardless of source.

### JD Body Location Requirements

The free-text job description is persisted to markdown report files (e.g., [`reports/001-acme-2024-05-01.md`](https://github.com/santifer/career-ops/blob/main/reports/001-acme-2024-05-01.md)) during pipeline execution. While the portal provides structured location metadata, the JD body may contain explicit geographic constraints—phrases like "must work on-site in Berlin" or "hybrid requirement in London"—that override or contradict the portal field.

## Implementing the Detection Logic

The detection algorithm normalizes both sources and performs a string comparison to surface conflicts.

### Extracting Location Cues from JD Text

Use a regex-based heuristic to pull location mentions from the description text. The pattern should identify work-mode keywords (`Remote`, `On-site`) and geographic entities.

```js
function extractLocationFromDescription(text) {
  const LOC_REGEX = /\b(?:Remote|On[-\s]site|[A-Z][a-z]+(?:,\s*[A-Z][a-z]+)?)\b/g;
  const matches = text.match(LOC_REGEX);
  if (!matches) return '';
  // Deduplicate and join (e.g. "Remote, Berlin, Germany")
  return [...new Set(matches)].join(', ');
}

```

### Comparing Normalized Values

Import the provider's normalization routine (such as `jobLocation` or `parseWorkdayResponse` from `providers/workday.mjs`) to ensure the comparison uses identical cleaning logic for both sides.

```js
import { parseWorkdayResponse } from './providers/workday.mjs';
import { readFile } from 'fs/promises';
import { resolve } from 'path';

export async function detectGeoMismatch(reportPath, rawJob) {
  const reportMd = await readFile(resolve(reportPath), 'utf8');
  
  // Portal location from normalized provider output
  const normJob = parseWorkdayResponse([rawJob])[0];
  const portalLoc = normJob.location ?? '';
  
  // Extracted location from JD body
  const jdLoc = extractLocationFromDescription(reportMd);
  
  // Normalize both sides for comparison
  const normPortal = portalLoc.trim().toLowerCase();
  const normJd = jdLoc.trim().toLowerCase();
  
  const mismatch = normPortal && normJd && normPortal !== normJd;
  return { portalLoc, jdLoc, mismatch };
}

```

When `mismatch` is true, the portal listing and JD body contain conflicting geographic requirements.

## Integrating with the Career-Ops Pipeline

The detection routine fits into the ingestion workflow at multiple extension points.

### Persistent Storage in the Tracker

The `tracker-parse.mjs` module defines the `location` column in [`applications.md`](https://github.com/santifer/career-ops/blob/main/applications.md), persisting the provider-normalized value. This allows you to re-run geo-mismatch checks retrospectively against newly generated reports without re-fetching from the portal.

### Schema Validation for Filters

Before implementing custom whitelist or blacklist logic for locations, reference `validate-portals.mjs`. This module validates the shape of `location_filter` objects, ensuring any geo-mismatch flags you add respect the same schema used by the portal validation system.

### Pipeline Integration Point

Invoke `detectGeoMismatch` inside `pipeline.mjs` immediately after report generation. If a mismatch is detected, append a warning flag to the report header:

```js
if (result.mismatch) {
  reportHeader += '\n**Geo-Mismatch:** true';
}

```

## Summary

- **Normalize both sources** using the provider's own cleaning functions (e.g., `parseWorkdayResponse`) to ensure consistent string formats.
- **Extract JD mentions** with regex patterns targeting work-mode keywords and city/country pairs.
- **Persist portal locations** via `tracker-parse.mjs` to enable retrospective validation against evolving JD text.
- **Validate custom logic** against the `location_filter` schema in `validate-portals.mjs` to maintain consistency with existing filtering code.
- **Flag discrepancies** during report generation in `pipeline.mjs` by appending metadata headers to the markdown output.

## Frequently Asked Questions

### How does Career-Ops normalize location data from different job portals?

Provider modules such as `providers/workday.mjs` and `providers/workingnomads.mjs` expose normalization functions that trim whitespace, standardize "Remote" tokens, and enforce consistent city-plus-country formatting. These functions ensure the `location` field uses a uniform structure regardless of the source portal's native format.

### Where is the job description text stored for geo-mismatch analysis?

The JD body is written to markdown files in the `reports/` directory (e.g., [`reports/001-acme-2024-05-01.md`](https://github.com/santifer/career-ops/blob/main/reports/001-acme-2024-05-01.md)) when the pipeline generates a report. The `detectGeoMismatch` function reads these files to extract location cues using regex patterns.

### Can geo-mismatch detection run after the initial ingestion?

Yes. Because `tracker-parse.mjs` persists the normalized `location` value in [`applications.md`](https://github.com/santifer/career-ops/blob/main/applications.md), you can run detection as a post-processing step. Re-scan the JD descriptions in existing report files against the stored location values without re-fetching data from the portals.

### What constitutes a geo-mismatch versus a valid variation?

A geo-mismatch occurs when the normalized portal location (e.g., "Remote") and the extracted JD location (e.g., "On-site, Berlin") are non-empty and differ after trimming and lowercasing. This flags explicit contradictions where the posting metadata conflicts with the description text, helping filter roles that violate your geographic preferences.