Skip to content

CDR Processing Rules

Every carrier sends call data in its own format: different columns, different date styles, different ways of marking call direction. Processing rules tell the platform how to read each carrier’s files, so the calls inside them can be rated and billed.

Each carrier has a set of processing rules per file type (daily CDRs, monthly CDRs, fixed fee files). The rules cover:

  • Recognising files as they arrive: which carrier and billing period a file belongs to, and whether it should be billed, held or rejected
  • Reading the file: encoding, delimiters, header rows and junk lines
  • Mapping columns to call fields such as the number, date, duration, charge band and cost
  • Producing call records: some files describe two call legs per row, or need calls split by direction

Processing rules are platform configuration, managed by your administrator or the support team. They are not a self-service screen: if a carrier changes its file format, or you add a new carrier, support will set up or adjust the rules. This page explains how they behave, and documents the rules language itself.

The platform supports two rules formats, and each carrier ruleset uses one or the other:

  • A legacy format used by long-standing carrier configurations. It still works unchanged.
  • The YAML rules language documented here, shared with Bulk Update rules. New rulesets use YAML, and legacy rulesets are migrated one at a time. A converter produces a draft YAML ruleset from a legacy one, together with a review report listing anything that needs a human decision.

The platform detects the format automatically, so migration is gradual and per carrier.

Before a file is read, the recognise: rules classify it:

  • Monthly, daily or reject rules match on the file’s name, path or content. A rejected file is marked Rejected and not processed. The billing setting decides whether a matched file feeds billing, statistics or is held for review.
  • Required headers protect you from silent format changes. If a supplier renames or drops an expected column, the file is Held rather than half-processed, and the queue report says why. This stops a quiet supplier-side change producing wrong bills.
  • Duplicate handling controls what happens when a file arrives with a familiar name or identical contents: import, ignore, hold or reject. See Potential Duplicate Files for the operator’s view.

The rest of this page is a reference for the YAML rules language as it applies to CDR and fixed fee files. It is written so that an AI assistant can draft a ruleset from a sample file, given the file’s header row and a few sample rows.

Working with an AI assistant? Give it the plain-Markdown version of this page: /telecommunications/cdr-processing-rules/agents.md. Ask the assistant to fetch that address, or if it cannot fetch pages, download the file and attach it to your conversation. Both are more reliable than copying and pasting this web page.

For the AI assistant: you are writing a kind: cdr (or kind: transactions) YAML ruleset for the SAFE Billing Platform’s CDR import engine. The shared language basics are identical to the Update Rules Reference: file: framing, fields: with sources (header, headers, column, value, source: filename), translations, skipRowValues, transforms, rowRules (including warn groups, which flag rows in the processing report without skipping them), ignore, and the condition grammar (anyOf/allOf/noneOf; operators equals, prefix, suffix, contains, matches, over, under, atLeast, atMost, before, after, onOrBefore, onOrAfter, lengthOver, lengthUnder, sameAs, empty, present, always, columnCountAbove, columnCountBelow). The date operators take a fixed date or a named expression such as first of last month, anchored to the file’s upload date. Validation is strict: unknown keys are errors, so never invent keys. version: and kind: are optional; if the ruleset is for fixed fee (carrier transaction) files use kind: transactions, otherwise cdr.

What differs for CDR rules:

  1. Field names are call fields, and you may use their friendly spellings: Date, Time, Duration, Target (the billed number), Called Number, Calling Number, Charge Band, Call Type, Cost, Extension, and so on. A field name the call record does not use, such as Temporary Field 1 or any temp.-prefixed name, is scratch space: you can read, transform and test it in conditions, but it is not written to the call.
  2. records: produces the output calls. Without it, each row produces one call. With it, one row can produce several call legs, each with its own direction and column bindings.
  3. flags:, settings: and recognise: are available; lookups: and emit: are not (they belong to bulk update rules).

Each entry describes one output record. All shared fields: apply to every record; a record can override or add its own.

