From 01af626cdf903f6910da8750c542f6205b6504ff Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 23 Oct 2023 16:53:23 +0100 Subject: [PATCH] feat: space billing instruction implementation --- billing/data/consumer.js | 8 +- billing/data/customer.js | 2 +- billing/data/space-diff.js | 6 +- billing/data/space-snapshot.js | 6 +- billing/data/subscription.js | 8 +- billing/data/usage.js | 2 +- billing/lib/api.ts | 7 - billing/lib/customer-queue.js | 14 +- billing/lib/runner.js | 8 +- billing/lib/space-queue.js | 45 ++++-- billing/lib/ucan-stream.js | 10 +- billing/types.ts | 281 +-------------------------------- docs/billing.tldr | 256 +++++++++++++++--------------- 13 files changed, 187 insertions(+), 466 deletions(-) diff --git a/billing/data/consumer.js b/billing/data/consumer.js index 00be947c..4ca3e3d5 100644 --- a/billing/data/consumer.js +++ b/billing/data/consumer.js @@ -2,7 +2,7 @@ import * as Link from 'multiformats/link' import { DecodeFailure, asDID } from './lib.js' /** - * @type {import('../lib/api').Decoder} + * @type {import('../lib/api').Decoder} */ export const decode = input => { try { @@ -24,7 +24,7 @@ export const decode = input => { } /** - * @type {import('../lib/api').Encoder>} + * @type {import('../lib/api').Encoder>} */ export const encodeKey = input => ({ ok: { @@ -36,11 +36,11 @@ export const encodeKey = input => ({ /** Encoders/decoders for listings. */ export const lister = { /** - * @type {import('../lib/api').Encoder>} + * @type {import('../lib/api').Encoder>} */ encodeKey: input => ({ ok: { consumer: input.consumer } }), /** - * @type {import('../lib/api').Decoder>} + * @type {import('../lib/api').Decoder>} */ decode: input => { try { diff --git a/billing/data/customer.js b/billing/data/customer.js index e7456f81..52e4cae5 100644 --- a/billing/data/customer.js +++ b/billing/data/customer.js @@ -2,7 +2,7 @@ import * as Link from 'multiformats/link' import { DecodeFailure, asDIDMailto } from './lib.js' /** - * @type {import('../lib/api').Decoder} + * @type {import('../lib/api').Decoder} */ export const decode = input => { try { diff --git a/billing/data/space-diff.js b/billing/data/space-diff.js index c24fc437..e8379c23 100644 --- a/billing/data/space-diff.js +++ b/billing/data/space-diff.js @@ -31,7 +31,7 @@ export const validate = input => { } /** - * @type {import('../lib/api').Encoder>} + * @type {import('../lib/api').Encoder>} */ export const encode = input => { try { @@ -55,12 +55,12 @@ export const encode = input => { } /** - * @type {import('../lib/api').Encoder>} + * @type {import('../lib/api').Encoder>} */ export const encodeKey = input => ({ ok: { customer: input.customer } }) /** - * @type {import('../lib/api').Decoder} + * @type {import('../lib/api').Decoder} */ export const decode = input => { try { diff --git a/billing/data/space-snapshot.js b/billing/data/space-snapshot.js index 386a0a80..1dbadc61 100644 --- a/billing/data/space-snapshot.js +++ b/billing/data/space-snapshot.js @@ -23,7 +23,7 @@ export const validate = input => { return { ok: {} } } -/** @type {import('../lib/api').Encoder, 'provider'>>} */ +/** @type {import('../lib/api').Encoder, 'provider'>>} */ export const encode = input => { try { return { @@ -42,7 +42,7 @@ export const encode = input => { } /** - * @type {import('../lib/api').Encoder, 'provider'>>} + * @type {import('../lib/api').Encoder, 'provider'>>} */ export const encodeKey = input => ({ ok: { @@ -52,7 +52,7 @@ export const encodeKey = input => ({ }) /** - * @type {import('../lib/api').Decoder} + * @type {import('../lib/api').Decoder} */ export const decode = input => { if (typeof input.space !== 'string') { diff --git a/billing/data/subscription.js b/billing/data/subscription.js index 758df7ff..78b4e0d4 100644 --- a/billing/data/subscription.js +++ b/billing/data/subscription.js @@ -2,7 +2,7 @@ import * as Link from 'multiformats/link' import { DecodeFailure, isDIDMailto, isDID } from './lib.js' /** - * @type {import('../lib/api').Encoder>} + * @type {import('../lib/api').Encoder>} */ export const encodeKey = input => ({ ok: { @@ -12,7 +12,7 @@ export const encodeKey = input => ({ }) /** - * @type {import('../lib/api').Decoder} + * @type {import('../lib/api').Decoder} */ export const decode = input => { if (!isDIDMailto(input.customer)) { @@ -48,12 +48,12 @@ export const decode = input => { /** Encoders/decoders for listings. */ export const lister = { /** - * @type {import('../lib/api').Encoder>} + * @type {import('../lib/api').Encoder>} */ encodeKey: input => ({ ok: { customer: input.customer } }), /** - * @type {import('../lib/api').Decoder>} + * @type {import('../lib/api').Decoder>} */ decode: input => { if (!isDIDMailto(input.customer)) { diff --git a/billing/data/usage.js b/billing/data/usage.js index 69b95883..1503fdee 100644 --- a/billing/data/usage.js +++ b/billing/data/usage.js @@ -35,7 +35,7 @@ export const validate = input => { return { ok: {} } } -/** @type {import('../lib/api').Encoder>} */ +/** @type {import('../lib/api').Encoder>} */ export const encode = input => { try { return { diff --git a/billing/lib/api.ts b/billing/lib/api.ts index ee90b705..c260b07e 100644 --- a/billing/lib/api.ts +++ b/billing/lib/api.ts @@ -261,13 +261,6 @@ export interface RecordNotFound extends Failure { key: K } -export type InferStoreRecord = { - [Property in keyof T]: T[Property] extends Number ? T[Property] : string -} - -/** A record that is of suitable type to be put in storage. */ -export type StoreRecord = Record - export interface StorePutter { put: (rec: T) => Promise> } diff --git a/billing/lib/customer-queue.js b/billing/lib/customer-queue.js index 25bc5c4a..08964692 100644 --- a/billing/lib/customer-queue.js +++ b/billing/lib/customer-queue.js @@ -4,33 +4,29 @@ * subscriptionStore: import('./api').SubscriptionStore * consumerStore: import('./api').ConsumerStore * spaceBillingQueue: import('./api').SpaceBillingQueue - * }} stores + * }} ctx * @returns {Promise} */ -export const handleCustomerBillingInstruction = async (instruction, { - subscriptionStore, - consumerStore, - spaceBillingQueue -}) => { +export const handleCustomerBillingInstruction = async (instruction, ctx) => { console.log(`processing customer billing instruction for: ${instruction.customer}`) console.log(`period: ${instruction.from.toISOString()} - ${instruction.to.toISOString()}`) let cursor while (true) { - const subsList = await subscriptionStore.list({ customer: instruction.customer }, { cursor }) + const subsList = await ctx.subscriptionStore.list({ customer: instruction.customer }, { cursor }) if (subsList.error) return subsList // TODO: this is going to be inefficient for any client with many spaces // and may eventually cause billing to fail. for (const s of subsList.ok.results) { - const consumerGet = await consumerStore.get({ + const consumerGet = await ctx.consumerStore.get({ subscription: s.subscription, provider: s.provider }) if (consumerGet.error) return consumerGet console.log(`adding space billing instruction for: ${consumerGet.ok.consumer}`) - const queueAdd = await spaceBillingQueue.add({ + const queueAdd = await ctx.spaceBillingQueue.add({ ...instruction, provider: s.provider, space: consumerGet.ok.consumer diff --git a/billing/lib/runner.js b/billing/lib/runner.js index b051cc7b..bdc0d7d6 100644 --- a/billing/lib/runner.js +++ b/billing/lib/runner.js @@ -2,21 +2,21 @@ * @param {{ * customerStore: import('./api').CustomerStore * customerBillingQueue: import('./api').CustomerBillingQueue - * }} stores + * }} ctx * @returns {Promise} */ -export const handleCronTick = async ({ customerStore, customerBillingQueue }) => { +export const handleCronTick = async ctx => { const from = startOfMonth() const to = startOfNextMonth() let cursor while (true) { - const customerList = await customerStore.list({}, { cursor, size: 1000 }) + const customerList = await ctx.customerStore.list({}, { cursor, size: 1000 }) if (customerList.error) return customerList for (const c of customerList.ok.results) { console.log(`adding customer billing instruction for: ${c.customer}`) - const queueAdd = await customerBillingQueue.add({ + const queueAdd = await ctx.customerBillingQueue.add({ customer: c.customer, account: c.account, product: c.product, diff --git a/billing/lib/space-queue.js b/billing/lib/space-queue.js index 3e8cfca1..9f446486 100644 --- a/billing/lib/space-queue.js +++ b/billing/lib/space-queue.js @@ -4,18 +4,14 @@ * spaceDiffStore: import('./api').SpaceDiffStore * spaceSnapshotStore: import('./api').SpaceSnapshotStore * usageStore: import('./api').UsageStore - * }} stores + * }} ctx * @returns {Promise} */ -export const handleSpaceBillingInstruction = async (instruction, { - spaceDiffStore, - spaceSnapshotStore, - usageStore -}) => { +export const handleSpaceBillingInstruction = async (instruction, ctx) => { console.log(`processing space billing instruction for: ${instruction.customer}`) console.log(`period: ${instruction.from.toISOString()} - ${instruction.to.toISOString()}`) - const { ok: snap, error } = await spaceSnapshotStore.get({ + const { ok: snap, error } = await ctx.spaceSnapshotStore.get({ space: instruction.space, provider: instruction.provider, recordedAt: instruction.from @@ -24,27 +20,42 @@ export const handleSpaceBillingInstruction = async (instruction, { console.log(`space ${snap.space} is ${snap.size} bytes @ ${snap.recordedAt.toISOString()}`) - /** @type {import('./api').SpaceDiff[]} */ - const diffs = [] + let size = snap.size + let usage = size * (instruction.to.getTime() - instruction.from.getTime()) let cursor while (true) { - const { ok: listing, error: listErr } = await spaceDiffStore.listBetween( + const spaceDiffList = await ctx.spaceDiffStore.listBetween( { customer: instruction.customer }, instruction.from, instruction.to, { cursor, size: 1000 } ) - if (listErr) return { error: listErr } - for (const diff of listing.results) { + if (spaceDiffList.error) return spaceDiffList + for (const diff of spaceDiffList.ok.results) { if (diff.provider !== snap.provider) continue - diffs.push(diff) + console.log(`${diff.receiptAt.toISOString()}: ${diff.change} bytes`) + size += diff.change + usage += diff.change * (instruction.to.getTime() - diff.receiptAt.getTime()) } - if (!listing.cursor) break - cursor = listing.cursor + if (!spaceDiffList.ok.cursor) break + cursor = spaceDiffList.ok.cursor } - console.log(`${diffs.length} space updates`) + console.log(`space ${snap.space} is ${size} bytes @ ${instruction.to.toISOString()}`) + const snapPut = await ctx.spaceSnapshotStore.put({ + provider: instruction.provider, + space: instruction.space, + size, + recordedAt: instruction.to, + insertedAt: new Date() + }) + if (snapPut.error) return snapPut - return { ok: {} } + console.log(`${usage} bytes consumed over ${instruction.to.getTime() - instruction.from.getTime()} ms`) + return await ctx.usageStore.put({ + ...instruction, + usage, + insertedAt: new Date() + }) } diff --git a/billing/lib/ucan-stream.js b/billing/lib/ucan-stream.js index 77cce43b..66112dc3 100644 --- a/billing/lib/ucan-stream.js +++ b/billing/lib/ucan-stream.js @@ -6,10 +6,10 @@ import * as StoreCaps from '@web3-storage/capabilities/store' * spaceDiffStore: import('./api').SpaceDiffStore * subscriptionStore: import('./api').SubscriptionStore * consumerStore: import('./api').ConsumerStore - * }} stores + * }} ctx * @returns {Promise} */ -export const handleUcanStreamMessage = async (message, { spaceDiffStore, subscriptionStore, consumerStore }) => { +export const handleUcanStreamMessage = async (message, ctx) => { if (!isReceipt(message)) return { ok: {} } /** @type {number|undefined} */ @@ -27,16 +27,16 @@ export const handleUcanStreamMessage = async (message, { spaceDiffStore, subscri } const space = /** @type {import('@ucanto/interface').DID} */ (message.value.att[0].with) - const consumerList = await consumerStore.list({ consumer: space }) + const consumerList = await ctx.consumerStore.list({ consumer: space }) if (consumerList.error) return consumerList // There should only be one subscription per provider, but in theory you // could have multiple providers for the same consumer (space). for (const consumer of consumerList.ok.results) { - const subGet = await subscriptionStore.get({ provider: consumer.provider, subscription: consumer.subscription }) + const subGet = await ctx.subscriptionStore.get({ provider: consumer.provider, subscription: consumer.subscription }) if (subGet.error) return subGet - const spaceDiffPut = await spaceDiffStore.put({ + const spaceDiffPut = await ctx.spaceDiffStore.put({ customer: subGet.ok.customer, provider: consumer.provider, subscription: subGet.ok.subscription, diff --git a/billing/types.ts b/billing/types.ts index ee90b705..41f3dcdd 100644 --- a/billing/types.ts +++ b/billing/types.ts @@ -1,285 +1,6 @@ -import { DID, Link, LinkJSON, Result, Capabilities, Unit, Failure } from '@ucanto/interface' - -// Billing stores ///////////////////////////////////////////////////////////// - -/** Captures a size change that occurred in a space. */ -export interface SpaceDiff { - /** Storage provider for the space. */ - provider: DID - /** Space that changed size. */ - space: DID - /** Customer responsible for paying for the space at the time the size changed. */ - customer: DID<'mailto'> - /** Subscription in use when the size changed. */ - subscription: string - /** UCAN invocation that caused the size change. */ - cause: Link - /** Number of bytes that were added/removed from the space. */ - change: number - /** Time the receipt was issued by the service. */ - receiptAt: Date - /** Time the change was added to the database. */ - insertedAt: Date -} - -export interface SpaceDiffKey { customer: DID<'mailto'> } - -export interface SpaceDiffStore extends StorePutter { - listBetween: (key: SpaceDiffKey, from: Date, to: Date, options?: Pageable) => Promise, EncodeFailure|DecodeFailure|StoreOperationFailure>> -} - -/** Captures size of a space at a given point in time. */ -export interface SpaceSnapshot { - /** Storage provider this snapshot refers to. */ - provider: DID - /** Space this snapshot refers to. */ - space: DID - /** Total allocated size in bytes. */ - size: number - /** Time the total allocated size was recorded at. */ - recordedAt: Date - /** Time the snapshot was added to the database. */ - insertedAt: Date -} - -export interface SpaceSnapshotKey { provider: DID, space: DID, recordedAt: Date } - -export type SpaceSnapshotStore = - & StorePutter - & StoreGetter - -/** - * Captures information about a customer of the service that may need to be - * billed for storage usage. - */ -export interface Customer { - /** CID of the UCAN invocation that set it to the current value. */ - cause: Link, - /** DID of the user account e.g. `did:mailto:agent`. */ - customer: DID<'mailto'>, - /** - * Opaque identifier representing an account in the payment system - * e.g. Stripe customer ID (stripe:cus_9s6XKzkNRiz8i3) - */ - account: string, - /** Unique identifier of the product a.k.a tier. */ - product: string, - /** Time the record was added to the database. */ - insertedAt: Date, - /** Time the record was updated in the database. */ - updatedAt: Date -} - -export interface CustomerListOptions extends Pageable {} - -export type CustomerStore = StoreLister<{}, Customer> - -/** - * Captures storage usage by a given customer for a given space in the given - * time period. - */ -export interface Usage { - /** Customer DID (did:mailto:...). */ - customer: DID - /** - * Opaque identifier representing an account in the payment system. - * - * e.g. Stripe customer ID (stripe:cus_9s6XKzkNRiz8i3) - */ - account: string - /** Unique identifier of the product a.k.a tier. */ - product: string - /** Space DID (did:key:...). */ - space: DID - /** Usage in GB/month */ - usage: number - /** Time the usage period spans from (inclusive). */ - from: Date - /** Time the usage period spans to (exclusive). */ - to: Date - /** Time the record was added to the database. */ - insertedAt: Date -} - -export type UsageStore = StorePutter - -// Billing queues ///////////////////////////////////////////////////////////// - -/** - * Captures details about a customer that should be billed for a given period - * of usage. - */ -export interface CustomerBillingInstruction { - /** Customer DID (did:mailto:...). */ - customer: DID<'mailto'> - /** - * Opaque identifier representing an account in the payment system. - * - * e.g. Stripe customer ID (stripe:cus_9s6XKzkNRiz8i3) - */ - account: string - /** Unique identifier of the product a.k.a tier. */ - product: string - /** Time the billing period spans from (inlusive). */ - from: Date - /** Time the billing period spans to (exclusive). */ - to: Date -} - -export type CustomerBillingQueue = Queue - -/** - * Captures details about a space that should be billed for a given customer in - * the given period of usage. - */ -export interface SpaceBillingInstruction extends CustomerBillingInstruction { - /** Space DID (did:key:...). */ - space: DID - /** Storage provider for the space. */ - provider: DID -} - -export type SpaceBillingQueue = Queue - -// Upload API stores ////////////////////////////////////////////////////////// - -export interface Consumer { - consumer: DID - provider: DID - subscription: string - cause: Link - insertedAt: Date - updatedAt: Date -} - -export interface ConsumerKey { subscription: string, provider: DID } -export interface ConsumerListKey { consumer: DID } - -export type ConsumerStore = - & StoreGetter - & StoreLister> - -export interface Subscription { - customer: DID<'mailto'> - provider: DID - subscription: string - cause: Link - insertedAt: Date - updatedAt: Date -} - -export interface SubscriptionKey { provider: DID, subscription: string } -export interface SubscriptionListKey { customer: DID<'mailto'> } - -export type SubscriptionStore = - & StoreGetter - & StoreLister> - -// UCAN invocation //////////////////////////////////////////////////////////// - -// TODO: replace `UcanInvocation` type in `ucan-invocation/types.ts` with this? -export interface UcanMessage { - carCid: Link - invocationCid: Link - value: UcanMessageValue - ts: Date -} - -export interface UcanMessageValue { - att: C, - aud: DID, - iss?: DID, - prf?: Array> -} - -export interface UcanReceiptMessage< - C extends Capabilities = Capabilities, - R extends Result = Result -> extends UcanMessage { - type: 'receipt' - out: R -} - -export interface UcanWorkflowMessage extends UcanMessage { - type: 'workflow' -} - -export type UcanStreamMessage = UcanWorkflowMessage | UcanReceiptMessage - -// Utility //////////////////////////////////////////////////////////////////// - -export interface ListSuccess { - /** - * Opaque string specifying where to start retrival of the next page of - * results. - */ - cursor?: string - results: R[] -} - -export interface Pageable { - /** - * Opaque string specifying where to start retrival of the next page of - * results. - */ - cursor?: string - /** - * Maximum number of items to return. - */ - size?: number -} - -export type Encoder = (input: I) => Result - -export type Decoder = (input: I) => Result - -export type Validator = (input: T) => Result - -export interface InvalidInput extends Failure { - name: 'InvalidInput' - field?: string -} - -export interface EncodeFailure extends Failure { - name: 'EncodeFailure' -} - -export interface DecodeFailure extends Failure { - name: 'DecodeFailure' -} - -export interface QueueOperationFailure extends Failure { - name: 'QueueOperationFailure' -} - -export interface StoreOperationFailure extends Failure { - name: 'StoreOperationFailure' -} - -export interface RecordNotFound extends Failure { - name: 'RecordNotFound' - key: K -} - export type InferStoreRecord = { [Property in keyof T]: T[Property] extends Number ? T[Property] : string } -/** A record that is of suitable type to be put in storage. */ +/** A record that is of suitable type to be put in DynamoDB. */ export type StoreRecord = Record - -export interface StorePutter { - put: (rec: T) => Promise> -} - -export interface StoreGetter { - get: (key: K) => Promise|DecodeFailure|StoreOperationFailure>> -} - -export interface StoreLister { - list: (key: K, options?: Pageable) => Promise, EncodeFailure|DecodeFailure|StoreOperationFailure>> -} - -export interface Queue { - add: (message: T) => Promise> -} diff --git a/docs/billing.tldr b/docs/billing.tldr index ff20ed04..ce68ad7b 100644 --- a/docs/billing.tldr +++ b/docs/billing.tldr @@ -96,8 +96,8 @@ "screenBounds": { "x": 0, "y": 0, - "w": 1627, - "h": 941 + "w": 1208, + "h": 562 }, "zoomBrush": null, "isGridMode": false, @@ -198,11 +198,39 @@ { "id": "pointer:pointer", "typeName": "pointer", - "x": 1565.2773893363276, - "y": 1316.7380511220674, - "lastActivityTimestamp": 1697822244520, + "x": 1554.442207266402, + "y": 1183.9720113666547, + "lastActivityTimestamp": 1698075297250, "meta": {} }, + { + "meta": {}, + "id": "page:Rrzf2lpl3e1GzXW65i7-e", + "name": "Billing Infra v2", + "index": "a2", + "typeName": "page" + }, + { + "x": -716.2203406842682, + "y": -842.6432788371222, + "z": 1.0337358574683464, + "meta": {}, + "id": "camera:page:Rrzf2lpl3e1GzXW65i7-e", + "typeName": "camera" + }, + { + "editingShapeId": null, + "croppingShapeId": null, + "selectedShapeIds": [], + "hoveredShapeId": null, + "erasingShapeIds": [], + "hintingShapeIds": [], + "focusedGroupId": null, + "meta": {}, + "id": "instance_page_state:page:Rrzf2lpl3e1GzXW65i7-e", + "pageId": "page:Rrzf2lpl3e1GzXW65i7-e", + "typeName": "instance_page_state" + }, { "x": 30, "y": 0, @@ -1768,7 +1796,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:FMrKntvKsSjiJJSDClojS", "type": "arrow", "parentId": "page:SqRFlwa4qqn_hzEPWcNI-", "index": "b02", @@ -1794,6 +1821,7 @@ "text": "", "font": "draw" }, + "id": "shape:FMrKntvKsSjiJJSDClojS", "typeName": "shape" }, { @@ -1803,7 +1831,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:O-ql4ecTUcszUzVtRfnTR", "type": "text", "props": { "color": "black", @@ -1817,6 +1844,7 @@ }, "parentId": "page:SqRFlwa4qqn_hzEPWcNI-", "index": "b03", + "id": "shape:O-ql4ecTUcszUzVtRfnTR", "typeName": "shape" }, { @@ -1826,7 +1854,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:XeJ9SBMhRyta0zSHpZeGy", "type": "arrow", "parentId": "page:SqRFlwa4qqn_hzEPWcNI-", "index": "b04", @@ -1852,6 +1879,7 @@ "text": "", "font": "draw" }, + "id": "shape:XeJ9SBMhRyta0zSHpZeGy", "typeName": "shape" }, { @@ -1861,7 +1889,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:XEfn6hY0l37s_zRZ8IzBm", "type": "text", "props": { "color": "black", @@ -1875,6 +1902,7 @@ }, "parentId": "page:SqRFlwa4qqn_hzEPWcNI-", "index": "b05", + "id": "shape:XEfn6hY0l37s_zRZ8IzBm", "typeName": "shape" }, { @@ -1884,7 +1912,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:zAJIo7FXS2HIvRQaDbYAy", "type": "text", "props": { "color": "black", @@ -1898,6 +1925,7 @@ }, "parentId": "page:SqRFlwa4qqn_hzEPWcNI-", "index": "b06", + "id": "shape:zAJIo7FXS2HIvRQaDbYAy", "typeName": "shape" }, { @@ -1907,7 +1935,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:1L9OCjMmVHM5SngbXm0iB", "type": "text", "props": { "color": "black", @@ -1921,6 +1948,7 @@ }, "parentId": "page:SqRFlwa4qqn_hzEPWcNI-", "index": "b07", + "id": "shape:1L9OCjMmVHM5SngbXm0iB", "typeName": "shape" }, { @@ -1930,7 +1958,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:-A6TXRp4JaVo0FLDh-haJ", "type": "arrow", "parentId": "page:SqRFlwa4qqn_hzEPWcNI-", "index": "b08", @@ -1956,6 +1983,7 @@ "text": "", "font": "draw" }, + "id": "shape:-A6TXRp4JaVo0FLDh-haJ", "typeName": "shape" }, { @@ -1965,7 +1993,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:jtRlomuR5n8xCnfGlIzPJ", "type": "text", "props": { "color": "black", @@ -1979,36 +2006,9 @@ }, "parentId": "page:SqRFlwa4qqn_hzEPWcNI-", "index": "b09", + "id": "shape:jtRlomuR5n8xCnfGlIzPJ", "typeName": "shape" }, - { - "meta": {}, - "id": "page:Rrzf2lpl3e1GzXW65i7-e", - "name": "Billing Infra v2", - "index": "a2", - "typeName": "page" - }, - { - "x": 336.70651024811104, - "y": -180.17379887371334, - "z": 0.6278929005450193, - "meta": {}, - "id": "camera:page:Rrzf2lpl3e1GzXW65i7-e", - "typeName": "camera" - }, - { - "editingShapeId": null, - "croppingShapeId": null, - "selectedShapeIds": [], - "hoveredShapeId": null, - "erasingShapeIds": [], - "hintingShapeIds": [], - "focusedGroupId": null, - "meta": {}, - "id": "instance_page_state:page:Rrzf2lpl3e1GzXW65i7-e", - "pageId": "page:Rrzf2lpl3e1GzXW65i7-e", - "typeName": "instance_page_state" - }, { "x": 446.9889371286745, "y": -286.6653378309586, @@ -2016,7 +2016,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:P5SAMAo1F03KZP4z8u6Bb", "type": "geo", "props": { "w": 96.984375, @@ -2036,6 +2035,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "a1", + "id": "shape:P5SAMAo1F03KZP4z8u6Bb", "typeName": "shape" }, { @@ -2045,11 +2045,11 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:dzfIku8IXrVyAtbR4qOwH", "type": "group", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "a2", "props": {}, + "id": "shape:dzfIku8IXrVyAtbR4qOwH", "typeName": "shape" }, { @@ -2059,7 +2059,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:isT8vZhDfzbWWknw4hUg0", "type": "geo", "props": { "w": 25.359375, @@ -2079,6 +2078,7 @@ }, "parentId": "shape:dzfIku8IXrVyAtbR4qOwH", "index": "a1", + "id": "shape:isT8vZhDfzbWWknw4hUg0", "typeName": "shape" }, { @@ -2088,7 +2088,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:cjT3qyfXJj263DD_9-GW1", "type": "geo", "props": { "w": 25.359375, @@ -2108,6 +2107,7 @@ }, "parentId": "shape:dzfIku8IXrVyAtbR4qOwH", "index": "a2", + "id": "shape:cjT3qyfXJj263DD_9-GW1", "typeName": "shape" }, { @@ -2117,7 +2117,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:t53d2-ppEJn71TupbcOvM", "type": "geo", "props": { "w": 25.359375, @@ -2137,6 +2136,7 @@ }, "parentId": "shape:dzfIku8IXrVyAtbR4qOwH", "index": "a3", + "id": "shape:t53d2-ppEJn71TupbcOvM", "typeName": "shape" }, { @@ -2146,7 +2146,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:4KmdH6u__SjpGloNbyI4V", "type": "geo", "props": { "w": 25.359375, @@ -2166,6 +2165,7 @@ }, "parentId": "shape:dzfIku8IXrVyAtbR4qOwH", "index": "a4", + "id": "shape:4KmdH6u__SjpGloNbyI4V", "typeName": "shape" }, { @@ -2175,7 +2175,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:4vY_8b3Ms-ui78EZoTSrL", "type": "geo", "props": { "w": 25.359375, @@ -2195,6 +2194,7 @@ }, "parentId": "shape:dzfIku8IXrVyAtbR4qOwH", "index": "a5", + "id": "shape:4vY_8b3Ms-ui78EZoTSrL", "typeName": "shape" }, { @@ -2204,7 +2204,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:o1J2jZEUCBCZuQzt4hE5r", "type": "geo", "props": { "w": 46.188021535170094, @@ -2224,6 +2223,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "a3", + "id": "shape:o1J2jZEUCBCZuQzt4hE5r", "typeName": "shape" }, { @@ -2233,7 +2233,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:7Ilt_4XiLYEd4V1zcD6N9", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "a4", @@ -2267,6 +2266,7 @@ "text": "", "font": "draw" }, + "id": "shape:7Ilt_4XiLYEd4V1zcD6N9", "typeName": "shape" }, { @@ -2276,7 +2276,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:hXzrRD2CCEfNyrv3aVF95", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "a5", @@ -2310,6 +2309,7 @@ "text": "", "font": "draw" }, + "id": "shape:hXzrRD2CCEfNyrv3aVF95", "typeName": "shape" }, { @@ -2319,7 +2319,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:dKAYL28hsz-E0VP5BaFVe", "type": "geo", "props": { "w": 300, @@ -2339,6 +2338,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "a6", + "id": "shape:dKAYL28hsz-E0VP5BaFVe", "typeName": "shape" }, { @@ -2348,7 +2348,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:RG3fEplWZl6u9qtefX_dc", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "a7", @@ -2382,6 +2381,7 @@ "text": "", "font": "draw" }, + "id": "shape:RG3fEplWZl6u9qtefX_dc", "typeName": "shape" }, { @@ -2391,7 +2391,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:F_InulYsiW7U3E3FmLXRs", "type": "text", "props": { "color": "black", @@ -2405,6 +2404,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "a8", + "id": "shape:F_InulYsiW7U3E3FmLXRs", "typeName": "shape" }, { @@ -2414,7 +2414,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:40MayB1plPHK2fqfst4Px", "type": "text", "props": { "color": "black", @@ -2428,6 +2427,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "a9", + "id": "shape:40MayB1plPHK2fqfst4Px", "typeName": "shape" }, { @@ -2437,7 +2437,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:X-9RyMJb-ZsFyX5KfnrDq", "type": "text", "props": { "color": "black", @@ -2451,6 +2450,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aA", + "id": "shape:X-9RyMJb-ZsFyX5KfnrDq", "typeName": "shape" }, { @@ -2460,7 +2460,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:2Ov2m6W9Mc6eViZseHT7v", "type": "geo", "props": { "w": 46.188021535170094, @@ -2480,6 +2479,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aB", + "id": "shape:2Ov2m6W9Mc6eViZseHT7v", "typeName": "shape" }, { @@ -2489,7 +2489,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:WXfYhlnbVIkPsSbH8g_Nz", "type": "text", "props": { "color": "black", @@ -2503,6 +2502,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aD", + "id": "shape:WXfYhlnbVIkPsSbH8g_Nz", "typeName": "shape" }, { @@ -2512,7 +2512,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:Z3AO4wx2-61ILzhA3CbMY", "type": "geo", "props": { "w": 310, @@ -2532,6 +2531,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aE", + "id": "shape:Z3AO4wx2-61ILzhA3CbMY", "typeName": "shape" }, { @@ -2541,7 +2541,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:1Gah4REdExE2mlZlWgTzi", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aF", @@ -2575,6 +2574,7 @@ "text": "", "font": "draw" }, + "id": "shape:1Gah4REdExE2mlZlWgTzi", "typeName": "shape" }, { @@ -2584,7 +2584,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:V_Q8PMp-tsXNT2KgFTqaO", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aG", @@ -2618,6 +2617,7 @@ "text": "", "font": "draw" }, + "id": "shape:V_Q8PMp-tsXNT2KgFTqaO", "typeName": "shape" }, { @@ -2627,7 +2627,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:HvdoX-VTV7951vgPSkPHd", "type": "text", "props": { "color": "black", @@ -2641,6 +2640,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aH", + "id": "shape:HvdoX-VTV7951vgPSkPHd", "typeName": "shape" }, { @@ -2650,7 +2650,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:Z2kZK6imqe1rBz03iEh_a", "type": "geo", "props": { "w": 310, @@ -2670,6 +2669,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aI", + "id": "shape:Z2kZK6imqe1rBz03iEh_a", "typeName": "shape" }, { @@ -2679,7 +2679,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:EMER6uTTOreehjNf7BsSi", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aJ", @@ -2713,6 +2712,7 @@ "text": "", "font": "draw" }, + "id": "shape:EMER6uTTOreehjNf7BsSi", "typeName": "shape" }, { @@ -2722,7 +2722,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:vFSpR8EIJ939U8iKfMW88", "type": "text", "props": { "color": "grey", @@ -2736,6 +2735,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aK", + "id": "shape:vFSpR8EIJ939U8iKfMW88", "typeName": "shape" }, { @@ -2745,7 +2745,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:OAxtnBUS2259eTFWOHSmh", "type": "text", "props": { "color": "grey", @@ -2759,6 +2758,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aL", + "id": "shape:OAxtnBUS2259eTFWOHSmh", "typeName": "shape" }, { @@ -2768,7 +2768,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:rffsD5WoSQ9xbmgU_4KJ1", "type": "text", "props": { "color": "black", @@ -2782,6 +2781,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aM", + "id": "shape:rffsD5WoSQ9xbmgU_4KJ1", "typeName": "shape" }, { @@ -2791,7 +2791,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:oskJiebNLL-Ae5KABf5Ly", "type": "text", "props": { "color": "grey", @@ -2805,6 +2804,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aN", + "id": "shape:oskJiebNLL-Ae5KABf5Ly", "typeName": "shape" }, { @@ -2814,7 +2814,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:Q8DFMz1vne2aIffOiwDeA", "type": "text", "props": { "color": "black", @@ -2828,6 +2827,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aO", + "id": "shape:Q8DFMz1vne2aIffOiwDeA", "typeName": "shape" }, { @@ -2837,11 +2837,11 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:Hbm34VLCWKeahFF7EPMda", "type": "group", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aP", "props": {}, + "id": "shape:Hbm34VLCWKeahFF7EPMda", "typeName": "shape" }, { @@ -2851,7 +2851,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:GsG-eZG_NOAXXHRIgbGZ3", "type": "geo", "props": { "w": 233.13082686769366, @@ -2871,6 +2870,7 @@ }, "parentId": "shape:Hbm34VLCWKeahFF7EPMda", "index": "a1", + "id": "shape:GsG-eZG_NOAXXHRIgbGZ3", "typeName": "shape" }, { @@ -2880,7 +2880,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:DEFMxmcOYRPfuxpkpMZYr", "type": "image", "props": { "w": 167.8081957513436, @@ -2892,6 +2891,7 @@ }, "parentId": "shape:Hbm34VLCWKeahFF7EPMda", "index": "a2", + "id": "shape:DEFMxmcOYRPfuxpkpMZYr", "typeName": "shape" }, { @@ -2901,7 +2901,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:Wu474jafUm8AYvbPwop41", "type": "text", "props": { "color": "grey", @@ -2915,6 +2914,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aQ", + "id": "shape:Wu474jafUm8AYvbPwop41", "typeName": "shape" }, { @@ -2924,7 +2924,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:5P7KhVFy-60fMgfkOCBSG", "type": "text", "props": { "color": "black", @@ -2938,6 +2937,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aR", + "id": "shape:5P7KhVFy-60fMgfkOCBSG", "typeName": "shape" }, { @@ -2947,7 +2947,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:gDnK4GHqJ4k6rVs2brOti", "type": "text", "props": { "color": "black", @@ -2961,6 +2960,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aS", + "id": "shape:gDnK4GHqJ4k6rVs2brOti", "typeName": "shape" }, { @@ -2970,7 +2970,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:Lz_tUAZSBnP4qyVw65eEA", "type": "text", "props": { "color": "black", @@ -2984,6 +2983,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aT", + "id": "shape:Lz_tUAZSBnP4qyVw65eEA", "typeName": "shape" }, { @@ -2993,7 +2993,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:si96h47JQ4ZeCtXsqxMdB", "type": "text", "props": { "color": "black", @@ -3007,6 +3006,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aU", + "id": "shape:si96h47JQ4ZeCtXsqxMdB", "typeName": "shape" }, { @@ -3016,7 +3016,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:zs6bPBD8FQnGXkm9m7Ez1", "type": "geo", "props": { "w": 238.14801854788288, @@ -3036,6 +3035,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aV", + "id": "shape:zs6bPBD8FQnGXkm9m7Ez1", "typeName": "shape" }, { @@ -3045,7 +3045,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:Fnv7Prl-Q3IRUOvIrmOAg", "type": "geo", "props": { "w": 46.188021535170094, @@ -3065,6 +3064,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aW", + "id": "shape:Fnv7Prl-Q3IRUOvIrmOAg", "typeName": "shape" }, { @@ -3074,7 +3074,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:FE7MGGsCEDL8K2E6GnV45", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aX", @@ -3108,6 +3107,7 @@ "text": "", "font": "draw" }, + "id": "shape:FE7MGGsCEDL8K2E6GnV45", "typeName": "shape" }, { @@ -3117,7 +3117,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:ZO8_pM89XtmdEeP1FGFtR", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aY", @@ -3151,6 +3150,7 @@ "text": "", "font": "draw" }, + "id": "shape:ZO8_pM89XtmdEeP1FGFtR", "typeName": "shape" }, { @@ -3160,7 +3160,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:NcCjfT0whoihf1H-eP908", "type": "text", "props": { "color": "black", @@ -3174,6 +3173,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aZ", + "id": "shape:NcCjfT0whoihf1H-eP908", "typeName": "shape" }, { @@ -3183,7 +3183,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:5mf_tmzTckZbSE9G_B9Lp", "type": "text", "props": { "color": "black", @@ -3197,6 +3196,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aa", + "id": "shape:5mf_tmzTckZbSE9G_B9Lp", "typeName": "shape" }, { @@ -3206,7 +3206,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:MS1QyGWEQneMFgZvSsIM0", "type": "text", "props": { "color": "black", @@ -3220,6 +3219,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ab", + "id": "shape:MS1QyGWEQneMFgZvSsIM0", "typeName": "shape" }, { @@ -3229,7 +3229,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:tQYRnfYAxw85TvS2QeSb0", "type": "geo", "props": { "w": 310, @@ -3249,6 +3248,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ac", + "id": "shape:tQYRnfYAxw85TvS2QeSb0", "typeName": "shape" }, { @@ -3258,7 +3258,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:PsrDDkfLBVl75CIhnWSvC", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ad", @@ -3292,6 +3291,7 @@ "text": "", "font": "draw" }, + "id": "shape:PsrDDkfLBVl75CIhnWSvC", "typeName": "shape" }, { @@ -3301,13 +3301,12 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:oyAAsbYMF1kGIIWcIgrmQ", "type": "text", "props": { "color": "grey", "size": "s", - "w": 394.75, - "text": "customer: DID string (pk)\naccount: Stripe ID\nproduct: plan string\nspace: DID string\nprovider: DID string\nusage: GB/month\nperiod: from ISO string - to ISO string (sk)\ninsertedAt: ISO string", + "w": 234.484375, + "text": "customer: DID string (pk)\naccount: Stripe ID\nproduct: plan string\nspace: DID string\nprovider: DID string\nusage: GB/month\nfrom: ISO string (sk)\nto: ISO string\ninsertedAt: ISO string", "font": "draw", "align": "start", "autoSize": true, @@ -3315,6 +3314,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ae", + "id": "shape:oyAAsbYMF1kGIIWcIgrmQ", "typeName": "shape" }, { @@ -3324,7 +3324,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:kBTR7rveFAN82M9y1-b1p", "type": "geo", "props": { "w": 46.188021535170094, @@ -3344,6 +3343,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "af", + "id": "shape:kBTR7rveFAN82M9y1-b1p", "typeName": "shape" }, { @@ -3353,7 +3353,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:H0kLtoESIvz3doQAF0uWw", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ag", @@ -3387,6 +3386,7 @@ "text": "", "font": "draw" }, + "id": "shape:H0kLtoESIvz3doQAF0uWw", "typeName": "shape" }, { @@ -3396,7 +3396,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:1PtRMSztuPIFOAns1qhmc", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ah", @@ -3430,6 +3429,7 @@ "text": "", "font": "draw" }, + "id": "shape:1PtRMSztuPIFOAns1qhmc", "typeName": "shape" }, { @@ -3439,7 +3439,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:fIoMbL2QnCm3hsa-xRJz2", "type": "text", "props": { "color": "black", @@ -3453,6 +3452,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ai", + "id": "shape:fIoMbL2QnCm3hsa-xRJz2", "typeName": "shape" }, { @@ -3462,7 +3462,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:XEoEOA_S7ZNij_1rKXXiL", "type": "text", "props": { "color": "black", @@ -3476,6 +3475,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aj", + "id": "shape:XEoEOA_S7ZNij_1rKXXiL", "typeName": "shape" }, { @@ -3485,7 +3485,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:YSqtRJxsmzYlNR305Oz0j", "type": "text", "props": { "color": "black", @@ -3499,6 +3498,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ak", + "id": "shape:YSqtRJxsmzYlNR305Oz0j", "typeName": "shape" }, { @@ -3508,7 +3508,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:fnlWSn9sKYJMCBLl8_1ni", "type": "text", "props": { "color": "black", @@ -3522,6 +3521,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "al", + "id": "shape:fnlWSn9sKYJMCBLl8_1ni", "typeName": "shape" }, { @@ -3531,7 +3531,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:PRY1gg8q7OmwxuKT7ijEt", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "am", @@ -3557,6 +3556,7 @@ "text": "", "font": "draw" }, + "id": "shape:PRY1gg8q7OmwxuKT7ijEt", "typeName": "shape" }, { @@ -3566,7 +3566,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:gEgPnCORnqRDZxxjos4Ds", "type": "text", "props": { "color": "black", @@ -3580,6 +3579,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "an", + "id": "shape:gEgPnCORnqRDZxxjos4Ds", "typeName": "shape" }, { @@ -3589,7 +3589,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:XiqJL3BGSsfta2QuTSu5A", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ao", @@ -3615,6 +3614,7 @@ "text": "", "font": "draw" }, + "id": "shape:XiqJL3BGSsfta2QuTSu5A", "typeName": "shape" }, { @@ -3624,7 +3624,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:CJxgzR4oWEEUcrNXjT13r", "type": "text", "props": { "color": "black", @@ -3638,6 +3637,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ap", + "id": "shape:CJxgzR4oWEEUcrNXjT13r", "typeName": "shape" }, { @@ -3647,7 +3647,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:WHihHhF-EEIyULtvoz-5Z", "type": "text", "props": { "color": "black", @@ -3661,6 +3660,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aq", + "id": "shape:WHihHhF-EEIyULtvoz-5Z", "typeName": "shape" }, { @@ -3670,7 +3670,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:IKwWNSFQNXBsij55bg2yo", "type": "text", "props": { "color": "black", @@ -3684,6 +3683,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ar", + "id": "shape:IKwWNSFQNXBsij55bg2yo", "typeName": "shape" }, { @@ -3693,7 +3693,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:I5EFe3kj-lmZpTTue3O0G", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "as", @@ -3719,6 +3718,7 @@ "text": "", "font": "draw" }, + "id": "shape:I5EFe3kj-lmZpTTue3O0G", "typeName": "shape" }, { @@ -3728,7 +3728,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:GfMrR-zEsXTjJNNYEKkEz", "type": "text", "props": { "color": "black", @@ -3742,6 +3741,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "at", + "id": "shape:GfMrR-zEsXTjJNNYEKkEz", "typeName": "shape" }, { @@ -3751,7 +3751,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:jH_iohlQG86dkcjXfjV4E", "type": "geo", "props": { "w": 46.188021535170094, @@ -3771,6 +3770,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "au", + "id": "shape:jH_iohlQG86dkcjXfjV4E", "typeName": "shape" }, { @@ -3780,7 +3780,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:9Tq6geb54LMa1j9idqFLp", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "av", @@ -3814,6 +3813,7 @@ "text": "", "font": "draw" }, + "id": "shape:9Tq6geb54LMa1j9idqFLp", "typeName": "shape" }, { @@ -3823,7 +3823,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:hxy7sTyGVxHpOc70joxBL", "type": "text", "props": { "color": "black", @@ -3837,6 +3836,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "aw", + "id": "shape:hxy7sTyGVxHpOc70joxBL", "typeName": "shape" }, { @@ -3846,7 +3846,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:H1lPFsuGIKRm6MdXIY7l8", "type": "geo", "props": { "w": 300, @@ -3866,6 +3865,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ax", + "id": "shape:H1lPFsuGIKRm6MdXIY7l8", "typeName": "shape" }, { @@ -3875,7 +3875,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:pN2hxuExfNiOM9FJWOCto", "type": "geo", "props": { "w": 300, @@ -3895,6 +3894,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "ay", + "id": "shape:pN2hxuExfNiOM9FJWOCto", "typeName": "shape" }, { @@ -3904,7 +3904,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:26e376oFkSPuGfie9v0LC", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "axV", @@ -3938,6 +3937,7 @@ "text": "", "font": "draw" }, + "id": "shape:26e376oFkSPuGfie9v0LC", "typeName": "shape" }, { @@ -3947,7 +3947,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:UXEz3jIWR2PFKVMZOZ_0F", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "az", @@ -3981,6 +3980,7 @@ "text": "", "font": "draw" }, + "id": "shape:UXEz3jIWR2PFKVMZOZ_0F", "typeName": "shape" }, { @@ -3990,11 +3990,11 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:hXw6eW4SnTRxhhnlT7tmE", "type": "group", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b00", "props": {}, + "id": "shape:hXw6eW4SnTRxhhnlT7tmE", "typeName": "shape" }, { @@ -4004,7 +4004,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:CJbHOP_mdIa2qmUPlN_-m", "type": "geo", "props": { "w": 25.359375, @@ -4024,6 +4023,7 @@ }, "parentId": "shape:hXw6eW4SnTRxhhnlT7tmE", "index": "a1", + "id": "shape:CJbHOP_mdIa2qmUPlN_-m", "typeName": "shape" }, { @@ -4033,7 +4033,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:Xtb1z8l9u9lsHYPruO-ic", "type": "geo", "props": { "w": 25.359375, @@ -4053,6 +4052,7 @@ }, "parentId": "shape:hXw6eW4SnTRxhhnlT7tmE", "index": "a2", + "id": "shape:Xtb1z8l9u9lsHYPruO-ic", "typeName": "shape" }, { @@ -4062,7 +4062,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:9HxelGyNkTrdDhn3H01Jh", "type": "geo", "props": { "w": 25.359375, @@ -4082,6 +4081,7 @@ }, "parentId": "shape:hXw6eW4SnTRxhhnlT7tmE", "index": "a3", + "id": "shape:9HxelGyNkTrdDhn3H01Jh", "typeName": "shape" }, { @@ -4091,7 +4091,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:J-DOsDm-jtmHRg5Ce-R43", "type": "geo", "props": { "w": 25.359375, @@ -4111,6 +4110,7 @@ }, "parentId": "shape:hXw6eW4SnTRxhhnlT7tmE", "index": "a4", + "id": "shape:J-DOsDm-jtmHRg5Ce-R43", "typeName": "shape" }, { @@ -4120,7 +4120,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:MMioFNJ0uK1Rf4vaYHdJQ", "type": "geo", "props": { "w": 25.359375, @@ -4140,6 +4139,7 @@ }, "parentId": "shape:hXw6eW4SnTRxhhnlT7tmE", "index": "a5", + "id": "shape:MMioFNJ0uK1Rf4vaYHdJQ", "typeName": "shape" }, { @@ -4149,7 +4149,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:Dyfl13nfA1oUR7UBKf4wH", "type": "text", "props": { "color": "black", @@ -4163,6 +4162,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b01", + "id": "shape:Dyfl13nfA1oUR7UBKf4wH", "typeName": "shape" }, { @@ -4172,7 +4172,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:v4fKxjLyrbBTEm1gi0l3f", "type": "text", "props": { "color": "grey", @@ -4186,6 +4185,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b02", + "id": "shape:v4fKxjLyrbBTEm1gi0l3f", "typeName": "shape" }, { @@ -4195,7 +4195,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:clLrNbftFiGYTgfnbyrxz", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b00V", @@ -4225,6 +4224,7 @@ "text": "", "font": "draw" }, + "id": "shape:clLrNbftFiGYTgfnbyrxz", "typeName": "shape" }, { @@ -4234,7 +4234,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:zzAijOPtmI9vULkZsQPw0", "type": "arrow", "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b00G", @@ -4268,6 +4267,7 @@ "text": "", "font": "draw" }, + "id": "shape:zzAijOPtmI9vULkZsQPw0", "typeName": "shape" }, { @@ -4277,7 +4277,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:iJQxJ4G-JFg1eaDykEPMn", "type": "text", "props": { "color": "black", @@ -4291,6 +4290,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b04", + "id": "shape:iJQxJ4G-JFg1eaDykEPMn", "typeName": "shape" }, { @@ -4300,7 +4300,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:mPAYs9qrAahK-rIG64-_E", "type": "geo", "props": { "w": 38.27393595730382, @@ -4320,6 +4319,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b05", + "id": "shape:mPAYs9qrAahK-rIG64-_E", "typeName": "shape" }, { @@ -4329,7 +4329,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:4aJU2W15Qr1P7LE9PUEVU", "type": "geo", "props": { "w": 45.01695289203633, @@ -4349,6 +4348,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b06", + "id": "shape:4aJU2W15Qr1P7LE9PUEVU", "typeName": "shape" }, { @@ -4358,7 +4358,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:ENAvvg6mxySkYw3Ww5jMQ", "type": "text", "props": { "color": "black", @@ -4372,6 +4371,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b07", + "id": "shape:ENAvvg6mxySkYw3Ww5jMQ", "typeName": "shape" }, { @@ -4381,7 +4381,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:IlUz8nPpZURQ1zi2AfyTJ", "type": "text", "props": { "color": "black", @@ -4395,6 +4394,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b08", + "id": "shape:IlUz8nPpZURQ1zi2AfyTJ", "typeName": "shape" }, { @@ -4404,7 +4404,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:WpM7nIUkJqsmEsenR_T0r", "type": "text", "props": { "color": "black", @@ -4418,6 +4417,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b09", + "id": "shape:WpM7nIUkJqsmEsenR_T0r", "typeName": "shape" }, { @@ -4427,7 +4427,6 @@ "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:y-IvFXS5sYoIwOh6dJPvV", "type": "text", "props": { "color": "black", @@ -4441,6 +4440,7 @@ }, "parentId": "page:Rrzf2lpl3e1GzXW65i7-e", "index": "b0A", + "id": "shape:y-IvFXS5sYoIwOh6dJPvV", "typeName": "shape" }, {