question_kit 0.1.0 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37b2cc86be99b7513693957fd7e67cd9f19209c47ab31678836acfc0a8332661
4
- data.tar.gz: 882bc674404e1cf01084da328595a14a0deff0096c96a775648ba97e54960d7a
3
+ metadata.gz: 530bd9b4f43db8122b6c3fb52a35a8f966f6fbcce4f36a6d7326339737fa3a55
4
+ data.tar.gz: 6a8cb861855110b00ec6f74ff23a7b6a1f29651008e4cf035dd98577575d6af5
5
5
  SHA512:
6
- metadata.gz: f51ba58ee9eafbeaa9faa1539f617c5bbddb09bf0248a35462537323f5fda0131968c725ec4f867de5f671ee472443067dce5b3ac2b4085cc6f076ee6dd69a46
7
- data.tar.gz: c7be35b2bbe19282322aa576353a6a7ee50f3f1479b42e787e1688f3884d220d01812a2bec116446bc655262346642bb4236c910dd39da2826a1666b9f9eab83
6
+ metadata.gz: aba30b74f0271ed2b3f3ec2d8d0071d23a98ff65a0b0b39a212a08feb2d05c064b76762500333de0f43e8338492564ca1c6f2674ba6d8d615aa140baea98e855
7
+ data.tar.gz: b0b80230b2bb6b39be79e865e966cdd60a2e10337c492bdd3b42c6c7574b6c885254a206ac23cefc9d3fdbc4b5645033e5ad74217363a9a47d495c0cfe4559cc
@@ -0,0 +1,15 @@
1
+ module QuestionKit
2
+ module AnswerKey
3
+ def self.chosen(step)
4
+ case step["type"]
5
+ when "multiple_select" then Score.right_answers(step)
6
+ when "fill_in_the_blank" then Array(step["accepted_answers"]).first&.fetch("value", nil)
7
+ else Score.right_answers(step).first
8
+ end
9
+ end
10
+
11
+ def self.accepted(step)
12
+ Array(step["accepted_answers"]).filter_map { |answer| answer["value"].presence }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ module QuestionKit
2
+ module Score
3
+ def self.total(answers, steps)
4
+ answers.sum { |id, value| worth(steps[id.to_s].to_h, value) }
5
+ end
6
+
7
+ def self.worth(step, value)
8
+ return picked_worth(step, value) if step["type"] == "multiple_select"
9
+ return typed_worth(step, value) if step["type"] == "fill_in_the_blank"
10
+
11
+ chosen = EasyFlow::Steps::Question.answers_of(step).find { |answer| answer.is_a?(Hash) && answer["value"] == value }
12
+
13
+ chosen&.fetch("weight", nil).to_i
14
+ end
15
+
16
+ def self.typed_worth(step, text)
17
+ Array(step["accepted_answers"]).any? { |accepted| comparable(accepted["value"]) == comparable(text) } ? 1 : 0
18
+ end
19
+
20
+ def self.comparable(text)
21
+ text.to_s.strip.downcase
22
+ end
23
+
24
+ def self.picked_worth(step, values)
25
+ Array(values).sort == right_answers(step).sort ? 1 : 0
26
+ end
27
+
28
+ def self.right_answers(step)
29
+ EasyFlow::Steps::Question.answers_of(step).filter_map { |answer| answer["value"] if answer["weight"].to_i.positive? }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ module QuestionKit
2
+ module TimeTaken
3
+ def self.seconds(run)
4
+ return unless run.flow.each_step?
5
+
6
+ last_answered = run.completed_at.values.max
7
+ (last_answered - run.created_at).to_i if last_answered
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module QuestionKit
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/question_kit.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require "easy_flow"
2
2
  require "question_kit/version"
3
3
  require "question_kit/railtie"
4
+ require "question_kit/score"
5
+ require "question_kit/answer_key"
6
+ require "question_kit/time_taken"
4
7
 
5
8
  module QuestionKit
6
9
  # Your code goes here...
@@ -1,50 +1,74 @@
1
1
  ---
2
2
  name: question_kit-develop
