Quepid

repository·main·Indexed 43 days ago

https://github.com/o19s/quepid

A platform designed to turn search relevance improvement into a repeatable engineering process. Quepid provides tools for testing and iterating on search results, facilitating collaboration between teams through abstractions like Search Endpoints, Cases, Ratings, Scorers, and Books. It supports integration with external evaluation tools via a REST API and offers multiple deployment options including Docker Compose, AWS CloudFormation (EC2), and ECS Fargate.

Tokens
56.2K
Snippets
114
Records
294
Agent score
55%

What's inside Quepid

  1. Understand the Quepid frontend architecture and AngularJS removal plan

    main

    Quepid's frontend is currently split between two distinct stacks. The Core case UI (handling queries, ratings, and Solr JSONP) is an AngularJS 1.8 Single Page Application (SPA) located at /case/:id and /case/:id/try/:try_number. The rest of the application (teams, books, scorers, etc.) runs on Rails + Stimulus (using ERB and Turbo Streams).

    A migration plan is in progress to completely remove AngularJS. The strategy is to migrate isolated, lower-risk pieces first (like toolbar Stimulus twins and management modals) and tackle the high-complexity live query/search/score state last. The backend (Rails 8.1, MySQL, REST API) remains stable and is not part of the frontend stack migration.

  2. What is a Scorer in Quepid

    main

    A Scorer is a JavaScript formula that transforms a set of graded judgements into a single relevance score for a query (for example, calculating metrics like nDCG@10 or Precision@10).

    Quepid provides two types of scorers:

    • Communal Scorers: Built-in scorers available to all users (e.g., nDCG@10, AP@10). These are owned by the "System".
    • Custom Scorers: Scorers created by users. These can be private or shared with a team.

    You can access the Scorers management page via the Sidebar icon (list-task) or by navigating to /scorers.

  3. Overview of the Core Workbench

    main

    The Core Workbench is the primary interface for relevance-tuning workflows in Quepid. It is accessed via the URLs /case/:id or /case/:id/try/:try_number.

    In the Workbench, you can:

    • Add and manage queries.
    • Run queries against your search endpoint.
    • Rate documents to provide ground truth.
    • Monitor real-time score changes.
    • Tweak query and engine settings via the 'Tune Relevance' drawer.
  4. What is a Book in Quepid?

    main

    A Book is Quepid's offline relevance-judgement workflow. It consists of a set of query/document pairs (Query Doc Pairs) that one or more people (or AI Judges) rate independently (Judgements). These judgements can later be synced back into a Case's ratings.

    Every book page includes a tab strip for managing its lifecycle and data:

    • Overview: Summary of the book, including AI judge status, teams, and related cases.
    • Query/Doc Pairs: The list of pairs to be judged.
    • Judgement Stats: Statistical overview of ratings.
    • Judgements: The individual ratings provided by users or AI.
    • Import: Tools to bring in new books or additional data via JSON.
    • Share book: Controls for visibility.
    • Export: Tools to download book data as JSON or CSV.
    • Settings: Configuration for the book, including rating scales and danger-zone maintenance tools.
  5. Understand the Case and Query lifecycle for search testing

    main

    The Case is the central entity for search experiments. It acts as a connector for all elements related to a specific search setup.

    Core Components:

    • Case: The primary container for an experiment. Identified by a name.
    • Query: A representation of a search term within a Case. It includes query_text and can have associated notes or options. Queries can be re-ordered within a case.
    • Rating: Users assign ratings to search results. A rating is linked to a Query via query_id and to a specific search result via doc_id.

    Note: Quepid does not store the full search results or the search engine's response; it only stores the doc_id of the results being rated.

  6. Evaluate migration decisions using decision lenses

    main

    Before starting the final phase of the live query-state migration, the following architectural questions (decision lenses) must be addressed:

    • Incremental shipper: What can be shipped without a feature freeze (e.g., Toolbar Stimulus twins, DOM utilities)?
    • Search/IR domain: Will live tuning continue to search customer engines from the browser (using a proxy when needed)?
    • UX fidelity: Can the rating experience remain instant (currently achieved via client-side scoreAll() on every rating)?
    • Security: What will replace browser eval() for scorers (e.g., Web Workers)?
    • Performance: How to handle large cases (1,000+ queries) where scoreAll() is currently O(n queries).
    • A11y: Ensuring scores are not communicated via color alone and implementing real ARIA on badges and rating controls.
  7. Understand the Angular event-bus mechanisms in Quepid

    main

    Quepid uses several AngularJS event mechanisms for cross-controller and cross-service communication. Understanding the direction and scope of these events is critical for preventing silent breaks during migration or feature development.

    Event Mechanisms

    MechanismPurposeDirectionDetails
    broadcastSvc.send(name, data)App-wide pub/subDownwardWraps $rootScope.$broadcast. Sends from root to all child scopes.
    $rootScope.$broadcast(name, data)Direct fan-outDownwardOnly used internally by broadcastSvc.
    $scope.$emit(name, data)BubblingUpwardBubbles up the scope chain to ancestor $on handlers.
    $rootScope.$emit(name, data)Root-onlyRoot onlyFires $on handlers registered on $rootScope. Does not bubble.
    $scope.$on(name, fn) / $rootScope.$on(name, fn)Subscriptionn/aUsed to listen for events.

    Important: The $scope Alias Trap

    In some singleton services, $rootScope is injected but named $scope in the parameter list. Static searches for $scope.$on will miss these root-level listeners.

    Specifically, check the Dependency Injection (DI) arrays for:

    • queriesSvc (services/queriesSvc.js)
    • ratingsStoreSvc (services/ratingsStoreSvc.js)

    Events like rating-changed and scoring-complete use $rootScope.$emit / $rootScope.$on via these services, meaning they do not bubble and must be caught by root listeners.

  8. Use Find and Rate Missing Documents patterns

    main

    The 'Find and Rate Missing Documents' UI uses two distinct query patterns:

    1. Find Documents

    Used to find specific documents based on a Lucene query to be rated.

    • Key Parameter: explainOther is used to pass the Lucene query entered in the modal. If building an adapter, target this property.

    2. List All Documents That Have Been Rated

    Used to look up documents that have already been rated by their IDs.

    • Key Parameter: Uses the {!terms} component in the q parameter to perform a bulk lookup by ID (e.g., q={!terms f=id}193,13475).
    • Key Parameter: defType=lucene is used to specify the Lucene query parser.
    // Pattern 1: Find Documents
    http://quepid-solr.dev.o19s.com:8985/solr/tmdb/select?q=star&explainOther=title:war&fl=id title poster_path overview cast&wt=json&debug=true&debug.explain.structured=true&hl=false&rows=10&json.wrf=angular.callbacks._8
    
    // Pattern 2: List Rated Documents via terms lookup
    http://quepid-solr.dev.o19s.com:8985/solr/tmdb/select?qf=title%20id&rows=10&start=0&q={!terms%20f=id}193,13475&defType=lucene&fl=id title poster_path overview cast&wt=json&debug=true&debug.explain.structured=true&hl=false&json.wrf=angular.callbacks._z
  9. Understand Solr connection modes in Quepid

    main

    Quepid connects to Solr using one of two methods depending on your hosting environment:

    1. JSONP (Default/Cross-Domain): Used when Quepid and Solr are hosted on different domains. This avoids CORS issues. When using JSONP, Solr responses are wrapped in a dynamically generated JavaScript function name provided via the json.wrf parameter.
    2. Traditional GET (Same-Domain): Used if Quepid and Solr are hosted on the same domain. This returns standard JSON responses without the function wrapper.

    If you see a json.wrf parameter in your request, you must treat the resulting response as a JavaScript function call rather than raw JSON.

    // Example of a JSONP wrapped response
    angular.callbacks._2({
      "responseHeader":{ ... },
      "response":{ ... }
    });
    
    // Example of a standard GET JSON response
    {
      "responseHeader":{ ... },
      "response":{ ... }
    }
  10. Manage search engine configurations with Tries and SearchEndpoints

    main

    Quepid tracks configuration changes and search engine connectivity through Try and SearchEndpoint models.

    Configuration History (Try)

    To allow developers to tweak search engine configurations and test results, Quepid uses the Try model. Every time a configuration is changed, a new Try record is created. This maintains a history of changes, making it easy to revert to previous settings.

    Connecting to Search Engines (SearchEndpoint)

    Each Try connects to a search engine via a SearchEndpoint.

    • Standard Endpoints: Support engines like Solr or OpenSearch.
    • SearchAPI Endpoint: A flexible variation that allows you to provide custom JavaScript to map any search response format into the specific format Quepid expects.
  11. Important constraints when developing with PostgreSQL

    main

    When using PostgreSQL instead of the default MySQL, be aware of these critical behaviors:

    • Case Sensitivity: PostgreSQL is case-sensitive. While a compatibility shim handles some collation mapping, you should not assume case-insensitive behavior for columns (e.g., email lookups) unless the SQL query explicitly handles it.
    • Schema Authoring: Never run db:migrate against PostgreSQL to author new migrations. db/schema.rb is generated from MySQL and contains MySQL-specific options. Always write migrations and regenerate the schema on MySQL, then use PostgreSQL to run against the committed schema.
    • SQL Portability: PostgreSQL is stricter than MySQL. Ensure queries are portable: avoid SELECT DISTINCT over json columns without equality operators, avoid LIKE against numeric columns, and always use explicit ORDER BY clauses.
    • SQL Functions: Functions like RANDOM() are abstracted via lib/adapter_functions.rb to ensure cross-adapter compatibility.