Project ROUNDTABLE Docs

Matching and notifications (WS3)

How a submission becomes ranked matches and in-app alerts

Matching is the bridge between an industry's language and a government's technical organization. By default the pilot uses understandable deterministic scoring: lowercase tags and keyword overlap produce ranked portfolio candidates. Deployments can opt in to semantic scoring with Amazon Bedrock embeddings by setting MATCHING_SCORER=bedrock; the scorer interface keeps route handlers unchanged either way.

What happens after Submit?

The portal route validates the session, fields, optional call, and optional file.
The route creates a Submission with SUBMITTED status and then calls routeSubmission(submissionId).
The matcher unions organization tags and submission tags, then loads every PortfolioNode with its command.
The selected scorer (TagOverlapScorer by default, BedrockScorer when MATCHING_SCORER=bedrock) scores each node and filters candidates.
The service replaces old SubmissionMatch rows atomically and changes status to ROUTED if any exist.
Matching interest profiles receive one SUBMISSION_MATCH notification when settings allow it.
An audit row records scorer name, match count, and notified count.

Scoring details

The current constants are:

ConstantValueMeaning
MATCH_THRESHOLD0.2Minimum score normally retained for both scorers.
MIN_MATCHES_ON_OVERLAP3TagOverlapScorer-only fallback count when overlap exists but scores miss threshold; BedrockScorer does not retain sub-threshold candidates.
Tag weight0.75TagOverlapScorer-only contribution from tag overlap.
Keyword weight0.35TagOverlapScorer-only contribution from title/summary keyword hits.
Score cap1Normalized score cap used by both scorers.

Tags are normalized and unioned from organization and submission. Keyword hits compare node tags with submission title and summary. Matches are sorted descending and rounded to three decimals for persistence.

If at least one score reaches threshold, those candidates are persisted. Under tag-overlap scoring, if no score reaches threshold but there is meaningful overlap, the top three candidates are retained so a sparse pilot dataset does not silently discard a potentially useful submission; this fallback does not apply under embedding scoring, where a positive score does not imply literal overlap. If rerouting produces no candidates, a previously ROUTED record returns to SUBMITTED.

POC fan-out

The matcher checks every InterestProfile. A profile matches when its assigned portfolio node is among the matched nodes or its tags/keywords overlap the submission tags. The service deduplicates by user and link URL; rerouting the same submission does not create an identical notification again.

notifyUserForTags then applies NotificationSetting: disabled categories are skipped, and a nonempty preference filter requires at least one overlapping event tag. The notification body identifies organization, submission title/type, and whether the match came from a portfolio or tags.

Notification functions

FunctionUse
notifyUserOne recipient, no event-tag filtering.
notifyUserForTagsOne recipient, with event-tag filtering.
notifyGovByTagsFind government interest profiles overlapping event tags.
notifyAllIndustryFan out a published announcement/call to all industry users.

The bell UI reads notification rows. The unread-count route counts read = false; read routes update only the current user's rows.

Bedrock embedding scorer

When MATCHING_SCORER=bedrock, BedrockScorer embeds the submission text (title, summary, tags) and each portfolio node's name/description/tags with a Bedrock embedding model (BEDROCK_EMBEDDING_MODEL_ID, default Amazon Titan Text Embeddings V2) and scores by cosine similarity. Similarity is rescaled against BEDROCK_SIMILARITY_FLOOR (default 0.35) so scores are comparable to MATCH_THRESHOLD. Node embeddings are cached per server process; the submission is embedded once per routing pass. Credentials come from the default AWS provider chain (IAM task role) and AWS_REGION — no static keys.

If Bedrock is not configured, unreachable, or any call fails, the routing pass falls back to TagOverlapScorer in full: a degraded pass is rescored deterministically so semantic and tag-overlap scores are never mixed in one ranking, and routeSubmission never breaks. Deployments that do not set MATCHING_SCORER keep the deterministic scorer.

This is real model invocation: routing calls Amazon Bedrock (InvokeModel) with the deployment's IAM role. Persistence and notification behavior are identical for both scorers — only the score and rationale computation differs.

Troubleshooting a missing notification

Check that the profile has the expected tags or portfolio node, the category setting is enabled, and the submission's normalized tags overlap. Check whether an identical notification link already exists. A match row and a notification row are separate: a submission can be matched while a user is skipped by preference policy.

Worked Pelagic example

Pelagic Dynamics is seeded with uuv,autonomy,acoustics. A white paper tagged uuv,autonomy produces a union containing those terms. The Unmanned Maritime Systems node contains uuv,usv,unmanned,maritime robotics,autonomy, so it has strong overlap. The Undersea Warfare node contains sonar,acoustics,undersea,asw,torpedoes; it may be retained through acoustics or fallback overlap depending on the submission text. The service persists node matches first, then evaluates POC profiles.

The default tag-overlap scorer is intentionally explainable: a reviewer can inspect normalized tags, node tags, score, and rationale rather than trusting an opaque classifier. Bedrock scoring trades that tag-level inspectability for semantic similarity, although its score and rationale are still persisted for the record. With the default scorer, changing spelling (unmanned surface vehicle versus usv) can change a result; tag vocabulary is a product contract.

Rerouting

POST /api/notifications/reroute is useful after seed data or interest profiles change. It selects SUBMITTED records, invokes the same service as a new submission, and reports how many records were routed and how many matches were created. Existing matching rows are replaced transactionally. Existing identical notification links are not resent.

Rerouting does not make a submission reviewed or archived. It only recalculates routing state and associated alerts.

Diagnosing a missing match

When an expected match is absent, compare the normalized tag tokens first. Tags are lowercased, trimmed, and split on commas; C2 (Afloat) and c2 (afloat) therefore compare consistently, while a semicolon-separated list is one token. Then check whether the submission's organization tags were included in the union passed to the scorer.

Next inspect the portfolio node's techTags, its InterestProfile users, and the configured threshold. MATCH_THRESHOLD is 0.2 for both scorers, with Bedrock similarity rescaled against its configured floor before comparison. Under tag-overlap scoring, a score below the threshold can still retain one of the top three candidates when meaningful overlap exists; Bedrock scoring does not use that overlap fallback, so sub-threshold candidates are discarded. A match row without a notification can be explained by a disabled setting, a tag filter, or a recipient without a matching profile.

Diagnosing a duplicate notification

Notification links are part of de-duplication. Two different links can represent two rows even when titles look the same. Conversely, rerunning routing for the same submission and node should not create an identical alert. Inspect Notification.kind, userId, and linkUrl together rather than searching title text alone.