@grapadigital/shared-schemas 1.0.221 → 1.0.222

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.
package/dist/index.d.ts CHANGED
@@ -37,3 +37,4 @@ export * from './schemas/job.schema';
37
37
  export * from './schemas/snapshot.schema';
38
38
  export * from './schemas/lead.schema';
39
39
  export * from './utils/normalize-content-link.util';
40
+ export * from './utils/merge-metrics-breakdown.util';
package/dist/index.js CHANGED
@@ -58,3 +58,4 @@ __exportStar(require("./schemas/job.schema"), exports);
58
58
  __exportStar(require("./schemas/snapshot.schema"), exports);
59
59
  __exportStar(require("./schemas/lead.schema"), exports);
60
60
  __exportStar(require("./utils/normalize-content-link.util"), exports);
61
+ __exportStar(require("./utils/merge-metrics-breakdown.util"), exports);
@@ -113,6 +113,15 @@ export declare class Metrics {
113
113
  cpmi?: number | null;
114
114
  cpi?: number | null;
115
115
  cpe?: number | null;
116
+ /**
117
+ * Alcance Potencial (o antigo PO Reach): seguidores no momento do post. Em
118
+ * Content.metrics soma o autor e todos os coautores. Ver GD-703.
119
+ */
120
+ potentialReach?: number | null;
121
+ /** Tx. de Engaj. por Alcance: engagementCount ÷ reachCount (fração). Ver GD-703. */
122
+ reachEngagementRate?: number | null;
123
+ /** Tx. de Engaj. por Seguidores: engagementCount ÷ potentialReach (fração). Ver GD-703. */
124
+ followersEngagementRate?: number | null;
116
125
  }
117
126
  export declare const MetricsSchema: MongooseSchema<Metrics, import("mongoose").Model<Metrics, any, any, any, import("mongoose").Document<unknown, any, Metrics, any, {}> & Metrics & {
118
127
  _id: Types.ObjectId;
@@ -123,6 +132,111 @@ export declare const MetricsSchema: MongooseSchema<Metrics, import("mongoose").M
123
132
  } & {
124
133
  __v: number;
125
134
  }>;
