@grapadigital/shared-schemas 1.0.197 → 1.0.198
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/schemas/client.schema.d.ts +8 -1
- package/dist/schemas/client.schema.js +1 -1
- package/dist/schemas/comment.schema.d.ts +36 -0
- package/dist/schemas/comment.schema.js +68 -1
- package/dist/schemas/content.schema.d.ts +29 -1
- package/dist/schemas/content.schema.js +65 -3
- package/package.json +1 -1
- package/src/schemas/client.schema.ts +8 -1
- package/src/schemas/comment.schema.ts +57 -0
- package/src/schemas/content.schema.ts +69 -3
|
@@ -122,7 +122,14 @@ export declare class Client extends Party {
|
|
|
122
122
|
* time já resolve (padrão da maioria dos clientes). Ver GD-532/GD-535.
|
|
123
123
|
*/
|
|
124
124
|
requiredTeams: string[];
|
|
125
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Análise GenIA: captura de comentários, classificação por IA e tudo que
|
|
127
|
+
* depende deles (spam, taxa de engajamento qualificado, score, CPMI). Sem
|
|
128
|
+
* isso, os conteúdos do cliente só recebem as métricas base da Primetag.
|
|
129
|
+
* Também define quais clientes aparecem no hub de relatórios GenIA.
|
|
130
|
+
* (Antigo `hasReport`.)
|
|
131
|
+
*/
|
|
132
|
+
hasGenIAAnalysis: boolean;
|
|
126
133
|
reportResponsable?: Responsable;
|
|
127
134
|
}
|
|
128
135
|
export declare const ClientSchema: import("mongoose").Schema<Client, import("mongoose").Model<Client, any, any, any, import("mongoose").Document<unknown, any, Client, any, {}> & Client & Required<{
|
|
@@ -164,7 +164,7 @@ __decorate([
|
|
|
164
164
|
__decorate([
|
|
165
165
|
(0, mongoose_1.Prop)({ type: Boolean, default: false }),
|
|
166
166
|
__metadata("design:type", Boolean)
|
|
167
|
-
], Client.prototype, "
|
|
167
|
+
], Client.prototype, "hasGenIAAnalysis", void 0);
|
|
168
168
|
__decorate([
|
|
169
169
|
(0, mongoose_1.Prop)({ type: exports.ResponsableSchema, required: false }),
|
|
170
170
|
__metadata("design:type", Responsable)
|
|
@@ -33,6 +33,37 @@ export declare const COMMENT_SUBJECT_VALUES: readonly ["compliment_to_person", "
|
|
|
33
33
|
export type CommentSubject = (typeof COMMENT_SUBJECT_VALUES)[number];
|
|
34
34
|
export declare const COMMENT_CLASSIFICATION_STATUS_VALUES: readonly ["pending", "processing", "completed", "failed", "not_applicable"];
|
|
35
35
|
export type CommentClassificationStatus = (typeof COMMENT_CLASSIFICATION_STATUS_VALUES)[number];
|
|
36
|
+
export declare const COMMENT_SPAM_RULE_VALUES: readonly ["duplicate_text", "repeat_author", "manual", "unknown"];
|
|
37
|
+
export type CommentSpamRule = (typeof COMMENT_SPAM_RULE_VALUES)[number];
|
|
38
|
+
export declare class CommentSpamReason {
|
|
39
|
+
rule: CommentSpamRule;
|
|
40
|
+
threshold?: number;
|
|
41
|
+
matchCount?: number;
|
|
42
|
+
groupKey?: string;
|
|
43
|
+
}
|
|
44
|
+
export declare const CommentSpamReasonSchema: MongooseSchema<CommentSpamReason, import("mongoose").Model<CommentSpamReason, any, any, any, import("mongoose").Document<unknown, any, CommentSpamReason, any, {}> & CommentSpamReason & {
|
|
45
|
+
_id: Types.ObjectId;
|
|
46
|
+
} & {
|
|
47
|
+
__v: number;
|
|
48
|
+
}, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, CommentSpamReason, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<CommentSpamReason>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<CommentSpamReason> & {
|
|
49
|
+
_id: Types.ObjectId;
|
|
50
|
+
} & {
|
|
51
|
+
__v: number;
|
|
52
|
+
}>;
|
|
53
|
+
export declare class CommentClassifiedBy {
|
|
54
|
+
team?: string;
|
|
55
|
+
name?: string;
|
|
56
|
+
email?: string;
|
|
57
|
+
}
|
|
58
|
+
export declare const CommentClassifiedBySchema: MongooseSchema<CommentClassifiedBy, import("mongoose").Model<CommentClassifiedBy, any, any, any, import("mongoose").Document<unknown, any, CommentClassifiedBy, any, {}> & CommentClassifiedBy & {
|
|
59
|
+
_id: Types.ObjectId;
|
|
60
|
+
} & {
|
|
61
|
+
__v: number;
|
|
62
|
+
}, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, CommentClassifiedBy, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<CommentClassifiedBy>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<CommentClassifiedBy> & {
|
|
63
|
+
_id: Types.ObjectId;
|
|
64
|
+
} & {
|
|
65
|
+
__v: number;
|
|
66
|
+
}>;
|
|
36
67
|
export declare class Comment {
|
|
37
68
|
_id?: Types.ObjectId;
|
|
38
69
|
content: Types.ObjectId | Content;
|
|
@@ -52,6 +83,11 @@ export declare class Comment {
|
|
|
52
83
|
funnel?: 'awareness' | 'consideration' | 'conversion' | 'none' | null;
|
|
53
84
|
isEmojiOnly?: boolean;
|
|
54
85
|
isSpam?: boolean;
|
|
86
|
+
spamReason?: CommentSpamReason | null;
|
|
87
|
+
spamOverride?: boolean;
|
|
88
|
+
isManuallyClassified?: boolean;
|
|
89
|
+
manuallyClassifiedAt?: Date | null;
|
|
90
|
+
manuallyClassifiedBy?: CommentClassifiedBy | null;
|
|
55
91
|
classificationStatus?: CommentClassificationStatus;
|
|
56
92
|
classificationError?: string | null;
|
|
57
93
|
classificationProcessedAt?: Date | null;
|
|
@@ -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.CommentModel = exports.CommentSchema = exports.Comment = exports.COMMENT_CLASSIFICATION_STATUS_VALUES = exports.COMMENT_SUBJECT_VALUES = exports.COMMENT_TARGET_VALUES = void 0;
|
|
12
|
+
exports.CommentModel = exports.CommentSchema = exports.Comment = exports.CommentClassifiedBySchema = exports.CommentClassifiedBy = exports.CommentSpamReasonSchema = exports.CommentSpamReason = exports.COMMENT_SPAM_RULE_VALUES = exports.COMMENT_CLASSIFICATION_STATUS_VALUES = exports.COMMENT_SUBJECT_VALUES = exports.COMMENT_TARGET_VALUES = void 0;
|
|
13
13
|
const mongoose_1 = require("@nestjs/mongoose");
|
|
14
14
|
const mongoose_2 = require("mongoose");
|
|
15
15
|
exports.COMMENT_TARGET_VALUES = ['brand', 'influencer', 'content', 'emoji', 'spam'];
|
|
@@ -37,6 +37,52 @@ exports.COMMENT_CLASSIFICATION_STATUS_VALUES = [
|
|
|
37
37
|
'failed',
|
|
38
38
|
'not_applicable',
|
|
39
39
|
];
|
|
40
|
+
exports.COMMENT_SPAM_RULE_VALUES = ['duplicate_text', 'repeat_author', 'manual', 'unknown'];
|
|
41
|
+
// Por que o comentário foi marcado como spam. `groupKey` identifica os outros
|
|
42
|
+
// comentários do mesmo conteúdo que caíram na mesma regra: autor normalizado
|
|
43
|
+
// (repeat_author) ou `autor::texto` normalizado (duplicate_text).
|
|
44
|
+
let CommentSpamReason = class CommentSpamReason {
|
|
45
|
+
};
|
|
46
|
+
__decorate([
|
|
47
|
+
(0, mongoose_1.Prop)({ type: String, enum: exports.COMMENT_SPAM_RULE_VALUES, required: true }),
|
|
48
|
+
__metadata("design:type", String)
|
|
49
|
+
], CommentSpamReason.prototype, "rule", void 0);
|
|
50
|
+
__decorate([
|
|
51
|
+
(0, mongoose_1.Prop)({ type: Number, required: false }),
|
|
52
|
+
__metadata("design:type", Number)
|
|
53
|
+
], CommentSpamReason.prototype, "threshold", void 0);
|
|
54
|
+
__decorate([
|
|
55
|
+
(0, mongoose_1.Prop)({ type: Number, required: false }),
|
|
56
|
+
__metadata("design:type", Number)
|
|
57
|
+
], CommentSpamReason.prototype, "matchCount", void 0);
|
|
58
|
+
__decorate([
|
|
59
|
+
(0, mongoose_1.Prop)({ type: String, required: false }),
|
|
60
|
+
__metadata("design:type", String)
|
|
61
|
+
], CommentSpamReason.prototype, "groupKey", void 0);
|
|
62
|
+
CommentSpamReason = __decorate([
|
|
63
|
+
(0, mongoose_1.Schema)({ _id: false })
|
|
64
|
+
], CommentSpamReason);
|
|
65
|
+
exports.CommentSpamReason = CommentSpamReason;
|
|
66
|
+
exports.CommentSpamReasonSchema = mongoose_1.SchemaFactory.createForClass(CommentSpamReason);
|
|
67
|
+
let CommentClassifiedBy = class CommentClassifiedBy {
|
|
68
|
+
};
|
|
69
|
+
__decorate([
|
|
70
|
+
(0, mongoose_1.Prop)({ type: String, required: false }),
|
|
71
|
+
__metadata("design:type", String)
|
|
72
|
+
], CommentClassifiedBy.prototype, "team", void 0);
|
|
73
|
+
__decorate([
|
|
74
|
+
(0, mongoose_1.Prop)({ type: String, required: false }),
|
|
75
|
+
__metadata("design:type", String)
|
|
76
|
+
], CommentClassifiedBy.prototype, "name", void 0);
|
|
77
|
+
__decorate([
|
|
78
|
+
(0, mongoose_1.Prop)({ type: String, required: false }),
|
|
79
|
+
__metadata("design:type", String)
|
|
80
|
+
], CommentClassifiedBy.prototype, "email", void 0);
|
|
81
|
+
CommentClassifiedBy = __decorate([
|
|
82
|
+
(0, mongoose_1.Schema)({ _id: false })
|
|
83
|
+
], CommentClassifiedBy);
|
|
84
|
+
exports.CommentClassifiedBy = CommentClassifiedBy;
|
|
85
|
+
exports.CommentClassifiedBySchema = mongoose_1.SchemaFactory.createForClass(CommentClassifiedBy);
|
|
40
86
|
let Comment = class Comment {
|
|
41
87
|
};
|
|
42
88
|
__decorate([
|
|
@@ -131,6 +177,26 @@ __decorate([
|
|
|
131
177
|
(0, mongoose_1.Prop)({ type: Boolean, required: false, default: false }),
|
|
132
178
|
__metadata("design:type", Boolean)
|
|
133
179
|
], Comment.prototype, "isSpam", void 0);
|
|
180
|
+
__decorate([
|
|
181
|
+
(0, mongoose_1.Prop)({ type: exports.CommentSpamReasonSchema, required: false, default: null }),
|
|
182
|
+
__metadata("design:type", Object)
|
|
183
|
+
], Comment.prototype, "spamReason", void 0);
|
|
184
|
+
__decorate([
|
|
185
|
+
(0, mongoose_1.Prop)({ type: Boolean, required: false, default: false }),
|
|
186
|
+
__metadata("design:type", Boolean)
|
|
187
|
+
], Comment.prototype, "spamOverride", void 0);
|
|
188
|
+
__decorate([
|
|
189
|
+
(0, mongoose_1.Prop)({ type: Boolean, required: false, default: false }),
|
|
190
|
+
__metadata("design:type", Boolean)
|
|
191
|
+
], Comment.prototype, "isManuallyClassified", void 0);
|
|
192
|
+
__decorate([
|
|
193
|
+
(0, mongoose_1.Prop)({ type: Date, required: false, default: null }),
|
|
194
|
+
__metadata("design:type", Object)
|
|
195
|
+
], Comment.prototype, "manuallyClassifiedAt", void 0);
|
|
196
|
+
__decorate([
|
|
197
|
+
(0, mongoose_1.Prop)({ type: exports.CommentClassifiedBySchema, required: false, default: null }),
|
|
198
|
+
__metadata("design:type", Object)
|
|
199
|
+
], Comment.prototype, "manuallyClassifiedBy", void 0);
|
|
134
200
|
__decorate([
|
|
135
201
|
(0, mongoose_1.Prop)({
|
|
136
202
|
type: String,
|
|
@@ -174,4 +240,5 @@ exports.CommentSchema.index({ action: 1, funnel: 1 });
|
|
|
174
240
|
exports.CommentSchema.index({ action: 1, target: 1 });
|
|
175
241
|
exports.CommentSchema.index({ content: 1, parent: 1 });
|
|
176
242
|
exports.CommentSchema.index({ content: 1, classificationStatus: 1 });
|
|
243
|
+
exports.CommentSchema.index({ content: 1, 'spamReason.groupKey': 1 });
|
|
177
244
|
exports.CommentModel = (0, mongoose_2.model)('Comment', exports.CommentSchema);
|
|
@@ -97,6 +97,11 @@ export declare class Metrics {
|
|
|
97
97
|
tapBackCount?: number | null;
|
|
98
98
|
clickCount?: number | null;
|
|
99
99
|
replyCount?: number | null;
|
|
100
|
+
tapForwardCount?: number | null;
|
|
101
|
+
swipeForwardCount?: number | null;
|
|
102
|
+
exitCount?: number | null;
|
|
103
|
+
favoriteCount?: number | null;
|
|
104
|
+
dislikeCount?: number | null;
|
|
100
105
|
emv?: number | null;
|
|
101
106
|
engagementCount?: number | null;
|
|
102
107
|
engagementRate?: number | null;
|
|
@@ -146,6 +151,25 @@ export declare const EstimatesSchema: MongooseSchema<Estimates, import("mongoose
|
|
|
146
151
|
} & {
|
|
147
152
|
__v: number;
|
|
148
153
|
}>;
|
|
154
|
+
export declare const CONTENT_STATUS_VALUES: readonly ["pending", "approved", "deleted", "processing", "classified"];
|
|
155
|
+
export type ContentStatus = (typeof CONTENT_STATUS_VALUES)[number];
|
|
156
|
+
export declare const CONTENT_REPORTABLE_STATUS_VALUES: ContentStatus[];
|
|
157
|
+
export declare const CONTENT_METRICS_SOURCE_VALUES: readonly ["real", "organic", "estimated"];
|
|
158
|
+
export type ContentMetricsSource = (typeof CONTENT_METRICS_SOURCE_VALUES)[number];
|
|
159
|
+
export declare class ContentStatusChangedBy {
|
|
160
|
+
team?: string;
|
|
161
|
+
name?: string;
|
|
162
|
+
email?: string;
|
|
163
|
+
}
|
|
164
|
+
export declare const ContentStatusChangedBySchema: MongooseSchema<ContentStatusChangedBy, import("mongoose").Model<ContentStatusChangedBy, any, any, any, import("mongoose").Document<unknown, any, ContentStatusChangedBy, any, {}> & ContentStatusChangedBy & {
|
|
165
|
+
_id: Types.ObjectId;
|
|
166
|
+
} & {
|
|
167
|
+
__v: number;
|
|
168
|
+
}, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, ContentStatusChangedBy, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<ContentStatusChangedBy>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<ContentStatusChangedBy> & {
|
|
169
|
+
_id: Types.ObjectId;
|
|
170
|
+
} & {
|
|
171
|
+
__v: number;
|
|
172
|
+
}>;
|
|
149
173
|
export declare class Content {
|
|
150
174
|
_id?: Types.ObjectId;
|
|
151
175
|
caption?: string;
|
|
@@ -153,7 +177,11 @@ export declare class Content {
|
|
|
153
177
|
type: 'reel' | 'carousel' | 'image' | 'story' | 'video' | 'post' | 'pin' | 'tweet';
|
|
154
178
|
tagType: 'withTag' | 'withoutTag' | 'brandExposure';
|
|
155
179
|
socialnetwork: 'instagram' | 'tiktok' | 'youtube' | 'pinterest' | 'linkedin' | 'twitter';
|
|
156
|
-
status:
|
|
180
|
+
status: ContentStatus;
|
|
181
|
+
metricsSource?: ContentMetricsSource | null;
|
|
182
|
+
processedCommentCount?: number | null;
|
|
183
|
+
approvedAt?: Date | null;
|
|
184
|
+
approvedBy?: ContentStatusChangedBy | null;
|
|
157
185
|
profile: Types.ObjectId | Profile;
|
|
158
186
|
action: Types.ObjectId | Action;
|
|
159
187
|
postedAt: Date;
|
|
@@ -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.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_METRICS_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;
|
|
13
13
|
const mongoose_1 = require("@nestjs/mongoose");
|
|
14
14
|
const mongoose_2 = require("mongoose");
|
|
15
15
|
let SentimentComment = class SentimentComment {
|
|
@@ -142,6 +142,26 @@ __decorate([
|
|
|
142
142
|
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
143
143
|
__metadata("design:type", Object)
|
|
144
144
|
], Metrics.prototype, "replyCount", void 0);
|
|
145
|
+
__decorate([
|
|
146
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
147
|
+
__metadata("design:type", Object)
|
|
148
|
+
], Metrics.prototype, "tapForwardCount", void 0);
|
|
149
|
+
__decorate([
|
|
150
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
151
|
+
__metadata("design:type", Object)
|
|
152
|
+
], Metrics.prototype, "swipeForwardCount", void 0);
|
|
153
|
+
__decorate([
|
|
154
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
155
|
+
__metadata("design:type", Object)
|
|
156
|
+
], Metrics.prototype, "exitCount", void 0);
|
|
157
|
+
__decorate([
|
|
158
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
159
|
+
__metadata("design:type", Object)
|
|
160
|
+
], Metrics.prototype, "favoriteCount", void 0);
|
|
161
|
+
__decorate([
|
|
162
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
163
|
+
__metadata("design:type", Object)
|
|
164
|
+
], Metrics.prototype, "dislikeCount", void 0);
|
|
145
165
|
__decorate([
|
|
146
166
|
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
147
167
|
__metadata("design:type", Object)
|
|
@@ -229,6 +249,29 @@ Estimates = __decorate([
|
|
|
229
249
|
], Estimates);
|
|
230
250
|
exports.Estimates = Estimates;
|
|
231
251
|
exports.EstimatesSchema = mongoose_1.SchemaFactory.createForClass(Estimates);
|
|
252
|
+
exports.CONTENT_STATUS_VALUES = ['pending', 'approved', 'deleted', 'processing', 'classified'];
|
|
253
|
+
// Status que entram em relatórios/KPIs (pending e deleted ficam de fora).
|
|
254
|
+
exports.CONTENT_REPORTABLE_STATUS_VALUES = ['approved', 'processing', 'classified'];
|
|
255
|
+
exports.CONTENT_METRICS_SOURCE_VALUES = ['real', 'organic', 'estimated'];
|
|
256
|
+
let ContentStatusChangedBy = class ContentStatusChangedBy {
|
|
257
|
+
};
|
|
258
|
+
__decorate([
|
|
259
|
+
(0, mongoose_1.Prop)({ type: String, required: false }),
|
|
260
|
+
__metadata("design:type", String)
|
|
261
|
+
], ContentStatusChangedBy.prototype, "team", void 0);
|
|
262
|
+
__decorate([
|
|
263
|
+
(0, mongoose_1.Prop)({ type: String, required: false }),
|
|
264
|
+
__metadata("design:type", String)
|
|
265
|
+
], ContentStatusChangedBy.prototype, "name", void 0);
|
|
266
|
+
__decorate([
|
|
267
|
+
(0, mongoose_1.Prop)({ type: String, required: false }),
|
|
268
|
+
__metadata("design:type", String)
|
|
269
|
+
], ContentStatusChangedBy.prototype, "email", void 0);
|
|
270
|
+
ContentStatusChangedBy = __decorate([
|
|
271
|
+
(0, mongoose_1.Schema)({ _id: false })
|
|
272
|
+
], ContentStatusChangedBy);
|
|
273
|
+
exports.ContentStatusChangedBy = ContentStatusChangedBy;
|
|
274
|
+
exports.ContentStatusChangedBySchema = mongoose_1.SchemaFactory.createForClass(ContentStatusChangedBy);
|
|
232
275
|
let Content = class Content {
|
|
233
276
|
};
|
|
234
277
|
__decorate([
|
|
@@ -270,12 +313,28 @@ __decorate([
|
|
|
270
313
|
__decorate([
|
|
271
314
|
(0, mongoose_1.Prop)({
|
|
272
315
|
type: String,
|
|
273
|
-
enum:
|
|
316
|
+
enum: exports.CONTENT_STATUS_VALUES,
|
|
274
317
|
required: true,
|
|
275
|
-
default: '
|
|
318
|
+
default: 'pending',
|
|
276
319
|
}),
|
|
277
320
|
__metadata("design:type", String)
|
|
278
321
|
], Content.prototype, "status", void 0);
|
|
322
|
+
__decorate([
|
|
323
|
+
(0, mongoose_1.Prop)({ type: String, enum: exports.CONTENT_METRICS_SOURCE_VALUES, required: false, default: null }),
|
|
324
|
+
__metadata("design:type", Object)
|
|
325
|
+
], Content.prototype, "metricsSource", void 0);
|
|
326
|
+
__decorate([
|
|
327
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
328
|
+
__metadata("design:type", Object)
|
|
329
|
+
], Content.prototype, "processedCommentCount", void 0);
|
|
330
|
+
__decorate([
|
|
331
|
+
(0, mongoose_1.Prop)({ type: Date, required: false, default: null }),
|
|
332
|
+
__metadata("design:type", Object)
|
|
333
|
+
], Content.prototype, "approvedAt", void 0);
|
|
334
|
+
__decorate([
|
|
335
|
+
(0, mongoose_1.Prop)({ type: exports.ContentStatusChangedBySchema, required: false, default: null }),
|
|
336
|
+
__metadata("design:type", Object)
|
|
337
|
+
], Content.prototype, "approvedBy", void 0);
|
|
279
338
|
__decorate([
|
|
280
339
|
(0, mongoose_1.Prop)({ type: mongoose_2.Schema.Types.ObjectId, ref: 'Profile', required: true }),
|
|
281
340
|
__metadata("design:type", Object)
|
|
@@ -369,4 +428,7 @@ exports.ContentSchema.index({ action: 1, profile: 1, socialnetwork: 1 });
|
|
|
369
428
|
exports.ContentSchema.index({ action: 1, status: 1, postedAt: 1 });
|
|
370
429
|
exports.ContentSchema.index({ profile: 1 });
|
|
371
430
|
exports.ContentSchema.index({ createdAt: -1, status: 1 });
|
|
431
|
+
// Chave de idempotência da captura da Primetag — conteúdos manuais (sem
|
|
432
|
+
// primetagId) ficam fora do índice.
|
|
433
|
+
exports.ContentSchema.index({ action: 1, primetagId: 1 }, { unique: true, partialFilterExpression: { primetagId: { $type: 'string' } } });
|
|
372
434
|
exports.ContentModel = (0, mongoose_2.model)('Content', exports.ContentSchema);
|
package/package.json
CHANGED
|
@@ -121,8 +121,15 @@ export class Client extends Party {
|
|
|
121
121
|
@Prop({ type: [String], default: [] })
|
|
122
122
|
requiredTeams: string[];
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Análise GenIA: captura de comentários, classificação por IA e tudo que
|
|
126
|
+
* depende deles (spam, taxa de engajamento qualificado, score, CPMI). Sem
|
|
127
|
+
* isso, os conteúdos do cliente só recebem as métricas base da Primetag.
|
|
128
|
+
* Também define quais clientes aparecem no hub de relatórios GenIA.
|
|
129
|
+
* (Antigo `hasReport`.)
|
|
130
|
+
*/
|
|
124
131
|
@Prop({ type: Boolean, default: false })
|
|
125
|
-
|
|
132
|
+
hasGenIAAnalysis: boolean;
|
|
126
133
|
|
|
127
134
|
@Prop({ type: ResponsableSchema, required: false })
|
|
128
135
|
reportResponsable?: Responsable;
|
|
@@ -41,6 +41,43 @@ export const COMMENT_CLASSIFICATION_STATUS_VALUES = [
|
|
|
41
41
|
] as const;
|
|
42
42
|
export type CommentClassificationStatus = (typeof COMMENT_CLASSIFICATION_STATUS_VALUES)[number];
|
|
43
43
|
|
|
44
|
+
export const COMMENT_SPAM_RULE_VALUES = ['duplicate_text', 'repeat_author', 'manual', 'unknown'] as const;
|
|
45
|
+
export type CommentSpamRule = (typeof COMMENT_SPAM_RULE_VALUES)[number];
|
|
46
|
+
|
|
47
|
+
// Por que o comentário foi marcado como spam. `groupKey` identifica os outros
|
|
48
|
+
// comentários do mesmo conteúdo que caíram na mesma regra: autor normalizado
|
|
49
|
+
// (repeat_author) ou `autor::texto` normalizado (duplicate_text).
|
|
50
|
+
@Schema({ _id: false })
|
|
51
|
+
export class CommentSpamReason {
|
|
52
|
+
@Prop({ type: String, enum: COMMENT_SPAM_RULE_VALUES, required: true })
|
|
53
|
+
rule: CommentSpamRule;
|
|
54
|
+
|
|
55
|
+
@Prop({ type: Number, required: false })
|
|
56
|
+
threshold?: number;
|
|
57
|
+
|
|
58
|
+
@Prop({ type: Number, required: false })
|
|
59
|
+
matchCount?: number;
|
|
60
|
+
|
|
61
|
+
@Prop({ type: String, required: false })
|
|
62
|
+
groupKey?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const CommentSpamReasonSchema = SchemaFactory.createForClass(CommentSpamReason);
|
|
66
|
+
|
|
67
|
+
@Schema({ _id: false })
|
|
68
|
+
export class CommentClassifiedBy {
|
|
69
|
+
@Prop({ type: String, required: false })
|
|
70
|
+
team?: string;
|
|
71
|
+
|
|
72
|
+
@Prop({ type: String, required: false })
|
|
73
|
+
name?: string;
|
|
74
|
+
|
|
75
|
+
@Prop({ type: String, required: false })
|
|
76
|
+
email?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const CommentClassifiedBySchema = SchemaFactory.createForClass(CommentClassifiedBy);
|
|
80
|
+
|
|
44
81
|
@Schema({ timestamps: true })
|
|
45
82
|
export class Comment {
|
|
46
83
|
@Prop({ type: MongooseSchema.Types.ObjectId, auto: true, required: false })
|
|
@@ -117,6 +154,25 @@ export class Comment {
|
|
|
117
154
|
@Prop({ type: Boolean, required: false, default: false })
|
|
118
155
|
isSpam?: boolean;
|
|
119
156
|
|
|
157
|
+
@Prop({ type: CommentSpamReasonSchema, required: false, default: null })
|
|
158
|
+
spamReason?: CommentSpamReason | null;
|
|
159
|
+
|
|
160
|
+
// Tirado do spam manualmente: as regras automáticas de spam ignoram esse
|
|
161
|
+
// comentário (e ele sai da contagem do grupo), mas a IA continua classificando.
|
|
162
|
+
@Prop({ type: Boolean, required: false, default: false })
|
|
163
|
+
spamOverride?: boolean;
|
|
164
|
+
|
|
165
|
+
// Classificação ajustada manualmente: nenhum worker sobrescreve
|
|
166
|
+
// target/subject/polarity/funnel/isSpam desse comentário.
|
|
167
|
+
@Prop({ type: Boolean, required: false, default: false })
|
|
168
|
+
isManuallyClassified?: boolean;
|
|
169
|
+
|
|
170
|
+
@Prop({ type: Date, required: false, default: null })
|
|
171
|
+
manuallyClassifiedAt?: Date | null;
|
|
172
|
+
|
|
173
|
+
@Prop({ type: CommentClassifiedBySchema, required: false, default: null })
|
|
174
|
+
manuallyClassifiedBy?: CommentClassifiedBy | null;
|
|
175
|
+
|
|
120
176
|
@Prop({
|
|
121
177
|
type: String,
|
|
122
178
|
enum: COMMENT_CLASSIFICATION_STATUS_VALUES,
|
|
@@ -155,5 +211,6 @@ CommentSchema.index({ action: 1, funnel: 1 });
|
|
|
155
211
|
CommentSchema.index({ action: 1, target: 1 });
|
|
156
212
|
CommentSchema.index({ content: 1, parent: 1 });
|
|
157
213
|
CommentSchema.index({ content: 1, classificationStatus: 1 });
|
|
214
|
+
CommentSchema.index({ content: 1, 'spamReason.groupKey': 1 });
|
|
158
215
|
|
|
159
216
|
export const CommentModel = MongooseModel('Comment', CommentSchema);
|
|
@@ -107,6 +107,21 @@ export class Metrics {
|
|
|
107
107
|
@Prop({ type: Number, required: false, default: null })
|
|
108
108
|
replyCount?: number | null;
|
|
109
109
|
|
|
110
|
+
@Prop({ type: Number, required: false, default: null })
|
|
111
|
+
tapForwardCount?: number | null;
|
|
112
|
+
|
|
113
|
+
@Prop({ type: Number, required: false, default: null })
|
|
114
|
+
swipeForwardCount?: number | null;
|
|
115
|
+
|
|
116
|
+
@Prop({ type: Number, required: false, default: null })
|
|
117
|
+
exitCount?: number | null;
|
|
118
|
+
|
|
119
|
+
@Prop({ type: Number, required: false, default: null })
|
|
120
|
+
favoriteCount?: number | null;
|
|
121
|
+
|
|
122
|
+
@Prop({ type: Number, required: false, default: null })
|
|
123
|
+
dislikeCount?: number | null;
|
|
124
|
+
|
|
110
125
|
@Prop({ type: Number, required: false, default: null })
|
|
111
126
|
emv?: number | null;
|
|
112
127
|
|
|
@@ -173,6 +188,29 @@ export class Estimates {
|
|
|
173
188
|
|
|
174
189
|
export const EstimatesSchema = SchemaFactory.createForClass(Estimates);
|
|
175
190
|
|
|
191
|
+
export const CONTENT_STATUS_VALUES = ['pending', 'approved', 'deleted', 'processing', 'classified'] as const;
|
|
192
|
+
export type ContentStatus = (typeof CONTENT_STATUS_VALUES)[number];
|
|
193
|
+
|
|
194
|
+
// Status que entram em relatórios/KPIs (pending e deleted ficam de fora).
|
|
195
|
+
export const CONTENT_REPORTABLE_STATUS_VALUES: ContentStatus[] = ['approved', 'processing', 'classified'];
|
|
196
|
+
|
|
197
|
+
export const CONTENT_METRICS_SOURCE_VALUES = ['real', 'organic', 'estimated'] as const;
|
|
198
|
+
export type ContentMetricsSource = (typeof CONTENT_METRICS_SOURCE_VALUES)[number];
|
|
199
|
+
|
|
200
|
+
@Schema({ _id: false })
|
|
201
|
+
export class ContentStatusChangedBy {
|
|
202
|
+
@Prop({ type: String, required: false })
|
|
203
|
+
team?: string;
|
|
204
|
+
|
|
205
|
+
@Prop({ type: String, required: false })
|
|
206
|
+
name?: string;
|
|
207
|
+
|
|
208
|
+
@Prop({ type: String, required: false })
|
|
209
|
+
email?: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const ContentStatusChangedBySchema = SchemaFactory.createForClass(ContentStatusChangedBy);
|
|
213
|
+
|
|
176
214
|
@Schema({ timestamps: true })
|
|
177
215
|
export class Content {
|
|
178
216
|
@Prop({ type: MongooseSchema.Types.ObjectId, auto: true, required: false })
|
|
@@ -205,13 +243,35 @@ export class Content {
|
|
|
205
243
|
})
|
|
206
244
|
socialnetwork: 'instagram' | 'tiktok' | 'youtube' | 'pinterest' | 'linkedin' | 'twitter';
|
|
207
245
|
|
|
246
|
+
// pending: capturado da Primetag, aguardando aprovação manual.
|
|
247
|
+
// approved: aprovado manualmente, entra no pipeline (comentários → classificação → métricas).
|
|
248
|
+
// processing: dentro do pipeline — nunca é reprocessado nem aprovado/excluído nesse estado.
|
|
249
|
+
// classified: pipeline concluído.
|
|
250
|
+
// deleted: reprovado ou excluído manualmente.
|
|
208
251
|
@Prop({
|
|
209
252
|
type: String,
|
|
210
|
-
enum:
|
|
253
|
+
enum: CONTENT_STATUS_VALUES,
|
|
211
254
|
required: true,
|
|
212
|
-
default: '
|
|
255
|
+
default: 'pending',
|
|
213
256
|
})
|
|
214
|
-
status:
|
|
257
|
+
status: ContentStatus;
|
|
258
|
+
|
|
259
|
+
// Origem das métricas vindas da Primetag (ver metrics_snapshot):
|
|
260
|
+
// real = insights oficiais (perfil conectado), organic = métrica pública,
|
|
261
|
+
// estimated = estimada pela Primetag ou pela nossa estimativa de story.
|
|
262
|
+
@Prop({ type: String, enum: CONTENT_METRICS_SOURCE_VALUES, required: false, default: null })
|
|
263
|
+
metricsSource?: ContentMetricsSource | null;
|
|
264
|
+
|
|
265
|
+
// Contagem de comentários da Primetag no último processamento concluído —
|
|
266
|
+
// decide se há comentário novo pra recapturar/classificar.
|
|
267
|
+
@Prop({ type: Number, required: false, default: null })
|
|
268
|
+
processedCommentCount?: number | null;
|
|
269
|
+
|
|
270
|
+
@Prop({ type: Date, required: false, default: null })
|
|
271
|
+
approvedAt?: Date | null;
|
|
272
|
+
|
|
273
|
+
@Prop({ type: ContentStatusChangedBySchema, required: false, default: null })
|
|
274
|
+
approvedBy?: ContentStatusChangedBy | null;
|
|
215
275
|
|
|
216
276
|
@Prop({ type: MongooseSchema.Types.ObjectId, ref: 'Profile', required: true })
|
|
217
277
|
profile: Types.ObjectId | Profile;
|
|
@@ -288,5 +348,11 @@ ContentSchema.index({ action: 1, profile: 1, socialnetwork: 1 });
|
|
|
288
348
|
ContentSchema.index({ action: 1, status: 1, postedAt: 1 });
|
|
289
349
|
ContentSchema.index({ profile: 1 });
|
|
290
350
|
ContentSchema.index({ createdAt: -1, status: 1 });
|
|
351
|
+
// Chave de idempotência da captura da Primetag — conteúdos manuais (sem
|
|
352
|
+
// primetagId) ficam fora do índice.
|
|
353
|
+
ContentSchema.index(
|
|
354
|
+
{ action: 1, primetagId: 1 },
|
|
355
|
+
{ unique: true, partialFilterExpression: { primetagId: { $type: 'string' } } },
|
|
356
|
+
);
|
|
291
357
|
|
|
292
358
|
export const ContentModel = MongooseModel('Content', ContentSchema);
|