Validate a Review Summary
Validate a Review Summary
A fictional online bookshop asks a language model to summarize each customer review as JSON. A moderation dashboard filters the summaries by sentiment and topic and averages the rating per book, so a reply that breaks the contract must never reach it. Model replies are untrusted text: most follow the contract and some do not.
Implement validateReviewSummary(raw), where raw is the model's reply as a string. Return:
{ ok: true, value: { rating, sentiment, topics } }when the reply is accepted{ ok: false, errors: [...] }with error codes when it is not
Bad model output is an expected input, so the function returns codes and never throws.
The contract
| Field | Rule |
|---|---|
rating | An integer from 1 to 5. An integer is a JSON number with no fractional part, so 4.0 counts as 4. Booleans, strings, null, and fractions are not integers. |
sentiment | Exactly "positive", "mixed", or "negative" (case-sensitive, no trimming). |
topics | An array of 0 to 3 items. Each item is exactly "price", "delivery", "condition", or "story", and no item repeats. |
| any other key | Not allowed. |
Error codes, in check order
| Step | Checks | Codes, in rule order |
|---|---|---|
| 1 | whole reply | reply:parse: raw is not valid JSON. Return at once with only this code. |
| 2 | whole reply | reply:type: the parsed value is not a plain object (an array, null, a string, a number, or a boolean). Return at once with only this code. |
| 3 | rating | rating:missing (the key is absent), then rating:type (not an integer), then rating:range (below 1 or above 5) |
| 4 | sentiment | sentiment:missing, then sentiment:type (not a string), then sentiment:value (not one of the three values) |
| 5 | topics | topics:missing, then topics:type (not an array), then topics:too-many (more than 3 items), then topics:value (any item that is not one of the four topics, non-strings included; one code even if several items fail), then topics:duplicate (an item appears more than once) |
| 6 | other keys | extra:<key> for each key the contract does not name, written exactly as in the reply |
- Steps 3, 4, and 5 each add at most one code: the first rule that fails for that field. All three fields are always checked.
- A key set to
nullis present, not missing:"sentiment": nullissentiment:type. - Keys match exactly:
"Rating"or" rating"is notrating, so it counts as a missing field and an extra key. - Field codes come first in contract order (rating, sentiment, topics), whatever order the reply uses. The
extra:codes follow, sorted by plain string comparison (JavaScriptsort(), Pythonsorted()), so uppercase letters come before lowercase ones. Sort only the extra codes. - With no codes, the reply is accepted. Build
valueas a new object with the keysrating,sentiment,topicsin that order:ratingis an integer (4.0becomes 4), andtopicskeeps the reply's order. - The result has exactly two keys:
okandvalue, orokanderrors. - Do not repair keys or values: no trimming, no lowercasing, no converting
"4"to 4.
Examples
Example 1
validateReviewSummary('{"topics": ["price", "price"], "summary": "Arrived late", "sentiment": "neutral", "rating": 3}');
// { ok: false, errors: ['sentiment:value', 'topics:duplicate', 'extra:summary'] }Rating 3 passes. "neutral" is a string but not an allowed value. Both topics are allowed, but "price" repeats. summary is not in the contract. The reply lists topics first, yet the codes follow rating, sentiment, topics.
Example 2
validateReviewSummary('{"topics": ["delivery"], "sentiment": "mixed", "rating": 3.0}');
// { ok: true, value: { rating: 3, sentiment: 'mixed', topics: ['delivery'] } }3.0 has no fractional part, so it is the integer 3 (Python returns the int 3). The value is rebuilt in contract order.
Example 3
validateReviewSummary('{"rating": 0, "sentiment": null}');
// { ok: false, errors: ['rating:range', 'sentiment:type', 'topics:missing'] }0 is an integer below 1. sentiment is present with the value null, which is not a string. topics is absent.
In Python, implement validate_review_summary(raw) and return a dict with the same keys ("ok", "value", "errors"). errors and topics are lists, and rating is an int.
Constraints
rawis always a string. Whitespace around the JSON is allowed (both parsers accept it); values inside are never trimmed.- Key names in the tests are ASCII, so JavaScript
sort()and Pythonsorted()agree. NaN,Infinity, and repeated keys are outside the tests.- The first test checks eight replies, R1 to R8, from the AI Engineering question this problem belongs to. Predict their results there before you open or run the first test.
Expected Complexity
O(n + k log k) time for a reply of n characters with k extra keys, and O(n) extra space for the parsed value.
0 views
Solution
Hints
