Skip to main content

Termination Scripts (Optional)

Termination scripts allow you to automatically end a survey for respondents who meet certain criteria, such as being detected as duplicates or having low quality scores.


πŸ“Œ Overview​

Termination scripts are optional and should only be used if your research policy allows early termination. They help maintain data quality by automatically terminating surveys for respondents based on DQC Toolbox results.

Key Points:

  • Optional: Use only if your research policy permits early termination
  • How it works: Termination uses Decipher's <term> element to check values in the dqc_data holder. When conditions are met, the survey ends.
  • Placement: every termination block goes after your second question's <suspend/> β€” see Where termination blocks go for why.

Quick Reference​

TypeFieldValue rangeRecommended threshold rangeDefault Value
Duplicatedqc_data.dup.valTrue/FalseTrueTrue
Device Scoredqc_data.dcs.val0-1000-4010
Data Trust Scoredqc_data.dts.val0-10000-420200
Personadqc_data.per.valStringStringKEYBOARD MASHER

Lower scores = higher risk. Termination occurs when score <= threshold, so a higher threshold terminates more respondents.

One scale per score β€” and where to set your threshold​

There is only one scale per score, and the holder stores the raw value on it:

  • dqc_data.dcs.val holds the device score on its full 0–100 scale.
  • dqc_data.dts.val holds the Data Trust Score on its full 0–1000 scale.

Write your <term> threshold on that same scale β€” float(dqc_data.dcs.val) le 10 means a device score of 10 or below, out of 100.

The narrower 0–40 and 0–420 ranges in the table are not a second scale. They are the part of each scale where terminating is defensible, which is why the generator's sliders stop there:

ScoreRecommended ceilingWhy stop there
Device Score4041 and above is already a Good or Clean device β€” see Device Score. A threshold above 40 starts terminating devices DQC considers trustworthy.
Data Trust Score420Every participant starts from a neutral baseline of 600 β€” see Data Trust Score. A threshold above 420 starts terminating participants at or near neutral trust, including newcomers who simply have no history yet.

The generator clamps your threshold to the ceiling, so the only way past it is to edit the <term> condition by hand β€” and if you do, you are ending surveys for respondents DQC has not flagged as risky.


Where termination blocks go​

Put every termination block after your second question's <suspend/>. Not a convention β€” it follows from when the data actually exists:

  1. The toolbox runs client‑side on the first page and needs a round trip to DQC to resolve the participant's identity and scores.
  2. save_dqc_data() runs in <exec when="submit">, so it copies whatever the toolbox has resolved into the dqc_data holder at each page submit β€” not before.
  3. A <term> only reads that holder. It has nothing to compare against until a submit has filled it.

At the first question's <suspend/> that round trip may still be in flight β€” and what save_dqc_data() stores then is not a placeholder. The score fields fall back to '0':

dqc_data.dcs.val = getattr(p, 'client_dqc_device_score', '') or '0'
dqc_data.dts.val = getattr(p, 'client_dqc_data_trust_score', '') or '0'

'0' is a perfectly valid number, and it is at or below every threshold you could set. So a termination block placed before the data exists does not fail safe β€” it terminates everyone.

This is what the placeholder guard is for

What protects you is not a check on the score. It is the check on dqc_data.pid.val, which does get the placeholder (Submission too quick, data not processed) while identity is unresolved β€” so every generated condition requires a real participant id before it looks at a score.

This is the actual generated block, at the default threshold:

<term 
label="DQC_Device_Score_Term"
cond="(dqc_data.pid.val and isinstance(dqc_data.pid.val, str) and dqc_data.dcs.val is not None and (float(str(dqc_data.dcs.val)) le 10) and dqc_data.pid.val not in ('Could not process','Submission too quick, data not processed','Deactivated-Key'))"
markers="terminated, dqc_device_score_termination"
sst="0">
DQC Device Score Termination
</term>

If you write your own <term>, keep that pid.val guard. Guarding the score field against placeholder strings does nothing, because the score is never a placeholder β€” it is '0'.

Which fields get what, while identity is unresolved:

FieldsFallback
rid, pid, cty, sub, sidSubmission too quick, data not processed
dcs, dts'0'
per'NONE'
dupFalse

By the second question's <suspend/> the real values are in the holder, which is why the termination blocks go there.


Prerequisites​

