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 thedqc_dataholder. 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β
| Type | Field | Value range | Recommended threshold range | Default Value |
|---|---|---|---|---|
| Duplicate | dqc_data.dup.val | True/False | True | True |
| Device Score | dqc_data.dcs.val | 0-100 | 0-40 | 10 |
| Data Trust Score | dqc_data.dts.val | 0-1000 | 0-420 | 200 |
| Persona | dqc_data.per.val | String | String | KEYBOARD 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.valholds the device score on its full 0β100 scale.dqc_data.dts.valholds 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:
| Score | Recommended ceiling | Why stop there |
|---|---|---|
| Device Score | 40 | 41 and above is already a Good or Clean device β see Device Score. A threshold above 40 starts terminating devices DQC considers trustworthy. |
| Data Trust Score | 420 | Every 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:
- The toolbox runs clientβside on the first page and needs a round trip to DQC to resolve the participant's identity and scores.
save_dqc_data()runs in<exec when="submit">, so it copies whatever the toolbox has resolved into thedqc_dataholder at each page submit β not before.- 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.
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:
| Fields | Fallback |
|---|---|
rid, pid, cty, sub, sid | Submission too quick, data not processed |
dcs, dts | '0' |
per | 'NONE' |
dup | False |
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:
- β
Added the DQC Toolbox initialization script after the
<survey>tag (see Step 2.1) - β
Added the
dqc_datadata holder at the end of your survey (included in Step 2.1) - β
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):
| Order | Persona | Description |
|---|---|---|
| 1 (worst) | KEYBOARD MASHER | Consistent pattern of poor in-survey behavior. High failure rates, inattentive responses, or rushed completion. |
| 2 | INCOGNITO OPERATOR | History of using devices that exhibit fraudulent characteristics. Commonly associated with automated traffic, bots, or deliberate fraud activity. |
| 3 | LOSING TRUST | Trending 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 MASHERonly: Terminates only the worst respondents - recommendedKEYBOARD MASHER+INCOGNITO OPERATOR: Terminates the two worst persona groups - aggressive but useful in certain casesKEYBOARD 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
Terminatedtoo when they setterminatedor aterm:β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.