# core/selectors/violation.selector.ts

> 35 lines of code and 9 definitions.

Tree: GovLab Pipeline
Language: typescript
Layer: application
Canonical: https://banes-lab.com/anatomy/pipeline#file-pipeline-core-selectors-violation-selector-ts
Source text: https://banes-lab.com/source/pipeline/core/selectors/violation.selector.ts.txt

Listed in [core/selectors](https://banes-lab.com/api/source/pipeline/core/selectors.md), after [core/selectors/stage.selector.ts](https://banes-lab.com/source/pipeline/core/selectors/stage.selector.ts.md).

## Definitions

- `countBefore` (lexical_declaration, line 3)
- `lastCount` (lexical_declaration, line 29, exported)
- `lastCountOf` (lexical_declaration, line 19)
- `cursor` (lexical_declaration, line 4)
- `end` (lexical_declaration, line 8)
- `value` (lexical_declaration, line 15)
- `from` (lexical_declaration, line 21)
- `lower` (lexical_declaration, line 30, exported)
- `result` (lexical_declaration, line 32, exported)

## Contained in

- [core/selectors](https://banes-lab.com/anatomy/pipeline/folder-pipeline-core-selectors.md)

## Used by

- [core/converters/report.converter.ts](https://banes-lab.com/source/pipeline/core/converters/report.converter.ts.md)

## Linked from

- [core/converters](https://banes-lab.com/anatomy/pipeline/folder-pipeline-core-converters.md)
- [core/selectors](https://banes-lab.com/anatomy/pipeline/folder-pipeline-core-selectors.md)

## Source

```typescript
import { COUNT_KEYWORDS, DIGITS } from "#configuration/constants/violation.constants";

const countBefore = function countBefore(lower: string, at: number): number | null {
    let cursor = at - 1;
    while (cursor >= 0 && lower.charAt(cursor) === " ") {
        cursor -= 1;
    }
    const end = cursor;
    while (cursor >= 0 && DIGITS.has(lower.charAt(cursor))) {
        cursor -= 1;
    }
    if (end <= cursor) {
        return null;
    }
    const value = Number(lower.slice(cursor + 1, end + 1));
    return Number.isNaN(value) ? null : value;
};

const lastCountOf = function lastCountOf(lower: string, keyword: string): number | null {
    let result: number | null = null;
    let from = lower.indexOf(keyword);
    while (from !== -1) {
        result = countBefore(lower, from) ?? result;
        from = lower.indexOf(keyword, from + keyword.length);
    }
    return result;
};

export const lastCount = function lastCount(text: string): number | null {
    const lower = text.toLowerCase();
    for (const keyword of COUNT_KEYWORDS) {
        const result = lastCountOf(lower, keyword);
        if (result !== null) {
            return result;
        }
    }
    return null;
};
```
