Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a807142
feat: verify browser bundle consumption
the-Drunken-coder Jun 25, 2026
67c9a93
feat: expand curated SIDC catalog
the-Drunken-coder Jun 25, 2026
54dbdd7
feat: add reverse lookup v0
the-Drunken-coder Jun 25, 2026
1143ff9
feat: improve symbol search ranking
the-Drunken-coder Jun 25, 2026
d569f65
feat: add curated fixture generator
the-Drunken-coder Jun 25, 2026
e5679df
feat: add sidc-kit CLI
the-Drunken-coder Jun 25, 2026
8bab13d
feat: add partial SIDC decomposition
the-Drunken-coder Jun 25, 2026
ec5bd5c
refactor: split curated catalog files
the-Drunken-coder Jun 25, 2026
e531c67
test: use browser bundler defaults in smoke check
the-Drunken-coder Jun 25, 2026
d09cad5
fix: harden fixture generator checks
the-Drunken-coder Jun 25, 2026
b219a74
fix: tighten partial SIDC coverage
the-Drunken-coder Jun 25, 2026
dd979ce
fix: harden CLI argument handling
the-Drunken-coder Jun 25, 2026
a307b1b
fix: harden reverse lookup matching
the-Drunken-coder Jun 25, 2026
501bf65
fix: avoid unnecessary reverse lookup scoring
the-Drunken-coder Jun 25, 2026
cf37a7c
docs: integrate feature README updates
the-Drunken-coder Jun 25, 2026
546c789
Merge remote-tracking branch 'origin/codex/browser-bundle-check' into…
the-Drunken-coder Jun 25, 2026
fe2f72c
Merge remote-tracking branch 'origin/codex/expand-curated-catalog' in…
the-Drunken-coder Jun 25, 2026
d8fb7da
Merge remote-tracking branch 'origin/codex/reverse-lookup-v0' into co…
the-Drunken-coder Jun 25, 2026
82d4ee3
Merge remote-tracking branch 'origin/codex/improve-search-ranking' in…
the-Drunken-coder Jun 25, 2026
0bbe038
Merge remote-tracking branch 'origin/codex/add-fixture-generator' int…
the-Drunken-coder Jun 25, 2026
e9ab20c
Merge remote-tracking branch 'origin/codex/add-cli' into codex/integr…
the-Drunken-coder Jun 25, 2026
fa6960f
Merge remote-tracking branch 'origin/codex/richer-sidc-decomposition'…
the-Drunken-coder Jun 25, 2026
408e1f9
fix: address integrated PR review findings
the-Drunken-coder Jun 26, 2026
2124ebf
fix: address integrated review findings
the-Drunken-coder Jun 26, 2026
9707800
refactor: split public api implementation modules
the-Drunken-coder Jun 26, 2026
e82de38
chore: address opus review follow-ups
the-Drunken-coder Jun 26, 2026
f436c28
chore: trim unused declaration maps
the-Drunken-coder Jun 26, 2026
01e2ebe
fix: guard identify candidate rendering
the-Drunken-coder Jun 26, 2026
9902157
fix: support base64 svg data urls
the-Drunken-coder Jun 26, 2026
c09bfca
fix: clarify ambiguous build suggestions
the-Drunken-coder Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 70 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# SIDC Kit

SIDC Kit is a small Node-focused TypeScript toolkit for working with military Symbol Identification Codes.
SIDC Kit is a small TypeScript toolkit for working with military Symbol Identification Codes in Node and browser-bundled apps.

