@zeniai/client-epic-state 5.0.65-betaRR5 → 5.0.65-betaRR7

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 (51) hide show
  1. package/lib/entity/aiCfo/aiCfoPayload.d.ts +67 -64
  2. package/lib/entity/aiCfo/aiCfoReducer.d.ts +7 -1
  3. package/lib/entity/aiCfo/aiCfoReducer.js +87 -56
  4. package/lib/entity/aiCfo/aiCfoState.d.ts +58 -59
  5. package/lib/entity/cardPolicy/cardPolicyPayload.d.ts +106 -5
  6. package/lib/entity/cardPolicy/cardPolicyPayload.js +35 -4
  7. package/lib/entity/cardPolicy/cardPolicyReducer.d.ts +9 -3
  8. package/lib/entity/cardPolicy/cardPolicyReducer.js +57 -4
  9. package/lib/entity/cardPolicy/cardPolicySelector.d.ts +4 -0
  10. package/lib/entity/cardPolicy/cardPolicySelector.js +17 -0
  11. package/lib/entity/cardPolicy/cardPolicyState.d.ts +33 -0
  12. package/lib/entity/cardPolicy/policyDocumentExtractionToRecommendationBridgeEpic.d.ts +25 -0
  13. package/lib/entity/cardPolicy/policyDocumentExtractionToRecommendationBridgeEpic.js +39 -0
  14. package/lib/entity/cardPolicy/policyRecommendationFromUploadEpic.d.ts +23 -0
  15. package/lib/entity/cardPolicy/policyRecommendationFromUploadEpic.js +92 -0
  16. package/lib/epic.d.ts +3 -1
  17. package/lib/epic.js +3 -1
  18. package/lib/esm/entity/aiCfo/aiCfoReducer.js +86 -55
  19. package/lib/esm/entity/cardPolicy/cardPolicyPayload.js +32 -3
  20. package/lib/esm/entity/cardPolicy/cardPolicyReducer.js +56 -3
  21. package/lib/esm/entity/cardPolicy/cardPolicySelector.js +13 -0
  22. package/lib/esm/entity/cardPolicy/policyDocumentExtractionToRecommendationBridgeEpic.js +35 -0
  23. package/lib/esm/entity/cardPolicy/policyRecommendationFromUploadEpic.js +88 -0
  24. package/lib/esm/epic.js +4 -2
  25. package/lib/esm/index.js +10 -9
  26. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +6 -3
  27. package/lib/esm/view/aiCfoView/epics/createSessionAndSubmitEpic.js +5 -2
  28. package/lib/esm/view/aiCfoView/epics/createSessionEpic.js +2 -1
  29. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +22 -13
  30. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +26 -1
  31. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyTemplateEpic.js +27 -8
  32. package/lib/esm/view/spendManagement/chargeCards/chargeCardList/chargeCardListSelector.js +25 -0
  33. package/lib/index.d.ts +10 -10
  34. package/lib/index.js +29 -15
  35. package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +4 -2
  36. package/lib/view/aiCfoView/aiCfoViewReducer.js +6 -3
  37. package/lib/view/aiCfoView/epics/createSessionAndSubmitEpic.js +5 -2
  38. package/lib/view/aiCfoView/epics/createSessionEpic.js +2 -1
  39. package/lib/view/companyHealthMetricView/companyHealthMetricViewSelector.d.ts +1 -1
  40. package/lib/view/onboardingView/cockpitView/types/onboardingCockpitViewTypes.d.ts +1 -1
  41. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.d.ts +4 -3
  42. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +23 -14
  43. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.d.ts +22 -0
  44. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +28 -1
  45. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyState.d.ts +29 -8
  46. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyTemplateEpic.d.ts +20 -3
  47. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyTemplateEpic.js +27 -8
  48. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardList.d.ts +1 -1
  49. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListSelector.d.ts +14 -0
  50. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListSelector.js +27 -1
  51. package/package.json +1 -1
@@ -96,85 +96,88 @@ export type CardsCreationInitialDataPayload = {
96
96
  mode: 'cards-only';
97
97
  };
98
98
  /**
99
- * Vendor / category seed for the AI Card Policy interactive form.
100
- * `optional_code` carries the MCC (e.g. "6051") for category items; vendor
101
- * items omit it.
99
+ * Per-policy allow / block bucket on the wire. The two arrays are
100
+ * independent (a draft policy may populate both, only one, or neither).
102
101
  */
