@cosyte/synth 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +335 -0
- package/README.md +6 -2
- package/dist/astm/index.cjs +83 -42
- package/dist/astm/index.cjs.map +1 -1
- package/dist/astm/index.d.cts +1 -1
- package/dist/astm/index.d.ts +1 -1
- package/dist/astm/index.mjs +83 -42
- package/dist/astm/index.mjs.map +1 -1
- package/dist/ccda/index.cjs +89 -44
- package/dist/ccda/index.cjs.map +1 -1
- package/dist/ccda/index.d.cts +5 -3
- package/dist/ccda/index.d.ts +5 -3
- package/dist/ccda/index.mjs +89 -44
- package/dist/ccda/index.mjs.map +1 -1
- package/dist/deid/index.cjs +92 -13
- package/dist/deid/index.cjs.map +1 -1
- package/dist/deid/index.d.cts +1 -1
- package/dist/deid/index.d.ts +1 -1
- package/dist/deid/index.mjs +93 -14
- package/dist/deid/index.mjs.map +1 -1
- package/dist/fhir/index.cjs +78 -5
- package/dist/fhir/index.cjs.map +1 -1
- package/dist/fhir/index.mjs +78 -5
- package/dist/fhir/index.mjs.map +1 -1
- package/dist/hl7/index.cjs +89 -41
- package/dist/hl7/index.cjs.map +1 -1
- package/dist/hl7/index.d.cts +1 -1
- package/dist/hl7/index.d.ts +1 -1
- package/dist/hl7/index.mjs +89 -41
- package/dist/hl7/index.mjs.map +1 -1
- package/dist/index.cjs +91 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +126 -8
- package/dist/index.d.ts +126 -8
- package/dist/index.mjs +89 -41
- package/dist/index.mjs.map +1 -1
- package/dist/ncpdp/index.cjs +56 -4
- package/dist/ncpdp/index.cjs.map +1 -1
- package/dist/ncpdp/index.mjs +56 -4
- package/dist/ncpdp/index.mjs.map +1 -1
- package/dist/{quirk-JLyO1Ncj.d.ts → quirk-C9t9CkPS.d.ts} +17 -6
- package/dist/{quirk-DmkgoZdh.d.cts → quirk-DYMDojVw.d.cts} +17 -6
- package/dist/x12/index.cjs +82 -13
- package/dist/x12/index.cjs.map +1 -1
- package/dist/x12/index.d.cts +2 -1
- package/dist/x12/index.d.ts +2 -1
- package/dist/x12/index.mjs +83 -14
- package/dist/x12/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/ccda/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { S as SyntheticName, a as SyntheticIdentifier, R as Rng, C as Corpus } from '../providers-OLz3zAc-.cjs';
|
|
2
2
|
import { CcdaDocument, BuildCcdaPatient, BuildCode, BuildQuantity } from '@cosyte/ccda';
|
|
3
3
|
import { C as CodeConcept, Q as QuantConcept } from '../example-codes-DeXcnCSK.cjs';
|
|
4
|
-
import { Q as QuirkDescriptor, S as SynthProfile, a as QuirkArtifact, b as QuirkRoundTripResult } from '../quirk-
|
|
4
|
+
import { Q as QuirkDescriptor, S as SynthProfile, a as QuirkArtifact, b as QuirkRoundTripResult } from '../quirk-DYMDojVw.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Synthetic **C-CDA document generation** — a spec-clean Continuity of Care Document
|
|
@@ -168,7 +168,8 @@ declare function ccdaPatientIdentity(rng: Rng): CcdaPatientIdentity;
|
|
|
168
168
|
*
|
|
169
169
|
* @param concept - The FHIR-shaped `{ system, code, display }` concept.
|
|
170
170
|
* @returns The `@cosyte/ccda` `BuildCode`.
|
|
171
|
-
* @throws
|
|
171
|
+
* @throws SynthError `SYNTH_UNMAPPED_CODE_SYSTEM` when the concept's `system` URI has no known OID
|
|
172
|
+
* mapping. The refusal does not quote the URI: `concept` is caller-supplied.
|
|
172
173
|
* @example
|
|
173
174
|
* ```ts
|
|
174
175
|
* import { toBuildCode } from "@cosyte/synth/ccda";
|
|
@@ -258,7 +259,8 @@ declare const CCDA_QUIRKS: Readonly<Record<CcdaQuirkName, QuirkDescriptor>>;
|
|
|
258
259
|
* @param quirk - The quirk to inject.
|
|
259
260
|
* @param cleanXml - The spec-clean C-CDA XML.
|
|
260
261
|
* @returns The quirked XML.
|
|
261
|
-
* @throws
|
|
262
|
+
* @throws SynthError `SYNTH_UNSUPPORTED_QUIRK` when `quirk` is not a supported C-CDA quirk.
|
|
263
|
+
* @throws SynthError `SYNTH_QUIRK_ANCHOR_ABSENT` when the quirk found no structural anchor to mutate.
|
|
262
264
|
* @example
|
|
263
265
|
* ```ts
|
|
264
266
|
* import { injectCcdaQuirk } from "@cosyte/synth/ccda";
|
package/dist/ccda/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { S as SyntheticName, a as SyntheticIdentifier, R as Rng, C as Corpus } from '../providers-OLz3zAc-.js';
|
|
2
2
|
import { CcdaDocument, BuildCcdaPatient, BuildCode, BuildQuantity } from '@cosyte/ccda';
|
|
3
3
|
import { C as CodeConcept, Q as QuantConcept } from '../example-codes-DeXcnCSK.js';
|
|
4
|
-
import { Q as QuirkDescriptor, S as SynthProfile, a as QuirkArtifact, b as QuirkRoundTripResult } from '../quirk-
|
|
4
|
+
import { Q as QuirkDescriptor, S as SynthProfile, a as QuirkArtifact, b as QuirkRoundTripResult } from '../quirk-C9t9CkPS.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Synthetic **C-CDA document generation** — a spec-clean Continuity of Care Document
|
|
@@ -168,7 +168,8 @@ declare function ccdaPatientIdentity(rng: Rng): CcdaPatientIdentity;
|
|
|
168
168
|
*
|
|
169
169
|
* @param concept - The FHIR-shaped `{ system, code, display }` concept.
|
|
170
170
|
* @returns The `@cosyte/ccda` `BuildCode`.
|
|
171
|
-
* @throws
|
|
171
|
+
* @throws SynthError `SYNTH_UNMAPPED_CODE_SYSTEM` when the concept's `system` URI has no known OID
|
|
172
|
+
* mapping. The refusal does not quote the URI: `concept` is caller-supplied.
|
|
172
173
|
* @example
|
|
173
174
|
* ```ts
|
|
174
175
|
* import { toBuildCode } from "@cosyte/synth/ccda";
|
|
@@ -258,7 +259,8 @@ declare const CCDA_QUIRKS: Readonly<Record<CcdaQuirkName, QuirkDescriptor>>;
|
|
|
258
259
|
* @param quirk - The quirk to inject.
|
|
259
260
|
* @param cleanXml - The spec-clean C-CDA XML.
|
|
260
261
|
* @returns The quirked XML.
|
|
261
|
-
* @throws
|
|
262
|
+
* @throws SynthError `SYNTH_UNSUPPORTED_QUIRK` when `quirk` is not a supported C-CDA quirk.
|
|
263
|
+
* @throws SynthError `SYNTH_QUIRK_ANCHOR_ABSENT` when the quirk found no structural anchor to mutate.
|
|
262
264
|
* @example
|
|
263
265
|
* ```ts
|
|
264
266
|
* import { injectCcdaQuirk } from "@cosyte/synth/ccda";
|
package/dist/ccda/index.mjs
CHANGED
|
@@ -29,6 +29,65 @@ function sfc32Next(s) {
|
|
|
29
29
|
return t >>> 0;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// src/codes.ts
|
|
33
|
+
var SYNTH_FATAL_CODES = {
|
|
34
|
+
/**
|
|
35
|
+
* A vendor quirk was requested that the target format's profile system does not support. Fatal —
|
|
36
|
+
* never a silent no-op and never a fabricated quirk.
|
|
37
|
+
*/
|
|
38
|
+
SYNTH_UNSUPPORTED_QUIRK: "SYNTH_UNSUPPORTED_QUIRK",
|
|
39
|
+
/**
|
|
40
|
+
* A quirk transform found no structural anchor to mutate, so the fixture would not carry the
|
|
41
|
+
* deviation it is labelled with. Fatal — a golden file that lies about its parser verdict is worse
|
|
42
|
+
* than no golden file.
|
|
43
|
+
*/
|
|
44
|
+
SYNTH_QUIRK_ANCHOR_ABSENT: "SYNTH_QUIRK_ANCHOR_ABSENT",
|
|
45
|
+
/**
|
|
46
|
+
* A bare parse of a freshly-generated quirk artifact did not produce exactly the declared intended
|
|
47
|
+
* warning code(s). Fatal — never emit a mislabeled fixture.
|
|
48
|
+
*/
|
|
49
|
+
SYNTH_INTENDED_WARNING_MISMATCH: "SYNTH_INTENDED_WARNING_MISMATCH",
|
|
50
|
+
/** A concept's code-system URI has no OID mapping in the C-CDA example-code table. Fatal. */
|
|
51
|
+
SYNTH_UNMAPPED_CODE_SYSTEM: "SYNTH_UNMAPPED_CODE_SYSTEM",
|
|
52
|
+
/** An integer range was requested with its maximum below its minimum. Fatal. */
|
|
53
|
+
SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
|
|
54
|
+
/** A value was drawn from an empty pool. Fatal — never a fabricated substitute. */
|
|
55
|
+
SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
|
|
56
|
+
/** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
|
|
57
|
+
SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
|
|
58
|
+
/**
|
|
59
|
+
* A caller-supplied selector (a message kind, a document type, a corpus mix entry, a claim
|
|
60
|
+
* variant, a Bundle type, a resource profile) is not in the closed set that governs it. Fatal —
|
|
61
|
+
* see `resolveKind`: a selector union is erased at run time, and a selector that falls through
|
|
62
|
+
* either mislabels the fixture or hands the value to a peer builder that quotes it back.
|
|
63
|
+
*/
|
|
64
|
+
SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND"
|
|
65
|
+
};
|
|
66
|
+
var SYNTH_FATAL_MESSAGES = Object.freeze({
|
|
67
|
+
SYNTH_UNSUPPORTED_FORMAT: "The requested format is not generable by this build. A generator has no byte fallback: it builds through a parser's own serializer or it refuses.",
|
|
68
|
+
SYNTH_UNSUPPORTED_QUIRK: "The requested vendor quirk is not in the target format's quirk registry. Compare the request against that format's exported registry (HL7_QUIRKS, CCDA_QUIRKS, ASTM_QUIRKS).",
|
|
69
|
+
SYNTH_QUIRK_ANCHOR_ABSENT: "The quirk transform found no structural anchor to mutate, so the fixture would not carry the deviation it is labelled with. Refusing to emit a mislabeled fixture.",
|
|
70
|
+
SYNTH_INTENDED_WARNING_MISMATCH: "A bare parse of the generated quirk artifact did not produce exactly the declared intended warning code(s). Refusing to emit a mislabeled fixture.",
|
|
71
|
+
SYNTH_UNMAPPED_CODE_SYSTEM: "The concept's code-system URI has no OID mapping in the C-CDA example-code table.",
|
|
72
|
+
SYNTH_INVALID_DECIMAL: "The value could not be read as an X12 decimal.",
|
|
73
|
+
SYNTH_INVALID_RANGE: "An integer range was requested with its maximum below its minimum.",
|
|
74
|
+
SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
|
|
75
|
+
SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
|
|
76
|
+
SYNTH_UNSUPPORTED_KIND: "The requested kind, document type, corpus mix entry, variant or profile is not one this generator supports. The supported set is the exported union for that option."
|
|
77
|
+
});
|
|
78
|
+
var SynthError = class extends Error {
|
|
79
|
+
/** The stable fatal code. */
|
|
80
|
+
code;
|
|
81
|
+
/**
|
|
82
|
+
* @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
|
|
83
|
+
*/
|
|
84
|
+
constructor(code) {
|
|
85
|
+
super(SYNTH_FATAL_MESSAGES[code]);
|
|
86
|
+
this.name = "SynthError";
|
|
87
|
+
this.code = code;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
32
91
|
// src/rng/rng.ts
|
|
33
92
|
var Sfc32Rng = class {
|
|
34
93
|
seed;
|
|
@@ -46,7 +105,7 @@ var Sfc32Rng = class {
|
|
|
46
105
|
return this.nextUint32() / 4294967296;
|
|
47
106
|
}
|
|
48
107
|
int(min, max) {
|
|
49
|
-
if (max < min) throw new
|
|
108
|
+
if (max < min) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_RANGE);
|
|
50
109
|
const span = max - min + 1;
|
|
51
110
|
return min + Math.floor(this.float() * span);
|
|
52
111
|
}
|
|
@@ -54,7 +113,7 @@ var Sfc32Rng = class {
|
|
|
54
113
|
return this.float() < p;
|
|
55
114
|
}
|
|
56
115
|
pick(items) {
|
|
57
|
-
if (items.length === 0) throw new
|
|
116
|
+
if (items.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
|
|
58
117
|
return items[this.int(0, items.length - 1)];
|
|
59
118
|
}
|
|
60
119
|
digits(n) {
|
|
@@ -671,7 +730,7 @@ var URI_TO_OID = Object.freeze({
|
|
|
671
730
|
function toBuildCode(concept) {
|
|
672
731
|
const codeSystem = URI_TO_OID[concept.system];
|
|
673
732
|
if (codeSystem === void 0) {
|
|
674
|
-
throw new
|
|
733
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNMAPPED_CODE_SYSTEM);
|
|
675
734
|
}
|
|
676
735
|
return { code: concept.code, codeSystem, displayName: concept.display };
|
|
677
736
|
}
|
|
@@ -728,7 +787,19 @@ function ccdaPatientIdentity(rng) {
|
|
|
728
787
|
return { patient, person, mrn };
|
|
729
788
|
}
|
|
730
789
|
|
|
790
|
+
// src/select.ts
|
|
791
|
+
function resolveKind(allowed, requested) {
|
|
792
|
+
const match = allowed.find((value) => value === requested);
|
|
793
|
+
if (match === void 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_KIND);
|
|
794
|
+
return match;
|
|
795
|
+
}
|
|
796
|
+
function resolveMix(allowed, requested, fallback) {
|
|
797
|
+
if (requested === void 0) return fallback;
|
|
798
|
+
return requested.map((entry) => resolveKind(allowed, entry));
|
|
799
|
+
}
|
|
800
|
+
|
|
731
801
|
// src/ccda/ccd.ts
|
|
802
|
+
var CCDA_DOCUMENT_TYPES = Object.freeze(["ccd", "referralNote"]);
|
|
732
803
|
function pickN(rng, pool, n) {
|
|
733
804
|
const take = Math.min(n, pool.length);
|
|
734
805
|
const indices = pool.map((_v, i) => i);
|
|
@@ -811,7 +882,8 @@ function buildInit(rng, documentType) {
|
|
|
811
882
|
return base;
|
|
812
883
|
}
|
|
813
884
|
function generateCcda(options = {}) {
|
|
814
|
-
const { seed = 0
|
|
885
|
+
const { seed = 0 } = options;
|
|
886
|
+
const documentType = resolveKind(CCDA_DOCUMENT_TYPES, options.documentType ?? "ccd");
|
|
815
887
|
const rng = createRng(seed);
|
|
816
888
|
return buildCcda(buildInit(rng, documentType));
|
|
817
889
|
}
|
|
@@ -837,7 +909,7 @@ function roundTrip(doc) {
|
|
|
837
909
|
// src/profile.ts
|
|
838
910
|
function defineSynthProfile(spec) {
|
|
839
911
|
if (typeof spec.name !== "string" || spec.name.trim().length === 0) {
|
|
840
|
-
throw new
|
|
912
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_PROFILE);
|
|
841
913
|
}
|
|
842
914
|
return Object.freeze({
|
|
843
915
|
name: spec.name,
|
|
@@ -847,28 +919,6 @@ function defineSynthProfile(spec) {
|
|
|
847
919
|
});
|
|
848
920
|
}
|
|
849
921
|
|
|
850
|
-
// src/codes.ts
|
|
851
|
-
var SYNTH_FATAL_CODES = {
|
|
852
|
-
/**
|
|
853
|
-
* A vendor quirk was requested that the target format's profile system does not support. Fatal —
|
|
854
|
-
* never a silent no-op and never a fabricated quirk.
|
|
855
|
-
*/
|
|
856
|
-
SYNTH_UNSUPPORTED_QUIRK: "SYNTH_UNSUPPORTED_QUIRK"
|
|
857
|
-
};
|
|
858
|
-
var SynthError = class extends Error {
|
|
859
|
-
/** The stable fatal code. */
|
|
860
|
-
code;
|
|
861
|
-
/**
|
|
862
|
-
* @param code - The stable {@link SynthFatalCode}.
|
|
863
|
-
* @param message - A human-readable detail (never contains PHI — there is none).
|
|
864
|
-
*/
|
|
865
|
-
constructor(code, message) {
|
|
866
|
-
super(message);
|
|
867
|
-
this.name = "SynthError";
|
|
868
|
-
this.code = code;
|
|
869
|
-
}
|
|
870
|
-
};
|
|
871
|
-
|
|
872
922
|
// src/quirk.ts
|
|
873
923
|
var PROFILE_QUIRK_APPLIED = "PROFILE_QUIRK_APPLIED";
|
|
874
924
|
function sameCodeSet(a, b) {
|
|
@@ -885,12 +935,8 @@ function sameCodeSet(a, b) {
|
|
|
885
935
|
}
|
|
886
936
|
function resolveQuirk(registry, format, name2) {
|
|
887
937
|
const descriptor = registry[name2];
|
|
888
|
-
if (descriptor === void 0) {
|
|
889
|
-
|
|
890
|
-
throw new SynthError(
|
|
891
|
-
SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_QUIRK,
|
|
892
|
-
`${format}: unsupported quirk "${name2}". The ${format} profile system supports: ${supported}.`
|
|
893
|
-
);
|
|
938
|
+
if (descriptor === void 0 || descriptor.format !== format) {
|
|
939
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_QUIRK);
|
|
894
940
|
}
|
|
895
941
|
return descriptor;
|
|
896
942
|
}
|
|
@@ -905,11 +951,9 @@ function profileTolerated(disposition, intendedWarnings, warningsUnderProfile) {
|
|
|
905
951
|
return false;
|
|
906
952
|
}
|
|
907
953
|
}
|
|
908
|
-
function assertIntendedWarnings(
|
|
954
|
+
function assertIntendedWarnings(intendedWarnings, bareWarnings) {
|
|
909
955
|
if (!sameCodeSet(bareWarnings, intendedWarnings)) {
|
|
910
|
-
throw new
|
|
911
|
-
`quirk "${quirk}": the intended-warning contract does not hold \u2014 expected exactly [${intendedWarnings.join(", ")}] but a bare parse produced [${bareWarnings.join(", ")}]. Refusing to emit a mislabeled fixture.`
|
|
912
|
-
);
|
|
956
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INTENDED_WARNING_MISMATCH);
|
|
913
957
|
}
|
|
914
958
|
}
|
|
915
959
|
function validateProfileQuirks(profile, registry, format) {
|
|
@@ -974,11 +1018,10 @@ function applyQuirk(quirk, xml) {
|
|
|
974
1018
|
}
|
|
975
1019
|
}
|
|
976
1020
|
function injectCcdaQuirk(quirk, cleanXml) {
|
|
977
|
-
const
|
|
1021
|
+
const descriptor = resolveQuirk(CCDA_QUIRKS, "ccda", quirk);
|
|
1022
|
+
const content = applyQuirk(descriptor.name, cleanXml);
|
|
978
1023
|
if (content === cleanXml) {
|
|
979
|
-
throw new
|
|
980
|
-
`injectCcdaQuirk: quirk "${quirk}" found no structural anchor to mutate \u2014 refusing to emit a fixture that does not carry the intended deviation.`
|
|
981
|
-
);
|
|
1024
|
+
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_QUIRK_ANCHOR_ABSENT);
|
|
982
1025
|
}
|
|
983
1026
|
return content;
|
|
984
1027
|
}
|
|
@@ -989,7 +1032,6 @@ function generateCcdaQuirk(options) {
|
|
|
989
1032
|
const clean = serializeCcda(generateCcda({ seed, documentType }));
|
|
990
1033
|
const content = injectCcdaQuirk(options.quirk, clean);
|
|
991
1034
|
assertIntendedWarnings(
|
|
992
|
-
descriptor.name,
|
|
993
1035
|
descriptor.intendedWarnings,
|
|
994
1036
|
parseCcda(content).warnings.map((w) => String(w.code))
|
|
995
1037
|
);
|
|
@@ -1032,6 +1074,7 @@ var ALL_CCDA_QUIRKS = Object.freeze(
|
|
|
1032
1074
|
function ccdaQuirkCorpus(options) {
|
|
1033
1075
|
const quirks = options.profile ? validateProfileQuirks(options.profile, CCDA_QUIRKS, "ccda") : options.quirks ?? ALL_CCDA_QUIRKS;
|
|
1034
1076
|
const names = quirks.length > 0 ? quirks : ALL_CCDA_QUIRKS;
|
|
1077
|
+
for (const name2 of names) resolveQuirk(CCDA_QUIRKS, "ccda", name2);
|
|
1035
1078
|
const documentType = options.documentType ?? "ccd";
|
|
1036
1079
|
const count = options.count ?? names.length;
|
|
1037
1080
|
const seedStream = createRng(options.seed);
|
|
@@ -1054,9 +1097,11 @@ var ccdaQuirkProfile = defineSynthProfile({
|
|
|
1054
1097
|
});
|
|
1055
1098
|
|
|
1056
1099
|
// src/ccda/index.ts
|
|
1057
|
-
var
|
|
1100
|
+
var ALL_KINDS = Object.freeze(["ccd", "referralNote"]);
|
|
1101
|
+
var DEFAULT_MIX = ALL_KINDS;
|
|
1058
1102
|
function ccdaCorpus(options) {
|
|
1059
|
-
const { seed, count = 1
|
|
1103
|
+
const { seed, count = 1 } = options;
|
|
1104
|
+
const mix = resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
|
|
1060
1105
|
const seedStream = createRng(seed);
|
|
1061
1106
|
const artifacts = Array.from({ length: count }, (_unused, i) => {
|
|
1062
1107
|
const documentType = mix[i % mix.length] ?? "ccd";
|