Before implementing termination scripts, complete the General Setup steps:

  1. βœ… Added the DQC Toolbox initialization script after the <survey> tag (see Step 2.1)
  2. βœ… Added the dqc_data data holder at the end of your survey (included in Step 2.1)
  3. βœ… Placed the termination script after your second question's <suspend/> tag β€” see Where termination blocks go

Termination Types​

Termination by Duplicate​

Automatically ends the survey for respondents who have already participated in the same survey (based on survey ID).

Checks: dqc_data.dup.val field. Terminates when value is 'True'.

Implementation:

Add this block after your second question's <suspend/> (why):

<term 
label="DQC_Duplicate_Term"
cond="dqc_data.dup.val == 'True'"
markers="terminated, dqc_duplicate_termination"
sst="0">
DQC Duplicate Termination
</term>

Termination by Device Score​

Ends the survey for respondents whose device score is at or below a specified threshold.

Score range: 0–100, lower scores indicate higher risk. Thresholds above 40 are not recommended β€” see One scale per score.

Checks: dqc_data.dcs.val field. Terminates when device_score <= threshold (excluding API placeholders).

Implementation:

Add this block after your second question's <suspend/> (why):

Tip: Use the slider below to adjust the threshold. The code block updates automatically.

<term 
label="DQC_Device_Score_Term"
cond="(dqc_data.pid.val and isinstance(dqc_data.pid.val, str) and dqc_data.dcs.val is not None and (float(str(dqc_data.dcs.val)) le 10) and dqc_data.pid.val not in ('Could not process','Submission too quick, data not processed','Deactivated-Key'))"
markers="terminated, dqc_device_score_termination"
sst="0">
DQC Device Score Termination
</term>

Termination by Data Trust Score​

Ends the survey for respondents whose data trust score falls below a specified threshold.

Score range: 0–1000, lower scores indicate higher risk. Thresholds above 420 are not recommended β€” see One scale per score.

Checks: dqc_data.dts.val field. Terminates when data_trust_score <= threshold (excluding API placeholders).

Implementation:

Add this block after your second question's <suspend/> (why):

Tip: Use the slider below to adjust the threshold. The code block updates automatically.

<term 
label="DQC_Data_Trust_Score_Term"
cond="(dqc_data.pid.val and isinstance(dqc_data.pid.val, str) and dqc_data.dts.val is not None and (float(str(dqc_data.dts.val)) le 200) and dqc_data.pid.val not in ('Could not process','Submission too quick, data not processed','Deactivated-Key'))"
markers="terminated, dqc_data_trust_score_termination"
sst="0">
DQC Data Trust Score Termination
</term>

Termination by Persona​

Ends the survey for respondents whose persona matches one of the specified high-risk personas.

Checks: dqc_data.per.val field. Terminates when the persona matches any value in the configured list. No additional validation is needed since the persona defaults to NONE when data is not available.

We only recommend terminating by the following personas. They are ordered from worst to less worst β€” the more personas you select, the more participants will be automatically terminated.

Available personas for termination (worst β†’ less worst):

OrderPersonaDescription
1 (worst)KEYBOARD MASHERConsistent pattern of poor in-survey behavior. High failure rates, inattentive responses, or rushed completion.
2INCOGNITO OPERATORHistory of using devices that exhibit fraudulent characteristics. Commonly associated with automated traffic, bots, or deliberate fraud activity.
3LOSING TRUSTTrending toward Keyboard Mashers or Incognito Operators. May use clean devices but exhibit neutral or negative historical behavior.

Default termination persona: KEYBOARD MASHER

For more details on all personas, see Data Trust Score β€” Personas.

Implementation:

Add this block after your second question's <suspend/> (why):

Tip: Use the checkboxes below to select which personas should trigger termination. The code block updates automatically.

<term
label="DQC_Persona_Term"
cond="dqc_data.per.val in ('KEYBOARD MASHER')"
markers="terminated, dqc_persona_termination"
sst="0">
DQC Persona Termination
</term>

Threshold Configuration​

Understanding Thresholds​

  • Higher threshold = More aggressive: Terminates more respondents (e.g., threshold 30 terminates scores 0-30)
  • Lower threshold = More lenient: Terminates fewer respondents (e.g., threshold 10 terminates scores 0-10)
  • Default values: Device Score = 10, Data Trust Score = 200. Maximum recommended: Device Score = 40, Data Trust Score = 420 β€” why those ceilings.

