@grapadigital/shared-schemas 1.0.197 → 1.0.199
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-capture-process.schema.d.ts +1 -1
- package/dist/schemas/content-capture-process.schema.js +1 -1
- package/dist/schemas/content.schema.d.ts +29 -1
- package/dist/schemas/content.schema.js +84 -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-capture-process.schema.ts +2 -2
- package/src/schemas/content.schema.ts +88 -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);
|
|
@@ -60,7 +60,7 @@ export declare const ContentCaptureProcessInfluencerSchema: MongooseSchema<Conte
|
|
|
60
60
|
__v: number;
|
|
61
61
|
}>;
|
|
62
62
|
export declare class ContentCaptureProcessCommentCaptureStatus {
|
|
63
|
-
status: 'captured' | 'skipped_cooldown' | 'skipped_network' | 'out_of_scope' | 'failed';
|
|
63
|
+
status: 'captured' | 'skipped_cooldown' | 'skipped_network' | 'skipped_no_comments' | 'out_of_scope' | 'failed';
|
|
64
64
|
commentsCaptured?: number;
|
|
65
65
|
error?: string;
|
|
66
66
|
}
|
|
@@ -75,7 +75,7 @@ let ContentCaptureProcessCommentCaptureStatus = class ContentCaptureProcessComme
|
|
|
75
75
|
__decorate([
|
|
76
76
|
(0, mongoose_1.Prop)({
|
|
77
77
|
type: String,
|
|
78
|
-
enum: ['captured', 'skipped_cooldown', 'skipped_network', 'out_of_scope', 'failed'],
|
|
78
|
+
enum: ['captured', 'skipped_cooldown', 'skipped_network', 'skipped_no_comments', 'out_of_scope', 'failed'],
|
|
79
79
|
required: true,
|
|
80
80
|
}),
|
|
81
81
|
__metadata("design:type", String)
|
|
@@ -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", "error", "partial_error"];
|
|
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,48 @@ Estimates = __decorate([
|
|
|
229
249
|
], Estimates);
|
|
230
250
|
exports.Estimates = Estimates;
|
|
231
251
|
exports.EstimatesSchema = mongoose_1.SchemaFactory.createForClass(Estimates);
|
|
252
|
+
// error: captura de comentários falhou ou a classificação falhou por inteiro.
|
|
253
|
+
// partial_error: parte dos comentários não foi classificada. Nenhum dos dois
|
|
254
|
+
// volta sozinho pro pipeline — só por reenvio manual (aprovação).
|
|
255
|
+
exports.CONTENT_STATUS_VALUES = [
|
|
256
|
+
'pending',
|
|
257
|
+
'approved',
|
|
258
|
+
'deleted',
|
|
259
|
+
'processing',
|
|
260
|
+
'classified',
|
|
261
|
+
'error',
|
|
262
|
+
'partial_error',
|
|
263
|
+
];
|
|
264
|
+
// Status que entram em relatórios/KPIs (pending e deleted ficam de fora). error
|
|
265
|
+
// e partial_error entram: as métricas da Primetag são válidas, só os
|
|
266
|
+
// comentários ficaram incompletos.
|
|
267
|
+
exports.CONTENT_REPORTABLE_STATUS_VALUES = [
|
|
268
|
+
'approved',
|
|
269
|
+
'processing',
|
|
270
|
+
'classified',
|
|
271
|
+
'error',
|
|
272
|
+
'partial_error',
|
|
273
|
+
];
|
|
274
|
+
exports.CONTENT_METRICS_SOURCE_VALUES = ['real', 'organic', 'estimated'];
|
|
275
|
+
let ContentStatusChangedBy = class ContentStatusChangedBy {
|
|
276
|
+
};
|
|
277
|
+
__decorate([
|
|
278
|
+
(0, mongoose_1.Prop)({ type: String, required: false }),
|
|
279
|
+
__metadata("design:type", String)
|
|
280
|
+
], ContentStatusChangedBy.prototype, "team", void 0);
|
|
281
|
+
__decorate([
|
|
282
|
+
(0, mongoose_1.Prop)({ type: String, required: false }),
|
|
283
|
+
__metadata("design:type", String)
|
|
284
|
+
], ContentStatusChangedBy.prototype, "name", void 0);
|
|
285
|
+
__decorate([
|
|
286
|
+
(0, mongoose_1.Prop)({ type: String, required: false }),
|
|
287
|
+
__metadata("design:type", String)
|
|
288
|
+
], ContentStatusChangedBy.prototype, "email", void 0);
|
|
289
|
+
ContentStatusChangedBy = __decorate([
|
|
290
|
+
(0, mongoose_1.Schema)({ _id: false })
|
|
291
|
+
], ContentStatusChangedBy);
|
|
292
|
+
exports.ContentStatusChangedBy = ContentStatusChangedBy;
|
|
293
|
+
exports.ContentStatusChangedBySchema = mongoose_1.SchemaFactory.createForClass(ContentStatusChangedBy);
|
|
232
294
|
let Content = class Content {
|
|
233
295
|
};
|
|
234
296
|
__decorate([
|
|
@@ -270,12 +332,28 @@ __decorate([
|
|
|
270
332
|
__decorate([
|
|
271
333
|
(0, mongoose_1.Prop)({
|
|
272
334
|
type: String,
|
|
273
|
-
enum:
|
|
335
|
+
enum: exports.CONTENT_STATUS_VALUES,
|
|
274
336
|
required: true,
|
|
275
|
-
default: '
|
|
337
|
+
default: 'pending',
|
|
276
338
|
}),
|
|
277
339
|
__metadata("design:type", String)
|
|
278
340
|
], Content.prototype, "status", void 0);
|
|
341
|
+
__decorate([
|
|
342
|
+
(0, mongoose_1.Prop)({ type: String, enum: exports.CONTENT_METRICS_SOURCE_VALUES, required: false, default: null }),
|
|
343
|
+
__metadata("design:type", Object)
|
|
344
|
+
], Content.prototype, "metricsSource", void 0);
|
|
345
|
+
__decorate([
|
|
346
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
347
|
+
__metadata("design:type", Object)
|
|
348
|
+
], Content.prototype, "processedCommentCount", void 0);
|
|
349
|
+
__decorate([
|
|
350
|
+
(0, mongoose_1.Prop)({ type: Date, required: false, default: null }),
|
|
351
|
+
__metadata("design:type", Object)
|
|
352
|
+
], Content.prototype, "approvedAt", void 0);
|
|
353
|
+
__decorate([
|
|
354
|
+
(0, mongoose_1.Prop)({ type: exports.ContentStatusChangedBySchema, required: false, default: null }),
|
|
355
|
+
__metadata("design:type", Object)
|
|
356
|
+
], Content.prototype, "approvedBy", void 0);
|
|
279
357
|
__decorate([
|
|
280
358
|
(0, mongoose_1.Prop)({ type: mongoose_2.Schema.Types.ObjectId, ref: 'Profile', required: true }),
|
|
281
359
|
__metadata("design:type", Object)
|
|
@@ -369,4 +447,7 @@ exports.ContentSchema.index({ action: 1, profile: 1, socialnetwork: 1 });
|
|
|
369
447
|
exports.ContentSchema.index({ action: 1, status: 1, postedAt: 1 });
|
|
370
448
|
exports.ContentSchema.index({ profile: 1 });
|
|
371
449
|
exports.ContentSchema.index({ createdAt: -1, status: 1 });
|
|
450
|
+
// Chave de idempotência da captura da Primetag — conteúdos manuais (sem
|
|
451
|
+
// primetagId) ficam fora do índice.
|
|
452
|
+
exports.ContentSchema.index({ action: 1, primetagId: 1 }, { unique: true, partialFilterExpression: { primetagId: { $type: 'string' } } });
|
|
372
453
|
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);
|
|
@@ -60,10 +60,10 @@ export const ContentCaptureProcessInfluencerSchema = SchemaFactory.createForClas
|
|
|
60
60
|
export class ContentCaptureProcessCommentCaptureStatus {
|
|
61
61
|
@Prop({
|
|
62
62
|
type: String,
|
|
63
|
-
enum: ['captured', 'skipped_cooldown', 'skipped_network', 'out_of_scope', 'failed'],
|
|
63
|
+
enum: ['captured', 'skipped_cooldown', 'skipped_network', 'skipped_no_comments', 'out_of_scope', 'failed'],
|
|
64
64
|
required: true,
|
|
65
65
|
})
|
|
66
|
-
status: 'captured' | 'skipped_cooldown' | 'skipped_network' | 'out_of_scope' | 'failed';
|
|
66
|
+
status: 'captured' | 'skipped_cooldown' | 'skipped_network' | 'skipped_no_comments' | 'out_of_scope' | 'failed';
|
|
67
67
|
|
|
68
68
|
@Prop({ type: Number, required: false })
|
|
69
69
|
commentsCaptured?: number;
|
|
@@ -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,48 @@ export class Estimates {
|
|
|
173
188
|
|
|
174
189
|
export const EstimatesSchema = SchemaFactory.createForClass(Estimates);
|
|
175
190
|
|
|
191
|
+
// error: captura de comentários falhou ou a classificação falhou por inteiro.
|
|
192
|
+
// partial_error: parte dos comentários não foi classificada. Nenhum dos dois
|
|
193
|
+
// volta sozinho pro pipeline — só por reenvio manual (aprovação).
|
|
194
|
+
export const CONTENT_STATUS_VALUES = [
|
|
195
|
+
'pending',
|
|
196
|
+
'approved',
|
|
197
|
+
'deleted',
|
|
198
|
+
'processing',
|
|
199
|
+
'classified',
|
|
200
|
+
'error',
|
|
201
|
+
'partial_error',
|
|
202
|
+
] as const;
|
|
203
|
+
export type ContentStatus = (typeof CONTENT_STATUS_VALUES)[number];
|
|
204
|
+
|
|
205
|
+
// Status que entram em relatórios/KPIs (pending e deleted ficam de fora). error
|
|
206
|
+
// e partial_error entram: as métricas da Primetag são válidas, só os
|
|
207
|
+
// comentários ficaram incompletos.
|
|
208
|
+
export const CONTENT_REPORTABLE_STATUS_VALUES: ContentStatus[] = [
|
|
209
|
+
'approved',
|
|
210
|
+
'processing',
|
|
211
|
+
'classified',
|
|
212
|
+
'error',
|
|
213
|
+
'partial_error',
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
export const CONTENT_METRICS_SOURCE_VALUES = ['real', 'organic', 'estimated'] as const;
|
|
217
|
+
export type ContentMetricsSource = (typeof CONTENT_METRICS_SOURCE_VALUES)[number];
|
|
218
|
+
|
|
219
|
+
@Schema({ _id: false })
|
|
220
|
+
export class ContentStatusChangedBy {
|
|
221
|
+
@Prop({ type: String, required: false })
|
|
222
|
+
team?: string;
|
|
223
|
+
|
|
224
|
+
@Prop({ type: String, required: false })
|
|
225
|
+
name?: string;
|
|
226
|
+
|
|
227
|
+
@Prop({ type: String, required: false })
|
|
228
|
+
email?: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export const ContentStatusChangedBySchema = SchemaFactory.createForClass(ContentStatusChangedBy);
|
|
232
|
+
|
|
176
233
|
@Schema({ timestamps: true })
|
|
177
234
|
export class Content {
|
|
178
235
|
@Prop({ type: MongooseSchema.Types.ObjectId, auto: true, required: false })
|
|
@@ -205,13 +262,35 @@ export class Content {
|
|
|
205
262
|
})
|
|
206
263
|
socialnetwork: 'instagram' | 'tiktok' | 'youtube' | 'pinterest' | 'linkedin' | 'twitter';
|
|
207
264
|
|
|
265
|
+
// pending: capturado da Primetag, aguardando aprovação manual.
|
|
266
|
+
// approved: aprovado manualmente, entra no pipeline (comentários → classificação → métricas).
|
|
267
|
+
// processing: dentro do pipeline — nunca é reprocessado nem aprovado/excluído nesse estado.
|
|
268
|
+
// classified: pipeline concluído.
|
|
269
|
+
// deleted: reprovado ou excluído manualmente.
|
|
208
270
|
@Prop({
|
|
209
271
|
type: String,
|
|
210
|
-
enum:
|
|
272
|
+
enum: CONTENT_STATUS_VALUES,
|
|
211
273
|
required: true,
|
|
212
|
-
default: '
|
|
274
|
+
default: 'pending',
|
|
213
275
|
})
|
|
214
|
-
status:
|
|
276
|
+
status: ContentStatus;
|
|
277
|
+
|
|
278
|
+
// Origem das métricas vindas da Primetag (ver metrics_snapshot):
|
|
279
|
+
// real = insights oficiais (perfil conectado), organic = métrica pública,
|
|
280
|
+
// estimated = estimada pela Primetag ou pela nossa estimativa de story.
|
|
281
|
+
@Prop({ type: String, enum: CONTENT_METRICS_SOURCE_VALUES, required: false, default: null })
|
|
282
|
+
metricsSource?: ContentMetricsSource | null;
|
|
283
|
+
|
|
284
|
+
// Contagem de comentários da Primetag no último processamento concluído —
|
|
285
|
+
// decide se há comentário novo pra recapturar/classificar.
|
|
286
|
+
@Prop({ type: Number, required: false, default: null })
|
|
287
|
+
processedCommentCount?: number | null;
|
|
288
|
+
|
|
289
|
+
@Prop({ type: Date, required: false, default: null })
|
|
290
|
+
approvedAt?: Date | null;
|
|
291
|
+
|
|
292
|
+
@Prop({ type: ContentStatusChangedBySchema, required: false, default: null })
|
|
293
|
+
approvedBy?: ContentStatusChangedBy | null;
|
|
215
294
|
|
|
216
295
|
@Prop({ type: MongooseSchema.Types.ObjectId, ref: 'Profile', required: true })
|
|
217
296
|
profile: Types.ObjectId | Profile;
|
|
@@ -288,5 +367,11 @@ ContentSchema.index({ action: 1, profile: 1, socialnetwork: 1 });
|
|
|
288
367
|
ContentSchema.index({ action: 1, status: 1, postedAt: 1 });
|
|
289
368
|
ContentSchema.index({ profile: 1 });
|
|
290
369
|
ContentSchema.index({ createdAt: -1, status: 1 });
|
|
370
|
+
// Chave de idempotência da captura da Primetag — conteúdos manuais (sem
|
|
371
|
+
// primetagId) ficam fora do índice.
|
|
372
|
+
ContentSchema.index(
|
|
373
|
+
{ action: 1, primetagId: 1 },
|
|
374
|
+
{ unique: true, partialFilterExpression: { primetagId: { $type: 'string' } } },
|
|
375
|
+
);
|
|
291
376
|
|
|
292
377
|
export const ContentModel = MongooseModel('Content', ContentSchema);
|