# Canonical Application States and Alias Handling in Career-Ops

> Learn about canonical application states and alias handling in Career-Ops. Understand how santifer/career-ops normalizes user inputs for data consistency.

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

---

**Career-Ops enforces a strict set of eight canonical application states defined in [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml), normalizing multilingual user inputs through the `validateStatus()` function in `merge-tracker.mjs` to ensure data consistency across batch processing pipelines.**

The Career-Ops repository (santifer/career-ops) manages job application tracking through a centralized status system. Canonical application states serve as the single source of truth for both automated batch scripts and the dashboard interface, ensuring consistent reporting regardless of whether inputs arrive in English, Spanish, or marked-down formats.

## What Are Canonical Application States?

Canonical application states represent the fixed vocabulary that Career-Ops uses to track job application progress. These states are declared centrally in [[`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml)](https://github.com/santifer/career-ops/blob/main/templates/states.yml) (lines 9–56) and function as the **single source of truth** for the entire system.

The repository defines exactly eight canonical states:

- **`evaluated`** — Initial assessment complete (aliases: `evaluada`, `condicional`, `hold`, `verificar`)
- **`applied`** — Application submitted (aliases: `aplicado`, `enviada`, `aplicada`, `applied`, `sent`)
- **`responded`** — Employer replied (aliases: `respondido`)
- **`interview`** — Interview scheduled or completed (aliases: `entrevista`)
- **`offer`** — Job offer received (aliases: `oferta`)
- **`rejected`** — Application declined (aliases: `rechazado`, `rechazada`)
- **`discarded`** — Manually removed from active tracking (aliases: `descartado`, `descartada`, `cerrada`, `cancelada`)
- **`SKIP`** — Intentionally skipped entries (aliases: `no_aplicar`, `no aplicar`, `skip`, `monitor`, `geo blocker`)

Every entry written to [`applications.md`](https://github.com/santifer/career-ops/blob/main/applications.md) must resolve to one of these IDs, regardless of the original input format.

## How Alias Resolution Works in merge-tracker.mjs

When batch workers generate TSV files in `batch/tracker-additions/`, they may use localized labels, markdown formatting, or shorthand aliases. The `validateStatus()` function in [`merge-tracker.mjs`](https://github.com/santifer/career-ops/blob/main/merge-tracker.mjs) (lines 34–78) handles normalization through a five-step pipeline:

1. **Strips markdown** — Removes bold markers (`**`) and trailing date text
2. **Normalizes case** — Converts the string to lower-case for comparison
3. **Checks canonical states** — Validates against the `CANONICAL_STATES` array
4. **Maps aliases** — If not a direct match, looks up the value in the internal alias map
5. **Falls back safely** — Returns `"Evaluated"` as the default state while emitting a warning for unrecognized inputs

This guarantees that [`applications.md`](https://github.com/santifer/career-ops/blob/main/applications.md) contains only canonical values, preventing data drift between writer scripts and the reader dashboard.

### The Validation Pipeline

The `validateStatus()` function implements strict normalization before writing to the tracker. It accepts raw strings from TSV inputs and applies regex cleaning to handle user-generated variations.

### Fallback Behavior

When `validateStatus()` encounters an unrecognized status—one that matches neither a canonical state nor a defined alias—it automatically assigns the **Evaluated** state and logs a warning. This prevents pipeline failures while flagging data quality issues for manual review.

## Working with Aliases: Practical Examples

Career-Ops supports multilingual workflows through its alias system. Below are concrete implementations showing how raw inputs transform into canonical states.

### Processing Spanish Aliases in TSV Inputs

Batch scripts often generate TSV rows using Spanish terminology. When `merge-tracker.mjs` processes these files, it maps aliases automatically.

Consider this raw TSV entry:

```tsv
12	2024-06-13	Acme Corp	Software Engineer	4.2/5	Aplicado	✅	[12](reports/12-acme-software-engineer-2024-06-13.md)	Initial eval

```

The `validateStatus('Aplicado')` call normalizes this to the canonical **Applied** state, writing the following to [`applications.md`](https://github.com/santifer/career-ops/blob/main/applications.md):

```markdown
| 12 | 2024-06-13 | Acme Corp | Software Engineer | 4.2/5 | Applied | ✅ | [12](reports/12-acme-software-engineer-2024-06-13.md) | Initial eval |

```

### Using the validateStatus Helper Directly

You can import and use the validation logic in custom scripts:

```javascript
import { validateStatus } from './merge-tracker.mjs';

const rawStatus = '**Enviada**';        // bold-markdown variant
const canonical = validateStatus(rawStatus);
console.log(canonical); // → "Applied"

```

The function handles markdown stripping, case normalization, and alias mapping in a single call.

### Extending the Alias Map

To support new terminology, modify the `aliases` object inside `merge-tracker.mjs` (around line 58):

```javascript
const aliases = {
  // existing entries ...
  'enviado': 'Applied',   // new alias for Applied
  'pendiente': 'Evaluated', // new alias for Evaluated
};

```

After adding entries, any TSV containing "enviado" or "pendiente" automatically maps to the appropriate canonical state.

## Key Files and Their Responsibilities

| File | Role | Location |
|------|------|----------|
| [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml) | Defines the canonical state list and default aliases | [[`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml)](https://github.com/santifer/career-ops/blob/main/templates/states.yml) |
| `merge-tracker.mjs` | Reads pending TSVs, normalizes states via `validateStatus()`, merges into tracker | [`merge-tracker.mjs`](https://github.com/santifer/career-ops/blob/main/merge-tracker.mjs) |
| `batch/tracker-additions/` | Directory holding TSV files generated by batch evaluation scripts | [`batch/tracker-additions/`](https://github.com/santifer/career-ops/tree/main/batch/tracker-additions) |
| [`applications.md`](https://github.com/santifer/career-ops/blob/main/applications.md) | Human-readable markdown table displaying canonical statuses | Root or [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) |

These components enforce a **single source of truth** across the Career-Ops pipeline, supporting multilingual inputs while maintaining strict data consistency.

## Summary

- **Canonical application states** are defined in [`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml) and represent the only valid values written to the tracker.
- **Alias resolution** occurs in `merge-tracker.mjs` through the `validateStatus()` function, which strips markdown, normalizes case, and maps multilingual inputs.
- **Fallback protection** ensures unrecognized statuses default to "Evaluated" rather than breaking the pipeline.
- **Extensibility** is achieved by adding entries to the alias map in `merge-tracker.mjs`, allowing customization without modifying core state definitions.

## Frequently Asked Questions

### What happens if I use an unrecognized status alias?

Career-Ops falls back to the **Evaluated** state and emits a warning. The `validateStatus()` function in `merge-tracker.mjs` (lines 34–78) treats any unrecognized input as requiring initial evaluation, ensuring the pipeline continues while flagging data quality issues.

### Where are the canonical application states defined?

The canonical list resides in [[`templates/states.yml`](https://github.com/santifer/career-ops/blob/main/templates/states.yml)](https://github.com/santifer/career-ops/blob/main/templates/states.yml) (lines 9–56). This file serves as the single source of truth for both the batch processing scripts and the dashboard interface, defining eight possible states from `evaluated` to `SKIP`.

### How does Career-Ops handle multilingual status inputs?

The system supports Spanish and English variants through a hard-coded alias map within `validateStatus()`. Inputs like `Aplicado`, `enviada`, or `rechazada` automatically map to their English canonical equivalents (`Applied`, `Rejected`) during the merge process, enabling multilingual batch workflows without requiring translation layers.

### Can I add custom aliases for existing states?

Yes. Modify the `aliases` object in [`merge-tracker.mjs`](https://github.com/santifer/career-ops/blob/main/merge-tracker.mjs) around line 58 to include new key-value pairs. For example, adding `'enviado': 'Applied'` allows TSV files containing "enviado" to resolve correctly to the **Applied** canonical state without changing the underlying [`states.yml`](https://github.com/santifer/career-ops/blob/main/states.yml) definition.