103
- export interface CardPolicyTagSeedPayload {
104
- id: string;
105
- is_selected: boolean;
106
- label: string;
107
- optional_code?: string | null;
102
+ export interface DraftPolicyEntityPayload {
103
+ category_codes: string[];
104
+ merchant_names: string[];
108
105
  }
109
- export interface CardPolicyOwnerSeedPayload {
110
- display_name: string;
111
- user_id: string;
112
- role_label?: string | null;
106
+ /**
107
+ * Spend-limit bucket per policy. `transaction` is the only cadence the
108
+ * BE ships today; left open-ended for future cadences (e.g.
109
+ * `monthly`, `quarterly`).
110
+ */
111
+ export interface DraftPolicySpendLimitsPayload {
112
+ transaction: number;
113
+ [cadence: string]: number | undefined;
113
114
  }
114
- export interface CardPolicyCardRowSeedPayload {
115
- card_name: string;
116
- id: string;
117
- is_checked: boolean;
118
- last4: string;
119
- limit_amount: number;
120
- owner: CardPolicyOwnerSeedPayload;
121
- utilization_amount: number;
122
- limit_cadence?: string | null;
123
- }
124
- export interface CardPolicyLimitRowSeedPayload {
125
- id: string;
126
- is_enabled: boolean;
127
- amount?: number | null;
115
+ /**
116
+ * Single draft policy on the wire. The controller emits one of these
117
+ * per recommended policy in `step_5_review.draft_policies`. Identity is
118
+ * positional (no stable `id` from the BE); the FE synthesizes one on
119
+ * decode.
120
+ */
121
+ export interface DraftPolicyPayload {
122
+ allowed_entity: DraftPolicyEntityPayload;
123
+ apply_to_cards: string[];
124
+ blocked_entity: DraftPolicyEntityPayload;
125
+ description: string;
126
+ /** Enforcement mode for the policy (e.g. `"strict"`). Carried into
127
+ * state for guided submit; ignored for `auto` / `upload`. */
128
+ mode: string;
129
+ name: string;
130
+ required_receipt_threshold: number;
131
+ spend_limits: DraftPolicySpendLimitsPayload;
128
132
  }
129
133
  /**
130
- * Suggested-policy seed for the 1-step `review-only` variant and Step 5 of
131
- * the `full-builder` variant. Each chip group is a flat list rendered inside
132
- * the expandable policy card; the FE classifies them visually via the
133
- * `chip_kind` discriminator.
134
+ * `step_5_review` block the only step in `wizard_plan` the FE reads.
135
+ * Steps 1–4 are intentionally not modeled.
134
136
  */
135
- export interface CardPolicySuggestedPolicyChipPayload {
136
- chip_kind: 'positive' | 'negative' | 'avatar';
137
- id: string;
138
- label: string;
137
+ export interface CardPolicyStep5ReviewPayload {
138
+ draft_policies: DraftPolicyPayload[];
139
+ /** Header text shown above the review list / wizard. */
140
+ prompt: string;
141
+ /** How the BE grouped the suggested policies. `"none"` today; may
142
+ * surface as a header banner in a future iteration. */
143
+ grouping_source?: string;
139
144
  }
140
- export interface CardPolicySuggestedPolicyChipGroupPayload {
141
- chips: CardPolicySuggestedPolicyChipPayload[];
142
- id: string;
143
- label: string;
145
+ export interface CardPolicyWizardPlanPayload {
146
+ step_5_review: CardPolicyStep5ReviewPayload;
144
147
  }
145
- export interface CardPolicySuggestedPolicyPayload {
146
- chip_groups: CardPolicySuggestedPolicyChipGroupPayload[];
147
- id: string;
148
- is_checked: boolean;
149
- name: string;
148
+ /**
149
+ * Upload-mode metadata that the `policy-recommendation-from-upload` API
150
+ * returns alongside the wizard plan. Absent on the initial controller
151
+ * response; populated after the file upload + recommendation chain.
152
+ */
153
+ export interface CardPolicyUploadSourcePayload {
154
+ confidence_score: number;
155
+ file_name: string;
156
+ low_confidence_fields: string[];
150
157
  }
151
158
  /**
152
- * Discriminated payload for the AI Card Policy interactive form. Mirrors
153
- * the FE-side `CardPolicyInitialData`.
159
+ * Discriminated payload for the AI Card Policy interactive form.
160
+ * All three modes share the same `wizard_plan.step_5_review.draft_policies`
161
+ * envelope; the FE renders different forms based on `mode`.
154
162
  *
155
- * - `review-only` (1-step): AI auto-applied default rules; user sees only
156
- * the policy review.
157
- * - `full-builder` (5-step): vendors categories → cards → limits →
158
- * review.
159
- * - `doc-upload` (3-step): upload cards review. Vendors / categories
160
- * / limits are filled in by the policy-document-extract API on the FE,
161
- * so the payload only carries `cards` (still user-picked) and any
162
- * optional suggested-policy seed.
163
+ * - `auto` (1-step review): user reviews suggested policies and submits.
164
+ * - `guided` (5-step wizard): Steps 1–4 seeded from `draft_policies[0]`,
165
+ * Step 5 lists every draft policy.
166
+ * - `upload` (3-step): upload doc → pick cards → review. Controller
167
+ * responses for `mode === "upload"` arrive *before* the user uploads,
168
+ * so `wizard_plan` is optional; populated after the upload-recommendation
169
+ * API returns.
163
170
  */
164
171
  export type CardPolicyInitialDataPayload = {
165
- policy_kind: 'review-only';
166
- suggested_policies: CardPolicySuggestedPolicyPayload[];
172
+ mode: 'auto';
173
+ wizard_plan: CardPolicyWizardPlanPayload;
167
174
  } | {
168
- cards: CardPolicyCardRowSeedPayload[];
169
- categories: CardPolicyTagSeedPayload[];
170
- default_limits: CardPolicyLimitRowSeedPayload[];
171
- policy_kind: 'full-builder';
172
- suggested_policies: CardPolicySuggestedPolicyPayload[];
173
- vendors: CardPolicyTagSeedPayload[];
175
+ mode: 'guided';
176
+ wizard_plan: CardPolicyWizardPlanPayload;
174
177
  } | {
175
- cards: CardPolicyCardRowSeedPayload[];
176
- policy_kind: 'doc-upload';
177
- suggested_policies: CardPolicySuggestedPolicyPayload[];
178
+ mode: 'upload';
179
+ source?: CardPolicyUploadSourcePayload;
180
+ wizard_plan?: CardPolicyWizardPlanPayload;
178
181
  };
179
182
  /**
180
183
  * Discriminated visualization payload. The FE narrows on `form_type` and
@@ -2,6 +2,7 @@ import { ID } from '../../commonStateTypes/common';
2
2
  import { ZeniDate } from '../../zeniDayJS';
3
3
  import { AiCfoAnswerPayload, AiCfoVisualizationPayload, ChatSessionPayload, MessagePayload } from './aiCfoPayload';
4
4
  import { AiCfoState, AiCfoVisualization, FileAttachmentMetadata } from './aiCfoState';
5
+ import type { CardPolicyUploadSource, CardPolicyWizardPlan } from './aiCfoState';
5
6
  export declare const initialAiCfoState: AiCfoState;
6
7
  export declare const toAiCfoVisualization: (visualization: AiCfoVisualizationPayload) => AiCfoVisualization | undefined;
7
8
  export declare const setNewSession: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
@@ -41,6 +42,11 @@ export declare const setNewSession: import("@reduxjs/toolkit").ActionCreatorWith
41
42
  }, "aiCfo/addQuestionPayload">, clearAiCfo: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"aiCfo/clearAiCfo">, deleteQuestion: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
42
43
  chatSessionId: ID;
43
44
  questionAnswerId: ID;
44
- }, "aiCfo/deleteQuestion">;
45
+ }, "aiCfo/deleteQuestion">, updateAiCfoAnswerCardPolicyWizardPlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
46
+ answerId: ID;
47
+ chatSessionId: ID;
48
+ wizardPlan: CardPolicyWizardPlan;
49
+ source?: CardPolicyUploadSource;
50
+ }, "aiCfo/updateAiCfoAnswerCardPolicyWizardPlan">;
45
51
  declare const _default: import("redux").Reducer<AiCfoState>;
46
52
  export default _default;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  var _a;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.deleteQuestion = exports.clearAiCfo = exports.addQuestionPayload = exports.upsertOrAddQuestionAnswerPayload = exports.upsertAnswerPayload = exports.setChatHistory = exports.setSessions = exports.clearSession = exports.setNewSession = exports.toAiCfoVisualization = exports.initialAiCfoState = void 0;
4
+ exports.updateAiCfoAnswerCardPolicyWizardPlan = exports.deleteQuestion = exports.clearAiCfo = exports.addQuestionPayload = exports.upsertOrAddQuestionAnswerPayload = exports.upsertAnswerPayload = exports.setChatHistory = exports.setSessions = exports.clearSession = exports.setNewSession = exports.toAiCfoVisualization = exports.initialAiCfoState = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
6
  const zeniDayJS_1 = require("../../zeniDayJS");
7
7
  const aiCfoState_1 = require("./aiCfoState");
@@ -268,67 +268,55 @@ const withSynthesizedIds = (cards) => {
268
268
  return { card, uniqueId: synthesized };
269
269
  });
270
270
  };
271
+ const toCardPolicyDraftPolicy = (payload, index) => ({
272
+ id: `draft-${index}`,
273
+ name: payload.name,
274
+ description: payload.description ?? '',
275
+ mode: payload.mode,
276
+ allowedEntity: {
277
+ categoryCodes: payload.allowed_entity?.category_codes ?? [],
278
+ merchantNames: payload.allowed_entity?.merchant_names ?? [],
279
+ },
280
+ blockedEntity: {
281
+ categoryCodes: payload.blocked_entity?.category_codes ?? [],
282
+ merchantNames: payload.blocked_entity?.merchant_names ?? [],
283
+ },
284
+ applyToCards: payload.apply_to_cards ?? [],
285
+ requiredReceiptThreshold: payload.required_receipt_threshold ?? 0,
286
+ spendLimits: {
287
+ transaction: payload.spend_limits?.transaction ?? 0,
288
+ },
289
+ });
290
+ const toCardPolicyStep5Review = (payload) => ({
291
+ draftPolicies: (payload.draft_policies ?? []).map(toCardPolicyDraftPolicy),
292
+ groupingSource: payload.grouping_source ?? 'none',
293
+ prompt: payload.prompt ?? '',
294
+ });
295
+ const toCardPolicyWizardPlan = (payload) => ({
296
+ step5Review: toCardPolicyStep5Review(payload.step_5_review),
297
+ });
298
+ const toCardPolicyUploadSource = (payload) => ({
299
+ confidenceScore: payload.confidence_score,
300
+ fileName: payload.file_name,
301
+ lowConfidenceFields: payload.low_confidence_fields ?? [],
302
+ });
271
303
  const toCardPolicyInitialData = (payload) => {
272
- const suggestedPolicies = (payload.suggested_policies ?? []).map((policy) => ({
273
- id: policy.id,
274
- isChecked: policy.is_checked,
275
- name: policy.name,
276
- chipGroups: (policy.chip_groups ?? []).map((group) => ({
277
- id: group.id,
278
- label: group.label,
279
- chips: (group.chips ?? []).map((chip) => ({
280
- id: chip.id,
281
- label: chip.label,
282
- chipKind: chip.chip_kind,
283
- })),
284
- })),
285
- }));
286
- if (payload.policy_kind === 'review-only') {
287
- return {
288
- policyKind: 'review-only',
289
- suggestedPolicies,
290
- };
291
- }
292
- const toCardRowSeed = (card) => ({
293
- cardName: card.card_name,
294
- id: card.id,
295
- isChecked: card.is_checked,
296
- last4: card.last4,
297
- limitAmount: card.limit_amount,
298
- limitCadence: card.limit_cadence ?? undefined,
299
- owner: {
300
- userId: card.owner.user_id,
301
- displayName: card.owner.display_name,
302
- roleLabel: card.owner.role_label ?? undefined,
303
- },
304
- utilizationAmount: card.utilization_amount,
305
- });
306
- if (payload.policy_kind === 'doc-upload') {
304
+ if (payload.mode === 'upload') {
307
305
  return {
308
- policyKind: 'doc-upload',
309
- suggestedPolicies,
310
- cards: (payload.cards ?? []).map(toCardRowSeed),
306
+ policyKind: 'upload',
307
+ wizardPlan: payload.wizard_plan != null
308
+ ? toCardPolicyWizardPlan(payload.wizard_plan)
309
+ : undefined,
310
+ source: payload.source != null
311
+ ? toCardPolicyUploadSource(payload.source)
312
+ : undefined,
311
313
  };
312
314
  }
313
315
  return {
314
- policyKind: 'full-builder',
315
- suggestedPolicies,
316
- vendors: (payload.vendors ?? []).map(toCardPolicyTagSeed),
317
- categories: (payload.categories ?? []).map(toCardPolicyTagSeed),
318
- cards: (payload.cards ?? []).map(toCardRowSeed),
319
- defaultLimits: (payload.default_limits ?? []).map((row) => ({
320
- id: row.id,
321
- isEnabled: row.is_enabled,
322
- amount: row.amount ?? undefined,
323
- })),
316
+ policyKind: payload.mode,
317
+ wizardPlan: toCardPolicyWizardPlan(payload.wizard_plan),
324
318
  };
325
319
  };
326
- const toCardPolicyTagSeed = (tag) => ({
327
- id: tag.id,
328
- isSelected: tag.is_selected,
329
- label: tag.label,
330
- optionalCode: tag.optional_code ?? undefined,
331
- });
332
320
  const toAiCfoVisualizationData = (data) => {
333
321
  if ('x' in data && 'y' in data) {
334
322
  // Normalize y to always be an array
@@ -642,6 +630,49 @@ const aiCfo = (0, toolkit_1.createSlice)({
642
630
  session.questionAnswers.push(qa);
643
631
  }
644
632
  },
633
+ /**
634
+ * Patch the `card_policy` interactive-form payload for a specific
635
+ * answer in a chat session to merge in the wizard plan + upload
636
+ * source metadata returned by
637
+ * `POST /cards/1.0/ai-cfo/policy-recommendation-from-upload`.
638
+ *
639
+ * Dispatched by the recommendation epic after a successful response;
640
+ * the wizard re-renders Steps 2 + 3 of the upload variant once the
641
+ * payload becomes a populated `CardPolicyInitialData` with
642
+ * `policyKind: "upload"` + `wizardPlan` + `source`.
643
+ *
644
+ * No-op when the target answer / chat session / form payload is
645
+ * missing or isn't an upload-mode card-policy interactive form —
646
+ * the bridge epic is best-effort and silently drops in edge cases
647
+ * (e.g. the user closed the chat before recommendation returned).
648
+ */
649
+ updateAiCfoAnswerCardPolicyWizardPlan(draft, action) {
650
+ const { answerId, chatSessionId, source, wizardPlan } = action.payload;
651
+ const session = draft.aiCfoByChatSessionId[chatSessionId];
652
+ if (session == null) {
653
+ return;
654
+ }
655
+ const qa = session.questionAnswers.find((entry) => entry.questionAnswerId === answerId);
656
+ if (qa == null) {
657
+ return;
658
+ }
659
+ for (const response of Object.values(qa.responses)) {
660
+ const visualization = response?.content.visualization;
661
+ if (visualization == null || visualization.type !== 'interactive_form') {
662
+ continue;
663
+ }
664
+ const data = visualization.data;
665
+ if (data.formType !== 'card_policy' ||
666
+ data.payload.policyKind !== 'upload') {
667
+ continue;
668
+ }
669
+ data.payload = {
670
+ ...data.payload,
671
+ wizardPlan,
672
+ source: source ?? data.payload.source,
673
+ };
674
+ }
675
+ },
645
676
  clearSession(draft, action) {
646
677
  const sessionId = action.payload;
647
678
  delete draft.aiCfoByChatSessionId[sessionId];
@@ -677,5 +708,5 @@ const toChatSession = (chatSessionPayload) => {
677
708
  createdAt: (0, zeniDayJS_1.date)(created_at),
678
709
  };
679
710
  };
680
- _a = aiCfo.actions, exports.setNewSession = _a.setNewSession, exports.clearSession = _a.clearSession, exports.setSessions = _a.setSessions, exports.setChatHistory = _a.setChatHistory, exports.upsertAnswerPayload = _a.upsertAnswerPayload, exports.upsertOrAddQuestionAnswerPayload = _a.upsertOrAddQuestionAnswerPayload, exports.addQuestionPayload = _a.addQuestionPayload, exports.clearAiCfo = _a.clearAiCfo, exports.deleteQuestion = _a.deleteQuestion;
711
+ _a = aiCfo.actions, exports.setNewSession = _a.setNewSession, exports.clearSession = _a.clearSession, exports.setSessions = _a.setSessions, exports.setChatHistory = _a.setChatHistory, exports.upsertAnswerPayload = _a.upsertAnswerPayload, exports.upsertOrAddQuestionAnswerPayload = _a.upsertOrAddQuestionAnswerPayload, exports.addQuestionPayload = _a.addQuestionPayload, exports.clearAiCfo = _a.clearAiCfo, exports.deleteQuestion = _a.deleteQuestion, exports.updateAiCfoAnswerCardPolicyWizardPlan = _a.updateAiCfoAnswerCardPolicyWizardPlan;
681
712
  exports.default = aiCfo.reducer;
@@ -126,80 +126,79 @@ export type CardsCreationInitialData = {
126
126
  mode: 'cards-only';
127
127
  };
128
128
  /**
129
- * Vendor / category seed for the AI Card Policy interactive form.
130
- * `optionalCode` carries the MCC (e.g. "6051") for category items; vendor
131
- * items omit it.
129
+ * Per-policy allow / block bucket. CamelCase mirror of
130
+ * `DraftPolicyEntityPayload`.
132
131
  */
133
- export interface CardPolicyTagSeed {
134
- id: string;
135
- isSelected: boolean;
136
- label: string;
137
- optionalCode?: string;
132
+ export interface CardPolicyDraftPolicyEntity {
133
+ categoryCodes: string[];
134
+ merchantNames: string[];
138
135
  }
139
- export interface CardPolicyOwnerSeed {
140
- displayName: string;
141
- userId: ID;
142
- roleLabel?: string;
143
- }
144
- export interface CardPolicyCardRowSeed {
145
- cardName: string;
146
- id: string;
147
- isChecked: boolean;
148
- last4: string;
149
- limitAmount: number;
150
- owner: CardPolicyOwnerSeed;
151
- utilizationAmount: number;
152
- limitCadence?: string;
136
+ /**
137
+ * Spend-limit bucket per policy. `transaction` is the only cadence the
138
+ * BE ships today.
139
+ */
140
+ export interface CardPolicySpendLimits {
141
+ transaction: number;
153
142
  }
154
- export interface CardPolicyLimitRowSeed {
143
+ /**
144
+ * Single draft policy in FE state. Identity is positional — the reducer
145
+ * synthesizes a stable `id` (`draft-0`, `draft-1`, ...) at decode time
146
+ * since the BE doesn't ship one today.
147
+ */
148
+ export interface CardPolicyDraftPolicy {
149
+ allowedEntity: CardPolicyDraftPolicyEntity;
150
+ applyToCards: string[];
151
+ blockedEntity: CardPolicyDraftPolicyEntity;
152
+ description: string;
155
153
  id: string;
156
- isEnabled: boolean;
157
- amount?: number;
154
+ /** Enforcement mode (e.g. `"strict"`). Carried for guided submit;
155
+ * ignored by auto / upload submit. */
156
+ mode: string;
157
+ name: string;
158
+ requiredReceiptThreshold: number;
159
+ spendLimits: CardPolicySpendLimits;
158
160
  }
159
- export interface CardPolicySuggestedPolicyChip {
160
- chipKind: 'positive' | 'negative' | 'avatar';
161
- id: string;
162
- label: string;
161
+ export interface CardPolicyStep5Review {
162
+ draftPolicies: CardPolicyDraftPolicy[];
163
+ groupingSource: string;
164
+ prompt: string;
163
165
  }
164
- export interface CardPolicySuggestedPolicyChipGroup {
165
- chips: CardPolicySuggestedPolicyChip[];
166
- id: string;
167
- label: string;
166
+ export interface CardPolicyWizardPlan {
167
+ step5Review: CardPolicyStep5Review;
168
168
  }
169
- export interface CardPolicySuggestedPolicy {
170
- chipGroups: CardPolicySuggestedPolicyChipGroup[];
171
- id: string;
172
- isChecked: boolean;
173
- name: string;
169
+ /**
170
+ * Upload-mode metadata returned by the
171
+ * `policy-recommendation-from-upload` API alongside the wizard plan.
172
+ */
173
+ export interface CardPolicyUploadSource {
174
+ confidenceScore: number;
175
+ fileName: string;
176
+ lowConfidenceFields: string[];
174
177
  }
175
178
  /**
176
179
  * Initial data for the AI Card Policy interactive form. Discriminated on
177
- * `policyKind`:
180
+ * `policyKind`. All three modes consume the same `wizardPlan.step5Review`
181
+ * envelope; the wizard renders different surfaces per mode.
178
182
  *
179
- * - `review-only` (1-step): AI auto-applied default rules; user sees the
180
- * policy review only.
181
- * - `full-builder` (5-step): vendors categories cards → limits →
182
- * review.
183
- * - `doc-upload` (3-step): upload → cards → review. The user uploads a
184
- * policy document on Step 1; the cards micro-service extracts the rules
185
- * and the form is pre-filled via the `policyDocumentExtraction` slice
186
- * before the user lands on Step 2. `cards` is still required because
187
- * the user picks which cards the policy applies to in Step 2.
183
+ * - `auto` (1-step review): chip groups built directly from each draft
184
+ * policy. Submit omits `mode` + `spend_limits` + `required_receipt_threshold`.
185
+ * - `guided` (5-step wizard): Steps 1–4 seeded from `draftPolicies[0]`,
186
+ * Step 5 lists every draft. Submit includes all keys.
187
+ * - `upload` (3-step): upload → cards → review. `wizardPlan` is
188
+ * `undefined` until the upload-recommendation API succeeds, then
189
+ * populated alongside `source` metadata. Submit includes
190
+ * `spend_limits` + `required_receipt_threshold`; omits `mode`.
188
191
  */
189
192
  export type CardPolicyInitialData = {
190
- policyKind: 'review-only';
191
- suggestedPolicies: CardPolicySuggestedPolicy[];
193
+ policyKind: 'auto';
194
+ wizardPlan: CardPolicyWizardPlan;
192
195
  } | {
193
- cards: CardPolicyCardRowSeed[];
194
- categories: CardPolicyTagSeed[];
195
- defaultLimits: CardPolicyLimitRowSeed[];
196
- policyKind: 'full-builder';
197
- suggestedPolicies: CardPolicySuggestedPolicy[];
198
- vendors: CardPolicyTagSeed[];
196
+ policyKind: 'guided';
197
+ wizardPlan: CardPolicyWizardPlan;
199
198
  } | {
200
- cards: CardPolicyCardRowSeed[];
201
- policyKind: 'doc-upload';
202
- suggestedPolicies: CardPolicySuggestedPolicy[];
199
+ policyKind: 'upload';
200
+ source?: CardPolicyUploadSource;
201
+ wizardPlan?: CardPolicyWizardPlan;
203
202
  };
204
203
  /**
205
204
  * Visualization variant rendered as an interactive RHF form (in lieu of a
@@ -52,9 +52,12 @@ interface CardPolicyTemplateSpendLimitsPayload {
52
52
  transaction: number | null;
53
53
  }
54
54
  /**
55
- * Request body for `POST /cards/1.0/policy-templates`. Consumers pass the
56
- * already-camelCased payload to the action creator; the epic re-maps it to
57
- * the snake_case wire format before sending.
55
+ * Per-template request shape. `POST /cards/1.0/policy-templates` now
56
+ * accepts a bulk wrapper (`CreateCardPolicyTemplatesRequest`); this
57
+ * interface describes a single entry inside the `templates` array.
58
+ *
59
+ * `PUT /cards/1.0/policy-templates/{id}` (update) still consumes this
60
+ * shape directly — see `UpdateCardPolicyTemplateRequest` below.
58
61
  */
59
62
  export interface CreateCardPolicyTemplateRequest {
60
63
  allowedEntity: CardPolicyTemplateEntityList;
@@ -81,6 +84,24 @@ export interface CreateCardPolicyTemplateRequestBody {
81
84
  required_receipt_threshold: number | null;
82
85
  spend_limits: CardPolicyTemplateSpendLimitsPayload;
83
86
  }
87
+ /**
88
+ * Bulk request for `POST /cards/1.0/policy-templates`. The single-policy
89
+ * create flow wraps its one request in a length-1 `templates` array;
90
+ * future multi-policy submit (AI CFO review step) sends N entries in a
91
+ * single round-trip.
92
+ *
93
+ * `sourceChatSessionId` ties the submit back to an AI CFO chat session so
94
+ * the BE can attribute the created templates to it. Manual create-page
95
+ * callers omit it.
96
+ */
97
+ export interface CreateCardPolicyTemplatesRequest {
98
+ templates: CreateCardPolicyTemplateRequest[];
99
+ sourceChatSessionId?: string;
100
+ }
101
+ export interface CreateCardPolicyTemplatesRequestBody {
102
+ templates: CreateCardPolicyTemplateRequestBody[];
103
+ source_chat_session_id?: string;
104
+ }
84
105
  export interface CardPolicyTemplatePayload {
85
106
  allowed_entity: CardPolicyTemplateEntityListPayload;
86
107
  applied_cards: ID[];
@@ -99,11 +120,55 @@ export interface CardPolicyTemplatePayload {
99
120
  tenant_id: ID;
100
121
  updated_at: string;
101
122
  }
123
+ /**
124
+ * Per-template response envelope. Still returned by `PUT /policy-templates/{id}`,
125
+ * `GET /policy-templates/{id}`, and (as the row payload) the list endpoint.
126
+ * Also nested inside `CreateCardPolicyTemplatesResponseData.templates_created`.
127
+ */
102
128
  export interface CreateCardPolicyTemplateResponseData {
103
129
  cards_applied: number;
104
130
  template: CardPolicyTemplatePayload;
105
131
  }
106
132
  export type CreateCardPolicyTemplateResponse = ZeniAPIResponse<CreateCardPolicyTemplateResponseData>;
133
+ /**
134
+ * Per-item error returned by the bulk POST when one or more templates in
135
+ * the request fail validation / persistence. The exact field shape is
136
+ * **TBD** with the BE — `message` is the only guaranteed field today; the
137
+ * rest are best-effort placeholders that callers should not assume to be
138
+ * present.
139
+ *
140
+ * TODO: lock down the shape (`index`, `name`, `code`) once the BE
141
+ * documents it. See plan §"Open question to resolve with BE".
142
+ */
143
+ export interface BulkCreateCardPolicyTemplateErrorPayload {
144
+ message: string;
145
+ code?: string;
146
+ index?: number;
147
+ name?: string;
148
+ }
149
+ /**
150
+ * Wire shape returned by the bulk `POST /cards/1.0/policy-templates`.
151
+ * Partial success is allowed — `templates_created` holds the successfully
152
+ * created rows (each in the same envelope as the single endpoint), and
153
+ * `errors` holds per-item failures for the rest. `status.message` follows
154
+ * the "N of M templates created" convention.
155
+ */
156
+ export interface CreateCardPolicyTemplatesResponseData {
157
+ errors: BulkCreateCardPolicyTemplateErrorPayload[];
158
+ templates_created: CreateCardPolicyTemplateResponseData[];
159
+ }
160
+ export type CreateCardPolicyTemplatesResponse = ZeniAPIResponse<CreateCardPolicyTemplatesResponseData>;
161
+ /**
162
+ * Camel-cased mirror of `BulkCreateCardPolicyTemplateErrorPayload` stored
163
+ * on the create-card-policy view slice so the UI can render a partial-success
164
+ * banner without re-touching the wire types.
165
+ */
166
+ export interface BulkCreateCardPolicyTemplateError {
167
+ message: string;
168
+ code?: string;
169
+ index?: number;
170
+ name?: string;
171
+ }
107
172
  export type UpdateCardPolicyTemplateRequest = CreateCardPolicyTemplateRequest;
108
173
  export type UpdateCardPolicyTemplateRequestBody = CreateCardPolicyTemplateRequestBody;
109
174
  export type UpdateCardPolicyTemplateResponseData = CreateCardPolicyTemplateResponseData;
@@ -138,10 +203,26 @@ export declare const toCardPolicyStats: (payload: CardPolicyStatsPayload) => Car
138
203
  export declare const toCardPolicyTemplateList: (data: ListCardPolicyTemplatesResponseData) => CardPolicyTemplate[];
139
204
  export declare const toCardPolicyTemplate: (payload: CardPolicyTemplatePayload) => CardPolicyTemplate;
140
205
  /**
141
- * Re-shape a camelCase create request into the snake_case wire body.
142
- * Used by the epic at request time.
206
+ * Re-shape a single camelCase create request into the snake_case wire
207
+ * body. Used by the bulk-create mapper to build each entry of the
208
+ * `templates` array, and directly by the update epic (PUT) since the
209
+ * update endpoint still consumes a single object.
143
210
  */
144
211
  export declare const toCreateCardPolicyTemplateRequestBody: (request: CreateCardPolicyTemplateRequest) => CreateCardPolicyTemplateRequestBody;
212
+ /**
213
+ * Re-shape a bulk create request into the snake_case wire body for
214
+ * `POST /cards/1.0/policy-templates`. Maps each entry through
215
+ * `toCreateCardPolicyTemplateRequestBody` and only emits
216
+ * `source_chat_session_id` when the camelCase counterpart is defined,
217
+ * so manual-create callers don't ship a stray `undefined` over the wire.
218
+ */
219
+ export declare const toCreateCardPolicyTemplatesRequestBody: (request: CreateCardPolicyTemplatesRequest) => CreateCardPolicyTemplatesRequestBody;
220
+ /**
221
+ * Map a single bulk-response error payload to its camelCased view shape.
222
+ * Defensive against missing optional fields (see `BulkCreateCardPolicyTemplateErrorPayload`
223
+ * docstring — the BE contract isn't fully locked yet).
224
+ */
225
+ export declare const toBulkCreateCardPolicyTemplateError: (payload: BulkCreateCardPolicyTemplateErrorPayload) => BulkCreateCardPolicyTemplateError;
145
226
  export declare const toUpdateCardPolicyTemplateRequestBody: (request: CreateCardPolicyTemplateRequest) => CreateCardPolicyTemplateRequestBody;
146
227
  /**
147
228
  * Seed an edit-form draft (`CreateCardPolicyTemplateRequest`) from an
@@ -173,4 +254,24 @@ export interface PolicyDocumentExtractResponseData {
173
254
  }
174
255
  export type PolicyDocumentExtractResponse = ZeniAPIResponse<PolicyDocumentExtractResponseData>;
175
256
  export declare const toExtractedCardPolicyRules: (payload: ExtractedCardPolicyRulesPayload) => ExtractedCardPolicyRules;
257
+ /**
258
+ * Wire shape returned by `POST /cards/1.0/ai-cfo/policy-recommendation-from-upload`.
259
+ * Chained after `extractPolicyDocument` succeeds in the AI CFO `upload`
260
+ * flow: the FE forwards the just-returned `extracted_rules` and the BE
261
+ * synthesizes a wizard plan + upload source metadata.
262
+ *
263
+ * The response mirrors the same `mode`-discriminated envelope the
264
+ * controller API ships for `auto` / `guided`, but always with `mode:
265
+ * "upload"` and a populated `wizard_plan.step_5_review`. The FE merges
266
+ * the returned `wizard_plan` + `source` into the in-flight AI CFO
267
+ * answer's `CardPolicyInitialData` via
268
+ * `updateAiCfoAnswerCardPolicyWizardPlan`.
269
+ */
270
+ export interface CardPolicyRecommendationFromUploadResponseData {
271
+ mode: 'upload';
272
+ source: import('../aiCfo/aiCfoPayload').CardPolicyUploadSourcePayload;
273
+ tenant_id: string;
274
+ wizard_plan: import('../aiCfo/aiCfoPayload').CardPolicyWizardPlanPayload;
275
+ }
276
+ export type CardPolicyRecommendationFromUploadResponse = ZeniAPIResponse<CardPolicyRecommendationFromUploadResponseData>;
176
277
  export {};