# How the Transform Module Normalizes LLM JSON to JSON Resume Format

> Learn how the transform module normalizes LLM JSON to JSON Resume format. It uses deterministic transformers to standardize dates, flatten arrays, and map keys for hiring-agent.

- Repository: [HackerRank/hiring-agent](https://github.com/interviewstreet/hiring-agent)
- Tags: how-to-guide
- Published: 2026-07-14

---

**The transform module in `interviewstreet/hiring-agent` converts arbitrary LLM-generated JSON into the standardized JSON-Resume schema through a deterministic pipeline of section-specific transformers that normalize dates, flatten description arrays, and map ambiguous keys to canonical fields.**

The hiring-agent repository provides a robust pipeline for processing resume data extracted by large language models. At its core, the `transform` module ([`transform.py`](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py)) serves as the normalization engine that bridges raw LLM outputs with the structured JSON-Resume specification, ensuring downstream components receive validated, predictable data structures.

## The Normalization Pipeline Architecture

The transformation process follows a sequential, deterministic flow where `transform_parsed_data` acts as the central dispatcher. This function receives the raw LLM output as a Python dictionary, validates that the top-level object is a mapping, and then routes each section to specialized transformer functions. The architecture ensures that even loosely structured LLM JSON—containing inconsistent field names, free-form date strings, or nested description arrays—emerges as a standards-compliant JSON-Resume document.

## Core Transformation Functions

### Entry Point and Validation

The `transform_parsed_data` function serves as the primary entry point in [`transform.py`](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py). It inspects the incoming dictionary for standard resume sections and delegates each to appropriate handlers. Before processing, it verifies that the input is a valid mapping, preventing malformed LLM outputs from propagating through the pipeline.

### Normalizing Basics and Profiles

When a `basics` section exists, the module invokes `transform_basics` to standardize personal information and online profiles. This function processes profile URLs by extracting domain information and deriving canonical network names (GitHub, LinkedIn, etc.) using the helper functions `extract_domain_from_url` and `get_network_name`. If usernames are missing from profile URLs, the transformer extracts them automatically, ensuring the final JSON-Resume `basics` object contains properly structured `profiles` arrays with `network`, `username`, and `url` fields.

### Standardizing Work Experience

The `transform_work_experience` function handles both `work` and `work_experience` keys, normalizing them into the JSON-Resume `work` schema. This transformer performs three critical operations:

- **Array Flattening**: Converts description arrays (e.g., `["Built API", "Wrote tests"]`) into single `summary` strings
- **Date Parsing**: Processes free-form date ranges like "Jan-Mar 2021" into ISO-8601 `startDate` and `endDate` fields using `parse_date_range`
- **Key Mapping**: Translates ambiguous LLM keys such as `type` or `title` into the canonical `position` field required by the specification

### Handling Volunteer and Organizations

For non-professional experience, `transform_organizations` normalizes any `organizations` array into the JSON-Resume `volunteer` schema. This function provides default placeholders for dates and highlights when the LLM output lacks temporal information, ensuring schema compliance even with incomplete data.

### Education Standardization

The `transform_education` function converts education entries by extracting degree information into `studyType` and `area` fields, as defined in [`transform.py`](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py). It parses GPA or percentage values into standardized strings and splits date ranges into separate `startDate` and `endDate` fields, handling variations in how LLMs represent academic timelines.

### Formatting Awards and Achievements

Achievements are normalized through `transform_achievements`, which creates uniform award objects containing `title`, optional `date`, `awarder`, and `summary` fields. This ensures that recognition data from arbitrary LLM structures fits the JSON-Resume `awards` schema.

### Categorizing Skills

The `transform_skills_comprehensive` function implements a dual-pathway approach for skills normalization. When the LLM returns a simple list of strings, the transformer categorizes them under "Programming Languages." For objects containing a `category` field, it preserves the existing structure. Additionally, the function merges top-level fields like `librariesFrameworks`, `toolsPlatforms`, and `databases` into descriptive category names, creating a comprehensive `skills` array that matches the JSON-Resume specification.

### Processing Projects

Both standard `projects` and open-source `projectsOpenSource` sections are handled by `transform_projects_comprehensive`. This function extracts skill information from project titles using pipe-delimited formats (e.g., "MyApp | Python, Flask"), normalizes `technologies` fields, and produces uniform project entries with `name`, `description`, `highlights`, and `keywords` fields.

### Preserving Meta and Miscellaneous Data

Any remaining top-level keys—including `certificates`, `publications`, `languages`, `interests`, `references`, and `meta`—are copied verbatim during final assembly in `transform_parsed_data`. This ensures the output dictionary complies with the full JSON-Resume schema without data loss.

## Practical Implementation Examples

The following example demonstrates the complete normalization workflow from raw LLM output to validated JSON-Resume:

```python
from transform import transform_parsed_data
from models import JSONResume

# `llm_output` is the raw JSON dict returned by the LLM

normalized = transform_parsed_data(llm_output)

# Build a proper JSONResume object (uses pydantic for validation)

resume = JSONResume(**normalized)

print(resume.json(indent=2))

```

For processing individual sections without the full pipeline:

```python
from transform import transform_work_experience

raw_work = [
    {
        "title": "Software Engineer",
        "company": "Acme Corp",
        "startDate": "Jan-Mar 2021",
        "description": ["Built API", "Wrote tests"]
    }
]

standard_work = transform_work_experience(raw_work)
print(standard_work)

# → [{'name': '', 'position': 'Software Engineer', 'url': None,

#     'startDate': 'Jan 2021', 'endDate': 'Mar 2021',

#     'summary': 'Built API Wrote tests', 'highlights': []}]

```

## Integration with the Hiring Agent Pipeline

The transform module operates between the LLM invocation layer and the validation/scoring layers. According to the `interviewstreet/hiring-agent` source code:

- **[`llm_utils.py`](https://github.com/interviewstreet/hiring-agent/blob/main/llm_utils.py)** handles the LLM invocation and initial JSON parsing before data reaches the transform module
- **[`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py)** defines the `JSONResume` Pydantic model that validates the transformed output, ensuring type safety and schema compliance
- **[`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py)** consumes the normalized `JSONResume` objects for downstream candidate evaluation, relying on the canonical structure produced by the transform functions

This architecture creates a robust pipeline that takes loosely-structured LLM JSON and guarantees a standards-compliant JSON-Resume document ready for analysis, scoring, or export.

## Summary

- The `transform` module serves as the normalization engine in `interviewstreet/hiring-agent`, converting raw LLM JSON into JSON-Resume format through `transform_parsed_data` and specialized section transformers.
- Key functions include `transform_basics` for profile normalization, `transform_work_experience` for date parsing and key mapping, and `transform_skills_comprehensive` for category management.
- Helper functions like `parse_date_range`, `extract_domain_from_url`, and `get_network_name` handle domain-specific normalization tasks.
- The output is validated against the `JSONResume` Pydantic model in [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py), ensuring full compliance with the JSON-Resume specification before downstream processing.

## Frequently Asked Questions

### What is the JSON-Resume format and why does the hiring-agent use it?

JSON-Resume is an open-source standard for structuring resume data in JSON format. The hiring-agent uses this specification because it provides a consistent, machine-readable schema that enables reliable parsing, validation, and scoring across different candidate profiles, regardless of variations in the original LLM output structure.

### How does the transform module handle inconsistent date formats from LLMs?

The `transform_work_experience` and `transform_education` functions utilize the `parse_date_range` helper to interpret free-form date strings (such as "Jan-Mar 2021" or "2020-Present") and convert them into standardized ISO-8601 date formats. This ensures chronological data remains comparable and validatable across all candidate entries.

### What happens when the LLM uses ambiguous field names like "title" instead of "position"?

The `transform_work_experience` function implements explicit key mapping logic that translates ambiguous LLM field names—such as `title`, `type`, or `role`—into the canonical `position` field required by the JSON-Resume schema. This normalization occurs during the transformation process, ensuring standardization before validation.

### How does the module ensure the transformed output is valid JSON-Resume?

After transformation, the normalized dictionary is passed to the `JSONResume` Pydantic model defined in [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py). This model enforces strict type checking and schema validation, ensuring that all required fields are present and correctly formatted according to the JSON-Resume specification before the data reaches the scoring layer in [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py).