135
+ /**
136
+ * Métricas que dependem dos seguidores de uma pessoa do post (autor ou
137
+ * coautor). As brutas ficam uma vez só em Content.metrics. Ver GD-703.
138
+ */
139
+ export declare class ContentPersonMetrics {
140
+ potentialReach?: number | null;
141
+ followersEngagementRate?: number | null;
142
+ effectiveReach?: number | null;
143
+ reachPerformance?: number | null;
144
+ qualifiedEngagementRate?: number | null;
145
+ score?: number | null;
146
+ /** Nome do Client.tiers da pessoa; null sem Tiers ou fora das faixas. */
147
+ tier?: string | null;
148
+ /** reachEngagementRate ≥ meta viralReachEngagementRate do Tier; null sem Tier ou sem a meta. */
149
+ isViral?: boolean | null;
150
+ }
151
+ export declare const ContentPersonMetricsSchema: MongooseSchema<ContentPersonMetrics, import("mongoose").Model<ContentPersonMetrics, any, any, any, import("mongoose").Document<unknown, any, ContentPersonMetrics, any, {}> & ContentPersonMetrics & {
152
+ _id: Types.ObjectId;
153
+ } & {
154
+ __v: number;
155
+ }, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, ContentPersonMetrics, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<ContentPersonMetrics>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<ContentPersonMetrics> & {
156
+ _id: Types.ObjectId;
157
+ } & {
158
+ __v: number;
159
+ }>;
160
+ /** Coautor do post (Colab), com os seguidores dele no momento do post. Ver GD-703. */
161
+ export declare class ContentCollab {
162
+ profile: Types.ObjectId | Profile;
163
+ followersAtPost?: number | null;
164
+ metrics?: ContentPersonMetrics | null;
165
+ }
166
+ export declare const ContentCollabSchema: MongooseSchema<ContentCollab, import("mongoose").Model<ContentCollab, any, any, any, import("mongoose").Document<unknown, any, ContentCollab, any, {}> & ContentCollab & {
167
+ _id: Types.ObjectId;
168
+ } & {
169
+ __v: number;
170
+ }, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, ContentCollab, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<ContentCollab>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<ContentCollab> & {
171
+ _id: Types.ObjectId;
172
+ } & {
173
+ __v: number;
174
+ }>;
175
+ /** Chaves brutas de Metrics que têm parte Orgânica e Impulsionada. Ver GD-707. */
176
+ export declare const CONTENT_BASE_METRIC_KEYS: readonly ["impressionCount", "reachCount", "likeCount", "commentCount", "savedCount", "sharesCount", "storyCount", "tapBackCount", "clickCount", "replyCount", "tapForwardCount", "swipeForwardCount", "exitCount", "favoriteCount", "dislikeCount", "emv"];
177
+ export type ContentBaseMetricKey = (typeof CONTENT_BASE_METRIC_KEYS)[number];
178
+ export declare class ContentBaseMetrics {
179
+ impressionCount?: number | null;
180
+ reachCount?: number | null;
181
+ likeCount?: number | null;
182
+ commentCount?: number | null;
183
+ savedCount?: number | null;
184
+ sharesCount?: number | null;
185
+ storyCount?: number | null;
186
+ tapBackCount?: number | null;
187
+ clickCount?: number | null;
188
+ replyCount?: number | null;
189
+ tapForwardCount?: number | null;
190
+ swipeForwardCount?: number | null;
191
+ exitCount?: number | null;
192
+ favoriteCount?: number | null;
193
+ dislikeCount?: number | null;
194
+ emv?: number | null;
195
+ }
196
+ export declare const ContentBaseMetricsSchema: MongooseSchema<ContentBaseMetrics, import("mongoose").Model<ContentBaseMetrics, any, any, any, import("mongoose").Document<unknown, any, ContentBaseMetrics, any, {}> & ContentBaseMetrics & {
197
+ _id: Types.ObjectId;
198
+ } & {
199
+ __v: number;
200
+ }, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, ContentBaseMetrics, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<ContentBaseMetrics>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<ContentBaseMetrics> & {
201
+ _id: Types.ObjectId;
202
+ } & {
203
+ __v: number;
204
+ }>;
205
+ /** Divisão Orgânico e Impulsionado; o total em Content.metrics é sempre a soma. Ver GD-707. */
206
+ export declare class ContentMetricsBreakdown {
207
+ organic: ContentBaseMetrics;
208
+ boosted: ContentBaseMetrics;
209
+ }
210
+ export declare const ContentMetricsBreakdownSchema: MongooseSchema<ContentMetricsBreakdown, import("mongoose").Model<ContentMetricsBreakdown, any, any, any, import("mongoose").Document<unknown, any, ContentMetricsBreakdown, any, {}> & ContentMetricsBreakdown & {
211
+ _id: Types.ObjectId;
212
+ } & {
213
+ __v: number;
214
+ }, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, ContentMetricsBreakdown, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<ContentMetricsBreakdown>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<ContentMetricsBreakdown> & {
215
+ _id: Types.ObjectId;
216
+ } & {
217
+ __v: number;
218
+ }>;
219
+ /**
220
+ * Dados do vídeo para o VCR. Views não ficam aqui: impressões e views são
221
+ * sempre Content.metrics.impressionCount. Ver GD-707.
222
+ */
223
+ export declare class ContentVideo {
224
+ /** Duração em segundos. */
225
+ duration?: number | null;
226
+ /** Tempo total assistido, somando todas as visualizações, em segundos. */
227
+ watchTime?: number | null;
228
+ /** (watchTime ÷ duration) ÷ impressionCount, sem teto. */
229
+ vcr?: number | null;
230
+ }
231
+ export declare const ContentVideoSchema: MongooseSchema<ContentVideo, import("mongoose").Model<ContentVideo, any, any, any, import("mongoose").Document<unknown, any, ContentVideo, any, {}> & ContentVideo & {
232
+ _id: Types.ObjectId;
233
+ } & {
234
+ __v: number;
235
+ }, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, ContentVideo, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<ContentVideo>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<ContentVideo> & {
236
+ _id: Types.ObjectId;
237
+ } & {
238
+ __v: number;
239
+ }>;
126
240
  export declare class ContentMetricsHistoryEntry {
127
241
  metrics: Metrics;
128
242
  recordedAt: Date;
@@ -195,8 +309,19 @@ export declare class Content {
195
309
  isCollab: boolean;
196
310
  isBoosted: boolean;
197
311
  hasAffiliateLink: boolean;
198
- collabs?: Types.ObjectId[] | Profile[];
312
+ /** Coautores do post; isCollab = collabs.length > 0. Ver GD-703. */
313
+ collabs?: ContentCollab[];
314
+ /** Métricas do autor, com os seguidores dele (followersAtPost). Ver GD-703. */
315
+ profileMetrics?: ContentPersonMetrics | null;
316
+ /** Visão da Ação: brutas uma vez só e derivadas com o potentialReach somado. */
199
317
  metrics: Metrics;
318
+ metricsBreakdown?: ContentMetricsBreakdown | null;
319
+ /**
320
+ * Caminhos das partes digitadas à mão (ex.: organic.reachCount,
321
+ * video.watchTime): a captura não sobrescreve. Ver GD-707.
322
+ */
323
+ manualMetrics?: string[];
324
+ video?: ContentVideo | null;
200
325
  estimates: Estimates;
201
326
  googleSheetRow?: string;
202
327
  createdAt?: Date;
@@ -206,7 +331,9 @@ export declare class Content {
206
331
  sentimentAnalysis?: SentimentAnalysis | null;
207
332
  thumbnailUrl?: string;
208
333
  mediaUrl?: string;
334
+ /** @deprecated Obsoleto desde a GD-707: a edição vale por parte (manualMetrics). */
209
335
  isEdited?: boolean;
336
+ /** @deprecated Obsoleto desde a GD-707. */
210
337
  editedAt?: Date;
211
338
  followersAtPost?: number | null;
212
339
  history?: ContentMetricsHistoryEntry[];
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.ContentModel = exports.ContentSchema = exports.Content = exports.ContentStatusChangedBySchema = exports.ContentStatusChangedBy = exports.CONTENT_CLASSIFICATION_SOURCE_VALUES = exports.CONTENT_METRICS_SOURCE_VALUES = exports.CONTENT_COMMENT_COUNT_SOURCE_VALUES = exports.CONTENT_REPORTABLE_STATUS_VALUES = exports.CONTENT_STATUS_VALUES = exports.EstimatesSchema = exports.Estimates = exports.ContentMetricsHistoryEntrySchema = exports.ContentMetricsHistoryEntry = exports.MetricsSchema = exports.Metrics = exports.SentimentAnalysisSchema = exports.SentimentAnalysis = exports.SentimentScoreSchema = exports.SentimentScore = exports.WordCloudSchema = exports.WordCloud = exports.SentimentCommentSchema = exports.SentimentComment = void 0;
12
+ exports.ContentModel = exports.ContentSchema = exports.Content = exports.ContentStatusChangedBySchema = exports.ContentStatusChangedBy = exports.CONTENT_CLASSIFICATION_SOURCE_VALUES = exports.CONTENT_METRICS_SOURCE_VALUES = exports.CONTENT_COMMENT_COUNT_SOURCE_VALUES = exports.CONTENT_REPORTABLE_STATUS_VALUES = exports.CONTENT_STATUS_VALUES = exports.EstimatesSchema = exports.Estimates = exports.ContentMetricsHistoryEntrySchema = exports.ContentMetricsHistoryEntry = exports.ContentVideoSchema = exports.ContentVideo = exports.ContentMetricsBreakdownSchema = exports.ContentMetricsBreakdown = exports.ContentBaseMetricsSchema = exports.ContentBaseMetrics = exports.CONTENT_BASE_METRIC_KEYS = exports.ContentCollabSchema = exports.ContentCollab = exports.ContentPersonMetricsSchema = exports.ContentPersonMetrics = exports.MetricsSchema = exports.Metrics = exports.SentimentAnalysisSchema = exports.SentimentAnalysis = exports.SentimentScoreSchema = exports.SentimentScore = exports.WordCloudSchema = exports.WordCloud = exports.SentimentCommentSchema = exports.SentimentComment = void 0;
13
13
  const mongoose_1 = require("@nestjs/mongoose");
14
14
  const mongoose_2 = require("mongoose");
15
15
  const normalize_content_link_util_1 = require("../utils/normalize-content-link.util");
@@ -207,11 +207,215 @@ __decorate([
207
207
  (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
208
208
  __metadata("design:type", Object)
209
209
  ], Metrics.prototype, "cpe", void 0);
210
+ __decorate([
211
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
212
+ __metadata("design:type", Object)
213
+ ], Metrics.prototype, "potentialReach", void 0);
214
+ __decorate([
215
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
216
+ __metadata("design:type", Object)
217
+ ], Metrics.prototype, "reachEngagementRate", void 0);
218
+ __decorate([
219
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
220
+ __metadata("design:type", Object)
221
+ ], Metrics.prototype, "followersEngagementRate", void 0);
210
222
  Metrics = __decorate([
211
223
  (0, mongoose_1.Schema)({ _id: false })
212
224
  ], Metrics);
213
225
  exports.Metrics = Metrics;
214
226
  exports.MetricsSchema = mongoose_1.SchemaFactory.createForClass(Metrics);
227
+ /**
228
+ * Métricas que dependem dos seguidores de uma pessoa do post (autor ou
229
+ * coautor). As brutas ficam uma vez só em Content.metrics. Ver GD-703.
230
+ */
231
+ let ContentPersonMetrics = class ContentPersonMetrics {
232
+ };
233
+ __decorate([
234
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
235
+ __metadata("design:type", Object)
236
+ ], ContentPersonMetrics.prototype, "potentialReach", void 0);
237
+ __decorate([
238
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
239
+ __metadata("design:type", Object)
240
+ ], ContentPersonMetrics.prototype, "followersEngagementRate", void 0);
241
+ __decorate([
242
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
243
+ __metadata("design:type", Object)
244
+ ], ContentPersonMetrics.prototype, "effectiveReach", void 0);
245
+ __decorate([
246
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
247
+ __metadata("design:type", Object)
248
+ ], ContentPersonMetrics.prototype, "reachPerformance", void 0);
249
+ __decorate([
250
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
251
+ __metadata("design:type", Object)
252
+ ], ContentPersonMetrics.prototype, "qualifiedEngagementRate", void 0);
253
+ __decorate([
254
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
255
+ __metadata("design:type", Object)
256
+ ], ContentPersonMetrics.prototype, "score", void 0);
257
+ __decorate([
258
+ (0, mongoose_1.Prop)({ type: String, required: false, default: null }),
259
+ __metadata("design:type", Object)
260
+ ], ContentPersonMetrics.prototype, "tier", void 0);
261
+ __decorate([
262
+ (0, mongoose_1.Prop)({ type: Boolean, required: false, default: null }),
263
+ __metadata("design:type", Object)
264
+ ], ContentPersonMetrics.prototype, "isViral", void 0);
265
+ ContentPersonMetrics = __decorate([
266
+ (0, mongoose_1.Schema)({ _id: false })
267
+ ], ContentPersonMetrics);
268
+ exports.ContentPersonMetrics = ContentPersonMetrics;
269
+ exports.ContentPersonMetricsSchema = mongoose_1.SchemaFactory.createForClass(ContentPersonMetrics);
270
+ /** Coautor do post (Colab), com os seguidores dele no momento do post. Ver GD-703. */
271
+ let ContentCollab = class ContentCollab {
272
+ };
273
+ __decorate([
274
+ (0, mongoose_1.Prop)({ type: mongoose_2.Schema.Types.ObjectId, ref: 'Profile', required: true }),
275
+ __metadata("design:type", Object)
276
+ ], ContentCollab.prototype, "profile", void 0);
277
+ __decorate([
278
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
279
+ __metadata("design:type", Object)
280
+ ], ContentCollab.prototype, "followersAtPost", void 0);
281
+ __decorate([
282
+ (0, mongoose_1.Prop)({ type: exports.ContentPersonMetricsSchema, required: false, default: null }),
283
+ __metadata("design:type", Object)
284
+ ], ContentCollab.prototype, "metrics", void 0);
285
+ ContentCollab = __decorate([
286
+ (0, mongoose_1.Schema)({ _id: false })
287
+ ], ContentCollab);
288
+ exports.ContentCollab = ContentCollab;
289
+ exports.ContentCollabSchema = mongoose_1.SchemaFactory.createForClass(ContentCollab);
290
+ /** Chaves brutas de Metrics que têm parte Orgânica e Impulsionada. Ver GD-707. */
291
+ exports.CONTENT_BASE_METRIC_KEYS = [
292
+ 'impressionCount',
293
+ 'reachCount',
294
+ 'likeCount',
295
+ 'commentCount',
296
+ 'savedCount',
297
+ 'sharesCount',
298
+ 'storyCount',
299
+ 'tapBackCount',
300
+ 'clickCount',
301
+ 'replyCount',
302
+ 'tapForwardCount',
303
+ 'swipeForwardCount',
304
+ 'exitCount',
305
+ 'favoriteCount',
306
+ 'dislikeCount',
307
+ 'emv',
308
+ ];
309
+ let ContentBaseMetrics = class ContentBaseMetrics {
310
+ };
311
+ __decorate([
312
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
313
+ __metadata("design:type", Object)
314
+ ], ContentBaseMetrics.prototype, "impressionCount", void 0);
315
+ __decorate([
316
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
317
+ __metadata("design:type", Object)
318
+ ], ContentBaseMetrics.prototype, "reachCount", void 0);
319
+ __decorate([
320
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
321
+ __metadata("design:type", Object)
322
+ ], ContentBaseMetrics.prototype, "likeCount", void 0);
323
+ __decorate([
324
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
325
+ __metadata("design:type", Object)
326
+ ], ContentBaseMetrics.prototype, "commentCount", void 0);
327
+ __decorate([
328
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
329
+ __metadata("design:type", Object)
330
+ ], ContentBaseMetrics.prototype, "savedCount", void 0);
331
+ __decorate([
332
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
333
+ __metadata("design:type", Object)
334
+ ], ContentBaseMetrics.prototype, "sharesCount", void 0);
335
+ __decorate([
336
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
337
+ __metadata("design:type", Object)
338
+ ], ContentBaseMetrics.prototype, "storyCount", void 0);
339
+ __decorate([
340
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
341
+ __metadata("design:type", Object)
342
+ ], ContentBaseMetrics.prototype, "tapBackCount", void 0);
343
+ __decorate([
344
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
345
+ __metadata("design:type", Object)
346
+ ], ContentBaseMetrics.prototype, "clickCount", void 0);
347
+ __decorate([
348
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
349
+ __metadata("design:type", Object)
350
+ ], ContentBaseMetrics.prototype, "replyCount", void 0);
351
+ __decorate([
352
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
353
+ __metadata("design:type", Object)
354
+ ], ContentBaseMetrics.prototype, "tapForwardCount", void 0);
355
+ __decorate([
356
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
357
+ __metadata("design:type", Object)
358
+ ], ContentBaseMetrics.prototype, "swipeForwardCount", void 0);
359
+ __decorate([
360
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
361
+ __metadata("design:type", Object)
362
+ ], ContentBaseMetrics.prototype, "exitCount", void 0);
363
+ __decorate([
364
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
365
+ __metadata("design:type", Object)
366
+ ], ContentBaseMetrics.prototype, "favoriteCount", void 0);
367
+ __decorate([
368
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
369
+ __metadata("design:type", Object)
370
+ ], ContentBaseMetrics.prototype, "dislikeCount", void 0);
371
+ __decorate([
372
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
373
+ __metadata("design:type", Object)
374
+ ], ContentBaseMetrics.prototype, "emv", void 0);
375
+ ContentBaseMetrics = __decorate([
376
+ (0, mongoose_1.Schema)({ _id: false })
377
+ ], ContentBaseMetrics);
378
+ exports.ContentBaseMetrics = ContentBaseMetrics;
379
+ exports.ContentBaseMetricsSchema = mongoose_1.SchemaFactory.createForClass(ContentBaseMetrics);
380
+ /** Divisão Orgânico e Impulsionado; o total em Content.metrics é sempre a soma. Ver GD-707. */
381
+ let ContentMetricsBreakdown = class ContentMetricsBreakdown {
382
+ };
383
+ __decorate([
384
+ (0, mongoose_1.Prop)({ type: exports.ContentBaseMetricsSchema, required: false, default: () => ({}) }),
385
+ __metadata("design:type", ContentBaseMetrics)
386
+ ], ContentMetricsBreakdown.prototype, "organic", void 0);
387
+ __decorate([
388
+ (0, mongoose_1.Prop)({ type: exports.ContentBaseMetricsSchema, required: false, default: () => ({}) }),
389
+ __metadata("design:type", ContentBaseMetrics)
390
+ ], ContentMetricsBreakdown.prototype, "boosted", void 0);
391
+ ContentMetricsBreakdown = __decorate([
392
+ (0, mongoose_1.Schema)({ _id: false })
393
+ ], ContentMetricsBreakdown);
394
+ exports.ContentMetricsBreakdown = ContentMetricsBreakdown;
395
+ exports.ContentMetricsBreakdownSchema = mongoose_1.SchemaFactory.createForClass(ContentMetricsBreakdown);
396
+ /**
397
+ * Dados do vídeo para o VCR. Views não ficam aqui: impressões e views são
398
+ * sempre Content.metrics.impressionCount. Ver GD-707.
399
+ */
400
+ let ContentVideo = class ContentVideo {
401
+ };
402
+ __decorate([
403
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
404
+ __metadata("design:type", Object)
405
+ ], ContentVideo.prototype, "duration", void 0);
406
+ __decorate([
407
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
408
+ __metadata("design:type", Object)
409
+ ], ContentVideo.prototype, "watchTime", void 0);
410
+ __decorate([
411
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
412
+ __metadata("design:type", Object)
413
+ ], ContentVideo.prototype, "vcr", void 0);
414
+ ContentVideo = __decorate([
415
+ (0, mongoose_1.Schema)({ _id: false })
416
+ ], ContentVideo);
417
+ exports.ContentVideo = ContentVideo;
418
+ exports.ContentVideoSchema = mongoose_1.SchemaFactory.createForClass(ContentVideo);
215
419
  let ContentMetricsHistoryEntry = class ContentMetricsHistoryEntry {
216
420
  };
217
421
  __decorate([
@@ -391,15 +595,29 @@ __decorate([
391
595
  __metadata("design:type", Boolean)
392
596
  ], Content.prototype, "hasAffiliateLink", void 0);
393
597
  __decorate([
394
- (0, mongoose_1.Prop)([
395
- { type: mongoose_2.Schema.Types.ObjectId, ref: 'Profile', required: false },
396
- ]),
598
+ (0, mongoose_1.Prop)({ type: [exports.ContentCollabSchema], required: false, default: [] }),
397
599
  __metadata("design:type", Array)
398
600
  ], Content.prototype, "collabs", void 0);
601
+ __decorate([
602
+ (0, mongoose_1.Prop)({ type: exports.ContentPersonMetricsSchema, required: false, default: null }),
603
+ __metadata("design:type", Object)
604
+ ], Content.prototype, "profileMetrics", void 0);
399
605
  __decorate([
400
606
  (0, mongoose_1.Prop)({ type: exports.MetricsSchema, required: true }),
401
607
  __metadata("design:type", Metrics)
402
608
  ], Content.prototype, "metrics", void 0);
609
+ __decorate([
610
+ (0, mongoose_1.Prop)({ type: exports.ContentMetricsBreakdownSchema, required: false, default: null }),
611
+ __metadata("design:type", Object)
612
+ ], Content.prototype, "metricsBreakdown", void 0);
613
+ __decorate([
614
+ (0, mongoose_1.Prop)({ type: [String], required: false, default: [] }),
615
+ __metadata("design:type", Array)
616
+ ], Content.prototype, "manualMetrics", void 0);
617
+ __decorate([
618
+ (0, mongoose_1.Prop)({ type: exports.ContentVideoSchema, required: false, default: null }),
619
+ __metadata("design:type", Object)
620
+ ], Content.prototype, "video", void 0);
403
621
  __decorate([
404
622
  (0, mongoose_1.Prop)({ type: exports.MetricsSchema, required: true }),
405
623
  __metadata("design:type", Estimates)
@@ -500,6 +718,8 @@ exports.ContentSchema.index({ createdAt: -1, status: 1 });
500
718
  // Trava de aprovação entre Ações (GD-699): busca o mesmo post por publicação ou link.
501
719
  exports.ContentSchema.index({ primetagId: 1 });
502
720
  exports.ContentSchema.index({ linkKey: 1 });
721
+ // Visão por Influenciador (GD-703): posts em que o Perfil é coautor.
722
+ exports.ContentSchema.index({ 'collabs.profile': 1 });
503
723
  // Chave de idempotência da captura da Primetag — conteúdos manuais (sem
504
724
  // primetagId) ficam fora do índice.
505
725
  exports.ContentSchema.index({ action: 1, primetagId: 1 }, { unique: true, partialFilterExpression: { primetagId: { $type: 'string' } } });
@@ -0,0 +1,12 @@
1
+ import { ContentMetricsBreakdown, Metrics } from '../schemas/content.schema';
2
+ /**
3
+ * Totais de Content.metrics a partir da divisão Orgânico e Impulsionado. Ver GD-707.
4
+ *
5
+ * - metrics.<chave> = organic + boosted (null só se as duas forem null);
6
+ * - engagementCount = o da Primetag, a menos que alguma parte de curtidas,
7
+ * comentários, compartilhamentos ou salvos seja manual: aí vira a soma dos totais;
8
+ * - engagementRate = engagementCount ÷ impressionCount.
9
+ *
10
+ * Usada pela captura e pela edição (api-campaigns): as duas precisam dar o mesmo total.
11
+ */
12
+ export declare const mergeMetricsBreakdown: (breakdown: ContentMetricsBreakdown, primetagEngagementCount: number | null | undefined, manualMetrics?: string[]) => Partial<Metrics>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeMetricsBreakdown = void 0;
4
+ const content_schema_1 = require("../schemas/content.schema");
5
+ // Partes que, se digitadas à mão, fazem o engajamento virar a soma.
6
+ const ENGAGEMENT_KEYS = ['likeCount', 'commentCount', 'sharesCount', 'savedCount'];
7
+ const sumParts = (organic, boosted) => organic == null && boosted == null ? null : (organic ?? 0) + (boosted ?? 0);
8
+ /**
9
+ * Totais de Content.metrics a partir da divisão Orgânico e Impulsionado. Ver GD-707.
10
+ *
11
+ * - metrics.<chave> = organic + boosted (null só se as duas forem null);
12
+ * - engagementCount = o da Primetag, a menos que alguma parte de curtidas,
13
+ * comentários, compartilhamentos ou salvos seja manual: aí vira a soma dos totais;
14
+ * - engagementRate = engagementCount ÷ impressionCount.
15
+ *
16
+ * Usada pela captura e pela edição (api-campaigns): as duas precisam dar o mesmo total.
17
+ */
18
+ const mergeMetricsBreakdown = (breakdown, primetagEngagementCount, manualMetrics = []) => {
19
+ const organic = breakdown.organic ?? {};
20
+ const boosted = breakdown.boosted ?? {};
21
+ const totals = {};
22
+ for (const key of content_schema_1.CONTENT_BASE_METRIC_KEYS) {
23
+ totals[key] = sumParts(organic[key], boosted[key]);
24
+ }
25
+ const hasManualEngagement = ENGAGEMENT_KEYS.some((key) => manualMetrics.includes(`organic.${key}`) || manualMetrics.includes(`boosted.${key}`));
26
+ const engagementCount = hasManualEngagement
27
+ ? ENGAGEMENT_KEYS.reduce((sum, key) => sum + (totals[key] ?? 0), 0)
28
+ : primetagEngagementCount ?? null;
29
+ const impressionCount = totals.impressionCount ?? 0;
30
+ return {
31
+ ...totals,
32
+ engagementCount,
33
+ engagementRate: engagementCount != null && impressionCount > 0 ? engagementCount / impressionCount : null,
34
+ };
35
+ };
36
+ exports.mergeMetricsBreakdown = mergeMetricsBreakdown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grapadigital/shared-schemas",
3
- "version": "1.0.221",
3
+ "version": "1.0.222",
4
4
  "description": "Shared Mongoose Schemas",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -37,3 +37,4 @@ export * from './schemas/job.schema';
37
37
  export * from './schemas/snapshot.schema';
38
38
  export * from './schemas/lead.schema';
39
39
  export * from './utils/normalize-content-link.util';
40
+ export * from './utils/merge-metrics-breakdown.util';
@@ -157,10 +157,182 @@ export class Metrics {
157
157
 
158
158
  @Prop({ type: Number, required: false, default: null })
159
159
  cpe?: number | null;
160
+
161
+ /**
162
+ * Alcance Potencial (o antigo PO Reach): seguidores no momento do post. Em
163
+ * Content.metrics soma o autor e todos os coautores. Ver GD-703.
164
+ */
165
+ @Prop({ type: Number, required: false, default: null })
166
+ potentialReach?: number | null;
167
+
168
+ /** Tx. de Engaj. por Alcance: engagementCount ÷ reachCount (fração). Ver GD-703. */
169
+ @Prop({ type: Number, required: false, default: null })
170
+ reachEngagementRate?: number | null;
171
+
172
+ /** Tx. de Engaj. por Seguidores: engagementCount ÷ potentialReach (fração). Ver GD-703. */
173
+ @Prop({ type: Number, required: false, default: null })
174
+ followersEngagementRate?: number | null;
160
175
  }
161
176
 
162
177
  export const MetricsSchema = SchemaFactory.createForClass(Metrics);
163
178
 
179
+ /**
180
+ * Métricas que dependem dos seguidores de uma pessoa do post (autor ou
181
+ * coautor). As brutas ficam uma vez só em Content.metrics. Ver GD-703.
182
+ */
183
+ @Schema({ _id: false })
184
+ export class ContentPersonMetrics {
185
+ @Prop({ type: Number, required: false, default: null })
186
+ potentialReach?: number | null;
187
+
188
+ @Prop({ type: Number, required: false, default: null })
189
+ followersEngagementRate?: number | null;
190
+
191
+ @Prop({ type: Number, required: false, default: null })
192
+ effectiveReach?: number | null;
193
+
194
+ @Prop({ type: Number, required: false, default: null })
195
+ reachPerformance?: number | null;
196
+
197
+ @Prop({ type: Number, required: false, default: null })
198
+ qualifiedEngagementRate?: number | null;
199
+
200
+ @Prop({ type: Number, required: false, default: null })
201
+ score?: number | null;
202
+
203
+ /** Nome do Client.tiers da pessoa; null sem Tiers ou fora das faixas. */
204
+ @Prop({ type: String, required: false, default: null })
205
+ tier?: string | null;
206
+
207
+ /** reachEngagementRate ≥ meta viralReachEngagementRate do Tier; null sem Tier ou sem a meta. */
208
+ @Prop({ type: Boolean, required: false, default: null })
209
+ isViral?: boolean | null;
210
+ }
211
+
212
+ export const ContentPersonMetricsSchema = SchemaFactory.createForClass(ContentPersonMetrics);
213
+
214
+ /** Coautor do post (Colab), com os seguidores dele no momento do post. Ver GD-703. */
215
+ @Schema({ _id: false })
216
+ export class ContentCollab {
217
+ @Prop({ type: MongooseSchema.Types.ObjectId, ref: 'Profile', required: true })
218
+ profile: Types.ObjectId | Profile;
219
+
220
+ @Prop({ type: Number, required: false, default: null })
221
+ followersAtPost?: number | null;
222
+
223
+ @Prop({ type: ContentPersonMetricsSchema, required: false, default: null })
224
+ metrics?: ContentPersonMetrics | null;
225
+ }
226
+
227
+ export const ContentCollabSchema = SchemaFactory.createForClass(ContentCollab);
228
+
229
+ /** Chaves brutas de Metrics que têm parte Orgânica e Impulsionada. Ver GD-707. */
230
+ export const CONTENT_BASE_METRIC_KEYS = [
231
+ 'impressionCount',
232
+ 'reachCount',
233
+ 'likeCount',
234
+ 'commentCount',
235
+ 'savedCount',
236
+ 'sharesCount',
237
+ 'storyCount',
238
+ 'tapBackCount',
239
+ 'clickCount',
240
+ 'replyCount',
241
+ 'tapForwardCount',
242
+ 'swipeForwardCount',
243
+ 'exitCount',
244
+ 'favoriteCount',
245
+ 'dislikeCount',
246
+ 'emv',
247
+ ] as const;
248
+ export type ContentBaseMetricKey = (typeof CONTENT_BASE_METRIC_KEYS)[number];
249
+
250
+ @Schema({ _id: false })
251
+ export class ContentBaseMetrics {
252
+ @Prop({ type: Number, required: false, default: null })
253
+ impressionCount?: number | null;
254
+
255
+ @Prop({ type: Number, required: false, default: null })
256
+ reachCount?: number | null;
257
+
258
+ @Prop({ type: Number, required: false, default: null })
259
+ likeCount?: number | null;
260
+
261
+ @Prop({ type: Number, required: false, default: null })
262
+ commentCount?: number | null;
263
+
264
+ @Prop({ type: Number, required: false, default: null })
265
+ savedCount?: number | null;
266
+
267
+ @Prop({ type: Number, required: false, default: null })
268
+ sharesCount?: number | null;
269
+
270
+ @Prop({ type: Number, required: false, default: null })
271
+ storyCount?: number | null;
272
+
273
+ @Prop({ type: Number, required: false, default: null })
274
+ tapBackCount?: number | null;
275
+
276
+ @Prop({ type: Number, required: false, default: null })
277
+ clickCount?: number | null;
278
+
279
+ @Prop({ type: Number, required: false, default: null })
280
+ replyCount?: number | null;
281
+
282
+ @Prop({ type: Number, required: false, default: null })
283
+ tapForwardCount?: number | null;
284
+
285
+ @Prop({ type: Number, required: false, default: null })
286
+ swipeForwardCount?: number | null;
287
+
288
+ @Prop({ type: Number, required: false, default: null })
289
+ exitCount?: number | null;
290
+
291
+ @Prop({ type: Number, required: false, default: null })
292
+ favoriteCount?: number | null;
293
+
294
+ @Prop({ type: Number, required: false, default: null })
295
+ dislikeCount?: number | null;
296
+
297
+ @Prop({ type: Number, required: false, default: null })
298
+ emv?: number | null;
299
+ }
300
+
301
+ export const ContentBaseMetricsSchema = SchemaFactory.createForClass(ContentBaseMetrics);
302
+
303
+ /** Divisão Orgânico e Impulsionado; o total em Content.metrics é sempre a soma. Ver GD-707. */
304
+ @Schema({ _id: false })
305
+ export class ContentMetricsBreakdown {
306
+ @Prop({ type: ContentBaseMetricsSchema, required: false, default: () => ({}) })
307
+ organic: ContentBaseMetrics;
308
+
309
+ @Prop({ type: ContentBaseMetricsSchema, required: false, default: () => ({}) })
310
+ boosted: ContentBaseMetrics;
311
+ }
312
+
313
+ export const ContentMetricsBreakdownSchema = SchemaFactory.createForClass(ContentMetricsBreakdown);
314
+
315
+ /**
316
+ * Dados do vídeo para o VCR. Views não ficam aqui: impressões e views são
317
+ * sempre Content.metrics.impressionCount. Ver GD-707.
318
+ */
319
+ @Schema({ _id: false })
320
+ export class ContentVideo {
321
+ /** Duração em segundos. */
322
+ @Prop({ type: Number, required: false, default: null })
323
+ duration?: number | null;
324
+
325
+ /** Tempo total assistido, somando todas as visualizações, em segundos. */
326
+ @Prop({ type: Number, required: false, default: null })
327
+ watchTime?: number | null;
328
+
329
+ /** (watchTime ÷ duration) ÷ impressionCount, sem teto. */
330
+ @Prop({ type: Number, required: false, default: null })
331
+ vcr?: number | null;
332
+ }
333
+
334
+ export const ContentVideoSchema = SchemaFactory.createForClass(ContentVideo);
335
+
164
336
  @Schema({ _id: false })
165
337
  export class ContentMetricsHistoryEntry {
166
338
  @Prop({ type: MetricsSchema, required: true })
@@ -330,14 +502,31 @@ export class Content {
330
502
  @Prop({ type: Boolean, required: true, default: false })
331
503
  hasAffiliateLink: boolean;
332
504
 
333
- @Prop([
334
- { type: MongooseSchema.Types.ObjectId, ref: 'Profile', required: false },
335
- ])
336
- collabs?: Types.ObjectId[] | Profile[];
505
+ /** Coautores do post; isCollab = collabs.length > 0. Ver GD-703. */
506
+ @Prop({ type: [ContentCollabSchema], required: false, default: [] })
507
+ collabs?: ContentCollab[];
337
508
 
509
+ /** Métricas do autor, com os seguidores dele (followersAtPost). Ver GD-703. */
510
+ @Prop({ type: ContentPersonMetricsSchema, required: false, default: null })
511
+ profileMetrics?: ContentPersonMetrics | null;
512
+
513
+ /** Visão da Ação: brutas uma vez só e derivadas com o potentialReach somado. */
338
514
  @Prop({ type: MetricsSchema, required: true })
339
515
  metrics: Metrics;
340
516
 
517
+ @Prop({ type: ContentMetricsBreakdownSchema, required: false, default: null })
518
+ metricsBreakdown?: ContentMetricsBreakdown | null;
519
+
520
+ /**
521
+ * Caminhos das partes digitadas à mão (ex.: organic.reachCount,
522
+ * video.watchTime): a captura não sobrescreve. Ver GD-707.
523
+ */
524
+ @Prop({ type: [String], required: false, default: [] })
525
+ manualMetrics?: string[];
526
+
527
+ @Prop({ type: ContentVideoSchema, required: false, default: null })
528
+ video?: ContentVideo | null;
529
+
341
530
  @Prop({ type: MetricsSchema, required: true })
342
531
  estimates: Estimates;
343
532
 
@@ -368,9 +557,11 @@ export class Content {
368
557
  @Prop({ type: String, required: false })
369
558
  mediaUrl?: string;
370
559
 
560
+ /** @deprecated Obsoleto desde a GD-707: a edição vale por parte (manualMetrics). */
371
561
  @Prop({ type: Boolean, required: false, default: false })
372
562
  isEdited?: boolean;
373
563
 
564
+ /** @deprecated Obsoleto desde a GD-707. */
374
565
  @Prop({ type: Date, required: false })
375
566
  editedAt?: Date;
376
567
 
@@ -437,6 +628,8 @@ ContentSchema.index({ createdAt: -1, status: 1 });
437
628
  // Trava de aprovação entre Ações (GD-699): busca o mesmo post por publicação ou link.
438
629
  ContentSchema.index({ primetagId: 1 });
439
630
  ContentSchema.index({ linkKey: 1 });
631
+ // Visão por Influenciador (GD-703): posts em que o Perfil é coautor.
632
+ ContentSchema.index({ 'collabs.profile': 1 });
440
633
  // Chave de idempotência da captura da Primetag — conteúdos manuais (sem
441
634
  // primetagId) ficam fora do índice.
442
635
  ContentSchema.index(
@@ -0,0 +1,51 @@
1
+ import {
2
+ CONTENT_BASE_METRIC_KEYS,
3
+ ContentBaseMetricKey,
4
+ ContentBaseMetrics,
5
+ ContentMetricsBreakdown,
6
+ Metrics,
7
+ } from '../schemas/content.schema';
8
+
9
+ // Partes que, se digitadas à mão, fazem o engajamento virar a soma.
10
+ const ENGAGEMENT_KEYS: ContentBaseMetricKey[] = ['likeCount', 'commentCount', 'sharesCount', 'savedCount'];
11
+
12
+ const sumParts = (organic?: number | null, boosted?: number | null): number | null =>
13
+ organic == null && boosted == null ? null : (organic ?? 0) + (boosted ?? 0);
14
+
15
+ /**
16
+ * Totais de Content.metrics a partir da divisão Orgânico e Impulsionado. Ver GD-707.
17
+ *
18
+ * - metrics.<chave> = organic + boosted (null só se as duas forem null);
19
+ * - engagementCount = o da Primetag, a menos que alguma parte de curtidas,
20
+ * comentários, compartilhamentos ou salvos seja manual: aí vira a soma dos totais;
21
+ * - engagementRate = engagementCount ÷ impressionCount.
22
+ *
23
+ * Usada pela captura e pela edição (api-campaigns): as duas precisam dar o mesmo total.
24
+ */
25
+ export const mergeMetricsBreakdown = (
26
+ breakdown: ContentMetricsBreakdown,
27
+ primetagEngagementCount: number | null | undefined,
28
+ manualMetrics: string[] = [],
29
+ ): Partial<Metrics> => {
30
+ const organic: ContentBaseMetrics = breakdown.organic ?? {};
31
+ const boosted: ContentBaseMetrics = breakdown.boosted ?? {};
32
+
33
+ const totals: Partial<Metrics> = {};
34
+ for (const key of CONTENT_BASE_METRIC_KEYS) {
35
+ totals[key] = sumParts(organic[key], boosted[key]);
36
+ }
37
+
38
+ const hasManualEngagement = ENGAGEMENT_KEYS.some(
39
+ (key) => manualMetrics.includes(`organic.${key}`) || manualMetrics.includes(`boosted.${key}`),
40
+ );
41
+ const engagementCount = hasManualEngagement
42
+ ? ENGAGEMENT_KEYS.reduce((sum, key) => sum + (totals[key] ?? 0), 0)
43
+ : primetagEngagementCount ?? null;
44
+
45
+ const impressionCount = totals.impressionCount ?? 0;
46
+ return {
47
+ ...totals,
48
+ engagementCount,
49
+ engagementRate: engagementCount != null && impressionCount > 0 ? engagementCount / impressionCount : null,
50
+ };
51
+ };