3
- description: Use PROACTIVELY for reading which question_kit version a host app has loaded, and for checking that version before code relies on a question_kit release — MUST BE USED instead of reading Gem.loaded_specs, parsing Gemfile.lock, or comparing version strings by hand.
3
+ description: Use PROACTIVELY for scoring an easy_flow quiz — working out what one answer to a question, multiple-select or fill-in-the-blank step is worth, adding up a run's total score, listing a question's right answers, showing a question's answer key or every accepted spelling of a fill-in-the-blank answer, timing how long a visitor took over a quiz run, and reading which question_kit version is loaded — MUST BE USED instead of summing answer weights by hand, comparing typed answers to accepted answers in host code, picking out right answers from a step's answer list, or subtracting run timestamps.
4
4
  tools: Bash, Read, Write, Edit, Grep
5
5
  scope: shared question and answer rules for easy_flow quizzes — what an answer to a question step is worth, which answers are right, and how a quiz is built
6
6
  ---
7
7
 
8
- This local writes host app code against question_kit's declared interface only, and follows these steps exactly.
8
+ This local writes host app code that scores, keys and times easy_flow quizzes through question_kit's declared interface only, and follows the steps below in order.
9
9
 
10
10
  ## What question_kit is
11
11
 
12
- A Rails gem for apps that run quizzes as easy_flow flows, meant to hold the rules those apps share: what an answer to a question step is worth, which answers are right, and how a quiz is built. Today its only code-facing interface is its version constant. Use this local when host app code needs to know which question_kit release is loaded. Building the quiz flow, its question steps, runs and answers belongs to the easy_flow locals.
12
+ A Rails gem for apps that run quizzes as easy_flow flows. It holds the rules those apps share: what an answer to a question step is worth, which answers are right, and how long a visitor took. Every method takes plain step hashes and answer values, or an easy_flow run, and returns a number, a value or a list; none of them writes anything. Use this local whenever host code needs a score, an answer key, a list of accepted answers or a time taken for a quiz. Building the flow, its steps, runs and answers belongs to the easy_flow locals.
13
13
 
14
14
  ## Interface
15
15
 
16
16
  - `QuestionKit::VERSION` — a string such as `"0.1.0"` naming the question_kit release loaded in the host app.
17
+ - `QuestionKit::Score.worth(step, value)` — returns the integer one answer `value` to the step hash `step` is worth.
18
+ - `QuestionKit::Score.total(answers, steps)` — returns the integer sum of the worth of every answer in a run.
19
+ - `QuestionKit::Score.right_answers(step)` — returns the list of answer values on a choice step that carry a positive weight.
20
+ - `QuestionKit::AnswerKey.chosen(step)` — returns the answer key to show for a step: one value, a list of values, or `nil`.
21
+ - `QuestionKit::AnswerKey.accepted(step)` — returns every non-blank accepted answer on a fill-in-the-blank step.
22
+ - `QuestionKit::TimeTaken.seconds(run)` — returns the whole seconds from a run's start to its last answer, or `nil`.
23
+
24
+ ## The step hash
25
+
26
+ Every method that takes a `step` takes the step's settings as a hash with string keys, as easy_flow stores them in a flow's document. Pass the hash, not a record.
27
+
28
+ - `"type"` decides how it is read: `"multiple_select"`, `"fill_in_the_blank"`, or any other value (or none) for a single-choice question.
29
+ - A choice step lists its answers under `"answers"` or `"options"`, each a hash with a `"value"` and an optional `"weight"`. A weight may be an integer or a string of digits, and a missing weight counts as 0.
30
+ - A fill-in-the-blank step lists its accepted answers under `"accepted_answers"`, each a hash with a `"value"`.
17
31
 
18
32
  ## How to use it
19
33
 
20
34
  1. Confirm question_kit is in the host app's `Gemfile.lock`. If it is not, hand the work to `question_kit-install` and stop.
21
- 2. In the file that reads the version, require the gem explicitly at the top:
35
+ 2. At the top of the file that calls question_kit, require it explicitly:
22
36
 
