funifier-mcp 0.2.26 → 0.2.28

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.
Files changed (181) hide show
  1. package/.cursor/rules/funifier.mdc +38 -41
  2. package/.github/copilot-instructions.md +38 -41
  3. package/AGENTS.md +56 -49
  4. package/README.md +40 -22
  5. package/datasource-funifier-docs/.coverage.json +326 -0
  6. package/datasource-funifier-docs/.validation.json +593 -0
  7. package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
  8. package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
  9. package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
  10. package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
  11. package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
  12. package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
  13. package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
  14. package/datasource-funifier-docs/knowledge/index.md +4 -1
  15. package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
  16. package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
  17. package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
  18. package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
  19. package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
  20. package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
  21. package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
  22. package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
  23. package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
  24. package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
  25. package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
  26. package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
  27. package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
  28. package/datasource-funifier-docs/knowledge/modules/folder.md +935 -280
  29. package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
  30. package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
  31. package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
  32. package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
  33. package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
  34. package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
  35. package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
  36. package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
  37. package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
  38. package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
  39. package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
  40. package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
  41. package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
  42. package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
  43. package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
  44. package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
  45. package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
  46. package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
  47. package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
  48. package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
  49. package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
  50. package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
  51. package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
  52. package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
  53. package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
  54. package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
  55. package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
  56. package/dist/cli/init.d.ts.map +1 -1
  57. package/dist/cli/init.js +42 -1
  58. package/dist/cli/init.js.map +1 -1
  59. package/dist/cli/init.test.js +74 -3
  60. package/dist/cli/init.test.js.map +1 -1
  61. package/dist/cli/persona.d.ts +3 -0
  62. package/dist/cli/persona.d.ts.map +1 -0
  63. package/dist/cli/persona.js +25 -0
  64. package/dist/cli/persona.js.map +1 -0
  65. package/dist/mcp/bundle.js +119 -93
  66. package/dist/mcp/check-update.d.ts +5 -0
  67. package/dist/mcp/check-update.d.ts.map +1 -1
  68. package/dist/mcp/check-update.js +21 -10
  69. package/dist/mcp/check-update.js.map +1 -1
  70. package/dist/mcp/check-update.test.d.ts +2 -0
  71. package/dist/mcp/check-update.test.d.ts.map +1 -0
  72. package/dist/mcp/check-update.test.js +33 -0
  73. package/dist/mcp/check-update.test.js.map +1 -0
  74. package/dist/mcp/index.js +2 -2
  75. package/dist/mcp/index.js.map +1 -1
  76. package/dist/mcp/prompts/templates.d.ts.map +1 -1
  77. package/dist/mcp/prompts/templates.js +35 -0
  78. package/dist/mcp/prompts/templates.js.map +1 -1
  79. package/dist/mcp/resources/documentation.d.ts +1 -1
  80. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  81. package/dist/mcp/resources/documentation.js +39 -3
  82. package/dist/mcp/resources/documentation.js.map +1 -1
  83. package/dist/mcp/tools/connect.d.ts.map +1 -1
  84. package/dist/mcp/tools/connect.js +18 -8
  85. package/dist/mcp/tools/connect.js.map +1 -1
  86. package/dist/mcp/tools/database.d.ts.map +1 -1
  87. package/dist/mcp/tools/database.js +59 -47
  88. package/dist/mcp/tools/database.js.map +1 -1
  89. package/dist/mcp/tools/database.test.js +2 -2
  90. package/dist/mcp/tools/database.test.js.map +1 -1
  91. package/dist/mcp/tools/delete.d.ts.map +1 -1
  92. package/dist/mcp/tools/delete.js +13 -3
  93. package/dist/mcp/tools/delete.js.map +1 -1
  94. package/dist/mcp/tools/execute.d.ts.map +1 -1
  95. package/dist/mcp/tools/execute.js +20 -9
  96. package/dist/mcp/tools/execute.js.map +1 -1
  97. package/dist/mcp/tools/folder.d.ts.map +1 -1
  98. package/dist/mcp/tools/folder.js +22 -12
  99. package/dist/mcp/tools/folder.js.map +1 -1
  100. package/dist/mcp/tools/get.d.ts.map +1 -1
  101. package/dist/mcp/tools/get.js +16 -6
  102. package/dist/mcp/tools/get.js.map +1 -1
  103. package/dist/mcp/tools/index.d.ts +1 -1
  104. package/dist/mcp/tools/index.d.ts.map +1 -1
  105. package/dist/mcp/tools/index.js +28 -1
  106. package/dist/mcp/tools/index.js.map +1 -1
  107. package/dist/mcp/tools/list.d.ts.map +1 -1
  108. package/dist/mcp/tools/list.js +38 -14
  109. package/dist/mcp/tools/list.js.map +1 -1
  110. package/dist/mcp/tools/logs.d.ts.map +1 -1
  111. package/dist/mcp/tools/logs.js +15 -5
  112. package/dist/mcp/tools/logs.js.map +1 -1
  113. package/dist/mcp/tools/save.d.ts.map +1 -1
  114. package/dist/mcp/tools/save.js +14 -4
  115. package/dist/mcp/tools/save.js.map +1 -1
  116. package/dist/mcp/tools/save.test.js +3 -3
  117. package/dist/mcp/tools/save.test.js.map +1 -1
  118. package/dist/mcp/tools/search-docs.d.ts +3 -0
  119. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  120. package/dist/mcp/tools/search-docs.js +102 -0
  121. package/dist/mcp/tools/search-docs.js.map +1 -0
  122. package/package.json +6 -2
  123. package/skills/acquire-funifier-knowledge/SKILL.md +155 -0
  124. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  125. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  126. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  127. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  128. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  129. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  130. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +86 -0
  131. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  132. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  133. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  134. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  135. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  136. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  137. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  138. package/skills/funifier/SKILL.md +88 -0
  139. package/skills/funifier/references/configure-security.md +96 -0
  140. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  141. package/skills/funifier/references/create-aggregate.md +144 -0
  142. package/skills/funifier/references/create-challenge.md +116 -0
  143. package/skills/funifier/references/create-competition.md +98 -0
  144. package/skills/funifier/references/create-crossword.md +574 -0
  145. package/skills/funifier/references/create-custom-object.md +91 -0
  146. package/skills/funifier/references/create-custom-page.md +135 -0
  147. package/skills/funifier/references/create-folder.md +104 -0
  148. package/skills/funifier/references/create-lastmile.md +643 -0
  149. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  150. package/skills/funifier/references/create-level.md +94 -0
  151. package/skills/funifier/references/create-lottery.md +913 -0
  152. package/skills/funifier/references/create-mystery.md +769 -0
  153. package/skills/funifier/references/create-notification.md +75 -0
  154. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  155. package/skills/funifier/references/create-quiz.md +98 -0
  156. package/skills/funifier/references/create-scheduler.md +141 -0
  157. package/skills/funifier/references/create-story.md +636 -0
  158. package/skills/funifier/references/create-swap.md +95 -0
  159. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  160. package/skills/funifier/references/create-virtual-good.md +96 -0
  161. package/skills/funifier/references/create-webhook.md +72 -0
  162. package/skills/funifier/references/create-websocket.md +71 -0
  163. package/skills/funifier/references/create-widget.md +76 -0
  164. package/skills/funifier/references/debug.md +87 -0
  165. package/skills/funifier/references/help.md +81 -0
  166. package/skills/funifier/references/implement-frontend.md +106 -0
  167. package/skills/funifier/references/import-csv.md +75 -0
  168. package/skills/funifier/references/manage-player.md +82 -0
  169. package/skills/funifier/references/manage-team.md +76 -0
  170. package/skills/funifier/references/upload-file.md +91 -0
  171. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  172. package/skills/funifier-create-challenge/SKILL.md +0 -88
  173. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  174. package/skills/funifier-create-level/SKILL.md +0 -87
  175. package/skills/funifier-create-quiz/SKILL.md +0 -87
  176. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  177. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  178. package/skills/funifier-debug/SKILL.md +0 -92
  179. package/skills/funifier-help/SKILL.md +0 -86
  180. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  181. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,23 +1,418 @@
1
- # KPI Formulas (Fórmulas de KPI)
1
+ # `kpi-formulas`
2
2
 
3
- ## O que é
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
- Configuração de regras e cálculos avançados para avaliar ações dos jogadores. Permite criar fórmulas customizadas para avaliação de desempenho e resultados, indo além das regras padrão dos outros módulos.
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
- ## Quando usar
12
+ ---
8
13
 
9
- - Para cálculos avançados de desempenho que os módulos padrão não atendem
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
- ## Checklist de Configuração
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
- - [ ] Definir a fórmula de cálculo
17
- - [ ] Vincular aos desafios ou rankings relevantes
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
- ## Validações e Testes
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
- - [ ] Fórmula retorna resultados esperados
23
- - [ ] Cálculo funciona com diferentes combinações de dados
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.