Adjusting Thresholds​

Device Score examples:

  • Threshold 10: Only terminates very low scores (0-10) - lenient
  • Threshold 25: Terminates moderate to low scores (0-25) - balanced
  • Threshold 40: Terminates all below-average scores (0-40) - aggressive

Data Trust Score examples:

  • Threshold 50: Only terminates very low scores (0-50) - lenient
  • Threshold 200: Terminates low to moderate scores (0-200) - balanced
  • Threshold 420: Terminates all below-average scores (0-420) - aggressive

Persona examples:

  • KEYBOARD MASHER only: Terminates only the worst respondents - recommended
  • KEYBOARD MASHER + INCOGNITO OPERATOR: Terminates the two worst persona groups - aggressive but useful in certain cases
  • KEYBOARD MASHER + INCOGNITO OPERATOR + LOSING TRUST: Terminates all low-trust respondents - extremely aggressive

Why the conditions say le instead of <=​

Decipher parses the whole <term> as XML, so a literal < inside a cond would be read as the start of a tag and the survey would not compile β€” the same rule as no literal &, < or > in <exec> code. le is Decipher's word form for <=, so float(str(dqc_data.dcs.val)) le 10 reads "device score at or below 10".

Customization​

The termination code automatically excludes API placeholder values (Could not process, Submission too quick, data not processed, Deactivated-Key) to prevent false terminations.

You can customize the termination message by editing the text inside the <term> element.


Using Multiple Termination Types​

You can combine multiple termination types in the same survey. The survey will terminate if any condition is met.

Here's a complete example with all four termination types (using default recommended termination values):

View Complete XML with All Terminations
<?xml version="1.0" encoding="UTF-8"?>
<survey
alt="Quality Tools Integration - Testing"
autosave="0"
builder:wizardCompleted="1"
builderCompatible="1"
compat="155"
delphi="1"
extraVariables="source,record,decLang,list,userAgent"
fir="on"
html:showNumber="0"
mobile="compat"
mobileDevices="smartphone,tablet,desktop"
name="Survey"
secure="1"
setup="term,decLang,quota,time"
ss:disableBackButton="1"
ss:enableNavigation="1"
ss:hideProgressBar="0"
state="testing">

<samplesources default="0">
<samplesource list="0">
<title>Data Quality Co-op</title>
<invalid>You are missing information in the URL. Please verify the URL with the original invite.</invalid>
<completed>It seems you have already completed this survey.</completed>
<exit cond="terminated">Thank you for taking our survey.</exit>
<exit cond="qualified">Thank you for taking our survey. Your efforts are greatly appreciated!</exit>
<exit cond="overquota">Thank you for taking our survey.</exit>
</samplesource>
</samplesources>

<style name="respview.client.meta"><![CDATA[
<link rel="preconnect" href="https://api.dqco-op.com" crossorigin="anonymous">
<link rel="preconnect" href="https://fpmetrics.dqco-op.com" crossorigin="anonymous">
]]></style>

<style name="global.page.head" wrap="ready"><![CDATA[
(async () => {
try {
const { DQCToolBox } = await import('https://api.dqco-op.com/tools/toolbox/DQC_API_KEY');
await DQCToolBox.getIdentity();
} catch (error) {
console.error('Error in client code:', error);
}
})();
]]></style>
<suspend/>

<exec when="init">
# ===========================================================================
# DQC INTERNALS - GENERAL FUNCTIONS
# ===========================================================================
defaultAnswer = 'Submission too quick, data not processed'
def save_dqc_data():
defaultDeviceFailuresAnswer = 'None' if getattr(p, 'client_dqc_participant_id', '') else defaultAnswer
dqc_data.rid.val = getattr(p, 'client_dqc_request_id', '') or defaultAnswer
dqc_data.pid.val = getattr(p, 'client_dqc_participant_id', '') or defaultAnswer
dqc_data.per.val = getattr(p, 'client_dqc_persona', '') or 'NONE'
dqc_data.dts.val = getattr(p, 'client_dqc_data_trust_score', '') or '0'
dqc_data.dcs.val = getattr(p, 'client_dqc_device_score', '') or '0'
dqc_data.cty.val = getattr(p, 'client_dqc_country_code', '') or defaultAnswer
dqc_data.sub.val = getattr(p, 'client_dqc_subdivision_name', '') or defaultAnswer
dqc_data.dup.val = getattr(p, 'client_dqc_is_duplicate', False)
dqc_data.sid.val = getattr(p, 'client_dqc_survey_id', '') or defaultAnswer
dqc_data.dfc.val = getattr(p, 'client_dqc_device_failures', '') or defaultDeviceFailuresAnswer
</exec>