23
37
  ```ruby
24
38
  require "question_kit"
25
39
  ```
26
40
 
27
41
  This also loads easy_flow, so no separate `require "easy_flow"` is needed.
28
- 3. Read the version where it is needed:
42
+ 3. Put the calls in host code that reads a finished or stored run, such as a results page's controller, a model method on the app's own quiz record, or a job that records a score. Ask the developer where the score is used and whether it is shown only or also stored; do not pick.
43
+ 4. Build the arguments from the flow version the run was taken on, not from the flow's live version, so a later edit does not change an old run's score:
44
+ - `steps` is a hash from step id (string) to that step's settings hash.
45
+ - `answers` is a hash from step id to the value the visitor gave. Ids may be strings or symbols.
46
+ 5. Score one answer with `QuestionKit::Score.worth(step, value)`:
47
+ - Single-choice: `value` is the chosen answer's value, and the result is that answer's weight, or 0 when it carries none or matches no answer.
48
+ - Multiple-select: `value` is the list of ticked values, and the result is 1 when they are exactly the right answers in any order and 0 otherwise. Partial credit is not given.
49
+ - Fill-in-the-blank: `value` is the typed text, and the result is 1 when it matches an accepted answer after removing outer spaces and ignoring letter case, and 0 otherwise.
50
+ 6. Score a whole run with `QuestionKit::Score.total(answers, steps)`. An answer whose step id is not in `steps` counts 0, so steps that are not questions, such as info or branch steps, can be left in `answers` or taken out.
51
+ 7. Ask the developer whether a score of 0 for a single-choice question with no weights set is correct for their quizzes, or whether every quiz must set weights before it is published. question_kit cannot tell an unweighted question from a wrong answer.
52
+ 8. Show what was right:
53
+ - `QuestionKit::Score.right_answers(step)` for the full list of values with a positive weight on a choice step.
54
+ - `QuestionKit::AnswerKey.chosen(step)` for the single thing to display as the key: the list of right values for multiple-select, the first accepted answer for fill-in-the-blank, and the first right value for any other question. It returns `nil` when there is none, so render that case.
55
+ - `QuestionKit::AnswerKey.accepted(step)` for every accepted spelling on a fill-in-the-blank step, with blank entries left out.
56
+ 9. Time a run with `QuestionKit::TimeTaken.seconds(run)`, passing the easy_flow run record. It returns `nil` when the run's flow saves answers only once the flow ends, and when no step has been completed yet; render both cases, for example as "not timed".
57
+ 10. When code depends on a release, compare versions with `Gem::Version`, never as strings:
29
58
 
30
- ```ruby
31
- QuestionKit::VERSION
32
- ```
33
-
34
- 4. When code depends on a release, compare with `Gem::Version`, never with string comparison:
35
-
36
- ```ruby
37
- Gem::Version.new(QuestionKit::VERSION) >= Gem::Version.new("0.2.0")
38
- ```
59
+ ```ruby
60
+ Gem::Version.new(QuestionKit::VERSION) >= Gem::Version.new("0.2.0")
61
+ ```
39
62
 
40
- 5. Ask the developer what the app does when the loaded version is older than the one its code needs: raise at boot, log a warning, or skip the feature. Do not pick one.
41
- 6. If the check runs at boot, put it in an initializer under `config/initializers/` in the host app. Otherwise put it beside the code that depends on the release.
42
- 7. Write a test in the host app that asserts the app's own behaviour for each branch of the check, and run it.
63
+ Ask the developer what the app does when the loaded version is older: raise at boot, log a warning, or skip the feature.
64
+ 11. Write host app tests that assert the app's own behaviour with the score, key or time, such as what the results page shows, and run them. Do not test question_kit's own scoring rules.
43
65
 
44
66
  ## Conventions
45
67
 
