@codemation/core-nodes-ocr 0.2.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/LICENSE +37 -0
  3. package/README.md +67 -0
  4. package/codemation.plugin.ts +27 -0
  5. package/dist/analyzeInvoiceNode-BIw8j_Zb.cjs +360 -0
  6. package/dist/analyzeInvoiceNode-BIw8j_Zb.cjs.map +1 -0
  7. package/dist/analyzeInvoiceNode-uVwe3GHD.js +321 -0
  8. package/dist/analyzeInvoiceNode-uVwe3GHD.js.map +1 -0
  9. package/dist/chunk-BaqVhFee.cjs +46 -0
  10. package/dist/codemation.plugin.cjs +17891 -0
  11. package/dist/codemation.plugin.cjs.map +1 -0
  12. package/dist/codemation.plugin.d.cts +325 -0
  13. package/dist/codemation.plugin.d.ts +266 -0
  14. package/dist/codemation.plugin.js +17883 -0
  15. package/dist/codemation.plugin.js.map +1 -0
  16. package/dist/index-C2KJPzqN.d.ts +876 -0
  17. package/dist/index-DoHR1J8T.d.ts +880 -0
  18. package/dist/index-OvXJkNm1.d.ts +874 -0
  19. package/dist/index.cjs +8 -0
  20. package/dist/index.d.cts +199 -0
  21. package/dist/index.d.ts +147 -0
  22. package/dist/index.js +3 -0
  23. package/dist/metadata.json +72 -0
  24. package/dist/runtimeTypes-C6YqmQG-.d.cts +762 -0
  25. package/dist/runtimeTypes-ffl603pJ.d.cts +764 -0
  26. package/dist/token-CIu4PqRI.js +58 -0
  27. package/dist/token-CIu4PqRI.js.map +1 -0
  28. package/dist/token-CgF09kyP.cjs +62 -0
  29. package/dist/token-CgF09kyP.cjs.map +1 -0
  30. package/dist/token-util-B2kSJtEV.cjs +458 -0
  31. package/dist/token-util-B2kSJtEV.cjs.map +1 -0
  32. package/dist/token-util-BsR6OYHz.js +5 -0
  33. package/dist/token-util-EUxa8JtH.js +470 -0
  34. package/dist/token-util-EUxa8JtH.js.map +1 -0
  35. package/dist/token-util-Lr5foG4r.cjs +8 -0
  36. package/package.json +70 -0
  37. package/src/credentials/azureContentUnderstandingCredential.ts +76 -0
  38. package/src/index.ts +5 -0
  39. package/src/lib/analyzeWithAzure.ts +130 -0
  40. package/src/lib/readBinaryBody.ts +51 -0
  41. package/src/nodes/analyzeDocumentNode.ts +70 -0
  42. package/src/nodes/analyzeImageNode.ts +70 -0
  43. package/src/nodes/analyzeInvoiceNode.ts +61 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,8 @@
