Build Savio

KINDAreplaces $39/mosaves $468/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A feedback aggregation tool where the value is not counting requests but knowing who asked: the same request from forty trial users and from your three largest customers are different signals, and a tool that only counts cannot tell them apart.

estimated effort multi-daythe files for this build are in the project pack

IngestionManual import and per-source connectors, both first classExtractionRequests identified within a conversation with the quote keptDeduplicationProposed clustering with a person confirmingAttributionEvery request linked to an account with its own attributesRuntimeTypeScript with PostgreSQL

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the server and the import workers.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree locally

    Why Conversations, requests and account attribution with real relationships.

    Get it Docker or your package manager. open ↗

    Verify psql connects and full text search returns a result

  3. have readyfree with an existing system

    Why Extraction quality, clustering and the difference between adjacent and identical requests only appear at volume and in real language.

    Get it Export from your support system, including the threads that contain no request at all, which are most of them.

    Verify Five hundred conversations are imported including many with no request

  4. have readyfree

    Why The whole premise is that who asked matters more than how many, which requires knowing something about the accounts beyond their names.

    Get it Export accounts with plan, seats and value from wherever that lives, and decide how it stays current.

    Verify Accounts import with segment and value attributes

  5. decidefree

    Why The reporting is built around segments, and choosing them after seeing the data means choosing whichever segmentation supports the conclusion you already had.

    Get it Decide the segments that matter to you and write down why, before generating any theme reports.

    Verify A written note names the segments and the reasoning

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- accounts(id, external_ref, name, plan, seats, value_minor, health, attributes_json)
- people(id, account_id, email_hash, name, role)
- conversations(id, source, external_id, person_id, subject, body, received_at, imported_at)
- requests(id, conversation_id, quote, char_start, char_end, extracted_at, confirmed_by)
- themes(id, title, description, created_at, merged_into_id) · the deduplicated grouping
- request_themes(request_id, theme_id, confidence, confirmed_by, at)
- decisions(theme_id, outcome, note, decided_by, at, communicated_at) · what happened and whether anyone was told
- A request keeps the quote and its position in the conversation, because a paraphrased request loses the specificity that made it actionable.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://localhost:5432/feedbackCustomer conversations, which contain personal data and often frustration.
SUPPORT_API_KEYsecretoptionala read-only keyRead only. Absent means manual import, which is a supported path rather than a fallback.
EXTRACTION_MODELrequiredlocalRuns over customer conversations, which is a reason to keep it local.
COUNT_ONLY_RANKINGrequiredneverPresent as a constant. A bare count looks objective and hands the roadmap to whoever writes in most.
REQUIRE_HUMAN_CONFIRMATIONrequiredalwaysFor extraction and clustering both. Wrong merges are not recoverable.
CONVERSATION_RETENTION_DAYSrequired1095Customer conversations are personal data with a stated retention.

The build, in order

  1. Getting conversations in, both ways

    Feedback arrives from a connector or by paste, equally well.

    1. Connectors break and feedback keeps arriving.

      Files src/import.ts

    2. Not on content. The same message reimported is the common case.

      Files src/dedupe.ts

    3. A summary loses the sentence that made the request actionable.

      Files src/conversations.ts

    done when · tick each as it passes
    watch out
    • Building only around a connector means the tool stops working entirely when the support system changes its API, and the feedback keeps arriving in the meantime. Make manual import a first-class path from the start rather than a fallback nobody tested.
  2. Finding the request inside the conversation

    A support thread yields the specific sentence that is a feature request.

    1. The request is usually one sentence in a longer exchange.

      Files src/extract.ts

    2. Forcing one per thread is where the specificity goes.

      Files src/extract.ts

    3. Extraction quality varies and a wrong request pollutes a theme.

      Files src/review.ts

    done when · tick each as it passes
    watch out
    • Treating a whole support conversation as one feature request loses the specificity that made it useful, since a thread about a billing problem often contains one sentence that is the actual request. Extract at sentence level with the quote kept, and let a conversation produce several requests or none.
  3. The same request, twenty wordings

    Duplicate requests group without losing distinct ones.

    1. Adjacent but distinct asks merge wrongly and irreversibly otherwise.

      Files src/themes.ts

    2. The theme summary is not a substitute for what someone actually said.

      Files src/themes.ts

    3. You will merge some wrongly and notice a month later.

      Files src/themes.tstests/unmerge.test.ts

    done when · tick each as it passes
    watch out
    • Automatically clustering requests merges two adjacent but distinct asks into one theme, and once the individual requests are represented only by the theme's summary the distinction is gone and cannot be recovered. Propose, confirm, keep the originals, and allow unmerging.
  4. Who asked, not how many

    A theme shows the accounts behind it, not just a count.

    1. Who asked is the signal. How many is the noise.

      Files src/attribution.ts

    2. Forty trial users and three large customers are different findings.

      Files src/report.ts

    3. Its absence is the design. A bare count looks objective and is not.

      Files tests/no-count-ranking.test.ts

    done when · tick each as it passes
    watch out
    • Ranking themes by request count hands the roadmap to whoever contacts support most, which is frequently trial users and rarely your largest customers, and the ranking looks entirely objective. Show the segment breakdown and the account value beside the count, and refuse to provide a bare count ordering.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more user research.