46
- - Never hard-code the version string in host app code. Read `QuestionKit::VERSION`.
47
- - Never assign to or redefine `QuestionKit::VERSION`.
48
- - question_kit has no scoring, right-answer or quiz-building methods yet. Do not invent calls for them. If the developer asks for that behaviour, say the gem does not offer it and ask whether to build it in the host app or wait for question_kit to add it.
49
- - Adding the gem, and upgrading it, belongs to `question_kit-install`.
68
+ - Never compute a score, a right answer or a time taken in host code. Call question_kit so every app scores the same way.
69
+ - Never compare a typed answer to an accepted answer in host code; `Score.worth` already ignores case and outer spaces.
70
+ - Never mutate the step hash passed in.
71
+ - Never hard-code or redefine `QuestionKit::VERSION`.
72
+ - question_kit does not build quizzes, add step types or store scores. If the developer asks for that, say so and ask whether to build it in the host app or wait for question_kit to add it.
73
+ - Adding or upgrading the gem belongs to `question_kit-install`.
50
74
  - Flows, step types, runs and answers belong to the easy_flow locals.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: question_kit-info
3
- description: Use to learn what question_kit offers — shared rules for easy_flow quizzes, covering what an answer to a question step is worth, which answers are right, and how a quiz is built.
3
+ description: Use to learn what question_kit offers — shared rules for easy_flow quizzes, covering what an answer to a question step is worth, which answers are right, the answer key a quiz shows for each question, and how long a visitor took on a quiz.
4
4
  tools: Read
5
5
  scope: shared question and answer rules for easy_flow quizzes — what an answer to a question step is worth, which answers are right, and how a quiz is built
6
6
  ---
@@ -10,15 +10,18 @@ This local explains question_kit and makes no changes.
10
10
  ## What question_kit is
11
11
 
12
12
  question_kit is a Rails gem for apps that run quizzes as easy_flow flows. Each
13
- quiz question is an easy_flow question step. The gem is meant to hold the rules
14
- those apps share: what an answer to a question step is worth, which answers are
15
- right, and how a quiz is built. With the rules in one gem, each app does not
16
- keep its own copy.
13
+ quiz question is an easy_flow question step. The gem holds the rules those apps
14
+ share, so each app does not keep its own copy of them.
17
15
 
18
- Today the gem holds none of those rules yet. It loads easy_flow into the host
19
- app and reports its own version, and nothing more. Reach for it when an app
20
- runs easy_flow quizzes and should follow the shared rules as they are added.
21
- Until then, it is only a dependency to put in place.
16
+ Reach for it when an app runs easy_flow quizzes and needs to:
17
+
18
+ - score a visitor's answer to one question, or add up a whole quiz;
19
+ - say which answers to a question are right;
20
+ - show the answer key for a question once the quiz is over;
21
+ - report how long a visitor took to answer the quiz.
22
+
23
+ It reads the steps and runs easy_flow already stores. It adds no tables, no
24
+ pages and no step types of its own.
22
25
 
23
26
  ## Interface
24
27
 
@@ -26,24 +29,49 @@ This local documents no commands. The other two locals cover everything the
26
29
  gem exposes:
27
30
 
28
31
  - The install local, `question_kit-install`, adds the gem to a host app.
29
- - The develop local, `question_kit-develop`, covers the code a host app writes against the gem.
32
+ - The develop local, `question_kit-develop`, covers the scoring, answer key and
33
+ time rules a host app calls.
30
34
 
31
35
  Anything about building or running the flows themselves, such as the canvas,
32
- step types, runs and answers, belongs to easy_flow. The easy_flow locals cover
33
- that.
36
+ step types, runs and recorded answers, belongs to easy_flow. The easy_flow
37
+ locals cover that.
34
38
 
35
39
  ## How to use it
36
40
 
37
41
  - To add question_kit to an app, use `question_kit-install`.
38
- - To write code that uses question_kit, use `question_kit-develop`.
42
+ - To score answers, show an answer key or time a quiz in an app, use
43
+ `question_kit-develop`.
39
44
  - To build the quiz flow or a question step, use the easy_flow locals.
40
45
 
41
46
  ## Conventions
42
47
 
43
48
  - **Quiz** — an easy_flow flow whose steps ask the visitor questions.