1
+ const require_analyzeInvoiceNode = require('./analyzeInvoiceNode-BIw8j_Zb.cjs');
2
+
3
+ exports.analyzeDocumentNode = require_analyzeInvoiceNode.analyzeDocumentNode;
4
+ exports.analyzeImageNode = require_analyzeInvoiceNode.analyzeImageNode;
5
+ exports.analyzeInvoiceNode = require_analyzeInvoiceNode.analyzeInvoiceNode;
6
+ exports.analyzeWithAzure = require_analyzeInvoiceNode.analyzeWithAzure;
7
+ exports.azureContentUnderstandingCredentialType = require_analyzeInvoiceNode.azureContentUnderstandingCredentialType;
8
+ exports.mapAnalysisResult = require_analyzeInvoiceNode.mapAnalysisResult;
@@ -0,0 +1,199 @@
1
+ import { _ as CredentialSessionFactory, a as NodeActivationId, b as WorkflowId, c as RunnableNodeConfig, d as CredentialAdvancedSectionPresentation, f as CredentialAuthDefinition, g as CredentialMaterialSourceKind, h as CredentialJsonRecord, i as Items, m as CredentialHealthTester, n as TypeToken, o as RunDataSnapshot, p as CredentialFieldSchema, r as Item, s as RunId, u as AnyCredentialType, v as CredentialTypeId, y as NodeId } from "./runtimeTypes-ffl603pJ.cjs";
2
+ import { ZodType } from "zod";
3
+ import { AnalysisResult } from "@azure/ai-content-understanding";
4
+
5
+ //#region ../core/src/contracts/itemExpr.d.ts
6
+ declare const ITEM_EXPR_BRAND: unique symbol;
7
+ type ItemExprResolvedContext = Readonly<{
8
+ runId: RunId;
9
+ workflowId: WorkflowId;
10
+ nodeId: NodeId;
11
+ activationId: NodeActivationId;
12
+ data: RunDataSnapshot;
13
+ }>;
14
+ /**
15
+ * Context aligned with former {@link ItemInputMapperContext} — use **`data`** to read any completed upstream node.
16
+ */
17
+ type ItemExprContext = ItemExprResolvedContext;
18
+ type ItemExprArgs<TItemJson = unknown> = Readonly<{
19
+ item: Item<TItemJson>;
20
+ itemIndex: number;
21
+ items: Items<TItemJson>;
22
+ ctx: ItemExprContext;
23
+ }>;
24
+ type ItemExprCallback<T, TItemJson = unknown> = (args: ItemExprArgs<TItemJson>) => T | Promise<T>;
25
+ type ItemExpr<T, TItemJson = unknown> = Readonly<{
26
+ readonly [ITEM_EXPR_BRAND]: true;
27
+ readonly fn: ItemExprCallback<T, TItemJson>;
28
+ }>;
29
+ //#endregion
30
+ //#region ../core/src/contracts/params.d.ts
31
+ type Expr<T, TItemJson = unknown> = ItemExpr<T, TItemJson>;
32
+ type ParamDeep<T, TItemJson = unknown> = Expr<T, TItemJson> | (T extends readonly (infer U)[] ? ReadonlyArray<ParamDeep<U, TItemJson>> : never) | (T extends object ? { [K in keyof T]: ParamDeep<T[K], TItemJson> } : T);
33
+ //#endregion
34
+ //#region ../core/src/authoring/defineNode.types.d.ts
35
+ type ResolvableCredentialType = AnyCredentialType | CredentialTypeId;
36
+ type DefinedNodeCredentialBinding = ResolvableCredentialType | Readonly<{
37
+ readonly type: ResolvableCredentialType | ReadonlyArray<ResolvableCredentialType>;
38
+ readonly label?: string;
39
+ readonly optional?: true;
40
+ readonly helpText?: string;
41
+ readonly helpUrl?: string;
42
+ }>;
43
+ type DefinedNodeCredentialBindings = Readonly<Record<string, DefinedNodeCredentialBinding>>;
44
+ type DefinedNodeConfigInput<TConfigResolved extends CredentialJsonRecord, TItemJson> = ParamDeep<TConfigResolved, TItemJson>;
45
+ interface DefinedNode<TKey$1 extends string, TConfig extends CredentialJsonRecord, TInputJson, TOutputJson, _TBindings extends DefinedNodeCredentialBindings | undefined = undefined> {
46
+ readonly kind: "defined-node";
47
+ readonly key: TKey$1;
48
+ readonly title: string;
49
+ readonly description?: string;
50
+ create<TConfigItemJson = TInputJson>(config: DefinedNodeConfigInput<TConfig, TConfigItemJson>, name?: string, id?: string): RunnableNodeConfig<TInputJson, TOutputJson>;
51
+ register(context: {
52
+ registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>): void;
53
+ }): void;
54
+ }
55
+ //#endregion
56
+ //#region src/credentials/azureContentUnderstandingCredential.d.ts
57
+ type AzureContentUnderstandingSession = Readonly<{
58
+ endpoint: string;
59
+ apiKey: string;
60
+ }>;
61
+ declare const azureContentUnderstandingCredentialType: Readonly<{
62
+ definition: Readonly<{
63
+ typeId: CredentialTypeId;
64
+ displayName: string;
65
+ description?: string;
66
+ publicFields?: ReadonlyArray<CredentialFieldSchema>;
67
+ secretFields?: ReadonlyArray<CredentialFieldSchema>;
68
+ advancedSection?: CredentialAdvancedSectionPresentation;
69
+ supportedSourceKinds?: ReadonlyArray<CredentialMaterialSourceKind>;
70
+ auth?: CredentialAuthDefinition;
71
+ }>;
72
+ createSession: CredentialSessionFactory<{
73
+ endpoint: unknown;
74
+ }, {
75
+ apiKey: unknown;
76
+ }, Readonly<{
77
+ endpoint: string;
78
+ apiKey: string;
79
+ }>>;
80
+ test: CredentialHealthTester<{
81
+ endpoint: unknown;
82
+ }, {
83
+ apiKey: unknown;
84
+ }>;
85
+ }> & {
86
+ readonly key: string;
87
+ };
88
+ //#endregion
89
+ //#region src/lib/analyzeWithAzure.d.ts
90
+ /** Structured analyzer fields: scalars at leaves; nested objects and arrays preserved. */
91
+ type OcrStructuredFields = Readonly<Record<string, unknown>>;
92
+ /** The output shape returned by all OCR analyzer nodes. */
93
+ type OcrAnalysisOutput = Readonly<{
94
+ /** Markdown representation of the document content. */
95
+ content: string;
96
+ /** Structured fields extracted by the prebuilt analyzer. */
97
+ fields: OcrStructuredFields;
98
+ }>;
99
+ /**
100
+ * Analyzes a binary document using an Azure Content Understanding prebuilt analyzer.
101
+ * Retries on transient failures are handled by the engine via the node's `retryPolicy`.
102
+ */
103
+ declare function analyzeWithAzure(args: Readonly<{
104
+ session: AzureContentUnderstandingSession;
105
+ analyzerId: string;
106
+ body: Uint8Array;
107
+ contentType: string;
108
+ }>): Promise<OcrAnalysisOutput>;
109
+ /** @internal Exported for testing — maps a raw AnalysisResult to the node output shape. */
110
+ declare function mapAnalysisResult(result: AnalysisResult): OcrAnalysisOutput;
111
+ //#endregion
112
+ //#region src/nodes/analyzeDocumentNode.d.ts
113
+ type AnalyzeDocumentConfig = Readonly<{
114
+ /** Key on `item.binary` that holds the document bytes. Default: `"data"`. */
115
+ binaryField?: string;
116
+ /** MIME type override sent to the analyzer. Falls back to attachment `mimeType` when not set. */
117
+ contentType?: string;
118
+ /**
119
+ * Azure Content Understanding analyzer ID to use.
120
+ * Defaults to `"prebuilt-document"`. Set this to a custom analyzer ID when you have
121
+ * a trained model or need a different prebuilt variant.
122
+ */
123
+ analyzerId?: string;
124
+ /** Max bytes the attachment may have before reading. Defaults to 50 MiB. */
125
+ maxBytes?: number;
126
+ }>;
127
+ declare const analyzeDocumentNode: DefinedNode<"azure-ocr.analyze-document", {
128
+ binaryField: unknown;
129
+ contentType: unknown;
130
+ analyzerId: unknown;
131
+ maxBytes: unknown;
132
+ }, unknown, Readonly<{
133
+ content: string;
134
+ fields: OcrStructuredFields;
135
+ }>, {
136
+ contentUnderstanding: {
137
+ type: AnyCredentialType;
138
+ label: string;
139
+ helpText: string;
140
+ };
141
+ }>;
142
+ //#endregion
143
+ //#region src/nodes/analyzeImageNode.d.ts
144
+ type AnalyzeImageConfig = Readonly<{
145
+ /** Key on `item.binary` that holds the image bytes. Default: `"data"`. */
146
+ binaryField?: string;
147
+ /** MIME type override sent to the analyzer. Falls back to attachment `mimeType` when not set. */
148
+ contentType?: string;
149
+ /**
150
+ * Azure Content Understanding analyzer ID to use.
151
+ * Defaults to `"prebuilt-imageAnalyzer"`. Set this to a custom analyzer ID when you have
152
+ * a trained model or need a different prebuilt variant.
153
+ */
154
+ analyzerId?: string;
155
+ /** Max bytes the attachment may have before reading. Defaults to 50 MiB. */
156
+ maxBytes?: number;
157
+ }>;
158
+ declare const analyzeImageNode: DefinedNode<"azure-ocr.analyze-image", {
159
+ binaryField: unknown;
160
+ contentType: unknown;
161
+ analyzerId: unknown;
162
+ maxBytes: unknown;
163
+ }, unknown, Readonly<{
164
+ content: string;
165
+ fields: OcrStructuredFields;
166
+ }>, {
167
+ contentUnderstanding: {
168
+ type: AnyCredentialType;
169
+ label: string;
170
+ helpText: string;
171
+ };
172
+ }>;
173
+ //#endregion
174
+ //#region src/nodes/analyzeInvoiceNode.d.ts
175
+ type AnalyzeInvoiceConfig = Readonly<{
176
+ /** Key on `item.binary` that holds the document bytes. Default: `"data"`. */
177
+ binaryField?: string;
178
+ /** MIME type override sent to the analyzer. Falls back to attachment `mimeType` when not set. */
179
+ contentType?: string;
180
+ /** Max bytes the attachment may have before reading. Defaults to 50 MiB. */
181
+ maxBytes?: number;
182
+ }>;
183
+ declare const analyzeInvoiceNode: DefinedNode<"azure-ocr.analyze-invoice", {
184
+ binaryField: unknown;
185
+ contentType: unknown;
186
+ maxBytes: unknown;
187
+ }, unknown, Readonly<{
188
+ content: string;
189
+ fields: OcrStructuredFields;
190
+ }>, {
191
+ contentUnderstanding: {
192
+ type: AnyCredentialType;
193
+ label: string;
194
+ helpText: string;
195
+ };
196
+ }>;
197
+ //#endregion
198
+ export { AnalyzeDocumentConfig, AnalyzeImageConfig, AnalyzeInvoiceConfig, AzureContentUnderstandingSession, OcrAnalysisOutput, OcrStructuredFields, analyzeDocumentNode, analyzeImageNode, analyzeInvoiceNode, analyzeWithAzure, azureContentUnderstandingCredentialType, mapAnalysisResult };
199
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,147 @@
1
+ import { d as CredentialAuthDefinition, f as CredentialFieldSchema, g as CredentialTypeId, h as CredentialSessionFactory, l as AnyCredentialType, m as CredentialMaterialSourceKind, p as CredentialHealthTester, r as DefinedNode, u as CredentialAdvancedSectionPresentation } from "./index-C2KJPzqN.js";
2
+ import { AnalysisResult } from "@azure/ai-content-understanding";
3
+
4
+ //#region src/credentials/azureContentUnderstandingCredential.d.ts
5
+ type AzureContentUnderstandingSession = Readonly<{
6
+ endpoint: string;
7
+ apiKey: string;
8
+ }>;
9
+ declare const azureContentUnderstandingCredentialType: Readonly<{
10
+ definition: Readonly<{
11
+ typeId: CredentialTypeId;
12
+ displayName: string;
13
+ description?: string;
14
+ publicFields?: ReadonlyArray<CredentialFieldSchema>;
15
+ secretFields?: ReadonlyArray<CredentialFieldSchema>;
16
+ advancedSection?: CredentialAdvancedSectionPresentation;
17
+ supportedSourceKinds?: ReadonlyArray<CredentialMaterialSourceKind>;
18
+ auth?: CredentialAuthDefinition;
19
+ }>;
20
+ createSession: CredentialSessionFactory<{
21
+ endpoint: unknown;
22
+ }, {
23
+ apiKey: unknown;
24
+ }, Readonly<{
25
+ endpoint: string;
26
+ apiKey: string;
27
+ }>>;
28
+ test: CredentialHealthTester<{
29
+ endpoint: unknown;
30
+ }, {
31
+ apiKey: unknown;
32
+ }>;
33
+ }> & {
34
+ readonly key: string;
35
+ };
36
+ //#endregion
37
+ //#region src/lib/analyzeWithAzure.d.ts
38
+ /** Structured analyzer fields: scalars at leaves; nested objects and arrays preserved. */
39
+ type OcrStructuredFields = Readonly<Record<string, unknown>>;
40
+ /** The output shape returned by all OCR analyzer nodes. */
41
+ type OcrAnalysisOutput = Readonly<{
42
+ /** Markdown representation of the document content. */
43
+ content: string;
44
+ /** Structured fields extracted by the prebuilt analyzer. */
45
+ fields: OcrStructuredFields;
46
+ }>;
47
+ /**
48
+ * Analyzes a binary document using an Azure Content Understanding prebuilt analyzer.
49
+ * Retries on transient failures are handled by the engine via the node's `retryPolicy`.
50
+ */
51
+ declare function analyzeWithAzure(args: Readonly<{
52
+ session: AzureContentUnderstandingSession;
53
+ analyzerId: string;
54
+ body: Uint8Array;
55
+ contentType: string;
56
+ }>): Promise<OcrAnalysisOutput>;
57
+ /** @internal Exported for testing — maps a raw AnalysisResult to the node output shape. */
58
+ declare function mapAnalysisResult(result: AnalysisResult): OcrAnalysisOutput;
59
+ //#endregion
60
+ //#region src/nodes/analyzeDocumentNode.d.ts
61
+ type AnalyzeDocumentConfig = Readonly<{
62
+ /** Key on `item.binary` that holds the document bytes. Default: `"data"`. */
63
+ binaryField?: string;
64
+ /** MIME type override sent to the analyzer. Falls back to attachment `mimeType` when not set. */
65
+ contentType?: string;
66
+ /**
67
+ * Azure Content Understanding analyzer ID to use.
68
+ * Defaults to `"prebuilt-document"`. Set this to a custom analyzer ID when you have
69
+ * a trained model or need a different prebuilt variant.
70
+ */
71
+ analyzerId?: string;
72
+ /** Max bytes the attachment may have before reading. Defaults to 50 MiB. */
73
+ maxBytes?: number;
74
+ }>;
75
+ declare const analyzeDocumentNode: DefinedNode<"azure-ocr.analyze-document", {
76
+ binaryField: unknown;
77
+ contentType: unknown;
78
+ analyzerId: unknown;
79
+ maxBytes: unknown;
80
+ }, unknown, Readonly<{
81
+ content: string;
82
+ fields: OcrStructuredFields;
83
+ }>, {
84
+ contentUnderstanding: {
85
+ type: AnyCredentialType;
86
+ label: string;
87
+ helpText: string;
88
+ };
89
+ }>;
90
+ //#endregion
91
+ //#region src/nodes/analyzeImageNode.d.ts
92
+ type AnalyzeImageConfig = Readonly<{
93
+ /** Key on `item.binary` that holds the image bytes. Default: `"data"`. */
94
+ binaryField?: string;
95
+ /** MIME type override sent to the analyzer. Falls back to attachment `mimeType` when not set. */
96
+ contentType?: string;
97
+ /**
98
+ * Azure Content Understanding analyzer ID to use.
99
+ * Defaults to `"prebuilt-imageAnalyzer"`. Set this to a custom analyzer ID when you have
100
+ * a trained model or need a different prebuilt variant.
101
+ */
102
+ analyzerId?: string;
103
+ /** Max bytes the attachment may have before reading. Defaults to 50 MiB. */
104
+ maxBytes?: number;
105
+ }>;
106
+ declare const analyzeImageNode: DefinedNode<"azure-ocr.analyze-image", {
107
+ binaryField: unknown;
108
+ contentType: unknown;
109
+ analyzerId: unknown;
110
+ maxBytes: unknown;
111
+ }, unknown, Readonly<{
112
+ content: string;
113
+ fields: OcrStructuredFields;
114
+ }>, {
115
+ contentUnderstanding: {
116
+ type: AnyCredentialType;
117
+ label: string;
118
+ helpText: string;
119
+ };
120
+ }>;
121
+ //#endregion
122
+ //#region src/nodes/analyzeInvoiceNode.d.ts
123
+ type AnalyzeInvoiceConfig = Readonly<{
124
+ /** Key on `item.binary` that holds the document bytes. Default: `"data"`. */
125
+ binaryField?: string;
126
+ /** MIME type override sent to the analyzer. Falls back to attachment `mimeType` when not set. */
127
+ contentType?: string;
128
+ /** Max bytes the attachment may have before reading. Defaults to 50 MiB. */
129
+ maxBytes?: number;
130
+ }>;
131
+ declare const analyzeInvoiceNode: DefinedNode<"azure-ocr.analyze-invoice", {
132
+ binaryField: unknown;
133
+ contentType: unknown;
134
+ maxBytes: unknown;
135
+ }, unknown, Readonly<{
136
+ content: string;
137
+ fields: OcrStructuredFields;
138
+ }>, {
139
+ contentUnderstanding: {
140
+ type: AnyCredentialType;
141
+ label: string;
142
+ helpText: string;
143
+ };
144
+ }>;
145
+ //#endregion
146
+ export { AnalyzeDocumentConfig, AnalyzeImageConfig, AnalyzeInvoiceConfig, AzureContentUnderstandingSession, OcrAnalysisOutput, OcrStructuredFields, analyzeDocumentNode, analyzeImageNode, analyzeInvoiceNode, analyzeWithAzure, azureContentUnderstandingCredentialType, mapAnalysisResult };
147
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { a as mapAnalysisResult, i as analyzeWithAzure, n as analyzeImageNode, o as azureContentUnderstandingCredentialType, r as analyzeDocumentNode, t as analyzeInvoiceNode } from "./analyzeInvoiceNode-uVwe3GHD.js";
2
+
3
+ export { analyzeDocumentNode, analyzeImageNode, analyzeInvoiceNode, analyzeWithAzure, azureContentUnderstandingCredentialType, mapAnalysisResult };
@@ -0,0 +1,72 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "packageName": "@codemation/core-nodes-ocr",
4
+ "packageVersion": "0.2.0",
5
+ "description": "Azure AI Content Understanding (OCR) plugin for Codemation — prebuilt document, invoice, and image analyzers.",
6
+ "kind": "nodes",
7
+ "nodes": [
8
+ {
9
+ "name": "Analyze Document",
10
+ "kind": "node",
11
+ "description": "Runs an Azure Content Understanding document analyzer on a binary attachment and returns markdown text plus structured fields. Defaults to the prebuilt general document analyzer.",
12
+ "inputPorts": [
13
+ "main"
14
+ ],
15
+ "outputPorts": [
16
+ "main"
17
+ ],
18
+ "credentialRefs": [
19
+ "azureContentUnderstandingCredentialType"
20
+ ],
21
+ "sourcePath": "src\\nodes\\analyzeDocumentNode.ts"
22
+ },
23
+ {
24
+ "name": "Analyze Image",
25
+ "kind": "node",
26
+ "description": "Runs an Azure Content Understanding image analyzer on a binary attachment and returns markdown text plus structured fields. Defaults to the prebuilt image analyzer.",
27
+ "inputPorts": [
28
+ "main"
29
+ ],
30
+ "outputPorts": [
31
+ "main"
32
+ ],
33
+ "credentialRefs": [
34
+ "azureContentUnderstandingCredentialType"
35
+ ],
36
+ "sourcePath": "src\\nodes\\analyzeImageNode.ts"
37
+ },
38
+ {
39
+ "name": "Analyze Invoice",
40
+ "kind": "node",
41
+ "description": "Runs the Azure Content Understanding prebuilt invoice analyzer on a binary attachment and returns markdown text plus structured fields.",
42
+ "inputPorts": [
43
+ "main"
44
+ ],
45
+ "outputPorts": [
46
+ "main"
47
+ ],
48
+ "credentialRefs": [
49
+ "azureContentUnderstandingCredentialType"
50
+ ],
51
+ "sourcePath": "src\\nodes\\analyzeInvoiceNode.ts"
52
+ }
53
+ ],
54
+ "credentials": [
55
+ {
56
+ "name": "azure.contentUnderstanding",
57
+ "description": "Azure AI Content Understanding (endpoint + key) for prebuilt document, invoice, and image analyzers.",
58
+ "fields": [
59
+ {
60
+ "key": "endpoint",
61
+ "type": "string",
62
+ "required": true
63
+ },
64
+ {
65
+ "key": "apiKey",
66
+ "type": "password",
67
+ "required": true
68
+ }
69
+ ]
70
+ }
71
+ ]
72
+ }