From ce6d6e39bc7744af18ab05a60dc1fafeb8afe7aa Mon Sep 17 00:00:00 2001 From: xiongtao3 Date: Tue, 2 Jun 2026 18:52:28 +0800 Subject: [PATCH 1/3] feat(merriam_webster): add Merriam-Webster mirror site Adds a Flask mirror of merriam-webster.com as the 16th WebHarbor site: dictionary, thesaurus, word of the day, vocabulary quizzes, and full account/login flow. 142 real word entries, 30 thesaurus entries, 3 quizzes (10 questions each), all scraped from the live site. 20 WebVoyager-format benchmark tasks in tasks.jsonl. Registered as site index 15 (port 40015) in websyn_start.sh, control_server.py, and Dockerfile (EXPOSE 40000-40015). Pre-PR checks (passed locally): - docker build webharbor:dev (5.89GB) - 16/16 sites return HTTP 200 - /reset/merriam_webster byte-identical (md5 a4248bef..) - /reset-all 16 sites parallel ~1.1s - 20/20 benchmark tasks walkable in container - All 15 existing sites still byte-identical (no regression) Assets: heavy assets (instance_seed/merriam_webster.db, 12 real images from MW games/quizzes) uploaded to HF dataset YuanDaozeiii/WebHarbor at revision 8866e560. .assets-revision pins to the fork until the HF PR adding merriam_webster.tar.gz to ChilleD/WebHarbor is merged. Also fixes a pre-existing .gitignore bug where the inline comment on sites/*/scraped_data/ silently disabled the rule (gitignore does not support inline comments). Co-Authored-By: Claude Opus 4.7 --- .assets-revision | 10 +- .gitignore | 6 +- Dockerfile | 4 +- control_server.py | 2 +- sites/merriam_webster/_health.py | 3 + sites/merriam_webster/_seed_content.py | 7476 +++++++++++++++++ sites/merriam_webster/app.py | 565 ++ sites/merriam_webster/requirements.txt | 9 + sites/merriam_webster/seed_data.py | 110 + sites/merriam_webster/static/css/.gitkeep | 0 sites/merriam_webster/static/css/style.css | 229 + sites/merriam_webster/static/icons/.gitkeep | 0 sites/merriam_webster/static/images/.gitkeep | 0 sites/merriam_webster/static/js/.gitkeep | 0 sites/merriam_webster/tasks.jsonl | 20 + sites/merriam_webster/templates/.gitkeep | 0 sites/merriam_webster/templates/404.html | 9 + sites/merriam_webster/templates/500.html | 9 + sites/merriam_webster/templates/account.html | 56 + sites/merriam_webster/templates/base.html | 114 + .../templates/games_index.html | 27 + sites/merriam_webster/templates/index.html | 89 + sites/merriam_webster/templates/login.html | 29 + sites/merriam_webster/templates/quiz.html | 26 + .../templates/quiz_result.html | 30 + sites/merriam_webster/templates/register.html | 39 + sites/merriam_webster/templates/search.html | 41 + .../templates/thesaurus_detail.html | 37 + .../templates/word_detail.html | 77 + sites/merriam_webster/templates/wotd.html | 50 + websyn_start.sh | 12 +- 31 files changed, 9066 insertions(+), 13 deletions(-) create mode 100644 sites/merriam_webster/_health.py create mode 100644 sites/merriam_webster/_seed_content.py create mode 100644 sites/merriam_webster/app.py create mode 100644 sites/merriam_webster/requirements.txt create mode 100644 sites/merriam_webster/seed_data.py create mode 100644 sites/merriam_webster/static/css/.gitkeep create mode 100644 sites/merriam_webster/static/css/style.css create mode 100644 sites/merriam_webster/static/icons/.gitkeep create mode 100644 sites/merriam_webster/static/images/.gitkeep create mode 100644 sites/merriam_webster/static/js/.gitkeep create mode 100644 sites/merriam_webster/tasks.jsonl create mode 100644 sites/merriam_webster/templates/.gitkeep create mode 100644 sites/merriam_webster/templates/404.html create mode 100644 sites/merriam_webster/templates/500.html create mode 100644 sites/merriam_webster/templates/account.html create mode 100644 sites/merriam_webster/templates/base.html create mode 100644 sites/merriam_webster/templates/games_index.html create mode 100644 sites/merriam_webster/templates/index.html create mode 100644 sites/merriam_webster/templates/login.html create mode 100644 sites/merriam_webster/templates/quiz.html create mode 100644 sites/merriam_webster/templates/quiz_result.html create mode 100644 sites/merriam_webster/templates/register.html create mode 100644 sites/merriam_webster/templates/search.html create mode 100644 sites/merriam_webster/templates/thesaurus_detail.html create mode 100644 sites/merriam_webster/templates/word_detail.html create mode 100644 sites/merriam_webster/templates/wotd.html diff --git a/.assets-revision b/.assets-revision index 77578c00..64bf3b91 100644 --- a/.assets-revision +++ b/.assets-revision @@ -5,5 +5,11 @@ # is a git revision (branch name like `main`, a tag, or a specific commit # sha). Override at runtime with the ASSETS_REVISION env var. -repo: ChilleD/WebHarbor -revision: main +# Pinned to YuanDaozeiii/WebHarbor (fork) until the HF PR adding +# merriam_webster.tar.gz is merged into ChilleD/WebHarbor. Reviewers/CI: +# fetch_assets.sh will pull all 15 existing tarballs from ChilleD via +# fallback or you can run it against the fork directly. After the HF PR +# merges, this should be bumped back to `repo: ChilleD/WebHarbor` with +# the merge commit SHA. +repo: YuanDaozeiii/WebHarbor +revision: a591b293cf8d4cf52ea977feffbccb2dee98d18d diff --git a/.gitignore b/.gitignore index c2efc04c..24ce1529 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,10 @@ sites/*/static/external_cache/ # ============================================================= # Intermediate / volatile — never committed anywhere. # ============================================================= -sites/*/scraped_data/ # scrape pipeline intermediate; runtime data lives in instance_seed/*.db -sites/*/instance/ # rebuilt at every container boot from instance_seed/ +# scrape pipeline intermediate; runtime data lives in instance_seed/*.db +sites/*/scraped_data/ +# rebuilt at every container boot from instance_seed/ +sites/*/instance/ sites/*/venv/ # HF download metadata produced by `hf download`. diff --git a/Dockerfile b/Dockerfile index 991e5ab6..1e86b1d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # WebHarbor — slim, self-contained image. -# 15 Flask mirror sites + control plane on :8101. +# 16 Flask mirror sites + control plane on :8101. FROM python:3.12-slim-bookworm @@ -33,6 +33,6 @@ COPY control_server.py /opt/control_server.py COPY site_runner.py /opt/site_runner.py RUN chmod +x /opt/websyn_start.sh -EXPOSE 8101 40000-40014 +EXPOSE 8101 40000-40015 CMD ["/opt/websyn_start.sh"] diff --git a/control_server.py b/control_server.py index c255253c..4b6b995e 100644 --- a/control_server.py +++ b/control_server.py @@ -26,7 +26,7 @@ 'allrecipes', 'amazon', 'apple', 'arxiv', 'bbc_news', 'booking', 'github', 'google_flights', 'google_map', 'google_search', 'huggingface', 'wolfram_alpha', 'cambridge_dictionary', - 'coursera', 'espn', + 'coursera', 'espn', 'merriam_webster', ] BASE_PORT = 40000 WEBSYN_DIR = '/opt/WebSyn' diff --git a/sites/merriam_webster/_health.py b/sites/merriam_webster/_health.py new file mode 100644 index 00000000..839e1766 --- /dev/null +++ b/sites/merriam_webster/_health.py @@ -0,0 +1,3 @@ +"""Per-site health probe (optional, called by control_server).""" +def health(): + return {"ok": True, "site": "merriam_webster"} diff --git a/sites/merriam_webster/_seed_content.py b/sites/merriam_webster/_seed_content.py new file mode 100644 index 00000000..5bed8885 --- /dev/null +++ b/sites/merriam_webster/_seed_content.py @@ -0,0 +1,7476 @@ +"""Frozen seed content for the Merriam-Webster mirror. + +Generated by scraped_data/build_seed_content.py from real +merriam-webster.com data. Committed to git so the seed does not +depend on the gitignored scraped_data/ at runtime. +""" +WORDS = [ + { + "headword": "serendipity", + "slug": "serendipity", + "pos": "noun", + "pronunciation": "ˌser-ən-ˈdi-pə-tē", + "syllables": "ser-ən-di-pə-tē", + "first_known_use": "1754", + "etymology": "Serendip, variant of Sarandīb, Persian and Arabic name for Sri Lanka + -ity; from its possession by the heroes of the Persian fairy tale The Three Princes of Serendip", + "definitions": [ + { + "sense_num": 1, + "text": "the ability to find valuable or agreeable things not sought for", + "examples": [ + "… materials researchers, who now rely mostly on rules of thumb, trial-and-error and serendipity", + "… [Sh'Kia] Augustin landed the role through the charmed stroke of serendipity" + ] + }, + { + "sense_num": 2, + "text": "luck that takes the form of such finding", + "examples": [ + "As they leapfrog from South Africa to Singapore in search of local delicacies, the authors prove again and again that serendipity" + ] + }, + { + "sense_num": 3, + "text": "the gift of finding valuable or agreeable things not looked for", + "examples": [ + "… the pleasure of wandering while lost and discovering by serendipity" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "ubiquitous", + "slug": "ubiquitous", + "pos": "adjective", + "pronunciation": "yü-ˈbi-kwə-təs", + "syllables": "yü-bi-kwə-təs", + "first_known_use": "1772", + "etymology": "see ubiquity", + "definitions": [ + { + "sense_num": 1, + "text": "existing or being everywhere at the same time : constantly encountered : widespread", + "examples": [ + "The company's ads are ubiquitous", + "Florals are a ubiquitous" + ] + }, + { + "sense_num": 2, + "text": "existing or being everywhere at the same time : constantly encountered : widespread", + "examples": [ + "From the sky to the water, blue is one of the most ubiquitous", + "Hot dogs are the ideal road trip food—inexpensive, portable, ubiquitous" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "ephemeral", + "slug": "ephemeral", + "pos": "adjective", + "pronunciation": "i-ˈfe-mə-rəl", + "syllables": "i-fe-mə-rəl", + "first_known_use": "1576", + "etymology": "Greek ephēmeros lasting a day, daily, from epi- + hēmera day", + "definitions": [ + { + "sense_num": 1, + "text": "lasting a very short time", + "examples": [ + "Their fame turned out to be ephemeral" + ] + }, + { + "sense_num": 2, + "text": "lasting one day only", + "examples": [ + "… the ephemeral" + ] + }, + { + "sense_num": 3, + "text": "devoted to what is of temporary interest", + "examples": [ + "trillium, bloodroot, and other spring ephemerals" + ] + }, + { + "sense_num": 4, + "text": "something that lasts for a very short time : something ephemeral", + "examples": [ + "… several rather inflated pages of material about an ephemeral" + ] + }, + { + "sense_num": 5, + "text": "a plant that grows, flowers, and dies or goes dormant in a few days", + "examples": [] + }, + { + "sense_num": 6, + "text": "lasting one day only", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "quintessential", + "slug": "quintessential", + "pos": "adjective", + "pronunciation": "ˌkwin-tə-ˈsen(t)-shəl", + "syllables": "kwin-tə-sen(t)-shəl", + "first_known_use": "1551", + "etymology": "", + "definitions": [ + { + "sense_num": 1, + "text": "perfectly typical or representative of a particular kind of person or thing", + "examples": [ + "Jerry's your quintessential", + "His very faults were middling … It was not in his nature to be superlative in anything; unless, indeed, he was superlatively middling, the quintessential" + ] + }, + { + "sense_num": 2, + "text": "being a quintessence", + "examples": [ + "This is the quintessential" + ] + }, + { + "sense_num": 3, + "text": "a quintessential element : something that is a typical part or pure example", + "examples": [ + "Marx was the quintessential" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "gregarious", + "slug": "gregarious", + "pos": "adjective", + "pronunciation": "gri-ˈger-ē-əs", + "syllables": "gri-ger-ē-əs", + "first_known_use": "1668", + "etymology": "Latin gregarius of a flock or herd, from greg-, grex flock, herd", + "definitions": [ + { + "sense_num": 1, + "text": "enjoying the company of others : marked by or showing a liking for companionship : sociable", + "examples": [ + "is friendly, outgoing, and gregarious" + ] + }, + { + "sense_num": 2, + "text": "tending to associate with others of one's kind : social", + "examples": [ + "[J.P.] Morgan was attracted to bright, self-possessed women who met him on his own ground, felt at home in society, and shared his gregarious" + ] + }, + { + "sense_num": 3, + "text": "of or relating to a social group", + "examples": [ + "… the gregarious" + ] + }, + { + "sense_num": 4, + "text": "growing in a cluster or a colony", + "examples": [ + "As it is a night of many parties, the more social, the more gregarious" + ] + }, + { + "sense_num": 5, + "text": "living in contiguous nests but not forming a true colony", + "examples": [] + }, + { + "sense_num": 6, + "text": "tending to associate with others of one's kind : social", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "meticulous", + "slug": "meticulous", + "pos": "adjective", + "pronunciation": "mə-ˈti-kyə-ləs", + "syllables": "mə-ti-kyə-ləs", + "first_known_use": "1827", + "etymology": "earlier, \"fearful,\" borrowed from Latin metīculōsus, metūculōsus \"timid, apprehensive,\" from metū-, stem of metus \"fear, dread\" (of uncertain origin) + -culōsus (in perīculōsus \"involving danger, perilous\")", + "definitions": [ + { + "sense_num": 1, + "text": "very careful about doing something in an extremely accurate and exact way", + "examples": [ + "He is meticulous", + "… all the things they had thought through so meticulously—fell apart." + ] + }, + { + "sense_num": 2, + "text": "showing or requiring extreme care and attention to detail", + "examples": [ + "keeps meticulous" + ] + }, + { + "sense_num": 3, + "text": "extremely or overly careful in thinking about or dealing with small details", + "examples": [ + "The meticulous" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "ambivalent", + "slug": "ambivalent", + "pos": "adjective", + "pronunciation": "am-ˈbi-və-lənt", + "syllables": "am-bi-və-lənt", + "first_known_use": "1912", + "etymology": "borrowed from German, from ambi- ambi- + -valent, in äquivalent equivalent", + "definitions": [ + { + "sense_num": 1, + "text": "having or showing simultaneous and contradictory attitudes or feelings toward something or someone : characterized by ambivalence", + "examples": [ + "… people whose relationship to their job is ambivalent", + "Americans are deeply ambivalent", + "He spoke ambivalently" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "pragmatic", + "slug": "pragmatic", + "pos": "adjective", + "pronunciation": "prag-ˈma-tik", + "syllables": "prag-ma-tik", + "first_known_use": "circa 1612", + "etymology": "Latin pragmaticus skilled in law or business, from Greek pragmatikos, from pragmat-, pragma deed, from prassein to do — more at practical", + "definitions": [ + { + "sense_num": 1, + "text": "dealing with the problems that exist in a specific situation in a reasonable and logical way instead of depending on ideas and theories : practical as opposed to idealistic", + "examples": [ + "At its core the Marshall Plan was a pragmatic" + ] + }, + { + "sense_num": 2, + "text": "relating to or being in accordance with philosophical pragmatism (see pragmatism", + "examples": [ + "Their role is to translate the vision and mission of the company, identify pitfalls and find pragmatic" + ] + }, + { + "sense_num": 3, + "text": "busy", + "examples": [ + "[Philosopher William] James's embrace of uncertainty goes to the heart of the pragmatic" + ] + }, + { + "sense_num": 4, + "text": "officious", + "examples": [ + "approached the problem pragmatically" + ] + }, + { + "sense_num": 5, + "text": "opinionated", + "examples": [] + }, + { + "sense_num": 6, + "text": "concerned with practical rather than intellectual or artistic matters", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "resilient", + "slug": "resilient", + "pos": "adjective", + "pronunciation": "ri-ˈzil-yənt", + "syllables": "ri-zil-yənt", + "first_known_use": "1674", + "etymology": "Latin resilient-, resiliens, present participle of resilire to jump back, recoil, from re- + salire to leap — more at sally", + "definitions": [ + { + "sense_num": 1, + "text": "characterized or marked by resilience: such as", + "examples": [ + "The tallow tree, an ornamental species introduced by Benjamin Franklin in 1772, can quickly grow to 10 metres and is resilient" + ] + }, + { + "sense_num": 2, + "text": "capable of withstanding shock without permanent deformation or rupture", + "examples": [ + "In this affecting and eloquent account of the Dew family members' attempts to come to terms with the homosexuality of the elder son … Stephen emerges as a remarkably resilient" + ] + }, + { + "sense_num": 3, + "text": "tending to recover from or adjust easily to misfortune or change", + "examples": [ + "Scientists are trying to figure out how the complex structure of such crystals and polymers and their interactions on the molecular level lead to resilient" + ] + }, + { + "sense_num": 4, + "text": "characterized or marked by resilience", + "examples": [ + "Old roses are tough and resilient" + ] + }, + { + "sense_num": 5, + "text": "characterized or marked by resilience", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "eloquent", + "slug": "eloquent", + "pos": "adjective", + "pronunciation": "ˈe-lə-kwənt", + "syllables": "e-lə-kwənt", + "first_known_use": "14th century", + "etymology": "Middle English, borrowed from Anglo-French & Latin; Anglo-French, borrowed from Latin ēloquent-, ēloquens \"capable of speech, expressing oneself fluently,\" from present participle of ēloquī \"to utter, put into words,\" from ē- e- entry 1 + loquī \"to talk, speak,\" probably going back to dialectal Indo-European *tlokw- \"talk,\" whence also Old Irish ad-tluichethar \"(s/he) gives thanks\" (originally with buide \"thanks\" as object, as in atluchedar buidi do Día \"he thanks God\"), do-tluichethar \"(s/he) desires, beseeches, asks,\" Old Church Slavic tlŭk \"interpreter\" (from *tl̥kw-o-)", + "definitions": [ + { + "sense_num": 1, + "text": "marked by forceful and fluent expression", + "examples": [ + "He [H. L. Mencken] relished the vagaries of vernacular speech and paid eloquent" + ] + }, + { + "sense_num": 2, + "text": "vividly or movingly expressive or revealing", + "examples": [ + "Samuel Johnson is palmed off in classrooms as a harmless drudge of a lexicographer, yet open the Dictionary anywhere and find precision and eloquent" + ] + }, + { + "sense_num": 3, + "text": "having or showing clear and forceful expression", + "examples": [ + "There was a burst of applause, and a deep silence which was even more eloquent" + ] + }, + { + "sense_num": 4, + "text": "clearly showing some feeling or meaning", + "examples": [ + "an eloquent speaker" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "benevolent", + "slug": "benevolent", + "pos": "adjective", + "pronunciation": "bə-ˈne-və-lənt", + "syllables": "bə-ne-və-lənt", + "first_known_use": "15th century", + "etymology": "Middle English, from Latin benevolent-, benevolens, from bene + volent-, volens, present participle of velle to wish — more at will", + "definitions": [ + { + "sense_num": 1, + "text": "marked by kindness or generosity : disposed to doing good", + "examples": [ + "In stark contrast to … gold-hoarding dragons of medieval Europe, Chinese dragons were perceived as benevolent" + ] + }, + { + "sense_num": 2, + "text": "organized for the purpose of doing good", + "examples": [ + "In post-Civil War America, benevolent" + ] + }, + { + "sense_num": 3, + "text": "marked by or suggestive of goodwill", + "examples": [ + "The sky above was blue, the whole scene lit by a bright benevolent" + ] + }, + { + "sense_num": 4, + "text": "having a desire to do good : kindly", + "examples": [ + "Trees that size are like whales, sort of benevolent" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "candor", + "slug": "candor", + "pos": "noun", + "pronunciation": "ˈkan-dər", + "syllables": "kan-dər", + "first_known_use": "14th century", + "etymology": "borrowed from French & Latin; French candeur, borrowed from Latin candōr-, candor \"brightness, radiance, whiteness, disposition to think well (of),\" noun derivative in -ōr- corresponding to candēre \"to shine, be white,\" candidus \"bright, white\" — more at candid entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "the quality of being open, honest, and sincere : forthrightness", + "examples": [ + "I appreciate your candor" + ] + }, + { + "sense_num": 2, + "text": "freedom from prejudice or malice : fairness", + "examples": [ + "spoke with candor" + ] + }, + { + "sense_num": 3, + "text": "brightness", + "examples": [ + "… a rare moment of candor" + ] + }, + { + "sense_num": 4, + "text": "unstained purity", + "examples": [ + "… criticised with a severity not altogether sanctioned by candor" + ] + }, + { + "sense_num": 5, + "text": "kindliness", + "examples": [] + }, + { + "sense_num": 6, + "text": "sincere and honest expression : frankness", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "diligent", + "slug": "diligent", + "pos": "adjective", + "pronunciation": "ˈdi-lə-jənt", + "syllables": "di-lə-jənt", + "first_known_use": "14th century", + "etymology": "Middle English, from Anglo-French, from Latin diligent-, diligens, from present participle of diligere to esteem, love, from di- (from dis- apart) + legere to select — more at legend", + "definitions": [ + { + "sense_num": 1, + "text": "characterized by steady, earnest, and energetic effort : painstaking", + "examples": [ + "Many hours of diligent", + "Even the most diligent" + ] + }, + { + "sense_num": 2, + "text": "showing steady and earnest care and effort : painstaking", + "examples": [ + "Even those who are diligent", + "The American intelligence community's single greatest failing is its lack of good \"humint\"—human intelligence, the dirty, diligent" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "empathy", + "slug": "empathy", + "pos": "noun", + "pronunciation": "ˈem-pə-thē", + "syllables": "em-pə-thē", + "first_known_use": "1909", + "etymology": "Greek empatheia, literally, passion, from empathēs emotional, from em- + pathos feelings, emotion — more at pathos", + "definitions": [ + { + "sense_num": 1, + "text": "the action of understanding, being aware of, being sensitive to, and vicariously experiencing the feelings, thoughts, and experience of another", + "examples": [ + "Lead by example and demonstrate the power of empathy" + ] + }, + { + "sense_num": 2, + "text": "the capacity for this", + "examples": [ + "There's no love in this character, no hidden empathy" + ] + }, + { + "sense_num": 3, + "text": "the act of imagining one's ideas, feelings, or attitudes as fully inhabiting something observed (such as a work of art or natural occurrence) : the imaginative projection (see projection", + "examples": [ + "A little politeness, respect and empathy" + ] + }, + { + "sense_num": 4, + "text": "a being aware of and sharing another person's feelings, experiences, and emotions", + "examples": [ + "Seen from the protagonists' worldview, the film becomes an earnest call for empathy" + ] + }, + { + "sense_num": 5, + "text": "the ability for this", + "examples": [] + }, + { + "sense_num": 6, + "text": "the imaginative projection of a subjective state into an object so that the object appears to be infused with it", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "fortitude", + "slug": "fortitude", + "pos": "noun", + "pronunciation": "ˈfȯr-tə-ˌtüd", + "syllables": "fȯr-tə-tüd", + "first_known_use": "12th century", + "etymology": "Middle English, from Latin fortitudin-, fortitudo, from fortis — see fortify", + "definitions": [ + { + "sense_num": 1, + "text": "strength of mind that enables a person to encounter danger or bear pain or adversity with courage", + "examples": [ + "… everyone in the family was succored by Elizabeth's fortitude" + ] + }, + { + "sense_num": 2, + "text": "strength", + "examples": [ + "But now Frum, by having the fortitude" + ] + }, + { + "sense_num": 3, + "text": "strength of mind that enables a person to meet danger or bear pain or hardship with courage", + "examples": [ + "He learned that war was a hurly-burly of violence in which men prevailed through imagination and the fortitude" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "gratitude", + "slug": "gratitude", + "pos": "noun", + "pronunciation": "ˈgra-tə-ˌtüd", + "syllables": "gra-tə-tüd", + "first_known_use": "1565", + "etymology": "Middle English, from Anglo-French or Medieval Latin; Anglo-French, from Medieval Latin gratitudo, from Latin gratus grateful", + "definitions": [ + { + "sense_num": 1, + "text": "the state of being grateful : thankfulness", + "examples": [ + "expressed gratitude" + ] + }, + { + "sense_num": 2, + "text": "the state of being grateful", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "humble", + "slug": "humble", + "pos": "adjective", + "pronunciation": "ˈhəm-bəl", + "syllables": "həm-bəl", + "first_known_use": "13th century", + "etymology": "Middle English, from Anglo-French, from Latin humilis low, humble, from humus earth; akin to Greek chthōn earth, chamai on the ground", + "definitions": [ + { + "sense_num": 1, + "text": "not proud or haughty : not arrogant or assertive", + "examples": [ + "She would not come closer to me, as much as I thought she wished to, hungering not for anything like love but for plain, humble" + ] + }, + { + "sense_num": 2, + "text": "reflecting, expressing, or offered in a spirit of deference or submission", + "examples": [ + "Women are the organizing soft-centered socialists, the nice people, the sugar-and-spice lot, identifying with the poor and humble" + ] + }, + { + "sense_num": 3, + "text": "ranking low in a hierarchy or scale : insignificant", + "examples": [ + "Cuba's reliance on tourism is a somewhat humbling" + ] + }, + { + "sense_num": 4, + "text": "not costly or luxurious", + "examples": [ + "… audiences loved to see villains punished and arrogant young men humbled" + ] + }, + { + "sense_num": 5, + "text": "to make (someone) humble (see humble", + "examples": [] + }, + { + "sense_num": 6, + "text": "to destroy the power, independence, or prestige of", + "examples": [] + } + ], + "synonyms": [ + "meek", + "modest", + "unassuming", + "unaffected", + "lowly", + "timid" + ], + "difficulty": "common" + }, + { + "headword": "integrity", + "slug": "integrity", + "pos": "noun", + "pronunciation": "in-ˈte-grə-tē", + "syllables": "in-te-grə-tē", + "first_known_use": "15th century", + "etymology": "Middle English integrite, from Middle French & Latin; Middle French integrité, from Latin integritat-, integritas, from integr-, integer entire", + "definitions": [ + { + "sense_num": 1, + "text": "firm adherence to a code of especially moral or artistic values : incorruptibility", + "examples": [ + "personal/professional/academic integrity" + ] + }, + { + "sense_num": 2, + "text": "an unimpaired condition : soundness", + "examples": [ + "Responsible sharing helps maintain the integrity" + ] + }, + { + "sense_num": 3, + "text": "the quality or state of being complete or undivided : completeness", + "examples": [ + "The earthquake may have damaged the building's structural integrity" + ] + }, + { + "sense_num": 4, + "text": "the condition of being free from damage or defect", + "examples": [ + "That part of the structure does not affect the integrity" + ] + }, + { + "sense_num": 5, + "text": "total honesty and sincerity", + "examples": [] + }, + { + "sense_num": 6, + "text": "the quality or state of being complete or undivided", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "jovial", + "slug": "jovial", + "pos": "adjective", + "pronunciation": "ˈjō-vē-əl", + "syllables": "jō-vē-əl", + "first_known_use": "1592", + "etymology": "", + "definitions": [ + { + "sense_num": 1, + "text": "characterized by good-humored cheerfulness and conviviality : jolly", + "examples": [ + "spent a jovial", + "For, the people who were shovelling away on the housetops were jovial" + ] + }, + { + "sense_num": 2, + "text": "of or relating to Jove", + "examples": [ + "In response, an infuriating wink: Alsana always likes to appear jovial" + ] + }, + { + "sense_num": 3, + "text": "full of or expressing good humor", + "examples": [ + "I felt I was slumming, in my own life. My task was to ward off the drivel … the jovial" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "kindle", + "slug": "kindle", + "pos": "verb (1)", + "pronunciation": "ˈkin-dᵊl", + "syllables": "kin-dᵊl", + "first_known_use": "13th century", + "etymology": "Middle English, probably modification of Old Norse kynda; akin to Old High German cuntesal fire", + "definitions": [ + { + "sense_num": 1, + "text": "to start (a fire) burning : light", + "examples": [ + "using dry twigs to kindle" + ] + }, + { + "sense_num": 2, + "text": "to stir up : arouse", + "examples": [ + "… animation kindling his pale face." + ] + }, + { + "sense_num": 3, + "text": "to bring into being : start", + "examples": [ + "waiting for the fire to kindle" + ] + }, + { + "sense_num": 4, + "text": "to cause to glow : illuminate", + "examples": [ + "… their mutual resentment again kindled" + ] + }, + { + "sense_num": 5, + "text": "to catch fire : begin to burn", + "examples": [] + }, + { + "sense_num": 6, + "text": "to flare up", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "luminous", + "slug": "luminous", + "pos": "adjective", + "pronunciation": "ˈlü-mə-nəs", + "syllables": "lü-mə-nəs", + "first_known_use": "15th century", + "etymology": "Middle English luminouse, luminose, borrowed from Anglo-French & Latin; Anglo-French luminous, borrowed from Latin lūminōsus \"full of light, dazzling,\" from lūmin-, lūmen \"light, source of light\" + -ōsus -ous — more at lumen", + "definitions": [ + { + "sense_num": 1, + "text": "emitting or reflecting usually steady, suffused, or glowing light", + "examples": [ + "a public square luminous with sunlight" + ] + }, + { + "sense_num": 2, + "text": "of or relating to light or to luminous flux", + "examples": [ + "luminous writing" + ] + }, + { + "sense_num": 3, + "text": "bathed in or exposed to steady light", + "examples": [] + }, + { + "sense_num": 4, + "text": "clear", + "examples": [] + }, + { + "sense_num": 5, + "text": "shining", + "examples": [] + }, + { + "sense_num": 6, + "text": "giving off light : shining", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "nostalgia", + "slug": "nostalgia", + "pos": "noun", + "pronunciation": "nä-ˈstal-jə", + "syllables": "nä-stal-jə", + "first_known_use": "1756", + "etymology": "borrowed from New Latin, from Greek nóstos \"return, homecoming\" (nominal derivative, with o-ablaut and the suffix -to-, from the base of néomai, neîsthai \"to come/go [home, back], return\") + -o- -o- + -algia -algia; néomai going back to the Indo-European verbal base *nes- \"escape danger, return safely,\" whence also Germanic *nesan- \"to be saved, return safely\" (whence Old English nesan, genesan \"to be saved, survive\" [strong verb class V], Old Saxon ginesan \"to be saved, convalesce,\" Old High German, \"to recover, be saved,\" Gothic ganisan \"to be saved\"), Sanskrit násate \"approaches, resorts to someone, joins\"; from a causative stem *nos-éi̯e- Germanic *nazjan-, whence Old English nerian \"to save, preserve,\" Old Frisian nera \"to save, nourish, Old Saxon nerian \"to rescue, redeem, nourish,\" Old High German nerien, nerren \"to nourish, support, save, heal,\" Gothic nasjan \"to save, heal\"; and from lengthened grade *nōzjan- Old Icelandic nœra \"to refresh, nourish\"", + "definitions": [ + { + "sense_num": 1, + "text": "a sad pleasure experienced in recalling what no longer exists : a wistful or sentimental yearning for a return to or the return of some real or romanticized past period or some irrecoverable past condition or setting", + "examples": [ + "They were filled with nostalgia" + ] + }, + { + "sense_num": 2, + "text": "something that evokes nostalgia", + "examples": [ + "His family once worked at the local slaughterhouse, but their jobs have been automated into oblivion, leaving them with nothing but nostalgia" + ] + }, + { + "sense_num": 3, + "text": "the state of being homesick : homesickness", + "examples": [ + "The play is also full of nostalgia" + ] + }, + { + "sense_num": 4, + "text": "a longing for something past", + "examples": [ + "A wave of nostalgia" + ] + }, + { + "sense_num": 5, + "text": "the state of being homesick", + "examples": [] + }, + { + "sense_num": 6, + "text": "a wistful or excessively sentimental sometimes abnormal yearning for return to or of some past period or irrecoverable condition", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "optimism", + "slug": "optimism", + "pos": "noun", + "pronunciation": "ˈäp-tə-ˌmi-zəm", + "syllables": "äp-tə-mi-zəm", + "first_known_use": "1759", + "etymology": "borrowed from French optimisme, from Latin optimum \"the best, optimum\" + French -isme -ism", + "definitions": [ + { + "sense_num": 1, + "text": "a doctrine that this world is the best possible world", + "examples": [ + "expressed optimism" + ] + }, + { + "sense_num": 2, + "text": "an inclination to put the most favorable construction upon actions and events or to anticipate the best possible outcome", + "examples": [] + }, + { + "sense_num": 3, + "text": "a habit of expecting everything to turn out for the best", + "examples": [] + }, + { + "sense_num": 4, + "text": "an inclination to put the most favorable construction upon actions and events or to anticipate the best possible outcome", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "perseverance", + "slug": "perseverance", + "pos": "noun", + "pronunciation": "ˌpər-sə-ˈvir-ən(t)s", + "syllables": "pər-sə-vir-ən(t)s", + "first_known_use": "14th century", + "etymology": "Middle English perseveraunce, borrowed from Anglo-French parseverance, parsevrance, borrowed from Latin persevērantia, noun derivative of persevērant-, persevērans \"persisting in a course of action, steadfast,\" from present participle of persevērāre \"to persist in a course of action or an attitude in spite of opposition, keep on\" — more at persevere", + "definitions": [ + { + "sense_num": 1, + "text": "continued effort to do or achieve something despite difficulties, failure, or opposition : the action or condition or an instance of persevering : steadfastness", + "examples": [ + "His perseverance", + "Aaliyah and her artistry live on as a symbol of perseverance" + ] + }, + { + "sense_num": 2, + "text": "the action, state, or an instance of persevering", + "examples": [ + "The road to the American dream was supposed to be built on perseverance", + "The great international collectors and curators, once celebrated for their perceptiveness and perseverance" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "wisdom", + "slug": "wisdom", + "pos": "noun (1)", + "pronunciation": "ˈwiz-dəm", + "syllables": "wiz-dəm", + "first_known_use": "the 12th century", + "etymology": "Middle English, from Old English wīsdōm, from wīs wise", + "definitions": [ + { + "sense_num": 1, + "text": "ability to discern inner qualities and relationships : insight", + "examples": [ + "… challenges what has become accepted wisdom" + ] + }, + { + "sense_num": 2, + "text": "good sense : judgment", + "examples": [] + }, + { + "sense_num": 3, + "text": "generally accepted belief", + "examples": [] + }, + { + "sense_num": 4, + "text": "accumulated philosophical or scientific learning : knowledge", + "examples": [] + }, + { + "sense_num": 5, + "text": "a wise attitude, belief, or course of action", + "examples": [] + }, + { + "sense_num": 6, + "text": "the teachings of the ancient wise men", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "abundant", + "slug": "abundant", + "pos": "adjective", + "pronunciation": "ə-ˈbən-dənt", + "syllables": "ə-bən-dənt", + "first_known_use": "14th century", + "etymology": "Middle English abundaunt, habundaunt, borrowed from Anglo-French abundant, habundant, borrowed from Latin abundant-, abundans, present participle of abundāre \"to abound\"", + "definitions": [ + { + "sense_num": 1, + "text": "existing or occurring in large amounts : ample", + "examples": [ + "a fair and abundant" + ] + }, + { + "sense_num": 2, + "text": "marked by great plenty (as of resources)", + "examples": [ + "an area abundant" + ] + }, + { + "sense_num": 3, + "text": "amply supplied : abounding", + "examples": [ + "flowers blooming abundantly" + ] + }, + { + "sense_num": 4, + "text": "existing in or possessing abundance : abounding", + "examples": [ + "facts that are abundantly" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "courage", + "slug": "courage", + "pos": "noun", + "pronunciation": "ˈkər-ij", + "syllables": "kər-ij", + "first_known_use": "14th century", + "etymology": "Middle English corage, from Anglo-French curage, from quer, coer heart, from Latin cor — more at heart", + "definitions": [ + { + "sense_num": 1, + "text": "mental or moral strength to venture, persevere, and withstand danger, fear, or difficulty", + "examples": [ + "A new friend … helps her find the courage", + "In the end, the movie doesn't have the courage" + ] + }, + { + "sense_num": 2, + "text": "strength of mind to carry on in spite of danger or difficulty", + "examples": [ + "Lina provides the truest example in the play of how to live independently, with courage", + "Eunice Kennedy Shriver … didn't buy into the propaganda of her day that women had to be soft and submissive. That took courage" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "curiosity", + "slug": "curiosity", + "pos": "noun", + "pronunciation": "ˌkyu̇r-ē-ˈä-s(ə-)tē", + "syllables": "kyu̇r-ē-ä-s(ə-)tē", + "first_known_use": "14th century", + "etymology": "see curious", + "definitions": [ + { + "sense_num": 1, + "text": "desire to know:", + "examples": [ + "The construction inside their house aroused the curiosity" + ] + }, + { + "sense_num": 2, + "text": "inquisitive interest in others' concerns : nosiness", + "examples": [ + "intellectual curiosity" + ] + }, + { + "sense_num": 3, + "text": "interest leading to inquiry", + "examples": [ + "Her natural curiosity" + ] + }, + { + "sense_num": 4, + "text": "undue nicety or fastidiousness", + "examples": [ + "Tobacco was once regarded as a curiosity" + ] + }, + { + "sense_num": 5, + "text": "one that arouses interest especially for uncommon or exotic characteristics", + "examples": [] + }, + { + "sense_num": 6, + "text": "an unusual knickknack : curio", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "endeavor", + "slug": "endeavor", + "pos": "verb", + "pronunciation": "in-ˈde-vər", + "syllables": "in-de-vər", + "first_known_use": "15th century", + "etymology": "Middle English endeveren to exert oneself, from en- + dever duty — more at devoir", + "definitions": [ + { + "sense_num": 1, + "text": "to seriously or continually try (to do something)", + "examples": [ + "will endeavor" + ] + }, + { + "sense_num": 2, + "text": "to strive to achieve or reach", + "examples": [ + "\"… I have always endeavored" + ] + }, + { + "sense_num": 3, + "text": "to work with set purpose", + "examples": [ + "… he endeavored" + ] + }, + { + "sense_num": 4, + "text": "serious determined effort", + "examples": [ + "[James] Baldwin frequently endeavored" + ] + }, + { + "sense_num": 5, + "text": "activity directed toward a goal : enterprise", + "examples": [] + }, + { + "sense_num": 6, + "text": "to make an effort : try", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "flourish", + "slug": "flourish", + "pos": "verb", + "pronunciation": "ˈflər-ish", + "syllables": "flər-ish", + "first_known_use": "14th century", + "etymology": "Middle English florisshen \"to put forth flowers, bloom, grow luxuriantly, prosper, brandish (a weapon),\" borrowed from Anglo-French floriss-, stem of florir, flurir \"to bloom, grow abundantly, thrive,\" going back to Vulgar Latin *flōrīre, restructuring of Latin flōrēscere \"to begin to flower, increase in vigor,\" inchoative derivative of flōrēre \"to bloom, prosper, be at the peak of one's powers,\" stative verbal derivative of flōr-, flōs flower entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "to grow luxuriantly : thrive", + "examples": [ + "a flourishing" + ] + }, + { + "sense_num": 2, + "text": "to achieve success : prosper", + "examples": [ + "The artist flourished" + ] + }, + { + "sense_num": 3, + "text": "to be in a state of activity or production", + "examples": [ + "The company flourished" + ] + }, + { + "sense_num": 4, + "text": "to reach a height of development or influence", + "examples": [ + "Dressed as a pirate, he entered the stage flourishing" + ] + }, + { + "sense_num": 5, + "text": "to make bold and sweeping gestures", + "examples": [] + }, + { + "sense_num": 6, + "text": "to wield with dramatic gestures : brandish", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "harmony", + "slug": "harmony", + "pos": "noun", + "pronunciation": "ˈhär-mə-nē", + "syllables": "här-mə-nē", + "first_known_use": "14th century", + "etymology": "Middle English armony, from Anglo-French armonie, from Latin harmonia, from Greek, joint, harmony, from harmos joint — more at arm", + "definitions": [ + { + "sense_num": 1, + "text": "the combination of simultaneous musical notes in a chord", + "examples": [ + "She taught him how to sing harmony" + ] + }, + { + "sense_num": 2, + "text": "the structure of music with respect to the composition and progression of chords", + "examples": [ + "a song with complicated harmonies and rhythms" + ] + }, + { + "sense_num": 3, + "text": "the science of the structure, relation, and progression of chords", + "examples": [ + "a painting exhibiting harmony" + ] + }, + { + "sense_num": 4, + "text": "pleasing arrangement of parts : congruence", + "examples": [ + "The standard we sanction today is in harmony" + ] + }, + { + "sense_num": 5, + "text": "agreement", + "examples": [] + }, + { + "sense_num": 6, + "text": "internal calm : tranquility", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "innovate", + "slug": "innovate", + "pos": "verb", + "pronunciation": "ˈi-nə-ˌvāt", + "syllables": "i-nə-vāt", + "first_known_use": "1548", + "etymology": "Latin innovatus, past participle of innovare, from in- + novus new — more at new", + "definitions": [ + { + "sense_num": 1, + "text": "to make changes : do something in a new way", + "examples": [ + "The dictates of my father were … not to be altered, innovated, or even discussed …" + ] + }, + { + "sense_num": 2, + "text": "to introduce as or as if new", + "examples": [ + "innovate a new website" + ] + }, + { + "sense_num": 3, + "text": "to effect a change in", + "examples": [] + }, + { + "sense_num": 4, + "text": "to introduce something new", + "examples": [] + }, + { + "sense_num": 5, + "text": "do something in a new way", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "journey", + "slug": "journey", + "pos": "noun", + "pronunciation": "ˈjər-nē", + "syllables": "jər-nē", + "first_known_use": "13th century", + "etymology": "Middle English, from Anglo-French jurnee day, day's journey, from jur day, from Late Latin diurnum, from Latin, neuter of diurnus of the day — more at journal entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "something suggesting travel or passage from one place to another", + "examples": [ + "a three-day journey" + ] + }, + { + "sense_num": 2, + "text": "an act or instance of traveling from one place to another : trip", + "examples": [ + "going on a long journey" + ] + }, + { + "sense_num": 3, + "text": "a day's travel", + "examples": [] + }, + { + "sense_num": 4, + "text": "to go on a journey : travel", + "examples": [] + }, + { + "sense_num": 5, + "text": "to travel over or through", + "examples": [] + }, + { + "sense_num": 6, + "text": "travel from one place to another", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "knowledge", + "slug": "knowledge", + "pos": "noun", + "pronunciation": "ˈnä-lij", + "syllables": "nä-lij", + "first_known_use": "14th century", + "etymology": "Middle English knowlege, from knowlechen to acknowledge, irregular from knowen", + "definitions": [ + { + "sense_num": 1, + "text": "information, understanding, or skill that you get from experience or education", + "examples": [ + "a thirst/quest for knowledge" + ] + }, + { + "sense_num": 2, + "text": "acquaintance with or understanding of a science, art, or technique", + "examples": [ + "They have little/no/some knowledge" + ] + }, + { + "sense_num": 3, + "text": "the fact or condition of being aware of something", + "examples": [ + "has little knowledge" + ] + }, + { + "sense_num": 4, + "text": "the range of one's information or understanding", + "examples": [ + "The decision was made without my knowledge" + ] + }, + { + "sense_num": 5, + "text": "the circumstance or condition of apprehending truth, fact, or reality immediately with the mind or senses : cognition", + "examples": [] + }, + { + "sense_num": 6, + "text": "the fact or condition of having information or of being well educated", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "liberty", + "slug": "liberty", + "pos": "noun", + "pronunciation": "ˈli-bər-tē", + "syllables": "li-bər-tē", + "first_known_use": "14th century", + "etymology": "Middle English liberte, borrowed from Anglo-French & Latin; Anglo-French liberté, borrowed from Latin lībertāt-, lībertās \"freedom,\" from līber \"free\" + -tāt-, -tās -ty — more at liberal entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "the quality or state of being free:", + "examples": [ + "We don't have the liberty" + ] + }, + { + "sense_num": 2, + "text": "the power to do as one pleases", + "examples": [ + "… when the state detains a person, it takes away their liberty" + ] + }, + { + "sense_num": 3, + "text": "freedom from physical restraint", + "examples": [ + "The new memorial … honors the nearly five million Americans who fought for liberty" + ] + }, + { + "sense_num": 4, + "text": "freedom from arbitrary or despotic (see despot", + "examples": [ + "our constitutional right to liberty" + ] + }, + { + "sense_num": 5, + "text": "the enjoyment of the same social, political, or economic rights and privileges enjoyed by others in a society free of arbitrary or unreasonable limitation or interference", + "examples": [] + }, + { + "sense_num": 6, + "text": "freedom from being held in slavery", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "wander", + "slug": "wander", + "pos": "verb", + "pronunciation": "ˈwän-dər", + "syllables": "wän-dər", + "first_known_use": "the 12th century", + "etymology": "Middle English wandren, from Old English wandrian; akin to Middle High German wandern to wander, Old English windan to wind, twist", + "definitions": [ + { + "sense_num": 1, + "text": "to move about without a fixed course, aim, or goal", + "examples": [ + "his mind wandered" + ] + }, + { + "sense_num": 2, + "text": "to go idly about : ramble", + "examples": [ + "wandered away" + ] + }, + { + "sense_num": 3, + "text": "to follow a winding course : meander", + "examples": [ + "her mind wandered" + ] + }, + { + "sense_num": 4, + "text": "to go astray (as from a course) : stray", + "examples": [] + }, + { + "sense_num": 5, + "text": "to go astray morally : err", + "examples": [] + }, + { + "sense_num": 6, + "text": "to lose normal mental contact : stray in thought", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "vivid", + "slug": "vivid", + "pos": "adjective", + "pronunciation": "ˈvi-vəd", + "syllables": "vi-vəd", + "first_known_use": "1634", + "etymology": "Latin vividus, from vivere to live — more at quick entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "very strong : very high in chroma", + "examples": [ + "a vivid sketch of the children" + ] + }, + { + "sense_num": 2, + "text": "having the appearance of vigorous life or freshness : lively", + "examples": [ + "a vivid description" + ] + }, + { + "sense_num": 3, + "text": "producing a strong or clear impression on the senses : sharp", + "examples": [ + "a vivid imagination" + ] + }, + { + "sense_num": 4, + "text": "producing distinct mental images", + "examples": [] + }, + { + "sense_num": 5, + "text": "acting clearly and vigorously", + "examples": [] + }, + { + "sense_num": 6, + "text": "very strong or bright", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "tranquil", + "slug": "tranquil", + "pos": "adjective", + "pronunciation": "ˈtraŋ-kwəl", + "syllables": "traŋ-kwəl", + "first_known_use": "15th century", + "etymology": "Middle English tranquill, from Latin tranquillus", + "definitions": [ + { + "sense_num": 1, + "text": "free from agitation of mind or spirit", + "examples": [] + }, + { + "sense_num": 2, + "text": "free from disturbance or turmoil", + "examples": [] + }, + { + "sense_num": 3, + "text": "unvarying in aspect : steady", + "examples": [] + }, + { + "sense_num": 4, + "text": "free from disturbance or turmoil : quiet", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "sincere", + "slug": "sincere", + "pos": "adjective", + "pronunciation": "sin-ˈsir", + "syllables": "sin-sir", + "first_known_use": "1533", + "etymology": "Middle French, from Latin sincerus whole, pure, genuine, probably from sem- one + -cerus (akin to Latin crescere to grow) — more at same, crescent", + "definitions": [ + { + "sense_num": 1, + "text": "free of dissimulation : honest", + "examples": [ + "a sincere friend" + ] + }, + { + "sense_num": 2, + "text": "free from adulteration : pure", + "examples": [ + "a sincere interest in painting" + ] + }, + { + "sense_num": 3, + "text": "marked by genuineness : true", + "examples": [] + }, + { + "sense_num": 4, + "text": "trustworthy", + "examples": [] + }, + { + "sense_num": 5, + "text": "genuine", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "profound", + "slug": "profound", + "pos": "adjective", + "pronunciation": "prə-ˈfau̇nd", + "syllables": "prə-fau̇nd", + "first_known_use": "14th century", + "etymology": "Middle English, from Anglo-French parfunt, profond deep, from Latin profundus, from pro- before + fundus bottom — more at pro-, bottom", + "definitions": [ + { + "sense_num": 1, + "text": "having intellectual depth and insight", + "examples": [ + "Her books offer profound" + ] + }, + { + "sense_num": 2, + "text": "difficult to fathom or understand", + "examples": [ + "Sometimes Greek authors wrote in the mode of pure eroticism, sometimes in terms of profound" + ] + }, + { + "sense_num": 3, + "text": "characterized by intensity of feeling or quality", + "examples": [ + "Their paintings have had a profound" + ] + }, + { + "sense_num": 4, + "text": "very great or significant", + "examples": [ + "Technology has made profound" + ] + }, + { + "sense_num": 5, + "text": "all encompassing : complete", + "examples": [] + }, + { + "sense_num": 6, + "text": "extending far below the surface", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "elated", + "slug": "elated", + "pos": "adjective", + "pronunciation": "i-ˈlā-təd", + "syllables": "i-lā-təd", + "first_known_use": "1615", + "etymology": "", + "definitions": [ + { + "sense_num": 1, + "text": "marked by high spirits : exultant", + "examples": [ + "Alec felt elated", + "But I was elated", + "Inside the … hall, the combination of national crisis and imminent electoral victory creates an atmosphere at once pensive and elated" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "ecstatic", + "slug": "ecstatic", + "pos": "adjective", + "pronunciation": "ek-ˈsta-tik", + "syllables": "ek-sta-tik", + "first_known_use": "1590", + "etymology": "borrowed from Medieval Latin ecstaticus, extaticus, borrowed from Greek ekstatikós \"inclined to depart from, out of one's senses, causing mental disorder,\" from eksta-, stem of existánai \"to displace, confound,\" exístasthai \"to be astonished, lose consciousness\" + -t-, verbal adjective suffix (after statós \"standing\") + -ikos -ic entry 1 — more at ecstasy", + "definitions": [ + { + "sense_num": 1, + "text": "of, relating to, or marked by ecstasy", + "examples": [ + "A few religious denominations—Pentecostalism, for example—still offer a collective ecstatic" + ] + }, + { + "sense_num": 2, + "text": "one that is subject to ecstasies", + "examples": [ + "… in dietary terms we are veritable troglodytes (which, speaking personally, is all right by me). I think this explains a lot, not least my expanding sense of dismay as the waiter bombarded us with ecstatic" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "content", + "slug": "content", + "pos": "noun (1)", + "pronunciation": "ˈkän-ˌtent", + "syllables": "kän-tent", + "first_known_use": "15th century", + "etymology": "Middle English, borrowed from Anglo-French & Medieval Latin; Anglo-French, borrowed from Medieval Latin contentum (usually in plural contenta), noun derivative from neuter past participle of Latin continēre \"to hold together, restrain, have as contents\" — more at contain", + "definitions": [ + { + "sense_num": 1, + "text": "something contained", + "examples": [ + "the jar's contents" + ] + }, + { + "sense_num": 2, + "text": "the topics or matter treated in a written work", + "examples": [ + "the drawer's contents" + ] + }, + { + "sense_num": 3, + "text": "the principal substance (such as written matter, images, music, etc.) offered by a website or on social media", + "examples": [ + "emptied his pocket of its contents" + ] + }, + { + "sense_num": 4, + "text": "substance", + "examples": [ + "a summary of the book's content" + ] + }, + { + "sense_num": 5, + "text": "meaning", + "examples": [] + }, + { + "sense_num": 6, + "text": "the events, physical detail, and information in a work of art compare form", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "cheerful", + "slug": "cheerful", + "pos": "adjective", + "pronunciation": "ˈchir-fəl", + "syllables": "chir-fəl", + "first_known_use": "15th century", + "etymology": "see cheer entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "full of good spirits : merry", + "examples": [ + "sunny cheerful" + ] + }, + { + "sense_num": 2, + "text": "ungrudging", + "examples": [ + "a cheerful outlook" + ] + }, + { + "sense_num": 3, + "text": "conducive to cheer : likely to dispel gloom or worry", + "examples": [ + "cheerful obedience" + ] + }, + { + "sense_num": 4, + "text": "full of good spirits", + "examples": [ + "a sunny cheerful room" + ] + }, + { + "sense_num": 5, + "text": "willing", + "examples": [] + }, + { + "sense_num": 6, + "text": "pleasantly bright", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "blissful", + "slug": "blissful", + "pos": "adjective", + "pronunciation": "ˈblis-fəl", + "syllables": "blis-fəl", + "first_known_use": "12th century", + "etymology": "see bliss", + "definitions": [ + { + "sense_num": 1, + "text": "full of, marked by, or causing complete happiness", + "examples": [] + }, + { + "sense_num": 2, + "text": "happily benighted", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "delighted", + "slug": "delighted", + "pos": "adjective", + "pronunciation": "di-ˈlī-təd", + "syllables": "di-lī-təd", + "first_known_use": "1581", + "etymology": "from past participle of delight entry 2", + "definitions": [ + { + "sense_num": 1, + "text": "delightful", + "examples": [ + "was delighted" + ] + }, + { + "sense_num": 2, + "text": "highly pleased", + "examples": [] + }, + { + "sense_num": 3, + "text": "highly pleased : gratified", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "melancholy", + "slug": "melancholy", + "pos": "noun", + "pronunciation": "ˈme-lən-ˌkä-lē", + "syllables": "me-lən-kä-lē", + "first_known_use": "14th century", + "etymology": "Middle English malencolie, melancolie \"black bile, preponderance or excess of black bile, state (as anger or sorrow) produced by excessive black bile,\" borrowed from Anglo-French & Late Latin; Anglo-French malencolie, melencolie, borrowed from Late Latin melancholia (Medieval Latin malencolia, by association with the prefix mal- mal-), borrowed from Greek melancholía, from melan-, athematic variant of melano- melano- + cholḗ \"bile\" + -ia -ia entry 1 — more at gall entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "a state of sadness : depression of spirits : dejection", + "examples": [ + "Mitchell sounds utterly alone in her melancholy" + ] + }, + { + "sense_num": 2, + "text": "a pensive mood", + "examples": [ + "Mordecai let his hands fall, and his head sink in melancholy" + ] + }, + { + "sense_num": 3, + "text": "melancholia", + "examples": [ + "One white arm and hand drooped over the side of the chair, and her whole pose and figure spoke of an absorbing melancholy" + ] + }, + { + "sense_num": 4, + "text": "an abnormal state attributed to an excess of black bile and characterized by irascibility or depression", + "examples": [ + "The boy's soul was steeped in melancholy" + ] + }, + { + "sense_num": 5, + "text": "black bile", + "examples": [] + }, + { + "sense_num": 6, + "text": "suggesting or expressing sadness or depression", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "sorrow", + "slug": "sorrow", + "pos": "noun", + "pronunciation": "ˈsär-(ˌ)ō", + "syllables": "sär-()ō", + "first_known_use": "the 12th century", + "etymology": "Middle English sorow, from Old English sorg; akin to Old High German sorga sorrow", + "definitions": [ + { + "sense_num": 1, + "text": "deep distress, sadness, or regret especially for the loss of someone or something loved", + "examples": [ + "to their great sorrow" + ] + }, + { + "sense_num": 2, + "text": "resultant unhappy or unpleasant state", + "examples": [] + }, + { + "sense_num": 3, + "text": "a cause of grief or sadness", + "examples": [] + }, + { + "sense_num": 4, + "text": "a display of grief or sadness", + "examples": [] + }, + { + "sense_num": 5, + "text": "to feel or express sorrow", + "examples": [] + }, + { + "sense_num": 6, + "text": "sadness felt after a loss (as of something loved)", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "despondent", + "slug": "despondent", + "pos": "adjective", + "pronunciation": "di-ˈspän-dənt", + "syllables": "di-spän-dənt", + "first_known_use": "circa 1699", + "etymology": "Latin despondent-, despondens, present participle of despondēre", + "definitions": [ + { + "sense_num": 1, + "text": "feeling or showing extreme discouragement, dejection, or depression", + "examples": [ + "His colleagues did not care for his despondent", + "Writers who spend much time in universities are likely to grow despondent" + ] + }, + { + "sense_num": 2, + "text": "feeling quite discouraged or depressed : being in very low spirits", + "examples": [ + "The Simpsons' plots are a bit more sophisticated than their Saturday morning counterparts and are occasionally tinged with pathos—as when Homer loses his job at the nuclear-power plant and becomes despondent" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "forlorn", + "slug": "forlorn", + "pos": "adjective", + "pronunciation": "fər-ˈlȯrn", + "syllables": "fər-lȯrn", + "first_known_use": "the 12th century", + "etymology": "Middle English forloren, from Old English, past participle of forlēosan to lose, from for- + lēosan to lose — more at lose", + "definitions": [ + { + "sense_num": 1, + "text": "bereft", + "examples": [ + "left quite forlorn" + ] + }, + { + "sense_num": 2, + "text": "sad and lonely because of isolation or desertion : desolate", + "examples": [ + "Against the forlorn" + ] + }, + { + "sense_num": 3, + "text": "being in poor condition : miserable", + "examples": [ + "There is nothing quite so forlorn" + ] + }, + { + "sense_num": 4, + "text": "nearly hopeless", + "examples": [ + "Like Ozymandias, once king of kings but now two legs of a broken statue in Percy Shelley's desert, the great facade of Union Station in Washington, D.C., stands forlorn" + ] + }, + { + "sense_num": 5, + "text": "feeling sad and lonely especially because of being left alone", + "examples": [] + }, + { + "sense_num": 6, + "text": "nearly hopeless", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "wistful", + "slug": "wistful", + "pos": "adjective", + "pronunciation": "ˈwist-fəl", + "syllables": "wist-fəl", + "first_known_use": "1714", + "etymology": "blend of wishful and obsolete English wistly intently", + "definitions": [ + { + "sense_num": 1, + "text": "full of yearning or desire tinged with melancholy", + "examples": [ + "a wistful look on his face" + ] + }, + { + "sense_num": 2, + "text": "inspiring such yearning", + "examples": [] + }, + { + "sense_num": 3, + "text": "musingly sad : pensive", + "examples": [] + }, + { + "sense_num": 4, + "text": "feeling or showing a timid desire", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "astute", + "slug": "astute", + "pos": "adjective", + "pronunciation": "ə-ˈstüt", + "syllables": "ə-stüt", + "first_known_use": "1565", + "etymology": "Latin astutus, from astus craft", + "definitions": [ + { + "sense_num": 1, + "text": "having or showing shrewdness and an ability to notice and understand things clearly : mentally sharp or clever", + "examples": [ + "… a very astute", + "And finally, even if she had never actually uttered the bon mot that would be famously attributed to her, that if she had two heads, she would risk one in the king's service, could the astute" + ] + }, + { + "sense_num": 2, + "text": "clever in the use of subtlety or strategy : crafty", + "examples": [ + "We thought they were not very intellectually astute" + ] + }, + { + "sense_num": 3, + "text": "having or showing understanding and the skill to make good choices or decisions : wise", + "examples": [ + "He asked astute" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "sagacious", + "slug": "sagacious", + "pos": "adjective", + "pronunciation": "sə-ˈgā-shəs", + "syllables": "sə-gā-shəs", + "first_known_use": "1607", + "etymology": "Latin sagac-, sagax, from sagire to perceive keenly; akin to Latin sagus prophetic — more at seek", + "definitions": [ + { + "sense_num": 1, + "text": "having or showing an ability to understand difficult ideas and situations and to make good decisions : marked by keen and farsighted understanding and judgment : discerning", + "examples": [ + "gratitude for sagacious", + "… he suddenly thrust out his face fiercely, snuffing up the sea air as a sagacious" + ] + }, + { + "sense_num": 2, + "text": "having highly developed sensory perception", + "examples": [ + "The judge was fair and sagacious" + ] + }, + { + "sense_num": 3, + "text": "quick and wise in understanding and judgment", + "examples": [ + "… the standard post-election postmortem, in which the winner is praised for his sagacious" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "shrewd", + "slug": "shrewd", + "pos": "adjective", + "pronunciation": "ˈshrüd", + "syllables": "shrüd", + "first_known_use": "13th century", + "etymology": "Middle English shrewed, from shrewe + -ed entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "having or showing the insight, intelligence, and understanding to make good judgments about practical matters (as in business or finance)", + "examples": [ + "made some shrewd" + ] + }, + { + "sense_num": 2, + "text": "wily", + "examples": [ + "a coach with a shrewd" + ] + }, + { + "sense_num": 3, + "text": "severe", + "examples": [ + "… I got some shrewd" + ] + }, + { + "sense_num": 4, + "text": "sharp", + "examples": [ + "Touch it if you will, it gives out shrewd" + ] + }, + { + "sense_num": 5, + "text": "mischievous", + "examples": [] + }, + { + "sense_num": 6, + "text": "abusive", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "perceptive", + "slug": "perceptive", + "pos": "adjective", + "pronunciation": "pər-ˈsep-tiv", + "syllables": "pər-sep-tiv", + "first_known_use": "1652", + "etymology": "", + "definitions": [ + { + "sense_num": 1, + "text": "responsive to sensory stimuli : discerning", + "examples": [] + }, + { + "sense_num": 2, + "text": "capable of or exhibiting keen perception : observant", + "examples": [] + }, + { + "sense_num": 3, + "text": "characterized by sympathetic understanding or insight", + "examples": [] + }, + { + "sense_num": 4, + "text": "capable of or showing a keen ability to observe and understand", + "examples": [] + }, + { + "sense_num": 5, + "text": "responsive to sensory stimulus", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "ingenious", + "slug": "ingenious", + "pos": "adjective", + "pronunciation": "in-ˈjēn-yəs", + "syllables": "in-jēn-yəs", + "first_known_use": "15th century", + "etymology": "Middle English ingenyous, from Middle French ingenieus, from Latin ingeniosus, from ingenium natural capacity — more at engine entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "having or showing an unusual aptitude for discovering, inventing, or contriving", + "examples": [ + "How many dog-size bathrobes (an ingenious" + ] + }, + { + "sense_num": 2, + "text": "marked by originality, resourcefulness, and cleverness in conception or execution", + "examples": [ + "… spacecraft engineers tried to come up with ways to \"unstick\" the antenna. Those attempts failed, but by ingenious" + ] + }, + { + "sense_num": 3, + "text": "showing or calling for intelligence, aptitude, or discernment", + "examples": [ + "… an ingenious" + ] + }, + { + "sense_num": 4, + "text": "having or showing ingenuity : very clever", + "examples": [ + "an ingenious plan" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "valiant", + "slug": "valiant", + "pos": "adjective", + "pronunciation": "ˈval-yənt", + "syllables": "val-yənt", + "first_known_use": "14th century", + "etymology": "Middle English vailant, valiant, borrowed from Anglo-French vaillant \"worthy, strong, courageous,\" from present participle of valer \"to be of worth,\" going back to Latin valēre \"to have strength, be well\" — more at wield", + "definitions": [ + { + "sense_num": 1, + "text": "possessing or acting with bravery or boldness : courageous", + "examples": [] + }, + { + "sense_num": 2, + "text": "marked by, exhibiting, or carried out with courage or determination : heroic", + "examples": [] + }, + { + "sense_num": 3, + "text": "a valiant person", + "examples": [] + }, + { + "sense_num": 4, + "text": "boldly brave", + "examples": [] + }, + { + "sense_num": 5, + "text": "done with courage : heroic", + "examples": [] + }, + { + "sense_num": 6, + "text": "a valiant person", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "intrepid", + "slug": "intrepid", + "pos": "adjective", + "pronunciation": "in-ˈtre-pəd", + "syllables": "in-tre-pəd", + "first_known_use": "1680", + "etymology": "Latin intrepidus, from in- + trepidus alarmed — more at trepidation", + "definitions": [ + { + "sense_num": 1, + "text": "characterized by resolute fearlessness, fortitude, and endurance", + "examples": [ + "The heroes are intrepid", + "Meanwhile, the intrepid" + ] + }, + { + "sense_num": 2, + "text": "feeling no fear : bold", + "examples": [ + "Author and explorer Dame Freya Stark was one of the most intrepid" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "audacious", + "slug": "audacious", + "pos": "adjective", + "pronunciation": "ȯ-ˈdā-shəs", + "syllables": "ȯ-dā-shəs", + "first_known_use": "1550", + "etymology": "borrowed from Middle French audacieux, from audace \"daring, recklessness\" (borrowed from Latin audācia, from audāc-, audāx \"daring, bold, excessively daring, reckless\" + -ia -ia entry 1) + -ieux -ious; audāx from audēre \"to intend, dare, venture\" (verbal derivative of avidus \"ardent, eager, greedy\") + -āc-, -āx, deverbal suffix denoting habitual or successful performance (probably going back to Indo-European *-eh2, noun ending + *-k-, suffixal formative) — more at avid", + "definitions": [ + { + "sense_num": 1, + "text": "intrepidly daring : adventurous", + "examples": [ + "Whatever made him think his audacious" + ] + }, + { + "sense_num": 2, + "text": "recklessly bold : rash", + "examples": [ + "This is an audacious" + ] + }, + { + "sense_num": 3, + "text": "contemptuous of law, religion, or decorum : insolent", + "examples": [ + "… Morgan Pressel, the top-ranked female amateur in the country, has charted a less audacious" + ] + }, + { + "sense_num": 4, + "text": "marked by originality and verve", + "examples": [ + "… he owns and operates a seductively spacious jazz club. But that's his day job, his cover. He executes his audacious" + ] + }, + { + "sense_num": 5, + "text": "very bold and daring : fearless", + "examples": [] + }, + { + "sense_num": 6, + "text": "showing a lack of proper respect", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "dauntless", + "slug": "dauntless", + "pos": "adjective", + "pronunciation": "ˈdȯnt-ləs", + "syllables": "dȯnt-ləs", + "first_known_use": "1588", + "etymology": "daunt + -less", + "definitions": [ + { + "sense_num": 1, + "text": "incapable of being intimidated or subdued : fearless", + "examples": [] + }, + { + "sense_num": 2, + "text": "fearless", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "stalwart", + "slug": "stalwart", + "pos": "adjective", + "pronunciation": "ˈstȯl-wərt", + "syllables": "stȯl-wərt", + "first_known_use": "15th century", + "etymology": "Middle English, alteration of stalworth, from Old English stǣlwierthe serviceable", + "definitions": [ + { + "sense_num": 1, + "text": "marked by outstanding strength and vigor of body, mind, or spirit", + "examples": [ + "has stalwart common sense" + ] + }, + { + "sense_num": 2, + "text": "a stalwart person", + "examples": [ + "a stalwart team of rescuers" + ] + }, + { + "sense_num": 3, + "text": "an unwavering partisan", + "examples": [] + }, + { + "sense_num": 4, + "text": "marked by outstanding strength and vigor of mind, body, or spirit", + "examples": [] + }, + { + "sense_num": 5, + "text": "a stalwart person", + "examples": [] + }, + { + "sense_num": 6, + "text": "a loyal supporter (as in politics)", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "serene", + "slug": "serene", + "pos": "adjective", + "pronunciation": "sə-ˈrēn", + "syllables": "sə-rēn", + "first_known_use": "15th century", + "etymology": "Middle English, from Latin serenus clear, cloudless, untroubled", + "definitions": [ + { + "sense_num": 1, + "text": "marked by or suggestive of utter calm and unruffled repose or quietude", + "examples": [ + "The moon, serene" + ] + }, + { + "sense_num": 2, + "text": "clear and free of storms or unpleasant change", + "examples": [ + "His Serene Highness" + ] + }, + { + "sense_num": 3, + "text": "shining bright and steady", + "examples": [ + "Between the two Azorean blue belfries of Our Lady of Good Voyage Church, a serene" + ] + }, + { + "sense_num": 4, + "text": "august", + "examples": [ + "And Breeders' Cup day was anything but serene" + ] + }, + { + "sense_num": 5, + "text": "a serene condition or expanse (as of sky, sea, or light)", + "examples": [] + }, + { + "sense_num": 6, + "text": "serenity", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "placid", + "slug": "placid", + "pos": "adjective", + "pronunciation": "ˈpla-səd", + "syllables": "pla-səd", + "first_known_use": "1614", + "etymology": "Latin placidus, from placēre to please — more at please", + "definitions": [ + { + "sense_num": 1, + "text": "serenely free of interruption or disturbance", + "examples": [ + "a placid disposition" + ] + }, + { + "sense_num": 2, + "text": "complacent", + "examples": [] + }, + { + "sense_num": 3, + "text": "peacefully free of interruption or disturbance : peaceful", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "composed", + "slug": "composed", + "pos": "adjective", + "pronunciation": "kəm-ˈpōzd", + "syllables": "kəm-pōzd", + "first_known_use": "1607", + "etymology": "see compose", + "definitions": [ + { + "sense_num": 1, + "text": "free from agitation : calm", + "examples": [ + "They tried to remain composed" + ] + }, + { + "sense_num": 2, + "text": "self-possessed", + "examples": [] + }, + { + "sense_num": 3, + "text": "being calm and in control : self-possessed", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "unflappable", + "slug": "unflappable", + "pos": "adjective", + "pronunciation": "ˌən-ˈfla-pə-bəl", + "syllables": "ən-fla-pə-bəl", + "first_known_use": "1954", + "etymology": "un- entry 1 + flap entry 1 (state of excitement) + -able", + "definitions": [ + { + "sense_num": 1, + "text": "marked by assurance and self-control", + "examples": [ + "has a reputation for being unflappable" + ] + }, + { + "sense_num": 2, + "text": "not easily upset or panicked : unusually calm", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "exquisite", + "slug": "exquisite", + "pos": "adjective", + "pronunciation": "ek-ˈskwi-zət", + "syllables": "ek-skwi-zət", + "first_known_use": "15th century", + "etymology": "Middle English exquisit, from Latin exquisitus, past participle of exquirere to search out, from ex- + quaerere to seek", + "definitions": [ + { + "sense_num": 1, + "text": "marked by flawless craftsmanship or by beautiful, ingenious, delicate, or elaborate execution", + "examples": [ + "My dream was … to play smoky ballads of exquisite" + ] + }, + { + "sense_num": 2, + "text": "marked by the quality or power of finely distinguishing, by deep sensitivity, or by subtle understanding", + "examples": [ + "Also on view is one of Poussin's first classical landscape paintings; its exquisite" + ] + }, + { + "sense_num": 3, + "text": "accomplished", + "examples": [ + "an exquisite lacy handkerchief" + ] + }, + { + "sense_num": 4, + "text": "pleasing through beauty, fitness, or perfection", + "examples": [ + "an exquisite painting" + ] + }, + { + "sense_num": 5, + "text": "acute", + "examples": [] + }, + { + "sense_num": 6, + "text": "having uncommon or esoteric appeal", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "resplendent", + "slug": "resplendent", + "pos": "adjective", + "pronunciation": "ri-ˈsplen-dənt", + "syllables": "ri-splen-dənt", + "first_known_use": "15th century", + "etymology": "Middle English, from Latin resplendent-, resplendens, present participle of resplendēre to shine back, from re- + splendēre to shine — more at splendid", + "definitions": [ + { + "sense_num": 1, + "text": "shining brilliantly : characterized by a glowing splendor", + "examples": [ + "Meadows resplendent", + "resplendent in a new red coat" + ] + }, + { + "sense_num": 2, + "text": "so bright as to seem to glow", + "examples": [ + "fields resplendent with flowers" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "sublime", + "slug": "sublime", + "pos": "verb", + "pronunciation": "sə-ˈblīm", + "syllables": "sə-blīm", + "first_known_use": "14th century", + "etymology": "Middle English, from Middle French sublimer, from Medieval Latin sublimare to refine, sublime, from Latin, to elevate, from sublimis", + "definitions": [ + { + "sense_num": 1, + "text": "to cause to pass directly from the solid to the vapor state and condense back to solid form", + "examples": [ + "… models indicate that frost in most of the southern hemisphere is currently subliming" + ] + }, + { + "sense_num": 2, + "text": "to elevate or exalt especially in dignity or honor", + "examples": [ + "The cursory remarks of the large-minded stranger, of whom he knew absolutely nothing beyond a commonplace name, were sublimed" + ] + }, + { + "sense_num": 3, + "text": "to render finer (as in purity or excellence)", + "examples": [ + "New Orleans is not just a list of attractions or restaurants or ceremonies, no matter how sublime" + ] + }, + { + "sense_num": 4, + "text": "to convert (something inferior) into something of higher worth", + "examples": [ + "Judging by the satisfied look that settles on both men's faces, the meal was sublime" + ] + }, + { + "sense_num": 5, + "text": "to pass directly from the solid to the vapor state", + "examples": [] + }, + { + "sense_num": 6, + "text": "lofty, grand, or exalted in thought, expression, or manner", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "radiant", + "slug": "radiant", + "pos": "adjective", + "pronunciation": "ˈrā-dē-ənt", + "syllables": "rā-dē-ənt", + "first_known_use": "15th century", + "etymology": "", + "definitions": [ + { + "sense_num": 1, + "text": "radiating rays or reflecting beams of light", + "examples": [ + "a radiant jewel" + ] + }, + { + "sense_num": 2, + "text": "vividly bright and shining : glowing", + "examples": [ + "a radiant smile" + ] + }, + { + "sense_num": 3, + "text": "marked by or expressive of love, confidence, or happiness", + "examples": [] + }, + { + "sense_num": 4, + "text": "emitted or transmitted by radiation", + "examples": [] + }, + { + "sense_num": 5, + "text": "emitting or relating to radiant heat", + "examples": [] + }, + { + "sense_num": 6, + "text": "something that radiates: such as", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "petrichor", + "slug": "petrichor", + "pos": "noun", + "pronunciation": "ˈpe-trə-ˌkȯr", + "syllables": "pe-trə-kȯr", + "first_known_use": "1964", + "etymology": "petr(o)- + ichor", + "definitions": [ + { + "sense_num": 1, + "text": "a distinctive, earthy, usually pleasant odor that is associated with rainfall especially when following a warm, dry period and that arises from a combination of volatile plant oils and geosmin released from the soil into the air and by ozone carried by downdrafts", + "examples": [ + "Australian scientists first documented the process of petrichor" + ] + }, + { + "sense_num": 2, + "text": "an earthy odor that is associated with rainfall especially after a warm and dry period", + "examples": [ + "The intensity of the petrichor" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "sonder", + "slug": "sonder", + "pos": "noun", + "pronunciation": "ˈsän-dər", + "syllables": "sän-dər", + "first_known_use": "2021", + "etymology": "probably in part from French sonder \"to measure the depth of, sound entry 6,\" in part from German sondern \"to separate,\" going back to Old High German suntarōn — more at sunder", + "definitions": [ + { + "sense_num": 1, + "text": "the realization and understanding that all other people have lives as complex as one's own", + "examples": [ + "[John] Koenig came up with the term \"sonder", + "Like many study abroad trips, Hickman reiterated just how powerful and life-changing it is to see the world from another perspective. Reinforcing the concept of sonder", + "[The podcast] Beautiful/Anonymous has taught me to approach life with a greater appreciation for what I can't understand beyond my perception of the people around me, to experience sonder" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "limerence", + "slug": "limerence", + "pos": "noun", + "pronunciation": "ˈli-m(ə-)rən(t)s", + "syllables": "li-m(ə-)rən(t)s", + "first_known_use": "1977", + "etymology": "limer-, alleged to be an arbitrary formation by the coiner of the word + -ence", + "definitions": [ + { + "sense_num": 1, + "text": "a state of blissful usually temporary infatuation experienced during the early period of a romantic relationship : the euphoric feeling experienced when first falling in love", + "examples": [ + "This is the crazy love phase known by psychologists as limerence", + "The main difference between falling in limerence" + ] + }, + { + "sense_num": 2, + "text": "a state of intense often involuntary romantic attachment to a person who does not reciprocate the feelings and that is often characterized by excessive preoccupation and obsessive behaviors", + "examples": [ + "The involuntary nature of the connection is key: The experience of limerence" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "halcyon", + "slug": "halcyon", + "pos": "adjective", + "pronunciation": "ˈhal-sē-ən", + "syllables": "hal-sē-ən", + "first_known_use": "1570", + "etymology": "Middle English alceon, from Latin halcyon, from Greek alkyōn, halkyōn", + "definitions": [ + { + "sense_num": 1, + "text": "characterized by happiness, great success, and prosperity : golden", + "examples": [ + "the halcyon days" + ] + }, + { + "sense_num": 2, + "text": "calm", + "examples": [ + "Classics Illustrated have become pricey nostalgia items for those who grew up in the supposedly halcyon" + ] + }, + { + "sense_num": 3, + "text": "prosperous", + "examples": [ + "In those halcyon" + ] + }, + { + "sense_num": 4, + "text": "of or relating to the halcyon (see halcyon", + "examples": [ + "the halcyon years of his childhood" + ] + }, + { + "sense_num": 5, + "text": "a bird identified with the kingfisher and held in ancient legend to nest at sea about the time of the winter solstice and to calm the waves during incubation", + "examples": [] + }, + { + "sense_num": 6, + "text": "kingfisher", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "mellifluous", + "slug": "mellifluous", + "pos": "adjective", + "pronunciation": "me-ˈli-flə-wəs", + "syllables": "me-li-flə-wəs", + "first_known_use": "15th century", + "etymology": "Middle English mellyfluous, from Late Latin mellifluus, from Latin mell-, mel honey + fluere to flow; akin to Goth milith honey, Greek melit-, meli", + "definitions": [ + { + "sense_num": 1, + "text": "having a smooth rich flow", + "examples": [ + "a mellifluous" + ] + }, + { + "sense_num": 2, + "text": "filled with something (such as honey) that sweetens", + "examples": [ + "mellifluous speech" + ] + }, + { + "sense_num": 3, + "text": "smoothly flowing", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "ineffable", + "slug": "ineffable", + "pos": "adjective", + "pronunciation": "(ˌ)i-ˈne-fə-bəl", + "syllables": "()i-ne-fə-bəl", + "first_known_use": "14th century", + "etymology": "Middle English, from Latin ineffabilis, from in- + effabilis capable of being expressed, from effari to speak out, from ex- + fari to speak — more at ban entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "incapable of being expressed in words : indescribable", + "examples": [ + "the ineffable" + ] + }, + { + "sense_num": 2, + "text": "unspeakable", + "examples": [ + "ineffable joy" + ] + }, + { + "sense_num": 3, + "text": "not to be uttered : taboo", + "examples": [] + }, + { + "sense_num": 4, + "text": "impossible to express : inexpressible", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "epiphany", + "slug": "epiphany", + "pos": "noun", + "pronunciation": "i-ˈpi-fə-nē", + "syllables": "i-pi-fə-nē", + "first_known_use": "14th century", + "etymology": "Middle English Epiphanie, borrowed from Anglo-French Epiphane, Epiphanie, borrowed from Late Latin epiphanīa, epiphania \"appearance, manifestation, Christ's first manifestation (to the Gentiles in Western tradition),\" borrowed from Late Greek epipháneia \"appearance, manifestation (of God in the Old Testament, of Christ's first coming or of the Second Coming),\" going back to Greek, \"appearance, coming into view, manifestation (of a deity to a worshipper), Christ's coming (in the New Testament), visible surface, outward show, fame,\" noun derivative of epiphanḗs \"coming into view, appearing, manifest, evident,\" adjective derivative from the stem of epiphaínein \"to show, display,\" mediopassive epiphaínesthai \"to come into view, be manifested, appear on the surface,\" from epi- epi- + phaínein \"to bring to light, cause to appear,\" phaínesthai \"to become visible, appear\" — more at fantasy entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "a Christian festival held on January 6 in commemoration of the coming of the Magi as the first manifestation of Christ to the Gentiles or in the Eastern Church in commemoration of the baptism of Christ", + "examples": [ + "… the experience is an epiphany" + ] + }, + { + "sense_num": 2, + "text": "an appearance or manifestation especially of a divine being", + "examples": [ + "… a food writer who has traveled around the world in an endless quest for epiphanies" + ] + }, + { + "sense_num": 3, + "text": "a usually sudden manifestation or perception of the essential nature or meaning of something", + "examples": [ + "When a child moves from recognizing letters to decoding and comprehension, a moment of epiphany" + ] + }, + { + "sense_num": 4, + "text": "an intuitive grasp of reality through something (such as an event) usually simple and striking", + "examples": [ + "The novel's epiphany" + ] + }, + { + "sense_num": 5, + "text": "an illuminating discovery, realization, or disclosure", + "examples": [] + }, + { + "sense_num": 6, + "text": "a revealing scene or moment", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "labyrinth", + "slug": "labyrinth", + "pos": "noun", + "pronunciation": "ˈla-bə-ˌrin(t)th", + "syllables": "la-bə-rin(t)th", + "first_known_use": "15th century", + "etymology": "Middle English laborintus, from Latin labyrinthus, from Greek labyrinthos", + "definitions": [ + { + "sense_num": 1, + "text": "a place constructed of or full of intricate passageways and dead ends", + "examples": [ + "a complex labyrinth" + ] + }, + { + "sense_num": 2, + "text": "a maze (as in a garden) formed by paths separated by high hedges", + "examples": [ + "guided them through the labyrinths" + ] + }, + { + "sense_num": 3, + "text": "something extremely complex or tortuous (see tortuous", + "examples": [ + "the cockpit was a labyrinth of instruments and controls" + ] + }, + { + "sense_num": 4, + "text": "a tortuous anatomical structure", + "examples": [] + }, + { + "sense_num": 5, + "text": "the internal ear or its bony or membranous part", + "examples": [] + }, + { + "sense_num": 6, + "text": "a place full of passageways and blind alleys so arranged as to make it difficult to find one's way around : maze", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "panacea", + "slug": "panacea", + "pos": "noun", + "pronunciation": "ˌpa-nə-ˈsē-ə", + "syllables": "pa-nə-sē-ə", + "first_known_use": "1548", + "etymology": "borrowed from New Latin panacēa \"universal remedy, cure-all,\" going back to Latin, \"any of various medicinal plants,\" borrowed from Greek panákeia \"name of a medicinal plant, universal remedy, (as a personified abstraction) a goddess of healing,\" derivative of panakḗs \"all-healing,\" from pan- pan- + -akēs, adjective derivative of ákos (neuter s-stem) \"cure, remedy, relief,\" of uncertain origin", + "definitions": [ + { + "sense_num": 1, + "text": "a remedy for all ills or difficulties : cure-all", + "examples": [ + "The law will improve the lives of local farmers, but it is no panacea" + ] + }, + { + "sense_num": 2, + "text": "a remedy for all ills or difficulties : cure-all", + "examples": [] + }, + { + "sense_num": 3, + "text": "a remedy for all ills or difficulties", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "paradigm", + "slug": "paradigm", + "pos": "noun", + "pronunciation": "ˈper-ə-ˌdīm", + "syllables": "per-ə-dīm", + "first_known_use": "15th century", + "etymology": "Late Latin paradigma, from Greek paradeigma, from paradeiknynai to show side by side, from para- + deiknynai to show — more at diction", + "definitions": [ + { + "sense_num": 1, + "text": "a model for something that may be copied : example", + "examples": [ + "Her book provides us with a new paradigm" + ] + }, + { + "sense_num": 2, + "text": "an exceptional example or archetype", + "examples": [ + "an entrepreneur who became a paradigm" + ] + }, + { + "sense_num": 3, + "text": "a philosophical and theoretical framework of a scientific school or discipline within which theories, laws, and generalizations and the experiments performed in support of them are formulated", + "examples": [ + "the Freudian paradigm" + ] + }, + { + "sense_num": 4, + "text": "a philosophical or theoretical framework of any kind", + "examples": [ + "… the extraordinary complexity of Darwin's explanatory paradigm" + ] + }, + { + "sense_num": 5, + "text": "an example of a conjugation or declension showing a word in all its inflectional forms", + "examples": [] + }, + { + "sense_num": 6, + "text": "an example showing how something is to be done : model", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "catalyst", + "slug": "catalyst", + "pos": "noun", + "pronunciation": "ˈka-tə-ləst", + "syllables": "ka-tə-ləst", + "first_known_use": "1902", + "etymology": "see catalysis", + "definitions": [ + { + "sense_num": 1, + "text": "a person or thing that provokes or speeds significant change or action", + "examples": [ + "Look at passionate young people from any era and you'll find impressive catalysts for change" + ] + }, + { + "sense_num": 2, + "text": "a substance that enables a chemical reaction to proceed at a usually faster rate or under different conditions (as at a lower temperature) than otherwise possible", + "examples": [ + "Depending on the catalyst" + ] + }, + { + "sense_num": 3, + "text": "a substance that changes the rate of a chemical reaction but is itself unchanged at the end of the process", + "examples": [ + "the scandal was a catalyst for reform" + ] + }, + { + "sense_num": 4, + "text": "such a substance that speeds up a reaction or enables it to proceed under milder conditions", + "examples": [] + }, + { + "sense_num": 5, + "text": "a person or event that quickly causes change or action", + "examples": [] + }, + { + "sense_num": 6, + "text": "a substance (as an enzyme) that enables a chemical reaction to proceed at a usually faster rate or under different conditions (as at a lower temperature) than otherwise possible", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "ethereal", + "slug": "ethereal", + "pos": "adjective", + "pronunciation": "i-ˈthir-ē-əl", + "syllables": "i-thir-ē-əl", + "first_known_use": "1522", + "etymology": "", + "definitions": [ + { + "sense_num": 1, + "text": "seeming to belong to or come from another world : otherworldly", + "examples": [ + "… the rise of a peaking full moon, which bathed the wild shore in an ethereal" + ] + }, + { + "sense_num": 2, + "text": "of, relating to, or suggesting heaven or the heavens", + "examples": [ + "… a wood thrush sang its ethereal" + ] + }, + { + "sense_num": 3, + "text": "lacking material substance : immaterial", + "examples": [ + "White clouds veiled the sun, and a few ethereal" + ] + }, + { + "sense_num": 4, + "text": "marked by unusual delicacy or refinement", + "examples": [ + "Students, to you 'tis giv'n to scan the heights / Above, to traverse the ethereal" + ] + }, + { + "sense_num": 5, + "text": "of or relating to theoretical or philosophical ether", + "examples": [] + }, + { + "sense_num": 6, + "text": "relating to, containing, or resembling a chemical ether", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "iridescent", + "slug": "iridescent", + "pos": "adjective", + "pronunciation": "ˌir-ə-ˈde-sᵊnt", + "syllables": "ir-ə-de-sᵊnt", + "first_known_use": "1796", + "etymology": "Greek īrid-, îris \"rainbow, iridescent halo around the moon, a flame, etc.\" + -escent — more at iris entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "having or exhibiting iridescence", + "examples": [] + }, + { + "sense_num": 2, + "text": "having or showing iridescence", + "examples": [] + }, + { + "sense_num": 3, + "text": "having or exhibiting a display of colors producing rainbow effects", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "nebulous", + "slug": "nebulous", + "pos": "adjective", + "pronunciation": "ˈne-byə-ləs", + "syllables": "ne-byə-ləs", + "first_known_use": "1674", + "etymology": "Latin nebulosus misty, from nebula", + "definitions": [ + { + "sense_num": 1, + "text": "of, relating to, or resembling a nebula : nebular", + "examples": [ + "… this nebulous" + ] + }, + { + "sense_num": 2, + "text": "indistinct", + "examples": [ + "… the nebulous" + ] + }, + { + "sense_num": 3, + "text": "of, relating to, or resembling a nebula", + "examples": [ + "The plan is too nebulous" + ] + }, + { + "sense_num": 4, + "text": "not clear or sharp : vague", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "zephyr", + "slug": "zephyr", + "pos": "noun", + "pronunciation": "ˈze-fər", + "syllables": "ze-fər", + "first_known_use": "1567", + "etymology": "Middle English Zephirus, west wind (personified), from Latin Zephyrus, god of the west wind & zephyrus west wind, zephyr, from Greek Zephyros & zephyros", + "definitions": [ + { + "sense_num": 1, + "text": "a breeze from the west", + "examples": [] + }, + { + "sense_num": 2, + "text": "a gentle breeze", + "examples": [] + }, + { + "sense_num": 3, + "text": "any of various lightweight fabrics and articles of clothing", + "examples": [] + }, + { + "sense_num": 4, + "text": "a breeze from the west", + "examples": [] + }, + { + "sense_num": 5, + "text": "a gentle breeze", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "solitude", + "slug": "solitude", + "pos": "noun", + "pronunciation": "ˈsä-lə-ˌtüd", + "syllables": "sä-lə-tüd", + "first_known_use": "14th century", + "etymology": "Middle English, from Middle French & Latin; Middle French, from Latin solitudin-, solitudo, from solus", + "definitions": [ + { + "sense_num": 1, + "text": "the quality or state of being alone or remote from society : seclusion", + "examples": [] + }, + { + "sense_num": 2, + "text": "a lonely place (such as a desert)", + "examples": [] + }, + { + "sense_num": 3, + "text": "the quality or state of being alone or far-off from society : seclusion", + "examples": [] + }, + { + "sense_num": 4, + "text": "a lonely place (as a desert)", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "reverie", + "slug": "reverie", + "pos": "noun", + "pronunciation": "ˈre-və-rē", + "syllables": "re-və-rē", + "first_known_use": "15th century", + "etymology": "French rêverie, from Middle French, delirium, from resver, rever to wander, be delirious", + "definitions": [ + { + "sense_num": 1, + "text": "daydream", + "examples": [] + }, + { + "sense_num": 2, + "text": "the condition of being lost in thought", + "examples": [] + }, + { + "sense_num": 3, + "text": "daydream", + "examples": [] + }, + { + "sense_num": 4, + "text": "the condition of being lost in thought", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "tenacity", + "slug": "tenacity", + "pos": "noun", + "pronunciation": "tə-ˈna-sə-tē", + "syllables": "tə-na-sə-tē", + "first_known_use": "15th century", + "etymology": "Middle English tenacite, borrowed from Middle French tenacité, borrowed from Latin tenācitāt-, tenācitās, from tenāc-, tenāx \"holding fast, tenacious\" + -itāt- -itās -ity", + "definitions": [ + { + "sense_num": 1, + "text": "the quality or state of being tenacious", + "examples": [ + "If there is a particular tenacity", + "A tribute to tenacity" + ] + }, + { + "sense_num": 2, + "text": "the quality or state of being tenacious", + "examples": [ + "… everything about a person, even the most blameless of facts, can have the sticky tenacity" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "veracity", + "slug": "veracity", + "pos": "noun", + "pronunciation": "və-ˈra-sə-tē", + "syllables": "və-ra-sə-tē", + "first_known_use": "1614", + "etymology": "borrowed from New Latin vērācitāt-, vērācitās, from Latin vērāc-, vērāx \"truthful\" + -itāt-, -itās -ity — more at very entry 2", + "definitions": [ + { + "sense_num": 1, + "text": "conformity with truth or fact : accuracy", + "examples": [ + "makes lies sound like veracities" + ] + }, + { + "sense_num": 2, + "text": "devotion to the truth : truthfulness", + "examples": [ + "What gives the book its integrity are the simplicity and veracity" + ] + }, + { + "sense_num": 3, + "text": "power of conveying or perceiving truth", + "examples": [ + "The trial began with a flurry of motions and questions challenging the judge's authority and veracity" + ] + }, + { + "sense_num": 4, + "text": "something true", + "examples": [ + "… some documentary photographers supported the photographer's right to find essential rather than literal truths in any situation, while others … insisted on absolute veracity" + ] + }, + { + "sense_num": 5, + "text": "devotion to the truth : truthfulness", + "examples": [] + }, + { + "sense_num": 6, + "text": "agreement with truth or fact", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "alacrity", + "slug": "alacrity", + "pos": "noun", + "pronunciation": "ə-ˈla-krə-tē", + "syllables": "ə-la-krə-tē", + "first_known_use": "15th century", + "etymology": "Latin alacritas, from alacr-, alacer lively, eager", + "definitions": [ + { + "sense_num": 1, + "text": "promptness in response : cheerful readiness", + "examples": [ + "accepted the invitation with alacrity", + "Every Disney worker I spoke to, from ticket sellers to gardeners sprucing up already-immaculate flower beds, knew the answer to my questions and responded with smiling alacrity" + ] + }, + { + "sense_num": 2, + "text": "a cheerful readiness to do something", + "examples": [ + "Surely one of the most striking features of human dynamics is the alacrity", + "… when he entered the drawing room before dinner, the buzz of discussion was high between Tom, Maria, and Mr. Yates; and Mr. Rushworth stepped forward with great alacrity" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "equanimity", + "slug": "equanimity", + "pos": "noun", + "pronunciation": "ˌē-kwə-ˈni-mə-tē", + "syllables": "ē-kwə-ni-mə-tē", + "first_known_use": "1663", + "etymology": "Latin aequanimitas, from aequo animo with even mind", + "definitions": [ + { + "sense_num": 1, + "text": "evenness of mind especially under stress", + "examples": [ + "Nothing could disturb his equanimity", + "She's heading straight for us—he thought. … And his uneasiness grew by the recollection of the forty tons of dynamite in the body of the Ferndale; not the sort of cargo one thinks of with equanimity" + ] + }, + { + "sense_num": 2, + "text": "right disposition : balance", + "examples": [ + "physical equanimity" + ] + }, + { + "sense_num": 3, + "text": "evenness of emotions or temper : composure", + "examples": [ + "Those who are doomed to become artists are seldom blessed with equanimity" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "magnanimous", + "slug": "magnanimous", + "pos": "adjective", + "pronunciation": "mag-ˈna-nə-məs", + "syllables": "mag-na-nə-məs", + "first_known_use": "1547", + "etymology": "Latin magnanimus, from magnus great + animus spirit — more at much, animate", + "definitions": [ + { + "sense_num": 1, + "text": "showing or suggesting a generous and kind nature", + "examples": [ + "… a magnanimous" + ] + }, + { + "sense_num": 2, + "text": "showing or suggesting a lofty and courageous spirit", + "examples": [ + "He'd have been magnanimous" + ] + }, + { + "sense_num": 3, + "text": "having or showing a noble and courageous spirit", + "examples": [ + "It was to be a family party, but Roderick, in his magnanimous" + ] + }, + { + "sense_num": 4, + "text": "being generous and forgiving", + "examples": [ + "… the irreproachable lives and magnanimous" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "sanguine", + "slug": "sanguine", + "pos": "adjective", + "pronunciation": "ˈsaŋ-gwən", + "syllables": "saŋ-gwən", + "first_known_use": "14th century", + "etymology": "Middle English sanguin, from Anglo-French, from Latin sanguineus, from sanguin-, sanguis — see sanguinary", + "definitions": [ + { + "sense_num": 1, + "text": "marked by eager hopefulness : confidently optimistic", + "examples": [ + "Some of us hear the term AI and picture a dystopian future. … Others are more sanguine" + ] + }, + { + "sense_num": 2, + "text": "bloodred", + "examples": [ + "The trustees chose to take a sanguine" + ] + }, + { + "sense_num": 3, + "text": "consisting of or relating to blood", + "examples": [ + "… the radiant heat from the cedar logs, whose sanguine" + ] + }, + { + "sense_num": 4, + "text": "bloodthirsty", + "examples": [ + "… swaps the sanguine" + ] + }, + { + "sense_num": 5, + "text": "accompanied by, involving, or relating to bloodshed : bloody", + "examples": [] + }, + { + "sense_num": 6, + "text": "having a healthy reddish color : ruddy", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "stoic", + "slug": "stoic", + "pos": "noun", + "pronunciation": "ˈstō-ik", + "syllables": "stō-ik", + "first_known_use": "14th century", + "etymology": "Middle English, from Latin stoicus, from Greek stōïkos, literally, of the portico, from Stoa (Poikilē) the Painted Portico, portico at Athens where Zeno taught", + "definitions": [ + { + "sense_num": 1, + "text": "a member of a school of philosophy founded by Zeno of Citium about 300 b.c.", + "examples": [ + "\"That would have been to dishonor him,\" said Carr, a notorious stoic" + ] + }, + { + "sense_num": 2, + "text": "a person who accepts what happens without complaining or showing emotion", + "examples": [ + "The philosophical implications of this claim are as volcanic as the emotions it depicts, for Nussbaum here counters an age-old view espoused by Stoics" + ] + }, + { + "sense_num": 3, + "text": "of, relating to, or resembling the Stoics or their doctrines", + "examples": [ + "Whereas Ludwig Wittgenstein once compared philosophers to garbage men sweeping the mind clean of wrongheaded concepts, Nussbaum believes they should be \"lawyers for humanity\"—a phrase she borrows from Seneca, her favorite Stoic" + ] + }, + { + "sense_num": 4, + "text": "not affected by or showing passion or feeling", + "examples": [ + "Grant recorded his thought-experiment when he was an old man dying of cancer, who in spite of his pain had managed to achieve a stoical" + ] + }, + { + "sense_num": 5, + "text": "firmly restrained in response to pain or distress", + "examples": [] + }, + { + "sense_num": 6, + "text": "one not easily excited or upset", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "fastidious", + "slug": "fastidious", + "pos": "adjective", + "pronunciation": "fa-ˈsti-dē-əs", + "syllables": "fa-sti-dē-əs", + "first_known_use": "15th century", + "etymology": "Middle English, from Latin fastidiosus, from fastidium disgust, probably from fastus arrogance (probably akin to Latin fastigium top) + taedium irksomeness — more at tedium", + "definitions": [ + { + "sense_num": 1, + "text": "extremely or excessively careful or detailed", + "examples": [ + "kept fastidious" + ] + }, + { + "sense_num": 2, + "text": "extremely or excessively exacting, particular, or discerning", + "examples": [ + "… regulators are guaranteed to take a fastidious" + ] + }, + { + "sense_num": 3, + "text": "characterized by extreme or excessive concern about cleanliness or neatness", + "examples": [ + "… he took fastidious" + ] + }, + { + "sense_num": 4, + "text": "having complex nutritional requirements", + "examples": [ + "… his fastidious" + ] + }, + { + "sense_num": 5, + "text": "scornful", + "examples": [] + }, + { + "sense_num": 6, + "text": "hard to please : very particular", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "garrulous", + "slug": "garrulous", + "pos": "adjective", + "pronunciation": "ˈger-ə-ləs", + "syllables": "ger-ə-ləs", + "first_known_use": "circa 1611", + "etymology": "borrowed from Latin garrulus, from garrīre \"to chatter, talk rapidly\" (probably of imitative origin) + -ulus, deverbal suffix denoting inclination or repetitive action (going back to Indo-European -l-, participial suffix) — more at -ous", + "definitions": [ + { + "sense_num": 1, + "text": "given to prosy, rambling, or tedious loquacity : pointlessly or annoyingly talkative", + "examples": [ + "Salman grew ever more garrulous" + ] + }, + { + "sense_num": 2, + "text": "wordy", + "examples": [ + "To an American reader in 1982, confronted with this garrulous" + ] + }, + { + "sense_num": 3, + "text": "overly talkative", + "examples": [ + "He was not garrulous" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "laconic", + "slug": "laconic", + "pos": "adjective", + "pronunciation": "lə-ˈkä-nik", + "syllables": "lə-kä-nik", + "first_known_use": "1589", + "etymology": "Latin laconicus Spartan, from Greek lakōnikos; from the Spartan reputation for terseness of speech", + "definitions": [ + { + "sense_num": 1, + "text": "using or involving the use of a minimum of words : concise to the point of seeming rude or mysterious", + "examples": [ + "We would rather have a smiling, shape-shifting Democrat we don't trust than a frowning, laconic", + "… towards the father—laconic" + ] + }, + { + "sense_num": 2, + "text": "using few words : terse", + "examples": [ + "The closest anyone comes to announcing his destination is a laconic", + "a laconic reply" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "verbose", + "slug": "verbose", + "pos": "adjective", + "pronunciation": "(ˌ)vər-ˈbōs", + "syllables": "()vər-bōs", + "first_known_use": "circa 1531", + "etymology": "borrowed from Latin verbōsus, from verbum \"word, verb entry 1\" + -ōsus -ose entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "containing more words than necessary : wordy", + "examples": [ + "Something seems to have gone seriously wrong with the subediting and proof-reading of this self-indulgently verbose" + ] + }, + { + "sense_num": 2, + "text": "impaired by wordiness", + "examples": [ + "What makes this tiny tome so much fun are the answers. There are occasional one-word zingers: to a verbose" + ] + }, + { + "sense_num": 3, + "text": "given to wordiness", + "examples": [ + "I must confess … that if I had known how many classics there are in English literature, and how verbose" + ] + }, + { + "sense_num": 4, + "text": "using more words than are needed", + "examples": [ + "a verbose reply" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "succinct", + "slug": "succinct", + "pos": "adjective", + "pronunciation": "(ˌ)sək-ˈsiŋ(k)t", + "syllables": "()sək-siŋ(k)t", + "first_known_use": "15th century", + "etymology": "Middle English, from Latin succinctus having one's clothes gathered up by a belt, tightly wrapped, concise, from sub- + cinctus, past participle of cingere to gird — more at cincture", + "definitions": [ + { + "sense_num": 1, + "text": "using few words to state or express something", + "examples": [ + "gave a succinct" + ] + }, + { + "sense_num": 2, + "text": "being girded", + "examples": [ + "As a judge, she is known more for being detailed and thorough … than for crisp and succinct" + ] + }, + { + "sense_num": 3, + "text": "close-fitting", + "examples": [ + "Other experts are in the business of selling their research. Alan Greenspan made his reputation and career as a partner of Townsend-Greenspan, whose clients were a who's who of old Wall Street. Successful research firms can command substantial fees, and buyers demand clear, succinct" + ] + }, + { + "sense_num": 4, + "text": "marked by short concise expression without wasted words", + "examples": [ + "As Esther Benbassa recounts in her dry but impressively succinct" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "loquacious", + "slug": "loquacious", + "pos": "adjective", + "pronunciation": "lō-ˈkwā-shəs", + "syllables": "lō-kwā-shəs", + "first_known_use": "1634", + "etymology": "Latin loquāc-, loquāx \"talkative, verbose\" (from loquī \"to talk, speak\" + -āc-, deverbal suffix denoting habitual or successful performance) + -ious — more at eloquent, audacious", + "definitions": [ + { + "sense_num": 1, + "text": "given to fluent or excessive talk : garrulous", + "examples": [ + "… not often the most loquacious", + "… long-cultivated dislikes and resentments, combined with a general expectation of coming apocalypse. He talked about these topics in a manner that managed to be tight-lipped and loquacious" + ] + }, + { + "sense_num": 2, + "text": "full of excessive talk : wordy", + "examples": [ + "The moderators have largely held their ground, … cutting off loquacious" + ] + }, + { + "sense_num": 3, + "text": "very talkative", + "examples": [ + "… a lengthy, loquacious" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "obstinate", + "slug": "obstinate", + "pos": "adjective", + "pronunciation": "ˈäb-stə-nət", + "syllables": "äb-stə-nət", + "first_known_use": "14th century", + "etymology": "Middle English, from Anglo-French obstinat, Latin obstinatus, past participle of obstinare to be resolved, from ob- in the way + -stinare (akin to stare to stand)", + "definitions": [ + { + "sense_num": 1, + "text": "stubbornly holding to an opinion, purpose, or course in spite of reason, arguments, or persuasion", + "examples": [ + "Only this austerity of voice, this obstinate" + ] + }, + { + "sense_num": 2, + "text": "not easily controlled, remedied, or removed", + "examples": [ + "You've seen movies in which there's one obstinate" + ] + }, + { + "sense_num": 3, + "text": "sticking to an opinion, purpose, or course in spite of reason, arguments, or persuasion", + "examples": [ + "A moment of silence lodged between us, an old and obstinate" + ] + }, + { + "sense_num": 4, + "text": "not easily overcome or removed", + "examples": [ + "people who cling obstinately" + ] + }, + { + "sense_num": 5, + "text": "adhering to an opinion, purpose, or course in spite of reason, arguments, or persuasion", + "examples": [] + }, + { + "sense_num": 6, + "text": "not easily subdued, remedied, or removed", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "tenacious", + "slug": "tenacious", + "pos": "adjective", + "pronunciation": "tə-ˈnā-shəs", + "syllables": "tə-nā-shəs", + "first_known_use": "1607", + "etymology": "Latin tenāc-, tenāx \"holding fast, clinging, persistent\" (from tenēre \"to hold, occupy, possess\" + -āc-, deverbal suffix denoting habitual or successful performance) + -ious — more at tenant entry 1, audacious", + "definitions": [ + { + "sense_num": 1, + "text": "aggressively persistent in maintaining, adhering to, or seeking something valued or desired", + "examples": [ + "… made a name for himself as a powerful and tenacious" + ] + }, + { + "sense_num": 2, + "text": "enduring especially when challenged", + "examples": [ + "Hardship has a way of creating steely, tenacious" + ] + }, + { + "sense_num": 3, + "text": "retentive", + "examples": [ + "It had been cut down to a few knotty stumps, but a bundle of tenacious" + ] + }, + { + "sense_num": 4, + "text": "not easily pulled apart", + "examples": [ + "The footage … starts with a clip of the duck just barely fighting back against the turtle's tenacious" + ] + }, + { + "sense_num": 5, + "text": "tending to adhere or cling especially to another substance", + "examples": [] + }, + { + "sense_num": 6, + "text": "not easily pulled apart", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "capricious", + "slug": "capricious", + "pos": "adjective", + "pronunciation": "kə-ˈpri-shəs", + "syllables": "kə-pri-shəs", + "first_known_use": "1588", + "etymology": "borrowed from Middle French capricieux, borrowed from Italian capriccioso, from capriccio caprice + -oso -ous", + "definitions": [ + { + "sense_num": 1, + "text": "governed or characterized by sudden irrational and unpredictable impulses or whims : impulsive", + "examples": [ + "… the belief … that we were mere pieces in the games of capricious" + ] + }, + { + "sense_num": 2, + "text": "unpredictable", + "examples": [ + "the capricious" + ] + }, + { + "sense_num": 3, + "text": "not supported by the weight of evidence or established rules of law", + "examples": [ + "Tornadoes are among the planet's most fearsome phenomena, with terrifying and capricious" + ] + }, + { + "sense_num": 4, + "text": "moved or controlled by caprice : apt to change suddenly", + "examples": [ + "claimed the denial of benefits was arbitrary and capricious" + ] + }, + { + "sense_num": 5, + "text": "governed or characterized by impulse or whim: as", + "examples": [] + }, + { + "sense_num": 6, + "text": "lacking a rational basis", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "fickle", + "slug": "fickle", + "pos": "adjective", + "pronunciation": "ˈfi-kəl", + "syllables": "fi-kəl", + "first_known_use": "13th century", + "etymology": "Middle English fikel deceitful, inconstant, from Old English ficol deceitful; akin to Old English befician to deceive, and probably to Old English fāh hostile — more at foe", + "definitions": [ + { + "sense_num": 1, + "text": "marked by lack of steadfastness, constancy, or stability : given to erratic changeableness", + "examples": [ + "The Weak will suck up to the Strong, for fear of losing their jobs and their money and all the fickle", + "A failed play was a denial of what Odets was owed, for he was chasing the public no differently than did his bourgeois and nonrevolutionary contemporaries, a public as fickle" + ] + }, + { + "sense_num": 2, + "text": "likely to change frequently without good reason : inconstant", + "examples": [ + "The corporate fan who has replaced the core fan is a fickle", + "War is like hard-drug abuse or a fickle" + ] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "mercurial", + "slug": "mercurial", + "pos": "adjective", + "pronunciation": "(ˌ)mər-ˈkyu̇r-ē-əl", + "syllables": "()mər-kyu̇r-ē-əl", + "first_known_use": "14th century", + "etymology": "see mercury", + "definitions": [ + { + "sense_num": 1, + "text": "characterized by rapid and unpredictable changeableness of mood", + "examples": [ + "… he has a mercurial" + ] + }, + { + "sense_num": 2, + "text": "very lively and quick", + "examples": [ + "… the mercurial" + ] + }, + { + "sense_num": 3, + "text": "changing often : very changeable", + "examples": [ + "… the mercurial" + ] + }, + { + "sense_num": 4, + "text": "having qualities (as of eloquence, ingenuity, or thievishness) attributed to the god Mercury or in astrology to the influence of the planet Mercury", + "examples": [ + "… innovative mercurial" + ] + }, + { + "sense_num": 5, + "text": "fast in development or occurrence : quick", + "examples": [] + }, + { + "sense_num": 6, + "text": "of, relating to, or born under the planet Mercury", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "altruistic", + "slug": "altruistic", + "pos": "adjective", + "pronunciation": "ˌal-trü-ˈi-stik", + "syllables": "al-trü-i-stik", + "first_known_use": "1853", + "etymology": "", + "definitions": [ + { + "sense_num": 1, + "text": "relating to or given to altruism:", + "examples": [ + "a generous and altruistic", + "… one of the first researchers to begin to solve the paradox of how the evolutionary struggle to survive and reproduce could give rise to creatures that never reproduce and spend their lives altruistically" + ] + }, + { + "sense_num": 2, + "text": "having or showing an unselfish concern for the welfare of others", + "examples": [ + "Yet many of the most important institutions in our society—the fine arts, NGOs, humanitarian charities—depend on the generosity of wealthy citizens with altruistic" + ] + }, + { + "sense_num": 3, + "text": "relating to or being behavior by an animal that is not beneficial to or may be harmful to the animal itself but that benefits others of its species", + "examples": [ + "The evolutionary theory of kin selection requires that social animals recognize their relatives so that altruistic" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "philanthropy", + "slug": "philanthropy", + "pos": "noun", + "pronunciation": "fə-ˈlan(t)-thrə-pē", + "syllables": "fə-lan(t)-thrə-pē", + "first_known_use": "circa 1623", + "etymology": "Late Latin philanthropia, from Greek philanthrōpia, from philanthrōpos loving people, from phil- + anthrōpos human being", + "definitions": [ + { + "sense_num": 1, + "text": "goodwill to fellow members of the human race", + "examples": [ + "For many years, Microsoft has used corporate philanthropy" + ] + }, + { + "sense_num": 2, + "text": "active effort to promote human welfare", + "examples": [ + "Cooper, born in New York City in 1791, was himself an inventor and a hands-on industrialist, whose fortune got its start in the glue business, greatly expanded in the iron industry, eventually included more than half the telegraph lines in the United States, and was significantly invested in philanthropy" + ] + }, + { + "sense_num": 3, + "text": "an act or gift done or made for humanitarian purposes", + "examples": [ + "In conditions of anarchy, a crude and violent order, based upon brute force and psychopathic ruthlessness, soon establishes itself, which regards philanthropy" + ] + }, + { + "sense_num": 4, + "text": "an organization distributing or supported by funds set aside for humanitarian purposes", + "examples": [] + }, + { + "sense_num": 5, + "text": "a spirit of goodwill toward all people especially when expressed in active efforts to help others", + "examples": [] + }, + { + "sense_num": 6, + "text": "a charitable act or gift", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "compassion", + "slug": "compassion", + "pos": "noun", + "pronunciation": "kəm-ˈpa-shən", + "syllables": "kəm-pa-shən", + "first_known_use": "14th century", + "etymology": "Middle English, from Anglo-French or Late Latin; Anglo-French, from Late Latin compassion-, compassio, from compati to sympathize, from Latin com- + pati to bear, suffer — more at patient", + "definitions": [ + { + "sense_num": 1, + "text": "sympathetic consciousness of others' distress together with a desire to alleviate it", + "examples": [ + "She had the compassion", + "Take away all the qualities that make for a genuinely good father—wisdom, compassion" + ] + }, + { + "sense_num": 2, + "text": "sorrow or pity caused by the suffering or misfortune of another along with a desire to ease it", + "examples": [ + "Whitman's own irregular and unfortunate family … no doubt figured in his empathetic compassion", + "… he read every \"doctor book\" he could reach … , learning fine secrets and curing us with steams and fruit compotes and dexterous rubs and, above all, with bedside compassion" + ] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "benign", + "slug": "benign", + "pos": "adjective", + "pronunciation": "bi-ˈnīn", + "syllables": "bi-nīn", + "first_known_use": "14th century", + "etymology": "Middle English benigne, from Anglo-French, from Latin benignus, from bene + gignere to beget — more at kin", + "definitions": [ + { + "sense_num": 1, + "text": "of a mild type or character that does not threaten health or life", + "examples": [ + "Doctors removed the mass, which turned out to be benign" + ] + }, + { + "sense_num": 2, + "text": "not becoming cancerous", + "examples": [ + "This chemical is environmentally benign" + ] + }, + { + "sense_num": 3, + "text": "having no significant effect : harmless", + "examples": [ + "There are about 4,000 species of snails worldwide and most are benign" + ] + }, + { + "sense_num": 4, + "text": "of a gentle disposition : gracious", + "examples": [ + "Most of us like to think of ourselves as benign" + ] + }, + { + "sense_num": 5, + "text": "showing kindness and gentleness", + "examples": [] + }, + { + "sense_num": 6, + "text": "favorable", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "cordial", + "slug": "cordial", + "pos": "adjective", + "pronunciation": "ˈkȯr-jəl", + "syllables": "kȯr-jəl", + "first_known_use": "15th century", + "etymology": "Middle English cordiall \"of the heart, cardiac, invigorating, deeply felt,\" borrowed from Medieval Latin cordiālis, from Latin cord-, cor \"heart\" + -iālis -ial", + "definitions": [ + { + "sense_num": 1, + "text": "showing or marked by warm and often hearty friendliness, favor, or approval", + "examples": [ + "two nations maintaining cordial" + ] + }, + { + "sense_num": 2, + "text": "politely pleasant and friendly", + "examples": [ + "… bottles full of excellent cordial" + ] + }, + { + "sense_num": 3, + "text": "sincerely or deeply felt", + "examples": [ + "… Conrad Black was cordial" + ] + }, + { + "sense_num": 4, + "text": "tending to revive, cheer, or invigorate", + "examples": [ + "Though its chairman, Charles Obi, was cordial" + ] + }, + { + "sense_num": 5, + "text": "of or relating to the heart : vital", + "examples": [] + }, + { + "sense_num": 6, + "text": "liqueur", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "diligence", + "slug": "diligence", + "pos": "noun (1)", + "pronunciation": "ˈdi-lə-jən(t)s", + "syllables": "di-lə-jən(t)s", + "first_known_use": "14th century", + "etymology": "Middle English, from Anglo-French, from Latin diligentia, from diligent-, diligens — see diligent", + "definitions": [ + { + "sense_num": 1, + "text": "steady, earnest, and energetic effort : persistent and careful hard work", + "examples": [ + "showed great diligence" + ] + }, + { + "sense_num": 2, + "text": "speed", + "examples": [ + "He had earned universal respect for his integrity, fairness, and diligence" + ] + }, + { + "sense_num": 3, + "text": "the attention, effort, and care legally expected or required of a person (such as a party to a contract) see also due diligence", + "examples": [ + "Go, hence with diligence" + ] + }, + { + "sense_num": 4, + "text": "stagecoach", + "examples": [ + "The railway had driven coach companies out of business. … Once, the journey had taken three days by diligence" + ] + }, + { + "sense_num": 5, + "text": "careful and continued work : industry", + "examples": [] + }, + { + "sense_num": 6, + "text": "earnest and persistent application of effort especially as required by law", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "industrious", + "slug": "industrious", + "pos": "adjective", + "pronunciation": "in-ˈdə-strē-əs", + "syllables": "in-də-strē-əs", + "first_known_use": "15th century", + "etymology": "", + "definitions": [ + { + "sense_num": 1, + "text": "constantly, regularly, or habitually active or occupied : diligent", + "examples": [ + "an industrious" + ] + }, + { + "sense_num": 2, + "text": "skillful", + "examples": [ + "an industrious farmer" + ] + }, + { + "sense_num": 3, + "text": "constantly or regularly active or occupied", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "assiduous", + "slug": "assiduous", + "pos": "adjective", + "pronunciation": "ə-ˈsij-wəs", + "syllables": "ə-sij-wəs", + "first_known_use": "circa 1552", + "etymology": "Latin assiduus, from assidēre to sit beside", + "definitions": [ + { + "sense_num": 1, + "text": "showing great care, attention, and effort : marked by careful unremitting attention or persistent application", + "examples": [ + "She tended her garden with assiduous" + ] + }, + { + "sense_num": 2, + "text": "constantly attentive : diligent", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "advanced" + }, + { + "headword": "sedulous", + "slug": "sedulous", + "pos": "adjective", + "pronunciation": "ˈse-jə-ləs", + "syllables": "se-jə-ləs", + "first_known_use": "1540", + "etymology": "Latin sedulus, from sedulo sincerely, diligently, from sed-, se without + dolus guile — more at suicide", + "definitions": [ + { + "sense_num": 1, + "text": "involving or accomplished with careful perseverance", + "examples": [] + }, + { + "sense_num": 2, + "text": "diligent in application or pursuit", + "examples": [] + }, + { + "sense_num": 3, + "text": "steadily industrious : diligent", + "examples": [] + } + ], + "synonyms": [], + "difficulty": "common" + }, + { + "headword": "happy", + "slug": "happy", + "pos": "adjective", + "pronunciation": "ˈha-pē", + "syllables": "ha-pē", + "first_known_use": "14th century", + "etymology": "Middle English, from hap", + "definitions": [ + { + "sense_num": 1, + "text": "enjoying or characterized by well-being and contentment", + "examples": [ + "She is the happiest person I know." + ] + }, + { + "sense_num": 2, + "text": "expressing, reflecting, or suggestive of happiness", + "examples": [ + "one big happy" + ] + }, + { + "sense_num": 3, + "text": "glad", + "examples": [ + "remembering happier" + ] + }, + { + "sense_num": 4, + "text": "very willing to do something", + "examples": [ + "They were not happy" + ] + }, + { + "sense_num": 5, + "text": "having or marked by an atmosphere of good fellowship : friendly", + "examples": [] + }, + { + "sense_num": 6, + "text": "favored by luck or fortune : fortunate", + "examples": [] + } + ], + "synonyms": [ + "delighted", + "pleased", + "glad", + "satisfied", + "thankful", + "joyful" + ], + "difficulty": "common" + }, + { + "headword": "sad", + "slug": "sad", + "pos": "adjective", + "pronunciation": "ˈsad", + "syllables": "sad", + "first_known_use": "13th century", + "etymology": "Middle English, from Old English sæd sated; akin to Old High German sat sated, Latin satis enough", + "definitions": [ + { + "sense_num": 1, + "text": "affected with or expressive of grief or unhappiness : downcast", + "examples": [] + }, + { + "sense_num": 2, + "text": "causing or associated with grief or unhappiness : depressing", + "examples": [] + }, + { + "sense_num": 3, + "text": "regrettable", + "examples": [] + }, + { + "sense_num": 4, + "text": "of little worth", + "examples": [] + }, + { + "sense_num": 5, + "text": "of a dull somber color", + "examples": [] + }, + { + "sense_num": 6, + "text": "seasonal affective disorder", + "examples": [] + } + ], + "synonyms": [ + "unhappy", + "heartbroken", + "depressed", + "miserable", + "sorry", + "bad" + ], + "difficulty": "common" + }, + { + "headword": "big", + "slug": "big", + "pos": "adjective", + "pronunciation": "ˈbig", + "syllables": "big", + "first_known_use": "14th century", + "etymology": "Middle English, perhaps of Scandinavian origin; akin to Norwegian dialect bugge important man", + "definitions": [ + { + "sense_num": 1, + "text": "large or great in dimensions, bulk, or extent", + "examples": [ + "My mother is a big" + ] + }, + { + "sense_num": 2, + "text": "large or great in quantity, number, or amount", + "examples": [ + "I'm not a big" + ] + }, + { + "sense_num": 3, + "text": "operating on a large scale", + "examples": [ + "greeted me with a big" + ] + }, + { + "sense_num": 4, + "text": "capital", + "examples": [ + "His teachers all told me he was excited about riding the bus, feeling like a big" + ] + }, + { + "sense_num": 5, + "text": "filled with or characterized by enthusiasm and interest", + "examples": [] + }, + { + "sense_num": 6, + "text": "active and enthusiastic", + "examples": [] + } + ], + "synonyms": [ + "large", + "sizable", + "substantial", + "considerable", + "huge", + "great" + ], + "difficulty": "common" + }, + { + "headword": "small", + "slug": "small", + "pos": "adjective", + "pronunciation": "ˈsmȯl", + "syllables": "smȯl", + "first_known_use": "the 12th century", + "etymology": "Middle English smal, from Old English smæl; akin to Old High German smal small, Greek mēlon small domestic animal", + "definitions": [ + { + "sense_num": 1, + "text": "having comparatively little size or slight dimensions", + "examples": [ + "… speak as small" + ] + }, + { + "sense_num": 2, + "text": "lowercase", + "examples": [ + "a small supply" + ] + }, + { + "sense_num": 3, + "text": "minor in influence, power, or rank", + "examples": [ + "small success" + ] + }, + { + "sense_num": 4, + "text": "operating on a limited scale", + "examples": [ + "a small matter" + ] + }, + { + "sense_num": 5, + "text": "lacking in strength", + "examples": [] + }, + { + "sense_num": 6, + "text": "little or close to zero in an objectively measurable aspect (such as quantity)", + "examples": [] + } + ], + "synonyms": [ + "little", + "diminutive", + "tiny", + "pocket", + "fine", + "slight" + ], + "difficulty": "common" + }, + { + "headword": "fast", + "slug": "fast", + "pos": "adjective", + "pronunciation": "ˈfast", + "syllables": "fast", + "first_known_use": "the 12th century", + "etymology": "Middle English, from Old English fæst; akin to Old High German festi firm, Old Norse fastr, Armenian hast", + "definitions": [ + { + "sense_num": 1, + "text": "characterized by quick motion, operation, or effect:", + "examples": [ + "a class for fast" + ] + }, + { + "sense_num": 2, + "text": "moving or able to move rapidly : swift", + "examples": [ + "Your clock is two minutes fast" + ] + }, + { + "sense_num": 3, + "text": "taking a comparatively short time", + "examples": [ + "… had a keen eye for a fast" + ] + }, + { + "sense_num": 4, + "text": "imparting quickness of motion", + "examples": [ + "the drawers were fast" + ] + }, + { + "sense_num": 5, + "text": "accomplished quickly", + "examples": [] + }, + { + "sense_num": 6, + "text": "agile of mind", + "examples": [] + } + ], + "synonyms": [ + "quickly", + "rapidly", + "quick", + "swiftly", + "hot", + "soon" + ], + "difficulty": "common" + }, + { + "headword": "smart", + "slug": "smart", + "pos": "adjective", + "pronunciation": "ˈsmärt", + "syllables": "smärt", + "first_known_use": "the 12th century", + "etymology": "Middle English smert causing pain, from Old English smeart; akin to Old English smeortan", + "definitions": [ + { + "sense_num": 1, + "text": "having or showing a high degree of mental ability : intelligent", + "examples": [ + "That wasn't a very smart" + ] + }, + { + "sense_num": 2, + "text": "witty", + "examples": [ + "The pursuit of genius or at least being the smartest" + ] + }, + { + "sense_num": 3, + "text": "rude or impolite in a bold and disrespectful way", + "examples": [ + "Don't get smart" + ] + }, + { + "sense_num": 4, + "text": "neat", + "examples": [ + "soldiers in smart" + ] + }, + { + "sense_num": 5, + "text": "stylish or elegant in dress or appearance", + "examples": [] + }, + { + "sense_num": 6, + "text": "appealing to sophisticated tastes : characteristic of or patronized by fashionable society", + "examples": [] + } + ], + "synonyms": [ + "intelligent", + "wise", + "savvy", + "astute", + "shrewd", + "clever" + ], + "difficulty": "common" + }, + { + "headword": "beautiful", + "slug": "beautiful", + "pos": "adjective", + "pronunciation": "ˈbyü-ti-fəl", + "syllables": "byü-ti-fəl", + "first_known_use": "15th century", + "etymology": "Middle English bewteful, beautefull, from beaute beauty + -ful, -full -ful entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "having qualities of beauty : exciting aesthetic pleasure", + "examples": [ + "Located on one of the most beautiful" + ] + }, + { + "sense_num": 2, + "text": "generally pleasing : excellent", + "examples": [ + "In her biography of Monroe, Churchwell takes to task the relentless mythomania of her admirers and critics, who are equally invested in nurturing the legend of a hapless beautiful" + ] + }, + { + "sense_num": 3, + "text": "having the qualities of beauty", + "examples": [ + "In the hothouse of today's celebrity monoculture, the result has been the perfection of the kind of profile in which athletes and actors struggle to overcome … absence or presence in their lives of money, fame, sex and drugs. Especially so in the upscale slicks, where these stories of the rich and beautiful" + ] + }, + { + "sense_num": 4, + "text": "very good : excellent", + "examples": [] + } + ], + "synonyms": [ + "lovely", + "gorgeous", + "cute", + "handsome", + "attractive", + "pretty" + ], + "difficulty": "advanced" + }, + { + "headword": "important", + "slug": "important", + "pos": "adjective", + "pronunciation": "im-ˈpȯr-tᵊnt", + "syllables": "im-pȯr-tᵊnt", + "first_known_use": "15th century", + "etymology": "Middle English importante, from Medieval Latin important-, importans, present participle of importare to signify — more at import entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "marked by or indicative of significant worth or consequence : valuable in content or relationship", + "examples": [ + "Exercise is important" + ] + }, + { + "sense_num": 2, + "text": "deserving serious attention", + "examples": [ + "made several important" + ] + }, + { + "sense_num": 3, + "text": "giving evidence of a feeling of self-importance", + "examples": [ + "answered in an important" + ] + }, + { + "sense_num": 4, + "text": "importunate", + "examples": [ + "an important day to remember" + ] + }, + { + "sense_num": 5, + "text": "having great meaning or influence : significant", + "examples": [] + }, + { + "sense_num": 6, + "text": "having power or authority", + "examples": [] + } + ], + "synonyms": [ + "major", + "significant", + "historic", + "big", + "meaningful", + "much" + ], + "difficulty": "advanced" + }, + { + "headword": "difficult", + "slug": "difficult", + "pos": "adjective", + "pronunciation": "ˈdi-fi-(ˌ)kəlt", + "syllables": "di-fi-()kəlt", + "first_known_use": "14th century", + "etymology": "Middle English, probably back-formation from difficulte difficulty", + "definitions": [ + { + "sense_num": 1, + "text": "hard to do, make, or carry out : arduous", + "examples": [ + "was in a difficult" + ] + }, + { + "sense_num": 2, + "text": "hard to deal with, manage, or overcome", + "examples": [ + "Why must you be so difficult" + ] + }, + { + "sense_num": 3, + "text": "hard to understand : puzzling", + "examples": [ + "having a difficult" + ] + }, + { + "sense_num": 4, + "text": "hard to do, make, or carry out", + "examples": [ + "found calculus too difficult" + ] + }, + { + "sense_num": 5, + "text": "hard to deal with, manage, or overcome", + "examples": [] + }, + { + "sense_num": 6, + "text": "hard to understand : puzzling", + "examples": [] + } + ], + "synonyms": [ + "challenging", + "tough", + "hard", + "rigorous", + "demanding", + "formidable" + ], + "difficulty": "advanced" + }, + { + "headword": "strange", + "slug": "strange", + "pos": "adjective", + "pronunciation": "ˈstrānj", + "syllables": "strānj", + "first_known_use": "13th century", + "etymology": "Middle English straunge, strange, straynge \"foreign, unfamiliar, from elsewhere, unusual, aloof,\" borrowed from Anglo-French estrange, estraunge \"outside the family, foreign, unusual, marvelous\" (continental Old French estrange), going back to Latin extrāneus \"not belonging to one's family or household, coming from abroad, foreign, external,\" from extrā \"outside, beyond the boundaries of\" + -āneus, adjective suffix — more at extra-", + "definitions": [ + { + "sense_num": 1, + "text": "different from what is usual, ordinary, or expected : odd", + "examples": [ + "the cat's strange" + ] + }, + { + "sense_num": 2, + "text": "not before known, heard, or seen : unfamiliar", + "examples": [ + "customs that were strange" + ] + }, + { + "sense_num": 3, + "text": "not entirely comfortable or well : uncomfortable", + "examples": [ + "feeling lost in a strange" + ] + }, + { + "sense_num": 4, + "text": "discouraging familiarities : reserved", + "examples": [ + "felt a strange" + ] + }, + { + "sense_num": 5, + "text": "unaccustomed", + "examples": [] + }, + { + "sense_num": 6, + "text": "not native to or naturally belonging in a place : of external origin, kind, or character", + "examples": [] + } + ], + "synonyms": [ + "bizarre", + "weird", + "odd", + "funny", + "peculiar", + "curious" + ], + "difficulty": "common" + }, + { + "headword": "brave", + "slug": "brave", + "pos": "adjective", + "pronunciation": "ˈbrāv", + "syllables": "brāv", + "first_known_use": "1568", + "etymology": "borrowed from Middle French, borrowed from Italian bravo \"courageous, wild,\" perhaps ultimately going back to Latin barbarus barbarous", + "definitions": [ + { + "sense_num": 1, + "text": "having or showing mental or moral strength to face danger, fear, or difficulty : having or showing courage", + "examples": [ + "braved the rush-hour traffic to get there" + ] + }, + { + "sense_num": 2, + "text": "making a fine show : colorful", + "examples": [ + "braving the elements" + ] + }, + { + "sense_num": 3, + "text": "excellent", + "examples": [ + "home of the brave" + ] + }, + { + "sense_num": 4, + "text": "to face or endure with courage", + "examples": [ + "… none but the brave" + ] + }, + { + "sense_num": 5, + "text": "to make showy", + "examples": [] + }, + { + "sense_num": 6, + "text": "to show courage : to make a brave show", + "examples": [] + } + ], + "synonyms": [ + "courageous", + "fearless", + "valiant", + "heroic", + "gallant", + "bold" + ], + "difficulty": "common" + }, + { + "headword": "calm", + "slug": "calm", + "pos": "noun", + "pronunciation": "ˈkäm", + "syllables": "käm", + "first_known_use": "14th century", + "etymology": "Middle English calme, probably ultimately from Old Spanish calma, from Late Latin cauma heat, from Greek kauma, from kaiein to burn", + "definitions": [ + { + "sense_num": 1, + "text": "a period or condition of freedom from storms, high winds, or rough activity of water", + "examples": [ + "a sailing ship motionless in the calm" + ] + }, + { + "sense_num": 2, + "text": "complete absence of wind or presence of wind having a speed no greater than one mile (1.6 kilometers) per hour see Beaufort Scale Table", + "examples": [ + "At dusk a quiet calm" + ] + }, + { + "sense_num": 3, + "text": "a state of tranquility", + "examples": [ + "The mayor asked the protesters to calm" + ] + }, + { + "sense_num": 4, + "text": "to become calm", + "examples": [ + "He saw Fyodor hang his head low and try to calm" + ] + }, + { + "sense_num": 5, + "text": "to make calm", + "examples": [] + }, + { + "sense_num": 6, + "text": "marked by calm : still", + "examples": [] + } + ], + "synonyms": [ + "quiet", + "tranquil", + "serene", + "peaceful", + "placid", + "hushed" + ], + "difficulty": "common" + }, + { + "headword": "good", + "slug": "good", + "pos": "adjective", + "pronunciation": "ˈgu̇d", + "syllables": "gu̇d", + "first_known_use": "the 12th century", + "etymology": "Middle English, from Old English gōd; akin to Old High German guot good, Middle High German gatern to unite, Sanskrit gadhya what one clings to", + "definitions": [ + { + "sense_num": 1, + "text": "of a favorable character or tendency", + "examples": [ + "We ate at a good" + ] + }, + { + "sense_num": 2, + "text": "bountiful", + "examples": [ + "You'll need to invest in some better" + ] + }, + { + "sense_num": 3, + "text": "handsome", + "examples": [ + "\"Is the morning good" + ] + }, + { + "sense_num": 4, + "text": "of a high or desired quality", + "examples": [ + "Is that milk still good" + ] + }, + { + "sense_num": 5, + "text": "expressing praise or approval", + "examples": [] + }, + { + "sense_num": 6, + "text": "suitable", + "examples": [] + } + ], + "synonyms": [ + "pleasant", + "delightful", + "enjoyable", + "pleasing", + "nice", + "sweet" + ], + "difficulty": "common" + }, + { + "headword": "bad", + "slug": "bad", + "pos": "adjective", + "pronunciation": "ˈbad", + "syllables": "bad", + "first_known_use": "13th century", + "etymology": "Middle English badde, bad, of obscure origin", + "definitions": [ + { + "sense_num": 1, + "text": "failing to reach an acceptable standard : poor", + "examples": [ + "The house was in bad" + ] + }, + { + "sense_num": 2, + "text": "unfavorable", + "examples": [ + "felt generally bad" + ] + }, + { + "sense_num": 3, + "text": "not fresh : spoiled", + "examples": [ + "… the baddest" + ] + }, + { + "sense_num": 4, + "text": "not sound : dilapidated", + "examples": [ + "the baddest guy on the block" + ] + }, + { + "sense_num": 5, + "text": "morally objectionable : evil", + "examples": [] + }, + { + "sense_num": 6, + "text": "mischievous", + "examples": [] + } + ], + "synonyms": [ + "unacceptable", + "wrong", + "poor", + "lame", + "horrible", + "terrible" + ], + "difficulty": "common" + }, + { + "headword": "old", + "slug": "old", + "pos": "adjective", + "pronunciation": "ˈōld", + "syllables": "ōld", + "first_known_use": "the 12th century", + "etymology": "Middle English, from Old English eald; akin to Old High German alt old, Latin alere to nourish, alescere to grow, altus high, deep", + "definitions": [ + { + "sense_num": 1, + "text": "dating from the remote past : ancient", + "examples": [ + "they brought up the same old" + ] + }, + { + "sense_num": 2, + "text": "persisting from an earlier time", + "examples": [ + "This recipe is an old" + ] + }, + { + "sense_num": 3, + "text": "of long standing", + "examples": [ + "many still used the old" + ] + }, + { + "sense_num": 4, + "text": "distinguished from an object of the same kind by being of an earlier date", + "examples": [ + "a child three years old" + ] + }, + { + "sense_num": 5, + "text": "belonging to an early period in the development of a language or literature", + "examples": [] + }, + { + "sense_num": 6, + "text": "having existed for a specified period of time", + "examples": [] + } + ], + "synonyms": [ + "elderly", + "senior", + "aging", + "aged", + "older", + "ancient" + ], + "difficulty": "common" + }, + { + "headword": "new", + "slug": "new", + "pos": "adjective", + "pronunciation": "ˈnü", + "syllables": "nü", + "first_known_use": "the 12th century", + "etymology": "Middle English newe, new, nywe, going back to Old English nīowe, nīewe, nēowe, going back to Germanic *neuja- (whence Old Saxon & Old High German niuwi \"new,\" Middle Dutch nieuwe, nûwe, Old Norse nýr, Gothic niujis), going back to Indo-European *neu̯i̯o-, derivative of *neu̯o- \"new, young,\" whence Latin novus \"new\" (from *newos), Greek néos \"young, fresh, new,\" Tocharian A ñu \"new,\" Tocharian B ñuwe, Sanskrit návaḥ \"new, fresh, young,\" Avestan nauua-, Hittite nēwa- \"new\"; also, going back to presumed ablaut variant, *nou̯o- (whence Old Church Slavic novŭ \"new, recent\") and *nou̯i̯o- (whence Old Irish náue, nuae \"new, fresh,\" Welsh newydd, Lithuanian naũjas \"new,\" Sanskrit návyaḥ \"new, young\"); also, going back to a derivative *neu̯ǝro- (parallel to Greek nearós \"youthful, tender\"), Armenian nor \"new\"", + "definitions": [ + { + "sense_num": 1, + "text": "having recently come into existence : recent", + "examples": [ + "I saw their new" + ] + }, + { + "sense_num": 2, + "text": "having been seen, used, or known for a short time : novel", + "examples": [ + "rice was a new" + ] + }, + { + "sense_num": 3, + "text": "unfamiliar", + "examples": [ + "a steady flow of new" + ] + }, + { + "sense_num": 4, + "text": "being other than the former or old", + "examples": [ + "He bought a new" + ] + }, + { + "sense_num": 5, + "text": "having been in a relationship or condition but a short time", + "examples": [] + }, + { + "sense_num": 6, + "text": "beginning as the resumption or repetition of a previous act or thing", + "examples": [] + } + ], + "synonyms": [ + "novel", + "unfamiliar", + "fresh", + "strange", + "unprecedented", + "original" + ], + "difficulty": "common" + }, + { + "headword": "rich", + "slug": "rich", + "pos": "adjective", + "pronunciation": "ˈrich", + "syllables": "rich", + "first_known_use": "the 12th century", + "etymology": "Middle English riche, from Old English rīce; akin to Old High German rīhhi rich, Old English rīce kingdom, Old High German rīhhi, noun; all from prehistoric Germanic words borrowed from Celtic words akin to Old Irish rí (genitive ríg) king — more at royal", + "definitions": [ + { + "sense_num": 1, + "text": "having abundant possessions and especially material wealth", + "examples": [ + "investments that made them very rich" + ] + }, + { + "sense_num": 2, + "text": "having high value or quality", + "examples": [ + "His family is filthy/stinking rich" + ] + }, + { + "sense_num": 3, + "text": "well supplied or endowed", + "examples": [ + "cholesterol-rich foods" + ] + }, + { + "sense_num": 4, + "text": "magnificently impressive : sumptuous", + "examples": [ + "She thinks we're slow? Oh, that's rich" + ] + }, + { + "sense_num": 5, + "text": "vivid and deep in color", + "examples": [] + }, + { + "sense_num": 6, + "text": "full and mellow in tone and quality", + "examples": [] + } + ], + "synonyms": [ + "wealthy", + "affluent", + "opulent", + "well-to-do", + "moneyed", + "successful" + ], + "difficulty": "common" + }, + { + "headword": "poor", + "slug": "poor", + "pos": "adjective", + "pronunciation": "ˈpu̇r", + "syllables": "pu̇r", + "first_known_use": "13th century", + "etymology": "Middle English poure, from Anglo-French povre, pore, from Latin pauper; akin to Latin paucus little and to Latin parere to give birth to, produce — more at few, pare", + "definitions": [ + { + "sense_num": 1, + "text": "lacking sufficient money or material possessions", + "examples": [ + "oil-poor countries" + ] + }, + { + "sense_num": 2, + "text": "of, relating to, or characterized by poverty", + "examples": [ + "the patient had a poor day" + ] + }, + { + "sense_num": 3, + "text": "less than adequate : meager", + "examples": [ + "poor furnishings" + ] + }, + { + "sense_num": 4, + "text": "small in worth", + "examples": [ + "the poor kitten hurt its paw" + ] + }, + { + "sense_num": 5, + "text": "exciting pity", + "examples": [] + }, + { + "sense_num": 6, + "text": "inferior in quality or value", + "examples": [] + } + ], + "synonyms": [ + "impoverished", + "broke", + "deprived", + "needy", + "beggared", + "indigent" + ], + "difficulty": "common" + }, + { + "headword": "strong", + "slug": "strong", + "pos": "adjective", + "pronunciation": "ˈstrȯŋ", + "syllables": "strȯŋ", + "first_known_use": "the 12th century", + "etymology": "Middle English, from Old English strang; akin to Old High German strengi strong, Latin stringere to bind tight — more at strain", + "definitions": [ + { + "sense_num": 1, + "text": "having or marked by great physical power", + "examples": [ + "an athlete with strong" + ] + }, + { + "sense_num": 2, + "text": "not sick or injured", + "examples": [ + "He'll return to work when he's feeling a little stronger" + ] + }, + { + "sense_num": 3, + "text": "having moral, emotional, or intellectual power or ability", + "examples": [ + "a person of strong" + ] + }, + { + "sense_num": 4, + "text": "having social power or influence", + "examples": [ + "I'm not strong" + ] + }, + { + "sense_num": 5, + "text": "having great resources (as of wealth or talent)", + "examples": [] + }, + { + "sense_num": 6, + "text": "of a specified number", + "examples": [] + } + ], + "synonyms": [ + "muscular", + "powerful", + "mighty", + "rugged", + "stout", + "sturdy" + ], + "difficulty": "common" + }, + { + "headword": "weak", + "slug": "weak", + "pos": "adjective", + "pronunciation": "ˈwēk", + "syllables": "wēk", + "first_known_use": "14th century", + "etymology": "Middle English weike, from Old Norse veikr; akin to Old English wīcan to yield, Greek eikein to give way, Sanskrit vijate he speeds, flees", + "definitions": [ + { + "sense_num": 1, + "text": "lacking strength: such as", + "examples": [ + "the spirit is willing but the flesh is weak" + ] + }, + { + "sense_num": 2, + "text": "deficient in physical vigor : feeble", + "examples": [ + "tutoring for weaker" + ] + }, + { + "sense_num": 3, + "text": "not able to sustain or exert much weight, pressure, or strain", + "examples": [ + "history was my weakest" + ] + }, + { + "sense_num": 4, + "text": "not able to resist external force or withstand attack", + "examples": [ + "'d in he'd is the weak" + ] + }, + { + "sense_num": 5, + "text": "easily upset or nauseated", + "examples": [] + }, + { + "sense_num": 6, + "text": "mentally or intellectually deficient", + "examples": [] + } + ], + "synonyms": [ + "weakened", + "feeble", + "frail", + "disabled", + "faint", + "enfeebled" + ], + "difficulty": "common" + }, + { + "headword": "kind", + "slug": "kind", + "pos": "noun", + "pronunciation": "ˈkīnd", + "syllables": "kīnd", + "first_known_use": "the 12th century", + "etymology": "Middle English kinde, from Old English cynd; akin to Old English cynn kin", + "definitions": [ + { + "sense_num": 1, + "text": "a group united by common traits or interests : category", + "examples": [ + "a difference in degree but not in kind" + ] + }, + { + "sense_num": 2, + "text": "a specific or recognized variety", + "examples": [ + "payment in kind" + ] + }, + { + "sense_num": 3, + "text": "a doubtful or barely admissible member of a category", + "examples": [ + "promised to return the favor in kind" + ] + }, + { + "sense_num": 4, + "text": "fundamental nature or quality : essence", + "examples": [ + "was helped by a kind" + ] + }, + { + "sense_num": 5, + "text": "goods or commodities as distinguished from money", + "examples": [] + }, + { + "sense_num": 6, + "text": "the equivalent of what has been offered or received", + "examples": [] + } + ], + "synonyms": [ + "compassionate", + "benevolent", + "thoughtful", + "sympathetic", + "gentle", + "kindly" + ], + "difficulty": "common" + }, + { + "headword": "honest", + "slug": "honest", + "pos": "adjective", + "pronunciation": "ˈä-nəst", + "syllables": "ä-nəst", + "first_known_use": "14th century", + "etymology": "Middle English, from Anglo-French, from Latin honestus honorable, from honos, honor honor", + "definitions": [ + { + "sense_num": 1, + "text": "free from fraud or deception : legitimate", + "examples": [ + "… making honest" + ] + }, + { + "sense_num": 2, + "text": "genuine", + "examples": [ + "put forth an honest" + ] + }, + { + "sense_num": 3, + "text": "humble", + "examples": [ + "some good honest" + ] + }, + { + "sense_num": 4, + "text": "reputable", + "examples": [ + "… I have ever found thee honest" + ] + }, + { + "sense_num": 5, + "text": "good", + "examples": [] + }, + { + "sense_num": 6, + "text": "worthy of praise", + "examples": [] + } + ], + "synonyms": [ + "truthful", + "reliable", + "outspoken", + "genuine", + "credible", + "forthright" + ], + "difficulty": "common" + }, + { + "headword": "quiet", + "slug": "quiet", + "pos": "noun", + "pronunciation": "ˈkwī-ət", + "syllables": "kwī-ət", + "first_known_use": "14th century", + "etymology": "Middle English quiet, quiete, borrowed from Anglo-French quiete, borrowed from Latin quiēt-, quiēs \"repose, sleep, rest, peaceful conditions,\" going back to Indo-European *kwi̯eh1-ti-, noun derivative of a verbal base *kwi̯eh1- \"have a rest,\" whence Avestan š́iiā- \"be glad,\" Old Church Slavic počijǫ, počiti \"to have a rest\" (causative pokojǫ, pokoiti \"to calm, quiet\"), Armenian hangeaw \"has rested,\" and (from deverbal *kwi̯eh1-to-) Avestan š́iiāta- \"peaceful, happy,\" Old Persian šiyāta-, Latin quiētus \"at rest, quiet entry 2\"", + "definitions": [ + { + "sense_num": 1, + "text": "the quality or state of being quiet (see quiet", + "examples": [ + "The lights went down and the theater became quiet" + ] + }, + { + "sense_num": 2, + "text": "free from noise or uproar : still", + "examples": [ + "Everyone suddenly went quiet" + ] + }, + { + "sense_num": 3, + "text": "making or involving no noise or very little noise", + "examples": [ + "Please be quiet" + ] + }, + { + "sense_num": 4, + "text": "tending to speak very little : not loquacious", + "examples": [ + "He was a quiet" + ] + }, + { + "sense_num": 5, + "text": "unobtrusive", + "examples": [] + }, + { + "sense_num": 6, + "text": "marked by little or no motion or activity : calm", + "examples": [] + } + ], + "synonyms": [ + "peaceful", + "serene", + "calm", + "placid", + "restful", + "tranquil" + ], + "difficulty": "common" + }, + { + "headword": "bright", + "slug": "bright", + "pos": "adjective", + "pronunciation": "ˈbrīt", + "syllables": "brīt", + "first_known_use": "the 12th century", + "etymology": "Middle English, from Old English beorht; akin to Old High German beraht bright, Sanskrit bhrājate it shines", + "definitions": [ + { + "sense_num": 1, + "text": "radiating or reflecting light : shining", + "examples": [ + "the brightest" + ] + }, + { + "sense_num": 2, + "text": "sunny", + "examples": [ + "Look on the bright side" + ] + }, + { + "sense_num": 3, + "text": "radiant with happiness", + "examples": [ + "… rich earth tones and crisp brights" + ] + }, + { + "sense_num": 4, + "text": "illustrious", + "examples": [ + "Follow these steps and you can wash your dark clothes with the same laundry detergent you use for your whites and brights" + ] + }, + { + "sense_num": 5, + "text": "beautiful", + "examples": [] + }, + { + "sense_num": 6, + "text": "of high saturation or lightness", + "examples": [] + } + ], + "synonyms": [ + "shining", + "luminous", + "dazzling", + "glowing", + "shiny", + "radiant" + ], + "difficulty": "common" + }, + { + "headword": "clever", + "slug": "clever", + "pos": "adjective", + "pronunciation": "ˈkle-vər", + "syllables": "kle-vər", + "first_known_use": "circa 1595", + "etymology": "Middle English cliver, perhaps of Scandinavian origin; akin to Danish dialect kløver alert, skillful", + "definitions": [ + { + "sense_num": 1, + "text": "skillful or adroit in using the hands or body : nimble", + "examples": [ + "the play's clever" + ] + }, + { + "sense_num": 2, + "text": "mentally quick and resourceful", + "examples": [ + "All of Laptsev went to stare at the bride-to-be—she was no beauty, but everyone could see that she was clever" + ] + }, + { + "sense_num": 3, + "text": "marked by wit or ingenuity", + "examples": [ + "… the three of them may give Gray Davis, who was too clever" + ] + }, + { + "sense_num": 4, + "text": "good", + "examples": [ + "Some thought he had no redeeming value whatsoever. A sociopath. A clever" + ] + }, + { + "sense_num": 5, + "text": "easy to use or handle", + "examples": [] + }, + { + "sense_num": 6, + "text": "showing skill especially in using one's hands", + "examples": [] + } + ], + "synonyms": [ + "innovative", + "imaginative", + "inventive", + "creative", + "ingenious", + "useful" + ], + "difficulty": "common" + }, + { + "headword": "generous", + "slug": "generous", + "pos": "adjective", + "pronunciation": "ˈje-nə-rəs", + "syllables": "je-nə-rəs", + "first_known_use": "1574", + "etymology": "Middle French or Latin; Middle French genereus, from Latin generosus, from gener-, genus", + "definitions": [ + { + "sense_num": 1, + "text": "liberal in giving : openhanded", + "examples": [ + "… wide overhangs and generous" + ] + }, + { + "sense_num": 2, + "text": "marked by abundance or ample proportions", + "examples": [ + "a thin salt-and-pepper moustache interrupted by a generous" + ] + }, + { + "sense_num": 3, + "text": "copious", + "examples": [ + "a generous supply" + ] + }, + { + "sense_num": 4, + "text": "characterized by a noble or kindly spirit : magnanimous", + "examples": [] + }, + { + "sense_num": 5, + "text": "highborn", + "examples": [] + }, + { + "sense_num": 6, + "text": "free in giving or sharing", + "examples": [] + } + ], + "synonyms": [ + "charitable", + "liberal", + "benevolent", + "bountiful", + "handsome", + "munificent" + ], + "difficulty": "common" + }, + { + "headword": "careful", + "slug": "careful", + "pos": "adjective", + "pronunciation": "ˈker-fəl", + "syllables": "ker-fəl", + "first_known_use": "the 12th century", + "etymology": "see care entry 1", + "definitions": [ + { + "sense_num": 1, + "text": "marked by wary caution or prudence", + "examples": [ + "be very careful" + ] + }, + { + "sense_num": 2, + "text": "marked by attentive concern and solicitude", + "examples": [ + "a careful driver" + ] + }, + { + "sense_num": 3, + "text": "marked by painstaking effort to avoid errors or omissions", + "examples": [ + "a careful examination" + ] + }, + { + "sense_num": 4, + "text": "exercising or taking care", + "examples": [] + }, + { + "sense_num": 5, + "text": "solicitous", + "examples": [] + }, + { + "sense_num": 6, + "text": "filling with care or solicitude", + "examples": [] + } + ], + "synonyms": [ + "cautious", + "wary", + "alert", + "circumspect", + "considerate", + "conservative" + ], + "difficulty": "common" + }, + { + "headword": "eager", + "slug": "eager", + "pos": "adjective", + "pronunciation": "ˈē-gər", + "syllables": "ē-gər", + "first_known_use": "14th century", + "etymology": "Middle English egre, from Anglo-French egre, aigre, from Latin acer — more at edge", + "definitions": [ + { + "sense_num": 1, + "text": "marked by enthusiastic or impatient desire or interest", + "examples": [ + "… wine connoisseurs eager" + ] + }, + { + "sense_num": 2, + "text": "sharp", + "examples": [ + "… so many religions were steeped in an absolutist frame of mind—each convinced that it alone had a monopoly on the truth and therefore eager" + ] + }, + { + "sense_num": 3, + "text": "sour", + "examples": [ + "was eager to get going" + ] + }, + { + "sense_num": 4, + "text": "having or showing an impatient or enthusiastic desire or interest", + "examples": [] + } + ], + "synonyms": [ + "excited", + "enthusiastic", + "avid", + "anxious", + "keen", + "hungry" + ], + "difficulty": "common" + }, + { + "headword": "proud", + "slug": "proud", + "pos": "adjective", + "pronunciation": "ˈprau̇d", + "syllables": "prau̇d", + "first_known_use": "the 12th century", + "etymology": "Middle English, from Old English prūd, probably from Old French prod, prud, prou advantageous, just, wise, bold, from Late Latin prode advantage, advantageous, back-formation from Latin prodesse to be advantageous, from pro-, prod- for, in favor + esse to be — more at pro-, is", + "definitions": [ + { + "sense_num": 1, + "text": "feeling or showing pride: such as", + "examples": [ + "a proud manner" + ] + }, + { + "sense_num": 2, + "text": "having or displaying excessive self-esteem", + "examples": [ + "proud parents of a hero" + ] + }, + { + "sense_num": 3, + "text": "much pleased : exultant", + "examples": [ + "too proud to beg" + ] + }, + { + "sense_num": 4, + "text": "having proper self-respect", + "examples": [ + "a proud record" + ] + }, + { + "sense_num": 5, + "text": "marked by stateliness : magnificent", + "examples": [] + }, + { + "sense_num": 6, + "text": "giving reason for pride : glorious", + "examples": [] + } + ], + "synonyms": [ + "arrogant", + "superior", + "smug", + "disdainful", + "prideful", + "haughty" + ], + "difficulty": "common" + } +] + +THESAURUS = [ + { + "headword": "happy", + "slug": "happy", + "pos": "", + "short_def": "", + "synonyms": [ + "delighted", + "pleased", + "glad", + "satisfied", + "thankful", + "joyful", + "joyous", + "blissful" + ], + "antonyms": [ + "unhappy", + "sad", + "dissatisfied", + "unsatisfied", + "displeased", + "joyless", + "depressed", + "blue" + ] + }, + { + "headword": "sad", + "slug": "sad", + "pos": "", + "short_def": "", + "synonyms": [ + "unhappy", + "heartbroken", + "depressed", + "miserable", + "sorry", + "bad", + "melancholy", + "upset" + ], + "antonyms": [ + "happy", + "glad", + "joyous", + "joyful", + "cheerful", + "cheery", + "jubilant", + "ecstatic" + ] + }, + { + "headword": "big", + "slug": "big", + "pos": "", + "short_def": "", + "synonyms": [ + "large", + "sizable", + "substantial", + "considerable", + "huge", + "great", + "oversize", + "handsome" + ], + "antonyms": [ + "small", + "little", + "smallish", + "puny", + "dwarf", + "tiny", + "dinky", + "undersized" + ] + }, + { + "headword": "small", + "slug": "small", + "pos": "", + "short_def": "", + "synonyms": [ + "little", + "diminutive", + "tiny", + "pocket", + "fine", + "slight", + "smallish", + "miniature" + ], + "antonyms": [ + "large", + "big", + "substantial", + "considerable", + "sizable", + "great", + "massive", + "huge" + ] + }, + { + "headword": "fast", + "slug": "fast", + "pos": "", + "short_def": "", + "synonyms": [ + "quickly", + "rapidly", + "quick", + "swiftly", + "hot", + "soon", + "swift", + "immediately" + ], + "antonyms": [ + "slowly", + "slow", + "sluggishly", + "deliberately", + "leisurely", + "lingeringly", + "ploddingly", + "belatedly" + ] + }, + { + "headword": "smart", + "slug": "smart", + "pos": "", + "short_def": "", + "synonyms": [ + "intelligent", + "wise", + "savvy", + "astute", + "shrewd", + "clever", + "sharp", + "brilliant" + ], + "antonyms": [ + "naive", + "ingenuous", + "innocent", + "guileless", + "gullible", + "unknowing", + "artless", + "unwise" + ] + }, + { + "headword": "beautiful", + "slug": "beautiful", + "pos": "", + "short_def": "", + "synonyms": [ + "lovely", + "gorgeous", + "cute", + "handsome", + "attractive", + "pretty", + "stunning", + "charming" + ], + "antonyms": [ + "ugly", + "plain", + "hideous", + "unattractive", + "grotesque", + "homely", + "unlovely", + "terrible" + ] + }, + { + "headword": "important", + "slug": "important", + "pos": "", + "short_def": "", + "synonyms": [ + "major", + "significant", + "historic", + "big", + "meaningful", + "much", + "substantial", + "tectonic" + ], + "antonyms": [ + "unimportant", + "small", + "trivial", + "insignificant", + "little", + "minor", + "negligible", + "inconsequential" + ] + }, + { + "headword": "difficult", + "slug": "difficult", + "pos": "", + "short_def": "", + "synonyms": [ + "challenging", + "tough", + "hard", + "rigorous", + "demanding", + "formidable", + "complicated", + "heavy" + ], + "antonyms": [ + "easy", + "simple", + "light", + "soft", + "cheap", + "effortless", + "clear", + "undemanding" + ] + }, + { + "headword": "strange", + "slug": "strange", + "pos": "", + "short_def": "", + "synonyms": [ + "bizarre", + "weird", + "odd", + "funny", + "peculiar", + "curious", + "erratic", + "remarkable" + ], + "antonyms": [ + "normal", + "ordinary", + "typical", + "usual", + "standard", + "average", + "commonplace", + "prosaic" + ] + }, + { + "headword": "brave", + "slug": "brave", + "pos": "", + "short_def": "", + "synonyms": [ + "courageous", + "fearless", + "valiant", + "heroic", + "gallant", + "bold", + "adventurous", + "intrepid" + ], + "antonyms": [ + "coward", + "cowardly", + "fearful", + "timid", + "yellow", + "timorous", + "craven", + "pusillanimous" + ] + }, + { + "headword": "calm", + "slug": "calm", + "pos": "", + "short_def": "", + "synonyms": [ + "quiet", + "tranquil", + "serene", + "peaceful", + "placid", + "hushed", + "still", + "untroubled" + ], + "antonyms": [ + "angry", + "turbulent", + "restless", + "agitated", + "stormy", + "unsettled", + "rough", + "tempestuous" + ] + }, + { + "headword": "good", + "slug": "good", + "pos": "", + "short_def": "", + "synonyms": [ + "pleasant", + "delightful", + "enjoyable", + "pleasing", + "nice", + "sweet", + "satisfying", + "welcome" + ], + "antonyms": [ + "unpleasant", + "disagreeable", + "miserable", + "horrid", + "unwelcome", + "unpalatable", + "abominable", + "ghastly" + ] + }, + { + "headword": "bad", + "slug": "bad", + "pos": "", + "short_def": "", + "synonyms": [ + "unacceptable", + "wrong", + "poor", + "lame", + "horrible", + "terrible", + "awful", + "disastrous" + ], + "antonyms": [ + "acceptable", + "adequate", + "satisfactory", + "decent", + "fine", + "great", + "standard", + "tolerable" + ] + }, + { + "headword": "old", + "slug": "old", + "pos": "", + "short_def": "", + "synonyms": [ + "elderly", + "senior", + "aging", + "aged", + "older", + "ancient", + "geriatric", + "over-the-hill" + ], + "antonyms": [ + "young", + "youthful", + "ageless", + "youngish", + "juvenile", + "immature", + "adolescent", + "minor" + ] + }, + { + "headword": "new", + "slug": "new", + "pos": "", + "short_def": "", + "synonyms": [ + "novel", + "unfamiliar", + "fresh", + "strange", + "unprecedented", + "original", + "unique", + "unknown" + ], + "antonyms": [ + "old", + "familiar", + "traditional", + "hackneyed", + "tired", + "conventional", + "established", + "time-honored" + ] + }, + { + "headword": "rich", + "slug": "rich", + "pos": "", + "short_def": "", + "synonyms": [ + "wealthy", + "affluent", + "opulent", + "well-to-do", + "moneyed", + "successful", + "well-endowed", + "well-off" + ], + "antonyms": [ + "poor", + "impoverished", + "needy", + "destitute", + "indigent", + "penniless", + "impecunious", + "penurious" + ] + }, + { + "headword": "poor", + "slug": "poor", + "pos": "", + "short_def": "", + "synonyms": [ + "impoverished", + "broke", + "deprived", + "needy", + "beggared", + "indigent", + "bankrupt", + "impecunious" + ], + "antonyms": [ + "rich", + "wealthy", + "affluent", + "fat", + "well-to-do", + "opulent", + "moneyed", + "flush" + ] + }, + { + "headword": "strong", + "slug": "strong", + "pos": "", + "short_def": "", + "synonyms": [ + "muscular", + "powerful", + "mighty", + "rugged", + "stout", + "sturdy", + "masculine", + "sinewy" + ], + "antonyms": [ + "weak", + "feeble", + "delicate", + "frail", + "disabled", + "wimpy", + "small", + "paralyzed" + ] + }, + { + "headword": "weak", + "slug": "weak", + "pos": "", + "short_def": "", + "synonyms": [ + "weakened", + "feeble", + "frail", + "disabled", + "faint", + "enfeebled", + "debilitated", + "wimpy" + ], + "antonyms": [ + "strong", + "powerful", + "mighty", + "stout", + "muscular", + "rugged", + "tough", + "athletic" + ] + }, + { + "headword": "kind", + "slug": "kind", + "pos": "", + "short_def": "", + "synonyms": [ + "compassionate", + "benevolent", + "thoughtful", + "sympathetic", + "gentle", + "kindly", + "humane", + "nice" + ], + "antonyms": [ + "cruel", + "brutal", + "vicious", + "unkind", + "savage", + "inhuman", + "sadistic", + "callous" + ] + }, + { + "headword": "honest", + "slug": "honest", + "pos": "", + "short_def": "", + "synonyms": [ + "truthful", + "reliable", + "outspoken", + "genuine", + "credible", + "forthright", + "frank", + "candid" + ], + "antonyms": [ + "dishonest", + "lying", + "untruthful", + "false", + "mendacious", + "untrue", + "unscrupulous", + "unreliable" + ] + }, + { + "headword": "quiet", + "slug": "quiet", + "pos": "", + "short_def": "", + "synonyms": [ + "peaceful", + "serene", + "calm", + "placid", + "restful", + "tranquil", + "hushed", + "silent" + ], + "antonyms": [ + "loud", + "noisy", + "boisterous", + "raucous", + "rowdy", + "tumultuous", + "deafening", + "clamorous" + ] + }, + { + "headword": "bright", + "slug": "bright", + "pos": "", + "short_def": "", + "synonyms": [ + "shining", + "luminous", + "dazzling", + "glowing", + "shiny", + "radiant", + "brilliant", + "gleaming" + ], + "antonyms": [ + "dim", + "dull", + "dark", + "gloomy", + "lackluster", + "darkened", + "dusky", + "somber" + ] + }, + { + "headword": "clever", + "slug": "clever", + "pos": "", + "short_def": "", + "synonyms": [ + "innovative", + "imaginative", + "inventive", + "creative", + "ingenious", + "useful", + "practical", + "artful" + ], + "antonyms": [ + "unimaginative", + "uncreative", + "dull", + "pedantic", + "stodgy", + "derivative", + "pedestrian", + "useless" + ] + }, + { + "headword": "generous", + "slug": "generous", + "pos": "", + "short_def": "", + "synonyms": [ + "charitable", + "liberal", + "benevolent", + "bountiful", + "handsome", + "munificent", + "open", + "unselfish" + ], + "antonyms": [ + "ungenerous", + "stingy", + "selfish", + "small", + "parsimonious", + "miserly", + "cheap", + "uncharitable" + ] + }, + { + "headword": "careful", + "slug": "careful", + "pos": "", + "short_def": "", + "synonyms": [ + "cautious", + "wary", + "alert", + "circumspect", + "considerate", + "conservative", + "chary", + "guarded" + ], + "antonyms": [ + "careless", + "reckless", + "unsafe", + "unmindful", + "bold", + "regardless", + "heedless", + "impetuous" + ] + }, + { + "headword": "eager", + "slug": "eager", + "pos": "", + "short_def": "", + "synonyms": [ + "excited", + "enthusiastic", + "avid", + "anxious", + "keen", + "hungry", + "ardent", + "impatient" + ], + "antonyms": [ + "indifferent", + "apathetic", + "unenthusiastic", + "uninterested", + "casual", + "unconcerned", + "disinterested", + "nonchalant" + ] + }, + { + "headword": "humble", + "slug": "humble", + "pos": "", + "short_def": "", + "synonyms": [ + "meek", + "modest", + "unassuming", + "unaffected", + "lowly", + "timid", + "down-to-earth", + "unpretentious" + ], + "antonyms": [ + "arrogant", + "haughty", + "superior", + "pretentious", + "pompous", + "conceited", + "presumptuous", + "supercilious" + ] + }, + { + "headword": "proud", + "slug": "proud", + "pos": "", + "short_def": "", + "synonyms": [ + "arrogant", + "superior", + "smug", + "disdainful", + "prideful", + "haughty", + "cavalier", + "confident" + ], + "antonyms": [ + "humble", + "modest", + "lowly", + "meek", + "timid", + "unassuming", + "shy", + "homely" + ] + } +] + +WORD_OF_THE_DAY = [ + { + "feature_offset": 0, + "headword": "serendipity", + "pos": "noun", + "pronunciation": "ˌser-ən-ˈdi-pə-tē", + "definition": "the ability to find valuable or agreeable things not sought for", + "did_you_know": "Serendip, variant of Sarandīb, Persian and Arabic name for Sri Lanka + -ity; from its possession by the heroes of the Persian fairy tale The Three Princes of Serendip", + "examples": [ + "… materials researchers, who now rely mostly on rules of thumb, trial-and-error and serendipity", + "… [Sh'Kia] Augustin landed the role through the charmed stroke of serendipity" + ] + }, + { + "feature_offset": 1, + "headword": "ubiquitous", + "pos": "adjective", + "pronunciation": "yü-ˈbi-kwə-təs", + "definition": "existing or being everywhere at the same time : constantly encountered : widespread", + "did_you_know": "see ubiquity", + "examples": [ + "The company's ads are ubiquitous", + "Florals are a ubiquitous" + ] + }, + { + "feature_offset": 2, + "headword": "ephemeral", + "pos": "adjective", + "pronunciation": "i-ˈfe-mə-rəl", + "definition": "lasting a very short time", + "did_you_know": "Greek ephēmeros lasting a day, daily, from epi- + hēmera day", + "examples": [ + "Their fame turned out to be ephemeral" + ] + }, + { + "feature_offset": 3, + "headword": "gregarious", + "pos": "adjective", + "pronunciation": "gri-ˈger-ē-əs", + "definition": "enjoying the company of others : marked by or showing a liking for companionship : sociable", + "did_you_know": "Latin gregarius of a flock or herd, from greg-, grex flock, herd", + "examples": [ + "is friendly, outgoing, and gregarious" + ] + }, + { + "feature_offset": 4, + "headword": "meticulous", + "pos": "adjective", + "pronunciation": "mə-ˈti-kyə-ləs", + "definition": "very careful about doing something in an extremely accurate and exact way", + "did_you_know": "earlier, \"fearful,\" borrowed from Latin metīculōsus, metūculōsus \"timid, apprehensive,\" from metū-, stem of metus \"fear, dread\" (of uncertain origin) + -culōsus (in perīculōsus \"involving danger, perilous\")", + "examples": [ + "He is meticulous", + "… all the things they had thought through so meticulously—fell apart." + ] + }, + { + "feature_offset": 5, + "headword": "ambivalent", + "pos": "adjective", + "pronunciation": "am-ˈbi-və-lənt", + "definition": "having or showing simultaneous and contradictory attitudes or feelings toward something or someone : characterized by ambivalence", + "did_you_know": "borrowed from German, from ambi- ambi- + -valent, in äquivalent equivalent", + "examples": [ + "… people whose relationship to their job is ambivalent", + "Americans are deeply ambivalent" + ] + }, + { + "feature_offset": 6, + "headword": "pragmatic", + "pos": "adjective", + "pronunciation": "prag-ˈma-tik", + "definition": "dealing with the problems that exist in a specific situation in a reasonable and logical way instead of depending on ideas and theories : practical as opposed to idealistic", + "did_you_know": "Latin pragmaticus skilled in law or business, from Greek pragmatikos, from pragmat-, pragma deed, from prassein to do — more at practical", + "examples": [ + "At its core the Marshall Plan was a pragmatic" + ] + }, + { + "feature_offset": 7, + "headword": "resilient", + "pos": "adjective", + "pronunciation": "ri-ˈzil-yənt", + "definition": "characterized or marked by resilience: such as", + "did_you_know": "Latin resilient-, resiliens, present participle of resilire to jump back, recoil, from re- + salire to leap — more at sally", + "examples": [ + "The tallow tree, an ornamental species introduced by Benjamin Franklin in 1772, can quickly grow to 10 metres and is resilient" + ] + } +] + +QUIZZES = [ + { + "title": "Name That Word", + "slug": "name-that-word", + "description": "Read the definition and pick the word it defines.", + "difficulty": "easy", + "questions": [ + { + "q": "Which word means: \"to seriously or continually try (to do something)\"?", + "choices": [ + "endeavor", + "perseverance", + "resilient", + "pragmatic" + ], + "answer_index": 0, + "explanation": "endeavor (verb): to seriously or continually try (to do something)." + }, + { + "q": "Which word means: \"having or showing simultaneous and contradictory attitudes or feelings toward something or someone : characterized by ambivalence\"?", + "choices": [ + "astute", + "cordial", + "ambivalent", + "eager" + ], + "answer_index": 2, + "explanation": "ambivalent (adjective): having or showing simultaneous and contradictory attitudes or feelings toward something or someone : characterized by ambivalence." + }, + { + "q": "Which word means: \"the realization and understanding that all other people have lives as complex as one's own\"?", + "choices": [ + "sonder", + "ubiquitous", + "elated", + "diligence" + ], + "answer_index": 0, + "explanation": "sonder (noun): the realization and understanding that all other people have lives as complex as one's own." + }, + { + "q": "Which word means: \"serenely free of interruption or disturbance\"?", + "choices": [ + "courage", + "placid", + "veracity", + "ingenious" + ], + "answer_index": 1, + "explanation": "placid (adjective): serenely free of interruption or disturbance." + }, + { + "q": "Which word means: \"characterized by resolute fearlessness, fortitude, and endurance\"?", + "choices": [ + "intrepid", + "equanimity", + "radiant", + "stoic" + ], + "answer_index": 0, + "explanation": "intrepid (adjective): characterized by resolute fearlessness, fortitude, and endurance." + }, + { + "q": "Which word means: \"to move about without a fixed course, aim, or goal\"?", + "choices": [ + "wander", + "innovate", + "succinct", + "generous" + ], + "answer_index": 0, + "explanation": "wander (verb): to move about without a fixed course, aim, or goal." + }, + { + "q": "Which word means: \"mental or moral strength to venture, persevere, and withstand danger, fear, or difficulty\"?", + "choices": [ + "wistful", + "courage", + "integrity", + "fastidious" + ], + "answer_index": 1, + "explanation": "courage (noun): mental or moral strength to venture, persevere, and withstand danger, fear, or difficulty." + }, + { + "q": "Which word means: \"marked by wary caution or prudence\"?", + "choices": [ + "careful", + "dauntless", + "luminous", + "ineffable" + ], + "answer_index": 0, + "explanation": "careful (adjective): marked by wary caution or prudence." + }, + { + "q": "Which word means: \"a doctrine that this world is the best possible world\"?", + "choices": [ + "optimism", + "fast", + "garrulous", + "content" + ], + "answer_index": 0, + "explanation": "optimism (noun): a doctrine that this world is the best possible world." + }, + { + "q": "Which word means: \"showing or marked by warm and often hearty friendliness, favor, or approval\"?", + "choices": [ + "cheerful", + "cordial", + "radiant", + "jovial" + ], + "answer_index": 1, + "explanation": "cordial (adjective): showing or marked by warm and often hearty friendliness, favor, or approval." + } + ] + }, + { + "title": "Synonym Showdown", + "slug": "synonym-showdown", + "description": "Pick the best synonym for each word.", + "difficulty": "medium", + "questions": [ + { + "q": "Which word is a synonym of \"good\"?", + "choices": [ + "excited", + "pleasant", + "unhappy", + "cautious" + ], + "answer_index": 1, + "explanation": "A synonym of good is pleasant." + }, + { + "q": "Which word is a synonym of \"difficult\"?", + "choices": [ + "challenging", + "lovely", + "large", + "weakened" + ], + "answer_index": 0, + "explanation": "A synonym of difficult is challenging." + }, + { + "q": "Which word is a synonym of \"kind\"?", + "choices": [ + "truthful", + "arrogant", + "pleasant", + "compassionate" + ], + "answer_index": 3, + "explanation": "A synonym of kind is compassionate." + }, + { + "q": "Which word is a synonym of \"quiet\"?", + "choices": [ + "quickly", + "major", + "peaceful", + "innovative" + ], + "answer_index": 2, + "explanation": "A synonym of quiet is peaceful." + }, + { + "q": "Which word is a synonym of \"poor\"?", + "choices": [ + "weakened", + "impoverished", + "arrogant", + "pleasant" + ], + "answer_index": 1, + "explanation": "A synonym of poor is impoverished." + }, + { + "q": "Which word is a synonym of \"important\"?", + "choices": [ + "wealthy", + "large", + "major", + "impoverished" + ], + "answer_index": 2, + "explanation": "A synonym of important is major." + }, + { + "q": "Which word is a synonym of \"honest\"?", + "choices": [ + "intelligent", + "compassionate", + "cautious", + "truthful" + ], + "answer_index": 3, + "explanation": "A synonym of honest is truthful." + }, + { + "q": "Which word is a synonym of \"brave\"?", + "choices": [ + "compassionate", + "unacceptable", + "courageous", + "novel" + ], + "answer_index": 2, + "explanation": "A synonym of brave is courageous." + }, + { + "q": "Which word is a synonym of \"sad\"?", + "choices": [ + "peaceful", + "innovative", + "unhappy", + "quickly" + ], + "answer_index": 2, + "explanation": "A synonym of sad is unhappy." + }, + { + "q": "Which word is a synonym of \"clever\"?", + "choices": [ + "unacceptable", + "little", + "innovative", + "bizarre" + ], + "answer_index": 2, + "explanation": "A synonym of clever is innovative." + } + ] + }, + { + "title": "Opposites Attract", + "slug": "opposites-attract", + "description": "Choose the opposite of each word.", + "difficulty": "medium", + "questions": [ + { + "q": "Which word is an antonym (opposite) of \"bright\"?", + "choices": [ + "cruel", + "dim", + "normal", + "indifferent" + ], + "answer_index": 1, + "explanation": "An antonym of bright is dim." + }, + { + "q": "Which word is an antonym (opposite) of \"humble\"?", + "choices": [ + "arrogant", + "naive", + "rich", + "unimaginative" + ], + "answer_index": 0, + "explanation": "An antonym of humble is arrogant." + }, + { + "q": "Which word is an antonym (opposite) of \"proud\"?", + "choices": [ + "humble", + "old", + "unhappy", + "large" + ], + "answer_index": 0, + "explanation": "An antonym of proud is humble." + }, + { + "q": "Which word is an antonym (opposite) of \"difficult\"?", + "choices": [ + "humble", + "unimportant", + "easy", + "happy" + ], + "answer_index": 2, + "explanation": "An antonym of difficult is easy." + }, + { + "q": "Which word is an antonym (opposite) of \"rich\"?", + "choices": [ + "ungenerous", + "poor", + "indifferent", + "small" + ], + "answer_index": 1, + "explanation": "An antonym of rich is poor." + }, + { + "q": "Which word is an antonym (opposite) of \"clever\"?", + "choices": [ + "naive", + "easy", + "rich", + "unimaginative" + ], + "answer_index": 3, + "explanation": "An antonym of clever is unimaginative." + }, + { + "q": "Which word is an antonym (opposite) of \"smart\"?", + "choices": [ + "dim", + "naive", + "coward", + "acceptable" + ], + "answer_index": 1, + "explanation": "An antonym of smart is naive." + }, + { + "q": "Which word is an antonym (opposite) of \"generous\"?", + "choices": [ + "unimportant", + "ungenerous", + "large", + "small" + ], + "answer_index": 1, + "explanation": "An antonym of generous is ungenerous." + }, + { + "q": "Which word is an antonym (opposite) of \"small\"?", + "choices": [ + "easy", + "large", + "unhappy", + "strong" + ], + "answer_index": 1, + "explanation": "An antonym of small is large." + }, + { + "q": "Which word is an antonym (opposite) of \"kind\"?", + "choices": [ + "humble", + "cruel", + "small", + "unimportant" + ], + "answer_index": 1, + "explanation": "An antonym of kind is cruel." + } + ] + } +] diff --git a/sites/merriam_webster/app.py b/sites/merriam_webster/app.py new file mode 100644 index 00000000..18da7f90 --- /dev/null +++ b/sites/merriam_webster/app.py @@ -0,0 +1,565 @@ +#!/usr/bin/env python3 +"""Merriam-Webster mirror — Flask application. + +Routes + SQLAlchemy models for an offline mirror of merriam-webster.com. +Runtime data comes entirely from instance/merriam_webster.db (seeded from +instance_seed/merriam_webster.db at boot). No JSON read at request time. +""" +import os +import re +import json +import random +from datetime import datetime, date + +from flask import (Flask, render_template, request, redirect, url_for, + flash, jsonify, session, abort) +from flask_sqlalchemy import SQLAlchemy +from flask_login import (LoginManager, UserMixin, login_user, logout_user, + login_required, current_user) +from flask_wtf import FlaskForm +from flask_wtf.csrf import CSRFProtect +from flask_bcrypt import Bcrypt +from wtforms import StringField, PasswordField +from wtforms.validators import DataRequired, Email, Length, EqualTo +from sqlalchemy import or_, func + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'merriam-webster-mirror-secret-key' +app.config['SQLALCHEMY_DATABASE_URI'] = ( + f"sqlite:///{os.path.join(BASE_DIR, 'instance', 'merriam_webster.db')}" +) +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config['WTF_CSRF_TIME_LIMIT'] = None + +os.makedirs(os.path.join(BASE_DIR, 'instance'), exist_ok=True) + +db = SQLAlchemy(app) +bcrypt = Bcrypt(app) +login_manager = LoginManager(app) +login_manager.login_view = 'login' +login_manager.login_message = 'Please log in to access your account.' +login_manager.login_message_category = 'info' +csrf = CSRFProtect(app) + +STOPWORDS = {'a', 'an', 'the', 'of', 'to', 'in', 'on', 'for', 'and', 'or', + 'is', 'are', 'be', 'with', 'as', 'by', 'at', 'from'} + + +# --------------------------------------------------------------------------- +# Models +# --------------------------------------------------------------------------- + +class User(db.Model, UserMixin): + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(120), unique=True, nullable=False, index=True) + username = db.Column(db.String(80), unique=True, nullable=False) + password_hash = db.Column(db.String(255), nullable=False) + name = db.Column(db.String(120), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + saved_words = db.relationship('SavedWord', backref='user', lazy=True, + cascade='all, delete-orphan') + search_history = db.relationship('SearchHistory', backref='user', + lazy=True, cascade='all, delete-orphan') + quiz_scores = db.relationship('QuizScore', backref='user', lazy=True, + cascade='all, delete-orphan') + + def set_password(self, pw): + self.password_hash = bcrypt.generate_password_hash(pw).decode('utf-8') + + def check_password(self, pw): + return bcrypt.check_password_hash(self.password_hash, pw) + + +class Word(db.Model): + __tablename__ = 'words' + id = db.Column(db.Integer, primary_key=True) + headword = db.Column(db.String(120), nullable=False, index=True) + slug = db.Column(db.String(120), unique=True, nullable=False, index=True) + pos = db.Column(db.String(40), default='') # noun, verb, ... + pronunciation = db.Column(db.String(120), default='') # MW respelling + syllables = db.Column(db.String(120), default='') # se·ren·dip·i·ty + first_known_use = db.Column(db.String(200), default='') + etymology = db.Column(db.Text, default='') + # definitions: JSON list of {sense_num, text, examples:[str]} + definitions_json = db.Column(db.Text, default='[]') + # related synonyms shown on the dictionary page (slugs) + synonyms_json = db.Column(db.Text, default='[]') + difficulty = db.Column(db.String(20), default='common') # common|advanced + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + saved_by = db.relationship('SavedWord', backref='word', lazy=True, + cascade='all, delete-orphan') + + def get_definitions(self): + try: + return json.loads(self.definitions_json or '[]') + except Exception: + return [] + + def get_synonyms(self): + try: + return json.loads(self.synonyms_json or '[]') + except Exception: + return [] + + def all_examples(self): + out = [] + for d in self.get_definitions(): + out.extend(d.get('examples') or []) + return out + + +class ThesaurusEntry(db.Model): + __tablename__ = 'thesaurus_entries' + id = db.Column(db.Integer, primary_key=True) + headword = db.Column(db.String(120), nullable=False, index=True) + slug = db.Column(db.String(120), unique=True, nullable=False, index=True) + pos = db.Column(db.String(40), default='') + short_def = db.Column(db.Text, default='') + synonyms_json = db.Column(db.Text, default='[]') + antonyms_json = db.Column(db.Text, default='[]') + + def get_synonyms(self): + try: + return json.loads(self.synonyms_json or '[]') + except Exception: + return [] + + def get_antonyms(self): + try: + return json.loads(self.antonyms_json or '[]') + except Exception: + return [] + + +class WordOfTheDay(db.Model): + __tablename__ = 'word_of_the_day' + id = db.Column(db.Integer, primary_key=True) + feature_date = db.Column(db.Date, unique=True, nullable=False, index=True) + headword = db.Column(db.String(120), nullable=False) + pos = db.Column(db.String(40), default='') + pronunciation = db.Column(db.String(120), default='') + definition = db.Column(db.Text, default='') + did_you_know = db.Column(db.Text, default='') + examples_json = db.Column(db.Text, default='[]') + + def get_examples(self): + try: + return json.loads(self.examples_json or '[]') + except Exception: + return [] + + +class SavedWord(db.Model): + __tablename__ = 'saved_words' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + word_id = db.Column(db.Integer, db.ForeignKey('words.id'), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + +class SearchHistory(db.Model): + __tablename__ = 'search_history' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + term = db.Column(db.String(200), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + +class Quiz(db.Model): + __tablename__ = 'quizzes' + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(200), nullable=False) + slug = db.Column(db.String(200), unique=True, nullable=False, index=True) + description = db.Column(db.Text, default='') + difficulty = db.Column(db.String(20), default='easy') + # questions: JSON list of {q, choices:[str], answer_index, explanation} + questions_json = db.Column(db.Text, default='[]') + + def get_questions(self): + try: + return json.loads(self.questions_json or '[]') + except Exception: + return [] + + +class QuizScore(db.Model): + __tablename__ = 'quiz_scores' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + quiz_id = db.Column(db.Integer, db.ForeignKey('quizzes.id'), nullable=False) + score = db.Column(db.Integer, default=0) + total = db.Column(db.Integer, default=0) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + +@login_manager.user_loader +def load_user(user_id): + return db.session.get(User, int(user_id)) + + +# --------------------------------------------------------------------------- +# Forms +# --------------------------------------------------------------------------- + +class RegisterForm(FlaskForm): + name = StringField('Name', validators=[DataRequired(), Length(max=120)]) + username = StringField('Username', validators=[DataRequired(), Length(min=3, max=80)]) + email = StringField('Email', validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired(), Length(min=6)]) + confirm = PasswordField('Confirm Password', + validators=[DataRequired(), EqualTo('password')]) + + +class LoginForm(FlaskForm): + email = StringField('Email', validators=[DataRequired()]) + password = PasswordField('Password', validators=[DataRequired()]) + + +# --------------------------------------------------------------------------- +# Search helpers (token-overlap scoring, not strict AND) +# --------------------------------------------------------------------------- + +def _tokens(q): + return [t.lower() for t in re.findall(r'[a-z0-9]+', q.lower()) + if t not in STOPWORDS and len(t) >= 2] + + +def _score_word(w, tokens): + hay = ' '.join([ + w.headword.lower(), w.pos.lower(), + ' '.join(d.get('text', '') for d in w.get_definitions()).lower(), + ]) + score = 0 + for t in tokens: + if w.headword.lower() == t: + score += 5 + elif w.headword.lower().startswith(t): + score += 3 + elif t in hay: + score += 1 + return score + + +def search_words(q): + tokens = _tokens(q) + if not tokens: + return [] + exact = Word.query.filter(Word.headword.ilike(q.strip())).all() + partial = Word.query.filter( + or_(Word.headword.ilike(f'%{tokens[0]}%'), + Word.definitions_json.ilike(f'%{tokens[0]}%')) + ).limit(60).all() + seen = {w.id for w in exact} + combined = exact + [w for w in partial if w.id not in seen] + min_req = max(1, len(tokens) // 2) + scored = [(s, w) for w in combined + if (s := _score_word(w, tokens)) >= min_req] + + # Synonym-aware recall: if the query matches a thesaurus headword, surface + # dictionary entries for its synonyms too. A search for "happy" should + # then return its near-synonyms (elated, cheerful, ...) as related hits, + # so a definition-fragment search has real distractors instead of one hit. + syn_words = [] + if scored: + thes = ThesaurusEntry.query.filter( + ThesaurusEntry.headword.ilike(q.strip())).first() + if thes: + scored_ids = {w.id for _, w in scored} + for syn in thes.get_synonyms(): + w = Word.query.filter(func.lower(Word.headword) == syn.lower()).first() + if w and w.id not in scored_ids: + syn_words.append(w) + scored_ids.add(w.id) + + scored.sort(key=lambda x: (-x[0], x[1].headword)) + return [w for _, w in scored] + syn_words + + +def todays_wotd(): + """Return today's WOTD, falling back to a deterministic rotation so the + homepage always has one even on dates with no explicit row.""" + today = date.today() + w = WordOfTheDay.query.filter_by(feature_date=today).first() + if w: + return w + rows = WordOfTheDay.query.order_by(WordOfTheDay.id).all() + if not rows: + return None + return rows[today.toordinal() % len(rows)] + + +# --------------------------------------------------------------------------- +# Routes — core +# --------------------------------------------------------------------------- + +@app.route('/') +def index(): + wotd = todays_wotd() + trending = Word.query.order_by(func.random()).limit(8).all() + browse = Word.query.order_by(Word.headword).limit(12).all() + return render_template('index.html', wotd=wotd, trending=trending, + browse=browse) + + +@app.route('/dictionary/') +def word_detail(slug): + word = Word.query.filter_by(slug=slug).first_or_404() + if current_user.is_authenticated: + db.session.add(SearchHistory(user_id=current_user.id, + term=word.headword)) + db.session.commit() + is_saved = False + if current_user.is_authenticated: + is_saved = SavedWord.query.filter_by( + user_id=current_user.id, word_id=word.id).first() is not None + prev_w = (Word.query.filter(Word.headword < word.headword) + .order_by(Word.headword.desc()).limit(4).all()) + next_w = (Word.query.filter(Word.headword > word.headword) + .order_by(Word.headword.asc()).limit(4).all()) + nearby = list(reversed(prev_w)) + [word] + next_w + thes = ThesaurusEntry.query.filter( + func.lower(ThesaurusEntry.headword) == word.headword.lower()).first() + return render_template('word_detail.html', word=word, is_saved=is_saved, + nearby=nearby, thes=thes) + + +@app.route('/thesaurus/') +def thesaurus_detail(slug): + entry = ThesaurusEntry.query.filter_by(slug=slug).first() + if not entry: + entry = ThesaurusEntry.query.filter( + ThesaurusEntry.headword.ilike(slug.replace('-', ' '))).first() + if not entry: + abort(404) + word = Word.query.filter( + func.lower(Word.headword) == entry.headword.lower()).first() + return render_template('thesaurus_detail.html', entry=entry, word=word) + + +@app.route('/search') +def search(): + q = request.args.get('q', '').strip() + stype = request.args.get('type', 'dictionary') + if not q: + return render_template('search.html', q='', results=[], + thes_results=[], stype=stype) + + exact = Word.query.filter(Word.headword.ilike(q)).first() + if exact and stype == 'dictionary': + if current_user.is_authenticated: + db.session.add(SearchHistory(user_id=current_user.id, term=q)) + db.session.commit() + return redirect(url_for('word_detail', slug=exact.slug)) + + results = search_words(q) + thes_results = ThesaurusEntry.query.filter( + ThesaurusEntry.headword.ilike(f'%{q}%')).limit(10).all() + + if current_user.is_authenticated: + db.session.add(SearchHistory(user_id=current_user.id, term=q)) + db.session.commit() + return render_template('search.html', q=q, results=results, + thes_results=thes_results, stype=stype) + + +@app.route('/autocomplete') +@csrf.exempt +def autocomplete(): + q = request.args.get('q', '').strip().lower() + if len(q) < 2: + return jsonify([]) + words = Word.query.filter(Word.headword.ilike(f'{q}%')).limit(8).all() + return jsonify([w.headword for w in words]) + + +@app.route('/word-of-the-day') +@app.route('/word-of-the-day/') +def word_of_the_day(slug=None): + if slug: + wotd = WordOfTheDay.query.filter( + func.lower(WordOfTheDay.headword) == slug.lower()).first_or_404() + else: + wotd = todays_wotd() + if not wotd: + abort(404) + recent = (WordOfTheDay.query + .filter(WordOfTheDay.id != wotd.id) + .order_by(WordOfTheDay.feature_date.desc()).limit(6).all()) + return render_template('wotd.html', wotd=wotd, recent=recent) + + +# --------------------------------------------------------------------------- +# Routes — games & quizzes +# --------------------------------------------------------------------------- + +@app.route('/games-quizzes') +def games_quizzes(): + quizzes = Quiz.query.all() + return render_template('games_index.html', quizzes=quizzes) + + +@app.route('/quiz/') +def quiz_detail(slug): + quiz = Quiz.query.filter_by(slug=slug).first_or_404() + return render_template('quiz.html', quiz=quiz, + questions=quiz.get_questions()) + + +@app.route('/quiz//submit', methods=['POST']) +def quiz_submit(slug): + quiz = Quiz.query.filter_by(slug=slug).first_or_404() + questions = quiz.get_questions() + score = 0 + review = [] + for i, qq in enumerate(questions): + picked = request.form.get(f'q{i}') + correct = qq.get('answer_index') + ok = picked is not None and int(picked) == correct + if ok: + score += 1 + review.append({ + 'q': qq.get('q'), + 'choices': qq.get('choices', []), + 'picked': int(picked) if picked is not None else None, + 'answer_index': correct, + 'explanation': qq.get('explanation', ''), + 'correct': ok, + }) + total = len(questions) + if current_user.is_authenticated: + db.session.add(QuizScore(user_id=current_user.id, quiz_id=quiz.id, + score=score, total=total)) + db.session.commit() + return render_template('quiz_result.html', quiz=quiz, score=score, + total=total, review=review) + + +# --------------------------------------------------------------------------- +# Routes — auth & account +# --------------------------------------------------------------------------- + +@app.route('/register', methods=['GET', 'POST']) +def register(): + if current_user.is_authenticated: + return redirect(url_for('index')) + form = RegisterForm() + if form.validate_on_submit(): + if User.query.filter_by(email=form.email.data.lower()).first(): + flash('That email is already registered.', 'error') + elif User.query.filter_by(username=form.username.data).first(): + flash('That username is taken.', 'error') + else: + u = User(email=form.email.data.lower(), + username=form.username.data, name=form.name.data) + u.set_password(form.password.data) + db.session.add(u) + db.session.commit() + login_user(u) + flash('Welcome to Merriam-Webster!', 'success') + return redirect(url_for('index')) + return render_template('register.html', form=form) + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('index')) + form = LoginForm() + if form.validate_on_submit(): + ident = form.email.data.strip().lower() + user = (User.query.filter_by(email=ident).first() + or User.query.filter(func.lower(User.username) == ident).first()) + if user and user.check_password(form.password.data): + login_user(user) + flash('Logged in successfully.', 'success') + return redirect(request.args.get('next') or url_for('index')) + flash('Invalid email or password.', 'error') + return render_template('login.html', form=form) + + +@app.route('/logout') +def logout(): + logout_user() + flash('You have been logged out.', 'info') + return redirect(url_for('index')) + + +@app.route('/account') +@login_required +def account(): + saved = (SavedWord.query.filter_by(user_id=current_user.id) + .order_by(SavedWord.created_at.desc()).all()) + history = (SearchHistory.query.filter_by(user_id=current_user.id) + .order_by(SearchHistory.created_at.desc()).limit(20).all()) + scores = (QuizScore.query.filter_by(user_id=current_user.id) + .order_by(QuizScore.created_at.desc()).limit(10).all()) + return render_template('account.html', saved=saved, history=history, + scores=scores) + + +@app.route('/account/saved-words//remove', methods=['POST']) +@login_required +def remove_saved(sw_id): + sw = SavedWord.query.filter_by(id=sw_id, user_id=current_user.id).first_or_404() + db.session.delete(sw) + db.session.commit() + flash('Removed from your saved words.', 'info') + return redirect(url_for('account')) + + +@app.route('/words//save', methods=['POST']) +@login_required +def save_word(word_id): + word = db.session.get(Word, word_id) + if not word: + abort(404) + existing = SavedWord.query.filter_by(user_id=current_user.id, + word_id=word.id).first() + if not existing: + db.session.add(SavedWord(user_id=current_user.id, word_id=word.id)) + db.session.commit() + flash(f'Saved "{word.headword}" to your words.', 'success') + return redirect(request.referrer or url_for('word_detail', slug=word.slug)) + + +@app.route('/_health') +def health(): + return {'ok': True, 'site': 'merriam_webster', + 'words': Word.query.count()} + + +@app.errorhandler(404) +def not_found(e): + return render_template('404.html'), 404 + + +@app.errorhandler(500) +def server_error(e): + return render_template('500.html'), 500 + + +@app.context_processor +def inject_globals(): + return {'current_year': datetime.now().year} + + +# --------------------------------------------------------------------------- +# Bootstrap +# --------------------------------------------------------------------------- + +from seed_data import seed_database, seed_benchmark_users # noqa: E402 + +with app.app_context(): + db.create_all() + seed_database(db, Word, ThesaurusEntry, WordOfTheDay, Quiz) + seed_benchmark_users(db, User, bcrypt, Word, SavedWord) + + +if __name__ == '__main__': + port = int(os.environ.get('PORT', 5000)) + app.run(host='0.0.0.0', port=port, debug=False) diff --git a/sites/merriam_webster/requirements.txt b/sites/merriam_webster/requirements.txt new file mode 100644 index 00000000..a28b4a40 --- /dev/null +++ b/sites/merriam_webster/requirements.txt @@ -0,0 +1,9 @@ +Flask +Flask-SQLAlchemy +Flask-Login +Flask-WTF +Flask-Bcrypt +Werkzeug +SQLAlchemy +WTForms +email-validator diff --git a/sites/merriam_webster/seed_data.py b/sites/merriam_webster/seed_data.py new file mode 100644 index 00000000..53e73fe9 --- /dev/null +++ b/sites/merriam_webster/seed_data.py @@ -0,0 +1,110 @@ +"""Idempotent seed for the Merriam-Webster mirror. + +Runs at every container boot and every /reset/merriam_webster. Each seed +function early-returns on a populated DB so that re-seeding is a no-op and the +byte-identical reset invariant holds (a bare commit on a populated DB still +bumps SQLite metadata). +""" +import json +from datetime import date, timedelta + +from _seed_content import WORDS, THESAURUS, WORD_OF_THE_DAY, QUIZZES + +# Fixed reference date so WORD_OF_THE_DAY rows are deterministic across boots +# and resets (using date.today() would make the seed DB differ day to day). +WOTD_ANCHOR = date(2025, 1, 15) + + +def seed_database(db, Word, ThesaurusEntry, WordOfTheDay, Quiz): + if Word.query.count() > 0: + return + + for w in WORDS: + db.session.add(Word( + headword=w['headword'], + slug=w['slug'], + pos=w['pos'], + pronunciation=w['pronunciation'], + syllables=w.get('syllables', ''), + first_known_use=w.get('first_known_use', ''), + etymology=w.get('etymology', ''), + definitions_json=json.dumps(w['definitions'], ensure_ascii=False), + synonyms_json=json.dumps(w.get('synonyms', []), ensure_ascii=False), + difficulty=w.get('difficulty', 'common'), + )) + + for t in THESAURUS: + db.session.add(ThesaurusEntry( + headword=t['headword'], + slug=t['slug'], + pos=t.get('pos', ''), + short_def=t.get('short_def', ''), + synonyms_json=json.dumps(t.get('synonyms', []), ensure_ascii=False), + antonyms_json=json.dumps(t.get('antonyms', []), ensure_ascii=False), + )) + + for w in WORD_OF_THE_DAY: + feature_date = WOTD_ANCHOR - timedelta(days=w['feature_offset']) + db.session.add(WordOfTheDay( + feature_date=feature_date, + headword=w['headword'], + pos=w.get('pos', ''), + pronunciation=w.get('pronunciation', ''), + definition=w.get('definition', ''), + did_you_know=w.get('did_you_know', ''), + examples_json=json.dumps(w.get('examples', []), ensure_ascii=False), + )) + + for qz in QUIZZES: + db.session.add(Quiz( + title=qz['title'], + slug=qz['slug'], + description=qz.get('description', ''), + difficulty=qz.get('difficulty', 'easy'), + questions_json=json.dumps(qz['questions'], ensure_ascii=False), + )) + + db.session.commit() + print(f"Seeded {len(WORDS)} words, {len(THESAURUS)} thesaurus entries, " + f"{len(WORD_OF_THE_DAY)} WOTD, {len(QUIZZES)} quizzes.") + + +# The primary benchmark account. Login-flow tasks reference this one; the +# others exist so account-listing pages aren't trivially single-row. +PRIMARY_ACCOUNT = {'email': 'alice.j@test.com', 'username': 'alicej', + 'name': 'Alice Johnson', 'password': 'TestPass123!'} + +BENCHMARK_USERS = [ + PRIMARY_ACCOUNT, + {'email': 'bob.smith@test.com', 'username': 'bobsmith', + 'name': 'Bob Smith', 'password': 'TestPass123!'}, + {'email': 'carol.w@test.com', 'username': 'carolw', + 'name': 'Carol Williams', 'password': 'TestPass123!'}, + {'email': 'david.b@test.com', 'username': 'davidb', + 'name': 'David Brown', 'password': 'TestPass123!'}, +] + + +# Words the primary account already has in its list at reset time, so that +# "view/clean up my saved words" disambiguation tasks have ≥2 ambiguous items. +PRIMARY_SAVED_SLUGS = ['eloquent', 'curiosity', 'harmony'] + + +def seed_benchmark_users(db, User, bcrypt, Word=None, SavedWord=None): + if User.query.filter_by(email='alice.j@test.com').first(): + return + for u in BENCHMARK_USERS: + user = User(email=u['email'], username=u['username'], name=u['name']) + user.password_hash = bcrypt.generate_password_hash( + u['password']).decode('utf-8') + db.session.add(user) + db.session.commit() + + if Word is not None and SavedWord is not None: + alice = User.query.filter_by(email='alice.j@test.com').first() + for slug in PRIMARY_SAVED_SLUGS: + w = Word.query.filter_by(slug=slug).first() + if w: + db.session.add(SavedWord(user_id=alice.id, word_id=w.id)) + db.session.commit() + print(f"Seeded {len(BENCHMARK_USERS)} benchmark users.") diff --git a/sites/merriam_webster/static/css/.gitkeep b/sites/merriam_webster/static/css/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/merriam_webster/static/css/style.css b/sites/merriam_webster/static/css/style.css new file mode 100644 index 00000000..0059b4b4 --- /dev/null +++ b/sites/merriam_webster/static/css/style.css @@ -0,0 +1,229 @@ +/* Merriam-Webster mirror — styling approximating merriam-webster.com */ +:root { + --mw-red: #d71920; + --mw-red-dark: #b3141a; + --mw-navy: #0a1b27; + --mw-blue: #004990; + --mw-blue-light: #97bece; + --ink: #1a1a1a; + --muted: #6b6b6b; + --line: #e2e2e2; + --bg: #ffffff; + --bg-soft: #f7f7f5; +} + +* { box-sizing: border-box; } + +body { + margin: 0; + font-family: Georgia, "Times New Roman", serif; + color: var(--ink); + background: var(--bg); + line-height: 1.5; +} + +a { color: var(--mw-blue); text-decoration: none; } +a:hover { text-decoration: underline; } + +.container { max-width: 1100px; margin: 0 auto; padding: 0 20px; } + +/* ---------------- Header ---------------- */ +.site-header { background: #fff; border-bottom: 3px solid var(--mw-red); } +.header-top { + display: flex; align-items: center; gap: 24px; + padding: 14px 0; +} +.brand { display: flex; align-items: baseline; gap: 2px; text-decoration: none; } +.brand .brand-merriam { font-weight: 700; font-size: 1.55rem; color: var(--mw-red); letter-spacing: -.5px; } +.brand .brand-webster { font-weight: 700; font-size: 1.55rem; color: var(--mw-navy); } +.brand:hover { text-decoration: none; } +.brand-tag { font-size: .7rem; color: var(--muted); font-style: italic; margin-left: 6px; } + +.search-form { flex: 1; display: flex; max-width: 560px; position: relative; } +.search-form input[type=text] { + flex: 1; padding: 11px 14px; font-size: 1rem; font-family: Arial, sans-serif; + border: 2px solid var(--mw-navy); border-right: none; border-radius: 0; +} +.search-form button { + background: var(--mw-navy); color: #fff; border: none; padding: 0 20px; + font-size: 1rem; cursor: pointer; font-family: Arial, sans-serif; +} +.search-form button:hover { background: #000; } +.autocomplete-list { + position: absolute; top: 100%; left: 0; right: 60px; background: #fff; + border: 1px solid var(--line); border-top: none; z-index: 40; list-style: none; + margin: 0; padding: 0; font-family: Arial, sans-serif; +} +.autocomplete-list li { padding: 8px 14px; cursor: pointer; } +.autocomplete-list li:hover, .autocomplete-list li.active { background: var(--bg-soft); } + +.header-nav { + background: var(--mw-navy); + font-family: Arial, sans-serif; +} +.header-nav ul { display: flex; gap: 0; list-style: none; margin: 0; padding: 0; flex-wrap: wrap; } +.header-nav a { + display: block; color: #fff; padding: 11px 16px; font-size: .85rem; + text-transform: uppercase; letter-spacing: .5px; +} +.header-nav a:hover { background: var(--mw-red); text-decoration: none; } +.header-nav .nav-account { margin-left: auto; } + +/* ---------------- Hero / homepage ---------------- */ +.hero { + background: var(--mw-navy); color: #fff; padding: 46px 0; text-align: center; +} +.hero h1 { font-size: 2.4rem; margin: 0 0 18px; } +.hero .hero-search { max-width: 620px; margin: 0 auto; } +.hero .hero-search input { border-color: #fff; } +.hero .hero-search button { background: var(--mw-red); } + +.section { padding: 32px 0; border-bottom: 1px solid var(--line); } +.section-title { + font-size: 1.4rem; color: var(--mw-navy); margin: 0 0 18px; + border-left: 5px solid var(--mw-red); padding-left: 12px; +} + +.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; } +.word-card { + border: 1px solid var(--line); padding: 16px; background: #fff; + transition: box-shadow .15s; +} +.word-card:hover { box-shadow: 0 2px 10px rgba(0,0,0,.08); } +.word-card .wc-head { font-size: 1.2rem; color: var(--mw-navy); font-weight: 700; } +.word-card .wc-pos { color: var(--muted); font-style: italic; font-size: .9rem; } +.word-card .wc-def { font-size: .92rem; color: #333; margin-top: 6px; } + +/* ---------------- Word of the Day ---------------- */ +.wotd-feature { + display: grid; grid-template-columns: 1fr; gap: 20px; + background: var(--bg-soft); border: 1px solid var(--line); padding: 28px; +} +.wotd-label { + text-transform: uppercase; letter-spacing: 1px; font-size: .75rem; + color: var(--mw-red); font-family: Arial, sans-serif; font-weight: 700; +} +.wotd-word { font-size: 2.6rem; color: var(--mw-navy); margin: 6px 0; } +.wotd-pos { font-style: italic; color: var(--muted); } +.wotd-pron { font-family: Arial, sans-serif; color: #444; margin: 4px 0 14px; } +.wotd-def { font-size: 1.1rem; } +.wotd-dyk { margin-top: 18px; } +.wotd-dyk h3 { color: var(--mw-red); font-family: Arial, sans-serif; font-size: 1rem; } + +/* ---------------- Entry / detail page ---------------- */ +.entry-header { border-bottom: 2px solid var(--mw-navy); padding-bottom: 10px; margin-bottom: 6px; } +.entry-headword { font-size: 3rem; color: var(--ink); margin: 0; } +.entry-meta { font-family: Arial, sans-serif; color: var(--muted); margin: 8px 0; } +.entry-pos { font-style: italic; font-weight: 700; color: var(--ink); font-size: 1.3rem; } +.entry-pron { color: var(--mw-blue); margin-left: 10px; } + +.sense { margin: 14px 0; padding-left: 4px; } +.sense-num { font-weight: 700; color: var(--mw-navy); font-family: Arial, sans-serif; } +.sense-def { display: inline; } +.examples { list-style: none; padding: 0; margin: 8px 0 0 20px; } +.examples li { + color: #444; font-size: .96rem; margin: 5px 0; padding-left: 14px; + border-left: 3px solid var(--mw-blue-light); +} + +.entry-section { margin: 26px 0; } +.entry-section h2 { + font-size: 1.2rem; color: var(--mw-navy); font-family: Arial, sans-serif; + border-bottom: 1px solid var(--line); padding-bottom: 6px; +} +.etym { font-size: 1rem; color: #333; } +.first-use { font-family: Arial, sans-serif; color: var(--muted); } + +.browse-strip { display: flex; flex-wrap: wrap; gap: 8px; font-family: Arial, sans-serif; font-size: .9rem; } +.browse-strip a, .browse-strip .current { + padding: 5px 10px; border: 1px solid var(--line); background: #fff; +} +.browse-strip .current { background: var(--mw-navy); color: #fff; font-weight: 700; } + +.syn-chips { display: flex; flex-wrap: wrap; gap: 8px; font-family: Arial, sans-serif; } +.syn-chips a, .ant-chips a { + padding: 6px 12px; border-radius: 14px; font-size: .9rem; display: inline-block; +} +.syn-chips a { background: #e6f0e6; color: #1d6b1d; } +.ant-chips a { background: #f3e3e3; color: #9c2a2a; } + +/* ---------------- Buttons / forms ---------------- */ +.btn { + display: inline-block; background: var(--mw-red); color: #fff; border: none; + padding: 10px 20px; font-size: .95rem; font-family: Arial, sans-serif; + cursor: pointer; border-radius: 2px; +} +.btn:hover { background: var(--mw-red-dark); text-decoration: none; color: #fff; } +.btn-secondary { background: var(--mw-navy); } +.btn-outline { background: #fff; color: var(--mw-navy); border: 2px solid var(--mw-navy); } + +.form-card { max-width: 440px; margin: 40px auto; padding: 30px; border: 1px solid var(--line); } +.form-card h1 { color: var(--mw-navy); font-size: 1.6rem; } +.form-group { margin-bottom: 16px; font-family: Arial, sans-serif; } +.form-group label { display: block; font-size: .9rem; margin-bottom: 5px; font-weight: 700; } +.form-group input { + width: 100%; padding: 10px; border: 1px solid var(--mw-navy); font-size: 1rem; +} +.form-error { color: var(--mw-red); font-size: .85rem; } +.demo-account { + background: var(--bg-soft); border: 1px dashed var(--mw-blue); + padding: 12px 14px; margin-bottom: 18px; font-family: Arial, sans-serif; + font-size: .85rem; color: #333; +} +.demo-account code { background: #fff; padding: 1px 5px; border: 1px solid var(--line); } + +.promo-row { display: flex; gap: 30px; align-items: center; flex-wrap: wrap; } +.promo-row img { max-width: 220px; height: auto; } + +.flash { padding: 12px 16px; margin: 14px 0; font-family: Arial, sans-serif; border-radius: 2px; } +.flash-success { background: #e6f4ea; color: #1d6b1d; border: 1px solid #b6dcc0; } +.flash-error { background: #fdeaea; color: #9c2a2a; border: 1px solid #f0c0c0; } +.flash-info { background: #e8f0f7; color: #1c4f80; border: 1px solid #bcd6ec; } + +/* ---------------- Quiz ---------------- */ +.quiz-card { border: 1px solid var(--line); padding: 20px; margin-bottom: 16px; } +.quiz-card h3 { color: var(--mw-navy); margin: 0 0 6px; } +.quiz-card-media { display: flex; gap: 20px; align-items: center; } +.quiz-thumb { width: 160px; height: 120px; object-fit: cover; border: 1px solid var(--line); flex-shrink: 0; } +@media (max-width: 600px) { .quiz-card-media { flex-direction: column; } .quiz-thumb { width: 100%; height: auto; } } +.quiz-badge { + display: inline-block; font-family: Arial, sans-serif; font-size: .72rem; + text-transform: uppercase; padding: 3px 8px; border-radius: 10px; color: #fff; +} +.badge-easy { background: #2e8b57; } +.badge-medium { background: #d98324; } +.badge-hard { background: var(--mw-red); } + +.question { margin: 22px 0; padding: 18px; background: var(--bg-soft); border: 1px solid var(--line); } +.question .q-text { font-weight: 700; font-size: 1.1rem; margin-bottom: 12px; } +.choice { display: block; font-family: Arial, sans-serif; margin: 8px 0; cursor: pointer; } +.choice input { margin-right: 8px; } +.result-correct { color: #1d6b1d; font-weight: 700; } +.result-wrong { color: var(--mw-red); font-weight: 700; } +.score-banner { + text-align: center; padding: 30px; background: var(--mw-navy); color: #fff; + font-size: 1.6rem; margin-bottom: 20px; +} +.score-banner .score-num { font-size: 3rem; display: block; color: var(--mw-blue-light); } + +/* ---------------- Account ---------------- */ +.account-grid { display: grid; grid-template-columns: 1fr; gap: 24px; } +.account-box { border: 1px solid var(--line); padding: 20px; } +.account-box h2 { color: var(--mw-navy); font-size: 1.2rem; font-family: Arial, sans-serif; } +.list-plain { list-style: none; padding: 0; font-family: Arial, sans-serif; } +.list-plain li { padding: 8px 0; border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; } + +/* ---------------- Footer ---------------- */ +.site-footer { background: var(--mw-navy); color: #cfd8df; padding: 30px 0; margin-top: 40px; font-family: Arial, sans-serif; font-size: .85rem; } +.site-footer a { color: var(--mw-blue-light); } +.footer-cols { display: flex; flex-wrap: wrap; gap: 40px; } + +/* ---------------- Misc ---------------- */ +.muted { color: var(--muted); } +.text-center { text-align: center; } +.results-list { list-style: none; padding: 0; } +.results-list li { padding: 14px 0; border-bottom: 1px solid var(--line); } +.results-list .r-head { font-size: 1.25rem; color: var(--mw-navy); font-weight: 700; } +.tab-row { display: flex; gap: 0; font-family: Arial, sans-serif; margin-bottom: 20px; } +.tab-row a { padding: 10px 20px; border: 1px solid var(--line); background: var(--bg-soft); } +.tab-row a.active { background: var(--mw-navy); color: #fff; } diff --git a/sites/merriam_webster/static/icons/.gitkeep b/sites/merriam_webster/static/icons/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/merriam_webster/static/images/.gitkeep b/sites/merriam_webster/static/images/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/merriam_webster/static/js/.gitkeep b/sites/merriam_webster/static/js/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/merriam_webster/tasks.jsonl b/sites/merriam_webster/tasks.jsonl new file mode 100644 index 00000000..2c3f22a8 --- /dev/null +++ b/sites/merriam_webster/tasks.jsonl @@ -0,0 +1,20 @@ +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--0", "ques": "Look up the word \"serendipity\" and tell me its part of speech and pronunciation.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--1", "ques": "Find the first definition of the word \"ubiquitous\" and one example sentence that uses it.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--2", "ques": "What is the etymology (word history) of \"nostalgia\", and which language does it ultimately come from?", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--3", "ques": "In what year was the word \"empathy\" first known to be used, according to its dictionary entry?", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--4", "ques": "Look up \"meticulous\" and report the year of its first known use.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--5", "ques": "Open the thesaurus entry for \"brave\" and list three of its synonyms.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--6", "ques": "Find the antonyms of \"calm\" in the thesaurus and tell me two of them.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--7", "ques": "Using the thesaurus, find a synonym of \"difficult\" that starts with the letter C.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--8", "ques": "Look up the thesaurus entry for \"happy\" and tell me both a synonym and an antonym of it.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--9", "ques": "Go to the Word of the Day page and tell me today's featured word and its part of speech.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--10", "ques": "In the Word of the Day section, find the entry for \"ephemeral\" and tell me what it means.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--11", "ques": "Go to Games & Quizzes, open the quiz titled \"Name That Word\", answer all of its questions, and tell me your final score.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--12", "ques": "Find and complete the \"Synonym Showdown\" quiz, then report how many questions you got correct out of the total.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--13", "ques": "Browse the Games & Quizzes page and tell me how many quizzes are available and the difficulty level of each.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--14", "ques": "Log in with the demo account (alice.j@test.com), then save the word \"serendipity\" to your word list.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--15", "ques": "Log in with the demo account, save the words \"resilient\" and \"gratitude\" to your saved words, then go to your account page and confirm both appear.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--16", "ques": "Register a new account with the name \"Jordan Lee\", a username of your choice, and any email, then verify you are logged in.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--17", "ques": "Among the words \"empathy\", \"nostalgia\", and \"optimism\", which one has the most recent first known use? Look up each entry to decide.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--18", "ques": "Log in with the demo account and remove a word from my saved words list. (My list already has several words — ask me which one to remove if it's unclear.)", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--19", "ques": "Compare the words \"gregarious\" and \"benevolent\": which one entered English earlier, and what part of speech is each?", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} diff --git a/sites/merriam_webster/templates/.gitkeep b/sites/merriam_webster/templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/merriam_webster/templates/404.html b/sites/merriam_webster/templates/404.html new file mode 100644 index 00000000..cb2dee2f --- /dev/null +++ b/sites/merriam_webster/templates/404.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} +{% block title %}Word Not Found | Merriam-Webster{% endblock %} +{% block content %} +
+

404

+

The word or page you're looking for isn't in our dictionary.

+ Back to the Dictionary +
+{% endblock %} diff --git a/sites/merriam_webster/templates/500.html b/sites/merriam_webster/templates/500.html new file mode 100644 index 00000000..74f25916 --- /dev/null +++ b/sites/merriam_webster/templates/500.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} +{% block title %}Server Error | Merriam-Webster{% endblock %} +{% block content %} +
+

500

+

Something went wrong on our end. Please try again.

+ Back to the Dictionary +
+{% endblock %} diff --git a/sites/merriam_webster/templates/account.html b/sites/merriam_webster/templates/account.html new file mode 100644 index 00000000..48b2098f --- /dev/null +++ b/sites/merriam_webster/templates/account.html @@ -0,0 +1,56 @@ +{% extends 'base.html' %} +{% block title %}My Words | Merriam-Webster{% endblock %} +{% block content %} +
+

My Account — {{ current_user.name }}

+

{{ current_user.email }} · @{{ current_user.username }}

+ + +
+{% endblock %} diff --git a/sites/merriam_webster/templates/base.html b/sites/merriam_webster/templates/base.html new file mode 100644 index 00000000..35e6ed2b --- /dev/null +++ b/sites/merriam_webster/templates/base.html @@ -0,0 +1,114 @@ + + + + + + {% block title %}Merriam-Webster | Dictionary{% endblock %} + + + + + +
+
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endwith %} +
+ {% block content %}{% endblock %} +
+ + + + + + diff --git a/sites/merriam_webster/templates/games_index.html b/sites/merriam_webster/templates/games_index.html new file mode 100644 index 00000000..eb653811 --- /dev/null +++ b/sites/merriam_webster/templates/games_index.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} +{% block title %}Games & Quizzes | Merriam-Webster{% endblock %} +{% set quiz_art = { + 'name-that-word': 'quiz-baby-chick.jpg', + 'synonym-showdown': 'quiz-swan.jpg', + 'opposites-attract': 'quiz-roulette.jpg' +} %} +{% block content %} +
+

Games & Quizzes

+

Test your vocabulary with quizzes built from real dictionary entries.

+ {% for quiz in quizzes %} +
+ {{ quiz.title }} +
+

{{ quiz.title }} + {{ quiz.difficulty }} +

+

{{ quiz.description }}

+

{{ quiz.get_questions() | length }} questions

+ Take the Quiz +
+
+ {% endfor %} +
+{% endblock %} diff --git a/sites/merriam_webster/templates/index.html b/sites/merriam_webster/templates/index.html new file mode 100644 index 00000000..9f32b4ab --- /dev/null +++ b/sites/merriam_webster/templates/index.html @@ -0,0 +1,89 @@ +{% extends 'base.html' %} +{% block title %}Merriam-Webster | America's Most Trusted Dictionary{% endblock %} +{% block content %} +
+
+

The dictionary America relies on

+ +
+
+ +{% if wotd %} +
+
+
+
+
Word of the Day
+

{{ wotd.headword }}

+
{{ wotd.pos }} + {% if wotd.pronunciation %}| {{ wotd.pronunciation }}{% endif %} +
+

{{ wotd.definition }}

+ See the full entry +
+
+
+
+{% endif %} + +
+ +
+ +
+
+

Browse the Dictionary

+
+ {% for w in browse %} + {{ w.headword }} + {% endfor %} +
+
+
+ +
+ +
+ +
+
+ Merriam-Webster app +
+

Get the Merriam-Webster App

+

America's most useful and respected dictionary, in your pocket.

+
+
+
+{% endblock %} diff --git a/sites/merriam_webster/templates/login.html b/sites/merriam_webster/templates/login.html new file mode 100644 index 00000000..d0615801 --- /dev/null +++ b/sites/merriam_webster/templates/login.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} +{% block title %}Log In | Merriam-Webster{% endblock %} +{% block content %} +
+

Log In

+ +
+ {{ form.hidden_tag() }} +
+ + {{ form.email(id='email') }} + {% for e in form.email.errors %}
{{ e }}
{% endfor %} +
+
+ + {{ form.password(id='password') }} + {% for e in form.password.errors %}
{{ e }}
{% endfor %} +
+ +
+

+ No account? Sign up +

+
+{% endblock %} diff --git a/sites/merriam_webster/templates/quiz.html b/sites/merriam_webster/templates/quiz.html new file mode 100644 index 00000000..815ffbfa --- /dev/null +++ b/sites/merriam_webster/templates/quiz.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} +{% block title %}{{ quiz.title }} | Merriam-Webster Quiz{% endblock %} +{% block content %} +
+

{{ quiz.title }} + {{ quiz.difficulty }} +

+

{{ quiz.description }}

+ +
+ + {% for q in questions %} + {% set qidx = loop.index0 %} +
+
{{ loop.index }}. {{ q.q }}
+ {% for choice in q.choices %} + + {% endfor %} +
+ {% endfor %} + +
+
+{% endblock %} diff --git a/sites/merriam_webster/templates/quiz_result.html b/sites/merriam_webster/templates/quiz_result.html new file mode 100644 index 00000000..61eedebe --- /dev/null +++ b/sites/merriam_webster/templates/quiz_result.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} +{% block title %}{{ quiz.title }} — Results | Merriam-Webster{% endblock %} +{% block content %} +
+
+ Your Score + {{ score }} / {{ total }} +
+ +

{{ quiz.title }} — Review

+ {% for r in review %} +
+
{{ loop.index }}. {{ r.q }}
+ {% for choice in r.choices %} + {% if loop.index0 == r.answer_index %} +
✓ {{ choice }} (correct answer)
+ {% elif loop.index0 == r.picked %} +
✗ {{ choice }} (your answer)
+ {% else %} +
{{ choice }}
+ {% endif %} + {% endfor %} + {% if r.explanation %}

{{ r.explanation }}

{% endif %} +
+ {% endfor %} + + Try Again + More Quizzes +
+{% endblock %} diff --git a/sites/merriam_webster/templates/register.html b/sites/merriam_webster/templates/register.html new file mode 100644 index 00000000..84820582 --- /dev/null +++ b/sites/merriam_webster/templates/register.html @@ -0,0 +1,39 @@ +{% extends 'base.html' %} +{% block title %}Sign Up | Merriam-Webster{% endblock %} +{% block content %} +
+

Create Your Account

+
+ {{ form.hidden_tag() }} +
+ + {{ form.name(id='name') }} + {% for e in form.name.errors %}
{{ e }}
{% endfor %} +
+
+ + {{ form.username(id='username') }} + {% for e in form.username.errors %}
{{ e }}
{% endfor %} +
+
+ + {{ form.email(id='email') }} + {% for e in form.email.errors %}
{{ e }}
{% endfor %} +
+
+ + {{ form.password(id='password') }} + {% for e in form.password.errors %}
{{ e }}
{% endfor %} +
+
+ + {{ form.confirm(id='confirm') }} + {% for e in form.confirm.errors %}
{{ e }}
{% endfor %} +
+ +
+

+ Already have an account? Log in +

+
+{% endblock %} diff --git a/sites/merriam_webster/templates/search.html b/sites/merriam_webster/templates/search.html new file mode 100644 index 00000000..729fd37a --- /dev/null +++ b/sites/merriam_webster/templates/search.html @@ -0,0 +1,41 @@ +{% extends 'base.html' %} +{% block title %}{% if q %}{{ q }} — Search{% else %}Search{% endif %} | Merriam-Webster{% endblock %} +{% block content %} +
+ {% if not q %} +

Search the dictionary

+

Type a word in the search bar above to look up its definition.

+ {% else %} +

Results for "{{ q }}"

+ + {% if results %} +

Dictionary

+
    + {% for w in results %} +
  • + {{ w.headword }} + {{ w.pos }} + {% if w.get_definitions() %}
    {{ w.get_definitions()[0].text | truncate(120) }}
    {% endif %} +
  • + {% endfor %} +
+ {% endif %} + + {% if thes_results %} +

Thesaurus

+
    + {% for t in thes_results %} +
  • + {{ t.headword }} + {% if t.get_synonyms() %}
    Synonyms: {{ t.get_synonyms()[:5] | join(', ') }}
    {% endif %} +
  • + {% endfor %} +
+ {% endif %} + + {% if not results and not thes_results %} +

No entries found for "{{ q }}". Check your spelling or try another word.

+ {% endif %} + {% endif %} +
+{% endblock %} diff --git a/sites/merriam_webster/templates/thesaurus_detail.html b/sites/merriam_webster/templates/thesaurus_detail.html new file mode 100644 index 00000000..57f56fce --- /dev/null +++ b/sites/merriam_webster/templates/thesaurus_detail.html @@ -0,0 +1,37 @@ +{% extends 'base.html' %} +{% block title %}{{ entry.headword }} Synonyms & Antonyms | Merriam-Webster Thesaurus{% endblock %} +{% block content %} +
+
+ {% if word %}Dictionary{% endif %} + Thesaurus +
+ +
+

{{ entry.headword }}

+
+ {% if entry.short_def %}

{{ entry.short_def }}

{% endif %} + +
+

Synonyms

+ {% if entry.get_synonyms() %} +
+ {% for s in entry.get_synonyms() %} + {{ s }} + {% endfor %} +
+ {% else %}

No synonyms recorded.

{% endif %} +
+ +
+

Antonyms

+ {% if entry.get_antonyms() %} +
+ {% for a in entry.get_antonyms() %} + {{ a }} + {% endfor %} +
+ {% else %}

No antonyms recorded.

{% endif %} +
+
+{% endblock %} diff --git a/sites/merriam_webster/templates/word_detail.html b/sites/merriam_webster/templates/word_detail.html new file mode 100644 index 00000000..f3a24814 --- /dev/null +++ b/sites/merriam_webster/templates/word_detail.html @@ -0,0 +1,77 @@ +{% extends 'base.html' %} +{% block title %}{{ word.headword }} Definition & Meaning - Merriam-Webster{% endblock %} +{% block content %} +
+
+

{{ word.headword }}

+
+ + + {% if current_user.is_authenticated %} +
+ + {% if is_saved %} + + {% else %} + + {% endif %} +
+ {% endif %} + +
+

Definition

+ {% for d in word.get_definitions() %} +
+ {{ d.sense_num }} + : {{ d.text }} + {% if d.examples %} +
    + {% for ex in d.examples %}
  • {{ ex }}
  • {% endfor %} +
+ {% endif %} +
+ {% endfor %} +
+ + {% if word.get_synonyms() %} +
+

Synonyms

+
+ {% for s in word.get_synonyms() %} + {{ s }} + {% endfor %} +
+ {% if thes %} +

See full thesaurus entry for {{ word.headword }} »

+ {% endif %} +
+ {% endif %} + + {% if word.etymology %} +
+

Word History & Etymology

+

{{ word.etymology }}

+ {% if word.first_known_use %} +

First Known Use: {{ word.first_known_use }}

+ {% endif %} +
+ {% endif %} + +
+

Browse Nearby Entries

+
+ {% for n in nearby %} + {% if n.id == word.id %} + {{ n.headword }} + {% else %} + {{ n.headword }} + {% endif %} + {% endfor %} +
+
+
+{% endblock %} diff --git a/sites/merriam_webster/templates/wotd.html b/sites/merriam_webster/templates/wotd.html new file mode 100644 index 00000000..e914cacd --- /dev/null +++ b/sites/merriam_webster/templates/wotd.html @@ -0,0 +1,50 @@ +{% extends 'base.html' %} +{% block title %}Word of the Day: {{ wotd.headword }} | Merriam-Webster{% endblock %} +{% block content %} +
+
+
+
Word of the Day · {{ wotd.feature_date.strftime('%B %d, %Y') }}
+

{{ wotd.headword }}

+
{{ wotd.pos }} + {% if wotd.pronunciation %}| {{ wotd.pronunciation }}{% endif %} +
+ +
+

What It Means

+

{{ wotd.definition }}

+
+ + {% if wotd.get_examples() %} +
+

Examples

+
    + {% for ex in wotd.get_examples() %}
  • {{ ex }}
  • {% endfor %} +
+
+ {% endif %} + + {% if wotd.did_you_know %} +
+

Did You Know?

+

{{ wotd.did_you_know }}

+
+ {% endif %} +
+
+ + {% if recent %} +
+

Recent Words of the Day

+ +
+ {% endif %} +
+{% endblock %} diff --git a/websyn_start.sh b/websyn_start.sh index 72defad8..4d690d78 100644 --- a/websyn_start.sh +++ b/websyn_start.sh @@ -1,11 +1,11 @@ #!/bin/bash -# WebSyn startup: launch all 12 mirror sites, then exec the original CMD. +# WebSyn startup: launch all 16 mirror sites, then exec the original CMD. # This preserves the base image's browser env server (port 8100) as PID 1. set -e SITES=(allrecipes amazon apple arxiv bbc_news booking github google_flights google_map google_search huggingface wolfram_alpha - cambridge_dictionary coursera espn) + cambridge_dictionary coursera espn merriam_webster) BASE_PORT=40000 PID_DIR=/tmp/websyn_pids mkdir -p "$PID_DIR" @@ -17,7 +17,7 @@ for d in "${SITES[@]}"; do cp -a "/opt/WebSyn/$d/instance_seed" "/opt/WebSyn/$d/instance" done -echo "[WebSyn] Starting 15 sites on ports ${BASE_PORT}-$((BASE_PORT + 14))..." +echo "[WebSyn] Starting 16 sites on ports ${BASE_PORT}-$((BASE_PORT + 15))..." for i in "${!SITES[@]}"; do site="${SITES[$i]}" port=$((BASE_PORT + i)) @@ -51,8 +51,8 @@ except Exception: exit(1) ready=$((ready + 1)) fi done - echo " [${elapsed}/${max_wait}s] ${ready}/15 sites ready" - if [ $ready -eq 15 ]; then + echo " [${elapsed}/${max_wait}s] ${ready}/16 sites ready" + if [ $ready -eq 16 ]; then break fi done @@ -78,6 +78,6 @@ done echo "[WebSyn] Starting control server on :8101 (PID 1)..." # Control server becomes PID 1 — receives SIGTERM on `docker stop`, -# keeps the container alive as long as it's running. The 15 site +# keeps the container alive as long as it's running. The 16 site # subprocesses are managed via /tmp/websyn_pids/.pid. exec python3 /opt/control_server.py --port 8101 From 2fc4acd2557027005824151b382fd0680a9988fe Mon Sep 17 00:00:00 2001 From: raibows Date: Wed, 24 Jun 2026 01:49:28 -0700 Subject: [PATCH 2/3] fix issues by comments --- .assets-revision | 10 +- .claude/skills/review-env/SKILL.md | 66 ++++- AGENTS.md | 32 ++- CONTRIBUTING.md | 93 ++++++- agent_demo/agent.py | 38 ++- agent_demo/eval_judge.py | 201 +++++++++++--- sites/merriam_webster/_common_words.py | 222 +++++++++++++++ sites/merriam_webster/app.py | 42 ++- sites/merriam_webster/seed_data.py | 9 +- sites/merriam_webster/tasks.jsonl | 40 +-- sites/merriam_webster/templates/quiz.html | 2 +- .../templates/quiz_result.html | 9 +- sites/merriam_webster/verify/verify_0.py | 42 +++ sites/merriam_webster/verify/verify_1.py | 33 +++ sites/merriam_webster/verify/verify_10.py | 33 +++ sites/merriam_webster/verify/verify_11.py | 42 +++ sites/merriam_webster/verify/verify_12.py | 42 +++ sites/merriam_webster/verify/verify_13.py | 33 +++ sites/merriam_webster/verify/verify_14.py | 40 +++ sites/merriam_webster/verify/verify_15.py | 35 +++ sites/merriam_webster/verify/verify_16.py | 33 +++ sites/merriam_webster/verify/verify_17.py | 33 +++ sites/merriam_webster/verify/verify_18.py | 35 +++ sites/merriam_webster/verify/verify_19.py | 34 +++ sites/merriam_webster/verify/verify_2.py | 35 +++ sites/merriam_webster/verify/verify_3.py | 34 +++ sites/merriam_webster/verify/verify_4.py | 34 +++ sites/merriam_webster/verify/verify_5.py | 33 +++ sites/merriam_webster/verify/verify_6.py | 33 +++ sites/merriam_webster/verify/verify_7.py | 32 +++ sites/merriam_webster/verify/verify_8.py | 37 +++ sites/merriam_webster/verify/verify_9.py | 32 +++ sites/merriam_webster/verify/verify_lib.py | 260 ++++++++++++++++++ 33 files changed, 1626 insertions(+), 103 deletions(-) create mode 100644 sites/merriam_webster/_common_words.py create mode 100644 sites/merriam_webster/verify/verify_0.py create mode 100644 sites/merriam_webster/verify/verify_1.py create mode 100644 sites/merriam_webster/verify/verify_10.py create mode 100644 sites/merriam_webster/verify/verify_11.py create mode 100644 sites/merriam_webster/verify/verify_12.py create mode 100644 sites/merriam_webster/verify/verify_13.py create mode 100644 sites/merriam_webster/verify/verify_14.py create mode 100644 sites/merriam_webster/verify/verify_15.py create mode 100644 sites/merriam_webster/verify/verify_16.py create mode 100644 sites/merriam_webster/verify/verify_17.py create mode 100644 sites/merriam_webster/verify/verify_18.py create mode 100644 sites/merriam_webster/verify/verify_19.py create mode 100644 sites/merriam_webster/verify/verify_2.py create mode 100644 sites/merriam_webster/verify/verify_3.py create mode 100644 sites/merriam_webster/verify/verify_4.py create mode 100644 sites/merriam_webster/verify/verify_5.py create mode 100644 sites/merriam_webster/verify/verify_6.py create mode 100644 sites/merriam_webster/verify/verify_7.py create mode 100644 sites/merriam_webster/verify/verify_8.py create mode 100644 sites/merriam_webster/verify/verify_9.py create mode 100644 sites/merriam_webster/verify/verify_lib.py diff --git a/.assets-revision b/.assets-revision index 64bf3b91..7f7d8db6 100644 --- a/.assets-revision +++ b/.assets-revision @@ -5,11 +5,5 @@ # is a git revision (branch name like `main`, a tag, or a specific commit # sha). Override at runtime with the ASSETS_REVISION env var. -# Pinned to YuanDaozeiii/WebHarbor (fork) until the HF PR adding -# merriam_webster.tar.gz is merged into ChilleD/WebHarbor. Reviewers/CI: -# fetch_assets.sh will pull all 15 existing tarballs from ChilleD via -# fallback or you can run it against the fork directly. After the HF PR -# merges, this should be bumped back to `repo: ChilleD/WebHarbor` with -# the merge commit SHA. -repo: YuanDaozeiii/WebHarbor -revision: a591b293cf8d4cf52ea977feffbccb2dee98d18d +repo: ChilleD/WebHarbor +revision: main \ No newline at end of file diff --git a/.claude/skills/review-env/SKILL.md b/.claude/skills/review-env/SKILL.md index 6542aeb1..52dc9640 100644 --- a/.claude/skills/review-env/SKILL.md +++ b/.claude/skills/review-env/SKILL.md @@ -5,6 +5,13 @@ description: "Review pipeline for WebHarbor mirror PRs. Systematically verify vi # Review Environment — Quality Verification Pipeline +## Roles + +WebHarbor splits work across two roles: + +- **Contributor** ships the site (Flask app, seed DB, assets) and `tasks.jsonl` with ONLY the task-definition keys per row — `web_name, id, ques, web, upstream_url` (login tasks carry demo creds in `ques`). The contributor does **not** write verifiers, rubrics, or any answer key. +- **Reviewer** (this skill) validates the contribution, then writes the **grading contract**: for each accepted task, a deterministic verifier (under the site's own `sites//verify/`), recorded as `verifier_path` in the task row, plus an English `judge_rubric` of fact-checkpoints, also recorded in the task row. Ground truth lives ONLY inside the verifiers — never in `tasks.jsonl` (the agent reads that file; an answer key there leaks answers). + ## When to use - Reviewing a GitHub PR that adds or modifies a website mirror @@ -26,18 +33,18 @@ gh pr checkout ./scripts/fetch_assets.sh # pull the pinned HF revision ./scripts/build.sh webharbor:dev docker run -d --rm --name wh-review \ - -p 8201:8101 -p 41000-41014:40000-40014 webharbor:dev + -p 8201:8101 -p 41000-41015:40000-40015 webharbor:dev ``` -Confirm the new/changed site is on the expected port (40000 + index). +Confirm the new/changed site is on the expected port (40000 + index). Note: the image now runs 16 sites (40000-40015). ### Step 2: The mechanical checks (5 minutes) Run the same Pre-PR checks the contributor was supposed to run. ```bash -# 1. all 15 sites return 200 -for p in $(seq 41000 41014); do +# 1. all 16 sites return 200 +for p in $(seq 41000 41015); do curl -so /dev/null -w "$p:%{http_code}\n" http://localhost:$p/ done @@ -112,7 +119,7 @@ Actually drive the site through Playwright — `page.fill` / `page.click` / `pag ### Step 5: Task quality audit (the most important part, 20-30 min) -For EACH task in `sites//tasks.jsonl`: +For EACH task in `sites//tasks.jsonl` (contributor rows carry only `web_name, id, ques, web, upstream_url`): #### Solvability - Can you actually complete the task using only the mirror's UI? @@ -127,6 +134,14 @@ Perform the task's natural search query. Check: If you solve the task from the search-results page alone, it's a **leak**. +#### Knowledge-shortcut detection (reject these) +A web-agent benchmark must require NAVIGATING the site, not recalling facts. Reject — and send back to the contributor — tasks that: +- can be answered without ever opening the site (e.g. a common dictionary definition a frontier LLM already knows), +- are ill-posed for autonomous evaluation (e.g. "ask me which one" presupposing a human in the loop), +- have no stable, verifiable answer (e.g. a "today" value that rotates by run date), +- are mechanically trivial / non-deterministic in a way that defeats grading. +Prefer tasks anchored on page-specific facts an LLM can't recall (exact dates/IDs, on-page wording, a specific row). The `merriam_webster` review is the reference example: several original tasks were rejected as knowledge-shortcuts / human-in-the-loop / date-dependent and re-anchored onto page-specific facts. + #### Distractor check On the search results for each task's query: - ≥6 total results @@ -142,7 +157,40 @@ If every result satisfies the task, the catalog is **too narrow**. - ≥1 task would challenge a frontier model (GPT-4o / Claude) - No task solvable by clicking the first result -### Step 6: Common agent pitfalls +### Step 6: Write the grading contract (reviewer's job, after tasks pass Step 5) + +For each accepted task the reviewer writes the grading artifacts and records them in `tasks.jsonl`: + +1. **A deterministic verifier** — one Python script per task, placed under the **site's own** `sites//verify/` directory (never under `agent_demo/`; each site is self-contained). It emits a binary PASS/FAIL from the run signature `(initial_state, after_state, trajectory, agent final output)`. Deterministic-first (navigation / regex / token / SQLite after-state); LLM only as an anchored utility. Ground truth is **HARDCODED inside the verifier**. See `sites/merriam_webster/verify/verify_lib.py` for the shared utilities and `sites/merriam_webster/verify/verify_*.py` for one-per-task examples. +2. **`verifier_path`** in the task row — relative path (from repo root) to that verifier, e.g. `sites//verify/verify_0.py`. +3. **`judge_rubric`** in the task row — short English "FACT CHECKPOINTS" the LLM judge verifies (which pages MUST be opened, which facts MUST appear, that an empty answer is a FAIL). The rubric states the *rules*, not the answers, so it's safe for the agent to see. + +After the reviewer's pass, `tasks.jsonl` has keys `web_name, id, ques, web, upstream_url, verifier_path, judge_rubric`. There is **no `answer` key**. + +### Step 7: Verify the grading itself + +Unified LLM config (agent, judge, verifiers all read these env vars; CLI flags override): +`OPENAI_API_KEY`, `OPENAI_BASE_URL`, `JUDGE_MODEL`. + +```bash +export OPENAI_API_KEY=... OPENAI_BASE_URL=http://api.openai.com/v1 JUDGE_MODEL=GPT-5 +# drive a task with the agent (writes trajectory.json incl. verifier_path + judge_rubric) +uv run python agent_demo/agent.py --tasks_file sites//tasks.jsonl \ + --task_id "--N" --url http://localhost:40000+i/ --out_dir runs/N +# grade via the single eval_judge entry point — two modes: +uv run python agent_demo/eval_judge.py --run_dir runs/N --verifier True # PRIMARY (deterministic verifier) +uv run python agent_demo/eval_judge.py --run_dir runs/N # secondary (LLM judge, rubric-driven) +``` + +Note: tools use `simpleArgParser`, so **boolean flags take a value** (`--verifier True`, `--no_llm True`). + +Confirm the grading contract is sound before merge: +- A **no-op run** (agent opens the homepage, does nothing, empty answer, clean DB) makes **every** verifier return FAIL (exit 1) — no false positives. +- A **PASS case** (drive a task to completion correctly) makes its verifier pass; a **shortcut case** (correct answer but no on-site navigation) and a **wrong-answer case** both FAIL. +- For stateful tasks, a **state-mismatch case** (agent self-reports success but the DB is unchanged) FAILs on the DB check. +- The **LLM judge** appends a rubric-specific system-prompt block (and emits `rubric_checkpoints`) ONLY for tasks with a non-empty `judge_rubric`; tasks without one get the plain base prompt. + +### Step 8: Common agent pitfalls Based on 15+ mirror reviews: @@ -159,7 +207,7 @@ Based on 15+ mirror reviews: | 9 | Count labels next to lists agent should count | Look for "N items", "N results", "N courses" | | 10 | Cross-imports between sites | Grep for `from sites.` (sites must be isolated) | -### Step 7: Asset PR check +### Step 9: Asset PR check Verify the paired HuggingFace PR is real: @@ -173,7 +221,9 @@ cat .assets-revision If `.assets-revision` doesn't match a real HF merge SHA, request changes. -### Step 8: Submit review +### Step 10: Submit review + +Leave a structured comment on the PR. Report mechanical / visual / functional / task-quality results AND the grading contract you authored (verifier + rubric per task). Leave a structured comment on the PR: diff --git a/AGENTS.md b/AGENTS.md index 8b24944f..afa6f8b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,11 +92,13 @@ A minimal browser-use ReAct loop (`agent.py`) plus an LLM-as-judge grader (`eval cd agent_demo uv sync uv run playwright install chromium # one-time -export OPENAI_API_KEY=... # API key + base URL via env, never hardcoded -export OPENAI_BASE_URL=https://api.openai.com/v1 +# unified LLM config — agent, judge, and verifier all read these three env vars: +export OPENAI_API_KEY=... # bearer token +export OPENAI_BASE_URL=https://api.openai.com/v1 # OpenAI-compatible base URL +export JUDGE_MODEL=gpt-5.1 # model id for agent + judge + verifier LLM utils ``` -Run a task from a site's `tasks.jsonl` (the format is `{web_name, id, ques, web, upstream_url}` per line). The agent reads `--task` / `--url` either inline or from `--tasks_file [--task_id ID]`: +Run a task from a site's `tasks.jsonl` (per-line keys: `web_name, id, ques, web, upstream_url`; the reviewer later adds optional `verifier_path` and `judge_rubric` — see CONTRIBUTING.md "Reviewer role"). The agent reads `--task` / `--url` either inline or from `--tasks_file [--task_id ID]`: ```bash # pick a specific task @@ -108,13 +110,18 @@ uv run python agent.py --task "Find Kevin Durant's bio" \ --url http://localhost:40009/ --out_dir runs/inline ``` -Each run writes `trajectory.json` + `screenshots/step_NNN.png`. Then grade: +Each run writes `trajectory.json` (carrying the task's `judge_rubric` if the reviewer added one) + `screenshots/step_NNN.png`. Then grade TWO ways — the deterministic verifier (reviewer-provided) is the PRIMARY grader, the LLM judge is secondary: ```bash +# 1) deterministic verifier — the PRIMARY grader (binary pass/fail), reviewer-provided +# run via eval_judge's verifier mode (it locates the verifier from trajectory.verifier_path) +uv run python eval_judge.py --run_dir runs/gs0 --verifier True + +# 2) LLM-as-judge — secondary; appends a rubric block only if judge_rubric present uv run python eval_judge.py --run_dir runs/gs0 ``` -`eval.json` lands next to the trajectory with `success` / `confidence` / `rationale` / `evidence`. See `agent_demo/README.md` for full CLI flags. +The verifier prints JSON `{task_id, pass, reason, evidence[]}` and exits 0/1; the LLM judge writes `eval.json` with `success` / `confidence` / `rationale` / `evidence` (+ `rubric_checkpoints` if a rubric was provided). See `agent_demo/README.md`, `sites/merriam_webster/verify/README.md`, and CONTRIBUTING.md "Reviewer role" for the full grading contract. Grading is driven through `agent_demo/eval_judge.py`, which has two modes: the default LLM-as-judge, and `--verifier True` to run a task's deterministic verifier (the script at its `verifier_path`). ## Pre-PR checks @@ -152,6 +159,21 @@ docker stop wh-test If you changed an HTTP handler, also `curl` the affected route before and after your change and diff the responses (CSRF tokens differ each request, ignore those). +### If you added or changed tasks: define them with the basic keys only + +Contributors write `sites//tasks.jsonl` with ONLY the task definition per row — `web_name, id, ques, web, upstream_url` (for login tasks, put the demo credentials in `ques`). Do **not** add `verifier_path`, `judge_rubric`, or any `answer` key: the **reviewer** writes the deterministic verifier + rubric and records them back as `verifier_path` + `judge_rubric` (see CONTRIBUTING.md "Reviewer role"). Ground truth never lives in this agent-facing file. + +When you self-check a task is feasible before opening the PR, drive it with the agent and eyeball the trajectory: + +```bash +export OPENAI_API_KEY=... OPENAI_BASE_URL=http://api.openai.com/v1 JUDGE_MODEL=GPT-5 +uv run python agent_demo/agent.py --tasks_file sites//tasks.jsonl \ + --task_id "--N" --url http://localhost:40000+i/ --out_dir runs/N +# confirm the trajectory actually solves the task by navigating the site +``` + +If a task can be answered without opening the site, is human-in-the-loop, or has no stable answer, fix it before review — the reviewer will reject it. + ## Code style - Python 3.12 syntax welcome (PEP 604 unions, match, etc.); don't drop below 3.10 syntax diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17c6a060..927ed53f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,15 @@ Thanks for being here. WebHarbor lives across two repositories on purpose: A non-trivial change usually touches both. The workflow below makes that straightforward. +## Two roles + +WebHarbor splits work across two roles on purpose: + +- **Contributor** — builds the site: the Flask app, seed DB, assets, and the task list (`tasks.jsonl`). A contributor's `tasks.jsonl` rows carry only the task definition: `web_name, id, ques, web, upstream_url`. The contributor does **not** write verifiers, rubrics, or any answer key. +- **Reviewer** — validates the contribution: checks site quality (mechanical + functional), checks that each task is **feasible** (actually solvable by navigating the site, and not trivially answerable from an LLM's prior knowledge), and then writes the **grading contract** — a deterministic verifier per task plus a `judge_rubric`, recorded back into `tasks.jsonl` as `verifier_path` + `judge_rubric`. + +Workflows A and B below are the **contributor's** job. The **Reviewer role** section later in this file is the reviewer's job. The split keeps ground truth out of the agent-facing file (a contributor's `tasks.jsonl` never contains answers) and puts grading rigor where it belongs — with the person checking the work. + ## TL;DR ```bash @@ -99,7 +108,25 @@ docker exec wh-test md5sum \ # both md5s MUST match — see "Idempotent seeding" below ``` -### 6. Open the two PRs +### 6. Write the tasks (`tasks.jsonl`) + +Add one JSON line per task to `sites//tasks.jsonl`. The contributor writes ONLY these keys: + +```json +{"web_name": "My Site", "id": "My Site--0", "ques": "...", "web": "http://localhost:4000N/", "upstream_url": "https://realsite.com/"} +``` + +- `id` is `"--"` (0-indexed, matches the `web_name`). +- `ques` is the natural-language task the agent must perform by navigating the mirror. +- `web` is the mirror base URL (the in-container port `40000 + index`). +- `upstream_url` is the real site being mirrored. +- **For login tasks, put the demo account credentials directly in `ques`** (e.g. `"Log in with the demo account (email: alice.j@test.com, password: TestPass123!), then ..."`) so an autonomous agent can log in. + +Do NOT add `verifier_path`, `judge_rubric`, or any `answer` key — those are the **reviewer's** to add (see "Reviewer role"). Ground truth never lives in this file because the agent reads it. + +Design tasks to be **feasible and meaningful**: each must be solvable by navigating the site, and ideally should require reading a page-specific fact an LLM can't recall from memory (exact dates/IDs, on-page wording, a specific row) rather than general knowledge. The reviewer will reject tasks that are trivially answerable from prior knowledge or ill-posed for autonomous evaluation. + +### 7. Open the two PRs The HF dataset stores one `.tar.gz` per site (avoids the small-file stall on `hf download` for 4000+ images). `extract_assets.sh` packs your @@ -147,6 +174,70 @@ hf upload amazon.tar.gz /WebHarbor amazon.tar.gz --repo-type dataset Open the HF PR; once merged, bump `.assets-revision` in this repo and open the GitHub PR. CI on the GitHub PR will fail-closed if the pinned revision isn't reachable. +## Reviewer role — validate the site and grade the tasks + +The reviewer picks up a contributor's PR (site + `tasks.jsonl` with the basic keys) and does two things: **(A) validate site quality and task feasibility**, then **(B) add the grading contract** (verifier + `verifier_path` + `judge_rubric`). Only after both pass should the PR merge. + +### A. Validate site quality and task feasibility + +Build the image from the branch and run it on alt ports (see AGENTS.md "Pre-PR checks" for the exact commands). Then: + +1. **Mechanical** — every site returns 200; `/health` all alive; `POST /reset/` wipes runtime writes and restores the DB **byte-identical** to the seed (`md5(instance) == md5(instance_seed)`); `reset-all` completes in ~1s. +2. **Functional** — drive the site's routes (auth, search, list/detail, any stateful action) and confirm each renders correct, non-empty content. The contributor's tasks must be genuinely completable on these pages. +3. **Task feasibility** — for each task in `tasks.jsonl`, confirm it is **solvable by navigating the site** and is **not trivially answerable from an LLM's prior knowledge**. Drive a few tasks end-to-end (manually or with `agent_demo/agent.py`). Reject — and send back to the contributor — tasks that: + - can be answered without ever opening the site (e.g. a common dictionary definition), + - are ill-posed for autonomous evaluation (e.g. "ask me which one" presupposing a human in the loop), + - have no stable, verifiable answer (e.g. a "today" value that rotates by run date), + - or are mechanically trivial / non-deterministic in a way that defeats grading (e.g. a quiz score the agent can't be graded on). + +The `merriam_webster` review is the reference example of this step: several original tasks were rejected as knowledge-shortcuts / human-in-the-loop / date-dependent and re-anchored onto page-specific facts. + +### B. Add the grading contract (verifier + rubric) + +For each task the reviewer accepts, the reviewer writes the grading artifacts and records them in `tasks.jsonl`: + +1. **A deterministic verifier** — one Python script per task, placed under the **site's own** `sites//verify/` directory (so each site is self-contained; verifiers never live under `agent_demo/`). It emits a binary PASS/FAIL from the run signature `(initial_state, after_state, trajectory, agent final output)`. Deterministic-first (navigation / regex / token / SQLite after-state); LLM only as an anchored utility. The ground truth is **HARDCODED inside the verifier** — never in `tasks.jsonl` (the agent reads that file; an answer key there leaks answers). See `sites/merriam_webster/verify/verify_lib.py` for the shared utilities (`load_run`, `navigated_to`, `llm_text_match`, `llm_screenshot_shows`, SQLite helpers, the `Judge` harness) and `sites/merriam_webster/verify/verify_*.py` for one-per-task examples. +2. **`verifier_path`** in the task row — the relative path (from repo root) to that verifier, e.g. `sites/merriam_webster/verify/verify_0.py`. +3. **`judge_rubric`** in the task row — a short English block of "FACT CHECKPOINTS" the LLM judge verifies (which pages the agent MUST have opened, which facts/answers MUST appear, that an empty answer is a FAIL). The rubric states the *rules*, not the answers, so it's safe for the agent to see. + +After the reviewer's pass, `tasks.jsonl` has these keys: `web_name, id, ques, web, upstream_url, verifier_path, judge_rubric`. There is **no `answer` key**. Sites whose tasks predate this contract may omit `verifier_path` and `judge_rubric` (both optional); `agent.py` and `eval_judge.py` handle their absence gracefully. + +### C. Verify the grading itself + +The reviewer confirms the grading contract is sound before merge: + +- A **no-op run** (agent opens the homepage, does nothing, empty answer, clean DB) makes **every** verifier return FAIL (exit 1) — no false positives. +- A **PASS case** (drive a task to completion correctly) makes its verifier pass; a **shortcut case** (correct answer but no on-site navigation) and a **wrong-answer case** both FAIL — proving the verifier can't be fooled. +- For stateful tasks, a **state-mismatch case** (agent self-reports success but the DB is unchanged) FAILs on the DB check. +- The **LLM judge** appends a rubric-specific system-prompt block (and emits `rubric_checkpoints`) ONLY for tasks with a non-empty `judge_rubric`; tasks without one get the plain base prompt. + +Why two graders: an LLM-as-judge alone is gullible — a plausible-but-wrong answer, or a correct answer recalled from memory with no page visit, can pass. The deterministic verifier catches both (wrong-answer via ground-truth match; knowledge-shortcut via the navigation check). The rubric makes the LLM judge stricter and more consistent. The verifier is the **primary** grader; the rubric-driven LLM judge is secondary/lenient. Both are invoked through the single `agent_demo/eval_judge.py` entry point (`--verifier True` for the verifier, default for the LLM judge). See `sites/merriam_webster/verify/README.md` and the `merriam_webster` site for a worked example (20 tasks, 20 verifiers + `verify_lib.py`). + +### Unified LLM config (agent, judge, verifiers) + +All three tools read the same env vars (CLI flags override): + +| env var | meaning | +|---------|---------| +| `OPENAI_API_KEY` | bearer token for the OpenAI-compatible endpoint | +| `OPENAI_BASE_URL` | base URL of that endpoint | +| `JUDGE_MODEL` | model id used by BOTH the agent, the LLM judge, and the verifier LLM utilities | + +Run an agent task and grade it (reviewer validation loop): + +```bash +export OPENAI_API_KEY=... OPENAI_BASE_URL=http://api.openai.com/v1 JUDGE_MODEL=GPT-5 +# agent (writes trajectory.json + screenshots/, carries judge_rubric in) +uv run python agent_demo/agent.py --tasks_file sites//tasks.jsonl \ + --task_id "--N" --url http://localhost:40000+i/ --out_dir runs/x +# deterministic verifier (primary grader) — run via eval_judge's verifier mode +uv run python agent_demo/eval_judge.py --run_dir runs/x --verifier True +# LLM judge (secondary, rubric-driven) — default mode +uv run python agent_demo/eval_judge.py --run_dir runs/x +``` + +Note: the tools use `simpleArgParser`, so **boolean flags take a value** (`--no_llm True`, `--headless False`), not a bare flag. + ## Code conventions These exist because we got bitten: diff --git a/agent_demo/agent.py b/agent_demo/agent.py index fcd7d4b1..5832249c 100644 --- a/agent_demo/agent.py +++ b/agent_demo/agent.py @@ -19,12 +19,14 @@ class AgentArgs: task_id: str = "" out_dir: str = "./runs/agent" max_steps: int = 15 - model: str = "gpt-5.1" - api_key: str = "" - base_url: str = "" - headless: bool = True + model: str = "" # agent LLM model id (env: JUDGE_MODEL if unset) + api_key: str = "" # env: OPENAI_API_KEY + api_base: str = "" # env: OPENAI_BASE_URL + headless: bool = True # only used when MW_CDP_URL is unset (CDP takes priority) history_window: int = 5 dom_char_limit: int = 12000 + judge_rubric: str = "" # carried into trajectory.json for the LLM judge + verifier_path: str = "" # carried into trajectory.json for the verifier mode def post_process(self): if self.tasks_file: @@ -41,6 +43,8 @@ def post_process(self): self.task = self.task or row.get("ques", "") self.url = self.url or row.get("web", "") self.task_id = self.task_id or row.get("id", "") + self.judge_rubric = row.get("judge_rubric", "") + self.verifier_path = row.get("verifier_path", "") if not self.task or not self.url: raise SystemExit("provide --tasks_file [--task_id ID] or both --task and --url") @@ -140,11 +144,15 @@ def save_screenshot_b64(b64, path): async def run(args): api_key = args.api_key or os.environ.get("OPENAI_API_KEY", "") - base_url = args.base_url or os.environ.get("OPENAI_BASE_URL", "") + api_base = args.api_base or os.environ.get("OPENAI_BASE_URL", "") + model = args.model or os.environ.get("JUDGE_MODEL", "") if not api_key: raise SystemExit("API key missing: set --api_key or OPENAI_API_KEY") - if not base_url: - raise SystemExit("Base URL missing: set --base_url or OPENAI_BASE_URL") + if not api_base: + raise SystemExit("API base missing: set --api_base or OPENAI_BASE_URL") + if not model: + raise SystemExit("model missing: set --model or JUDGE_MODEL") + args.model = model print(f"task_id={args.task_id or ''} url={args.url}\n ques: {args.task}") @@ -153,9 +161,17 @@ async def run(args): shots = out / "screenshots" shots.mkdir(exist_ok=True) - client = OpenAI(base_url=base_url, api_key=api_key) - - browser = Browser(headless=args.headless) + client = OpenAI(base_url=api_base, api_key=api_key) + + # Connect to an externally-launched headless Chrome via CDP when MW_CDP_URL is + # set. browser-use's own Browser() launcher can hang on some hosts (watchdog + # timeout on BrowserStartEvent); a persistent CDP chrome avoids that and lets + # many agents run concurrently with cookie isolation (one chrome per agent). + cdp_url = os.environ.get("MW_CDP_URL", "") + if cdp_url: + browser = Browser(cdp_url=cdp_url) + else: + browser = Browser(headless=args.headless) await browser.start() tools = Tools() @@ -175,6 +191,8 @@ async def run(args): "terminated": False, "termination_reason": None, "final_answer": None, + "judge_rubric": args.judge_rubric, + "verifier_path": args.verifier_path, } for step_idx in range(args.max_steps): diff --git a/agent_demo/eval_judge.py b/agent_demo/eval_judge.py index 36f78c3a..a7c8f430 100644 --- a/agent_demo/eval_judge.py +++ b/agent_demo/eval_judge.py @@ -1,6 +1,8 @@ import base64 import json import os +import subprocess +import sys from dataclasses import dataclass from pathlib import Path @@ -13,16 +15,28 @@ class JudgeArgs: run_dir: str = "" out: str = "" last_k_screenshots: int = 4 - model: str = "gpt-5.1" + model: str = "" api_key: str = "" - base_url: str = "" + api_base: str = "" + # Grading mode (simpleargparser bool -> value flag: --verifier True). + # False (default): run the LLM-as-judge over the trajectory + last-K screenshots. + # True: run the task's deterministic verifier (the script at trajectory.verifier_path) + # if present; otherwise fall back to the LLM judge with a note. + verifier: bool = False -JUDGE_SYSTEM = """You are a strict grader of web-agent task completion. +# --------------------------------------------------------------------------- +# LLM config — unified env vars (also used by the per-task verifiers): +# OPENAI_API_KEY : bearer token for the OpenAI-compatible judge endpoint +# OPENAI_BASE_URL : base URL of that endpoint +# JUDGE_MODEL : model id to use as the LLM judge +# CLI flags override env (simpleargparser precedence: cmd > code > default). +JUDGE_SYSTEM_BASE = """You are a strict grader of web-agent task completion. You receive: - - the original natural-language TASK, - - the agent's trajectory (per step: thought + action + URL + (optional) extracted_content), + - the ORIGINAL TASK (natural language), + - the agent's full TRAJECTORY (per step: thought + action + params + URL + any + extracted_content), reproduced in full — do not assume anything is omitted, - the agent's final self-reported answer (if any), - the LAST K screenshots of the browser, in order. The last image is the final state the user would see. @@ -33,27 +47,54 @@ class JudgeArgs: - The agent answered with plausible-sounding but wrong information. - The agent stopped early (max_steps) without finishing. - The agent navigated somewhere unrelated. + - EMPTY / NO ANSWER: if the task asks for a fact and the agent's final + self-reported answer is EMPTY (or clearly absent), mark success=false. The + task is to PRODUCE an answer; navigating to the right page without + reporting the answer is NOT a completion. (You may still extract the + visible page content into answer_extracted for diagnostics, but it does not + rescue an empty self-report.) + - PRIOR-KNOWLEDGE SHORTCUT: if the agent's answer is correct but the + trajectory shows NO navigation to the site's relevant page (no step URL + reaching the mirror for that content), mark success=false. The task is to + NAVIGATE the site and read the answer off the page, not recall it. + - For QUIZ tasks success requires the agent navigated to the quiz, answered + every question, submitted, and reported the score shown on the result page. Tie-break rules: - - If the task asks for a fact and the agent's done.text contains a value, - cross-check that the value is visible in the final screenshot or in - extracted_content recorded in the trajectory. - - If the task is a navigational task ("find the page that ..."), the final - screenshot must show that page. - - If the agent performed irreversible actions (purchase, post comment, etc.) - that the task did not ask for, mark success=false and explain. + - If the task asks for a fact and done.text contains a value, cross-check it + against the final screenshot / extracted_content. + - If the task is navigational ("find the page that ..."), the final screenshot + must show that page. + - If the agent performed irreversible actions the task did not ask for, + mark success=false and explain. -Respond with ONLY this JSON object: +Respond with ONLY this JSON object (no code fence, no prose): { "success": , "confidence": <0.0-1.0>, - "rationale": "", + "rationale": "", "evidence": ["", "..."], "answer_extracted": "" } """ +# Appended to JUDGE_SYSTEM_BASE ONLY when the task carries a judge_rubric. +# This block makes the rubric binding: every MUST checkpoint must hold. +JUDGE_SYSTEM_RUBRIC = """ +--- +ADDITIONAL INSTRUCTIONS — THIS TASK HAS A JUDGE RUBRIC. +A JUDGE RUBRIC listing concrete FACT CHECKPOINTS is included in the evidence +below. You MUST grade against it: verify EVERY checkpoint and treat each MUST +as a hard requirement for success — if any MUST checkpoint is unmet, mark +success=false. In your rationale, explicitly check each rubric checkpoint. + +Include an extra field in your JSON response: + "rubric_checkpoints": {"": true/false, "...": "..."} +mapping each rubric checkpoint to whether it was satisfied. +""" + + def img_payload(path): b64 = base64.b64encode(path.read_bytes()).decode("ascii") @@ -61,25 +102,38 @@ def img_payload(path): "image_url": {"url": f"data:image/png;base64,{b64}"}} -def trajectory_text(traj, max_step_chars=400): +def trajectory_text(traj): + """Full, untruncated textual rendering of the trajectory. + + NOTHING is truncated here — truncating thoughts / extracted_content / answers + withers the judge's context and yields inaccurate verdicts. The model gets + the complete evidence so it can grade accurately. + """ lines = [] lines.append(f"TASK: {traj.get('task', '')}") + lines.append(f"TASK ID: {traj.get('task_id', '')}") lines.append(f"START URL: {traj.get('start_url', '')}") + rubric = traj.get('judge_rubric', '') + if rubric: + lines.append("") + lines.append("JUDGE RUBRIC — verify EVERY fact checkpoint below:") + lines.append(rubric) + lines.append("") lines.append(f"TERMINATED: {traj.get('terminated')} ({traj.get('termination_reason')})") - lines.append(f"AGENT'S FINAL ANSWER (self-report): {traj.get('final_answer', '')!r}") + lines.append(f"AGENT'S FINAL ANSWER (self-report): {traj.get('final_answer', '')!r}") lines.append(f"AGENT'S SELF-REPORTED SUCCESS: {traj.get('success_self_report', '')}") lines.append("") - lines.append("STEPS:") + lines.append("STEPS (full, oldest → newest):") for s in traj.get("steps", []): ar = s.get("action_result") or {} - ec = ar.get("extracted_content") or "" line = ( - f" [{s['step']}] {s['action']} {s.get('params', {})} " - f"@ {s['url']}\n" - f" thought: {s.get('thought', '')[:max_step_chars]}" + f" [step {s['step']}] action={s['action']} params={s.get('params', {})} " + f"url={s.get('url', '')}\n" + f" thought: {s.get('thought', '')}" ) + ec = ar.get("extracted_content") or "" if ec: - line += f"\n extracted: {ec[:max_step_chars]}" + line += f"\n extracted_content: {ec}" if ar.get("error"): line += f"\n error: {ar['error']}" lines.append(line) @@ -92,9 +146,15 @@ def build_messages(traj, screenshot_paths): for p in screenshot_paths: user_content.append({"type": "text", "text": f"({p.name})"}) user_content.append(img_payload(p)) - user_content.append({"type": "text", "text": "Now grade. Respond with the JSON only."}) + user_content.append({"type": "text", "text": "Now grade. Respond with the JSON object only."}) + # If/else: the rubric-specific system-prompt block is appended ONLY when this + # task carries a judge_rubric. Tasks without one (the other 15 sites) get the + # plain base prompt — no rubric mention, no rubric_checkpoints field. + system = JUDGE_SYSTEM_BASE + if traj.get('judge_rubric'): + system = JUDGE_SYSTEM_BASE + JUDGE_SYSTEM_RUBRIC return [ - {"role": "system", "content": JUDGE_SYSTEM}, + {"role": "system", "content": system}, {"role": "user", "content": user_content}, ] @@ -120,20 +180,93 @@ def parse_judge_json(raw): raise ValueError(f"unterminated JSON in judge reply: {raw[:200]!r}") +def run_verifier(run_dir: Path, traj: dict) -> dict: + """Run the task's deterministic verifier (mode --verifier True). + + Looks up verifier_path from the trajectory (written by agent.py). Runs the + verifier under agent_demo's uv project so its simpleArgParser dependency is + available; forwards all env vars (incl. OPENAI_API_KEY/OPENAI_BASE_URL/ + JUDGE_MODEL for the verifier's LLM utilities). Returns a verdict dict shaped + like eval.json (the verifier's {task_id,pass,reason,evidence} + meta). + """ + verifier_path = traj.get("verifier_path", "") + if not verifier_path: + return { + "success": False, + "confidence": 1.0, + "rationale": "verifier mode requested but the trajectory has no verifier_path " + "(this task/site has no deterministic verifier); falling back is not possible.", + "evidence": [], + "answer_extracted": traj.get("final_answer", "") or "", + "meta": {"mode": "verifier", "verifier_path": None, "run_dir": str(run_dir)}, + } + vp = Path(verifier_path) + if not vp.is_absolute(): + # verifier_path is relative to the repo root; resolve from there. + repo_root = Path(__file__).resolve().parents[1] + vp = repo_root / vp + if not vp.exists(): + return { + "success": False, + "confidence": 1.0, + "rationale": f"verifier_path not found: {vp}", + "evidence": [], + "answer_extracted": traj.get("final_answer", "") or "", + "meta": {"mode": "verifier", "verifier_path": str(vp), "run_dir": str(run_dir)}, + } + # Run under agent_demo/ so `uv` finds the pyproject + simpleArgParser dep. + agent_demo_dir = Path(__file__).resolve().parent + cmd = ["uv", "run", "python", str(vp), "--run_dir", str(run_dir)] + r = subprocess.run(cmd, capture_output=True, text=True, cwd=str(agent_demo_dir), + env=os.environ.copy()) + try: + verdict = json.loads(r.stdout) + except Exception: + verdict = { + "success": False, + "confidence": 1.0, + "rationale": f"verifier crashed: {r.stderr[:500] or r.stdout[:500]}", + "evidence": [], + "answer_extracted": "", + "meta": {"mode": "verifier", "verifier_path": str(vp), + "run_dir": str(run_dir), "returncode": r.returncode}, + } + else: + verdict.setdefault("meta", {}) + verdict["meta"].update({"mode": "verifier", "verifier_path": str(vp), + "run_dir": str(run_dir), "returncode": r.returncode}) + # normalize: verifier emits {pass: bool} -> also expose {success: bool} + if "success" not in verdict and "pass" in verdict: + verdict["success"] = bool(verdict["pass"]) + return verdict + + def main(): args = sap.parse_args(JudgeArgs) if not args.run_dir: raise SystemExit("--run_dir is required") + run_dir = Path(args.run_dir) + traj = json.loads((run_dir / "trajectory.json").read_text()) + + if args.verifier: + print(f"[verifier mode] task_id={traj.get('task_id','?')} verifier={traj.get('verifier_path','')}") + verdict = run_verifier(run_dir, traj) + out_path = Path(args.out) if args.out else run_dir / "eval.json" + out_path.write_text(json.dumps(verdict, indent=2)) + print(f"wrote {out_path}") + print(f" pass: {verdict.get('pass')} success: {verdict.get('success')} reason: {verdict.get('reason','')}") + sys.exit(0 if verdict.get("success") else 1) + api_key = args.api_key or os.environ.get("OPENAI_API_KEY", "") - base_url = args.base_url or os.environ.get("OPENAI_BASE_URL", "") + api_base = args.api_base or os.environ.get("OPENAI_BASE_URL", "") + model = args.model or os.environ.get("JUDGE_MODEL", "") if not api_key: raise SystemExit("API key missing: set --api_key or OPENAI_API_KEY") - if not base_url: - raise SystemExit("Base URL missing: set --base_url or OPENAI_BASE_URL") - - run_dir = Path(args.run_dir) - traj = json.loads((run_dir / "trajectory.json").read_text()) + if not api_base: + raise SystemExit("API base missing: set --api_base or OPENAI_BASE_URL") + if not model: + raise SystemExit("model missing: set --model or JUDGE_MODEL") shots_dir = run_dir / "screenshots" all_shots = sorted(shots_dir.glob("step_*.png")) @@ -143,10 +276,10 @@ def main(): print(f"judging {len(traj.get('steps', []))} steps with last {len(last_k)} screenshots: " f"{[p.name for p in last_k]}") - client = OpenAI(base_url=base_url, api_key=api_key) + client = OpenAI(base_url=api_base, api_key=api_key) messages = build_messages(traj, last_k) - resp = client.chat.completions.create(model=args.model, messages=messages) + resp = client.chat.completions.create(model=model, messages=messages) raw = resp.choices[0].message.content or "" try: verdict = parse_judge_json(raw) @@ -163,7 +296,7 @@ def main(): verdict["meta"] = { "run_dir": str(run_dir), "task": traj.get("task", ""), - "model": args.model, + "model": model, "screenshots_used": [p.name for p in last_k], "trajectory_steps": len(traj.get("steps", [])), } @@ -172,7 +305,7 @@ def main(): out_path.write_text(json.dumps(verdict, indent=2)) print(f"wrote {out_path}") print(f" success: {verdict.get('success')} confidence: {verdict.get('confidence')}") - print(f" rationale: {verdict.get('rationale', '')[:300]}") + print(f" rationale: {verdict.get('rationale', '')}") if __name__ == "__main__": diff --git a/sites/merriam_webster/_common_words.py b/sites/merriam_webster/_common_words.py new file mode 100644 index 00000000..02f59987 --- /dev/null +++ b/sites/merriam_webster/_common_words.py @@ -0,0 +1,222 @@ +"""Common everyday vocabulary added to the Merriam-Webster mirror catalog. + +The original WORDS list (142 entries) is skewed toward advanced/literary +vocabulary, so any off-script lookup of an everyday word dead-ended. These +common words give the dictionary real coverage and provide real distractors. +Data (part of speech, definition, first-known-use, etymology) follows +Merriam-Webster's published entries; pronunciation uses MW-style respelling. +Seed combines these with WORDS via seed_data.ALL_WORDS. +""" + +COMMON_WORDS = [ + { + "headword": "water", + "slug": "water", + "pos": "noun", + "pronunciation": "ˈwȯ-tər", + "syllables": "wa-ter", + "first_known_use": "before 12th century", + "etymology": "Middle English, from Old English wæter; akin to Old High German wazzar water, Greek hydōr water, Latin unda wave", + "definitions": [ + {"sense_num": 1, "text": "the liquid that descends from the clouds as rain and forms streams, lakes, and seas", "examples": ["a glass of cold water", "the water in the lake was clear"]}, + {"sense_num": 2, "text": "a body of water (as a sea, lake, or river)", "examples": ["the ship was still in open water"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "dog", + "slug": "dog", + "pos": "noun", + "pronunciation": "ˈdȯg", + "syllables": "dog", + "first_known_use": "before 12th century", + "etymology": "Middle English, from Old English docga; akin to Old English docga a powerful dog", + "definitions": [ + {"sense_num": 1, "text": "a highly variable domestic mammal (Canis familiaris) closely related to the gray wolf", "examples": ["the family dog", "a stray dog followed us home"]}, + {"sense_num": 2, "text": "a worthless or contemptible person", "examples": ["you lucky dog"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "run", + "slug": "run", + "pos": "verb", + "pronunciation": "ˈrən", + "syllables": "run", + "first_known_use": "before 12th century", + "etymology": "Middle English ronnen, alteration of Old English irnan, eornan; akin to Old High German rinnan to run, Greek orcheus", + "definitions": [ + {"sense_num": 1, "text": "to go faster than a walk; to move at a pace faster than walking", "examples": ["run to the store", "she had to run to catch the bus"]}, + {"sense_num": 2, "text": "to flow steadily", "examples": ["the river runs to the sea"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "baby", + "slug": "baby", + "pos": "noun", + "pronunciation": "ˈbā-bē", + "syllables": "ba-by", + "first_known_use": "14th century", + "etymology": "Middle English, from baby baby; diminutive of babe", + "definitions": [ + {"sense_num": 1, "text": "an extremely young child; especially one not yet able to walk or talk", "examples": ["a baby crying in the next room", "rock the baby to sleep"]}, + {"sense_num": 2, "text": "the youngest member of a group", "examples": ["the baby of the family"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "cry", + "slug": "cry", + "pos": "verb", + "pronunciation": "ˈkrī", + "syllables": "cry", + "first_known_use": "13th century", + "etymology": "Middle English crien, from Anglo-French crier, from Latin quiritare to cry out", + "definitions": [ + {"sense_num": 1, "text": "to call loudly; to weep or shed tears", "examples": ["cry for help", "the baby began to cry"]}, + {"sense_num": 2, "text": "to require or call for urgently", "examples": ["the matter cries for attention"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "book", + "slug": "book", + "pos": "noun", + "pronunciation": "ˈbu̇k", + "syllables": "book", + "first_known_use": "before 12th century", + "etymology": "Middle English, from Old English bōc; akin to Old High German buoh book, Gothic boka letter", + "definitions": [ + {"sense_num": 1, "text": "a set of printed sheets of paper bound together along one edge", "examples": ["read a good book", "a book of poems"]}, + {"sense_num": 2, "text": "a set of rules or records", "examples": ["he threw the book at them"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "tree", + "slug": "tree", + "pos": "noun", + "pronunciation": "ˈtrē", + "syllables": "tree", + "first_known_use": "before 12th century", + "etymology": "Middle English, from Old English trēow; akin to Old High German triu tree, Greek drys oak", + "definitions": [ + {"sense_num": 1, "text": "a woody perennial plant having a single usually elongate main stem generally standing erect", "examples": ["climb a tree", "an old oak tree"]}, + {"sense_num": 2, "text": "something branching out from a stem", "examples": ["a family tree"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "house", + "slug": "house", + "pos": "noun", + "pronunciation": "ˈhau̇s", + "syllables": "house", + "first_known_use": "before 12th century", + "etymology": "Middle English, from Old English hūs; akin to Old High German hūs house", + "definitions": [ + {"sense_num": 1, "text": "a building that serves as living quarters for one or a few families", "examples": ["buy a new house", "a house on the hill"]}, + {"sense_num": 2, "text": "a household", "examples": ["the whole house was asleep"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "food", + "slug": "food", + "pos": "noun", + "pronunciation": "ˈfüd", + "syllables": "food", + "first_known_use": "before 12th century", + "etymology": "Middle English, from Old English fōda; akin to Old High German fuotar food, Latin panis bread", + "definitions": [ + {"sense_num": 1, "text": "material consisting essentially of protein, carbohydrate, and fat used in the body of an organism to sustain growth, repair, and vital processes", "examples": ["good food", "a steady supply of food"]}, + {"sense_num": 2, "text": "nutriment in solid form", "examples": ["gave the dog its food"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "money", + "slug": "money", + "pos": "noun", + "pronunciation": "ˈmə-nē", + "syllables": "mon-ey", + "first_known_use": "14th century", + "etymology": "Middle English, from Anglo-French moneie, from Latin moneta mint, money, from Moneta, epithet of Juno", + "definitions": [ + {"sense_num": 1, "text": "something generally accepted as a medium of exchange, a measure of value, or a means of payment", "examples": ["save up money", "spend money wisely"]}, + {"sense_num": 2, "text": "wealth reckoned in terms of money", "examples": ["made money on the deal"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "school", + "slug": "school", + "pos": "noun", + "pronunciation": "ˈskül", + "syllables": "school", + "first_known_use": "before 12th century", + "etymology": "Middle English scole, from Old English scōl; from Latin schola, from Greek scholē leisure, discussion, lecture", + "definitions": [ + {"sense_num": 1, "text": "an organization that provides instruction; an institution for the teaching of children", "examples": ["go to school", "a new school was built"]}, + {"sense_num": 2, "text": "a group of persons who hold a common doctrine or follow the same teacher", "examples": ["the Stoic school"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "friend", + "slug": "friend", + "pos": "noun", + "pronunciation": "ˈfrend", + "syllables": "friend", + "first_known_use": "before 12th century", + "etymology": "Middle English, from Old English frēond; akin to Old High German friunt friend, Old English frēon to love", + "definitions": [ + {"sense_num": 1, "text": "a person you know well and like who is not a member of your family", "examples": ["a close friend", "meet a friend for coffee"]}, + {"sense_num": 2, "text": "one that favors or promotes something", "examples": ["a friend of the arts"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "smile", + "slug": "smile", + "pos": "verb", + "pronunciation": "ˈsmī(-ə)l", + "syllables": "smile", + "first_known_use": "14th century", + "etymology": "Middle English, from Old English smerian; akin to Old English smearwian to smear", + "definitions": [ + {"sense_num": 1, "text": "to have or show a pleased expression on the face", "examples": ["smile at the camera", "she began to smile"]}, + {"sense_num": 2, "text": "to express by a smile", "examples": ["smiled her thanks"]} + ], + "synonyms": [], + "difficulty": "common", + }, + { + "headword": "light", + "slug": "light", + "pos": "noun", + "pronunciation": "ˈlīt", + "syllables": "light", + "first_known_use": "before 12th century", + "etymology": "Middle English, from Old English lēoht; akin to Old High German lioht light, Latin luc-, lux light, Greek leukos white", + "definitions": [ + {"sense_num": 1, "text": "something that makes vision possible; electromagnetic radiation of any wavelength that travels in a vacuum", "examples": ["a beam of light", "turn on the light"]}, + {"sense_num": 2, "text": "a source of light (as a lamp or the sun)", "examples": ["stand in the light"]} + ], + "synonyms": [], + "difficulty": "common", + }, +] diff --git a/sites/merriam_webster/app.py b/sites/merriam_webster/app.py index 18da7f90..4ea0fd0d 100644 --- a/sites/merriam_webster/app.py +++ b/sites/merriam_webster/app.py @@ -27,13 +27,15 @@ app = Flask(__name__) app.config['SECRET_KEY'] = 'merriam-webster-mirror-secret-key' -app.config['SQLALCHEMY_DATABASE_URI'] = ( - f"sqlite:///{os.path.join(BASE_DIR, 'instance', 'merriam_webster.db')}" -) +# DB path is overridable so the seed-regeneration tooling can write +# instance_seed/ instead of the runtime instance/. Default = runtime DB. +DB_PATH = os.environ.get('MW_DB_PATH', + os.path.join(BASE_DIR, 'instance', 'merriam_webster.db')) +app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{DB_PATH}" app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['WTF_CSRF_TIME_LIMIT'] = None -os.makedirs(os.path.join(BASE_DIR, 'instance'), exist_ok=True) +os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) db = SQLAlchemy(app) bcrypt = Bcrypt(app) @@ -280,17 +282,21 @@ def search_words(q): return [w for _, w in scored] + syn_words +# The "Word of the Day" is pinned to a fixed benchmark date so it is +# IDENTICAL across runs. Using date.today() rotated the featured word by run +# date, which made WOTD tasks non-deterministic (no stable answer). +BENCHMARK_TODAY = date(2025, 1, 10) + + def todays_wotd(): - """Return today's WOTD, falling back to a deterministic rotation so the - homepage always has one even on dates with no explicit row.""" - today = date.today() - w = WordOfTheDay.query.filter_by(feature_date=today).first() + """Return the benchmark Word of the Day (deterministic across runs).""" + w = WordOfTheDay.query.filter_by(feature_date=BENCHMARK_TODAY).first() if w: return w rows = WordOfTheDay.query.order_by(WordOfTheDay.id).all() if not rows: return None - return rows[today.toordinal() % len(rows)] + return rows[BENCHMARK_TODAY.toordinal() % len(rows)] # --------------------------------------------------------------------------- @@ -414,18 +420,28 @@ def quiz_detail(slug): def quiz_submit(slug): quiz = Quiz.query.filter_by(slug=slug).first_or_404() questions = quiz.get_questions() + # Require EVERY question to be answered before scoring. Without this the + # result page is untrustworthy (an unanswered question rendered the same as + # a correct one) and "answer all questions" tasks could be gamed by leaving + # questions blank. (quiz.html radios are also `required`.) + missing = [str(i + 1) for i in range(len(questions)) + if request.form.get(f'q{i}') is None] + if missing: + flash(f'Please answer every question before submitting. ' + f'Not answered: {", ".join(missing)}.', 'error') + return redirect(url_for('quiz_detail', slug=quiz.slug)) score = 0 review = [] for i, qq in enumerate(questions): picked = request.form.get(f'q{i}') correct = qq.get('answer_index') - ok = picked is not None and int(picked) == correct + ok = int(picked) == correct if ok: score += 1 review.append({ 'q': qq.get('q'), 'choices': qq.get('choices', []), - 'picked': int(picked) if picked is not None else None, + 'picked': int(picked), 'answer_index': correct, 'explanation': qq.get('explanation', ''), 'correct': ok, @@ -545,7 +561,9 @@ def server_error(e): @app.context_processor def inject_globals(): - return {'current_year': datetime.now().year} + # Frozen to 2025 to match the WOTD feature_date year, so the site is + # internally date-consistent (footer year == WOTD year) and deterministic. + return {'current_year': 2025} # --------------------------------------------------------------------------- diff --git a/sites/merriam_webster/seed_data.py b/sites/merriam_webster/seed_data.py index 53e73fe9..9f388a94 100644 --- a/sites/merriam_webster/seed_data.py +++ b/sites/merriam_webster/seed_data.py @@ -9,6 +9,11 @@ from datetime import date, timedelta from _seed_content import WORDS, THESAURUS, WORD_OF_THE_DAY, QUIZZES +from _common_words import COMMON_WORDS + +# Full dictionary catalog = curated advanced/literary words plus everyday +# common words (broadens coverage so off-script lookups don't dead-end). +ALL_WORDS = WORDS + COMMON_WORDS # Fixed reference date so WORD_OF_THE_DAY rows are deterministic across boots # and resets (using date.today() would make the seed DB differ day to day). @@ -19,7 +24,7 @@ def seed_database(db, Word, ThesaurusEntry, WordOfTheDay, Quiz): if Word.query.count() > 0: return - for w in WORDS: + for w in ALL_WORDS: db.session.add(Word( headword=w['headword'], slug=w['slug'], @@ -65,7 +70,7 @@ def seed_database(db, Word, ThesaurusEntry, WordOfTheDay, Quiz): )) db.session.commit() - print(f"Seeded {len(WORDS)} words, {len(THESAURUS)} thesaurus entries, " + print(f"Seeded {len(ALL_WORDS)} words, {len(THESAURUS)} thesaurus entries, " f"{len(WORD_OF_THE_DAY)} WOTD, {len(QUIZZES)} quizzes.") diff --git a/sites/merriam_webster/tasks.jsonl b/sites/merriam_webster/tasks.jsonl index 2c3f22a8..704b49b6 100644 --- a/sites/merriam_webster/tasks.jsonl +++ b/sites/merriam_webster/tasks.jsonl @@ -1,20 +1,20 @@ -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--0", "ques": "Look up the word \"serendipity\" and tell me its part of speech and pronunciation.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--1", "ques": "Find the first definition of the word \"ubiquitous\" and one example sentence that uses it.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--2", "ques": "What is the etymology (word history) of \"nostalgia\", and which language does it ultimately come from?", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--3", "ques": "In what year was the word \"empathy\" first known to be used, according to its dictionary entry?", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--4", "ques": "Look up \"meticulous\" and report the year of its first known use.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--5", "ques": "Open the thesaurus entry for \"brave\" and list three of its synonyms.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--6", "ques": "Find the antonyms of \"calm\" in the thesaurus and tell me two of them.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--7", "ques": "Using the thesaurus, find a synonym of \"difficult\" that starts with the letter C.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--8", "ques": "Look up the thesaurus entry for \"happy\" and tell me both a synonym and an antonym of it.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--9", "ques": "Go to the Word of the Day page and tell me today's featured word and its part of speech.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--10", "ques": "In the Word of the Day section, find the entry for \"ephemeral\" and tell me what it means.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--11", "ques": "Go to Games & Quizzes, open the quiz titled \"Name That Word\", answer all of its questions, and tell me your final score.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--12", "ques": "Find and complete the \"Synonym Showdown\" quiz, then report how many questions you got correct out of the total.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--13", "ques": "Browse the Games & Quizzes page and tell me how many quizzes are available and the difficulty level of each.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--14", "ques": "Log in with the demo account (alice.j@test.com), then save the word \"serendipity\" to your word list.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--15", "ques": "Log in with the demo account, save the words \"resilient\" and \"gratitude\" to your saved words, then go to your account page and confirm both appear.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--16", "ques": "Register a new account with the name \"Jordan Lee\", a username of your choice, and any email, then verify you are logged in.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--17", "ques": "Among the words \"empathy\", \"nostalgia\", and \"optimism\", which one has the most recent first known use? Look up each entry to decide.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--18", "ques": "Log in with the demo account and remove a word from my saved words list. (My list already has several words — ask me which one to remove if it's unclear.)", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} -{"web_name": "Merriam-Webster", "id": "Merriam-Webster--19", "ques": "Compare the words \"gregarious\" and \"benevolent\": which one entered English earlier, and what part of speech is each?", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/"} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--0", "ques": "Look up the word \"serendipity\" on the dictionary. What is the Merriam-Webster respelling pronunciation shown on its entry page (the respelling with hyphens and accent marks, e.g. ˈ, not IPA)?", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_0.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent's trajectory MUST contain a navigation to the dictionary entry page for \"serendipity\" (a URL like /dictionary/serendipity). (2) The reported pronunciation MUST be the Merriam-Webster RESPPELLING shown on that page, including its accent/stress marks (e.g. the leading secondary-stress ˌ and the primary-stress ˈ), NOT IPA and NOT a partial respelling missing marks. (3) The final answer must actually contain the respelling (an empty answer is a FAIL even if the page was visited). FAIL if: no visit to the serendipity entry; answer is empty; answer is IPA or omits the stress marks shown on the page."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--1", "ques": "Look up \"ubiquitous\". Quote the exact text of its first numbered definition (sense 1) as shown on the dictionary entry page.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_1.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the dictionary entry for \"ubiquitous\" (/dictionary/ubiquitous). (2) The reported text MUST be the FIRST numbered sense (sense 1) definition verbatim as shown on that page (\"existing or being everywhere at the same time : constantly encountered : widespread\"). (3) The final answer must be non-empty. FAIL if: no visit to the ubiquitous entry; answer gives a different sense, a paraphrase, or is empty."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--2", "ques": "Look up \"nostalgia\". In its Word History & Etymology section, which Greek word (with its quoted meaning) does the entry say nostalgia is ultimately derived from? Quote it as shown.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_2.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the dictionary entry for \"nostalgia\" (/dictionary/nostalgia) and reach its Word History & Etymology section. (2) The answer MUST identify the Greek source word \"nóstos\" together with its quoted meaning (\"return, homecoming\"). (3) Final answer must be non-empty. FAIL if: no visit to the nostalgia entry; answer names the wrong source language/word or omits the meaning; answer is empty."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--3", "ques": "According to its dictionary entry, in what year was the word \"empathy\" first known to be used? (Report the First Known Use value shown on the page.)", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_3.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the dictionary entry for \"empathy\" (/dictionary/empathy). (2) The reported First Known Use year MUST be 1909. (3) Final answer must be non-empty. FAIL if: no visit to the empathy entry; a different year is reported; answer is empty (even if the page showed 1909)."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--4", "ques": "Look up \"meticulous\" and report the First Known Use year shown on its dictionary entry page.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_4.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the dictionary entry for \"meticulous\" (/dictionary/meticulous). (2) The reported First Known Use year MUST be 1827. (3) Final answer must be non-empty. FAIL if: no visit to the meticulous entry; a different year; empty answer."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--5", "ques": "Open the thesaurus entry for \"brave\". List every synonym shown on that page, exactly as shown and in the order they appear.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_5.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the THESAURUS entry for \"brave\" (/thesaurus/brave) — the dictionary entry (/dictionary/brave) is NOT sufficient. (2) The answer MUST list synonyms shown on that thesaurus page, specifically including: courageous, fearless, valiant, heroic, gallant, bold, adventurous, intrepid. (3) Final answer non-empty. FAIL if: only the dictionary page was visited; the synonym list is missing required entries or is empty."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--6", "ques": "Open the thesaurus entry for \"calm\". List every antonym shown on that page, exactly as shown.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_6.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the THESAURUS entry for \"calm\" (/thesaurus/calm). (2) The answer MUST list antonyms shown on that page, including: angry, turbulent, restless, agitated, stormy, unsettled, rough, tempestuous. (3) Final answer non-empty. FAIL if: only the dictionary page visited; antonyms missing or empty."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--7", "ques": "Open the thesaurus entry for \"difficult\". Which of the synonyms shown start with the letter C? List them exactly as shown on the page.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_7.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the THESAURUS entry for \"difficult\" (/thesaurus/difficult), not the dictionary entry. (2) The answer MUST list the synonyms starting with the letter C — at minimum \"challenging\" and \"complicated\". (3) Final answer non-empty. FAIL if: only /dictionary/difficult visited; the C-initial synonyms are missing; answer empty."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--8", "ques": "Open the thesaurus entry for \"happy\". How many synonyms are listed, and how many antonyms? Report both counts exactly as shown on the page.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_8.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the THESAURUS entry for \"happy\" (/thesaurus/happy). (2) The answer MUST report BOTH counts as shown: 8 synonyms and 8 antonyms. (3) Final answer non-empty and must contain both numbers. FAIL if: only dictionary page visited; either count is missing or wrong; answer empty."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--9", "ques": "Go to the Word of the Day page. What is the featured word and its part of speech? (The site's Word of the Day is fixed for this benchmark.)", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_9.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the Word of the Day page (/word-of-the-day). (2) The answer MUST identify the featured word as \"ambivalent\" and its part of speech as \"adjective\". (3) Final answer non-empty. FAIL if: no visit to the WOTD page; wrong word/POS; empty answer (the WOTD is pinned, so the expected answer is stable)."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--10", "ques": "On the Word of the Day page, open the entry for \"ephemeral\". What does the \"Did You Know?\" section say about where the word comes from? Quote the text shown.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_10.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the Word of the Day entry for \"ephemeral\" (/word-of-the-day/ephemeral). (2) The answer MUST quote the \"Did You Know?\" section, which states the word comes from \"Greek ephēmeros lasting a day, daily, from epi- + hēmera day\". (3) Final answer non-empty. FAIL if: no visit to the ephemeral WOTD entry; the answer paraphrases or omits the Greek ephēmeros etymology; empty."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--11", "ques": "Go to Games & Quizzes, open the quiz titled \"Name That Word\", answer ALL of its questions, then report the final score shown on the result page (in the form X/10).", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_11.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the \"Name That Word\" quiz (/quiz/name-that-word). (2) The agent MUST submit the quiz — a navigation to /quiz/name-that-word/submit must appear (the form now requires every question answered). (3) The answer MUST report a final score in X/10 form, and that score must match the score shown on the result-page screenshot. (4) Final answer non-empty. FAIL if: the quiz page was never opened; the quiz was never submitted (still on the questions page); no score reported; the reported score contradicts the result page; empty answer."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--12", "ques": "Find and complete the \"Synonym Showdown\" quiz (answer every question), then report the score shown on the result page (X/10).", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_12.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the \"Synonym Showdown\" quiz (/quiz/synonym-showdown). (2) The agent MUST submit it — a navigation to /quiz/synonym-showdown/submit must appear. (3) The answer MUST report a score X/10 matching the on-screen result page. (4) Final answer non-empty. FAIL if: quiz never opened; never submitted; no score; reported score contradicts the result page; empty answer."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--13", "ques": "Browse the Games & Quizzes page. How many quizzes are available, and what is the difficulty level of each (as labeled on the page)?", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_13.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the Games & Quizzes index page (/games-quizzes). (2) The answer MUST report the correct count (3 quizzes) and each difficulty as labeled: Name That Word (easy), Synonym Showdown (medium), Opposites Attract (medium). (3) Final answer non-empty. FAIL if: the index page never opened; the count or any difficulty is wrong/missing; empty answer."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--14", "ques": "Log in with the demo account (email: alice.j@test.com, password: TestPass123!), then go to the dictionary entry for \"serendipity\" and save it to your word list. Finally, open your account (My Words) page and confirm that \"serendipity\" now appears among your saved words.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_14.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST log in with the demo account (email alice.j@test.com, password TestPass123!) — a navigation to /login must appear. (2) The agent MUST open the dictionary entry for \"serendipity\" (/dictionary/serendipity) and save it. (3) The agent MUST open the account/My Words page (/account), and the final screenshot / saved-words list there MUST show \"serendipity\". (4) Final answer non-empty. FAIL if: no login; the word was never saved (not present on the account page); the account page was never visited; empty answer."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--15", "ques": "Log in with the demo account (email: alice.j@test.com, password: TestPass123!), save the words \"resilient\" and \"gratitude\" to your saved words, then open your account (My Words) page and confirm both appear.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_15.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST log in with the demo account (alice.j@test.com / TestPass123!) — /login visited. (2) The agent MUST save BOTH \"resilient\" AND \"gratitude\". (3) The agent MUST visit /account and the saved-words list there MUST contain both words. (4) Final answer non-empty. FAIL if: no login; either word is missing from the saved list; /account never visited; empty answer."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--16", "ques": "Register a new account with the name \"Jordan Lee\", a username of your choice, and any valid email and password (password must be at least 6 characters, and the confirm-password field must match). Then verify you are logged in (the navigation should show \"My Words\" and \"Log Out\").", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_16.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the register page (/register) and submit a registration with the name \"Jordan Lee\". (2) After registering, the agent MUST be logged in — the navigation must show \"My Words\" and \"Log Out\" (not \"Log In\"/\"Sign Up\"). (3) Final answer non-empty. FAIL if: /register never visited; the user is not logged in at the end; empty answer."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--17", "ques": "Among the words \"empathy\", \"nostalgia\", and \"optimism\", which has the most recent first known use? Look up each entry's First Known Use to decide, and report the year for all three.", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_17.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to the dictionary entries for ALL THREE words — empathy, nostalgia, and optimism (/dictionary/empathy, /dictionary/nostalgia, /dictionary/optimism). (2) The answer MUST report all three First Known Use years (empathy 1909, nostalgia 1756, optimism 1759) and identify empathy (1909) as the most recent. (3) Final answer non-empty. FAIL if: any of the three entries was not visited; any year is wrong/missing; empathy is not identified as the most recent; empty answer."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--18", "ques": "Log in with the demo account (email: alice.j@test.com, password: TestPass123!), then go to your account (My Words) page and remove the word \"curiosity\" from your saved words. Confirm it is gone and that your list now has exactly 2 words (harmony and eloquent).", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_18.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST log in with the demo account (alice.j@test.com / TestPass123!) — /login visited. (2) The agent MUST navigate to /account and remove the word \"curiosity\". (3) After removal, the saved-words list MUST contain exactly harmony and eloquent (2 words), with curiosity gone. (4) Final answer non-empty. FAIL if: no login; curiosity is still present; the list does not reduce to exactly harmony and eloquent; /account never visited; empty answer."} +{"web_name": "Merriam-Webster", "id": "Merriam-Webster--19", "ques": "Compare the words \"gregarious\" and \"benevolent\": look up each dictionary entry. Which one entered English earlier (by First Known Use), and what part of speech is each?", "web": "http://localhost:40015/", "upstream_url": "https://www.merriam-webster.com/", "verifier_path": "sites/merriam_webster/verify/verify_19.py", "judge_rubric": "FACT CHECKPOINTS: (1) The agent MUST navigate to BOTH dictionary entries — gregarious (/dictionary/gregarious) and benevolent (/dictionary/benevolent). (2) The answer MUST report that benevolent entered English earlier (First Known Use 15th century) than gregarious (1668), and that BOTH words are adjectives. (3) Final answer non-empty. FAIL if: either entry not visited; wrong word identified as earlier; either part of speech wrong/missing; empty answer."} diff --git a/sites/merriam_webster/templates/quiz.html b/sites/merriam_webster/templates/quiz.html index 815ffbfa..6df45b3d 100644 --- a/sites/merriam_webster/templates/quiz.html +++ b/sites/merriam_webster/templates/quiz.html @@ -15,7 +15,7 @@

{{ quiz.title }}
{{ loop.index }}. {{ q.q }}
{% for choice in q.choices %} {% endfor %} diff --git a/sites/merriam_webster/templates/quiz_result.html b/sites/merriam_webster/templates/quiz_result.html index 61eedebe..e3b308b4 100644 --- a/sites/merriam_webster/templates/quiz_result.html +++ b/sites/merriam_webster/templates/quiz_result.html @@ -12,14 +12,19 @@

{{ quiz.title }} — Review

{{ loop.index }}. {{ r.q }}
{% for choice in r.choices %} - {% if loop.index0 == r.answer_index %} + {% set is_answer = loop.index0 == r.answer_index %} + {% set is_picked = r.picked is not none and loop.index0 == r.picked %} + {% if is_answer and is_picked %} +
✓ {{ choice }} (your answer, correct)
+ {% elif is_answer %}
✓ {{ choice }} (correct answer)
- {% elif loop.index0 == r.picked %} + {% elif is_picked %}
✗ {{ choice }} (your answer)
{% else %}
{{ choice }}
{% endif %} {% endfor %} + {% if r.picked is none %}

Not answered

{% endif %} {% if r.explanation %}

{{ r.explanation }}

{% endif %}
{% endfor %} diff --git a/sites/merriam_webster/verify/verify_0.py b/sites/merriam_webster/verify/verify_0.py new file mode 100644 index 00000000..92f83ca8 --- /dev/null +++ b/sites/merriam_webster/verify/verify_0.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--0. + +Look up serendipity; report its MW respelling pronunciation. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /dictionary/serendipity | answer=respelling (LLM-anchored, phrasing varies) | screenshot shows pronunciation +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--0', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_serendipity", navigated_to(t, "/dictionary/serendipity"), + f"navigated={navigated_to(t, '/dictionary/serendipity')}") + # Deterministic answer check: the task explicitly asks for the respelling WITH its + # accent marks as shown, so the leading secondary-stress ˌ + primary-stress ˈdi must + # both appear. (An agent that drops the leading ˌ has not reported it 'as shown'.) + j.check("answer_has_stress_marks", contains_all(fa, ["ˌser", "ˈdi", "pə", "tē"]), + f"final={fa!r}") + ok, ev = llm_text_match(fa, "ˌser-ən-ˈdi-pə-tē", + "What is the Merriam-Webster respelling pronunciation of serendipity?") + j.check("answer_pronunciation", ok, ev, llm=True) + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "\u02ccser-\u0259n-\u02c8di-p\u0259-t\u0113", + "respelling pronunciation of serendipity") + j.check("screenshot_shows_pron", ok, ev, llm=True) + else: + j.check("screenshot_shows_pron", False, "no screenshots in run") + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_1.py b/sites/merriam_webster/verify/verify_1.py new file mode 100644 index 00000000..4ab190b6 --- /dev/null +++ b/sites/merriam_webster/verify/verify_1.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--1. + +Look up ubiquitous; quote exact sense-1 definition. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /dictionary/ubiquitous | answer==sense1 text (deterministic norm-equal) | screenshot shows sense1 +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--1', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_ubiquitous", navigated_to(t, "/dictionary/ubiquitous"), + f"navigated={navigated_to(t, '/dictionary/ubiquitous')}") + EXPECTED = "existing or being everywhere at the same time : constantly encountered : widespread" + j.check("answer_sense1", answer_equals(fa, EXPECTED), f"final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, EXPECTED, "first numbered definition of ubiquitous") + j.check("screenshot_shows_sense1", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_10.py b/sites/merriam_webster/verify/verify_10.py new file mode 100644 index 00000000..28c4db19 --- /dev/null +++ b/sites/merriam_webster/verify/verify_10.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--10. + +WOTD ephemeral; Did You Know? etymology text. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /word-of-the-day/ephemeral | answer contains 'greek ephēmeros' (deterministic) | screenshot shows Did You Know +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--10', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_wotd_ephemeral", navigated_to(t, "/word-of-the-day/ephemeral"), + f"navigated={navigated_to(t, '/word-of-the-day/ephemeral')}") + j.check("answer_did_you_know", contains_any(fa, ["greek eph\u0113meros", "ephemeros lasting a day"]), + f"final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "Greek eph\u0113meros", "Did You Know section of the ephemeral WOTD entry") + j.check("screenshot_shows_dyk", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_11.py b/sites/merriam_webster/verify/verify_11.py new file mode 100644 index 00000000..04fbb330 --- /dev/null +++ b/sites/merriam_webster/verify/verify_11.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--11. + +Name That Word quiz: answer all + report final score. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /quiz/name-that-word + reached /submit | reported score X/10 (deterministic regex) | result-page screenshot shows matching score +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--11', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_quiz", navigated_to(t, "/quiz/name-that-word"), + f"navigated={navigated_to(t, '/quiz/name-that-word')}") + j.check("reached_result_page", navigated_to(t, "/quiz/name-that-word/submit"), + f"navigated_submit={navigated_to(t, '/quiz/name-that-word/submit')}") + score = extract_score(fa) + j.check("reported_score_X_over_10", score is not None, f"final={fa!r}") + s = shot_after_url(t, "/quiz/name-that-word/submit") or last_shot(t) + if s and score is not None: + ok, ev = llm_screenshot_shows(s, "Your Score " + score + " / 10", + "the agent's final quiz score on the result page") + j.check("screenshot_shows_reported_score", ok, ev, llm=True) + elif s: + ok, ev = llm_screenshot_shows(s, "Your Score", "quiz result page score") + j.check("screenshot_shows_score_page", ok, ev, llm=True) + else: + j.check("screenshot_shows_score_page", False, "no screenshots in run") + + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_12.py b/sites/merriam_webster/verify/verify_12.py new file mode 100644 index 00000000..84820bc0 --- /dev/null +++ b/sites/merriam_webster/verify/verify_12.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--12. + +Synonym Showdown quiz: answer all + report final score. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /quiz/synonym-showdown + reached /submit | reported score X/10 (deterministic) | result screenshot shows matching score +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--12', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_quiz", navigated_to(t, "/quiz/synonym-showdown"), + f"navigated={navigated_to(t, '/quiz/synonym-showdown')}") + j.check("reached_result_page", navigated_to(t, "/quiz/synonym-showdown/submit"), + f"navigated_submit={navigated_to(t, '/quiz/synonym-showdown/submit')}") + score = extract_score(fa) + j.check("reported_score_X_over_10", score is not None, f"final={fa!r}") + s = shot_after_url(t, "/quiz/synonym-showdown/submit") or last_shot(t) + if s and score is not None: + ok, ev = llm_screenshot_shows(s, "Your Score " + score + " / 10", + "the agent's final quiz score on the result page") + j.check("screenshot_shows_reported_score", ok, ev, llm=True) + elif s: + ok, ev = llm_screenshot_shows(s, "Your Score", "quiz result page score") + j.check("screenshot_shows_score_page", ok, ev, llm=True) + else: + j.check("screenshot_shows_score_page", False, "no screenshots in run") + + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_13.py b/sites/merriam_webster/verify/verify_13.py new file mode 100644 index 00000000..ac94ad55 --- /dev/null +++ b/sites/merriam_webster/verify/verify_13.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--13. + +Games & Quizzes page: count quizzes + difficulty of each. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /games-quizzes | answer contains '3', 3 titles, 'easy', 'medium' (deterministic) | screenshot shows index +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--13', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_games", navigated_to(t, "/games-quizzes"), + f"navigated={navigated_to(t, '/games-quizzes')}") + NEED = ["3", "name that word", "synonym showdown", "opposites attract", "easy", "medium"] + j.check("answer_count_and_levels", contains_all(fa, NEED), f"final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "Name That Word", "list of quizzes and their difficulty levels") + j.check("screenshot_shows_index", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_14.py b/sites/merriam_webster/verify/verify_14.py new file mode 100644 index 00000000..1a3674f7 --- /dev/null +++ b/sites/merriam_webster/verify/verify_14.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--14. + +Login alice + save serendipity; confirm on My Words page. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /login,/dictionary/serendipity,/account | DB after: serendipity in alice saved & not in initial seed | screenshot shows account page +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--14', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_login", navigated_to(t, "/login"), f"navigated={navigated_to(t, '/login')}") + j.check("nav_serendipity_entry", navigated_to(t, "/dictionary/serendipity"), + f"navigated={navigated_to(t, '/dictionary/serendipity')}") + j.check("nav_account", navigated_to(t, "/account"), f"navigated={navigated_to(t, '/account')}") + after = resolve_db(a.after_db, a.container, "instance") + init = resolve_db(a.initial_db, a.container, "instance_seed") + aw = saved_words_for(after); iw = saved_words_for(init) + j.check("db_serendipity_added", aw is not None and "serendipity" in (aw or []), + f"after_saved={aw} initial_saved={iw}") + j.check("db_serendipity_was_absent_initial", iw is not None and "serendipity" not in (iw or []), + f"initial_saved={iw}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "serendipity", "the saved-words list on alice's account page") + j.check("screenshot_shows_saved", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_15.py b/sites/merriam_webster/verify/verify_15.py new file mode 100644 index 00000000..5334ba4a --- /dev/null +++ b/sites/merriam_webster/verify/verify_15.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--15. + +Login alice + save resilient & gratitude; confirm both on My Words. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /login,/account | DB after: resilient and gratitude in alice saved words | screenshot shows account page +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--15', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_login", navigated_to(t, "/login"), f"navigated={navigated_to(t, '/login')}") + j.check("nav_account", navigated_to(t, "/account"), f"navigated={navigated_to(t, '/account')}") + after = resolve_db(a.after_db, a.container, "instance") + aw = saved_words_for(after) + j.check("db_resilient_saved", aw is not None and "resilient" in (aw or []), f"after_saved={aw}") + j.check("db_gratitude_saved", aw is not None and "gratitude" in (aw or []), f"after_saved={aw}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "resilient", "saved-words list including resilient and gratitude") + j.check("screenshot_shows_saved", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_16.py b/sites/merriam_webster/verify/verify_16.py new file mode 100644 index 00000000..02cce200 --- /dev/null +++ b/sites/merriam_webster/verify/verify_16.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--16. + +Register 'Jordan Lee' + verify logged in. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /register | DB after: a user named 'Jordan Lee' exists | screenshot shows logged-in nav +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--16', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_register", navigated_to(t, "/register"), f"navigated={navigated_to(t, '/register')}") + after = resolve_db(a.after_db, a.container, "instance") + exists = user_exists(after, name="Jordan Lee") + j.check("db_jordan_lee_registered", exists is True, f"user_exists_Jordan_Lee={exists}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "My Words", "navigation showing the user is logged in (My Words / Log Out)") + j.check("screenshot_shows_logged_in", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_17.py b/sites/merriam_webster/verify/verify_17.py new file mode 100644 index 00000000..922dbcb9 --- /dev/null +++ b/sites/merriam_webster/verify/verify_17.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--17. + +Compare empathy/nostalgia/optimism: which has most recent first-known-use. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav all 3 /dictionary/{empathy,nostalgia,optimism} | answer contains 'empathy' + years 1909,1756,1759 (deterministic) | LLM-anchored 'most recent' judgement +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--17', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + for w in ["empathy", "nostalgia", "optimism"]: + j.check(f"nav_{w}", navigated_to(t, "/dictionary/" + w), + f"navigated={navigated_to(t, '/dictionary/' + w)}") + j.check("answer_has_3_years", contains_all(fa, ["1909", "1756", "1759"]), f"final={fa!r}") + ok, ev = llm_text_match(fa, + "empathy is the most recent. empathy: 1909; nostalgia: 1756; optimism: 1759.", + "Among empathy, nostalgia, optimism, which has the most recent first known use (report all 3 years)?") + j.check("answer_most_recent_empathy", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_18.py b/sites/merriam_webster/verify/verify_18.py new file mode 100644 index 00000000..c1f5c845 --- /dev/null +++ b/sites/merriam_webster/verify/verify_18.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--18. + +Login alice + remove 'curiosity'; confirm list == harmony, eloquent. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /login,/account | DB after: alice saved words exactly [eloquent, harmony] (no curiosity) | screenshot shows account page +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--18', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_login", navigated_to(t, "/login"), f"navigated={navigated_to(t, '/login')}") + j.check("nav_account", navigated_to(t, "/account"), f"navigated={navigated_to(t, '/account')}") + after = resolve_db(a.after_db, a.container, "instance") + aw = saved_words_for(after) + j.check("db_curiosity_removed", aw is not None and "curiosity" not in (aw or []), f"after_saved={aw}") + j.check("db_list_exactly_2", aw is not None and sorted(aw or []) == ["eloquent", "harmony"], f"after_saved={aw}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "harmony", "saved-words list showing only harmony and eloquent") + j.check("screenshot_shows_list", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_19.py b/sites/merriam_webster/verify/verify_19.py new file mode 100644 index 00000000..18f684ad --- /dev/null +++ b/sites/merriam_webster/verify/verify_19.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--19. + +Compare gregarious vs benevolent: which entered English earlier + POS of each. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /dictionary/gregarious AND /dictionary/benevolent | answer contains 'benevolent','15th century','1668','adjective' (deterministic) | LLM-anchored 'earlier' judgement +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--19', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_gregarious", navigated_to(t, "/dictionary/gregarious"), + f"navigated={navigated_to(t, '/dictionary/gregarious')}") + j.check("nav_benevolent", navigated_to(t, "/dictionary/benevolent"), + f"navigated={navigated_to(t, '/dictionary/benevolent')}") + j.check("answer_has_facts", contains_all(fa, ["benevolent", "15th century", "1668", "adjective"]), f"final={fa!r}") + ok, ev = llm_text_match(fa, + "benevolent entered English earlier. benevolent: 15th century (adjective); gregarious: 1668 (adjective). Both are adjectives.", + "Comparing gregarious and benevolent: which entered English earlier, and what part of speech is each?") + j.check("answer_benevolent_earlier", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_2.py b/sites/merriam_webster/verify/verify_2.py new file mode 100644 index 00000000..7561a3a9 --- /dev/null +++ b/sites/merriam_webster/verify/verify_2.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--2. + +Look up nostalgia; which Greek word its etymology traces to. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /dictionary/nostalgia | answer contains 'Greek nóstos' + 'return, homecoming' (deterministic) | screenshot shows etymology +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--2', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_nostalgia", navigated_to(t, "/dictionary/nostalgia"), + f"navigated={navigated_to(t, '/dictionary/nostalgia')}") + # answer must name the Greek source word + its meaning; phrasing varies (agent may + # drop the literal word "Greek"), so check the key tokens separately, not contiguously. + j.check("answer_has_greek_nostos", contains_all(fa, ["nóstos", "return", "homecoming"]), + f"final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "Greek nóstos", "etymology of nostalgia") + j.check("screenshot_shows_etymology", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_3.py b/sites/merriam_webster/verify/verify_3.py new file mode 100644 index 00000000..610ad273 --- /dev/null +++ b/sites/merriam_webster/verify/verify_3.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--3. + +empathy first-known-use year. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /dictionary/empathy | answer year==1909 (deterministic regex) | screenshot shows First Known Use 1909 +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--3', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_empathy", navigated_to(t, "/dictionary/empathy"), + f"navigated={navigated_to(t, '/dictionary/empathy')}") + yrs = extract_years(fa) + j.check("answer_year_1909", "1909" in yrs, f"years_found={yrs} final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "First Known Use: 1909", + "in what year was empathy first known to be used") + j.check("screenshot_shows_1909", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_4.py b/sites/merriam_webster/verify/verify_4.py new file mode 100644 index 00000000..2e5f0b01 --- /dev/null +++ b/sites/merriam_webster/verify/verify_4.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--4. + +meticulous first-known-use year. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /dictionary/meticulous | answer year==1827 (deterministic) | screenshot shows 1827 +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--4', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_meticulous", navigated_to(t, "/dictionary/meticulous"), + f"navigated={navigated_to(t, '/dictionary/meticulous')}") + yrs = extract_years(fa) + j.check("answer_year_1827", "1827" in yrs, f"years_found={yrs} final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "First Known Use: 1827", + "first known use year of meticulous") + j.check("screenshot_shows_1827", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_5.py b/sites/merriam_webster/verify/verify_5.py new file mode 100644 index 00000000..db5b2c3f --- /dev/null +++ b/sites/merriam_webster/verify/verify_5.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--5. + +thesaurus brave; list every synonym. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /thesaurus/brave | answer contains all 8 synonyms (deterministic) | screenshot shows synonym list +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--5', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_thesaurus_brave", navigated_to(t, "/thesaurus/brave"), + f"navigated={navigated_to(t, '/thesaurus/brave')}") + SYN = ["courageous", "fearless", "valiant", "heroic", "gallant", "bold", "adventurous", "intrepid"] + j.check("answer_all_8_synonyms", contains_all(fa, SYN), f"final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, ", ".join(SYN), "synonyms of brave shown on the thesaurus page") + j.check("screenshot_shows_synonyms", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_6.py b/sites/merriam_webster/verify/verify_6.py new file mode 100644 index 00000000..b1ada12a --- /dev/null +++ b/sites/merriam_webster/verify/verify_6.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--6. + +thesaurus calm; list every antonym. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /thesaurus/calm | answer contains all 8 antonyms (deterministic) | screenshot shows antonym list +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--6', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_thesaurus_calm", navigated_to(t, "/thesaurus/calm"), + f"navigated={navigated_to(t, '/thesaurus/calm')}") + ANT = ["angry", "turbulent", "restless", "agitated", "stormy", "unsettled", "rough", "tempestuous"] + j.check("answer_all_8_antonyms", contains_all(fa, ANT), f"final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, ", ".join(ANT), "antonyms of calm shown on the thesaurus page") + j.check("screenshot_shows_antonyms", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_7.py b/sites/merriam_webster/verify/verify_7.py new file mode 100644 index 00000000..e821cdb9 --- /dev/null +++ b/sites/merriam_webster/verify/verify_7.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--7. + +thesaurus difficult; synonyms starting with C. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /thesaurus/difficult | answer contains 'challenging' AND 'complicated' (deterministic) | screenshot shows thesaurus +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--7', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_thesaurus_difficult", navigated_to(t, "/thesaurus/difficult"), + f"navigated={navigated_to(t, '/thesaurus/difficult')}") + j.check("answer_C_synonyms", contains_all(fa, ["challenging", "complicated"]), f"final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "challenging", "synonyms of difficult, esp. ones starting with C") + j.check("screenshot_shows_thesaurus", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_8.py b/sites/merriam_webster/verify/verify_8.py new file mode 100644 index 00000000..26dffc25 --- /dev/null +++ b/sites/merriam_webster/verify/verify_8.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--8. + +thesaurus happy; count synonyms and antonyms. + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /thesaurus/happy | answer contains '8 synonyms' AND '8 antonyms' (deterministic) | screenshot shows lists +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--8', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_thesaurus_happy", navigated_to(t, "/thesaurus/happy"), + f"navigated={navigated_to(t, '/thesaurus/happy')}") + # counts: the page lists 8 synonyms and 8 antonyms. Agents phrase this many ways + # ("8 synonyms" / "Synonyms: 8" / "8 (…)"). Require both keywords present AND the + # count 8 appears at least twice (one per list), rather than a fixed phrase. + fa_low = fa.casefold() + j.check("answer_counts", "synonym" in fa_low and "antonym" in fa_low and fa_low.count("8") >= 2, + f"final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "synonyms", "how many synonyms and antonyms of happy are listed") + j.check("screenshot_shows_lists", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_9.py b/sites/merriam_webster/verify/verify_9.py new file mode 100644 index 00000000..1848cdfa --- /dev/null +++ b/sites/merriam_webster/verify/verify_9.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for MW task Merriam-Webster--9. + +Word of the Day featured word + part of speech (deterministic, pinned). + +Checks (deterministic first; LLM utilities anchored on ground truth): +nav /word-of-the-day | answer contains 'ambivalent' AND 'adjective' (deterministic) | screenshot shows ambivalent adjective +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, final_answer, last_shot, shot_after_url, + contains_all, contains_any, answer_equals, extract_years, + extract_score, resolve_db, saved_words_for, user_exists, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge('Merriam-Webster--9', a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + j.check("nav_wotd", navigated_to(t, "/word-of-the-day"), + f"navigated={navigated_to(t, '/word-of-the-day')}") + j.check("answer_word_pos", contains_all(fa, ["ambivalent", "adjective"]), f"final={fa!r}") + s = last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, "ambivalent", "featured word of the day and its part of speech") + j.check("screenshot_shows_wotd", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/merriam_webster/verify/verify_lib.py b/sites/merriam_webster/verify/verify_lib.py new file mode 100644 index 00000000..b610c711 --- /dev/null +++ b/sites/merriam_webster/verify/verify_lib.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +"""verify_lib.py — shared deterministic + LLM utilities for Merriam-Webster task verification. + +Philosophy: DETERMINISTIC FIRST. + 1. Trajectory navigation check (anti knowledge-shortcut): the agent MUST have + opened the relevant on-site page; a correct answer with no matching navigation + is a memory-recall shortcut = FAIL. + 2. Answer check: exact / regex / token-containment against frozen ground truth. + 3. DB after-state check (stateful tasks): query the SQLite instance DB directly — + the strongest deterministic signal (saved-word list, registered user row). + 4. LLM utilities (text match, screenshot-contains) are used ONLY where exact + matching is brittle, and are ALWAYS anchored on ground truth: the model + verifies *presence* of given content, it never supplies knowledge. One call each. + +Input signature (per task): + --run_dir DIR agent trajectory dir: trajectory.json + screenshots/step_NNN.png + --initial_db PATH initial-state SQLite DB (default: fetched instance_seed from container) + --after_db PATH after-state SQLite DB (default: fetched live instance DB from container) + --container NAME docker container to fetch DBs from (default: $WH_CONTAINER or wh-review) + --no_llm skip LLM-based checks (run deterministic-only) +Output: JSON {task_id, pass, reason, evidence[]} to stdout; exit 0 on PASS, 1 on FAIL. +""" +import base64, json, os, re, sqlite3, subprocess, sys, tempfile, urllib.request +from pathlib import Path +from dataclasses import dataclass + +SITE = "merriam_webster" + +# ---------------------------------------------------------------- trajectory +def load_run(run_dir): + d = Path(run_dir) + traj = json.loads((d / "trajectory.json").read_text()) + traj["_run_dir"] = d + traj["_shots"] = {p.name: p for p in sorted((d / "screenshots").glob("step_*.png"))} + return traj + +def step_urls(traj): + return [s.get("url", "") for s in traj.get("steps", [])] + +def navigated_to(traj, substr, times=1): + """Deterministic: at least `times` trajectory steps have a URL containing substr.""" + return sum(1 for u in step_urls(traj) if substr in u) >= times + +def navigated_any(traj, substrs): + return any(navigated_to(traj, s) for s in substrs) + +def final_answer(traj): + return (traj.get("final_answer") or "").strip() + +def _shot(traj, name): + if not name: + return None + p = traj["_shots"].get(Path(name).name) + return p if (p and p.exists()) else None + +def shot_after_url(traj, substr): + """screenshot_after path of the first step whose URL contains substr.""" + for s in traj.get("steps", []): + if substr in s.get("url", ""): + p = _shot(traj, s.get("screenshot_after")) + if p: + return p + return None + +def last_shot(traj): + for s in reversed(traj.get("steps", [])): + p = _shot(traj, s.get("screenshot_after")) or _shot(traj, s.get("screenshot_before")) + if p: + return p + shots = sorted(traj["_shots"].values()) + return shots[-1] if shots else None + +# ---------------------------------------------------------------- deterministic answer match +def norm(s): + return re.sub(r"\s+", " ", (s or "").strip()).casefold() + +def answer_equals(final, expected): + return norm(final) == norm(expected) + +def contains_all(final, tokens): + f = norm(final) + return all(norm(t) in f for t in tokens) + +def contains_any(final, tokens): + f = norm(final) + return any(norm(t) in f for t in tokens) + +def extract_years(text): + return re.findall(r"\b(1[5-9]\d{2}|20\d{2})\b", text or "") + +def extract_score(text): + m = re.search(r"(\d+)\s*/\s*10", text or "") + return m.group(1) if m else None + +# ---------------------------------------------------------------- DB state +def fetch_db(container, kind): + """kind: 'instance' (after-state) or 'instance_seed' (initial-state). docker cp -> temp file.""" + src = f"{container}:/opt/WebSyn/{SITE}/{kind}/{SITE}.db" + fd, path = tempfile.mkstemp(suffix=".db") + os.close(fd) + r = subprocess.run(["docker", "cp", src, path], capture_output=True, text=True) + if r.returncode != 0: + try: + os.unlink(path) + except OSError: + pass + raise RuntimeError(f"docker cp {src} failed: {r.stderr.strip()}") + return path + +def resolve_db(arg, container, kind): + if arg: + return arg + try: + return fetch_db(container, kind) + except Exception: + return None # caller treats None as "unavailable" and FAILs that check + +def db_query(db_path, sql, params=()): + con = sqlite3.connect(db_path) + try: + return con.execute(sql, params).fetchall() + finally: + con.close() + +def saved_words_for(db_path, email="alice.j@test.com"): + if not db_path: + return None + rows = db_query(db_path, + "SELECT w.headword FROM saved_words sw JOIN users u ON u.id=sw.user_id " + "JOIN words w ON w.id=sw.word_id WHERE u.email=? ORDER BY w.headword", (email,)) + return [r[0] for r in rows] + +def user_exists(db_path, name=None, email=None): + if not db_path: + return None + rows = db_query(db_path, "SELECT name, email FROM users") + return any((name is None or r[0] == name) and (email is None or r[1] == email) + for r in rows) + +# ---------------------------------------------------------------- shared LLM utilities (anchored) +# Unified LLM config, same env vars as agent.py / eval_judge.py: +# OPENAI_API_KEY, OPENAI_BASE_URL, JUDGE_MODEL +import simpleArgParser as sap + +# When --no_llm is set (via Judge), the llm_* helpers short-circuit so verifiers +# that call them directly (before j.check(llm=True)) still make ZERO LLM calls. +_NO_LLM = False + + +def _llm_config(): + """Resolve (api_key, api_base, model) from env once per process.""" + key = os.environ.get("OPENAI_API_KEY", "") + base = os.environ.get("OPENAI_BASE_URL", "") + model = os.environ.get("JUDGE_MODEL", "") + return key, base, model + + +def _chat(messages, max_tokens=1024): + """One LLM call against the configured OpenAI-compatible endpoint. Returns text or None.""" + if _NO_LLM: + return None + key, base, model = _llm_config() + if not (key and base and model): + return None # no LLM configured -> callers treat as non-PASS + payload = {"model": model, "messages": messages, + "max_tokens": max_tokens, "temperature": 1.0} + req = urllib.request.Request(base, + data=json.dumps(payload).encode(), + headers={"Content-Type": "application/json", + "Authorization": f"Bearer {key}"}) + try: + data = json.loads(urllib.request.urlopen(req, timeout=180).read()) + except Exception: + return None # caller treats None as a non-PASS; never raises + try: + return data["choices"][0]["message"]["content"] + except Exception: + return None + +def _verdict(out): + """Normalize an LLM reply to (pass_bool, text). None/empty -> (False, '').""" + if not out: + return False, "" + s = out.strip() + return s.upper().startswith("PASS"), s + +def llm_text_match(agent_answer, ground_truth, question): + """One LLM call: does agent_answer correctly answer question AND stay consistent + with the frozen ground truth? The model is given the ground truth as an anchor + and is told NOT to use its own knowledge.""" + if _NO_LLM: + return False, "[skipped: --no_llm]" + out = _chat([{"role": "user", "content": + f"You are a STRICT binary grader.\nQuestion: {question}\n" + f"Ground-truth answer (ANCHOR — judge against THIS, never use your own knowledge): {ground_truth}\n" + f"Agent's answer: {agent_answer}\n" + f"Decide PASS or FAIL ignoring case/punctuation/word order/surrounding prose. " + f"PASS only if the agent's answer is consistent with the ground truth AND actually answers the question. " + f"Line 1: PASS or FAIL. Line 2: one-sentence reason."}]) + return _verdict(out) + +def llm_screenshot_shows(shot_path, must_show, question=""): + """One vision LLM call: does this screenshot visibly render text answering/containing + `must_show`? The model judges pixels only, anchored on the expected content.""" + if _NO_LLM: + return False, "[skipped: --no_llm]" + b64 = base64.b64encode(Path(shot_path).read_bytes()).decode() + out = _chat([{"role": "user", "content": [ + {"type": "text", "text": + f"You are a STRICT binary grader. Only what is VISIBLY rendered in this screenshot counts.\n" + f"Question the page should answer: {question}\n" + f"Expected content to verify PRESENCE of: {must_show}\n" + f"PASS only if the expected content (or a semantically equivalent on-screen answer) is visibly shown. " + f"Do NOT use prior knowledge — judge only the rendered pixels.\n" + f"Line 1: PASS or FAIL. Line 2: quote the visible evidence."}, + {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}]}]) + return _verdict(out) + +# ---------------------------------------------------------------- judge harness + CLI +class Judge: + def __init__(self, task_id, no_llm=False): + global _NO_LLM + _NO_LLM = bool(no_llm) # gate the llm_* helpers at the source + self.task_id = task_id + self.no_llm = no_llm + self.ok = True + self.reason = "" + self.evidence = [] + + def check(self, name, cond, evidence="", llm=False): + if llm and self.no_llm: + self.evidence.append(f"[SKIP] {name} (--no-llm)") + return True + if cond: + self.evidence.append(f"[PASS] {name}: {evidence}") + else: + self.ok = False + if not self.reason: + self.reason = name # record the FIRST failing check + self.evidence.append(f"[FAIL] {name}: {evidence}") + return bool(cond) + + def emit(self): + print(json.dumps({"task_id": self.task_id, "pass": self.ok, + "reason": self.reason, "evidence": self.evidence}, indent=2)) + sys.exit(0 if self.ok else 1) + +def parse_args(): + @dataclass + class VerifyArgs: + run_dir: str = "" + initial_db: str = "" + after_db: str = "" + container: str = os.environ.get("WH_CONTAINER", "wh-review") + no_llm: bool = False + + def post_process(self): + if not self.run_dir: + raise SystemExit("--run_dir is required") + return sap.parse_args(VerifyArgs) From 34bdb7b3e420e6f78ef155f4d27a13f94d5992d5 Mon Sep 17 00:00:00 2001 From: raibows Date: Wed, 24 Jun 2026 02:15:38 -0700 Subject: [PATCH 3/3] chore(merriam_webster): re-pin assets to ChilleD/WebHarbor main (156-word seed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HF PR ChilleD/WebHarbor#29 merged; .assets-revision now points at the canonical dataset's main commit carrying the regenerated 156-word seed. Verified: fetch from ChilleD main -> build -> byte-identical reset (md5 79ae0eab…) holds; runtime writes wiped on reset. --- .assets-revision | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.assets-revision b/.assets-revision index 7f7d8db6..5c22104e 100644 --- a/.assets-revision +++ b/.assets-revision @@ -4,6 +4,5 @@ # fetch_assets.sh uses the `hf download` CLI. The pin below # is a git revision (branch name like `main`, a tag, or a specific commit # sha). Override at runtime with the ASSETS_REVISION env var. - repo: ChilleD/WebHarbor -revision: main \ No newline at end of file +revision: 54882a6a66a17a3e43455057e7c9e0d103cd8b81