44
- - **Question step** — one easy_flow step that asks one question and records the
45
- visitor's answer.
46
- - **Worth** — what an answer is worth toward the quiz's result.
47
- - **Right answer** — an answer the quiz counts as correct for its question.
48
49
  - **Host app** — the Rails app that installs question_kit and easy_flow and runs
49
50
  the quizzes.
51
+ - **Question step** — one easy_flow step that asks one question and records the
52
+ visitor's answer. The rules know three kinds:
53
+ - a **single choice** question, where the visitor picks one of the step's
54
+ answers;
55
+ - a **multiple select** question, where the visitor ticks several;
56
+ - a **fill-in-the-blank** question, where the visitor types the answer.
57
+ - **Weight** — a number an admin sets on each answer of a choice question.
58
+ - **Right answer** — on a choice question, an answer whose weight is above
59
+ zero.
60
+ - **Accepted answer** — on a fill-in-the-blank question, a text the admin lists
61
+ as correct. An accepted answer left blank does not count.
62
+ - **Worth** — what one answer counts toward the quiz's result:
63
+ - a single choice answer is worth the weight of the answer picked, and zero
64
+ when it has none;
65
+ - a multiple select answer is worth one when the ticked answers are exactly
66
+ the right answers, and zero otherwise;
67
+ - a fill-in-the-blank answer is worth one when it matches any accepted answer,
68
+ ignoring capital letters and spaces at either end, and zero otherwise.
69
+ - **Total** — the sum of the worth of every answer in a run.
70
+ - **Answer key** — the answer a quiz shows as correct for a question: the first
71
+ right answer of a single choice question, every right answer of a multiple
72
+ select question, and the first accepted answer of a fill-in-the-blank
73
+ question.
74
+ - **Time taken** — the seconds from the start of a visitor's run to the last
75
+ answer they gave. A run with no answers has none. A flow that saves answers
76
+ only when the visitor finishes, rather than after each step, records no
77
+ time per answer, so its runs have no time taken.
@@ -5,8 +5,17 @@ install:
5
5
 
6
6
  develop:
7
7
  - QuestionKit::VERSION
8
+ - QuestionKit::Score.worth(step, value)
9
+ - QuestionKit::Score.total(answers, steps)
10
+ - QuestionKit::Score.right_answers(step)
11
+ - QuestionKit::AnswerKey.chosen(step)
12
+ - QuestionKit::AnswerKey.accepted(step)
13
+ - QuestionKit::TimeTaken.seconds(run)
8
14
 
9
15
  sources:
10
16
  - question_kit.gemspec
11
17
  - lib/question_kit.rb
12
18
  - lib/question_kit/version.rb
19
+ - lib/question_kit/score.rb
20
+ - lib/question_kit/answer_key.rb
21
+ - lib/question_kit/time_taken.rb
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: question_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tylercschneider
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-11-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -51,7 +50,10 @@ files:
51
50
  - README.md
52
51
  - Rakefile
53
52
  - lib/question_kit.rb
53
+ - lib/question_kit/answer_key.rb
54
54
  - lib/question_kit/railtie.rb
55
+ - lib/question_kit/score.rb
56
+ - lib/question_kit/time_taken.rb
55
57
  - lib/question_kit/version.rb
56
58
  - lib/tasks/question_kit_tasks.rake
57
59
  - the_local/agents/question_kit-develop.md
@@ -64,7 +66,6 @@ licenses:
64
66
  metadata:
65
67
  homepage_uri: https://github.com/DYB-Development/question_kit
66
68
  source_code_uri: https://github.com/DYB-Development/question_kit
67
- post_install_message:
68
69
  rdoc_options: []
69
70
  require_paths:
70
71
  - lib
@@ -79,8 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
80
  - !ruby/object:Gem::Version
80
81
  version: '0'
81
82
  requirements: []
82
- rubygems_version: 3.5.16
83
- signing_key:
83
+ rubygems_version: 4.0.20
84
84
  specification_version: 4
85
85
  summary: Shared question and answer rules for apps that run easy_flow quizzes.
86
86
  test_files: []