@contractspec/lib.analytics 1.57.0 → 1.58.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 (89) hide show
  1. package/dist/browser/churn/index.js +77 -0
  2. package/dist/browser/churn/predictor.js +77 -0
  3. package/dist/browser/cohort/index.js +117 -0
  4. package/dist/browser/cohort/tracker.js +117 -0
  5. package/dist/browser/funnel/analyzer.js +68 -0
  6. package/dist/browser/funnel/index.js +68 -0
  7. package/dist/browser/growth/hypothesis-generator.js +46 -0
  8. package/dist/browser/growth/index.js +46 -0
  9. package/dist/browser/index.js +723 -0
  10. package/dist/browser/lifecycle/index.js +287 -0
  11. package/dist/browser/lifecycle/metric-collectors.js +58 -0
  12. package/dist/browser/lifecycle/posthog-bridge.js +79 -0
  13. package/dist/browser/lifecycle/posthog-metric-source.js +205 -0
  14. package/dist/browser/posthog/event-source.js +138 -0
  15. package/dist/browser/posthog/index.js +138 -0
  16. package/dist/browser/types.js +0 -0
  17. package/dist/churn/index.d.ts +2 -2
  18. package/dist/churn/index.d.ts.map +1 -0
  19. package/dist/churn/index.js +77 -2
  20. package/dist/churn/predictor.d.ts +15 -19
  21. package/dist/churn/predictor.d.ts.map +1 -1
  22. package/dist/churn/predictor.js +72 -68
  23. package/dist/cohort/index.d.ts +2 -2
  24. package/dist/cohort/index.d.ts.map +1 -0
  25. package/dist/cohort/index.js +117 -2
  26. package/dist/cohort/tracker.d.ts +3 -7
  27. package/dist/cohort/tracker.d.ts.map +1 -1
  28. package/dist/cohort/tracker.js +106 -87
  29. package/dist/funnel/analyzer.d.ts +4 -8
  30. package/dist/funnel/analyzer.d.ts.map +1 -1
  31. package/dist/funnel/analyzer.js +67 -62
  32. package/dist/funnel/index.d.ts +2 -2
  33. package/dist/funnel/index.d.ts.map +1 -0
  34. package/dist/funnel/index.js +69 -3
  35. package/dist/growth/hypothesis-generator.d.ts +11 -15
  36. package/dist/growth/hypothesis-generator.d.ts.map +1 -1
  37. package/dist/growth/hypothesis-generator.js +46 -39
  38. package/dist/growth/index.d.ts +2 -2
  39. package/dist/growth/index.d.ts.map +1 -0
  40. package/dist/growth/index.js +47 -3
  41. package/dist/index.d.ts +8 -10
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +724 -12
  44. package/dist/lifecycle/index.d.ts +4 -4
  45. package/dist/lifecycle/index.d.ts.map +1 -0
  46. package/dist/lifecycle/index.js +287 -4
  47. package/dist/lifecycle/metric-collectors.d.ts +22 -26
  48. package/dist/lifecycle/metric-collectors.d.ts.map +1 -1
  49. package/dist/lifecycle/metric-collectors.js +55 -44
  50. package/dist/lifecycle/posthog-bridge.d.ts +9 -13
  51. package/dist/lifecycle/posthog-bridge.d.ts.map +1 -1
  52. package/dist/lifecycle/posthog-bridge.js +77 -25
  53. package/dist/lifecycle/posthog-metric-source.d.ts +40 -44
  54. package/dist/lifecycle/posthog-metric-source.d.ts.map +1 -1
  55. package/dist/lifecycle/posthog-metric-source.js +200 -180
  56. package/dist/node/churn/index.js +77 -0
  57. package/dist/node/churn/predictor.js +77 -0
  58. package/dist/node/cohort/index.js +117 -0
  59. package/dist/node/cohort/tracker.js +117 -0
  60. package/dist/node/funnel/analyzer.js +68 -0
  61. package/dist/node/funnel/index.js +68 -0
  62. package/dist/node/growth/hypothesis-generator.js +46 -0
  63. package/dist/node/growth/index.js +46 -0
  64. package/dist/node/index.js +723 -0
  65. package/dist/node/lifecycle/index.js +287 -0
  66. package/dist/node/lifecycle/metric-collectors.js +58 -0
  67. package/dist/node/lifecycle/posthog-bridge.js +79 -0
  68. package/dist/node/lifecycle/posthog-metric-source.js +205 -0
  69. package/dist/node/posthog/event-source.js +138 -0
  70. package/dist/node/posthog/index.js +138 -0
  71. package/dist/node/types.js +0 -0
  72. package/dist/posthog/event-source.d.ts +18 -22
  73. package/dist/posthog/event-source.d.ts.map +1 -1
  74. package/dist/posthog/event-source.js +131 -111
  75. package/dist/posthog/index.d.ts +2 -2
  76. package/dist/posthog/index.d.ts.map +1 -0
  77. package/dist/posthog/index.js +139 -3
  78. package/dist/types.d.ts +52 -55
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/types.js +1 -0
  81. package/package.json +189 -46
  82. package/dist/churn/predictor.js.map +0 -1
  83. package/dist/cohort/tracker.js.map +0 -1
  84. package/dist/funnel/analyzer.js.map +0 -1
  85. package/dist/growth/hypothesis-generator.js.map +0 -1
  86. package/dist/lifecycle/metric-collectors.js.map +0 -1
  87. package/dist/lifecycle/posthog-bridge.js.map +0 -1
  88. package/dist/lifecycle/posthog-metric-source.js.map +0 -1
  89. package/dist/posthog/event-source.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"posthog-metric-source.js","names":[],"sources":["../../src/lifecycle/posthog-metric-source.ts"],"sourcesContent":["import type {\n AnalyticsQueryResult,\n AnalyticsReader,\n} from '@contractspec/lib.contracts/integrations/providers/analytics';\nimport type { LifecycleMetricSource } from './metric-collectors';\n\nexport interface PosthogLifecycleMetricSourceOptions {\n activityEvents?: string[];\n retentionWindowDays?: number;\n revenueEvent?: string;\n revenueProperty?: string;\n customerEvent?: string;\n customerProperty?: string;\n teamSizeEvent?: string;\n teamSizeProperty?: string;\n burnMultipleEvent?: string;\n burnMultipleProperty?: string;\n}\n\nexport class PosthogLifecycleMetricSource implements LifecycleMetricSource {\n private readonly reader: AnalyticsReader;\n private readonly activityEvents?: string[];\n private readonly retentionWindowDays: number;\n private readonly revenueEvent?: string;\n private readonly revenueProperty?: string;\n private readonly customerEvent?: string;\n private readonly customerProperty?: string;\n private readonly teamSizeEvent?: string;\n private readonly teamSizeProperty?: string;\n private readonly burnMultipleEvent?: string;\n private readonly burnMultipleProperty?: string;\n\n constructor(\n reader: AnalyticsReader,\n options: PosthogLifecycleMetricSourceOptions = {}\n ) {\n this.reader = reader;\n this.activityEvents = options.activityEvents;\n this.retentionWindowDays = options.retentionWindowDays ?? 7;\n this.revenueEvent = options.revenueEvent;\n this.revenueProperty = options.revenueProperty ?? 'amount';\n this.customerEvent = options.customerEvent;\n this.customerProperty = options.customerProperty ?? 'is_customer';\n this.teamSizeEvent = options.teamSizeEvent;\n this.teamSizeProperty = options.teamSizeProperty ?? 'team_size';\n this.burnMultipleEvent = options.burnMultipleEvent;\n this.burnMultipleProperty = options.burnMultipleProperty ?? 'burn_multiple';\n }\n\n async getActiveUsers(): Promise<number | undefined> {\n return this.countDistinctUsers(1);\n }\n\n async getWeeklyActiveUsers(): Promise<number | undefined> {\n return this.countDistinctUsers(7);\n }\n\n async getRetentionRate(): Promise<number | undefined> {\n const windowDays = this.retentionWindowDays;\n const now = new Date();\n const prevEnd = new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1000);\n const prevStart = new Date(\n now.getTime() - windowDays * 2 * 24 * 60 * 60 * 1000\n );\n\n const returningUsers = await this.countReturningUsers(\n prevStart,\n prevEnd,\n now\n );\n const prevUsers = await this.countDistinctUsersBetween(prevStart, prevEnd);\n if (prevUsers === undefined || prevUsers === 0) return undefined;\n return returningUsers / prevUsers;\n }\n\n async getMonthlyRecurringRevenue(): Promise<number | undefined> {\n if (!this.revenueEvent || !this.revenueProperty) return undefined;\n return this.sumMetric(this.revenueEvent, this.revenueProperty);\n }\n\n async getCustomerCount(): Promise<number | undefined> {\n if (!this.customerEvent || !this.customerProperty) return undefined;\n return this.countDistinctUsersByProperty(\n this.customerEvent,\n this.customerProperty\n );\n }\n\n async getTeamSize(): Promise<number | undefined> {\n if (!this.teamSizeEvent || !this.teamSizeProperty) return undefined;\n return this.latestMetric(this.teamSizeEvent, this.teamSizeProperty);\n }\n\n async getBurnMultiple(): Promise<number | undefined> {\n if (!this.burnMultipleEvent || !this.burnMultipleProperty) return undefined;\n return this.latestMetric(this.burnMultipleEvent, this.burnMultipleProperty);\n }\n\n private async countDistinctUsers(days: number): Promise<number | undefined> {\n const range = buildDateRange(days);\n return this.countDistinctUsersBetween(range.from, range.to);\n }\n\n private async countDistinctUsersBetween(\n from: Date,\n to: Date\n ): Promise<number | undefined> {\n const eventFilter = buildEventFilter(this.activityEvents, 'activityEvent');\n const result = await this.queryHogQL({\n query: [\n 'select',\n ' countDistinct(distinct_id) as total',\n 'from events',\n `where timestamp >= {dateFrom} and timestamp < {dateTo}`,\n eventFilter.clause ? `and ${eventFilter.clause}` : '',\n ]\n .filter(Boolean)\n .join('\\n'),\n values: {\n dateFrom: from.toISOString(),\n dateTo: to.toISOString(),\n ...eventFilter.values,\n },\n });\n return readSingleNumber(result);\n }\n\n private async countReturningUsers(\n previousStart: Date,\n previousEnd: Date,\n currentEnd: Date\n ): Promise<number> {\n const eventFilter = buildEventFilter(this.activityEvents, 'activityEvent');\n const result = await this.queryHogQL({\n query: [\n 'select',\n ' countDistinct(distinct_id) as total',\n 'from events',\n `where timestamp >= {currentFrom} and timestamp < {currentTo}`,\n eventFilter.clause ? `and ${eventFilter.clause}` : '',\n 'and distinct_id in (',\n ' select distinct_id from events',\n ' where timestamp >= {previousFrom} and timestamp < {previousTo}',\n eventFilter.clause ? `and ${eventFilter.clause}` : '',\n ')',\n ].join('\\n'),\n values: {\n currentFrom: previousEnd.toISOString(),\n currentTo: currentEnd.toISOString(),\n previousFrom: previousStart.toISOString(),\n previousTo: previousEnd.toISOString(),\n ...eventFilter.values,\n },\n });\n return readSingleNumber(result) ?? 0;\n }\n\n private async sumMetric(\n eventName: string,\n propertyKey: string\n ): Promise<number | undefined> {\n const result = await this.queryHogQL({\n query: [\n 'select',\n ` sum(properties.${propertyKey}) as total`,\n 'from events',\n 'where event = {eventName}',\n ].join('\\n'),\n values: { eventName },\n });\n return readSingleNumber(result);\n }\n\n private async countDistinctUsersByProperty(\n eventName: string,\n propertyKey: string\n ): Promise<number | undefined> {\n const result = await this.queryHogQL({\n query: [\n 'select',\n ' countDistinct(distinct_id) as total',\n 'from events',\n 'where event = {eventName}',\n `and properties.${propertyKey} = {propertyValue}`,\n ].join('\\n'),\n values: { eventName, propertyValue: true },\n });\n return readSingleNumber(result);\n }\n\n private async latestMetric(\n eventName: string,\n propertyKey: string\n ): Promise<number | undefined> {\n const result = await this.queryHogQL({\n query: [\n 'select',\n ` properties.${propertyKey} as value`,\n 'from events',\n 'where event = {eventName}',\n 'order by timestamp desc',\n 'limit 1',\n ].join('\\n'),\n values: { eventName },\n });\n return readSingleNumber(result);\n }\n\n private async queryHogQL(input: {\n query: string;\n values: Record<string, unknown>;\n }): Promise<AnalyticsQueryResult> {\n if (!this.reader.queryHogQL) {\n throw new Error('Analytics reader does not support HogQL queries.');\n }\n return this.reader.queryHogQL(input);\n }\n}\n\nfunction buildDateRange(days: number): { from: Date; to: Date } {\n const to = new Date();\n const from = new Date(to.getTime() - days * 24 * 60 * 60 * 1000);\n return { from, to };\n}\n\nfunction buildEventFilter(\n events: string[] | undefined,\n prefix: string\n): { clause?: string; values?: Record<string, unknown> } {\n if (!events || events.length === 0) return {};\n if (events.length === 1) {\n return {\n clause: `event = {${prefix}0}`,\n values: { [`${prefix}0`]: events[0] },\n };\n }\n const clauses = events.map((_event, index) => `event = {${prefix}${index}}`);\n const values: Record<string, unknown> = {};\n events.forEach((event, index) => {\n values[`${prefix}${index}`] = event;\n });\n return {\n clause: `(${clauses.join(' or ')})`,\n values,\n };\n}\n\nfunction readSingleNumber(result: AnalyticsQueryResult): number | undefined {\n if (!Array.isArray(result.results) || result.results.length === 0) {\n return undefined;\n }\n const firstRow = result.results[0];\n if (Array.isArray(firstRow) && firstRow.length > 0) {\n const value = firstRow[0];\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value);\n if (Number.isFinite(parsed)) return parsed;\n }\n }\n return undefined;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"event-source.js","names":[],"sources":["../../src/posthog/event-source.ts"],"sourcesContent":["import type {\n AnalyticsReader,\n DateRangeInput,\n} from '@contractspec/lib.contracts/integrations/providers/analytics';\nimport type {\n AnalyticsEvent,\n CohortDefinition,\n FunnelDefinition,\n GrowthMetric,\n} from '../types';\n\nexport interface PosthogAnalyticsEventSourceOptions {\n limitPerEvent?: number;\n tenantPropertyKey?: string;\n defaultRangeDays?: number;\n}\n\nexport class PosthogAnalyticsEventSource {\n private readonly reader: AnalyticsReader;\n private readonly limitPerEvent: number;\n private readonly tenantPropertyKey: string;\n private readonly defaultRangeDays: number;\n\n constructor(\n reader: AnalyticsReader,\n options: PosthogAnalyticsEventSourceOptions = {}\n ) {\n this.reader = reader;\n this.limitPerEvent = options.limitPerEvent ?? 1000;\n this.tenantPropertyKey = options.tenantPropertyKey ?? 'tenantId';\n this.defaultRangeDays = options.defaultRangeDays ?? 30;\n }\n\n async getEventsForFunnel(\n definition: FunnelDefinition,\n dateRange?: DateRangeInput\n ): Promise<AnalyticsEvent[]> {\n const eventNames = definition.steps.map((step) => step.eventName);\n return this.getEventsByNames(eventNames, dateRange);\n }\n\n async getEventsForCohort(\n definition: CohortDefinition,\n dateRange?: DateRangeInput,\n eventNames?: string[]\n ): Promise<AnalyticsEvent[]> {\n const events = eventNames && eventNames.length > 0 ? eventNames : ['*'];\n if (events[0] === '*') {\n return this.getEventsByNames([], dateRange, definition.periods * 500);\n }\n return this.getEventsByNames(events, dateRange, definition.periods * 500);\n }\n\n async getUserActivity(\n userId: string,\n dateRange?: DateRangeInput,\n limit = 1000\n ): Promise<AnalyticsEvent[]> {\n if (!this.reader.getEvents) {\n throw new Error('Analytics reader does not support event queries.');\n }\n const response = await this.reader.getEvents({\n distinctId: userId,\n dateRange,\n limit,\n });\n return response.results.map((event) =>\n toAnalyticsEvent(event, this.tenantPropertyKey)\n );\n }\n\n async getGrowthMetrics(\n metricNames: string[],\n dateRange?: DateRangeInput\n ): Promise<GrowthMetric[]> {\n const range = resolveRange(dateRange, this.defaultRangeDays);\n const previous = shiftRange(range);\n const results: GrowthMetric[] = [];\n for (const metricName of metricNames) {\n const current = await this.countEvents(metricName, range);\n const previousCount = await this.countEvents(metricName, previous);\n results.push({\n name: metricName,\n current,\n previous: previousCount,\n });\n }\n return results;\n }\n\n private async getEventsByNames(\n eventNames: string[],\n dateRange?: DateRangeInput,\n limitPerEvent = this.limitPerEvent\n ): Promise<AnalyticsEvent[]> {\n if (!this.reader.getEvents) {\n throw new Error('Analytics reader does not support event queries.');\n }\n if (eventNames.length === 0) {\n const response = await this.reader.getEvents({\n dateRange,\n limit: limitPerEvent,\n });\n return response.results.map((event) =>\n toAnalyticsEvent(event, this.tenantPropertyKey)\n );\n }\n const events: AnalyticsEvent[] = [];\n for (const eventName of eventNames) {\n const response = await this.reader.getEvents({\n event: eventName,\n dateRange,\n limit: limitPerEvent,\n });\n response.results.forEach((event) => {\n events.push(toAnalyticsEvent(event, this.tenantPropertyKey));\n });\n }\n return events;\n }\n\n private async countEvents(\n eventName: string,\n range: { from: Date; to: Date }\n ): Promise<number> {\n if (!this.reader.queryHogQL) {\n throw new Error('Analytics reader does not support HogQL queries.');\n }\n const result = await this.reader.queryHogQL({\n query: [\n 'select',\n ' count() as total',\n 'from events',\n 'where event = {eventName}',\n 'and timestamp >= {dateFrom}',\n 'and timestamp < {dateTo}',\n ].join('\\n'),\n values: {\n eventName,\n dateFrom: range.from.toISOString(),\n dateTo: range.to.toISOString(),\n },\n });\n return readSingleNumber(result) ?? 0;\n }\n}\n\nfunction toAnalyticsEvent(\n event: {\n event: string;\n distinctId: string;\n properties?: Record<string, unknown>;\n timestamp: string;\n },\n tenantPropertyKey: string\n): AnalyticsEvent {\n const tenantIdValue =\n event.properties?.[tenantPropertyKey] ?? event.properties?.tenantId;\n return {\n name: event.event,\n userId: event.distinctId,\n tenantId: typeof tenantIdValue === 'string' ? tenantIdValue : undefined,\n timestamp: event.timestamp,\n properties: event.properties,\n };\n}\n\nfunction resolveRange(\n dateRange: DateRangeInput | undefined,\n defaultDays: number\n): { from: Date; to: Date } {\n const to =\n dateRange?.to instanceof Date\n ? dateRange.to\n : dateRange?.to\n ? new Date(dateRange.to)\n : new Date();\n const from =\n dateRange?.from instanceof Date\n ? dateRange.from\n : dateRange?.from\n ? new Date(dateRange.from)\n : new Date(to.getTime() - defaultDays * 24 * 60 * 60 * 1000);\n return { from, to };\n}\n\nfunction shiftRange(range: { from: Date; to: Date }): {\n from: Date;\n to: Date;\n} {\n const duration = range.to.getTime() - range.from.getTime();\n return {\n from: new Date(range.from.getTime() - duration),\n to: new Date(range.to.getTime() - duration),\n };\n}\n\nfunction readSingleNumber(result: { results: unknown }): number | undefined {\n if (!Array.isArray(result.results) || result.results.length === 0) {\n return undefined;\n }\n const firstRow = result.results[0];\n if (Array.isArray(firstRow) && firstRow.length > 0) {\n const value = firstRow[0];\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value);\n if (Number.isFinite(parsed)) return parsed;\n }\n }\n return undefined;\n}\n"],"mappings":";AAiBA,IAAa,8BAAb,MAAyC;CACvC,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,YACE,QACA,UAA8C,EAAE,EAChD;AACA,OAAK,SAAS;AACd,OAAK,gBAAgB,QAAQ,iBAAiB;AAC9C,OAAK,oBAAoB,QAAQ,qBAAqB;AACtD,OAAK,mBAAmB,QAAQ,oBAAoB;;CAGtD,MAAM,mBACJ,YACA,WAC2B;EAC3B,MAAM,aAAa,WAAW,MAAM,KAAK,SAAS,KAAK,UAAU;AACjE,SAAO,KAAK,iBAAiB,YAAY,UAAU;;CAGrD,MAAM,mBACJ,YACA,WACA,YAC2B;EAC3B,MAAM,SAAS,cAAc,WAAW,SAAS,IAAI,aAAa,CAAC,IAAI;AACvE,MAAI,OAAO,OAAO,IAChB,QAAO,KAAK,iBAAiB,EAAE,EAAE,WAAW,WAAW,UAAU,IAAI;AAEvE,SAAO,KAAK,iBAAiB,QAAQ,WAAW,WAAW,UAAU,IAAI;;CAG3E,MAAM,gBACJ,QACA,WACA,QAAQ,KACmB;AAC3B,MAAI,CAAC,KAAK,OAAO,UACf,OAAM,IAAI,MAAM,mDAAmD;AAOrE,UALiB,MAAM,KAAK,OAAO,UAAU;GAC3C,YAAY;GACZ;GACA;GACD,CAAC,EACc,QAAQ,KAAK,UAC3B,iBAAiB,OAAO,KAAK,kBAAkB,CAChD;;CAGH,MAAM,iBACJ,aACA,WACyB;EACzB,MAAM,QAAQ,aAAa,WAAW,KAAK,iBAAiB;EAC5D,MAAM,WAAW,WAAW,MAAM;EAClC,MAAM,UAA0B,EAAE;AAClC,OAAK,MAAM,cAAc,aAAa;GACpC,MAAM,UAAU,MAAM,KAAK,YAAY,YAAY,MAAM;GACzD,MAAM,gBAAgB,MAAM,KAAK,YAAY,YAAY,SAAS;AAClE,WAAQ,KAAK;IACX,MAAM;IACN;IACA,UAAU;IACX,CAAC;;AAEJ,SAAO;;CAGT,MAAc,iBACZ,YACA,WACA,gBAAgB,KAAK,eACM;AAC3B,MAAI,CAAC,KAAK,OAAO,UACf,OAAM,IAAI,MAAM,mDAAmD;AAErE,MAAI,WAAW,WAAW,EAKxB,SAJiB,MAAM,KAAK,OAAO,UAAU;GAC3C;GACA,OAAO;GACR,CAAC,EACc,QAAQ,KAAK,UAC3B,iBAAiB,OAAO,KAAK,kBAAkB,CAChD;EAEH,MAAM,SAA2B,EAAE;AACnC,OAAK,MAAM,aAAa,WAMtB,EALiB,MAAM,KAAK,OAAO,UAAU;GAC3C,OAAO;GACP;GACA,OAAO;GACR,CAAC,EACO,QAAQ,SAAS,UAAU;AAClC,UAAO,KAAK,iBAAiB,OAAO,KAAK,kBAAkB,CAAC;IAC5D;AAEJ,SAAO;;CAGT,MAAc,YACZ,WACA,OACiB;AACjB,MAAI,CAAC,KAAK,OAAO,WACf,OAAM,IAAI,MAAM,mDAAmD;AAiBrE,SAAO,iBAfQ,MAAM,KAAK,OAAO,WAAW;GAC1C,OAAO;IACL;IACA;IACA;IACA;IACA;IACA;IACD,CAAC,KAAK,KAAK;GACZ,QAAQ;IACN;IACA,UAAU,MAAM,KAAK,aAAa;IAClC,QAAQ,MAAM,GAAG,aAAa;IAC/B;GACF,CAAC,CAC6B,IAAI;;;AAIvC,SAAS,iBACP,OAMA,mBACgB;CAChB,MAAM,gBACJ,MAAM,aAAa,sBAAsB,MAAM,YAAY;AAC7D,QAAO;EACL,MAAM,MAAM;EACZ,QAAQ,MAAM;EACd,UAAU,OAAO,kBAAkB,WAAW,gBAAgB;EAC9D,WAAW,MAAM;EACjB,YAAY,MAAM;EACnB;;AAGH,SAAS,aACP,WACA,aAC0B;CAC1B,MAAM,KACJ,WAAW,cAAc,OACrB,UAAU,KACV,WAAW,KACT,IAAI,KAAK,UAAU,GAAG,mBACtB,IAAI,MAAM;AAOlB,QAAO;EAAE,MALP,WAAW,gBAAgB,OACvB,UAAU,OACV,WAAW,OACT,IAAI,KAAK,UAAU,KAAK,mBACxB,IAAI,KAAK,GAAG,SAAS,GAAG,cAAc,KAAK,KAAK,KAAK,IAAK;EACnD;EAAI;;AAGrB,SAAS,WAAW,OAGlB;CACA,MAAM,WAAW,MAAM,GAAG,SAAS,GAAG,MAAM,KAAK,SAAS;AAC1D,QAAO;EACL,MAAM,IAAI,KAAK,MAAM,KAAK,SAAS,GAAG,SAAS;EAC/C,IAAI,IAAI,KAAK,MAAM,GAAG,SAAS,GAAG,SAAS;EAC5C;;AAGH,SAAS,iBAAiB,QAAkD;AAC1E,KAAI,CAAC,MAAM,QAAQ,OAAO,QAAQ,IAAI,OAAO,QAAQ,WAAW,EAC9D;CAEF,MAAM,WAAW,OAAO,QAAQ;AAChC,KAAI,MAAM,QAAQ,SAAS,IAAI,SAAS,SAAS,GAAG;EAClD,MAAM,QAAQ,SAAS;AACvB,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,MAAM,CAAE,QAAO;AAChE,MAAI,OAAO,UAAU,YAAY,MAAM,MAAM,EAAE;GAC7C,MAAM,SAAS,OAAO,MAAM;AAC5B,OAAI,OAAO,SAAS,OAAO,CAAE,QAAO"}