Proposal to include target intervals in cht-datasource

We have been gradulally adding support to cht-datasource for persons, places, and reports.

I am proposing that we continue expanding the formal CHT data model to include “target intervals” (aka the data currently recorded in target docs). The motivation here is provided by the new effort to Display targets from the previous month. As a part of that effort, @paulalogno has been working to add support at the webapp service layer for getting a users target doc from the previous month.

The code Paul is adding is very similar to code we already have in the target aggregates service that is also getting target docs. That existing code in the target aggregate service feeds the analytics.getTargetDocs function that is currently available to targets/tasks/contact-summary.

All of this got me to thinking that target data should exist as a first-class citizen in the CHT data model and should be accessible via cht-datasource and its related REST endpoints. This would give the various code in webapp a single place to go for accessing historical target data (instead of various places all manually querying the db for it).

Thoughts? Does this seem like a bad idea for some reason? Do targets not belong in the data model? I liked this example as a good test case around what really should belong (since persons/places/reports are pretty obvious).

Here is the proposed TS api code that we could start with:

qualifier.ts
/**
 * A qualifier that identifies entities based on a reporting period. (e.g. "2025-07")
 */
export interface ReportingPeriodQualifier {
  readonly reportingPeriod: string;
}

/**
 * A qualifier that identifies entities based on a username (without the "org.couchdb.user:" prefix).
 */
export interface UsernameQualifier {
  readonly username: string
}
target-interval.ts
/** */
export namespace v1 {

  /**
   * Data from an interval about a particular target for a user.
   */
  export interface Target extends DataObject {
    readonly id: string;
    readonly value: {
      readonly pass: number;
      readonly total: number;
      readonly percent?: number;
    }
  }

  /**
   * Data about a user's targets from a particular interval.
   */
  export interface TargetInterval extends Doc {
    readonly user: string;
    readonly owner: string;
    readonly reporting_period: string;
    readonly updated_date: number;
    readonly targets: Target[];
  }

  /**
   * Returns a target interval for the given qualifier.
   * @param context the current data context
   * @returns the target interval or `null` if no target interval is found for the qualifier
   * @throws Error if no context is provided or if the context is invalid
   */
  export const get = (
    context: DataContext
  ) => {

    /**
     * Returns the target interval identified by the given qualifier.
     * @param qualifier the limiter defining which target interval to return
     * @returns the identified target interval
     * @throws InvalidArgumentError if no qualifier is provided or if the qualifier is invalid
     */
    return (
      qualifier: ReportingPeriodQualifier & UsernameQualifier
    ): Promise<Nullable<TargetInterval>> => ...
  }
}

Cons:

  • More effort than just centralizing logic in a webapp service or something.
  • Possibly makes it harder to evolve the target functionality in the future by making the structure of the target docs part of the public api.

Pros:

  • Further reduces direct DB access from client-side code (encapsulating the data-access in cht-datasource provides an abstraction layer, both logical an actual, between client code and Couch).
  • Provide consistent, documented access to target data both to internal cht-code as well as externally via the REST apis.
  • Gives us a version-able interface that can allow for future revisions while coherently maintaining backwards compatibility.
1 Like

Hi @jkuester,

Thank you for the thorough review and the detailed forum post! I really appreciate you taking the time to think through the broader architecture implications of this feature.

I completely agree with adding target intervals to cht-datasource as a first-class data model. Your analysis is spot-on—the current implementation does create code duplication between rules-engine.service and target-aggregates.service, and both are directly querying the database with similar patterns. Moving this logic into cht-datasource makes total sense for all the reasons you outlined.

Here’s my understanding of the implementation:

  1. Create a new target-interval module in cht-datasource following the established Person/Place/Report pattern

  2. Add new qualifiers (ReportingPeriodQualifier and UsernameQualifier) to support querying by reporting period and username

  3. Implement the local adapter by migrating the database query logic from my current PR into local/target-interval.ts

  4. Implement the remote adapter with corresponding API endpoints for online-only scenarios

  5. Refactor both services (rules-engine.service and target-aggregates.service) to consume the new cht-datasource API instead of directly accessing the database

  6. Expose the versioned API through the datasource’s public interface

I’m happy to take this on as additional sub-tasks for the feature. I’ll create the cht-datasource implementation first, then refactor the services to use it. This will make the overall solution much cleaner and more maintainable.

Let me know if my understanding aligns with what you’re proposing, or if there are any specific considerations I should keep in mind!

1 Like

Thanks @paulalogno! I have logged a new subtask for the basic get-target-interval flow. I plan to log another subtask tomorrow to add support for querying target intervals based on the associated contacts (which is the access pattern needed for the target aggregates flow).