KeyMeaning
nameRequired, unique. Used in the drop report.
prefixColumn-prefix sugar: with prefix: A, a shared field bound to header Number also binds to a column headed A Number for this record. This is the usual way to map per-leg columns.
fieldsPer-record field overrides. An override that declares a source replaces the shared one; an override with only transforms keeps the shared source.
directioninbound, outbound, aleg, bleg, neither, determine, or determine b leg. The determine forms compare the target against the called and calling numbers.
directionWhenA list of {direction: ..., when: <conditions>} entries; the first match wins over the static direction.
whenEmit this record only when the conditions match.
skipDrop this record when the conditions match (the row’s other records still emit).
formatted{when: ...} and/or {skip: ...} evaluated against the final values just before insert, after transforms.
useAlternateSwap in another field’s value conditionally: Charge Band: {from: Alternate Charge Band, when: [...]}.
surcharge{when: <conditions>}: marks the record as a surcharge leg; the platform derives the surcharge charge band from the call’s origin and destination.

Records that are declared but not emitted are counted per reason in the processing report, so nothing is dropped silently.

Each flag is {when: <conditions>}, evaluated per record. Declared flags are set true or false; undeclared flags are left for the platform to work out where it can.

Available flags: inbound, outbound, dataCall, mobileOrigination, payphoneOrigination, callRecording, sms, surcharge, numberWithheld.

flags:
mobileOrigination:
when:
- {field: 'Call Type', equals: ['M', 'MOB']}
KeyMeaning
autoMapHeaderstrue binds any column whose heading matches the platform’s built-in call field names automatically. Explicit fields: always win. Most rulesets set this and map only the exceptions.
cost{scale: 100, decimalSeparator: ','}: divide costs supplied in pence, or fix a decimal comma.
numbers{mode: normalise} (also international, e164, remove44): normalisation applied to the call’s number fields.
dates{date: '%d/%m/%Y', time: '%H:%M:%S', datetime: ...}: the file’s date and time patterns.
dataData-call handling: {round: ..., scale: ..., useDuration: true} treats a data volume as the duration.
normalisePer number-role fine tuning (billed, origination, termination, called): lookup order and minimum lengths.
generateCallIDsdefault, per-line or grouped-lines, for files without a usable unique call id.
skipOldCallsBeforeA date expression; calls starting before it are dropped and reported.
timezoneThe file’s timezone, where it differs from the platform’s.
KeyMeaning
monthly / daily / rejectMatch blocks with filename, exceptFilename, path, exceptPath (regular expressions), headerMatches (regex tested against the first lines), contentMatches: {line: N, pattern: ...}, and billing (billing, stats, hold, manual or none).
requiredHeadersColumn headings that must be present, or the file is held for review.
duplicates{filename: ..., contents: ...} each one of import, ignore, reject, hold; plus minimumSize in bytes.
housekeepingRetention of processed files, for example {dailyFiles: delete}.

Worked Example: Two Call Legs with Direction Logic

Section titled “Worked Example: Two Call Legs with Direction Logic”

One row per call, with A-prefixed and B-prefixed columns for the two legs. The B leg is only produced when its column is filled, and an alternate charge band is used for CPS and WLR calls:

kind: cdr
settings:
autoMapHeaders: true
cost:
scale: 100
numbers:
mode: normalise
dates:
date: '%d/%m/%Y'
fields:
Duration:
header: Call Length
transforms:
- duration: normalise
Call Status:
header: Status
translations:
OK: answered
ENGAGED: busy
rowRules:
skip:
- name: Internal calls
when:
- {field: 'Charge Band', equals: ['Internal', '']}
records:
- name: aLeg
prefix: A
fields:
Target:
header: Caller
directionWhen:
- direction: inbound
when:
- {field: 'Charge Band', prefix: ['IN']}
useAlternate:
Charge Band:
from: Alternate Charge Band
when:
- {field: 'Charge Band', equals: ['CPS', 'WLR']}
- name: bLeg
prefix: B
when:
- {field: 'B Number', present: true}
fields:
Target:
header: Called

Worked Example: Header-less File with Conditional Direction

Section titled “Worked Example: Header-less File with Conditional Direction”
kind: cdr
file:
header: false
fields:
Call Type: {column: 1}
Target: {column: 2}
records:
- name: leg
direction: outbound
directionWhen:
- direction: inbound
when:
- {field: 'Call Type', equals: ['I', 'A']}

Fixed fee (SDR) files use the same language with kind: transactions. The field roster is the carrier transaction’s fields, including the number references used to match each charge to a customer, number or feature, and the transaction value, which respects settings.cost. The records:, flags: and recognise: keys work the same way. See Fixed Fee Billing Files for how these files are matched and billed.