Architecture: Topic Relationships, Word Forms & Disambiguation
Depends on: #48 (MVP: Devil's Dictionary Phoenix Rebuild)
The Problem
How do we handle relationships between similar words and concepts in a way that's simple for MVP but won't create technical debt?
Key Scenarios:
- Word Forms: aristocracy → aristocratic → aristocrat (same root, different grammar)
- Synonyms: truth, verity, veracity (different words, same meaning)
- Related Concepts: truth → honesty, lie, reality (semantic connections)
- Disambiguation: "bank" (financial) vs "bank" (river edge)
- Empty Topic Fallback: if "veracity" has no definitions, what do we show?
Analysis: Relationship Types
Type 1: Morphological Variants (Word Forms)
Same root word, different grammatical forms:
- aristocracy (noun) → aristocratic (adj) → aristocrat (noun/person)
- truth → truthful → truthfully
Characteristic: May each have their own definitions (Bierce defined many word forms separately), but should cross-reference.
Type 2: Synonyms
Different words, essentially same meaning:
- truth, verity, veracity
- lie, falsehood, untruth
Characteristic: One may be "canonical" (more common), others might redirect or show "see also."
Type 3: Semantic Relations
Different concepts that are connected:
- Antonyms: truth ↔ lie
- Broader/Narrower: democracy, aristocracy, monarchy → (all are) government types
- Associated: truth → honesty → integrity
Characteristic: Each is a distinct concept; relationships are informational, not redirects.
Type 4: Disambiguation
Same word with completely different meanings:
- bank (financial institution) vs bank (river edge)
- crane (bird) vs crane (machine)
Characteristic: Must be separate topics with a disambiguation landing page.
Type 5: Pure Redirects (Aliases)
Alternative names for the same thing:
- USA → United States
- NYC → New York City
Characteristic: Hard redirect, user never sees the alias page.
Recommended Schema
Modified Topics Table
schema "topics" do
field :title, :string
field :slug, :string
field :type, Ecto.Enum, values: [:concept, :thing, :place, :event, :action, :person, :organization, :other]
# NEW: Display handling
field :display_type, Ecto.Enum,
values: [:standard, :redirect, :disambiguation],
default: :standard
# NEW: For pure redirects only (hard redirect, URL changes)
belongs_to :redirect_to, Topic
# Existing fields...
field :pronunciation, :string
field :part_of_speech, :string
field :definition_count, :integer, default: 0
timestamps()
end
Topic Relationships Table
schema "topic_relationships" do
belongs_to :topic, Topic
belongs_to :related_topic, Topic
field :relationship_type, Ecto.Enum, values: [
# MVP (Phase 1)
:word_form, # aristocracy ↔ aristocratic (morphological variants)
:synonym, # truth ↔ verity (same meaning, different word)
:related, # truth → honesty (semantically connected)
:see_also, # loose connection, editorial choice
# Phase 2+
:antonym, # truth ↔ lie (opposites)
:broader, # aristocracy → government (hypernym)
:narrower, # government → aristocracy (hyponym)
:disambiguates # bank → bank_finance, bank_geography
]
# Is this relationship symmetric? (synonym = yes, broader = no)
field :bidirectional, :boolean, default: true
# For ordering related topics by relevance
field :weight, :float, default: 1.0
timestamps()
end
UI Patterns by Relationship Type
1. Word Form (aristocracy ↔ aristocratic)
Topic with definitions:
┌────────────────────────────────────────────────────┐
│ ARISTOCRATIC, adj. │
│ [definitions...] │
│ │
│ ─────────────────────────────────────────────────│
│ Word Family: aristocracy • aristocrat │
└────────────────────────────────────────────────────┘
Topic WITHOUT definitions (empty word form):
┌────────────────────────────────────────────────────┐
│ ARISTOCRATICAL, adj. │
│ /əˌrɪstəˈkrætɪkəl/ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ An archaic form of ARISTOCRATIC. │ │
│ │ │ │
│ │ → See ARISTOCRATIC for definitions │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ Word Family: aristocracy • aristocratic │
└────────────────────────────────────────────────────┘
2. Synonym (truth ↔ verity)
Topic with definitions:
┌────────────────────────────────────────────────────┐
│ TRUTH, n. │
│ [definitions...] │
│ │
│ ─────────────────────────────────────────────────│
│ Synonyms: verity • veracity • sooth │
└────────────────────────────────────────────────────┘
Topic WITHOUT definitions (empty synonym):
┌────────────────────────────────────────────────────┐
│ VERITY, n. │
│ /ˈverɪti/ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ A formal synonym of TRUTH. │ │
│ │ │ │
│ │ → See TRUTH for definitions │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ Synonyms: truth • veracity │
└────────────────────────────────────────────────────┘
3. Related Topics
Sidebar section (all topics):
┌────────────────────────────────────────────────────┐
│ Related Topics │
│ ───────────── │
│ • honesty │
│ • reality │
│ • fact │
│ • sincerity │
└────────────────────────────────────────────────────┘
4. Disambiguation Page
When topic has display_type = :disambiguation:
┌────────────────────────────────────────────────────┐
│ BANK │
│ │
│ Bank may refer to: │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ BANK (finance) │ │
│ │ A financial institution that accepts │ │
│ │ deposits and makes loans. │ │
│ │ → 5 definitions │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ BANK (geography) │ │
│ │ The land alongside a river or body of │ │
│ │ water. │ │
│ │ → 2 definitions │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
5. Pure Redirect
User visits /topics/usa → automatically redirects to /topics/united-states
No page rendered for redirect topics. HTTP 301 redirect.
Empty Topic Fallback Logic
def render_topic(topic) do
cond do
# Hard redirect
topic.display_type == :redirect && topic.redirect_to ->
redirect_to(topic.redirect_to)
# Disambiguation page
topic.display_type == :disambiguation ->
render_disambiguation(topic)
# Has own definitions
topic.definition_count > 0 ->
render_standard_topic(topic)
# No definitions, but has word_form or synonym with definitions
related = find_related_with_definitions(topic, [:word_form, :synonym]) ->
render_see_also_topic(topic, related)
# Truly empty
true ->
render_empty_topic(topic)
end
end
defp find_related_with_definitions(topic, relationship_types) do
topic
|> get_relationships(relationship_types)
|> Enum.filter(& &1.related_topic.definition_count > 0)
|> Enum.sort_by(& &1.weight, :desc)
|> List.first()
end
Populating Relationships
Phase 1: Manual Curation (MVP)
- Admin creates relationships when adding topics
- Parse cross-references from Bierce ("See X" patterns)
Phase 2: API Integration
We already planned ConceptNet and Datamuse integrations:
| Source |
Provides |
| ConceptNet |
/r/RelatedTo, /r/Synonym, /r/Antonym, /r/FormOf |
| Datamuse |
rel_syn (synonyms), rel_trg (triggers/associations) |
Auto-population workflow:
- When topic is created/enriched, query ConceptNet
- Find existing topics that match related concepts
- Create relationship records with appropriate types
- Human review/approval for quality control
Phase 3: User Contributions
- Users can suggest relationships
- Moderation queue before publishing
Validation Rules
Prevent Circular Redirects
def changeset(topic, attrs) do
topic
|> cast(attrs, [:redirect_to_id])
|> validate_no_circular_redirect()
end
defp validate_no_circular_redirect(changeset) do
# redirect_to cannot point to a topic that itself redirects
# prevents: A → B → C → A
end
Disambiguation Requirements
def validate_disambiguation(topic) do
if topic.display_type == :disambiguation do
disambiguates_count = count_relationships(topic, :disambiguates)
if disambiguates_count < 2 do
{:error, "Disambiguation topics must have at least 2 disambiguates relations"}
end
end
end
Bidirectional Consistency
def create_relationship(topic_a, topic_b, type, opts \\ []) do
bidirectional = Keyword.get(opts, :bidirectional, default_bidirectional(type))
Multi.new()
|> Multi.insert(:forward, %TopicRelationship{
topic_id: topic_a.id,
related_topic_id: topic_b.id,
relationship_type: type,
bidirectional: bidirectional
})
|> Multi.insert(:reverse, fn _ ->
if bidirectional do
%TopicRelationship{
topic_id: topic_b.id,
related_topic_id: topic_a.id,
relationship_type: inverse_type(type),
bidirectional: bidirectional
}
end
end)
|> Repo.transaction()
end
defp inverse_type(:broader), do: :narrower
defp inverse_type(:narrower), do: :broader
defp inverse_type(type), do: type # symmetric types
Relationship Limits
@max_synonyms 10
@max_word_forms 5
@max_related 20
def validate_relationship_limits(topic, new_type) do
current_count = count_relationships(topic, new_type)
max = max_for_type(new_type)
if current_count >= max do
{:error, "Maximum #{max} #{new_type} relationships allowed"}
end
end
MVP Scope
✅ Phase 1 (MVP)
Schema:
Relationship Types:
UI:
Validation:
❌ Phase 2+
Schema:
Relationship Types:
Features:
Why This Won't Haunt Us
- Single relationships table - Not multiple join tables for different relationship types
- Enum for types - Easy to extend without schema changes
- Bidirectional flag - Handles symmetric (synonym) and asymmetric (broader) relations
- Weight field - Future-proofs ordering and relevance
- Clear UI patterns - Each relationship type has defined behavior
- Explicit empty handling - No magic, user sees "See X" messages
- Validation rules - Prevents circular redirects and orphaned disambiguation
- Migration path - MVP types are subset of full types
Open Questions
-
Should word forms share definitions by default?
- Recommendation: No, each topic owns its definitions. UI shows cross-references.
-
How do we handle Bierce's cross-references during seed?
- Recommendation: Parse "See X" patterns, create
see_also relationships
-
Should redirects be soft (show page) or hard (HTTP 301)?
- Recommendation: Hard redirects for aliases, soft "see X" for synonyms/word forms
-
How do we handle conflicting auto-generated relationships?
- Recommendation: Queue for human review, don't auto-publish
References
"The relationships between words are as important as the words themselves."
Architecture: Topic Relationships, Word Forms & Disambiguation
Depends on: #48 (MVP: Devil's Dictionary Phoenix Rebuild)
The Problem
How do we handle relationships between similar words and concepts in a way that's simple for MVP but won't create technical debt?
Key Scenarios:
Analysis: Relationship Types
Type 1: Morphological Variants (Word Forms)
Same root word, different grammatical forms:
Characteristic: May each have their own definitions (Bierce defined many word forms separately), but should cross-reference.
Type 2: Synonyms
Different words, essentially same meaning:
Characteristic: One may be "canonical" (more common), others might redirect or show "see also."
Type 3: Semantic Relations
Different concepts that are connected:
Characteristic: Each is a distinct concept; relationships are informational, not redirects.
Type 4: Disambiguation
Same word with completely different meanings:
Characteristic: Must be separate topics with a disambiguation landing page.
Type 5: Pure Redirects (Aliases)
Alternative names for the same thing:
Characteristic: Hard redirect, user never sees the alias page.
Recommended Schema
Modified Topics Table
Topic Relationships Table
UI Patterns by Relationship Type
1. Word Form (aristocracy ↔ aristocratic)
Topic with definitions:
Topic WITHOUT definitions (empty word form):
2. Synonym (truth ↔ verity)
Topic with definitions:
Topic WITHOUT definitions (empty synonym):
3. Related Topics
Sidebar section (all topics):
4. Disambiguation Page
When topic has display_type = :disambiguation:
5. Pure Redirect
User visits /topics/usa → automatically redirects to /topics/united-states
No page rendered for redirect topics. HTTP 301 redirect.
Empty Topic Fallback Logic
Populating Relationships
Phase 1: Manual Curation (MVP)
Phase 2: API Integration
We already planned ConceptNet and Datamuse integrations:
Auto-population workflow:
Phase 3: User Contributions
Validation Rules
Prevent Circular Redirects
Disambiguation Requirements
Bidirectional Consistency
Relationship Limits
MVP Scope
✅ Phase 1 (MVP)
Schema:
display_typeto topics (default :standard)redirect_to_idto topicstopic_relationshipstable with MVP typesRelationship Types:
word_form- morphological variantssynonym- same meaningrelated- semantic connectionsee_also- editorial connectionUI:
Validation:
❌ Phase 2+
Schema:
Relationship Types:
antonym- oppositesbroader/narrower- hypernym/hyponymdisambiguates- disambiguationFeatures:
Why This Won't Haunt Us
Open Questions
Should word forms share definitions by default?
How do we handle Bierce's cross-references during seed?
see_alsorelationshipsShould redirects be soft (show page) or hard (HTTP 301)?
How do we handle conflicting auto-generated relationships?
References
"The relationships between words are as important as the words themselves."