@grapadigital/shared-schemas 1.0.219 → 1.0.220
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/action.schema.d.ts +38 -1
- package/dist/schemas/action.schema.js +63 -1
- package/dist/schemas/client.schema.d.ts +1 -1
- package/dist/schemas/client.schema.js +8 -0
- package/dist/schemas/content.schema.d.ts +15 -0
- package/dist/schemas/content.schema.js +25 -1
- package/docs/specs/GD-692.md +112 -0
- package/package.json +1 -1
- package/src/schemas/action.schema.ts +61 -1
- package/src/schemas/client.schema.ts +8 -0
- package/src/schemas/content.schema.ts +28 -0
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
import { HydratedDocument, Schema as MongooseSchema, Types } from 'mongoose';
|
|
27
27
|
import { Recruitment } from './recruitment.schema';
|
|
28
28
|
import { Content } from './content.schema';
|
|
29
|
-
import { Client } from './client.schema';
|
|
29
|
+
import { Client, ClientMetricGoal } from './client.schema';
|
|
30
30
|
import { Sale } from './sale.schema';
|
|
31
31
|
import { Campaign } from './campaign.schema';
|
|
32
32
|
import { Note } from './note.schema';
|
|
@@ -54,6 +54,34 @@ export declare const ActionPromptConfigSchema: MongooseSchema<ActionPromptConfig
|
|
|
54
54
|
}>;
|
|
55
55
|
export declare const ACTION_STATUS_VALUES: readonly ["config", "inProgress", "finished", "archived", "cancelled"];
|
|
56
56
|
export type ActionStatus = (typeof ACTION_STATUS_VALUES)[number];
|
|
57
|
+
/**
|
|
58
|
+
* Fase da Ação (antigo briefing do genc). Com `_id` porque o Conteúdo aponta
|
|
59
|
+
* para ela (`Content.phase`). Período e hashtags opcionais fazem a captura
|
|
60
|
+
* sugerir a Fase sozinha; Fase sem os dois nunca é automática. Fases podem se
|
|
61
|
+
* sobrepor: vale a primeira por `order`. Ver GD-691.
|
|
62
|
+
*/
|
|
63
|
+
export declare class ActionPhase {
|
|
64
|
+
_id: Types.ObjectId;
|
|
65
|
+
name: string;
|
|
66
|
+
/** Hex #rrggbb, da paleta do cpanel. */
|
|
67
|
+
color: string;
|
|
68
|
+
order: number;
|
|
69
|
+
/** Inclusivo. */
|
|
70
|
+
startDate?: Date | null;
|
|
71
|
+
/** Inclusivo (vale o dia inteiro). */
|
|
72
|
+
endDate?: Date | null;
|
|
73
|
+
/** Normalizadas (minúsculas, sem # e sem acento). */
|
|
74
|
+
hashtags: string[];
|
|
75
|
+
}
|
|
76
|
+
export declare const ActionPhaseSchema: MongooseSchema<ActionPhase, import("mongoose").Model<ActionPhase, any, any, any, import("mongoose").Document<unknown, any, ActionPhase, any, {}> & ActionPhase & Required<{
|
|
77
|
+
_id: Types.ObjectId;
|
|
78
|
+
}> & {
|
|
79
|
+
__v: number;
|
|
80
|
+
}, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, ActionPhase, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<ActionPhase>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<ActionPhase> & Required<{
|
|
81
|
+
_id: Types.ObjectId;
|
|
82
|
+
}> & {
|
|
83
|
+
__v: number;
|
|
84
|
+
}>;
|
|
57
85
|
export declare class Action {
|
|
58
86
|
_id?: Types.ObjectId;
|
|
59
87
|
title: string;
|
|
@@ -100,6 +128,15 @@ export declare class Action {
|
|
|
100
128
|
businessUnit?: ActionBusinessUnit;
|
|
101
129
|
campaign?: Types.ObjectId | Campaign;
|
|
102
130
|
promptConfig?: ActionPromptConfig | null;
|
|
131
|
+
/**
|
|
132
|
+
* Hashtags que marcam o Conteúdo como Orgânico (Tipo, fora do escopo
|
|
133
|
+
* contratado). Normalizadas. Vazio = a captura não sugere Tipo. Ver GD-691.
|
|
134
|
+
*/
|
|
135
|
+
organicHashtags?: string[];
|
|
136
|
+
/** Fases da Ação. Ver ActionPhase e GD-691. */
|
|
137
|
+
phases?: ActionPhase[];
|
|
138
|
+
/** Metas da Ação: mesmo { metric, value } e catálogo dos Tiers do cliente. Ver GD-691. */
|
|
139
|
+
goals?: ClientMetricGoal[];
|
|
103
140
|
}
|
|
104
141
|
export declare const ActionSchema: MongooseSchema<Action, import("mongoose").Model<Action, any, any, any, import("mongoose").Document<unknown, any, Action, any, {}> & Action & Required<{
|
|
105
142
|
_id: Types.ObjectId;
|
|
@@ -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.ActionModel = exports.ActionSchema = exports.Action = exports.ACTION_STATUS_VALUES = exports.ActionPromptConfigSchema = exports.ActionPromptConfig = exports.ACTION_BUSINESS_UNIT_VALUES = void 0;
|
|
12
|
+
exports.ActionModel = exports.ActionSchema = exports.Action = exports.ActionPhaseSchema = exports.ActionPhase = exports.ACTION_STATUS_VALUES = exports.ActionPromptConfigSchema = exports.ActionPromptConfig = exports.ACTION_BUSINESS_UNIT_VALUES = void 0;
|
|
13
13
|
const mongoose_1 = require("@nestjs/mongoose");
|
|
14
14
|
const swagger_1 = require("@nestjs/swagger");
|
|
15
15
|
const mongoose_2 = require("mongoose");
|
|
@@ -75,6 +75,53 @@ exports.ACTION_STATUS_VALUES = [
|
|
|
75
75
|
'archived',
|
|
76
76
|
'cancelled',
|
|
77
77
|
];
|
|
78
|
+
/**
|
|
79
|
+
* Fase da Ação (antigo briefing do genc). Com `_id` porque o Conteúdo aponta
|
|
80
|
+
* para ela (`Content.phase`). Período e hashtags opcionais fazem a captura
|
|
81
|
+
* sugerir a Fase sozinha; Fase sem os dois nunca é automática. Fases podem se
|
|
82
|
+
* sobrepor: vale a primeira por `order`. Ver GD-691.
|
|
83
|
+
*/
|
|
84
|
+
let ActionPhase = class ActionPhase {
|
|
85
|
+
};
|
|
86
|
+
__decorate([
|
|
87
|
+
(0, swagger_1.ApiProperty)({ example: '6710a1b2c3d4e5f601234567' }),
|
|
88
|
+
__metadata("design:type", mongoose_2.Types.ObjectId)
|
|
89
|
+
], ActionPhase.prototype, "_id", void 0);
|
|
90
|
+
__decorate([
|
|
91
|
+
(0, swagger_1.ApiProperty)({ example: 'Lançamento' }),
|
|
92
|
+
(0, mongoose_1.Prop)({ type: String, required: true }),
|
|
93
|
+
__metadata("design:type", String)
|
|
94
|
+
], ActionPhase.prototype, "name", void 0);
|
|
95
|
+
__decorate([
|
|
96
|
+
(0, swagger_1.ApiProperty)({ example: '#2563eb' }),
|
|
97
|
+
(0, mongoose_1.Prop)({ type: String, required: true }),
|
|
98
|
+
__metadata("design:type", String)
|
|
99
|
+
], ActionPhase.prototype, "color", void 0);
|
|
100
|
+
__decorate([
|
|
101
|
+
(0, swagger_1.ApiProperty)({ example: 0, description: 'Prioridade quando Fases se sobrepõem (menor vale primeiro)' }),
|
|
102
|
+
(0, mongoose_1.Prop)({ type: Number, required: true }),
|
|
103
|
+
__metadata("design:type", Number)
|
|
104
|
+
], ActionPhase.prototype, "order", void 0);
|
|
105
|
+
__decorate([
|
|
106
|
+
(0, swagger_1.ApiProperty)({ type: Date, required: false, nullable: true }),
|
|
107
|
+
(0, mongoose_1.Prop)({ type: Date, required: false, default: null }),
|
|
108
|
+
__metadata("design:type", Object)
|
|
109
|
+
], ActionPhase.prototype, "startDate", void 0);
|
|
110
|
+
__decorate([
|
|
111
|
+
(0, swagger_1.ApiProperty)({ type: Date, required: false, nullable: true }),
|
|
112
|
+
(0, mongoose_1.Prop)({ type: Date, required: false, default: null }),
|
|
113
|
+
__metadata("design:type", Object)
|
|
114
|
+
], ActionPhase.prototype, "endDate", void 0);
|
|
115
|
+
__decorate([
|
|
116
|
+
(0, swagger_1.ApiProperty)({ type: [String], example: ['galaxys26'] }),
|
|
117
|
+
(0, mongoose_1.Prop)({ type: [String], default: [] }),
|
|
118
|
+
__metadata("design:type", Array)
|
|
119
|
+
], ActionPhase.prototype, "hashtags", void 0);
|
|
120
|
+
ActionPhase = __decorate([
|
|
121
|
+
(0, mongoose_1.Schema)()
|
|
122
|
+
], ActionPhase);
|
|
123
|
+
exports.ActionPhase = ActionPhase;
|
|
124
|
+
exports.ActionPhaseSchema = mongoose_1.SchemaFactory.createForClass(ActionPhase);
|
|
78
125
|
let Action = class Action {
|
|
79
126
|
};
|
|
80
127
|
__decorate([
|
|
@@ -297,6 +344,21 @@ __decorate([
|
|
|
297
344
|
(0, mongoose_1.Prop)({ type: exports.ActionPromptConfigSchema, required: false, default: null }),
|
|
298
345
|
__metadata("design:type", Object)
|
|
299
346
|
], Action.prototype, "promptConfig", void 0);
|
|
347
|
+
__decorate([
|
|
348
|
+
(0, swagger_1.ApiProperty)({ type: [String], required: false, example: ['galaxymoments'] }),
|
|
349
|
+
(0, mongoose_1.Prop)({ type: [String], default: [] }),
|
|
350
|
+
__metadata("design:type", Array)
|
|
351
|
+
], Action.prototype, "organicHashtags", void 0);
|
|
352
|
+
__decorate([
|
|
353
|
+
(0, swagger_1.ApiProperty)({ type: () => [ActionPhase], required: false }),
|
|
354
|
+
(0, mongoose_1.Prop)({ type: [exports.ActionPhaseSchema], default: [] }),
|
|
355
|
+
__metadata("design:type", Array)
|
|
356
|
+
], Action.prototype, "phases", void 0);
|
|
357
|
+
__decorate([
|
|
358
|
+
(0, swagger_1.ApiProperty)({ type: () => [client_schema_1.ClientMetricGoal], required: false }),
|
|
359
|
+
(0, mongoose_1.Prop)({ type: [client_schema_1.ClientMetricGoalSchema], default: [] }),
|
|
360
|
+
__metadata("design:type", Array)
|
|
361
|
+
], Action.prototype, "goals", void 0);
|
|
300
362
|
Action = __decorate([
|
|
301
363
|
(0, mongoose_1.Schema)({ timestamps: true })
|
|
302
364
|
], Action);
|
|
@@ -40,7 +40,7 @@ export type ClientCustomization = (typeof CLIENT_CUSTOMIZATION_VALUES)[number];
|
|
|
40
40
|
* `viralReachEngagementRate` é o limite de viral e só vale no Tier.
|
|
41
41
|
* Ver GD-687.
|
|
42
42
|
*/
|
|
43
|
-
export declare const CLIENT_GOAL_METRIC_VALUES: readonly ["potentialReach", "reachCount", "impressionCount", "engagementCount", "engagementRate", "reachEngagementRate", "qualifiedEngagementRate", "effectiveReach", "emv", "score", "cpmi", "cpi", "cpe", "viralReachEngagementRate"];
|
|
43
|
+
export declare const CLIENT_GOAL_METRIC_VALUES: readonly ["potentialReach", "reachCount", "impressionCount", "engagementCount", "engagementRate", "reachEngagementRate", "qualifiedEngagementRate", "effectiveReach", "emv", "score", "cpmi", "cpi", "cpe", "savedCount", "sharesCount", "vcr", "viralReachEngagementRate"];
|
|
44
44
|
export type ClientGoalMetric = (typeof CLIENT_GOAL_METRIC_VALUES)[number];
|
|
45
45
|
export type ClientGoalMetricUnit = 'count' | 'rate' | 'currency';
|
|
46
46
|
/** Unidade de cada métrica. `rate` é guardado como fração (0,10 = 10%). */
|
|
@@ -40,6 +40,9 @@ exports.CLIENT_GOAL_METRIC_VALUES = [
|
|
|
40
40
|
'cpmi',
|
|
41
41
|
'cpi',
|
|
42
42
|
'cpe',
|
|
43
|
+
'savedCount',
|
|
44
|
+
'sharesCount',
|
|
45
|
+
'vcr',
|
|
43
46
|
'viralReachEngagementRate',
|
|
44
47
|
];
|
|
45
48
|
/** Unidade de cada métrica. `rate` é guardado como fração (0,10 = 10%). */
|
|
@@ -57,6 +60,9 @@ exports.CLIENT_GOAL_METRIC_UNITS = {
|
|
|
57
60
|
cpmi: 'currency',
|
|
58
61
|
cpi: 'currency',
|
|
59
62
|
cpe: 'currency',
|
|
63
|
+
savedCount: 'count',
|
|
64
|
+
sharesCount: 'count',
|
|
65
|
+
vcr: 'rate',
|
|
60
66
|
viralReachEngagementRate: 'rate',
|
|
61
67
|
};
|
|
62
68
|
let Cluster = class Cluster {
|
|
@@ -111,10 +117,12 @@ exports.CommissionRangeSchema = mongoose_1.SchemaFactory.createForClass(Commissi
|
|
|
111
117
|
let ClientMetricGoal = class ClientMetricGoal {
|
|
112
118
|
};
|
|
113
119
|
__decorate([
|
|
120
|
+
(0, swagger_1.ApiProperty)({ enum: exports.CLIENT_GOAL_METRIC_VALUES, example: 'potentialReach' }),
|
|
114
121
|
(0, mongoose_1.Prop)({ type: String, enum: exports.CLIENT_GOAL_METRIC_VALUES, required: true }),
|
|
115
122
|
__metadata("design:type", String)
|
|
116
123
|
], ClientMetricGoal.prototype, "metric", void 0);
|
|
117
124
|
__decorate([
|
|
125
|
+
(0, swagger_1.ApiProperty)({ example: 120000000, description: 'Na unidade da métrica; percentuais em fração (0,25 = 25%)' }),
|
|
118
126
|
(0, mongoose_1.Prop)({ type: Number, required: true }),
|
|
119
127
|
__metadata("design:type", Number)
|
|
120
128
|
], ClientMetricGoal.prototype, "value", void 0);
|
|
@@ -157,6 +157,9 @@ export declare const CONTENT_REPORTABLE_STATUS_VALUES: ContentStatus[];
|
|
|
157
157
|
export declare const CONTENT_COMMENT_COUNT_SOURCE_VALUES: readonly ["primetag", "exportcomments"];
|
|
158
158
|
export type ContentCommentCountSource = (typeof CONTENT_COMMENT_COUNT_SOURCE_VALUES)[number];
|
|
159
159
|
export declare const CONTENT_METRICS_SOURCE_VALUES: readonly ["real", "organic", "estimated"];
|
|
160
|
+
/** Origem do Tipo e da Fase: auto = regra da captura, manual = quem revisou. Ver GD-691. */
|
|
161
|
+
export declare const CONTENT_CLASSIFICATION_SOURCE_VALUES: readonly ["auto", "manual"];
|
|
162
|
+
export type ContentClassificationSource = (typeof CONTENT_CLASSIFICATION_SOURCE_VALUES)[number];
|
|
160
163
|
export type ContentMetricsSource = (typeof CONTENT_METRICS_SOURCE_VALUES)[number];
|
|
161
164
|
export declare class ContentStatusChangedBy {
|
|
162
165
|
team?: string;
|
|
@@ -207,6 +210,18 @@ export declare class Content {
|
|
|
207
210
|
editedAt?: Date;
|
|
208
211
|
followersAtPost?: number | null;
|
|
209
212
|
history?: ContentMetricsHistoryEntry[];
|
|
213
|
+
/** Hashtags da publicação, normalizadas (minúsculas, sem # e sem acento). Ver GD-691. */
|
|
214
|
+
hashtags?: string[];
|
|
215
|
+
/**
|
|
216
|
+
* Tipo do Conteúdo: Orgânico (fora do escopo contratado) ou Pago. null =
|
|
217
|
+
* Ação sem hashtag de Orgânico. Não confundir com metricsSource 'organic'
|
|
218
|
+
* (métrica pública) nem com isBoosted (mídia paga). Ver GD-691.
|
|
219
|
+
*/
|
|
220
|
+
isOrganic?: boolean | null;
|
|
221
|
+
isOrganicSource?: ContentClassificationSource | null;
|
|
222
|
+
/** _id de uma Action.phases. Ver GD-691. */
|
|
223
|
+
phase?: Types.ObjectId | null;
|
|
224
|
+
phaseSource?: ContentClassificationSource | null;
|
|
210
225
|
}
|
|
211
226
|
export declare const ContentSchema: MongooseSchema<Content, import("mongoose").Model<Content, any, any, any, import("mongoose").Document<unknown, any, Content, any, {}> & Content & Required<{
|
|
212
227
|
_id: Types.ObjectId;
|
|
@@ -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_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.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 {
|
|
@@ -273,6 +273,8 @@ exports.CONTENT_REPORTABLE_STATUS_VALUES = [
|
|
|
273
273
|
];
|
|
274
274
|
exports.CONTENT_COMMENT_COUNT_SOURCE_VALUES = ['primetag', 'exportcomments'];
|
|
275
275
|
exports.CONTENT_METRICS_SOURCE_VALUES = ['real', 'organic', 'estimated'];
|
|
276
|
+
/** Origem do Tipo e da Fase: auto = regra da captura, manual = quem revisou. Ver GD-691. */
|
|
277
|
+
exports.CONTENT_CLASSIFICATION_SOURCE_VALUES = ['auto', 'manual'];
|
|
276
278
|
let ContentStatusChangedBy = class ContentStatusChangedBy {
|
|
277
279
|
};
|
|
278
280
|
__decorate([
|
|
@@ -449,6 +451,26 @@ __decorate([
|
|
|
449
451
|
(0, mongoose_1.Prop)({ type: [exports.ContentMetricsHistoryEntrySchema], required: false, default: [] }),
|
|
450
452
|
__metadata("design:type", Array)
|
|
451
453
|
], Content.prototype, "history", void 0);
|
|
454
|
+
__decorate([
|
|
455
|
+
(0, mongoose_1.Prop)({ type: [String], default: [] }),
|
|
456
|
+
__metadata("design:type", Array)
|
|
457
|
+
], Content.prototype, "hashtags", void 0);
|
|
458
|
+
__decorate([
|
|
459
|
+
(0, mongoose_1.Prop)({ type: Boolean, required: false, default: null }),
|
|
460
|
+
__metadata("design:type", Object)
|
|
461
|
+
], Content.prototype, "isOrganic", void 0);
|
|
462
|
+
__decorate([
|
|
463
|
+
(0, mongoose_1.Prop)({ type: String, enum: exports.CONTENT_CLASSIFICATION_SOURCE_VALUES, required: false, default: null }),
|
|
464
|
+
__metadata("design:type", Object)
|
|
465
|
+
], Content.prototype, "isOrganicSource", void 0);
|
|
466
|
+
__decorate([
|
|
467
|
+
(0, mongoose_1.Prop)({ type: mongoose_2.Schema.Types.ObjectId, required: false, default: null }),
|
|
468
|
+
__metadata("design:type", Object)
|
|
469
|
+
], Content.prototype, "phase", void 0);
|
|
470
|
+
__decorate([
|
|
471
|
+
(0, mongoose_1.Prop)({ type: String, enum: exports.CONTENT_CLASSIFICATION_SOURCE_VALUES, required: false, default: null }),
|
|
472
|
+
__metadata("design:type", Object)
|
|
473
|
+
], Content.prototype, "phaseSource", void 0);
|
|
452
474
|
Content = __decorate([
|
|
453
475
|
(0, mongoose_1.Schema)({ timestamps: true })
|
|
454
476
|
], Content);
|
|
@@ -459,6 +481,8 @@ exports.ContentSchema.index({ action: 1, profile: 1, type: 1 });
|
|
|
459
481
|
exports.ContentSchema.index({ action: 1, profile: 1, socialnetwork: 1 });
|
|
460
482
|
exports.ContentSchema.index({ action: 1, status: 1, postedAt: 1 });
|
|
461
483
|
exports.ContentSchema.index({ profile: 1 });
|
|
484
|
+
exports.ContentSchema.index({ action: 1, phase: 1 });
|
|
485
|
+
exports.ContentSchema.index({ action: 1, isOrganic: 1 });
|
|
462
486
|
exports.ContentSchema.index({ createdAt: -1, status: 1 });
|
|
463
487
|
// Chave de idempotência da captura da Primetag — conteúdos manuais (sem
|
|
464
488
|
// primetagId) ficam fora do índice.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Spec: action + content: Orgânico, Fases e Metas da Ação
|
|
2
|
+
|
|
3
|
+
**Feature:** GD-692 (Story GD-691)
|
|
4
|
+
**Consumido por:** api-campaigns (GD-693), content-capture-trigger e content-capture-worker (GD-693), web-campaigns (GD-694, via shared-app-modules)
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Descrição
|
|
9
|
+
|
|
10
|
+
A Ação da Samsung passa a guardar a hashtag de Orgânico, as Fases (antigo briefing do genc) e as
|
|
11
|
+
Metas da Ação. O Conteúdo passa a guardar as próprias hashtags e o Tipo (Orgânico ou Pago) e a Fase
|
|
12
|
+
sugeridos, cada um com a origem (`auto` = regra da captura, `manual` = quem revisou).
|
|
13
|
+
|
|
14
|
+
Todos os campos são opcionais e têm default vazio: Ações e Conteúdos existentes não mudam.
|
|
15
|
+
|
|
16
|
+
**Nome:** "Orgânico" aqui é o Tipo do Conteúdo (fora do escopo contratado). Não confundir com
|
|
17
|
+
`metricsSource: 'organic'` (Métrica pública) nem com `isBoosted` (mídia paga, GD-707).
|
|
18
|
+
|
|
19
|
+
## Schema
|
|
20
|
+
|
|
21
|
+
### `action.schema.ts`
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
@Schema() // com _id: o Conteúdo aponta para a Fase
|
|
25
|
+
export class ActionPhase {
|
|
26
|
+
_id: Types.ObjectId;
|
|
27
|
+
name: string;
|
|
28
|
+
color: string; // hex #rrggbb (paleta do cpanel)
|
|
29
|
+
order: number; // 0..n, define a prioridade quando Fases se sobrepõem
|
|
30
|
+
startDate?: Date | null; // inclusivo
|
|
31
|
+
endDate?: Date | null; // inclusivo (dia inteiro)
|
|
32
|
+
hashtags: string[]; // normalizadas, default []
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Action.organicHashtags: string[] // normalizadas, default []
|
|
36
|
+
Action.phases: ActionPhase[] // default []
|
|
37
|
+
Action.goals: ClientMetricGoal[] // default []; mesmo { metric, value } e catálogo dos Tiers (GD-688)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `content.schema.ts`
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
export const CONTENT_CLASSIFICATION_SOURCE_VALUES = ['auto', 'manual'] as const;
|
|
44
|
+
|
|
45
|
+
Content.hashtags: string[] // normalizadas, default []
|
|
46
|
+
Content.isOrganic: boolean | null // default null (Ação sem hashtag de Orgânico)
|
|
47
|
+
Content.isOrganicSource: 'auto' | 'manual' | null // default null
|
|
48
|
+
Content.phase: Types.ObjectId | null // _id de Action.phases; default null
|
|
49
|
+
Content.phaseSource: 'auto' | 'manual' | null // default null
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Índices novos: `ContentSchema.index({ action: 1, phase: 1 })` e `ContentSchema.index({ action: 1, isOrganic: 1 })`.
|
|
53
|
+
|
|
54
|
+
### Catálogo de métricas (`client.schema.ts`)
|
|
55
|
+
|
|
56
|
+
`CLIENT_GOAL_METRIC_VALUES` ganha as métricas do relatório Samsung que faltavam, com a unidade em
|
|
57
|
+
`CLIENT_GOAL_METRIC_UNITS`:
|
|
58
|
+
|
|
59
|
+
| métrica | unidade |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `savedCount` | count |
|
|
62
|
+
| `sharesCount` | count |
|
|
63
|
+
| `vcr` | rate (a métrica entra no Content na GD-707) |
|
|
64
|
+
|
|
65
|
+
O mesmo vale no espelho do shared-app-modules (`CLIENT_GOAL_METRIC_VALUES` com label): "Salvamentos",
|
|
66
|
+
"Compartilhamentos", "VCR".
|
|
67
|
+
|
|
68
|
+
## Normalização de hashtag
|
|
69
|
+
|
|
70
|
+
Regra única, implementada **localmente em cada repo que usa** (decisão da Story): api-campaigns,
|
|
71
|
+
content-capture-worker e web-campaigns. Não vai para o shared-schemas.
|
|
72
|
+
|
|
73
|
+
`normalizeHashtag(value)`: tira espaços e `#` do começo, remove acentos (NFD), passa para
|
|
74
|
+
minúsculas e remove o que não for `[a-z0-9_]`.
|
|
75
|
+
|
|
76
|
+
- `"#GalaxyMoments"` → `"galaxymoments"`
|
|
77
|
+
- `"#VerãoSamsung"` → `"veraosamsung"`
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Arquivos
|
|
82
|
+
|
|
83
|
+
- Alterar `src/schemas/action.schema.ts` (ActionPhase + 3 campos).
|
|
84
|
+
- Alterar `src/schemas/content.schema.ts` (5 campos, enum de origem, 2 índices).
|
|
85
|
+
- Alterar `src/schemas/client.schema.ts` (3 métricas no catálogo).
|
|
86
|
+
- Alterar `package.json` (1.0.220).
|
|
87
|
+
- shared-app-modules: `src/app/constants/values.constant.ts` (3 métricas com label) e
|
|
88
|
+
`src/app/interfaces/action/action.interface.ts` (`organicHashtags`, `phases`, `goals`, mais
|
|
89
|
+
`ActionPhaseInterface`); interface de Content usada no web-campaigns, se houver no pacote.
|
|
90
|
+
|
|
91
|
+
## Passos
|
|
92
|
+
|
|
93
|
+
1. `ActionPhase` com `_id` (subdocumento com id) e os três campos novos na Action, com JSDoc em PT e "Ver GD-691".
|
|
94
|
+
2. Campos e índices do Content.
|
|
95
|
+
3. Métricas novas no catálogo do client.
|
|
96
|
+
4. `npm run build` e conferir no `dist` os campos e exports; checar com `validateSync` que uma Action e um Content antigos (sem os campos) continuam válidos e que `phases[0]._id` é gerado.
|
|
97
|
+
5. shared-app-modules: catálogo e interfaces; `pnpm run build`.
|
|
98
|
+
6. Publicar os dois pacotes (José).
|
|
99
|
+
|
|
100
|
+
## Dependências
|
|
101
|
+
|
|
102
|
+
- GD-688 (catálogo de métricas) já em produção.
|
|
103
|
+
- Esta Task vem antes da GD-693 e da GD-694.
|
|
104
|
+
|
|
105
|
+
## Validação
|
|
106
|
+
|
|
107
|
+
- `npm run build` no shared-schemas; `pnpm run build` no shared-app-modules.
|
|
108
|
+
- `validateSync` de documentos antigos e novos (script rápido com o `dist`).
|
|
109
|
+
|
|
110
|
+
## Pontos abertos
|
|
111
|
+
|
|
112
|
+
- `vcr` entra no catálogo antes de existir no Content (chega na GD-707). Até lá a meta pode ser cadastrada, mas o Hub mostra o realizado como indisponível.
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
import { Recruitment } from './recruitment.schema';
|
|
12
12
|
import { Content } from './content.schema';
|
|
13
|
-
import { Client } from './client.schema';
|
|
13
|
+
import { Client, ClientMetricGoal, ClientMetricGoalSchema } from './client.schema';
|
|
14
14
|
import { Sale } from './sale.schema';
|
|
15
15
|
import { Campaign } from './campaign.schema';
|
|
16
16
|
import { Note, NoteSchema } from './note.schema';
|
|
@@ -68,6 +68,48 @@ export const ACTION_STATUS_VALUES = [
|
|
|
68
68
|
] as const;
|
|
69
69
|
export type ActionStatus = (typeof ACTION_STATUS_VALUES)[number];
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Fase da Ação (antigo briefing do genc). Com `_id` porque o Conteúdo aponta
|
|
73
|
+
* para ela (`Content.phase`). Período e hashtags opcionais fazem a captura
|
|
74
|
+
* sugerir a Fase sozinha; Fase sem os dois nunca é automática. Fases podem se
|
|
75
|
+
* sobrepor: vale a primeira por `order`. Ver GD-691.
|
|
76
|
+
*/
|
|
77
|
+
@Schema()
|
|
78
|
+
export class ActionPhase {
|
|
79
|
+
@ApiProperty({ example: '6710a1b2c3d4e5f601234567' })
|
|
80
|
+
_id: Types.ObjectId;
|
|
81
|
+
|
|
82
|
+
@ApiProperty({ example: 'Lançamento' })
|
|
83
|
+
@Prop({ type: String, required: true })
|
|
84
|
+
name: string;
|
|
85
|
+
|
|
86
|
+
/** Hex #rrggbb, da paleta do cpanel. */
|
|
87
|
+
@ApiProperty({ example: '#2563eb' })
|
|
88
|
+
@Prop({ type: String, required: true })
|
|
89
|
+
color: string;
|
|
90
|
+
|
|
91
|
+
@ApiProperty({ example: 0, description: 'Prioridade quando Fases se sobrepõem (menor vale primeiro)' })
|
|
92
|
+
@Prop({ type: Number, required: true })
|
|
93
|
+
order: number;
|
|
94
|
+
|
|
95
|
+
/** Inclusivo. */
|
|
96
|
+
@ApiProperty({ type: Date, required: false, nullable: true })
|
|
97
|
+
@Prop({ type: Date, required: false, default: null })
|
|
98
|
+
startDate?: Date | null;
|
|
99
|
+
|
|
100
|
+
/** Inclusivo (vale o dia inteiro). */
|
|
101
|
+
@ApiProperty({ type: Date, required: false, nullable: true })
|
|
102
|
+
@Prop({ type: Date, required: false, default: null })
|
|
103
|
+
endDate?: Date | null;
|
|
104
|
+
|
|
105
|
+
/** Normalizadas (minúsculas, sem # e sem acento). */
|
|
106
|
+
@ApiProperty({ type: [String], example: ['galaxys26'] })
|
|
107
|
+
@Prop({ type: [String], default: [] })
|
|
108
|
+
hashtags: string[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const ActionPhaseSchema = SchemaFactory.createForClass(ActionPhase);
|
|
112
|
+
|
|
71
113
|
@Schema({ timestamps: true })
|
|
72
114
|
export class Action {
|
|
73
115
|
@ApiProperty({ example: '64bfc3a78d3f1e2a12345678' })
|
|
@@ -252,6 +294,24 @@ export class Action {
|
|
|
252
294
|
@ApiProperty({ type: () => ActionPromptConfig, required: false, description: 'Customização de prompts de classificação de comentário por IA para esta action — campos ausentes caem no prompt genérico default' })
|
|
253
295
|
@Prop({ type: ActionPromptConfigSchema, required: false, default: null })
|
|
254
296
|
promptConfig?: ActionPromptConfig | null;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Hashtags que marcam o Conteúdo como Orgânico (Tipo, fora do escopo
|
|
300
|
+
* contratado). Normalizadas. Vazio = a captura não sugere Tipo. Ver GD-691.
|
|
301
|
+
*/
|
|
302
|
+
@ApiProperty({ type: [String], required: false, example: ['galaxymoments'] })
|
|
303
|
+
@Prop({ type: [String], default: [] })
|
|
304
|
+
organicHashtags?: string[];
|
|
305
|
+
|
|
306
|
+
/** Fases da Ação. Ver ActionPhase e GD-691. */
|
|
307
|
+
@ApiProperty({ type: () => [ActionPhase], required: false })
|
|
308
|
+
@Prop({ type: [ActionPhaseSchema], default: [] })
|
|
309
|
+
phases?: ActionPhase[];
|
|
310
|
+
|
|
311
|
+
/** Metas da Ação: mesmo { metric, value } e catálogo dos Tiers do cliente. Ver GD-691. */
|
|
312
|
+
@ApiProperty({ type: () => [ClientMetricGoal], required: false })
|
|
313
|
+
@Prop({ type: [ClientMetricGoalSchema], default: [] })
|
|
314
|
+
goals?: ClientMetricGoal[];
|
|
255
315
|
}
|
|
256
316
|
|
|
257
317
|
export const ActionSchema = SchemaFactory.createForClass(Action);
|
|
@@ -39,6 +39,9 @@ export const CLIENT_GOAL_METRIC_VALUES = [
|
|
|
39
39
|
'cpmi',
|
|
40
40
|
'cpi',
|
|
41
41
|
'cpe',
|
|
42
|
+
'savedCount',
|
|
43
|
+
'sharesCount',
|
|
44
|
+
'vcr',
|
|
42
45
|
'viralReachEngagementRate',
|
|
43
46
|
] as const;
|
|
44
47
|
export type ClientGoalMetric = (typeof CLIENT_GOAL_METRIC_VALUES)[number];
|
|
@@ -60,6 +63,9 @@ export const CLIENT_GOAL_METRIC_UNITS: Record<ClientGoalMetric, ClientGoalMetric
|
|
|
60
63
|
cpmi: 'currency',
|
|
61
64
|
cpi: 'currency',
|
|
62
65
|
cpe: 'currency',
|
|
66
|
+
savedCount: 'count',
|
|
67
|
+
sharesCount: 'count',
|
|
68
|
+
vcr: 'rate',
|
|
63
69
|
viralReachEngagementRate: 'rate',
|
|
64
70
|
};
|
|
65
71
|
|
|
@@ -101,10 +107,12 @@ export const CommissionRangeSchema = SchemaFactory.createForClass(CommissionRang
|
|
|
101
107
|
|
|
102
108
|
@Schema({ _id: false })
|
|
103
109
|
export class ClientMetricGoal {
|
|
110
|
+
@ApiProperty({ enum: CLIENT_GOAL_METRIC_VALUES, example: 'potentialReach' })
|
|
104
111
|
@Prop({ type: String, enum: CLIENT_GOAL_METRIC_VALUES, required: true })
|
|
105
112
|
metric: ClientGoalMetric;
|
|
106
113
|
|
|
107
114
|
/** Na unidade da métrica (`CLIENT_GOAL_METRIC_UNITS`); `rate` em fração. */
|
|
115
|
+
@ApiProperty({ example: 120000000, description: 'Na unidade da métrica; percentuais em fração (0,25 = 25%)' })
|
|
108
116
|
@Prop({ type: Number, required: true })
|
|
109
117
|
value: number;
|
|
110
118
|
}
|
|
@@ -217,6 +217,10 @@ export const CONTENT_COMMENT_COUNT_SOURCE_VALUES = ['primetag', 'exportcomments'
|
|
|
217
217
|
export type ContentCommentCountSource = (typeof CONTENT_COMMENT_COUNT_SOURCE_VALUES)[number];
|
|
218
218
|
|
|
219
219
|
export const CONTENT_METRICS_SOURCE_VALUES = ['real', 'organic', 'estimated'] as const;
|
|
220
|
+
|
|
221
|
+
/** Origem do Tipo e da Fase: auto = regra da captura, manual = quem revisou. Ver GD-691. */
|
|
222
|
+
export const CONTENT_CLASSIFICATION_SOURCE_VALUES = ['auto', 'manual'] as const;
|
|
223
|
+
export type ContentClassificationSource = (typeof CONTENT_CLASSIFICATION_SOURCE_VALUES)[number];
|
|
220
224
|
export type ContentMetricsSource = (typeof CONTENT_METRICS_SOURCE_VALUES)[number];
|
|
221
225
|
|
|
222
226
|
@Schema({ _id: false })
|
|
@@ -378,6 +382,28 @@ export class Content {
|
|
|
378
382
|
// empurra o `metrics` anterior (antes de ser sobrescrito) pra cá.
|
|
379
383
|
@Prop({ type: [ContentMetricsHistoryEntrySchema], required: false, default: [] })
|
|
380
384
|
history?: ContentMetricsHistoryEntry[];
|
|
385
|
+
|
|
386
|
+
/** Hashtags da publicação, normalizadas (minúsculas, sem # e sem acento). Ver GD-691. */
|
|
387
|
+
@Prop({ type: [String], default: [] })
|
|
388
|
+
hashtags?: string[];
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Tipo do Conteúdo: Orgânico (fora do escopo contratado) ou Pago. null =
|
|
392
|
+
* Ação sem hashtag de Orgânico. Não confundir com metricsSource 'organic'
|
|
393
|
+
* (métrica pública) nem com isBoosted (mídia paga). Ver GD-691.
|
|
394
|
+
*/
|
|
395
|
+
@Prop({ type: Boolean, required: false, default: null })
|
|
396
|
+
isOrganic?: boolean | null;
|
|
397
|
+
|
|
398
|
+
@Prop({ type: String, enum: CONTENT_CLASSIFICATION_SOURCE_VALUES, required: false, default: null })
|
|
399
|
+
isOrganicSource?: ContentClassificationSource | null;
|
|
400
|
+
|
|
401
|
+
/** _id de uma Action.phases. Ver GD-691. */
|
|
402
|
+
@Prop({ type: MongooseSchema.Types.ObjectId, required: false, default: null })
|
|
403
|
+
phase?: Types.ObjectId | null;
|
|
404
|
+
|
|
405
|
+
@Prop({ type: String, enum: CONTENT_CLASSIFICATION_SOURCE_VALUES, required: false, default: null })
|
|
406
|
+
phaseSource?: ContentClassificationSource | null;
|
|
381
407
|
}
|
|
382
408
|
|
|
383
409
|
export const ContentSchema = SchemaFactory.createForClass(Content);
|
|
@@ -387,6 +413,8 @@ ContentSchema.index({ action: 1, profile: 1, type: 1 });
|
|
|
387
413
|
ContentSchema.index({ action: 1, profile: 1, socialnetwork: 1 });
|
|
388
414
|
ContentSchema.index({ action: 1, status: 1, postedAt: 1 });
|
|
389
415
|
ContentSchema.index({ profile: 1 });
|
|
416
|
+
ContentSchema.index({ action: 1, phase: 1 });
|
|
417
|
+
ContentSchema.index({ action: 1, isOrganic: 1 });
|
|
390
418
|
ContentSchema.index({ createdAt: -1, status: 1 });
|
|
391
419
|
// Chave de idempotência da captura da Primetag — conteúdos manuais (sem
|
|
392
420
|
// primetagId) ficam fora do índice.
|