Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Clinics on Rails (Brighton Ruby 2026)

Avatar for Alex Watt Alex Watt
June 25, 2026
39

Clinics on Rails (Brighton Ruby 2026)

Avatar for Alex Watt

Alex Watt

June 25, 2026

Transcript

  1. Benefits of clinical data • Set volunteer expectations • Plan

    for pharmacy • Understand community health over time
  2. BackpackEMR ✅ Designed for mobile medical clinics
 ❌ PII stored

    outside of Ethiopia, could not host in country, limited reporting
  3. Google Sheet ✅ Free, ubiquitous, simple
 ❌ Relational data, limited

    data validation, more difficult integrations, permissions
  4. Built in a week (January 2025) Monday rails new Tuesday

    Finished data models Wednesday Deployed with Hatchbox Thursday User policies & launch Friday Addressed feedback
  5. Project Impact • Volunteers can help with medical data •

    Medical teams see their impact • Future teams are better prepared
  6. Embeddings for diagnoses diagnoses_with_embeddings = Diagnosis.all.map do |diagnosis| text =

    "#{diagnosis.name} (e.g., #{diagnosis.examples})" embedding = embedding(text) { diagnosis: diagnosis, embedding: embedding } end
  7. Query embedding QUERY_PREFIX = "Represent this sentence for searching relevant

    passages: " query_embedding = embedding(QUERY_PREFIX + query)
  8. Search with embeddings and fuzzy text search LIMIT = 3

    diagnoses_with_embeddings.filter_map do |d| semantic = cosine_similarity(query_embedding, d[:embedding]) fuzzy = fuzzy_score(query, d) semantic_relevant = semantic >= MIN_SEMANTIC_SCORE fuzzy_relevant = fuzzy >= MIN_FUZZY_SCORE next unless semantic_relevant || fuzzy_relevant [d[:diagnosis], [semantic, fuzzy].max] end.sort_by { |_, score| -score }.first(LIMIT).map(&:first)
  9. Search with embeddings and fuzzy text search LIMIT = 3

    diagnoses_with_embeddings.filter_map do |d| semantic = cosine_similarity(query_embedding, d[:embedding]) fuzzy = fuzzy_score(query, d) semantic_relevant = semantic >= MIN_SEMANTIC_SCORE fuzzy_relevant = fuzzy >= MIN_FUZZY_SCORE next unless semantic_relevant || fuzzy_relevant [d[:diagnosis], [semantic, fuzzy].max] end.sort_by { |_, score| -score }.first(LIMIT).map(&:first)
  10. Search with embeddings and fuzzy text search LIMIT = 3

    diagnoses_with_embeddings.filter_map do |d| semantic = cosine_similarity(query_embedding, d[:embedding]) fuzzy = fuzzy_score(query, d) semantic_relevant = semantic >= MIN_SEMANTIC_SCORE fuzzy_relevant = fuzzy >= MIN_FUZZY_SCORE next unless semantic_relevant || fuzzy_relevant [d[:diagnosis], [semantic, fuzzy].max] end.sort_by { |_, score| -score }.first(LIMIT).map(&:first)