diff --git a/app.py b/app.py
index dd28fcc..0a55bbb 100644
--- a/app.py
+++ b/app.py
@@ -97,6 +97,22 @@ if not kjv_cur:
if not sermon_cur:
print("WARN: Error connecting to Sermon Database")
+# Build grammatical variants lookup from word groups
+variant_groups = json.load(open("data/grammatical_variants.json", "r"))
+grammatical_variants_map = {}
+for group in variant_groups:
+ for word in group:
+ # Map each word to all other words in its group
+ grammatical_variants_map[word] = [w for w in group if w != word]
+
+# Build theological synonyms lookup from word groups
+theological_synonym_groups = json.load(open("data/theological_synonyms.json", "r"))
+theological_synonyms_map = {}
+for group in theological_synonym_groups:
+ for word in group:
+ # Map each word to other words in its group
+ theological_synonyms_map[word] = [w for w in group if w != word]
+
cached: dict = {
"verse_ids": [i[0] for i in kjv_cur.execute("SELECT id FROM fts_kjv;").fetchall()],
"book_names": [
@@ -105,7 +121,8 @@ cached: dict = {
"chapter_list": [],
"books_chapter_lengths": {},
"json_dictionary": json.load(open("data/1828_Webster_KJV.json", "r")),
- "grammatical_variants": json.load(open("data/grammatical_variants.json", "r")),
+ "grammatical_variants": grammatical_variants_map,
+ "theological_synonyms": theological_synonyms_map,
"html_response": open("html/response.html", "r").read(),
"html_navbar": open("html/navbar.html", "r").read(),
"css_navbar": open("styles/header.css", "r").read(),
@@ -777,11 +794,43 @@ def search():
# PRIORITY 1: Try grammatical variant expansion first
words = cleaned_keywords.split()
+ def sort_variants_by_similarity(original, variants):
+ """Sort variants by morphological similarity: forwards (longer/more specific) then backwards (shorter/simpler)"""
+ def get_common_prefix_length(w1, w2):
+ """Find how many characters match from the start"""
+ prefix_len = 0
+ for i in range(min(len(w1), len(w2))):
+ if w1[i].lower() == w2[i].lower():
+ prefix_len += 1
+ else:
+ break
+ return prefix_len
+
+ scored_variants = []
+ for variant in variants:
+ prefix_len = get_common_prefix_length(original, variant)
+
+ # Reject variants that don't share the stem (beginning doesn't match)
+ if prefix_len < min(2, len(original) // 2):
+ continue
+
+ # Score: prioritize common prefix length (stem preservation) heavily,
+ # then prefer longer variants (forward/more specific) over shorter (backward/simpler)
+ # Prefix is weighted 1000x to ensure stem-preservation is primary
+ score = (prefix_len * 1000) + len(variant)
+ scored_variants.append((score, variant))
+
+ # Sort by score descending: highest score = most similar with longest form first
+ return [v for _, v in sorted(scored_variants, key=lambda x: x[0], reverse=True)]
+
for word in words:
variants = cached["grammatical_variants"].get(word.lower(), [])
if variants:
- # Try each variant individually
- for variant in variants:
+ # Sort variants: forwards in likeness (longer/specific) then backwards (shorter/simple)
+ sorted_variants = sort_variants_by_similarity(word.lower(), variants)
+
+ # Try each variant in order of similarity
+ for variant in sorted_variants:
variant_query = cleaned_keywords.replace(word, variant, 1)
results = lookup_fts(variant_query)
if results:
@@ -832,17 +881,66 @@ def search():
response = jsonify({"corrected_from": cleaned_keywords, "corrected_to": corrected_keyword, "results": response_list})
return response
- # PRIORITY 3: If we still have no results, show spelling suggestions
+ # PRIORITY 3: Try theological synonym expansion
+ for word in words:
+ synonyms = cached["theological_synonyms"].get(word.lower(), None)
+ if synonyms:
+ for synonym in synonyms:
+ synonym_query = cleaned_keywords.replace(word, synonym, 1)
+ results = lookup_fts(synonym_query)
+ if results:
+ response_list = scripture_response(results)
+ arg_view = request.args.get("view", None)
+ explanation = f"In the KJV, '{word}' and '{synonym}' often refer to similar theological concepts. Consider also searching for related verses."
+ if arg_view == "plain":
+ response = f"Showing results for '{synonym_query}' (theological equivalent of '{cleaned_keywords}')\n{explanation}\n
" + "
".join(response_list)
+ elif arg_view == "rich":
+ theological_notice = f"
In the KJV, these terms often refer to similar theological concepts. Consider also searching for related verses.
" + for suggestion in theological_suggestions: + search_link = f"/search?kw={suggestion}&view=rich" + suggestions_html += f"