It wraps the MIT-licensed [`milsymbol`](https://www.npmjs.com/package/milsymbol) renderer and adds a curated semantic layer for common workflows:

- search for symbols by plain-language terms
- explain known SIDCs into structured parts
- build known SIDCs from structured parts
- render SIDCs to SVG with `milsymbol`
- identify clean `milsymbol` SVG renderings against the curated set

V0 can render syntactically valid 30-digit SIDCs that `milsymbol` supports. Search, explain, and build intentionally support only a tiny curated set and do not claim exhaustive MIL-STD-2525 or STANAG APP-6 semantic coverage.
V0 can render syntactically valid 30-digit SIDCs that `milsymbol` supports. Search, build, and reverse lookup intentionally support only a curated set. Explain returns curated semantics when a SIDC is in that set and partial field decomposition for other renderable number SIDCs where `milsymbol` or the curated function-ID table provides a label. It does not claim exhaustive MIL-STD-2525 or STANAG APP-6 semantic coverage.

## Install

Install from npm after the first release is published:
Install from npm:

```sh
npm install sidc-kit
Expand All @@ -32,10 +33,35 @@ npm run build
npm test
```

## Fixture Generation

For repository development, generated land-unit catalog entries come from `fixtures/curated-land-units.json` and are written into `src/data/catalog/land-units.ts`.

```sh
npm run generate:fixtures
npm run check:fixtures
```

The generator derives number-based MIL-STD-2525D/APP-6D SIDCs from structured land-unit parts, keeps echelon/mobility in digits 9-10 and function ID in digits 11-20, and verifies each generated SIDC renders with the installed `milsymbol` package.

## Usage

### CLI

```sh
sidc-kit search "friendly infantry" --limit 3
sidc-kit search "friendly infantry" --json
sidc-kit explain 130310001412110000000000000000 --json
sidc-kit build --affiliation friend --domain land --entity infantry --echelon platoon
sidc-kit render 130310001412110000000000000000 --size 40 > symbol.svg
```

The CLI wraps the public API without a separate data model. Human defaults use plain text, while `--json` returns JSON for commands and typed JSON errors on stderr. Render size must be between 1 and 4096 pixels.

### TypeScript

```ts
import { buildSidc, explainSidc, renderSymbol, searchSymbols } from "sidc-kit";
import { buildSidc, explainSidc, identifySymbol, renderSymbol, searchSymbols } from "sidc-kit";

const results = searchSymbols("friendly infantry platoon");

Expand All @@ -49,17 +75,42 @@ const sidc = buildSidc({
});

const rendered = renderSymbol(sidc, { size: 40 });

const matches = identifySymbol(rendered.svg, { size: 40 });
```

## Browser Bundles

SIDC Kit publishes browser-safe ESM at the package root. Browser and map UI builds should import from `sidc-kit` through a modern bundler:

```ts
import { renderSymbol, searchSymbols } from "sidc-kit";

const match = searchSymbols("friendly infantry platoon")[0];
const marker = renderSymbol(match.sidc, { size: 32 });
```

The package runtime does not import Node built-ins. The test suite verifies browser consumption by bundling the package root with esbuild using `platform: "browser"` defaults and exercising `renderSymbol` and `searchSymbols` from the generated bundle. Since rendering delegates to `milsymbol`, browser bundles include the `milsymbol` renderer unless your app lazy-loads this package.

## API

### `searchSymbols(query, options?)`

Performs deterministic lexical matching over curated names, aliases, and part labels. Returns ranked results with `score`, `sidc`, `name`, `aliases`, `parts`, and `coverage`.
Performs deterministic lexical matching over curated names, aliases, and part labels. Exact names, exact aliases, exact parts, and field-specific token matches are weighted predictably, with catalog order used as the tie-breaker. Returns ranked results with `score`, `sidc`, `name`, `aliases`, `parts`, and `coverage`.

Curated search terms include practical abbreviations and regional spellings for supported records, such as `inf`/`infantry`, `arty`/`artillery`, `recon`/`reconnaissance`, and `tank`/`armor`/`armour`.

### `explainSidc(sidc)`

Explains a curated 30-digit SIDC into a stable JSON-serializable object. Unknown but syntactically valid SIDCs fail with `UNSUPPORTED_SIDC`.
Explains a 30-digit SIDC into a stable JSON-serializable object.

Curated SIDCs return `coverage: "curated"` with `name`, `aliases`, and the curated `parts` object. Non-curated SIDCs that `milsymbol` can validate return `coverage: "partial"` with:

- `parts`: only the interpreted fields
- `fields`: per-field `code`, optional `value`, and `coverage`
- `unknownFields`: field names that were present in the SIDC but not interpreted

Unsupported or malformed SIDCs still fail with typed `SidcKitError` codes such as `INVALID_SIDC` or `UNSUPPORTED_SIDC`.

### `buildSidc(parts)`

Expand All @@ -70,9 +121,21 @@ Partial combinations that match more than one curated SIDC fail with `AMBIGUOUS_

Renders a syntactically valid 30-digit SIDC with `milsymbol` and returns SVG plus anchor and size metadata when available. SIDCs that `milsymbol` cannot validate or render are reported as `RENDER_FAILED`.

### `identifySymbol(input, options?)`

Compares a clean inline SVG string, or a percent-encoded `data:image/svg+xml` URL, against normalized `milsymbol` renderings for the curated fixture set. Returns ranked candidates with `confidence` and `evidence`; exact normalized SVG matches report `confidence: 1`.

The default `minConfidence` is `0.99`, so unrelated or weakly similar SVGs return an empty list rather than a guessed SIDC. Pass a lower `minConfidence` when you want to inspect near matches or ambiguous alternatives.

Reverse lookup v0 is deterministic clean-rendered-input comparison. Oversized SVG input is rejected before fuzzy scoring. It does not recognize screenshots, photos, scanned images, raster PNG/JPEG files, cropped symbols, hand-edited icons, map marker composites, or arbitrary MIL-STD-2525/APP-6 symbols outside the curated set.

## Coverage

Rendering coverage follows the installed `milsymbol` package. The curated semantic set includes a few common land-unit examples such as friendly infantry platoon, hostile infantry platoon, armor platoon, artillery platoon, reconnaissance platoon, and infantry company.
Rendering coverage follows the installed `milsymbol` package. The curated semantic set includes common, verified examples across land units, air, sea surface, sea subsurface, land equipment, land installations, and control measures. Examples include infantry platoons and companies, fighter and rotary-wing aircraft, UAVs, destroyers, frigates, merchant ships, submarines, unmanned underwater vehicles, tanks, armored personnel carriers, bases, airports, checkpoints, and waypoints.

Reverse lookup coverage is the same curated set and currently accepts clean SVG renderings only.

Partial decomposition is intentionally limited to affiliation, symbol set, status, domain, echelon, and entity. Entity labels come from function IDs already present in the curated table; unknown function IDs are reported through `unknownFields` instead of guessed. Status is labeled only when it is present or when `milsymbol` exposes a semantic condition label; otherwise status is reported through `unknownFields`.

Image-based reverse lookup is intentionally deferred.

Expand Down
98 changes: 98 additions & 0 deletions fixtures/curated-land-units.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
[
{
"name": "Friendly Land Unit Infantry Platoon",
"aliases": [
"friendly infantry platoon",
"friendly inf platoon",
"friend infantry platoon",
"friend inf platoon",
"blue infantry platoon",
"land infantry platoon"
],
"parts": {
"affiliation": "friend",
"entity": "infantry",
"echelon": "platoon"
}
},
{
"name": "Friendly Land Unit Infantry Company",
"aliases": [
"friendly infantry company",
"friendly inf company",
"friend infantry company",
"friend inf company",
"blue infantry company",
"land infantry company"
],
"parts": {
"affiliation": "friend",
"entity": "infantry",
"echelon": "company"
}
},
{
"name": "Hostile Land Unit Infantry Platoon",
"aliases": [
"hostile infantry platoon",
"hostile inf platoon",
"enemy infantry platoon",
"enemy inf platoon",
"red infantry platoon",
"land infantry platoon hostile"
],
"parts": {
"affiliation": "hostile",
"entity": "infantry",
"echelon": "platoon"
}
},
{
"name": "Friendly Land Unit Armor Platoon",
"aliases": [
"friendly armor platoon",
"friendly armour platoon",
"friendly tank platoon",
"friendly armoured platoon",
"friend tank platoon",
"blue armor platoon",
"land armor platoon"
],
"parts": {
"affiliation": "friend",
"entity": "armor",
"entityType": "tank",
"echelon": "platoon"
}
},
{
"name": "Friendly Land Unit Artillery Platoon",
"aliases": [
"friendly artillery platoon",
"friendly arty platoon",
"friend field artillery platoon",
"friend arty platoon",
"blue artillery platoon",
"land artillery platoon"
],
"parts": {
"affiliation": "friend",
"entity": "artillery",
"echelon": "platoon"
}
},
{
"name": "Friendly Land Unit Reconnaissance Platoon",
"aliases": [
"friendly reconnaissance platoon",
"friend recon platoon",
"blue reconnaissance platoon",
"land recon platoon"
],
"parts": {
"affiliation": "friend",
"entity": "reconnaissance",
"echelon": "platoon"
}
}
]
Loading
Loading