<exec when="submit">
save_dqc_data()
</exec>

<text
cond="0"
label="dqc_data"
optional="0"
size="10"
translateable="0"
where="execute,survey,report">
<title>DQC Data Holder</title>
<row label="rid">dqc-request-id</row>
<row label="pid">dqc-participant-id</row>
<row label="dts">dqc-data-trust-score</row>
<row label="per">dqc-persona</row>
<row label="dcs">dqc-device-score</row>
<row label="dup">dqc-is-duplicate</row>
<row label="cty">dqc-country</row>
<row label="sub">dqc-subdivision</row>
<row label="sid">dqc-survey-id</row>
<row label="dfc">dqc-device-failures</row>
</text>

<suspend/>

<radio
label="Q1">
<title>Q1: Are you human?</title>
<comment>Select one</comment>
<row label="r1">Yes</row>
<row label="r2">No</row>
</radio>

<suspend/>

<radio
label="Q2">
<title>Q2: Are you a duplicate?</title>
<comment>Select one</comment>
<row label="r1">Yes</row>
<row label="r2">No</row>
</radio>

<suspend/>


<term
label="DQC_Duplicate_Term"
cond="dqc_data.dup.val == 'True'"
markers="terminated, dqc_duplicate_termination"
sst="0">
DQC Duplicate Termination
</term>
<term
label="DQC_Device_Score_Term"
cond="(dqc_data.pid.val and isinstance(dqc_data.pid.val, str) and dqc_data.dcs.val is not None and (float(str(dqc_data.dcs.val)) le 10) and dqc_data.pid.val not in ('Could not process','Submission too quick, data not processed','Deactivated-Key'))"
markers="terminated, dqc_device_score_termination"
sst="0">
DQC Device Score Termination
</term>
<term
label="DQC_Data_Trust_Score_Term"
cond="(dqc_data.pid.val and isinstance(dqc_data.pid.val, str) and dqc_data.dts.val is not None and (float(str(dqc_data.dts.val)) le 200) and dqc_data.pid.val not in ('Could not process','Submission too quick, data not processed','Deactivated-Key'))"
markers="terminated, dqc_data_trust_score_termination"
sst="0">
DQC Data Trust Score Termination
</term>
<term
label="DQC_Persona_Term"
cond="dqc_data.per.val in ('KEYBOARD MASHER')"
markers="terminated, dqc_persona_termination"
sst="0">
DQC Persona Termination
</term>


<radio
label="Q3">
<title>Q3: You made it to the end of the example survey</title>
<comment>Select one</comment>
<row label="r1">Yay</row>
<row label="r2">Bummer</row>
</radio>

<suspend/>


</survey>

Custom (non‑DQC) terminations​

Your own terminations β€” screeners, trap questions, quota logic, anything unrelated to DQC β€” are reported as Terminated too, with no extra work: Decipher marks every <term> with a term:<label> marker, and any term:‑prefixed marker counts as a termination.

<term label="Custom_Not_Human_Term" cond="Q1.r2" sst="0">
Custom Termination β€” thank you for your time.
</term>

The complete marker list is sent in the payload's markers field, so DQC can see exactly which marker ended the interview. terminationReason stays DQC‑only β€” it is "" for a custom termination. See Sending Transaction Data β€” How status is derived.


βœ… Summary​

  • Termination scripts are optional - use only if your research policy allows early termination
  • Place termination blocks after your second question's <suspend/> tag β€” the holder is only filled at a page submit, so an earlier block reads a placeholder (why)
  • Higher thresholds = more aggressive termination (terminates more respondents)
  • Lower thresholds = more lenient termination (terminates fewer respondents)
  • Start with lower (more lenient) thresholds and adjust based on your data quality needs
  • Your own terminations are reported as Terminated too when they set terminated or a term:‑prefixed marker β€” see Custom (non‑DQC) terminations

Additional Resources​

For complete, labeled survey examples with all integration components, see the Decipher XML Generator.

If a termination is firing on the wrong respondents, see Troubleshooting.