@campfire-interactive/volume-intelligence-ui 0.1.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 (3) hide show
  1. package/dist/index.d.ts +401 -0
  2. package/dist/index.js +1716 -0
  3. package/package.json +43 -0
@@ -0,0 +1,401 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ /** Wide = months across columns (IHS/AFS); tall = one row per period (EDI). */
5
+ type VolumeSourceLayout = 'wide' | 'tall';
6
+ interface VolumeSource$1 {
7
+ id: string;
8
+ name: string;
9
+ layout: VolumeSourceLayout;
10
+ /** e.g. 'base' | 'overlay' — informational; the component does not branch on it. */
11
+ kind?: string;
12
+ /** False when the source can't accept an upload yet (unmapped, no calendar…). */
13
+ ready?: boolean;
14
+ /** Human-readable reasons the source isn't ready. Rendered as blockers. */
15
+ blockers?: string[];
16
+ }
17
+ type UploadStatus$1 = 'pending' | 'processing' | 'awaiting_calendar' | 'done' | 'done_with_errors' | 'failed';
18
+ interface UploadStats {
19
+ programs?: number;
20
+ volumes?: number;
21
+ [key: string]: number | undefined;
22
+ }
23
+ interface UploadRecord {
24
+ id: string;
25
+ fileName: string;
26
+ status: UploadStatus$1;
27
+ createdAt: string;
28
+ startedAt?: string | null;
29
+ completedAt?: string | null;
30
+ stats?: UploadStats | null;
31
+ errorCount?: number;
32
+ }
33
+ interface UploadErrorRow {
34
+ id?: string;
35
+ /** Typed category from VI (MISSING_KEY, INVALID_VALUE, …). */
36
+ type: string;
37
+ message: string;
38
+ /** 1-based source row, when the error is row-scoped. */
39
+ row?: number | null;
40
+ }
41
+ /** Parameters for a single upload. Period fields apply to wide-format sources. */
42
+ interface UploadInput {
43
+ sourceId: string;
44
+ file: File;
45
+ uploadYear?: number;
46
+ /** 1-based month (1 = January). */
47
+ uploadMonth?: number;
48
+ /** Months of lag before which data is treated as actuals vs predictions. */
49
+ actualsLagMonths?: number;
50
+ }
51
+ interface ListErrorsResult {
52
+ errors: UploadErrorRow[];
53
+ total: number;
54
+ }
55
+ /**
56
+ * Everything the component needs from the outside world. The host implements
57
+ * each method against its own wiring. `reprocess` is optional — hosts that
58
+ * don't expose it simply hide the reprocess affordance.
59
+ */
60
+ interface VolumeImportTransport {
61
+ listSources(): Promise<VolumeSource$1[]>;
62
+ uploadFile(input: UploadInput): Promise<UploadRecord>;
63
+ getUpload(uploadId: string): Promise<UploadRecord>;
64
+ listErrors(uploadId: string, opts?: {
65
+ page?: number;
66
+ }): Promise<ListErrorsResult>;
67
+ reprocess?(uploadId: string): Promise<UploadRecord>;
68
+ }
69
+ interface VolumeImportProps {
70
+ transport: VolumeImportTransport;
71
+ /** Pre-select this source id (e.g. a tenant's default automotive source). */
72
+ defaultSourceId?: string;
73
+ /** Poll interval (ms) while an upload is in a non-terminal state. Default 3000. */
74
+ pollIntervalMs?: number;
75
+ /** Called whenever an upload reaches a terminal state (done / errors / failed). */
76
+ onUploaded?: (upload: UploadRecord) => void;
77
+ /** Extra class on the root element, for host layout. */
78
+ className?: string;
79
+ }
80
+
81
+ /**
82
+ * VolumeImport — the shared volume-file import surface.
83
+ *
84
+ * Lean scope (data-plane ADR §6, "lean now, rich later"): pick a source, upload
85
+ * a file, watch it process, see any row errors. No column-mapping / preview /
86
+ * conflict-resolve wizard — those are a tracked follow-on. The component is
87
+ * auth- and transport-agnostic; the host wires `transport`.
88
+ */
89
+ declare function VolumeImport(props: VolumeImportProps): react.JSX.Element;
90
+
91
+ type SourceLayout = 'wide' | 'tall';
92
+ /** `base` = IHS/AFS historical training signal (one active per tenant);
93
+ * `overlay` = EDI-style additive customer-stated demand. */
94
+ type VolumeSourceKind = 'base' | 'overlay';
95
+ interface VolumeSource {
96
+ id: string;
97
+ code: string;
98
+ name: string;
99
+ description: string;
100
+ headerRowIndex: number;
101
+ volumeHeaderFormat: string;
102
+ isActive: boolean;
103
+ uniqueKeySegmentId: string | null;
104
+ layout: SourceLayout;
105
+ kind: VolumeSourceKind;
106
+ valueDateFormat: string;
107
+ _count?: {
108
+ segments: number;
109
+ };
110
+ }
111
+ interface VolumeSourceSegment {
112
+ id: string;
113
+ sourceId: string;
114
+ label: string;
115
+ dataType: string;
116
+ sortOrder: number;
117
+ }
118
+ interface SourceWithSegments extends VolumeSource {
119
+ segments: VolumeSourceSegment[];
120
+ }
121
+ interface TenantVolumeConfig {
122
+ id: string;
123
+ tenantId: string;
124
+ sourceId: string;
125
+ source: SourceWithSegments;
126
+ }
127
+ type SegmentMappingCategory = 'hierarchy_level' | 'automotive_field' | 'period' | 'value';
128
+ interface SegmentMappingRow {
129
+ id: string;
130
+ sourceId: string;
131
+ targetCategory: SegmentMappingCategory;
132
+ targetKey: string;
133
+ segmentId: string;
134
+ /** Display label of the source column, carried on every row. */
135
+ segmentLabel: string;
136
+ transformRule: string | null;
137
+ }
138
+ interface SegmentMappingCategoryKeys {
139
+ category: SegmentMappingCategory;
140
+ targetKeys: readonly string[];
141
+ requiredKeys: readonly string[];
142
+ }
143
+ interface SegmentMappingKeys {
144
+ categories: SegmentMappingCategoryKeys[];
145
+ transformRules: readonly string[];
146
+ }
147
+ /** A single entry in a category-replace (`replaceSegmentMappingCategory`). */
148
+ interface SegmentMappingEntry {
149
+ targetKey: string;
150
+ segmentId: string;
151
+ transformRule?: string | null;
152
+ }
153
+ interface SourceValueAlias {
154
+ id: string;
155
+ sourceId: string;
156
+ level: number;
157
+ rawValue: string;
158
+ canonicalValue: string;
159
+ createdAt: string;
160
+ updatedAt: string;
161
+ }
162
+ interface ValueAliasEntry {
163
+ rawValue: string;
164
+ canonicalValue: string;
165
+ }
166
+ interface RelinkResult {
167
+ totalOverlay: number;
168
+ linked: number;
169
+ unlinked: number;
170
+ ambiguous: number;
171
+ }
172
+ interface SourceTemplateSummary {
173
+ code: string;
174
+ name: string;
175
+ description: string;
176
+ }
177
+ type UploadStatus = 'pending' | 'processing' | 'awaiting_calendar' | 'done' | 'done_with_errors' | 'failed';
178
+ interface Upload {
179
+ id: string;
180
+ tenantId: string;
181
+ sourceId: string;
182
+ fileName: string;
183
+ s3Key: string;
184
+ uploadYear: number;
185
+ uploadMonth: number;
186
+ actualsLagMonths: number;
187
+ status: UploadStatus;
188
+ rowsTotal: number;
189
+ rowsProcessed: number;
190
+ rowsFailed: number;
191
+ rowsCanonicalised: number;
192
+ rowsQueued: number;
193
+ rowsBound: number;
194
+ errorSummary: string | null;
195
+ uploadedBy: string;
196
+ startedAt: string | null;
197
+ completedAt: string | null;
198
+ createdAt: string;
199
+ source: {
200
+ code: string;
201
+ name: string;
202
+ kind: VolumeSourceKind;
203
+ };
204
+ }
205
+ interface UploadError {
206
+ id: string;
207
+ uploadId: string;
208
+ rowNumber: number;
209
+ keyValue: string | null;
210
+ errorType: string;
211
+ errorDetail: string;
212
+ severity: 'error' | 'warn' | 'info';
213
+ createdAt: string;
214
+ }
215
+ interface UploadListParams {
216
+ limit?: number;
217
+ offset?: number;
218
+ status?: UploadStatus;
219
+ sourceId?: string;
220
+ year?: number;
221
+ month?: number;
222
+ }
223
+ interface UploadListResponse {
224
+ items: Upload[];
225
+ total: number;
226
+ limit: number;
227
+ offset: number;
228
+ }
229
+ interface CreateSourceInput {
230
+ code: string;
231
+ name: string;
232
+ description?: string;
233
+ headerRowIndex?: number;
234
+ volumeHeaderFormat?: string;
235
+ }
236
+ interface UpdateSourceInput {
237
+ name?: string;
238
+ description?: string;
239
+ headerRowIndex?: number;
240
+ volumeHeaderFormat?: string;
241
+ isActive?: boolean;
242
+ uniqueKeySegmentId?: string | null;
243
+ layout?: SourceLayout;
244
+ valueDateFormat?: string;
245
+ kind?: VolumeSourceKind;
246
+ }
247
+ interface CreateSegmentInput {
248
+ label: string;
249
+ dataType?: string;
250
+ sortOrder?: number;
251
+ }
252
+ interface DetectSegmentsResult {
253
+ detected: number;
254
+ skipped: number;
255
+ }
256
+ interface ClearSourceDataResult {
257
+ uploads: number;
258
+ criteria: number;
259
+ cleared: true;
260
+ }
261
+ /**
262
+ * Everything the source-management components need from the host. VI-webapp
263
+ * implements these against its `adminApi`; OMSF implements them against proxy
264
+ * routes that mint a `forecast-backend` system token and call VI, so OMSF's
265
+ * browser never holds a vi-backend token.
266
+ */
267
+ interface SourceAdminTransport {
268
+ listSources(): Promise<VolumeSource[]>;
269
+ getSource(id: string): Promise<SourceWithSegments>;
270
+ createSource(data: CreateSourceInput): Promise<VolumeSource>;
271
+ updateSource(id: string, data: UpdateSourceInput): Promise<VolumeSource>;
272
+ activateSource(id: string): Promise<{
273
+ activated: boolean;
274
+ wiped: boolean;
275
+ }>;
276
+ listSourceTemplates(): Promise<SourceTemplateSummary[]>;
277
+ createSourceFromTemplate(templateCode: string): Promise<{
278
+ id: string;
279
+ }>;
280
+ listSegments(sourceId: string): Promise<VolumeSourceSegment[]>;
281
+ detectSegments(sourceId: string, file: File): Promise<DetectSegmentsResult>;
282
+ createSegment(sourceId: string, input: CreateSegmentInput): Promise<VolumeSourceSegment>;
283
+ deleteSegment(segmentId: string): Promise<void>;
284
+ getSegmentMappingKeys(): Promise<SegmentMappingKeys>;
285
+ listSegmentMapping(sourceId: string, category?: SegmentMappingCategory): Promise<{
286
+ items: SegmentMappingRow[];
287
+ }>;
288
+ replaceSegmentMappingCategory(sourceId: string, category: SegmentMappingCategory, entries: SegmentMappingEntry[]): Promise<{
289
+ items: SegmentMappingRow[];
290
+ }>;
291
+ listValueAliases(sourceId: string): Promise<SourceValueAlias[]>;
292
+ replaceLevelValueAliases(sourceId: string, level: number, entries: ValueAliasEntry[]): Promise<SourceValueAlias[]>;
293
+ deleteValueAlias(aliasId: string): Promise<void>;
294
+ relinkOverlaySource(sourceId: string): Promise<RelinkResult>;
295
+ getTenantConfig(): Promise<TenantVolumeConfig | null>;
296
+ setTenantSource(sourceId: string): Promise<TenantVolumeConfig>;
297
+ listUploads(params?: UploadListParams): Promise<UploadListResponse>;
298
+ clearSourceData(sourceId: string, confirm: string): Promise<ClearSourceDataResult>;
299
+ }
300
+
301
+ interface VolumeSourcesListProps {
302
+ transport: SourceAdminTransport;
303
+ /** Open a source's detail view. The host owns routing. */
304
+ onOpenSource: (sourceId: string) => void;
305
+ /**
306
+ * Optional slot rendered inside the *active* source's card. The VI webapp
307
+ * injects its paid analytical sub-cards (enrichment toggle, backtests) here;
308
+ * OMSF and other lean hosts simply omit it. Keeps those surfaces VI-only
309
+ * (ADR §6) while the list itself stays host-agnostic.
310
+ */
311
+ renderActiveSourceExtras?: (source: VolumeSource) => ReactNode;
312
+ className?: string;
313
+ }
314
+ /**
315
+ * Volume-source list: cards per source, "add from template", manual create,
316
+ * and activate (with the data-wipe confirm). Transport-agnostic and router-free
317
+ * — the host supplies `transport` and an `onOpenSource` navigation callback.
318
+ *
319
+ * The VI-webapp's active-source analytical sub-cards (enrichment toggle,
320
+ * backtests) are intentionally NOT here — per ADR §6 they stay VI-webapp-only.
321
+ */
322
+ declare function VolumeSourcesList({ transport, onOpenSource, renderActiveSourceExtras, className }: VolumeSourcesListProps): react.JSX.Element;
323
+
324
+ interface AutomotiveSlot {
325
+ key: string;
326
+ label: string;
327
+ required: boolean;
328
+ defaultTransform?: string;
329
+ hierarchyCoveredByLevel?: number;
330
+ }
331
+ interface UnifiedSegmentMapperProps {
332
+ segments: VolumeSourceSegment[];
333
+ hierarchyInitial: SegmentMappingRow[];
334
+ automotiveInitial: SegmentMappingRow[];
335
+ automotiveSlots: AutomotiveSlot[];
336
+ transformRules: readonly string[];
337
+ transformRuleLabels: Record<string, string>;
338
+ onSave: (hierarchy: Array<{
339
+ targetKey: string;
340
+ segmentId: string;
341
+ }>, automotive: Array<{
342
+ targetKey: string;
343
+ segmentId: string;
344
+ transformRule?: string | null;
345
+ }>) => Promise<void>;
346
+ saving: boolean;
347
+ hierarchyMode?: 'tree' | 'sparse';
348
+ baseHierarchyHint?: Partial<Record<number, string>>;
349
+ }
350
+ declare function UnifiedSegmentMapper({ segments, hierarchyInitial, automotiveInitial, automotiveSlots, transformRules, transformRuleLabels, onSave, saving, hierarchyMode, baseHierarchyHint, }: UnifiedSegmentMapperProps): react.JSX.Element;
351
+
352
+ interface VolumeSourceDetailProps {
353
+ transport: SourceAdminTransport;
354
+ sourceId: string;
355
+ /** Navigate to another source (the header switcher). Host owns routing. */
356
+ onOpenSource: (sourceId: string) => void;
357
+ /** Optional "All sources →" affordance; hidden when omitted. */
358
+ onBack?: () => void;
359
+ className?: string;
360
+ }
361
+ /**
362
+ * Volume-source detail: layout, unique key, segment detect/add/delete, the
363
+ * segment mapper, tall mappings, value aliases, and the clear-data danger zone.
364
+ * Ported from forecast's SourceDetailPage; adminApi → injected transport,
365
+ * react-router → onOpenSource/onBack callbacks, shadcn/Tailwind → plain CSS.
366
+ */
367
+ declare function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, className }: VolumeSourceDetailProps): react.JSX.Element;
368
+
369
+ interface TallLayoutMappingsProps {
370
+ transport: SourceAdminTransport;
371
+ source: SourceWithSegments;
372
+ locked: boolean;
373
+ }
374
+ declare function TallLayoutMappings({ transport, source, locked }: TallLayoutMappingsProps): react.JSX.Element;
375
+
376
+ /**
377
+ * Per-source, per-level value alias editor. Ported from forecast; adminApi
378
+ * calls lifted to the injected transport. The matcher applies aliases to both
379
+ * sides of the overlay→base comparison, so an operator can fix vocabulary
380
+ * drift (e.g. "MICHIGAN" → "MICHIGAN ASSEMBLY") without re-uploading.
381
+ */
382
+ interface ValueAliasesCardProps {
383
+ transport: SourceAdminTransport;
384
+ sourceId: string;
385
+ locked: boolean;
386
+ isOverlay: boolean;
387
+ }
388
+ declare function ValueAliasesCard({ transport, sourceId, locked, isOverlay }: ValueAliasesCardProps): react.JSX.Element;
389
+
390
+ declare const HIERARCHY_LEVEL_COUNT = 6;
391
+ /** Display labels for hierarchy levels 1..6 (the mapping of source columns to
392
+ * levels is per-tenant config; this only controls the label). */
393
+ declare const DEFAULT_LEVEL_NAMES: Record<number, string>;
394
+ /** Automotive slots shown in the unified mapper for BASE sources (IHS/AFS/LMC).
395
+ * Five auto-cover from the hierarchy (oem_parent←L1, platform←L2, program←L3,
396
+ * plant_name←L4, nameplate←L5); `brand` falls back to oem_parent; the rest are
397
+ * required and explicitly mapped. Overlay sources pass an empty array. */
398
+ declare const AUTOMOTIVE_SLOTS: AutomotiveSlot[];
399
+ declare const AUTOMOTIVE_TRANSFORM_LABELS: Record<string, string>;
400
+
401
+ export { AUTOMOTIVE_SLOTS, AUTOMOTIVE_TRANSFORM_LABELS, type AutomotiveSlot, type ClearSourceDataResult, type CreateSegmentInput, type CreateSourceInput, DEFAULT_LEVEL_NAMES, type DetectSegmentsResult, HIERARCHY_LEVEL_COUNT, type ListErrorsResult, type RelinkResult, type SegmentMappingCategory, type SegmentMappingCategoryKeys, type SegmentMappingEntry, type SegmentMappingKeys, type SegmentMappingRow, type SourceAdminTransport, type SourceLayout, type SourceTemplateSummary, type SourceValueAlias, type SourceWithSegments, TallLayoutMappings, type TallLayoutMappingsProps, type TenantVolumeConfig, UnifiedSegmentMapper, type UnifiedSegmentMapperProps, type UpdateSourceInput, type Upload, type UploadError, type UploadErrorRow, type UploadInput, type UploadListParams, type UploadListResponse, type UploadRecord, type UploadStats, type UploadStatus, type ValueAliasEntry, ValueAliasesCard, type ValueAliasesCardProps, VolumeImport, type VolumeImportProps, type VolumeSource$1 as VolumeImportSource, type VolumeImportTransport, type UploadStatus$1 as VolumeImportUploadStatus, type VolumeSource, VolumeSourceDetail, type VolumeSourceDetailProps, type VolumeSourceKind, type VolumeSourceLayout, type VolumeSourceSegment, VolumeSourcesList, type VolumeSourcesListProps };