funifier-mcp 0.2.25 → 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 +5 -2
- 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 +1011 -77
- 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/core/api-client.d.ts +21 -1
- package/dist/core/api-client.d.ts.map +1 -1
- package/dist/core/api-client.js +154 -1
- package/dist/core/api-client.js.map +1 -1
- package/dist/core/constants.d.ts +14 -0
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +14 -0
- package/dist/core/constants.js.map +1 -1
- package/dist/core/types/Folder.d.ts +16 -0
- package/dist/core/types/Folder.d.ts.map +1 -0
- package/dist/core/types/Folder.js +3 -0
- package/dist/core/types/Folder.js.map +1 -0
- package/dist/core/types/FolderContent.d.ts +10 -0
- package/dist/core/types/FolderContent.d.ts.map +1 -0
- package/dist/core/types/FolderContent.js +3 -0
- package/dist/core/types/FolderContent.js.map +1 -0
- package/dist/core/types/FolderContentType.d.ts +10 -0
- package/dist/core/types/FolderContentType.d.ts.map +1 -0
- package/dist/core/types/FolderContentType.js +3 -0
- package/dist/core/types/FolderContentType.js.map +1 -0
- package/dist/core/types/FolderLog.d.ts +11 -0
- package/dist/core/types/FolderLog.d.ts.map +1 -0
- package/dist/core/types/FolderLog.js +3 -0
- package/dist/core/types/FolderLog.js.map +1 -0
- package/dist/core/types/index.d.ts +4 -0
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/core/types/index.js +4 -0
- package/dist/core/types/index.js.map +1 -1
- package/dist/mcp/bundle.js +121 -87
- package/dist/mcp/check-update.d.ts +2 -0
- package/dist/mcp/check-update.d.ts.map +1 -0
- package/dist/mcp/check-update.js +44 -0
- package/dist/mcp/check-update.js.map +1 -0
- package/dist/mcp/index.js +5 -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/_char-guard.js +1 -1
- package/dist/mcp/tools/_char-guard.js.map +1 -1
- package/dist/mcp/tools/_fetch-current.d.ts +1 -1
- package/dist/mcp/tools/_fetch-current.d.ts.map +1 -1
- package/dist/mcp/tools/_fetch-current.js +12 -0
- package/dist/mcp/tools/_fetch-current.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 +33 -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 +4 -0
- package/dist/mcp/tools/folder.d.ts.map +1 -0
- package/dist/mcp/tools/folder.js +68 -0
- package/dist/mcp/tools/folder.js.map +1 -0
- 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 +5 -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 +26 -4
- package/dist/mcp/tools/save.js.map +1 -1
- package/dist/mcp/tools/save.test.js +192 -1
- 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/datasource-funifier-docs/.search-index.json +0 -17318
- package/datasource-funifier-docs/.skills-map.json +0 -73
- 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,136 +1,656 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `question`
|
|
2
2
|
|
|
3
3
|
**Acesso Studio:** `/studio/question`
|
|
4
4
|
**API Endpoint:** `/v3/question`
|
|
5
|
+
**Coleção MongoDB:** `question` (definições) e `question_log` (respostas dos jogadores)
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
> Documentação de engenharia reversa baseada **exclusivamente** no código-fonte do projeto `funifier-service` (commit `830037e`). Fonte primária:
|
|
8
|
+
> `QuestionRest.java`, `QuestionManager.java`, `Question.java`, `QuestionLog.java`, `Choice.java`, `Feedback.java`, `InteractionModel.java` (+ `MatchingModel`, `MatchingItem`, `MatchingPair`, `MissingWordsModel`, `Blank`, `PoolItem`, `DragDropTextModel`, `Target`) e `presentation/PresentationSettings.java`.
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
---
|
|
9
11
|
|
|
10
|
-
##
|
|
12
|
+
## 1. Visão Geral
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
- Para pesquisas de opinião
|
|
14
|
-
- Para treinamentos e capacitação
|
|
15
|
-
- Para mini games educativos
|
|
14
|
+
O módulo `question` implementa **perguntas avaliáveis e coletas de resposta** dentro da gamificação Funifier. Ele tem dois documentos distintos:
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
- **`question`** — a *definição* da pergunta (enunciado, opções, gabarito, tipo, peso, feedbacks, limites).
|
|
17
|
+
- **`question_log`** — o *registro de cada resposta* dada por um jogador, já com a nota calculada (`grade`) e o percentual de acerto (`percent`).
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
- [ ] Definir título e texto da pergunta
|
|
21
|
-
- [ ] Criar opções de resposta com grades (pontuação)
|
|
22
|
-
- [ ] Definir se permite uma ou múltiplas respostas (select)
|
|
23
|
-
- [ ] Configurar embaralhamento (shuffle)
|
|
24
|
-
- [ ] Definir peso/grade da pergunta
|
|
19
|
+
Papel arquitetural:
|
|
25
20
|
|
|
26
|
-
|
|
21
|
+
- É um módulo de **avaliação síncrona**: ao registrar um `question_log`, o `QuestionManager` correge a resposta no mesmo request (`insertLog`) e devolve `grade`, `percent`, `feedbacks` e eventuais `errors` na própria resposta HTTP.
|
|
22
|
+
- Funciona **standalone** ou como **peça interna de um `quiz`**. Quando o log carrega `quiz_log`, o módulo recalcula o progresso do quiz (`QuizManager.updateLog`).
|
|
23
|
+
- É uma **porta de entrada para o motor de gamificação**: se a pergunta tiver o campo `action`, responder gera um `ActionLog` que é processado por `ActionManager.trackWithRestrictions`, podendo creditar pontos, completar desafios e disparar achievements.
|
|
24
|
+
- Persiste tudo diretamente via `Jongo` (MongoDB). **Não há `Repository`/`Dao` dedicado** — a manipulação é feita dentro do próprio `QuestionManager`.
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
**Método:** GET
|
|
30
|
-
**Endpoint:** `/v3/question`
|
|
26
|
+
Problemas que resolve:
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
- Quizzes, avaliações, provas, pesquisas de opinião e mini-games (matching, completar lacunas, arrastar-e-soltar).
|
|
29
|
+
- Correção automática multi-formato (8 tipos de pergunta) com pesos configuráveis.
|
|
30
|
+
- Coleta auditável de respostas por jogador, com limites de tentativas e integração com triggers/actions.
|
|
31
|
+
|
|
32
|
+
Relação com outros módulos (confirmada no código):
|
|
33
|
+
|
|
34
|
+
| Módulo | Direção | Como |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `quiz` | `question` → `quiz` | `insertLog` chama `QuizManager.updateLog(quiz_log)` quando o log pertence a um quiz |
|
|
37
|
+
| `action` / `achievement` | `question` → `action` | `insertLog` cria um `ActionLog` e chama `ActionManager.trackWithRestrictions` quando `question.action` está setado |
|
|
38
|
+
| `trigger` | bidirecional | eventos `before_create`/`after_create`/`before_update`/`after_update` na coleção `question`; `before_create`/`after_create` na coleção `question_log` |
|
|
39
|
+
| `player` | leitura | `PlayerManager.findById` em `verifyLimit` e na montagem do contexto de feedback |
|
|
40
|
+
| `technique` (GameTechnique) | escrita pontual | `GameTechniqueManager.autoConfigureMissingTechniqueFields` atribui o código `GT05` a perguntas sem `techniques` |
|
|
41
|
+
|
|
42
|
+
> **Fora de escopo deste módulo** (não servidos por `/v3/question`): a coleção `partner_question` (mesma classe `Question`, mas outra coleção) e o pacote `com.funifier.engine.system.question` (perguntas de suporte do sistema: `accountId`, `topic`, `subject`, `description` — domínio totalmente distinto). Ver §7.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 2. Arquitetura e Fluxos
|
|
47
|
+
|
|
48
|
+
### 2.1 Classes envolvidas
|
|
49
|
+
|
|
50
|
+
| Classe | Papel |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `com.funifier.rest.v3.rest.QuestionRest` | Controller REST v3 (`/v3/question`) |
|
|
53
|
+
| `com.funifier.engine.question.QuestionManager` | Manager: CRUD, validação, correção por tipo, limites, integração quiz/action |
|
|
54
|
+
| `com.funifier.engine.question.Question` | Documento raiz da coleção `question` |
|
|
55
|
+
| `com.funifier.engine.question.QuestionLog` | Documento da coleção `question_log` (resposta do jogador) |
|
|
56
|
+
| `Choice` | Opção de resposta (formato legado) |
|
|
57
|
+
| `Feedback` | Mensagem de retorno por evento (`correct`/`partial_correct`/`wrong`) |
|
|
58
|
+
| `InteractionModel` + `MatchingModel`/`MissingWordsModel`/`DragDropTextModel` | Modelo "novo" para tipos especiais (substitui a avaliação legada por `choices`) |
|
|
59
|
+
| `MatchingItem`, `MatchingPair`, `Blank`, `PoolItem`, `Target` | Sub-elementos dos modelos novos |
|
|
60
|
+
| `presentation.PresentationSettings` | Configuração de UI (`FeedbackSettings`, `StuckSettings`) — apenas armazenada |
|
|
61
|
+
|
|
62
|
+
Não há Scheduler/Job/Runner que processe `question`/`question_log` (confirmado por busca no projeto).
|
|
63
|
+
|
|
64
|
+
### 2.2 Pipeline de criação/atualização — `insert(Question)`
|
|
65
|
+
|
|
66
|
+
`QuestionManager.insert` (linhas 69-104) é um **upsert idempotente por `_id`**:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
[1] validate(question) → coleta erros por tipo
|
|
70
|
+
se erros.size() > 0 → NÃO salva; retorna a lista de erros (NÃO lança exceção)
|
|
71
|
+
[2] gera _id (Guid.newShortGuid) se vazio
|
|
72
|
+
[3] busca documento atual por _id
|
|
73
|
+
se não existe → created = now; create = true
|
|
74
|
+
se existe → mantém created original; create = false
|
|
75
|
+
[4] updated = now (sempre)
|
|
76
|
+
[5] trigger before_create OU before_update (coleção "question")
|
|
77
|
+
[6] normalizeQuestionForPersistence(question) → normaliza aliases do dragDropText
|
|
78
|
+
[7] c.save(question) → upsert no Mongo
|
|
79
|
+
[8] trigger after_create OU after_update (coleção "question")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```mermaid
|
|
83
|
+
flowchart TD
|
|
84
|
+
A["POST /v3/question"] --> B["QuestionManager.insert"]
|
|
85
|
+
B --> C{"validate() vazio?"}
|
|
86
|
+
C -->|"não"| Z["retorna exceptions<br/>NÃO salva — HTTP 201"]
|
|
87
|
+
C -->|"sim"| D["gera _id se vazio"]
|
|
88
|
+
D --> E{"_id já existe?"}
|
|
89
|
+
E -->|"não"| F["created=now<br/>trigger before_create"]
|
|
90
|
+
E -->|"sim"| G["mantém created<br/>trigger before_update"]
|
|
91
|
+
F --> H["updated=now<br/>normalizeQuestionForPersistence"]
|
|
92
|
+
G --> H
|
|
93
|
+
H --> I["c.save(question)"]
|
|
94
|
+
I --> J["trigger after_create / after_update"]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> **Atenção (síncrono, sem transação):** não há transação MongoDB. Os triggers `before_*` rodam **antes** do `save`; se um trigger lançar, o comportamento depende do `TriggerManager` (fora do escopo deste módulo).
|
|
98
|
+
|
|
99
|
+
### 2.3 Pipeline de resposta — `insertLog(QuestionLog, async)`
|
|
100
|
+
|
|
101
|
+
`QuestionManager.insertLog` (linhas 425-583) corrige e persiste a resposta **de forma síncrona**:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
[1] valida obrigatórios: log != null, log.question, log.player
|
|
105
|
+
[2] carrega Question por _id (erro "question dont exist" se não achar)
|
|
106
|
+
[3] verifyLimit(question, log, errors) → checa limite de tentativas
|
|
107
|
+
se há erros → encerra sem salvar
|
|
108
|
+
[4] dispatch por question.type → answerXxx(...) → calcula log.percent e log.grade
|
|
109
|
+
[5] monta log._id:
|
|
110
|
+
se log.quiz_log != null → id = quiz_log + "_" + question (impede 2ª resposta no quiz)
|
|
111
|
+
senão → id = Guid.newShortGuid()
|
|
112
|
+
[6] log.time = now (se ausente)
|
|
113
|
+
[7] feedbacks = question.getFeedback(log.grade) ← chave é grade, NÃO percent (ver §5)
|
|
114
|
+
[8] monta ActionLog (al) com id=log.id, actionId=question.action, userId=log.player
|
|
115
|
+
[9] trigger before_create (coleção "question_log", com TriggerContext)
|
|
116
|
+
[10] save(question_log)
|
|
117
|
+
[11] trigger after_create
|
|
118
|
+
[12] meta.attemptsCount = count(question_log {player, question})
|
|
119
|
+
[13] se quiz_log != null → QuizManager.updateLog(quiz_log)
|
|
120
|
+
[14] resolve mensagens de feedback via Mustache (player, log, feedbacks, errors, question)
|
|
121
|
+
[15] se question.action != null:
|
|
122
|
+
valida action existe (erro se não) → ActionManager.trackWithRestrictions(al, async)
|
|
123
|
+
adiciona result["action"]
|
|
124
|
+
[16] result = { log, feedbacks?, errors?, meta? , action? }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```mermaid
|
|
128
|
+
sequenceDiagram
|
|
129
|
+
participant C as Client
|
|
130
|
+
participant QR as QuestionRest
|
|
131
|
+
participant QM as QuestionManager
|
|
132
|
+
participant TM as TriggerManager
|
|
133
|
+
participant QZ as QuizManager
|
|
134
|
+
participant AM as ActionManager
|
|
135
|
+
C->>QR: POST /v3/question/log
|
|
136
|
+
QR->>QM: insertLog(log, async)
|
|
137
|
+
QM->>QM: valida + carrega Question
|
|
138
|
+
QM->>QM: verifyLimit
|
|
139
|
+
QM->>QM: answerXxx → percent, grade
|
|
140
|
+
QM->>TM: execute(before_create, question_log)
|
|
141
|
+
QM->>QM: save(question_log)
|
|
142
|
+
QM->>TM: execute(after_create, question_log)
|
|
143
|
+
alt log.quiz_log != null
|
|
144
|
+
QM->>QZ: updateLog(quiz_log)
|
|
145
|
+
end
|
|
146
|
+
alt question.action != null
|
|
147
|
+
QM->>AM: trackWithRestrictions(actionLog, async)
|
|
148
|
+
AM-->>QM: action (points/achievements)
|
|
149
|
+
end
|
|
150
|
+
QM-->>QR: {log, grade, percent, feedbacks, action, errors}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 2.4 Avaliação por tipo (métodos `answerXxx`)
|
|
154
|
+
|
|
155
|
+
| Tipo | Método | Formato de `answer` | Cálculo de `percent` |
|
|
156
|
+
|---|---|---|---|
|
|
157
|
+
| `MULTIPLE_CHOICE` | `answerMultipleChoice` | `List<String>` (valores `choice.answer`) | **soma** de `choice.grade` das opções marcadas. **Sem limite superior** (pode passar de 1.0) |
|
|
158
|
+
| `TRUE_FALSE` | `answerTrueFalse` | `Boolean` | `1` se `answer == question.correctAnswer`, senão `0` |
|
|
159
|
+
| `MATCHING` | `answerMatching` | `Map<String,String>` (`leftId`→`rightId`) | modelo: `correct/totalPairs` (`per_pair`) ou tudo-ou-nada (`all_or_nothing`); legado: `1/nº choices` por par correto (`answer`+`match`) |
|
|
160
|
+
| `SHORT_ANSWER` | `answerShortAnswer` | `String` | **maior** `choice.grade` entre as opções que casam com o texto (case-sensitive opcional, default `false`) |
|
|
161
|
+
| `SELECT_MISSING_WORDS` | `answerSelectMissingWords` | `Map<String,String>` (`blankId`→`optionId`) | modelo: `correct/nº blanks` (`per_blank`) ou `all_or_nothing`; legado: `1/nº lacunas [[n]]` por acerto |
|
|
162
|
+
| `DRAG_AND_DROP_INTO_TEXT` | `answerDragAndDropIntoText` | `Map<String,String>` (`targetId`→`optionId`) | modelo: `correct/nº targets` (`per_target`) ou `all_or_nothing`; legado: `1/nº lacunas [[n]]` por acerto |
|
|
163
|
+
| `ESSAY` | `answerEssay` | `String` | **sempre `0`** (não há correção automática) |
|
|
164
|
+
| `CUSTOM` | `answerCustom` | qualquer | **sempre `0`** (sem avaliação) |
|
|
165
|
+
|
|
166
|
+
**Em todos os tipos**: `log.grade = question.grade * percent` e `log.percent = percent`. Ou seja, `grade` é o `percent` multiplicado pelo peso da pergunta.
|
|
167
|
+
|
|
168
|
+
Pseudocódigo do dispatcher (`insertLog`, linhas 450-473):
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
se type == MULTIPLE_CHOICE → answerMultipleChoice
|
|
172
|
+
senão se type == TRUE_FALSE → answerTrueFalse
|
|
173
|
+
senão se type == MATCHING → answerMatching
|
|
174
|
+
senão se type == SHORT_ANSWER → answerShortAnswer
|
|
175
|
+
senão se type == SELECT_MISSING_WORDS → answerSelectMissingWords
|
|
176
|
+
senão se type == DRAG_AND_DROP_INTO_TEXT → answerDragAndDropIntoText
|
|
177
|
+
senão se type == ESSAY → answerEssay
|
|
178
|
+
senão se type == CUSTOM → answerCustom
|
|
179
|
+
(nenhum else: tipo desconhecido NÃO calcula grade/percent → ficam null)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
> **Modelo novo vs legado:** para `MATCHING`, `SELECT_MISSING_WORDS` e `DRAG_AND_DROP_INTO_TEXT`, a correção usa `question.model` **se** ele estiver presente e `model.interactionType` for `null` ou igual ao `type`. Caso contrário cai no caminho **legado** baseado em `choices` e marcadores `[[n]]` no campo `question`.
|
|
183
|
+
|
|
184
|
+
### 2.5 Cálculo de "já respondida" — campo `answered` (runtime, não persistido)
|
|
185
|
+
|
|
186
|
+
`findAllPaginated` (linhas 121-186) injeta o booleano `answered` **apenas quando `player` é informado**, via pipeline `$facet`:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
{ $match: { _id: { $exists: true } , <id?> , <q?> } }
|
|
190
|
+
{ $facet: {
|
|
191
|
+
p1: [ { $match: { _id: { $in: <ids_respondidos> } } }, { $addFields: { answered: true } } ],
|
|
192
|
+
p2: [ { $match: { _id: { $nin: <ids_respondidos> } } }, { $addFields: { answered: false } } ]
|
|
193
|
+
} }
|
|
194
|
+
{ $project: { all: { $concatArrays: [ "$p1", "$p2" ] } } }
|
|
195
|
+
{ $unwind: "$all" }
|
|
196
|
+
{ $replaceRoot: { newRoot: "$all" } }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`<ids_respondidos>` vem de `getAnsweredQuestionIds`: `db.question_log.distinct("question", { player: #, time: {$gte/$lte} })`. Os filtros `answered_min`/`answered_max` aceitam timestamp RFC 3339 **ou** keywords relativas (`-1d`, `-30m`, etc.) via `DateUtil.fromKeyword`.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 3. Estrutura dos Objetos
|
|
204
|
+
|
|
205
|
+
> Todas as classes do módulo usam `@JsonIgnoreProperties(ignoreUnknown=true)` → **qualquer campo desconhecido enviado é silenciosamente descartado** (sem erro, sem log).
|
|
206
|
+
|
|
207
|
+
### 3.1 `Question` — documento da coleção `question`
|
|
208
|
+
|
|
209
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
210
|
+
|---|---|---|---|---|
|
|
211
|
+
| `_id` | String | GUID curto se vazio | — | Identificador. Definido pelo cliente ou gerado em `insert` |
|
|
212
|
+
| `quiz` | String | `null` | não | ID do quiz pai. **Apenas referência informativa** — o vínculo de resposta é feito via `quiz_log` no log |
|
|
213
|
+
| `type` | String | `null` | **sim** | Tipo da pergunta (enum, §3.5) |
|
|
214
|
+
| `title` | String | `null` | **sim** (exceto `CUSTOM`) | Título usado no admin. Exigido por `validateBasic` |
|
|
215
|
+
| `question` | String | `null` | depende | Enunciado. Obrigatório (com `[[1]]`) nos caminhos **legados** de `DRAG_AND_DROP_INTO_TEXT` e `SELECT_MISSING_WORDS` |
|
|
216
|
+
| `grade` | double | `1` | não | **Peso** da pergunta. `log.grade = grade * percent` |
|
|
217
|
+
| `choices` | `List<Choice>` | `[]` | depende | Opções (obrigatório em `MULTIPLE_CHOICE`; usado nos caminhos legados) |
|
|
218
|
+
| `i18n` | `Map<String,Map<String,String>>` | `{}` | não | Traduções. **Apenas armazenado** — nenhuma lógica de servidor o lê |
|
|
219
|
+
| `techniques` | `List<String>` | `null` | não | Códigos de técnica de jogo (GT). Default `GT05` via job de técnicas |
|
|
220
|
+
| `select` | String | `null` | **sim** p/ `MULTIPLE_CHOICE` | `one_answer` / `multiple_answers`. **Dica de UI** — a correção NÃO força escolha única |
|
|
221
|
+
| `answerNumbering` | String | `null` | não | `arabic_numerals` / `roman_numerals` / `uppercase_letters` (UI) |
|
|
222
|
+
| `correctAnswer` | Boolean | `null` | **sim** p/ `TRUE_FALSE` | Resposta correta do verdadeiro/falso |
|
|
223
|
+
| `caseSensitive` | Boolean | `null` | **sim** p/ `SHORT_ANSWER` | Na avaliação assume `false` se `null` |
|
|
224
|
+
| `totalLines` | Integer | `null` | não | Linhas sugeridas para `ESSAY` (UI) |
|
|
225
|
+
| `shuffle` | boolean | `false` | não | Embaralhar opções (UI) |
|
|
226
|
+
| `feedbacks` | `List<Feedback>` | `[]` | não | Mensagens por evento (§3.4) |
|
|
227
|
+
| `presentation` | `PresentationSettings` | `null` | não | Settings de UI (feedback/stuck) — apenas armazenado |
|
|
228
|
+
| `model` | `InteractionModel` | `null` | não | Modelo novo para `MATCHING`/`SELECT_MISSING_WORDS`/`DRAG_AND_DROP_INTO_TEXT` |
|
|
229
|
+
| `extra` | `Map<String,Object>` | `{}` | não | Campos livres (imagens, vídeo, metadados) |
|
|
230
|
+
| `created` | Date | auto | — | Definido no primeiro `insert`; preservado em updates |
|
|
231
|
+
| `updated` | Date | auto | — | Atualizado a cada `insert` |
|
|
232
|
+
| `limit` | `Limit` | `null` | não | Limite de respostas (§3.6) |
|
|
233
|
+
| `requires` | `List<Requirement>` | `[]` | não | **Aceito e persistido, mas NUNCA lido** pelo `QuestionManager` (§7) |
|
|
234
|
+
| `action` | String | `null` | não | ID/nome da action disparada ao responder |
|
|
235
|
+
|
|
236
|
+
**Campos computados (não persistem na coleção `question`):**
|
|
237
|
+
- `answered` (boolean) — injetado em runtime no `GET /v3/question` quando `player` é informado (§2.5).
|
|
238
|
+
|
|
239
|
+
**Campos aceitos e silenciosamente ignorados:**
|
|
240
|
+
- Qualquer atributo não declarado nas classes (`@JsonIgnoreProperties(ignoreUnknown=true)`). Exemplos comuns que **não existem** no modelo de `Choice` e seriam descartados: `gradePercent`, `gradeCheck`.
|
|
241
|
+
|
|
242
|
+
### 3.2 `QuestionLog` — documento da coleção `question_log`
|
|
243
|
+
|
|
244
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
245
|
+
|---|---|---|---|---|
|
|
246
|
+
| `_id` | String | GUID, ou `quiz_log + "_" + question` | — | Em quiz, o id composto impede responder a mesma pergunta 2x |
|
|
247
|
+
| `player` | String | — | **sim** | Jogador que respondeu. `"me"` resolve para o player do token |
|
|
248
|
+
| `question` | String | — | **sim** | ID da pergunta respondida |
|
|
249
|
+
| `answer` | Object | — | **sim** | Resposta crua; formato varia por tipo (§2.4) |
|
|
250
|
+
| `percent` | Double | calculado | — | Percentual de acerto (saída da correção) |
|
|
251
|
+
| `grade` | Double | calculado | — | `question.grade * percent` (saída da correção) |
|
|
252
|
+
| `time` | Date | now | — | Momento da resposta |
|
|
253
|
+
| `mentor` | String | `null` | não | Instrutor avaliador. **Aceito/persistido, mas sem workflow** que o utilize (§7) |
|
|
254
|
+
| `extra` | `Map<String,Object>` | `null` | não | Campos adicionais |
|
|
255
|
+
| `quiz` | String | `null` | não | Referência ao quiz |
|
|
256
|
+
| `quiz_log` | String | `null` | não | Vincula a resposta ao log do quiz (dispara `QuizManager.updateLog`) |
|
|
257
|
+
|
|
258
|
+
### 3.3 `Choice` — opção de resposta (formato legado)
|
|
259
|
+
|
|
260
|
+
| Campo | Tipo | Descrição |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| `answer` | String | Identificador/valor da opção (referenciado pela resposta do jogador) |
|
|
263
|
+
| `label` | String | Texto amigável exibido |
|
|
264
|
+
| `grade` | Double | Percentual desta opção (0..1). Em `MULTIPLE_CHOICE` soma; em `SHORT_ANSWER` usa-se o máximo |
|
|
265
|
+
| `match` | String | Sub-pergunta do `MATCHING` legado |
|
|
266
|
+
| `extra` | `Map<String,Object>` | Metadados |
|
|
267
|
+
|
|
268
|
+
### 3.4 `Feedback`
|
|
269
|
+
|
|
270
|
+
| Campo | Tipo | Descrição |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| `event` | String | `correct` / `partial_correct` / `wrong` |
|
|
273
|
+
| `message` | String | Texto (processado por Mustache com `{player, log, feedbacks, errors, question}`) |
|
|
274
|
+
|
|
275
|
+
### 3.5 Enum de tipos (`Question.TYPE_*`)
|
|
276
|
+
|
|
277
|
+
| Valor | Significado operacional |
|
|
278
|
+
|---|---|
|
|
279
|
+
| `MULTIPLE_CHOICE` | Múltipla escolha; nota = soma dos `grade` das opções marcadas |
|
|
280
|
+
| `TRUE_FALSE` | Verdadeiro/Falso; nota cheia ou zero |
|
|
281
|
+
| `MATCHING` | Relacionar colunas (left↔right); pontuação por par ou tudo-ou-nada |
|
|
282
|
+
| `SHORT_ANSWER` | Resposta curta em texto; nota = maior `grade` entre as opções que casam |
|
|
283
|
+
| `SELECT_MISSING_WORDS` | Selecionar palavras em lacunas (dropdown) |
|
|
284
|
+
| `DRAG_AND_DROP_INTO_TEXT` | Arrastar opções para lacunas do texto |
|
|
285
|
+
| `ESSAY` | Dissertativa; **nota sempre 0** (correção manual não implementada) |
|
|
286
|
+
| `CUSTOM` | Customizada; **sem validação e sem avaliação** (nota 0) |
|
|
287
|
+
|
|
288
|
+
Outros enums:
|
|
289
|
+
- `select`: `one_answer`, `multiple_answers`.
|
|
290
|
+
- `answerNumbering`: `arabic_numerals`, `roman_numerals`, `uppercase_letters`.
|
|
291
|
+
- `scoring` (modelos novos): `per_pair`/`per_blank`/`per_target` (default) ou `all_or_nothing`.
|
|
292
|
+
|
|
293
|
+
### 3.6 `Limit` — limite de respostas
|
|
294
|
+
|
|
295
|
+
| Campo | Tipo | Descrição |
|
|
296
|
+
|---|---|---|
|
|
297
|
+
| `total` | Object | Número **ou** expressão Mustache+exp4j (`{{player.extra.max}}`) avaliada em runtime |
|
|
298
|
+
| `per` | String | `player` ou `gamification`. **`team` é definido mas NÃO tratado** em `verifyLimit` (§5/§7) |
|
|
299
|
+
| `every` | String | Janela (ex.: `1d`, `5h`). Sem `every` → janela de `-10y` |
|
|
300
|
+
| `query` | String | Filtro Mongo adicional (Mustache, ex.: `attributes.city:'{{player.extra.city}}'`) |
|
|
301
|
+
|
|
302
|
+
### 3.7 `InteractionModel` e submodelos (modelo novo)
|
|
303
|
+
|
|
304
|
+
`InteractionModel`: `interactionType` (`MATCHING`/`SELECT_MISSING_WORDS`/`DRAG_AND_DROP_INTO_TEXT`), `questionHtml`, `shufflePool`, mais um dos modelos:
|
|
305
|
+
|
|
306
|
+
- **`MatchingModel`**: `left: List<MatchingItem{id,text}>`, `right: List<MatchingItem>`, `solution: List<MatchingPair{leftId,rightId}>`, `solutions: Map<String,String>` (forma alternativa), `scoring`.
|
|
307
|
+
- **`MissingWordsModel`**: `html` (com `[[1]]`...), `blanks: List<Blank>`, `scoring`. `Blank`: `id`, `options: List<PoolItem{id,text}>`, `correctId` (JSON: `correctOptionId`, alias `correctId`).
|
|
308
|
+
- **`DragDropTextModel`**: `html` (JSON `text`, alias `html`), `pool` (JSON `optionsPool`, alias `pool`), `targets: List<Target>`, `scoring`. `Target`: `id`, `accepts` (JSON `acceptedOptionIds`, alias `accepts`), `correctId` (JSON `correctOptionId`, alias `correctId`).
|
|
309
|
+
|
|
310
|
+
> **Normalização assimétrica (`normalizeQuestionForPersistence`, linhas 42-65):** apenas o `dragDropText` é "endurecido" — o manager copia manualmente `text→html`, `optionsPool→pool`, `target.acceptedOptionIds→accepts`, `target.correctOptionId→correctId` antes de salvar. `MatchingModel` e `MissingWordsModel` dependem **somente** dos `@JsonAlias` do Jackson. Ver §7.
|
|
311
|
+
|
|
312
|
+
### 3.8 Técnicas de jogo (`techniques`)
|
|
313
|
+
|
|
314
|
+
| Código | Origem no código | Significado |
|
|
315
|
+
|---|---|---|
|
|
316
|
+
| `GT05` | `GameTechniqueManager` (`BLANKFILLS_CODE`) | Atribuído automaticamente a perguntas **sem** `techniques` durante `autoConfigureMissingTechniqueFields` |
|
|
317
|
+
|
|
318
|
+
Outros códigos GT podem ser adicionados manualmente ao array `techniques`, mas o único default automático para `question` é `GT05`.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## 4. Endpoints
|
|
323
|
+
|
|
324
|
+
Base: `/v3/question`. Autenticação: **Bearer token** (`AuthBean`) em todos. Multi-tenant via `apiKey` (`FrontController.getInstance(apiKey)`).
|
|
325
|
+
|
|
326
|
+
### `POST /v3/question`
|
|
327
|
+
|
|
328
|
+
| Aspecto | Detalhe |
|
|
329
|
+
|---|---|
|
|
330
|
+
| Finalidade | Criar **ou** atualizar (upsert por `_id`) uma pergunta |
|
|
331
|
+
| Full replace ou patch | **Full replace** — o `save` grava o documento inteiro recebido |
|
|
332
|
+
| Status | Sempre `201 CREATED`, **mesmo com erros de validação** |
|
|
333
|
+
|
|
334
|
+
**Comportamento real:** a validação **não lança** — erros voltam no array `exceptions` do corpo e o documento **não é salvo**. O cliente **deve** inspecionar `exceptions`.
|
|
35
335
|
|
|
36
|
-
**Exemplo de Body:**
|
|
37
336
|
```json
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"type": "MULTIPLE_CHOICE",
|
|
41
|
-
"title": "Visual Components",
|
|
42
|
-
"question": "In Funifier, what is the small visual components that can be added to a web page to display feedbacks?",
|
|
43
|
-
"grade": 1,
|
|
44
|
-
"choices": [
|
|
45
|
-
{ "answer": "1", "label": "Points", "grade": 0, "extra": {} },
|
|
46
|
-
{ "answer": "2", "label": "Badges", "grade": 0, "extra": {} },
|
|
47
|
-
{ "answer": "3", "label": "Challenges", "grade": 0, "extra": {} },
|
|
48
|
-
{ "answer": "4", "label": "Leaderboards", "grade": 0, "extra": {} },
|
|
49
|
-
{ "answer": "5", "label": "Widgets", "grade": 1, "extra": {} }
|
|
50
|
-
],
|
|
51
|
-
"techniques": ["GT05"],
|
|
52
|
-
"select": "one_answer",
|
|
53
|
-
"answerNumbering": "uppercase_letters",
|
|
54
|
-
"shuffle": false,
|
|
55
|
-
"feedbacks": [],
|
|
56
|
-
"extra": {}
|
|
57
|
-
}
|
|
337
|
+
// resposta
|
|
338
|
+
{ "question": { "_id": "...", "type": "MULTIPLE_CHOICE", ... }, "exceptions": [] }
|
|
58
339
|
```
|
|
59
340
|
|
|
60
|
-
###
|
|
61
|
-
**Método:** DELETE
|
|
62
|
-
**Endpoint:** `/v3/question/:id`
|
|
341
|
+
### `POST /v3/question/bulk`
|
|
63
342
|
|
|
64
|
-
|
|
65
|
-
**Método:** GET
|
|
66
|
-
**Endpoint:** `/v3/question/log?question=:id`
|
|
343
|
+
Recebe `List<Question>`; chama `insert` para cada. Resposta separa `registered`/`total_registered` de `ignored`/`total_ignored` (itens com `exceptions`). Status `201`.
|
|
67
344
|
|
|
68
|
-
###
|
|
69
|
-
|
|
70
|
-
|
|
345
|
+
### `GET /v3/question/{id}`
|
|
346
|
+
|
|
347
|
+
| Param | Tipo | Descrição |
|
|
348
|
+
|---|---|---|
|
|
349
|
+
| `id` (path) | String | ID da pergunta, ou `FUNIFIER_RANDOM_QUESTION` |
|
|
350
|
+
| `q` | String | Critério Mongo (usado no random) |
|
|
351
|
+
| `player` | String | `"me"` resolve para o player do token |
|
|
352
|
+
| `answered_min` / `answered_max` | String | Janela RFC 3339 ou keyword |
|
|
353
|
+
|
|
354
|
+
**Comportamento real:** se `id == FUNIFIER_RANDOM_QUESTION` **e** `player` informado → `findRandomNotAnsweredForPlayer` (sorteia uma pergunta ainda não respondida pelo player). Senão → `find(id)` simples.
|
|
355
|
+
|
|
356
|
+
### `GET /v3/question`
|
|
357
|
+
|
|
358
|
+
| Param | Tipo | Descrição |
|
|
359
|
+
|---|---|---|
|
|
360
|
+
| `id` | String | Filtra por id; aceita `FUNIFIER_RANDOM_QUESTION` |
|
|
361
|
+
| `player` | String | Se informado, injeta `answered:true/false` por pergunta (§2.5). `"me"` suportado |
|
|
362
|
+
| `answered_min`/`answered_max` | String | Janela para considerar "respondida" |
|
|
363
|
+
| `q` | String | Critério Mongo cru concatenado no `$match` |
|
|
364
|
+
| `fields` | String | Projeção (`campo1,campo2`) |
|
|
365
|
+
| `orderby` | String | Campo de ordenação |
|
|
366
|
+
| `reverse` | boolean | Ordem descendente |
|
|
367
|
+
| `Range` (header) | String | Paginação `items=0-100` (default/máx 100) |
|
|
368
|
+
|
|
369
|
+
### `DELETE /v3/question/{id}`
|
|
370
|
+
|
|
371
|
+
| Aspecto | Detalhe |
|
|
372
|
+
|---|---|
|
|
373
|
+
| Finalidade | Remover a pergunta |
|
|
374
|
+
| Status | `204 NO_CONTENT` |
|
|
375
|
+
|
|
376
|
+
**Comportamento real (cascata, não-REST padrão):** além de remover o documento `question`, executa `db.question_log.remove({ question: id })` — **apaga todos os logs de resposta daquela pergunta** (`delete`, linhas 111-115). Operação irreversível.
|
|
377
|
+
|
|
378
|
+
### `GET /v3/question/log`
|
|
379
|
+
|
|
380
|
+
Lista respostas. Params: `player`, `question`, `q`, `published_min`, `published_max`, `orderby`, `reverse`, `max_results` (default 100). **Não paginado por header** (retorna lista direta). Não existe `GET /v3/question/log/{id}` (busca de log único é só por filtro).
|
|
381
|
+
|
|
382
|
+
### `POST /v3/question/log`
|
|
383
|
+
|
|
384
|
+
| Aspecto | Detalhe |
|
|
385
|
+
|---|---|
|
|
386
|
+
| Finalidade | Registrar e **corrigir** a resposta de um jogador |
|
|
387
|
+
| Query `async` | Repassado para `ActionManager.trackWithRestrictions` |
|
|
388
|
+
| Status | `201 CREATED` |
|
|
389
|
+
|
|
390
|
+
**Comportamento real:** corrige a resposta no mesmo request; devolve `grade`, `percent`, `feedbacks`, `meta.attemptsCount` e, se houver `action`, o resultado do tracking. Erros de formato/limite voltam em `errors`. `player == "me"` (ou ausente) resolve para o token.
|
|
71
391
|
|
|
72
|
-
**Exemplo de Body:**
|
|
73
392
|
```json
|
|
393
|
+
// request
|
|
394
|
+
{ "player": "ricardo@x.com", "question": "q1", "answer": ["Pedro Alvares Cabral"] }
|
|
395
|
+
// response (exemplo)
|
|
74
396
|
{
|
|
75
|
-
"question": "
|
|
76
|
-
"
|
|
77
|
-
"
|
|
397
|
+
"log": { "_id":"...", "player":"ricardo@x.com", "question":"q1", "answer":["Pedro Alvares Cabral"], "percent":1.0, "grade":1.0, "time":"..." },
|
|
398
|
+
"feedbacks": [ { "event":"correct", "message":"Parabéns você acertou" } ],
|
|
399
|
+
"meta": { "attemptsCount": 1 }
|
|
78
400
|
}
|
|
79
401
|
```
|
|
80
402
|
|
|
81
|
-
###
|
|
82
|
-
**Método:** DELETE
|
|
83
|
-
**Endpoint:** `/v3/question/log/:id`
|
|
403
|
+
### `POST /v3/question/log/bulk`
|
|
84
404
|
|
|
85
|
-
|
|
405
|
+
Recebe `List<QuestionLog>`; chama `insertLog` para cada (capturando exceções por item). Resposta agrega `registered`/`total_registered`.
|
|
86
406
|
|
|
87
|
-
|
|
88
|
-
|------|-----------|
|
|
89
|
-
| `MULTIPLE_CHOICE` | Múltipla escolha (uma ou várias respostas) |
|
|
90
|
-
| `TRUE_FALSE` | Verdadeiro ou Falso |
|
|
91
|
-
| `ESSAY` | Dissertativa (resposta livre) |
|
|
92
|
-
| `CROSSWORD` | Caça-palavras (mini game) |
|
|
93
|
-
| `WORD_SEARCH` | Cruzadinha (mini game) |
|
|
407
|
+
### `POST /v3/question/log/aggregate`
|
|
94
408
|
|
|
95
|
-
|
|
409
|
+
Roda aggregation customizada sobre `question_log`. Body = `List<String>` de estágios Mongo. Filtros `player`/`question`/`q`/`published_min`/`published_max` montam o `$match` inicial; os estágios do body são anexados. Retorna via `Content-Range`.
|
|
96
410
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## 5. Regras de Negócio
|
|
414
|
+
|
|
415
|
+
Regras presentes no código mas não óbvias no schema:
|
|
416
|
+
|
|
417
|
+
1. **Validação por tipo (`validate`, linhas 673-709):**
|
|
418
|
+
- Todos (exceto `CUSTOM`): exigem `type` e `title` (`validateBasic`).
|
|
419
|
+
- `MULTIPLE_CHOICE`: exige `choices` e `select`.
|
|
420
|
+
- `TRUE_FALSE`: exige `correctAnswer`.
|
|
421
|
+
- `SHORT_ANSWER`: exige `caseSensitive`.
|
|
422
|
+
- `MATCHING`: modelo → `left`, `right` e (`solution` **ou** `solutions`); legado → `choices`.
|
|
423
|
+
- `SELECT_MISSING_WORDS`: modelo → `blanks` com `options`; legado → `choices` + `[[1]]` em `question`.
|
|
424
|
+
- `DRAG_AND_DROP_INTO_TEXT`: modelo → `targets` + `[[1]]` em `html`/`question`; legado → `choices` + `[[1]]` em `question`.
|
|
425
|
+
- `ESSAY`: apenas básico. `CUSTOM`: **nenhuma validação**.
|
|
426
|
+
|
|
427
|
+
2. **Peso vs feedback — pegadinha do `grade`:** `getFeedback` (linhas 129-145) seleciona feedback pela **nota final** `log.grade` (= `question.grade * percent`), **não** pelo `percent`. Limiares (inclusivos):
|
|
428
|
+
- `grade >= 1` → `correct`
|
|
429
|
+
- `grade <= 0` → `wrong`
|
|
430
|
+
- `0 < grade < 1` → `partial_correct`
|
|
431
|
+
|
|
432
|
+
Consequência: se `question.grade != 1`, os limiares deslocam. Ex.: `grade=2, percent=0.5 → log.grade=1 → "correct"`; `grade=0.5, percent=1.0 → log.grade=0.5 → "partial_correct"` mesmo com 100% de acerto.
|
|
433
|
+
|
|
434
|
+
3. **`MULTIPLE_CHOICE` é ilimitado e não força `select`:** o `percent` é a **soma** dos `choice.grade` das opções marcadas, sem teto. Marcar várias opções "certas" acumula acima de `1.0`. O campo `select: one_answer` é só dica de UI — o backend não impede múltiplas marcações.
|
|
435
|
+
|
|
436
|
+
4. **Limite de tentativas (`verifyLimit`, linhas 374-421):** se `limit.total` (número ou expressão) for atingido na janela `limit.every`, o log **não é registrado** e um erro é retornado. Suporta `per = player` e `per = gamification`; **`per = team` não tem branch** (é ignorado silenciosamente).
|
|
437
|
+
|
|
438
|
+
5. **Unicidade dentro de quiz:** quando `quiz_log` está presente, o id do log vira `quiz_log + "_" + question`, garantindo **uma resposta por pergunta por execução de quiz** (re-POST sobrescreve).
|
|
439
|
+
|
|
440
|
+
6. **Propagação para gamificação:** com `question.action` setado, responder gera um `ActionLog` (atributos: `question`, `percent`, `answer`, `grade`, `type`) processado por `trackWithRestrictions`. Se a action não existir → erro `question.action ... does not exist`.
|
|
441
|
+
|
|
442
|
+
7. **Multi-tenant:** todo acesso é isolado pelo `apiKey` do token; não há ACL por pergunta. O campo `requires` (que poderia gatear elegibilidade) **não é avaliado**.
|
|
443
|
+
|
|
444
|
+
8. **Consistência eventual:** sem transação. O `save` do log, o `updateLog` do quiz e o `trackWithRestrictions` da action são passos sequenciais independentes; falha em um passo posterior não desfaz os anteriores.
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## 6. Comportamentos Automáticos
|
|
449
|
+
|
|
450
|
+
| Comportamento | Trigger | Impacto | Persistência |
|
|
451
|
+
|---|---|---|---|
|
|
452
|
+
| Geração de `_id` | `insert`/`insertLog` sem id | Cria GUID curto (ou id composto em quiz) | Sim |
|
|
453
|
+
| `created`/`updated` | `insert` | `created` no 1º save, `updated` sempre | Sim |
|
|
454
|
+
| Normalização de aliases | `insert` (só `dragDropText`) | `text→html`, `optionsPool→pool`, `acceptedOptionIds→accepts`, `correctOptionId→correctId` | Sim |
|
|
455
|
+
| Triggers de ciclo de vida | `insert`/`insertLog` | `before_*`/`after_*` em `question` e `question_log` | Depende do trigger |
|
|
456
|
+
| Correção automática | `insertLog` | Calcula `percent` e `grade` | Sim (no log) |
|
|
457
|
+
| Atualização do quiz | `insertLog` com `quiz_log` | `QuizManager.updateLog` recalcula percent/grade/questions/answers | Sim (em `quiz_log`) |
|
|
458
|
+
| Tracking de action | `insertLog` com `question.action` | `ActionLog` → pontos/desafios/achievements | Sim (via engine) |
|
|
459
|
+
| Cascata de exclusão | `delete` | Remove todos os `question_log` da pergunta | Sim |
|
|
460
|
+
| `meta.attemptsCount` | `insertLog` | Conta tentativas player×question | Não (só na resposta) |
|
|
461
|
+
| Default de técnica | `autoConfigureMissingTechniqueFields` | Adiciona `GT05` a perguntas sem `techniques` | Sim |
|
|
462
|
+
|
|
463
|
+
```mermaid
|
|
464
|
+
flowchart LR
|
|
465
|
+
A["question_log salvo"] --> B{"quiz_log definido?"}
|
|
466
|
+
B -->|"sim"| C["QuizManager.updateLog<br/>recalcula progresso do quiz"]
|
|
467
|
+
A --> D{"question.action definido?"}
|
|
468
|
+
D -->|"sim"| E["cria ActionLog<br/>(question, percent, answer, grade, type)"]
|
|
469
|
+
E --> F["ActionManager.trackWithRestrictions"]
|
|
470
|
+
F --> G["Engine: pontos, desafios, achievements"]
|
|
471
|
+
D -->|"não"| H["fim"]
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## 7. Suportado vs NÃO Suportado
|
|
477
|
+
|
|
478
|
+
### ✅ Suportado
|
|
479
|
+
|
|
480
|
+
- CRUD de perguntas (`POST`, `GET`, `DELETE`) + `bulk`.
|
|
481
|
+
- 8 tipos de pergunta com correção automática (exceto `ESSAY`/`CUSTOM`).
|
|
482
|
+
- Modelo novo (`model`) para `MATCHING`/`SELECT_MISSING_WORDS`/`DRAG_AND_DROP_INTO_TEXT`, com `scoring` `per_*` ou `all_or_nothing`.
|
|
483
|
+
- Registro de respostas (`question_log`) com nota, percentual e feedbacks.
|
|
484
|
+
- Limite de tentativas por `player`/`gamification`, com `total` literal ou via expressão.
|
|
485
|
+
- Pergunta aleatória não respondida (`FUNIFIER_RANDOM_QUESTION`).
|
|
486
|
+
- Campo `answered` por jogador no `GET`.
|
|
487
|
+
- Integração com `quiz` (composição) e `action`/`achievement` (gamificação).
|
|
488
|
+
- Triggers de ciclo de vida em `question` e `question_log`.
|
|
489
|
+
- Aggregation customizada de logs (`/log/aggregate`).
|
|
490
|
+
- i18n armazenado para uso de front-end.
|
|
491
|
+
|
|
492
|
+
### ❌ NÃO Suportado
|
|
493
|
+
|
|
494
|
+
- **Correção/nota de `ESSAY`**: sempre `0`. Não há endpoint para o `mentor` lançar nota — `answerEssay` sobrescreve `grade=0` a cada save.
|
|
495
|
+
- **Campo `mentor`**: aceito e persistido, mas **nenhum fluxo o utiliza**.
|
|
496
|
+
- **Campo `requires` (`List<Requirement>`)**: presente na entidade, **nunca lido** pelo `QuestionManager` — não gateia elegibilidade.
|
|
497
|
+
- **`limit.per = team`**: definido em `Limit`, mas `verifyLimit` só trata `player` e `gamification`.
|
|
498
|
+
- **Forçar `select: one_answer`**: o backend não impede múltiplas marcações em `MULTIPLE_CHOICE`.
|
|
499
|
+
- **Teto de `percent` em `MULTIPLE_CHOICE`**: não há clamp; pode passar de `1.0`.
|
|
500
|
+
- **Campos `gradePercent`/`gradeCheck` em `Choice`**: **não existem** no modelo; seriam silenciosamente descartados (vale para qualquer campo desconhecido, devido a `@JsonIgnoreProperties(ignoreUnknown=true)`).
|
|
501
|
+
- **Hardening de aliases para `Matching`/`MissingWords`**: só o `dragDropText` é normalizado no manager; os outros dependem do Jackson. Se o binding ignorar os aliases, esses modelos podem perder dados silenciosamente.
|
|
502
|
+
- **`GET /v3/question/log/{id}`**: não existe — log único só por filtro.
|
|
503
|
+
- **Validação de tipo desconhecido na correção**: um `type` fora do enum não cai em nenhum `answerXxx` → `grade`/`percent` ficam `null`.
|
|
504
|
+
- **Scheduler/Job**: nenhum job processa `question`/`question_log`.
|
|
505
|
+
- **Fora do módulo**: coleção `partner_question` (mesma classe, outra coleção, não exposta em `/v3/question`) e `com.funifier.engine.system.question.*` (perguntas de suporte do sistema — `accountId`/`topic`/`subject`/`description`).
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## 8. Segurança e Permissões
|
|
510
|
+
|
|
511
|
+
- **Autenticação:** Bearer token (`AuthBean`) em todas as rotas; resolução de tenant pelo `apiKey` (`FrontController.getInstance`).
|
|
512
|
+
- **Identidade implícita:** `player == "me"` (ou ausente, no `POST /log`) é substituído pelo player do token (`getPlayerFromTokenIfExist`).
|
|
513
|
+
- **Isolamento multi-tenant:** cada `apiKey` opera sobre sua própria conexão `Jongo`/banco. Não há ACL por pergunta — qualquer chamador autenticado no tenant pode ler/escrever/excluir.
|
|
514
|
+
- **Superfície de injeção (por design):** os parâmetros `q`, `orderby` e `fields` são **concatenados como string crua** nas pipelines de aggregation:
|
|
515
|
+
- `query.append(", " + q)` no `$match` (`findAllPaginated`, `findAll`, `findAllQuestionLogs*`).
|
|
516
|
+
- `"{$sort : {" + orderby + " : #}}"` no `$sort`.
|
|
517
|
+
- `fields.split(",")` montando `$project`.
|
|
518
|
+
- `/log/aggregate` injeta diretamente os estágios enviados no body.
|
|
519
|
+
|
|
520
|
+
Isso expõe a sintaxe de query do Mongo ao cliente (comportamento intencional da plataforma Funifier), mas significa que entrada não confiável nesses campos pode alterar a pipeline. **Não sanitize no front esperando proteção do back** — trate esses campos como superfície de query Mongo.
|
|
521
|
+
- **Exclusão destrutiva:** `DELETE /v3/question/{id}` apaga em cascata todos os `question_log` — sem confirmação e sem soft-delete.
|
|
109
522
|
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## 9. Observabilidade e Troubleshooting
|
|
526
|
+
|
|
527
|
+
**Diagnóstico — a pergunta existe e está correta?**
|
|
528
|
+
```
|
|
529
|
+
GET /v3/question/<id>
|
|
530
|
+
GET /v3/question?q={"type":"MULTIPLE_CHOICE"}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**A resposta foi registrada?**
|
|
534
|
+
```
|
|
535
|
+
GET /v3/question/log?question=<id>&player=<player>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**Quantas tentativas um jogador fez?** (mesmo cálculo do `meta.attemptsCount`)
|
|
539
|
+
```
|
|
540
|
+
db.question_log.count({ player: "<player>", question: "<id>" })
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**Erros comuns e causas:**
|
|
544
|
+
|
|
545
|
+
| Mensagem / sintoma | Causa |
|
|
546
|
+
|---|---|
|
|
547
|
+
| `question dont exist` | `question` do log aponta para id inexistente |
|
|
548
|
+
| `answer in invalid format: type X requires ...` | Formato de `answer` não bate com o tipo (ex.: `MULTIPLE_CHOICE` exige `List<String>`, `MATCHING` exige `Map`) |
|
|
549
|
+
| `you have exced the limit ...` | `limit.total` atingido na janela `limit.every` |
|
|
550
|
+
| `question.action ... does not exist` | `question.action` referencia action inexistente |
|
|
551
|
+
| Pergunta criada mas "não salvou" | Erros em `exceptions` no `POST` (HTTP ainda é 201) — inspecione o array |
|
|
552
|
+
| `grade`/`percent` ausentes no log | `type` fora do enum, ou erro de validação anterior interrompeu a correção |
|
|
553
|
+
| Feedback "errado" para acerto parcial | Lembre que o feedback usa `log.grade` (peso × percent), não `percent` (§5.2) |
|
|
554
|
+
| `ESSAY` sempre com nota 0 | Comportamento esperado — sem correção automática |
|
|
555
|
+
| Dados do modelo "sumiram" em Matching/MissingWords | Aliases não normalizados no manager (só `dragDropText` é) — envie os nomes canônicos |
|
|
556
|
+
|
|
557
|
+
**O que verificar quando algo não funciona:**
|
|
558
|
+
1. O `type` está no enum e os campos obrigatórios da §5.1 estão presentes?
|
|
559
|
+
2. O `answer` está no formato da §2.4 para o tipo?
|
|
560
|
+
3. `question.grade` está em `1` quando você espera que `percent` e `grade` coincidam?
|
|
561
|
+
4. Para quiz: `quiz_log` está sendo enviado? (sem ele, não há `updateLog`.)
|
|
562
|
+
5. Para gamificação: `question.action` existe e está ativa?
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## 10. Exemplos Práticos
|
|
567
|
+
|
|
568
|
+
### 10.1 Mínimo funcional — Múltipla escolha
|
|
569
|
+
```json
|
|
570
|
+
POST /v3/question
|
|
571
|
+
{
|
|
572
|
+
"type": "MULTIPLE_CHOICE",
|
|
573
|
+
"title": "Descobrimento",
|
|
574
|
+
"question": "Quem descobriu o Brasil?",
|
|
575
|
+
"select": "one_answer",
|
|
576
|
+
"grade": 1.0,
|
|
577
|
+
"choices": [
|
|
578
|
+
{ "answer": "cabral", "label": "Pedro Alvares Cabral", "grade": 1.0 },
|
|
579
|
+
{ "answer": "dpedro", "label": "Dom Pedro I", "grade": 0.0 }
|
|
580
|
+
]
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
Resposta do jogador:
|
|
584
|
+
```json
|
|
585
|
+
POST /v3/question/log
|
|
586
|
+
{ "player": "tom", "question": "<id>", "answer": ["cabral"] }
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### 10.2 Avançado — Matching (modelo novo) + limite + action + feedbacks
|
|
110
590
|
```json
|
|
591
|
+
POST /v3/question
|
|
111
592
|
{
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"grade": 1,
|
|
115
|
-
"
|
|
116
|
-
|
|
117
|
-
|
|
593
|
+
"type": "MATCHING",
|
|
594
|
+
"title": "Números romanos",
|
|
595
|
+
"grade": 1.0,
|
|
596
|
+
"model": {
|
|
597
|
+
"interactionType": "MATCHING",
|
|
598
|
+
"matching": {
|
|
599
|
+
"left": [ {"id":"1","text":"1"}, {"id":"2","text":"2"}, {"id":"3","text":"3"} ],
|
|
600
|
+
"right": [ {"id":"I","text":"I"}, {"id":"II","text":"II"}, {"id":"III","text":"III"} ],
|
|
601
|
+
"solution": [ {"leftId":"1","rightId":"I"}, {"leftId":"2","rightId":"II"}, {"leftId":"3","rightId":"III"} ],
|
|
602
|
+
"scoring": "all_or_nothing"
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
"feedbacks": [
|
|
606
|
+
{ "event": "correct", "message": "Mandou bem, {{player.name}}!" },
|
|
607
|
+
{ "event": "wrong", "message": "Tente de novo." }
|
|
608
|
+
],
|
|
609
|
+
"limit": { "total": 3, "per": "player", "every": "1d" },
|
|
610
|
+
"action": "answer_quiz"
|
|
118
611
|
}
|
|
119
612
|
```
|
|
613
|
+
Resposta:
|
|
614
|
+
```json
|
|
615
|
+
POST /v3/question/log
|
|
616
|
+
{ "player": "tom", "question": "<id>", "answer": { "1": "I", "2": "II", "3": "III" } }
|
|
617
|
+
```
|
|
120
618
|
|
|
121
|
-
|
|
619
|
+
### 10.3 Anti-pattern (o que NÃO fazer)
|
|
620
|
+
```json
|
|
621
|
+
// ❌ Esperar que "select":"one_answer" impeça marcar 2 opções certas.
|
|
622
|
+
// O backend SOMA os grades — percent pode passar de 1.0.
|
|
623
|
+
{
|
|
624
|
+
"type": "MULTIPLE_CHOICE",
|
|
625
|
+
"title": "Errado",
|
|
626
|
+
"select": "one_answer",
|
|
627
|
+
"grade": 2.0, // ❌ peso != 1 desloca os limiares de feedback:
|
|
628
|
+
"choices": [ // percent=0.5 → grade=1.0 → feedback "correct"
|
|
629
|
+
{ "answer": "a", "grade": 1.0 },
|
|
630
|
+
{ "answer": "b", "grade": 1.0 } // marcar a+b → percent=2.0, grade=4.0
|
|
631
|
+
]
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
Por quê é ruim: (1) `select` não é enforced; (2) `grade=2` faz a seleção de feedback (`correct`/`partial`/`wrong`) divergir do percentual real de acerto. Mantenha `grade=1` quando quiser que `grade` e `percent` coincidam, e não dependa de `select` para validar escolha única.
|
|
635
|
+
|
|
636
|
+
```json
|
|
637
|
+
// ❌ Campos inexistentes em Choice são descartados em silêncio.
|
|
638
|
+
{ "answer": "a", "label": "A", "gradePercent": 100, "gradeCheck": true }
|
|
639
|
+
// 'gradePercent' e 'gradeCheck' NÃO existem no modelo → ignorados. Use apenas 'grade'.
|
|
640
|
+
```
|
|
122
641
|
|
|
123
|
-
|
|
124
|
-
- Texto com HTML
|
|
125
|
-
- Imagens (campo `image`)
|
|
126
|
-
- Vídeos e áudio (via `extra`)
|
|
127
|
-
- Mini games (caça-palavras, cruzadinha)
|
|
642
|
+
---
|
|
128
643
|
|
|
129
|
-
##
|
|
644
|
+
## Checklist de Configuração
|
|
130
645
|
|
|
131
|
-
- [ ]
|
|
132
|
-
- [ ]
|
|
133
|
-
- [ ]
|
|
134
|
-
- [ ]
|
|
135
|
-
- [ ]
|
|
136
|
-
- [ ]
|
|
646
|
+
- [ ] `type` definido e dentro do enum (`MULTIPLE_CHOICE`, `TRUE_FALSE`, `MATCHING`, `SHORT_ANSWER`, `SELECT_MISSING_WORDS`, `DRAG_AND_DROP_INTO_TEXT`, `ESSAY`, `CUSTOM`).
|
|
647
|
+
- [ ] `title` preenchido (obrigatório exceto em `CUSTOM`).
|
|
648
|
+
- [ ] Campos obrigatórios do tipo presentes (§5.1): `choices`+`select` (MC), `correctAnswer` (TF), `caseSensitive` (SA), `model.*` ou `choices` (MATCHING/SELECT/DRAG).
|
|
649
|
+
- [ ] `grade = 1` se você quer que `log.grade` reflita o percentual de acerto (armadilha §5.2).
|
|
650
|
+
- [ ] Para `DRAG_AND_DROP`/`SELECT_MISSING_WORDS` legados: `[[1]]`, `[[2]]`... presentes no campo `question`.
|
|
651
|
+
- [ ] Resposta (`answer`) enviada no formato correto do tipo (§2.4) — formato errado gera `answer in invalid format`.
|
|
652
|
+
- [ ] Para vincular a um quiz: enviar `quiz_log` no log (sem ele não há `updateLog`).
|
|
653
|
+
- [ ] Para gamificar: `action` existe e está ativa antes de criar a pergunta.
|
|
654
|
+
- [ ] Ciente da cascata: `DELETE /v3/question/{id}` apaga **todos** os `question_log` da pergunta.
|
|
655
|
+
- [ ] Não confie em `select` para forçar escolha única, nem use campos inexistentes (`gradePercent`, `gradeCheck`).
|
|
656
|
+
- [ ] Inspecionar `exceptions` no `POST /v3/question` (HTTP 201 não significa salvo).
|