@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 +1 -0
- package/dist/index.js +1 -0
- package/dist/schemas/content.schema.d.ts +128 -1
- package/dist/schemas/content.schema.js +224 -4
- package/dist/utils/merge-metrics-breakdown.util.d.ts +12 -0
- package/dist/utils/merge-metrics-breakdown.util.js +36 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/schemas/content.schema.ts +197 -4
- package/src/utils/merge-metrics-breakdown.util.ts +51 -0
package/dist/index.d.ts
CHANGED
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
|
|
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
package/src/index.ts
CHANGED
|
@@ -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
|
-
|
|
334
|
-
|
|
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
|
+
};
|