funifier-mcp 0.2.26 → 0.2.27
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/.cursor/rules/funifier.mdc +38 -41
- package/.github/copilot-instructions.md +38 -41
- package/AGENTS.md +56 -49
- package/README.md +40 -22
- package/datasource-funifier-docs/.coverage.json +326 -0
- package/datasource-funifier-docs/.validation.json +593 -0
- package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
- package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
- package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
- package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
- package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
- package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
- package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
- package/datasource-funifier-docs/knowledge/index.md +4 -1
- package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
- package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
- package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
- package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
- package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
- package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
- package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
- package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
- package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
- package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
- package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
- package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
- package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
- package/datasource-funifier-docs/knowledge/modules/folder.md +935 -280
- package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
- package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
- package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
- package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
- package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
- package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
- package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
- package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
- package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
- package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
- package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
- package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
- package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
- package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
- package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
- package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
- package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
- package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
- package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
- package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
- package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
- package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
- package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
- package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
- package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
- package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
- package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +42 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/init.test.js +74 -3
- package/dist/cli/init.test.js.map +1 -1
- package/dist/cli/persona.d.ts +3 -0
- package/dist/cli/persona.d.ts.map +1 -0
- package/dist/cli/persona.js +25 -0
- package/dist/cli/persona.js.map +1 -0
- package/dist/mcp/bundle.js +119 -93
- package/dist/mcp/index.js +2 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/resources/documentation.d.ts +1 -1
- package/dist/mcp/resources/documentation.d.ts.map +1 -1
- package/dist/mcp/resources/documentation.js +39 -3
- package/dist/mcp/resources/documentation.js.map +1 -1
- package/dist/mcp/tools/connect.d.ts.map +1 -1
- package/dist/mcp/tools/connect.js +18 -8
- package/dist/mcp/tools/connect.js.map +1 -1
- package/dist/mcp/tools/database.d.ts.map +1 -1
- package/dist/mcp/tools/database.js +59 -47
- package/dist/mcp/tools/database.js.map +1 -1
- package/dist/mcp/tools/database.test.js +2 -2
- package/dist/mcp/tools/database.test.js.map +1 -1
- package/dist/mcp/tools/delete.d.ts.map +1 -1
- package/dist/mcp/tools/delete.js +13 -3
- package/dist/mcp/tools/delete.js.map +1 -1
- package/dist/mcp/tools/execute.d.ts.map +1 -1
- package/dist/mcp/tools/execute.js +20 -9
- package/dist/mcp/tools/execute.js.map +1 -1
- package/dist/mcp/tools/folder.d.ts.map +1 -1
- package/dist/mcp/tools/folder.js +22 -12
- package/dist/mcp/tools/folder.js.map +1 -1
- package/dist/mcp/tools/get.d.ts.map +1 -1
- package/dist/mcp/tools/get.js +16 -6
- package/dist/mcp/tools/get.js.map +1 -1
- package/dist/mcp/tools/index.d.ts +1 -1
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +3 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/list.d.ts.map +1 -1
- package/dist/mcp/tools/list.js +38 -14
- package/dist/mcp/tools/list.js.map +1 -1
- package/dist/mcp/tools/logs.d.ts.map +1 -1
- package/dist/mcp/tools/logs.js +15 -5
- package/dist/mcp/tools/logs.js.map +1 -1
- package/dist/mcp/tools/save.d.ts.map +1 -1
- package/dist/mcp/tools/save.js +14 -4
- package/dist/mcp/tools/save.js.map +1 -1
- package/dist/mcp/tools/save.test.js +3 -3
- package/dist/mcp/tools/save.test.js.map +1 -1
- package/dist/mcp/tools/search-docs.d.ts +3 -0
- package/dist/mcp/tools/search-docs.d.ts.map +1 -0
- package/dist/mcp/tools/search-docs.js +102 -0
- package/dist/mcp/tools/search-docs.js.map +1 -0
- package/package.json +6 -2
- package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
- package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
- package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
- package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
- package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
- package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
- package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
- package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
- package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
- package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
- package/skills/funifier/SKILL.md +88 -0
- package/skills/funifier/references/configure-security.md +96 -0
- package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
- package/skills/funifier/references/create-aggregate.md +144 -0
- package/skills/funifier/references/create-challenge.md +116 -0
- package/skills/funifier/references/create-competition.md +98 -0
- package/skills/funifier/references/create-crossword.md +574 -0
- package/skills/funifier/references/create-custom-object.md +91 -0
- package/skills/funifier/references/create-custom-page.md +135 -0
- package/skills/funifier/references/create-folder.md +104 -0
- package/skills/funifier/references/create-lastmile.md +643 -0
- package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
- package/skills/funifier/references/create-level.md +94 -0
- package/skills/funifier/references/create-lottery.md +913 -0
- package/skills/funifier/references/create-mystery.md +769 -0
- package/skills/funifier/references/create-notification.md +75 -0
- package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
- package/skills/funifier/references/create-quiz.md +98 -0
- package/skills/funifier/references/create-scheduler.md +141 -0
- package/skills/funifier/references/create-story.md +636 -0
- package/skills/funifier/references/create-swap.md +95 -0
- package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
- package/skills/funifier/references/create-virtual-good.md +96 -0
- package/skills/funifier/references/create-webhook.md +72 -0
- package/skills/funifier/references/create-websocket.md +71 -0
- package/skills/funifier/references/create-widget.md +76 -0
- package/skills/funifier/references/debug.md +87 -0
- package/skills/funifier/references/help.md +81 -0
- package/skills/funifier/references/implement-frontend.md +106 -0
- package/skills/funifier/references/import-csv.md +75 -0
- package/skills/funifier/references/manage-player.md +82 -0
- package/skills/funifier/references/manage-team.md +76 -0
- package/skills/funifier/references/upload-file.md +91 -0
- package/skills/funifier-create-aggregate/SKILL.md +0 -127
- package/skills/funifier-create-challenge/SKILL.md +0 -88
- package/skills/funifier-create-custom-page/SKILL.md +0 -127
- package/skills/funifier-create-level/SKILL.md +0 -87
- package/skills/funifier-create-quiz/SKILL.md +0 -87
- package/skills/funifier-create-scheduler/SKILL.md +0 -127
- package/skills/funifier-create-virtual-good/SKILL.md +0 -87
- package/skills/funifier-debug/SKILL.md +0 -92
- package/skills/funifier-help/SKILL.md +0 -86
- package/skills/funifier-implement-frontend/SKILL.md +0 -90
- package/skills/funifier-index/SKILL.md +0 -58
|
@@ -1,23 +1,418 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `kpi-formulas`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Acesso Studio:** `não confirmado no código backend` — o router da base de conhecimento (`index.md`) cita o módulo como "KPI Formulas / Fórmulas e indicadores", mas não há rota `/studio/...` nem nome "kpi-formulas" no `funifier-service`. O termo é uma abstração de documentação.
|
|
4
|
+
**API Endpoints:**
|
|
5
|
+
- `/v3/prepared/aggregate` — CRUD das fórmulas parametrizadas (`PreparedRest`)
|
|
6
|
+
- `/v3/find/{id}` — **execução** de uma fórmula parametrizada (`FindRest`)
|
|
7
|
+
- `/v3/challenge/prepared` — listagem das fórmulas de KPI usadas em rankings (`ChallengeRest.findAllPrepared`)
|
|
8
|
+
**Coleções MongoDB:** `prepared_aggregate` e `challenge_rule_prepared` (auxiliares: `expire`, `find_cache_<id>__c`)
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
> **Nota de engenharia reversa.** Não existe no `funifier-service` nenhuma classe, coleção ou rota chamada `kpi-formulas`/`KpiFormula`. A documentação anterior (genérica) foi descartada. O que a plataforma realmente implementa sob o conceito de "fórmulas/KPIs derivados de dados" são **dois subsistemas independentes** (`prepared_aggregate` e `challenge_rule_prepared`), ambos baseados em comandos de _aggregate_ do MongoDB salvos no banco. Existe ainda um terceiro artefato — `dashboard.KPI` / coleção `kpi` — que é **código legado morto** (ver §7).
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
---
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
- Para fórmulas considerando múltiplos critérios (quantidade, valor, tempo)
|
|
11
|
-
- Para regras avançadas de rankings
|
|
12
|
-
- **Importante:** usar com critério, apenas quando recursos padrão não atendem
|
|
14
|
+
## 1. Visão Geral
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
"KPI Formulas" é, no código, uma **família de comandos de agregação MongoDB persistidos** que calculam valores derivados (indicadores, totais, médias, rankings) a partir dos dados da gamificação — sem que a lógica do pipeline fique acoplada à entidade que o consome.
|
|
17
|
+
|
|
18
|
+
São **dois mecanismos distintos, com coleções, rotas e sintaxe de parâmetros diferentes**:
|
|
19
|
+
|
|
20
|
+
| Subsistema | Coleção | Entidade | Config (REST) | Execução |
|
|
21
|
+
|---|---|---|---|---|
|
|
22
|
+
| **Prepared Aggregate / Find** | `prepared_aggregate` | `PreparedAggregate` (`com.funifier.engine.find`) | `/v3/prepared/aggregate` | `/v3/find/{id}` |
|
|
23
|
+
| **KPI Rule (ranking)** | `challenge_rule_prepared` | `ChallengeRulePrepared` (`com.funifier.engine.challenge`) | `/v3/database/challenge_rule_prepared` + `GET /v3/challenge/prepared` | embutida nos motores de ranking/desafio |
|
|
24
|
+
|
|
25
|
+
Papel arquitetural:
|
|
26
|
+
|
|
27
|
+
- **Prepared Aggregate** (`engine/find`) é o motor genérico de "consultas salvas": uma agregação parametrizável (`$param:`), com expressões de data Funifier (`$date`), pré-script Groovy opcional (sandbox), cache de resultado e encadeamento (`parent`). É configurada em `/v3/prepared/aggregate` e **executada** em `/v3/find/{id}`. É o recurso mais próximo de "criar um campo calculado/KPI a partir de qualquer coleção".
|
|
28
|
+
- **KPI Rule** (`challenge_rule_prepared`) é uma fórmula nomeada e **reutilizável de ranking**: um pipeline que retorna `_id`, `total` e `max` por jogador/equipe. É exatamente o artefato que o próprio código chama de "KPI" (`Operation.TYPE_PREPARED_KPI = 10`). É referenciada por desafios (`ChallengeRule.prepared`), leaderboards (`LeaderBoard_V2.operation`), competições e crownings.
|
|
29
|
+
|
|
30
|
+
Relação com outros módulos (confirmada no código):
|
|
31
|
+
|
|
32
|
+
- `challenge` — `AchievementManager.fireAction` copia `entity` + `aggregate` de um `ChallengeRulePrepared` para dentro da `ChallengeRule` quando `rule.prepared` está preenchido (`AchievementManager.java:295-299`).
|
|
33
|
+
- `leaderboard` — `LeaderBoardManager.findLiveLeadersAggregate` usa `TYPE_PREPARED_KPI` para montar o ranking (`LeaderBoardManager.java:332`).
|
|
34
|
+
- `competition` / `crowning` — `CompetitionManager` e `CrowningManager` usam o mesmo `TYPE_PREPARED_KPI` (`CrowningManager.java:712`).
|
|
35
|
+
- `database` / `studio-page` — fórmulas `prepared_aggregate` são tipicamente disparadas pelo frontend/Studio via HTTP em `/v3/find/{id}`.
|
|
36
|
+
|
|
37
|
+
Problema que resolve: centralizar pipelines de agregação complexos (que de outra forma seriam reescritos em cada leaderboard/desafio/página) em objetos reutilizáveis, parametrizáveis e cacheáveis.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 2. Arquitetura e Fluxos
|
|
42
|
+
|
|
43
|
+
### 2.1 Classes envolvidas
|
|
44
|
+
|
|
45
|
+
| Classe | Papel |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `com.funifier.engine.find.PreparedAggregate` | Entidade raiz do subsistema Prepared/Find (coleção `prepared_aggregate`) |
|
|
48
|
+
| `com.funifier.engine.find.PreparedManager` | Compila/cacheia scripts Groovy, salva/exclui, executa o pré-script; **não** executa a agregação Mongo (isso é feito no `FindRest`) |
|
|
49
|
+
| `com.funifier.rest.v3.rest.PreparedRest` | REST de configuração (`/v3/prepared`) |
|
|
50
|
+
| `com.funifier.rest.v3.rest.FindRest` | REST de **execução** (`/v3/find`) — monta e roda o pipeline, com cache e paginação |
|
|
51
|
+
| `com.funifier.engine.util.FunifierMarshaller` | Substitui `$param:xxx` e converte `$date` (keyword → epoch millis) |
|
|
52
|
+
| `com.funifier.engine.challenge.ChallengeRulePrepared` | Entidade raiz da fórmula de KPI de ranking (coleção `challenge_rule_prepared`) |
|
|
53
|
+
| `com.funifier.engine.crowning.Operation` | Define `TYPE_PREPARED_KPI = 10` e o campo `prepared` (id de `ChallengeRulePrepared`) |
|
|
54
|
+
| `LeaderBoardManager` / `CompetitionManager` / `CrowningManager` / `AchievementManager` | Consumidores da KPI Rule |
|
|
55
|
+
| `com.funifier.engine.crowning.Expire` | Controle de expiração de cache do Prepared/Find (coleção `expire`) |
|
|
56
|
+
|
|
57
|
+
Não há `Repository`/`Dao` dedicado: a manipulação MongoDB é feita via `Jongo` diretamente dentro dos managers/rest.
|
|
58
|
+
|
|
59
|
+
### 2.2 Pipeline de execução — `prepared_aggregate` (`POST /v3/find/{id}`)
|
|
60
|
+
|
|
61
|
+
Sequência real (`FindRest.run` → `findLiveAggregate` / `findCachedAggregate`):
|
|
62
|
+
|
|
63
|
+
1. **[Trigger]** `FindRest.run(id, params, Range, strict)`
|
|
64
|
+
2. **[Contexto]** `authBean.getPlayerFromTokenIfExist()` → `context.player` (jogador do token)
|
|
65
|
+
3. **[Load]** `PreparedManager.find(id)` — se não existir, retorna **401** com `{"message":"prepared aggregate dont exists"}`
|
|
66
|
+
4. **[Parent]** se `obj.parent` preenchido e o parent não tiver outro parent (encadeamento de **um único nível**), o pipeline do parent é gerado primeiro (com ou sem cache) e vira a base
|
|
67
|
+
5. **[Cache?]** se `obj.cache` preenchido → `findCachedAggregate`; senão → `findLiveAggregate`
|
|
68
|
+
6. **[Script]** dentro de `findLiveAggregate`, se `obj.script` existir, roda o pré-script Groovy via `PreparedManager.run(..., "script")` (`context.item = obj`)
|
|
69
|
+
7. **[Parse]** `JsonUtil.fromJson(obj.aggregate, List.class)` → lista de estágios
|
|
70
|
+
8. **[Substituição]** cada estágio passa por `FunifierMarshaller.toStringWithParams(stage, params)` → resolve `$param:xxx` e `$date`
|
|
71
|
+
9. **[Run]** `jongo.getCollection(obj.collection).aggregate(...).options(allowDiskUse(true))` + `.and(...)` para os demais estágios
|
|
72
|
+
10. **[Paginação]** `PaginationUtil.getPageResultThrowsException(a, range, 0, 100)` — **100 por página** por padrão; `?strict=true` usa serialização Gson
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
flowchart TD
|
|
76
|
+
A["POST /v3/find/{id}<br/>+ params (body)"] --> B["PreparedManager.find(id)"]
|
|
77
|
+
B -->|null| E["401 prepared aggregate dont exists"]
|
|
78
|
+
B -->|ok| P{"obj.parent?"}
|
|
79
|
+
P -->|sim| PC["gera aggregate do parent<br/>(1 nível) como base"]
|
|
80
|
+
P -->|não| C
|
|
81
|
+
PC --> C{"obj.cache?"}
|
|
82
|
+
C -->|sim| CACHE["findCachedAggregate<br/>$out find_cache_id__c"]
|
|
83
|
+
C -->|não| LIVE["findLiveAggregate"]
|
|
84
|
+
LIVE --> S{"obj.script?"}
|
|
85
|
+
S -->|sim| GR["PreparedManager.run (Groovy pré-script)"]
|
|
86
|
+
S -->|não| MAR
|
|
87
|
+
GR --> MAR["FunifierMarshaller.toStringWithParams<br/>($param: / $date)"]
|
|
88
|
+
MAR --> AGG["collection.aggregate(...) allowDiskUse"]
|
|
89
|
+
CACHE --> PAG["PaginationUtil (Range, default 100)"]
|
|
90
|
+
AGG --> PAG
|
|
91
|
+
PAG --> R["resposta paginada"]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 2.3 Pipeline de KPI de ranking — `challenge_rule_prepared` (`TYPE_PREPARED_KPI`)
|
|
95
|
+
|
|
96
|
+
Quando um leaderboard/competição/crowning tem `operation.type == TYPE_PREPARED_KPI` e `operation.prepared` (id de um `ChallengeRulePrepared`):
|
|
97
|
+
|
|
98
|
+
1. Carrega `ChallengeRulePrepared` por id na coleção `challenge_rule_prepared`
|
|
99
|
+
2. Substitui **placeholders literais** no `aggregate` via `String.replaceAll`:
|
|
100
|
+
- `FUNIFIER_PERIOD_START_TIME` → início do período (`currentRange[0].getTime()`)
|
|
101
|
+
- `FUNIFIER_PERIOD_END_TIME` → fim do período
|
|
102
|
+
- `FUNIFIER_TRIGGER_TIME` → `new Date().getTime()`
|
|
103
|
+
- `FUNIFIER_PLAYER_IDS` → JSON dos ids (usado em crowning; **comentado** no leaderboard)
|
|
104
|
+
3. Roda o pipeline sobre `jongo.getCollection(prepared.entity)` — o pipeline deve produzir `_id`, `total`, `max`
|
|
105
|
+
4. O motor de ranking acrescenta seus próprios estágios (lookup de team/player, `$facet` + `$unwind includeArrayIndex` para calcular `position`, ordenação por `total`/`max`)
|
|
106
|
+
|
|
107
|
+
```mermaid
|
|
108
|
+
sequenceDiagram
|
|
109
|
+
participant LB as LeaderBoardManager / CrowningManager
|
|
110
|
+
participant DB as Mongo (challenge_rule_prepared)
|
|
111
|
+
participant E as Mongo (prepared.entity)
|
|
112
|
+
LB->>DB: findOne {_id: operation.prepared}
|
|
113
|
+
DB-->>LB: ChallengeRulePrepared (entity, aggregate)
|
|
114
|
+
LB->>LB: replaceAll FUNIFIER_PERIOD_START_TIME / END / TRIGGER_TIME / PLAYER_IDS
|
|
115
|
+
LB->>E: aggregate(pipeline) → {_id, total, max}
|
|
116
|
+
E-->>LB: resultados por jogador/equipe
|
|
117
|
+
LB->>LB: + $lookup team/player, $facet position, $sort
|
|
118
|
+
LB-->>LB: ranking final
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 2.4 Pré-script Groovy (`PreparedManager.run`)
|
|
122
|
+
|
|
123
|
+
- `getScript(src)` envolve o script do usuário numa classe Groovy `FunifierTrigger` com imports utilitários (Unirest, mail, thumbnailator, entidades Funifier, `ManagerFactory`), `@TimedInterrupt(5s)`, setters `setContext`/`setManager` e `println` redirecionado para uma lista de output.
|
|
124
|
+
- `compile(script)` usa `SecureASTCustomizer` + `TriggerExpressionChecker` + whitelist de tokens (aritméticos, comparação, lógicos, colchetes).
|
|
125
|
+
- `executor(...)` roda num `Executors.newSingleThreadExecutor()` com timeout = `prepared.timeout` (default **5s**), invocando `setContext`, `setManager`, `prepare(aggregate, params)`, `addAllOutput(outputs)`.
|
|
126
|
+
- Classes compiladas ficam em cache (`Map compiled`); recompila apenas se `updated > lastCompilation`.
|
|
127
|
+
|
|
128
|
+
> Síncrono. A execução da agregação em `/v3/find` é bloqueante; o pré-script Groovy roda em outra thread mas é aguardado (com timeout).
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 3. Estrutura dos Objetos
|
|
133
|
+
|
|
134
|
+
### 3.1 `PreparedAggregate` — coleção `prepared_aggregate`
|
|
135
|
+
|
|
136
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
137
|
+
|---|---|---|---|---|
|
|
138
|
+
| `_id` | String | auto (`Guid.shortTimeMillis()`) | — | Gerado no `save` se vazio |
|
|
139
|
+
| `title` | String | — | não | Rótulo |
|
|
140
|
+
| `description` | String | — | não | Descrição |
|
|
141
|
+
| `parent` | String | — | não | `_id` de outro `prepared_aggregate` cujo resultado serve de base (encadeamento de **1 nível**) |
|
|
142
|
+
| `collection` | String | — | sim (se não usa `parent`) | Coleção onde o `aggregate` roda (ex.: `player`, `action_log`) |
|
|
143
|
+
| `aggregate` | String (JSON array) | — | sim | Comando de agregação; aceita `$param:xxx` e `$date` |
|
|
144
|
+
| `script` | String (Groovy) | — | não | Pré-script executado antes de montar o pipeline |
|
|
145
|
+
| `timeout` | Long (segundos) | `5` | não | Timeout do pré-script Groovy |
|
|
146
|
+
| `cache` | String (keyword) | — | não | Liga cache do resultado; expressão de TTL (ex.: `30m`, `+3h`) |
|
|
147
|
+
| `cache_before_find` | String (Groovy) | — | não | Script rodado antes de localizar o cache |
|
|
148
|
+
| `updated` | Date | auto | — | Definido no `save`; usado para invalidar o cache de classe compilada |
|
|
149
|
+
| `extra` | Map | `{}` | não | Campos livres |
|
|
150
|
+
|
|
151
|
+
Campos comentados/legados no código: `cache_before_save` (declarado e comentado em `PreparedAggregate.java:54`).
|
|
152
|
+
|
|
153
|
+
### 3.2 `ChallengeRulePrepared` — coleção `challenge_rule_prepared`
|
|
154
|
+
|
|
155
|
+
| Campo | Tipo | Obrigatório | Descrição |
|
|
156
|
+
|---|---|---|---|
|
|
157
|
+
| `_id` | String | — | Id |
|
|
158
|
+
| `title` | String | não | Rótulo |
|
|
159
|
+
| `description` | String | não | Descrição |
|
|
160
|
+
| `action` | String | não | Ação à qual o comando se aplica (usado na avaliação de desafio) |
|
|
161
|
+
| `entity` | String | sim | Coleção onde o `aggregate` é executado |
|
|
162
|
+
| `aggregate` | String (JSON array) | sim | Pipeline que deve retornar `_id`, `total`, `max`; aceita placeholders `FUNIFIER_*` |
|
|
163
|
+
|
|
164
|
+
> Diferença crítica de sintaxe: `ChallengeRulePrepared.aggregate` **não** usa `$param:` nem `$date`. Usa substituição literal de string (`FUNIFIER_PERIOD_START_TIME`, `FUNIFIER_PERIOD_END_TIME`, `FUNIFIER_TRIGGER_TIME`, `FUNIFIER_PLAYER_IDS`) feita por quem consome (leaderboard/crowning). Misturar as duas sintaxes não funciona.
|
|
165
|
+
|
|
166
|
+
### 3.3 `Operation` (crowning) — tipos de fórmula
|
|
167
|
+
|
|
168
|
+
`com.funifier.engine.crowning.Operation` define o "tipo de fórmula" de um ranking:
|
|
169
|
+
|
|
170
|
+
| Constante | Valor | Significado |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `TYPE_COUNT_ACTIONS` | 1 | Conta action logs |
|
|
173
|
+
| `TYPE_SUM_ATTRIBUTE` | 2 | Soma um atributo de action log |
|
|
174
|
+
| `TYPE_SUM_ACHIEVEMENTS` | 3 | Soma achievements |
|
|
175
|
+
| `TYPE_AVG_ATTRIBUTE` | 4 | Média de atributo |
|
|
176
|
+
| `TYPE_PREPARED_KPI` | 10 | **Usa um `challenge_rule_prepared`** (campo `operation.prepared` = id) |
|
|
177
|
+
|
|
178
|
+
Campos relevantes de `Operation`: `type`, `achievement_type`, `item`, `attribute`, `filters`, `sort`, `sub`, `subAttribute`, `prepared`.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 4. Endpoints
|
|
183
|
+
|
|
184
|
+
### `POST /v3/prepared/aggregate`
|
|
185
|
+
|
|
186
|
+
| Aspecto | Detalhe |
|
|
187
|
+
|---|---|
|
|
188
|
+
| Finalidade | Cria **ou atualiza** (upsert por `_id`) um `prepared_aggregate` |
|
|
189
|
+
| Autenticação | Bearer token |
|
|
190
|
+
| Full replace ou patch | **Full replace** (`save` faz `col.save(obj)`) |
|
|
191
|
+
|
|
192
|
+
**Comportamento real:** valida (compila) `script` e `cache_before_find` antes de salvar; se a compilação falhar retorna `{"status":"ERROR","message":...}` mas ainda com HTTP **201**. **Não valida** o campo `aggregate`. Define `updated = now`, gera `_id` se vazio, e limpa o cache de classe compilada + cache de resultado (`deleteCache`).
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{ "prepared": { "_id": "kpi_vendas", "...": "..." }, "status": "OK" }
|
|
196
|
+
```
|
|
15
197
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- [ ] Testar com dados reais
|
|
198
|
+
### `GET /v3/prepared/aggregate/{id}`
|
|
199
|
+
Retorna o objeto. Sem corpo se não existir (`null`).
|
|
19
200
|
|
|
20
|
-
|
|
201
|
+
### `DELETE /v3/prepared/aggregate/{id}`
|
|
202
|
+
Remove por id (`204 NO_CONTENT`), limpa caches de classe e de resultado.
|
|
203
|
+
|
|
204
|
+
### `DELETE /v3/prepared/aggregate?q=<mongo>`
|
|
205
|
+
Remove em massa os que casarem com `{q}` (query Mongo crua). **Engole todas as exceções silenciosamente** (`catch(Exception e){}`) e sempre retorna **200**, mesmo em erro de sintaxe da query.
|
|
206
|
+
|
|
207
|
+
### `POST /v3/prepared/aggregate/{id}/analyze`
|
|
208
|
+
|
|
209
|
+
| Aspecto | Detalhe |
|
|
210
|
+
|---|---|
|
|
211
|
+
| Finalidade | **Preview** da substituição de parâmetros + execução do pré-script |
|
|
212
|
+
| **NÃO executa a agregação** | As linhas que rodariam `log.aggregate(...)` estão **comentadas** (`PreparedRest.java:142-143,166,181-183`) |
|
|
213
|
+
|
|
214
|
+
**Comportamento real:** parseia `aggregate`, roda o pré-script Groovy (se houver) e devolve:
|
|
215
|
+
- `aggregate_step_2`: pipeline parseado (sem params)
|
|
216
|
+
- `aggregate_step_3`: pipeline com `$param:`/`$date` já substituídos
|
|
217
|
+
- `script_exceptions`, `script_outputs`
|
|
218
|
+
Header `X-Response-Time`. Se o objeto não existir → **401** com `{"message":"prepared aggregate dont exists"}`. **O nome "analyze" engana**: para obter dados reais use `/v3/find/{id}`.
|
|
219
|
+
|
|
220
|
+
### `POST /v3/find/{id}`
|
|
221
|
+
|
|
222
|
+
| Aspecto | Detalhe |
|
|
223
|
+
|---|---|
|
|
224
|
+
| Finalidade | **Executa** a fórmula e retorna os dados |
|
|
225
|
+
| Body | `HashMap` de params (chave = nome usado em `$param:nome`) |
|
|
226
|
+
| Query params | `strict` (`true` → Gson) |
|
|
227
|
+
| Headers | `Range` (paginação, ex.: `items=0-100`) |
|
|
228
|
+
|
|
229
|
+
**Comportamento real:** injeta `context.player` (do token), resolve `parent`, aplica cache (`$out`) se `cache` definido, substitui params, roda a agregação em `obj.collection`, pagina (default 100). Objeto inexistente → **401**.
|
|
230
|
+
|
|
231
|
+
**Path/Query params:**
|
|
232
|
+
|
|
233
|
+
| Param | Tipo | Descrição |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `id` | path | `_id` do `prepared_aggregate` |
|
|
236
|
+
| `strict` | query | `true` força serialização estrita (Gson) |
|
|
237
|
+
| `Range` | header | janela de paginação |
|
|
238
|
+
|
|
239
|
+
**Exemplo:**
|
|
240
|
+
```bash
|
|
241
|
+
POST /v3/find/kpi_vendas?strict=true
|
|
242
|
+
Range: items=0-50
|
|
243
|
+
{ "player": "ana", "start": "-30d" }
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### `GET /v3/find/{id}/params`
|
|
247
|
+
Retorna a lista de nomes de parâmetros (`$param:xxx`) encontrados **apenas** no campo `aggregate` por varredura de string. **Ignora** params usados só no `script`.
|
|
248
|
+
```json
|
|
249
|
+
["player","start"]
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### `GET /v3/challenge/prepared`
|
|
253
|
+
Lista todos os `challenge_rule_prepared` (`ChallengeRest.findAllPrepared`). O CRUD individual dessas regras é feito pelo endpoint genérico `/v3/database/challenge_rule_prepared`.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 5. Regras de Negócio
|
|
258
|
+
|
|
259
|
+
Regras presentes no código e não óbvias pelo schema:
|
|
260
|
+
|
|
261
|
+
- **Duas sintaxes de parâmetro incompatíveis** (ver §3.2): `prepared_aggregate` usa `$param:xxx` + `$date` (resolvidos por `FunifierMarshaller.toStringWithParams`); `challenge_rule_prepared` usa `FUNIFIER_*` via `String.replaceAll`.
|
|
262
|
+
- **Expressões de data Funifier** (`$date`): em `prepared_aggregate`, qualquer `{"$date":"<keyword>"}` é convertido para epoch millis por `DateUtil.fromKeyword` (ex.: `-5M`, `+3h`, `-30d`). Datas literais ficam inalteradas.
|
|
263
|
+
- **Contrato da KPI de ranking**: o `aggregate` de um `challenge_rule_prepared` **deve** produzir documentos com `_id` (id do jogador/equipe), `total` e `max`. Os motores de ranking dependem disso para `$group`/`$sort`.
|
|
264
|
+
- **Chave de cache inclui os params**: o registro `expire` é localizado por `{extra.item: _id, extra.params: params}` — params diferentes geram caches diferentes.
|
|
265
|
+
- **TTL de cache**: derivado da keyword em `cache`; se ausente/indefinido, default **+3h** (`FindRest.java:142`).
|
|
266
|
+
- **Encadeamento `parent` de 1 nível só**: o parent é processado apenas se ele próprio não tiver `parent` (`FindRest.java:81`).
|
|
267
|
+
- **Timeout do Groovy**: `prepared.timeout` (default 5s) limita o pré-script; estouro vira exceção coletada, não falha HTTP.
|
|
268
|
+
- **Multi-tenant**: tudo opera sobre a conexão Jongo da organização resolvida pela API key (`FrontController.getInstance(authBean.getApiKey())`). Coleções de cache (`find_cache_<id>__c`) são por organização.
|
|
269
|
+
- **`save` valida script mas não o pipeline**: um `aggregate` sintaticamente inválido só falha na execução (`/v3/find`), nunca na criação.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 6. Comportamentos Automáticos
|
|
274
|
+
|
|
275
|
+
| Comportamento | Trigger | Impacto | Persistência |
|
|
276
|
+
|---|---|---|---|
|
|
277
|
+
| Geração de `_id` | `save` com `_id` vazio | `Guid.shortTimeMillis()` | sim (`prepared_aggregate`) |
|
|
278
|
+
| `updated = now` | todo `save` | invalida cache de classe compilada | sim |
|
|
279
|
+
| Invalidação de cache | `save`/`delete` | remove classe compilada + dropa `find_cache_*__c` + remove `expire` | sim |
|
|
280
|
+
| Materialização de cache | `/v3/find` com `cache` | roda `$out` para `find_cache_<expireId>__c` e cria/atualiza `expire` | sim |
|
|
281
|
+
| **Polling de cache** | após `$out` | espera o cache materializar: **até 30 tentativas × `Thread.sleep(1000)`** (≈30s bloqueantes) | — |
|
|
282
|
+
| Cache de classe Groovy | 1ª execução | compila e guarda em `Map compiled`; reusa enquanto `updated` não mudar | em memória (por nó) |
|
|
283
|
+
| Cópia de `entity`/`aggregate` | `fireAction` com `rule.prepared` | resolve o `challenge_rule_prepared` e injeta na rule | runtime |
|
|
284
|
+
|
|
285
|
+
```mermaid
|
|
286
|
+
flowchart LR
|
|
287
|
+
F["/v3/find/{id} (cache on)"] --> X{"expire válido?"}
|
|
288
|
+
X -->|sim| READ["lê find_cache_id__c"]
|
|
289
|
+
X -->|não/expirado| OUT["roda pipeline + $out find_cache_id__c"]
|
|
290
|
+
OUT --> POLL["poll: até 30x sleep 1s<br/>até count > 0"]
|
|
291
|
+
POLL --> READ
|
|
292
|
+
READ --> RESP["resposta paginada"]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## 7. Suportado vs NÃO Suportado
|
|
298
|
+
|
|
299
|
+
### ✅ Suportado
|
|
300
|
+
- CRUD de `prepared_aggregate` (`/v3/prepared/aggregate`) com upsert por `_id`.
|
|
301
|
+
- Execução parametrizada com paginação (`/v3/find/{id}`), `$param:` e `$date`.
|
|
302
|
+
- Cache de resultado com TTL por keyword e chave por params.
|
|
303
|
+
- Pré-script Groovy sandboxed (timeout, whitelist de tokens).
|
|
304
|
+
- Encadeamento `parent` (1 nível).
|
|
305
|
+
- Introspecção de parâmetros (`GET /v3/find/{id}/params`).
|
|
306
|
+
- KPI de ranking via `challenge_rule_prepared` + `Operation.TYPE_PREPARED_KPI` em leaderboard/competição/crowning/desafio.
|
|
307
|
+
|
|
308
|
+
### ❌ NÃO Suportado / armadilhas confirmadas no código
|
|
309
|
+
- **`/v3/prepared/aggregate/{id}/analyze` NÃO executa a agregação** — só faz preview da substituição de params (código de execução comentado).
|
|
310
|
+
- **`GET /v3/find/{id}/params` ignora params do `script`** — só varre o campo `aggregate`.
|
|
311
|
+
- **`DELETE /v3/prepared/aggregate?q=` engole erros** e sempre retorna 200 (falha silenciosa).
|
|
312
|
+
- **Encadeamento `parent` > 1 nível**: ignorado (parent com parent não é processado).
|
|
313
|
+
- **Validação do `aggregate` no save**: inexistente.
|
|
314
|
+
- **Coleção `kpi` / `DashboardManager` / `dashboard.KPI` é LEGADO MORTO**: `calculateKpi`/`calculateSeries`/`addKPI` só são invocados por `DashboardTest.testKpi()`. Nenhuma rota REST os expõe. A coleção `kpi` é referenciada **apenas** em `DashboardManager.java`.
|
|
315
|
+
- **CRUD direto de `kpi` via `/v3/database/kpi` é parcialmente bloqueado**: o nome `kpi` tem 3 caracteres e `DatabaseRest` exige `collection.length() > 3` para `POST`, `PUT`, `GET /{id}` e bulk — logo só `GET` lista, `DELETE` por query, `aggregate`, `drop` e `count` funcionam para `kpi`.
|
|
316
|
+
- **Campo `cache_before_save`**: declarado e **comentado** em `PreparedAggregate.java:54` (script de cache simétrico ao `cache_before_find`, sem implementação ativa).
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 8. Segurança e Permissões
|
|
321
|
+
|
|
322
|
+
- **Autenticação**: Bearer token; a organização é resolvida via `authBean.getApiKey()`.
|
|
323
|
+
- **Isolamento por organização**: todas as operações usam a conexão Jongo da org; coleções de cache `find_cache_*__c` são por org.
|
|
324
|
+
- **Sandbox Groovy**: `SecureASTCustomizer` com whitelist de tokens (sem chamadas arbitrárias livres além do permitido), `TriggerExpressionChecker` customizado e timeout de 5s por `@TimedInterrupt` + executor. Mitiga (mas não elimina) risco de scripts maliciosos.
|
|
325
|
+
- **Superfícies de injeção** (documentadas sem omissão):
|
|
326
|
+
- `DELETE /v3/prepared/aggregate?q=` injeta `q` cru dentro de `{ ... }` enviado ao Mongo (`find("{" + q + "}")`). Um `q` malformado/abusivo é aceito; erros são silenciados.
|
|
327
|
+
- O campo `aggregate` é um pipeline cru fornecido pelo cliente, executado em qualquer `collection` informada — quem tem escopo de escrita pode ler qualquer coleção da org via `/v3/find`.
|
|
328
|
+
- **Comportamento inseguro a notar**: a falha silenciosa do delete em massa pode mascarar exclusões parciais ou nenhuma exclusão.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## 9. Observabilidade e Troubleshooting
|
|
333
|
+
|
|
334
|
+
Diagnóstico:
|
|
335
|
+
- **A fórmula existe?** `GET /v3/prepared/aggregate/{id}` (retorna `null` se não).
|
|
336
|
+
- **Quais params ela exige?** `GET /v3/find/{id}/params`.
|
|
337
|
+
- **A substituição está correta?** `POST /v3/prepared/aggregate/{id}/analyze` (veja `aggregate_step_3`) — lembrando que **não** roda a query.
|
|
338
|
+
- **Resultado real:** `POST /v3/find/{id}` com o corpo de params.
|
|
339
|
+
- **KPIs de ranking:** `GET /v3/challenge/prepared` lista as fórmulas; confira se o `aggregate` retorna `_id`, `total`, `max`.
|
|
340
|
+
|
|
341
|
+
Erros comuns e causas:
|
|
342
|
+
- `401 prepared aggregate dont exists` → id errado ou objeto em outra organização.
|
|
343
|
+
- Resultado vazio mas sem erro → pré-script falhou (veja `script_exceptions` no `analyze`) ou `$param:` não enviado.
|
|
344
|
+
- Dados "presos" / desatualizados → cache ativo; ajuste/remova `cache` ou aguarde o TTL. O cache vive em `find_cache_<id>__c` e o controle em `expire`.
|
|
345
|
+
- Ranking `TYPE_PREPARED_KPI` vazio → `operation.prepared` aponta para id inexistente em `challenge_rule_prepared`, ou o pipeline não emitiu `_id`/`total`/`max`, ou um placeholder `FUNIFIER_*` não foi substituído.
|
|
346
|
+
|
|
347
|
+
Queries úteis (via `/v3/database`):
|
|
348
|
+
```bash
|
|
349
|
+
GET /v3/database/prepared_aggregate
|
|
350
|
+
GET /v3/database/challenge_rule_prepared
|
|
351
|
+
GET /v3/database/expire?q=extra.item:'kpi_vendas'
|
|
352
|
+
GET /v3/database/find_cache_<expireId>__c
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## 10. Exemplos Práticos
|
|
358
|
+
|
|
359
|
+
### Exemplo mínimo — `prepared_aggregate` (total de logs de uma ação)
|
|
360
|
+
```json
|
|
361
|
+
POST /v3/prepared/aggregate
|
|
362
|
+
{
|
|
363
|
+
"_id": "total_checkins",
|
|
364
|
+
"title": "Total de check-ins",
|
|
365
|
+
"collection": "action_log",
|
|
366
|
+
"aggregate": "[{\"$match\":{\"actionId\":\"checkin\"}},{\"$count\":\"total\"}]"
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
Execução: `POST /v3/find/total_checkins` (corpo `{}`).
|
|
370
|
+
|
|
371
|
+
### Exemplo avançado — params + `$date` + cache
|
|
372
|
+
```json
|
|
373
|
+
POST /v3/prepared/aggregate
|
|
374
|
+
{
|
|
375
|
+
"_id": "vendas_jogador",
|
|
376
|
+
"title": "Vendas por jogador no período",
|
|
377
|
+
"collection": "action_log",
|
|
378
|
+
"cache": "30m",
|
|
379
|
+
"aggregate": "[{\"$match\":{\"actionId\":\"sell\",\"userId\":\"$param:player\",\"time\":{\"$gte\":{\"$date\":\"$param:start\"}}}},{\"$group\":{\"_id\":\"$userId\",\"total\":{\"$sum\":\"$attributes.price\"}}}]"
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
Execução:
|
|
383
|
+
```bash
|
|
384
|
+
POST /v3/find/vendas_jogador
|
|
385
|
+
Range: items=0-100
|
|
386
|
+
{ "player": "ana", "start": "-30d" }
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Exemplo — KPI de ranking (`challenge_rule_prepared`)
|
|
390
|
+
```json
|
|
391
|
+
POST /v3/database/challenge_rule_prepared
|
|
392
|
+
{
|
|
393
|
+
"_id": "kpi_volume_vendas",
|
|
394
|
+
"title": "Volume de vendas (ranking)",
|
|
395
|
+
"entity": "action_log",
|
|
396
|
+
"aggregate": "[{\"$match\":{\"actionId\":\"sell\",\"time\":{\"$gte\":{\"$date\":FUNIFIER_PERIOD_START_TIME},\"$lte\":{\"$date\":FUNIFIER_PERIOD_END_TIME}}}},{\"$group\":{\"_id\":\"$userId\",\"total\":{\"$sum\":\"$attributes.price\"},\"max\":{\"$max\":\"$attributes.price\"}}}]"
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
Depois referencie em um leaderboard com `operation.type = 10` e `operation.prepared = "kpi_volume_vendas"`.
|
|
400
|
+
|
|
401
|
+
### Anti-pattern (o que NÃO fazer)
|
|
402
|
+
- ❌ Usar `$param:player` dentro de um `challenge_rule_prepared` — ele só entende `FUNIFIER_*`; o `$param:` ficaria literal e quebraria a query.
|
|
403
|
+
- ❌ Confiar no `analyze` para validar dados — ele não roda a agregação. Use `/v3/find`.
|
|
404
|
+
- ❌ Criar a fórmula na coleção legada `kpi` esperando cálculo automático — `DashboardManager` é código morto e `/v3/database/kpi` rejeita `POST`/`PUT` (guard de 3 caracteres).
|
|
405
|
+
- ❌ Esquecer `max`/`total`/`_id` no pipeline de KPI de ranking — o ranking sai vazio sem erro explícito.
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Checklist de Configuração
|
|
21
410
|
|
|
22
|
-
- [ ]
|
|
23
|
-
- [ ]
|
|
411
|
+
- [ ] **Escolheu o subsistema certo**: `prepared_aggregate` (relatórios/campos calculados via `/v3/find`) **vs** `challenge_rule_prepared` (KPI de ranking).
|
|
412
|
+
- [ ] `collection` (em `prepared_aggregate`) ou `entity` (em `challenge_rule_prepared`) preenchido e existente.
|
|
413
|
+
- [ ] Sintaxe de parâmetro correta: `$param:`/`$date` para Prepared; `FUNIFIER_*` para KPI Rule.
|
|
414
|
+
- [ ] KPI de ranking: pipeline retorna `_id`, `total` e `max`.
|
|
415
|
+
- [ ] Se usar `cache`: TTL adequado (armadilha — resultado "preso" até expirar; polling de até ~30s na primeira materialização).
|
|
416
|
+
- [ ] Pré-script Groovy cabe no timeout (`timeout`, default 5s) e usa só tokens permitidos.
|
|
417
|
+
- [ ] `parent`, se usado, aponta para um `prepared_aggregate` **sem** parent (encadeamento de 1 nível).
|
|
418
|
+
- [ ] Não use a coleção legada `kpi` — sem efeito de cálculo e com CRUD bloqueado.
|