Spaces:
Running
Running
Download app.py from MartinLipsdorf/narrative_test: direct link, hf CLI and curl.
- Browser
- Download file 71.7 kB
-
https://huggingface.co/spaces/MartinLipsdorf/narrative_test/resolve/main/app.py
- Command line
-
hf download hf://spaces/MartinLipsdorf/narrative_test/app.py
-
curl -L -o app.py https://huggingface.co/spaces/MartinLipsdorf/narrative_test/resolve/main/app.py
71.7 kB
import os
import random
import hashlib
import tempfile
import threading
from typing import Dict, List, Tuple, Any, Optional
import gradio as gr
import plotly.graph_objects as go
# ==============================================================================
# 1. SKALEN, ITEMS (DE / EN / ZH), NORMWERTE & QUELLENVERZEICHNIS
# ==============================================================================
NORMS = {
"NCC_MEAN": 3.30, # Schlink & Walther (2007), Studierendenstichprobe (SD = 0.65)
"NCC_SD": 0.65,
"NCC_MID": 3.50, # Theoretische Skalenmitte (1-6)
"IKUE_STUD": 4.32, # Kovaleva et al. (2012) - Hohe Bildung (SD = 0.66)
"IKUE_POP": 4.12, # Gesamtbevölkerung (M = 4.12, SD = 0.81)
"EKUE_STUD": 2.31, # Kovaleva et al. (2012) - Hohe Bildung (SD = 0.88)
"EKUE_POP": 2.56, # Gesamtbevölkerung (M = 2.56, SD = 0.96)
}
# Umpolung bei Item 4, 11, 12 (0-basierter Index: 3, 10, 11)
NCCS_REVERSED_INDICES = {3, 10, 11}
TRANSLATIONS: Dict[str, Dict[str, Any]] = {
"DE": {
"lang_notice": "",
"intro": "**20 kurze Aussagen** (ca. 3 Min.). **100 % anonym** (keine IPs, Cookies oder Zeitstempel).",
"code_label": "Optional: Eigenes Kurz-Pseudonym (z. B. HERMANN-07)",
"code_placeholder": "Leer lassen für automatischen Code (z. B. FALKE-42)...",
"part_a_title": "### Teil A: Umgang mit Ungewissheit (16-NCCS)",
"part_a_instr": (
"*Bitte entscheiden Sie, wie stark die nachfolgenden Sätze Ihre persönliche Einstellung, Meinung und Erfahrung ausdrücken.*\n\n"
"<div class='scale-legend'><span><b>1</b> = stimme gar nicht zu</span><span>↔</span><span><b>6</b> = stimme völlig zu</span></div>"
),
"nccs_scale_hint": "1 = stimme gar nicht zu ↔ 6 = stimme völlig zu",
"nccs_choices": ["1", "2", "3", "4", "5", "6"],
"nccs_items": [
"Ich mag es nicht, wenn die Aussage einer Person mehrdeutig ist.",
"Ich finde, nachdem ich eine Lösung für ein Problem gefunden habe, ist es Zeitverschwendung, weitere mögliche Lösungen in Betracht zu ziehen.",
"Ich mag keine unvorhersehbaren Situationen.",
"Ich finde es spannend, nicht zu wissen, was das Leben einem bringen wird.",
"Ein Problem aus verschiedenen Blickwinkeln zu betrachten, führt nur zu Verwirrung.",
"Im Allgemeinen suche ich nicht nach Alternativlösungen für Probleme, für welche ich schon eine Lösung parat habe.",
"Ich bevorzuge die Gesellschaft guter Freunde, weil ich weiß, was ich von ihnen zu erwarten habe.",
"Ich fühle mich unbehaglich, wenn ich es nicht schaffe, eine schnelle Antwort auf Probleme zu geben, denen ich gegenüberstehe.",
"Ich bevorzuge Tätigkeiten, bei denen stets klar ist, was getan und wie es getan werden muss.",
"Wenn ich ein Problem lösen muss, verschwende ich im Allgemeinen keine Zeit damit, die unterschiedlichen Standpunkte dazu zu erwägen.",
"Ich mag Aufgaben, bei denen noch unklar ist, wie der genaue Lösungsweg aussieht.",
"Ich liebe die Ungewissheit und die Überraschung, die oft im Alltäglichen steckt.",
"Jedwede Lösung eines Problems ist besser, als in einem Zustand der Ungewissheit zu verharren.",
"Ich ziehe Dinge, die ich gewohnt bin, solchen vor, die ich nicht kenne und die ich nicht vorhersagen kann.",
"Im Allgemeinen vermeide ich es, mich an Diskussionen über uneindeutige und umstrittene Themen zu beteiligen.",
"Ich bevorzuge es, mich für die erstmögliche Lösung zu entscheiden, anstelle lange darüber nachzudenken, was für eine Entscheidung ich treffen sollte.",
],
"part_b_title": "### Teil B: Kontrollüberzeugung (IE-4)",
"part_b_instr": (
"*Die folgenden Aussagen können mehr oder weniger auf Sie zutreffen. Bitte geben Sie bei jeder Aussage an, inwieweit diese auf Sie persönlich zutrifft.*\n\n"
"<div class='scale-legend'><span><b>1</b> = trifft gar nicht zu</span><span><b>2</b> = wenig</span><span><b>3</b> = etwas</span><span><b>4</b> = ziemlich</span><span><b>5</b> = trifft voll und ganz zu</span></div>"
),
"ie4_scale_hint": "1 = trifft gar nicht zu | 2 = wenig | 3 = etwas | 4 = ziemlich | 5 = trifft voll und ganz zu",
"ie4_choices": ["1", "2", "3", "4", "5"],
"ie4_items": [
"Ich habe mein Leben selbst in der Hand.",
"Wenn ich mich anstrenge, werde ich auch Erfolg haben.",
"Egal ob privat oder im Beruf: Mein Leben wird zum großen Teil von anderen bestimmt.",
"Meine Pläne werden oft vom Schicksal durchkreuzt.",
],
"submit_btn": "📊 Auswerten & persönliches Profil anzeigen",
"missing_msg": "⚠️ **Bitte beantworten Sie noch alle offenen Fragen:**",
"sources_footer": """
---
### 📚 Wissenschaftliche Quellen & Original-Referenzen
**1. Eingesetzte Messinstrumente (Fragebögen):**
* **Teil A – Need for Cognitive Closure (16-NCCS, deutsche Kurzskala; 16 Items, 6er-Skala):** Schlink, S., & Walther, E. (2007). Kurz und gut: Eine deutsche Kurzskala zur Erfassung des Bedürfnisses nach kognitiver Geschlossenheit. *Zeitschrift für Sozialpsychologie*, 38(3), 153–161. [https://doi.org/10.1024/0044-3514.38.3.153](https://doi.org/10.1024/0044-3514.38.3.153) *(Englische 16-Item-Referenzskala: Roets, A., & Van Hiel, A., 2011, Item selection and validation of a brief, 15-item version of the Need for Closure Scale. Personality and Individual Differences, 50(1), 90–94; Ursprungsskala: Webster, D. M., & Kruglanski, A. W., 1994, Individual differences in need for cognitive closure. Journal of Personality and Social Psychology, 67(6), 1049–1062).*
* **Teil B – Internale-Externale-Kontrollüberzeugung-4 (IE-4; 4 Items, 5er-Skala):** Kovaleva, A., Beierlein, C., Kemper, C. J., & Rammstedt, B. (2012). *Eine Kurzskala zur Messung von Kontrollüberzeugung: Die Skala Internale-Externale-Kontrollüberzeugung-4 (IE-4)*. GESIS-Working Papers 2012/19. Köln: GESIS – Leibniz-Institut für Sozialwissenschaften. [https://nbn-resolving.org/urn:nbn:de:0168-ssoar-312096](https://nbn-resolving.org/urn:nbn:de:0168-ssoar-312096) *(Dokumentation im ZIS – Zusammenstellung sozialwissenschaftlicher Items und Skalen: [https://doi.org/10.6102/zis184](https://doi.org/10.6102/zis184)).*
**2. Theoretisches Fundament (Lay Epistemics, Need for Closure & Locus of Control):**
* Kruglanski, A. W. (1989). *Lay epistemics and human knowledge: Cognitive and motivational bases*. New York: Plenum Press.
* Kruglanski, A. W., & Webster, D. M. (1996). Motivated closing of the mind: "Seizing" and "freezing". *Psychological Review*, 103(2), 263–283. [https://doi.org/10.1037/0033-295X.103.2.263](https://doi.org/10.1037/0033-295X.103.2.263)
* Rotter, J. B. (1966). Generalized expectancies for internal versus external control of reinforcement. *Psychological Monographs: General and Applied*, 80(1), 1–28. [https://doi.org/10.1037/h0092976](https://doi.org/10.1037/h0092976)
""",
},
"EN": {
"lang_notice": "ℹ️ *Note: The English translation is provided for comprehension only and is not psychometrically validated as a combined instrument. Norm comparisons refer to the German reference sample.*",
"intro": "**20 brief statements** (approx. 3 min.). **100% anonymous** (no IPs, cookies, or timestamps stored).",
"code_label": "Optional: Custom short pseudonym (e.g., HERMANN-07)",
"code_placeholder": "Leave blank for an automatic code (e.g., FALKE-42)...",
"part_a_title": "### Part A: Dealing with Uncertainty (16-NCCS)",
"part_a_instr": (
"*To what extent do the following statements express your personal attitude, opinion, and experience?*\n\n"
"<div class='scale-legend'><span><b>1</b> = strongly disagree</span><span>↔</span><span><b>6</b> = strongly agree</span></div>"
),
"nccs_scale_hint": "1 = strongly disagree ↔ 6 = strongly agree",
"nccs_choices": ["1", "2", "3", "4", "5", "6"],
"nccs_items": [
"I dislike it when a person's statement could mean many different things.",
"I find that after finding a solution to a problem, it is a waste of time to consider further possible solutions.",
"I dislike unpredictable situations.",
"I think it is exciting not to know what life will bring.",
"Looking at a problem from different angles only leads to confusion.",
"In general, I do not look for alternative solutions to problems for which I already have a solution ready.",
"I prefer the company of good friends because I know what to expect from them.",
"I feel uncomfortable when I cannot give a quick answer to problems I face.",
"I prefer activities where it is always clear what needs to be done and how to do it.",
"When I have to solve a problem, I generally do not waste time considering the different viewpoints on it.",
"I like tasks where it is still unclear what the exact path to the solution looks like.",
"I love the uncertainty and surprise that often lies in everyday life.",
"Any solution to a problem is better than remaining in a state of uncertainty.",
"I prefer things I am used to over those I do not know and cannot predict.",
"In general, I avoid participating in discussions on ambiguous and controversial topics.",
"I prefer to decide on the first possible solution rather than thinking at length about what decision I should make.",
],
"part_b_title": "### Part B: Locus of Control (IE-4)",
"part_b_instr": (
"*The following statements may apply more or less to you. Please indicate for each statement to what extent it applies to you personally.*\n\n"
"<div class='scale-legend'><span><b>1</b> = not at all</span><span><b>2</b> = a little</span><span><b>3</b> = somewhat</span><span><b>4</b> = mostly</span><span><b>5</b> = completely</span></div>"
),
"ie4_scale_hint": "1 = does not apply at all | 2 = a little | 3 = somewhat | 4 = mostly | 5 = applies completely",
"ie4_choices": ["1", "2", "3", "4", "5"],
"ie4_items": [
"I am in control of my own life.",
"If I work hard, I will succeed.",
"Whether at work or in my private life: My life is largely determined by others.",
"My plans are often thwarted by fate.",
],
"submit_btn": "📊 Evaluate & Show Personal Profile",
"missing_msg": "⚠️ **Please answer all remaining questions before evaluating:**",
"sources_footer": """
---
### 📚 Scientific Sources & Original References
**1. Measurement Instruments Used (Questionnaires):**
* **Part A – Need for Cognitive Closure (16-NCCS, German short scale; 16 items, 6-point scale):** Schlink, S., & Walther, E. (2007). Kurz und gut: Eine deutsche Kurzskala zur Erfassung des Bedürfnisses nach kognitiver Geschlossenheit. *Zeitschrift für Sozialpsychologie*, 38(3), 153–161. [https://doi.org/10.1024/0044-3514.38.3.153](https://doi.org/10.1024/0044-3514.38.3.153) *(Original scale: Webster, D. M., & Kruglanski, A. W., 1994, Individual differences in need for cognitive closure. Journal of Personality and Social Psychology, 67(6), 1049–1062).*
* **Part B – Internal-External Locus of Control-4 (IE-4; 4 items, 5-point scale):** Kovaleva, A., Beierlein, C., Kemper, C. J., & Rammstedt, B. (2012). *Eine Kurzskala zur Messung von Kontrollüberzeugung: Die Skala Internale-Externale-Kontrollüberzeugung-4 (IE-4)*. GESIS-Working Papers 2012/19. Cologne: GESIS – Leibniz Institute for the Social Sciences. [https://nbn-resolving.org/urn:nbn:de:0168-ssoar-312096](https://nbn-resolving.org/urn:nbn:de:0168-ssoar-312096) *(ZIS documentation: [https://doi.org/10.6102/zis184](https://doi.org/10.6102/zis184)).*
**2. Theoretical Foundation (Lay Epistemics, Need for Closure & Locus of Control):**
* Kruglanski, A. W. (1989). *Lay epistemics and human knowledge: Cognitive and motivational bases*. New York: Plenum Press.
* Kruglanski, A. W., & Webster, D. M. (1996). Motivated closing of the mind: "Seizing" and "freezing". *Psychological Review*, 103(2), 263–283. [https://doi.org/10.1037/0033-295X.103.2.263](https://doi.org/10.1037/0033-295X.103.2.263)
* Rotter, J. B. (1966). Generalized expectancies for internal versus external control of reinforcement. *Psychological Monographs: General and Applied*, 80(1), 1–28. [https://doi.org/10.1037/h0092976](https://doi.org/10.1037/h0092976)
""",
},
"ZH": {
"lang_notice": "ℹ️ *注意:中文翻译仅供参考理解,未经心理测量学独立验证。常模对比基于德国参考样本。*",
"intro": "**20个简短陈述**(约3分钟)。**100%匿名**(不记录IP、Cookie或时间戳)。",
"code_label": "可选:自定义简短代号(例如 HERMANN-07)",
"code_placeholder": "留空以自动生成代号(例如 FALKE-42)...",
"part_a_title": "### 第一部分:应对不确定性与模糊性 (16-NCCS)",
"part_a_instr": (
"*请判断以下陈述在多大程度上表达了您的个人态度、观点和经验。*\n\n"
"<div class='scale-legend'><span><b>1</b> = 完全不同意</span><span>↔</span><span><b>6</b> = 完全同意</span></div>"
),
"nccs_scale_hint": "1 = 完全不同意 ↔ 6 = 完全同意",
"nccs_choices": ["1", "2", "3", "4", "5", "6"],
"nccs_items": [
"我不喜欢别人说话模棱两可。",
"我认为找到一个问题的解决方案后,再考虑其他可能的方案是浪费时间。",
"我不喜欢不可预测的情况。",
"不知道生活会带来什么,我觉得这很令人兴奋。",
"从不同角度看问题只会带来混乱。",
"通常我不会为已经有解决方案的问题寻找替代方案。",
"我更喜欢和熟悉的好朋友在一起,因为我知道他们会怎么做。",
"当我无法对面临的问题迅速给出答案时,我会感到不自在。",
"我更喜欢那些总是清楚该做什么以及怎么做的活动。",
"当我必须解决一个问题时,我通常不会浪费时间去考虑不同的观点。",
"我喜欢那些具体解决路径尚不明确的任务。",
"我热爱日常生活中常常蕴含的不确定性和惊喜。",
"任何解决问题的方案都比停留在不确定的状态中要好。",
"比起未知和无法预测的事物,我更喜欢我习惯的事物。",
"总的来说,我避免参与关于模棱两可和有争议话题的讨论。",
"我更喜欢选择第一个可行的解决方案,而不是花很长时间思考该做什么决定。",
],
"part_b_title": "### 第二部分:控制点 / 归因倾向 (IE-4)",
"part_b_instr": (
"*请在每一项中指出该陈述在多大程度上符合您的个人情况。*\n\n"
"<div class='scale-legend'><span><b>1</b> = 完全不符合</span><span><b>2</b> = 有一点</span><span><b>3</b> = 比较符合</span><span><b>4</b> = 相当符合</span><span><b>5</b> = 完全符合</span></div>"
),
"ie4_scale_hint": "1 = 完全不符合 | 2 = 有一点符合 | 3 = 比较符合 | 4 = 相当符合 | 5 = 完全符合",
"ie4_choices": ["1", "2", "3", "4", "5"],
"ie4_items": [
"我的人生掌握在自己手中。",
"只要我努力,就会取得成功。",
"无论是在私人生活还是工作中:我的生活很大程度上是由他人决定的。",
"我的计划经常被命运打乱。",
],
"submit_btn": "📊 立即评估并显示个人档案",
"missing_msg": "⚠️ **请在评估前回答以下未完成的问题:**",
"sources_footer": """
---
### 📚 科学文献与原始参考资料 (Scientific Sources & Original References)
**1. 使用的测量工具(问卷):**
* **第一部分 – 认知闭合需求简版量表 (16-NCCS, 德语验证版; 16项, 6点量表):** Schlink, S., & Walther, E. (2007). Kurz und gut: Eine deutsche Kurzskala zur Erfassung des Bedürfnisses nach kognitiver Geschlossenheit. *Zeitschrift für Sozialpsychologie*, 38(3), 153–161. [https://doi.org/10.1024/0044-3514.38.3.153](https://doi.org/10.1024/0044-3514.38.3.153) *(原始量表: Webster, D. M., & Kruglanski, A. W., 1994, Individual differences in need for cognitive closure. Journal of Personality and Social Psychology, 67(6), 1049–1062).*
* **第二部分 – 内外控制点简版量表 (IE-4; 4项, 5点量表):** Kovaleva, A., Beierlein, C., Kemper, C. J., & Rammstedt, B. (2012). *Eine Kurzskala zur Messung von Kontrollüberzeugung: Die Skala Internale-Externale-Kontrollüberzeugung-4 (IE-4)*. GESIS-Working Papers 2012/19. Köln: GESIS. [https://nbn-resolving.org/urn:nbn:de:0168-ssoar-312096](https://nbn-resolving.org/urn:nbn:de:0168-ssoar-312096) *(ZIS: [https://doi.org/10.6102/zis184](https://doi.org/10.6102/zis184)).*
**2. 理论基础 (Lay Epistemics, Need for Closure & Locus of Control):**
* Kruglanski, A. W. (1989). *Lay epistemics and human knowledge: Cognitive and motivational bases*. New York: Plenum Press.
* Kruglanski, A. W., & Webster, D. M. (1996). Motivated closing of the mind: "Seizing" and "freezing". *Psychological Review*, 103(2), 263–283. [https://doi.org/10.1037/0033-295X.103.2.263](https://doi.org/10.1037/0033-295X.103.2.263)
* Rotter, J. B. (1966). Generalized expectancies for internal versus external control of reinforcement. *Psychological Monographs: General and Applied*, 80(1), 1–28. [https://doi.org/10.1037/h0092976](https://doi.org/10.1037/h0092976)
""",
},
}
# ==============================================================================
# 2. ANONYMER CODE-GENERATOR & REIN DESKRIPTIVE AUSWERTUNGSLOGIK
# ==============================================================================
NEUTRAL_WORDS = [
"ANKER", "FALKE", "KOMPASS", "LOTSE", "QUARZ", "SEGEL", "GIPFEL",
"BRÜCKE", "KOBALT", "ORBIT", "PRISMA", "ZENIT", "DELTA", "ECHO",
"ATLAS", "PHÖNIX", "KOSMOS", "NOVA", "KOMET", "SOLAR", "FJORD", "POLAR"
]
def generate_anonymous_code(custom_code: str = "") -> str:
cleaned = custom_code.strip().upper()
if cleaned:
return cleaned[:15]
return f"{random.choice(NEUTRAL_WORDS)}-{random.randint(10, 99)}"
def evaluate_questionnaire(nccs_raw: List[int], ie4_raw: List[int]) -> Dict[str, Any]:
nccs_scored = [
(7 - int(val)) if idx in NCCS_REVERSED_INDICES else int(val)
for idx, val in enumerate(nccs_raw)
]
ncc_sum = sum(nccs_scored)
ncc_mean = round(ncc_sum / 16.0, 2)
ikue_mean = round((int(ie4_raw[0]) + int(ie4_raw[1])) / 2.0, 2)
ekue_mean = round((int(ie4_raw[2]) + int(ie4_raw[3])) / 2.0, 2)
return {
"ncc_mean": ncc_mean,
"ncc_sum": ncc_sum,
"ikue_mean": ikue_mean,
"ekue_mean": ekue_mean,
}
def generate_profile_feedback(scores: Dict[str, Any], lang: str = "DE") -> str:
ncc = scores["ncc_mean"]
ekue = scores["ekue_mean"]
ikue = scores["ikue_mean"]
high_closure = ncc >= NORMS["NCC_MEAN"]
high_external = ekue >= NORMS["EKUE_POP"]
if lang == "EN":
if high_closure and not high_external:
title = "High Need for Cognitive Closure & Low External Locus of Control"
text = (
"**Psychometric Classification:** Your score on the *Need for Cognitive Closure* scale (16-NCCS) is above the empirical reference mean (M = 3.30), indicating a preference for clear structure, predictability, and rapid decision-making (*Seizing & Freezing* according to Kruglanski & Webster, 1996). "
"Simultaneously, your *External Locus of Control* (EKÜ) is below the reference norm (M = 2.56), indicating that outcomes are primarily attributed to your own agency rather than external circumstances (Rotter, 1966; Kovaleva et al., 2012).\n\n"
"**Functional Strengths & Potential Pitfalls in Extreme Ranges:**\n"
"- *Strengths:* High capacity for rapid action, structured execution, and strong personal accountability—particularly effective under time pressure or in clearly defined tasks.\n"
"- *Potential Pitfalls:* In highly complex or ambiguous situations, a very high need for closure carries the risk of premature locking onto the first available solution (*freezing*) and overlooking alternative perspectives. An extremely low external attribution can also lead to an *illusion of control* or self-overload if systemic barriers and unchangeable external constraints are underestimated."
)
elif high_closure and high_external:
title = "High Need for Cognitive Closure & High External Locus of Control"
text = (
"**Psychometric Classification:** Your score on the *Need for Cognitive Closure* scale (16-NCCS) is above the empirical reference mean (M = 3.30), indicating a preference for unambiguous situations, predictability, and clear rules (Schlink & Walther, 2007). "
"At the same time, your *External Locus of Control* (EKÜ) is at or above the reference norm (M = 2.56), reflecting a stronger perception that external forces, institutional structures, or chance influence personal outcomes (Kovaleva et al., 2012).\n\n"
"**Functional Strengths & Potential Pitfalls in Extreme Ranges:**\n"
"- *Strengths:* High reliability within established structures and realistic awareness of external dependencies, institutional rules, and systemic limits (protecting against unrealistic self-blame when external obstacles arise).\n"
"- *Potential Pitfalls:* When external conditions become volatile or unpredictable, the combination of a strong desire for certainty and perceived external dependency can trigger heightened stress. Extremely high external scores may also reduce proactive initiative if personal scope for action is underestimated."
)
elif not high_closure and high_external:
title = "Low Need for Cognitive Closure & High External Locus of Control"
text = (
"**Psychometric Classification:** Your score on the *Need for Cognitive Closure* scale (16-NCCS) is below the empirical reference mean (M = 3.30), indicating higher tolerance for ambiguity and openness to multiple perspectives without immediate pressure for closure (Kruglanski & Webster, 1996). "
"Your *External Locus of Control* (EKÜ) is at or above the reference norm (M = 2.56), indicating that external factors or systemic conditions are perceived as having a substantial impact on outcomes (Kovaleva et al., 2012).\n\n"
"**Functional Strengths & Potential Pitfalls in Extreme Ranges:**\n"
"- *Strengths:* High adaptability and composure in dynamic environments; ability to observe complex systemic interdependencies without jumping to premature conclusions.\n"
"- *Potential Pitfalls:* In extreme ranges, low need for closure combined with high external attribution can lead to a passive wait-and-see attitude, indecisiveness (*analysis paralysis*), or postponing necessary commitments because circumstances appear externally determined."
)
else:
title = "Low Need for Cognitive Closure & Low External Locus of Control"
text = (
"**Psychometric Classification:** Your score on the *Need for Cognitive Closure* scale (16-NCCS) is below the empirical reference mean (M = 3.30), reflecting high tolerance for uncertainty and openness when processing ambiguous information (Schlink & Walther, 2007). "
"Simultaneously, your *External Locus of Control* (EKÜ) is below the population norm (M = 2.56), indicating a predominantly internal attribution of control and personal efficacy (Rotter, 1966; Kovaleva et al., 2012).\n\n"
"**Functional Strengths & Potential Pitfalls in Extreme Ranges:**\n"
"- *Strengths:* High openness to exploratory problem-solving, complex debate, and self-directed navigation in open-ended projects.\n"
"- *Potential Pitfalls:* Neither dimension is linearly 'better' at extreme levels: An extremely low need for closure can result in *decision paralysis*, chronic hesitation, and difficulty reaching timely closure in team processes. Likewise, an extreme internal control belief (very low EKÜ) can foster self-overburdening or excessive self-blame when outcomes are actually limited by external constraints or chance."
)
return f"""### Psychometric Profile: {title}
| Scale / Metric | Your Score | Reference (Students / Norm) | Scale Range |
| :--- | :---: | :---: | :---: |
| **Need for Cognitive Closure (16-NCCS)** | **{ncc:.2f}** *(Sum: {scores['ncc_sum']})* | 3.30 *(Sum: 53)* | 1.00 – 6.00 |
| **Internal Locus of Control (IKÜ)** | **{ikue:.2f}** | 4.32 *(Norm: 4.12)* | 1.00 – 5.00 |
| **External Locus of Control (EKÜ)** | **{ekue:.2f}** | 2.31 *(Norm: 2.56)* | 1.00 – 5.00 |
{text}"""
if lang == "ZH":
if high_closure and not high_external:
title = "高认知闭合需求 & 低外控倾向"
text = (
"**心理测量学分类说明:** 您的认知闭合需求 (16-NCCS) 高于参考均值 (M = 3.30),表明在信息处理中更倾向于明确性、结构化以及快速做出决定(Kruglanski & Webster, 1996)。"
"同时,您的外控倾向 (EKÜ) 低于常模参考值 (M = 2.56),表明您倾向于将事件结果归因于自身的努力与行动,而非外部环境或运气(Rotter, 1966; Kovaleva et al., 2012)。\n\n"
"**潜在优势与极端值风险(双向审视):**\n"
"- *适应性优势:* 决策果断、执行力强、责任感明确,在时间紧迫或目标清晰的任务中非常高效。\n"
"- *极端倾向的潜在风险:* 在复杂或高度模糊的情境下,过高的闭合需求可能导致过早锁定首个方案(“认知冻结”)并忽略替代视角;而极低的外控归因若忽视客观环境限制,则可能带来“控制错觉”或自我过度施压。"
)
elif high_closure and high_external:
title = "高认知闭合需求 & 高外控倾向"
text = (
"**心理测量学分类说明:** 您的认知闭合需求 (16-NCCS) 高于参考均值 (M = 3.30),表明您偏好确定性、可预测性及明确的规则(Schlink & Walther, 2007)。"
"同时,您的外控倾向 (EKÜ) 达到或高于常模参考值 (M = 2.56),反映出您认为外部条件、他人或机遇对个人生活和计划具有较强的影响(Kovaleva et al., 2012)。\n\n"
"**潜在优势与极端值风险(双向审视):**\n"
"- *适应性优势:* 在规则明确的组织框架中表现稳健,且对外部制度、资源限制和现实约束具有清醒认知(有助于避免在遭遇不可抗力时产生过度的自我归咎)。\n"
"- *极端倾向的潜在风险:* 当外部环境剧烈变动且难以预测时,“渴望确定性”与“感知受制于外部”的叠加容易引发不确定性压力;过高的外控倾向也可能削弱主动改变现状的意愿。"
)
elif not high_closure and high_external:
title = "低认知闭合需求 & 高外控倾向"
text = (
"**心理测量学分类说明:** 您的认知闭合需求 (16-NCCS) 低于参考均值 (M = 3.30),表明您对模糊性和不确定性具有较高的容忍度,愿意考虑多种不同观点(Kruglanski & Webster, 1996)。"
"同时,您的外控倾向 (EKÜ) 达到或高于常模参考值 (M = 2.56),表明您在较大程度上感知到外部环境或结构性因素对结果的制约(Kovaleva et al., 2012)。\n\n"
"**潜在优势与极端值风险(双向审视):**\n"
"- *适应性优势:* 在复杂多变的环境中保持平和与灵活,能够客观审视系统性制约,不易盲目下定论。\n"
"- *极端倾向的潜在风险:* 若两项指标处于极端水平,可能会导致过度观望、难以做出最终决定(“分析瘫痪”),或因认为结果受制于外因而推迟必要的行动。"
)
else:
title = "低认知闭合需求 & 低外控倾向"
text = (
"**心理测量学分类说明:** 您的认知闭合需求 (16-NCCS) 低于参考均值 (M = 3.30),反映出对不明确情境的开放性以及较低的过早定论倾向(Schlink & Walther, 2007)。"
"同时,您的外控倾向 (EKÜ) 低于常模参考值 (M = 2.56),表明您具有较强的内控归因倾向,认为结果主要取决于自身的行动(Rotter, 1966; Kovaleva et al., 2012)。\n\n"
"**潜在优势与极端值风险(双向审视):**\n"
"- *适应性优势:* 乐于探索未知、接纳多元观点,并在开放性课题中表现出高度的自主驱动力。\n"
"- *极端倾向的潜在风险:* 这两个维度并非“越低/越内控就绝对越好”。极低的认知闭合需求可能导致“决策瘫痪”(反复权衡却难以收尾,影响团队推进效率);而极端内控(完全忽视外部因素)则容易在遭遇客观结构性障碍或偶然挫败时,陷入不切实际的自我苛责与倦怠。"
)
return f"""### 心理测量学结果概览:{title}
| 量表指标 | 您的得分 | 参考值(学生 / 常模) | 量表范围 |
| :--- | :---: | :---: | :---: |
| **认知闭合需求 (16-NCCS)** | **{ncc:.2f}** *(总分: {scores['ncc_sum']})* | 3.30 *(总分: 53)* | 1.00 – 6.00 |
| **内控倾向 (IKÜ)** | **{ikue:.2f}** | 4.32 *(常模: 4.12)* | 1.00 – 5.00 |
| **外控倾向 (EKÜ)** | **{ekue:.2f}** | 2.31 *(常模: 2.56)* | 1.00 – 5.00 |
{text}"""
# Default: DE (Differenzierte psychometrische Einordnung inkl. Stärken & Fallstricke bei Extremwerten)
if high_closure and not high_external:
quadrant_title = "Hohes Bedürfnis nach kognitiver Geschlossenheit & niedrige externale Kontrollüberzeugung"
description = (
"**Psychometrische Einordnung:** Ihr Wert auf der Skala *Need for Cognitive Closure* (16-NCCS) liegt über dem empirischen Referenzmittelwert (M = 3.30; Schlink & Walther, 2007). "
"Dies beschreibt eine Präferenz für eindeutige Informationen, strukturierte Rahmenbedingungen und ein rasches Abschließen von Suchprozessen bei Entscheidungen (*Seizing & Freezing* nach Kruglanski & Webster, 1996). "
"Gleichzeitig liegt Ihre *externale Kontrollüberzeugung* (EKÜ) unter dem Referenzwert (M = 2.56; Kovaleva et al., 2012), was auf eine überwiegend internale Attribution von Handlungsergebnissen (Fokus auf eigenes Handeln statt auf äußere Umstände oder Zufall; Rotter, 1966) hinweist.\n\n"
"**Funktionale Stärken & potenzielle Fallstricke bei Extremwerten:**\n"
"- *Funktionale Stärken:* Hohe Entscheidungs- und Umsetzungskraft, klare Strukturierung sowie ausgeprägte Eigenverantwortung – besonders wirksam unter Zeitdruck oder bei klar definierten Projektzielen.\n"
"- *Potenzielle Fallstricke im Extrembereich:* Bei hoher Komplexität birgt ein sehr starkes Bedürfnis nach Geschlossenheit das Risiko, sich vorschnell auf die erstbeste Lösung festzulegen (*Freezing*) und relevante Gegenargumente auszublenden. Eine extrem niedrige Außenattribuierung kann zudem zu einer *Kontrollillusion* oder Selbstüberforderung führen, wenn reale strukturelle Hürden unterschätzt werden."
)
elif high_closure and high_external:
quadrant_title = "Hohes Bedürfnis nach kognitiver Geschlossenheit & hohe externale Kontrollüberzeugung"
description = (
"**Psychometrische Einordnung:** Ihr Wert auf der Skala *Need for Cognitive Closure* (16-NCCS) liegt über dem empirischen Referenzmittelwert (M = 3.30; Schlink & Walther, 2007) und verweist auf ein ausgeprägtes Bedürfnis nach Vorhersehbarkeit, klaren Regeln und dem Vermeiden von Mehrdeutigkeit. "
"Zugleich liegt Ihre *externale Kontrollüberzeugung* (EKÜ) auf oder über dem Referenzwert (M = 2.56; Kovaleva et al., 2012). "
"Dies bildet die Überzeugung ab, dass Lebensereignisse und Pläne in relevantem Maß durch äußere Rahmenbedingungen, andere Personen oder das Schicksal beeinflusst werden (Rotter, 1966).\n\n"
"**Funktionale Stärken & potenzielle Fallstricke bei Extremwerten:**\n"
"- *Funktionale Stärken:* Hohe Verlässlichkeit in klar geregelten Strukturen sowie ein realistisches Bewusstsein für institutionelle Rahmenbedingungen, Hierarchien und äußere Abhängigkeiten (was vor überzogenen Selbstvorwürfen bei unbeeinflussbaren Rückschlägen schützt).\n"
"- *Potenzielle Fallstricke im Extrembereich:* In dynamischen oder unübersichtlichen Umbruchphasen kann das Zusammentreffen von starkem Strukturbedürfnis und erlebter Fremdbestimmung erhöhten Stress auslösen. Extrem hohe externale Werte bergen zudem das Risiko, eigene Gestaltungsspielräume vorschnell aufzugeben."
)
elif not high_closure and high_external:
quadrant_title = "Niedriges Bedürfnis nach kognitiver Geschlossenheit & hohe externale Kontrollüberzeugung"
description = (
"**Psychometrische Einordnung:** Ihr Wert auf der Skala *Need for Cognitive Closure* (16-NCCS) liegt unter dem empirischen Referenzmittelwert (M = 3.30; Schlink & Walther, 2007). "
"Dies kennzeichnet eine höhere Ambiguitätstoleranz sowie die Bereitschaft, unterschiedliche Perspektiven und offene Lösungswege länger abzuwägen (Kruglanski & Webster, 1996). "
"Gleichzeitig liegt Ihre *externale Kontrollüberzeugung* (EKÜ) auf oder über dem Referenzwert (M = 2.56; Kovaleva et al., 2012), wonach der Einfluss äußerer Faktoren oder übergeordneter Strukturen auf eigene Ergebnisse als vergleichsweise hoch eingeschätzt wird.\n\n"
"**Funktionale Stärken & potenzielle Fallstricke bei Extremwerten:**\n"
"- *Funktionale Stärken:* Hohe Gelassenheit und Flexibilität in mehrdeutigen Situationen sowie ein wacher Blick für komplexe Systemzusammenhänge, ohne vorschnell zu urteilen.\n"
"- *Potenzielle Fallstricke im Extrembereich:* Bei sehr starker Ausprägung kann die Kombination aus geringem Abschlussbedürfnis und hoher Außenattribuierung zu abwartender Passivität, Entscheidungsaufschub (*Prokrastination*) oder dem Gefühl führen, sich in endlosen Abwägungen zu verlieren, weil Rahmenbedingungen als kaum beeinflussbar erlebt werden."
)
else:
quadrant_title = "Niedriges Bedürfnis nach kognitiver Geschlossenheit & niedrige externale Kontrollüberzeugung"
description = (
"**Psychometrische Einordnung:** Ihr Wert auf der Skala *Need for Cognitive Closure* (16-NCCS) liegt unter dem empirischen Referenzmittelwert (M = 3.30; Schlink & Walther, 2007), was auf eine hohe Offenheit gegenüber Ungewissheit, Mehrdeutigkeit und alternativen Sichtweisen hindeutet (Kruglanski & Webster, 1996). "
"Zugleich liegt Ihre *externale Kontrollüberzeugung* (EKÜ) unter dem Referenzwert (M = 2.56; Kovaleva et al., 2012), was einer geringen Außenattribuierung und einer stärkeren Überzeugung von der eigenen Handlungswirksamkeit entspricht (Rotter, 1966).\n\n"
"**Funktionale Stärken & potenzielle Fallstricke bei Extremwerten:**\n"
"- *Funktionale Stärken:* Hohe Offenheit für exploratives Problemlösen, Perspektivenvielfalt und eigenständiges Navigieren in offenen Forschungs- oder Innovationsprozessen.\n"
"- *Potenzielle Fallstricke im Extrembereich:* Keine der beiden Dimensionen ist im Extrem automatisch vorteilhaft: Ein extrem niedriges Bedürfnis nach Geschlossenheit kann zu *Entscheidungsparalyse (Analysis Paralysis)*, chronischem Zögern und mangelnder Verbindlichkeit führen, wenn unter Zeitdruck ein pragmatischer Abschluss nötig ist. Ebenso birgt eine extrem internale Kontrollüberzeugung (blinder Fleck für äußere Umstände/Zufall) die Gefahr von *Kontrollillusion*, Selbstüberforderung und harten Selbstvorwürfen, wenn Vorhaben an objektiven äußeren Grenzen scheitern."
)
return f"""### Psychometrische Profil-Auswertung: {quadrant_title}
| Skala / Kennwert | Ihr Wert | Referenz (Studierende / Norm) | Skalenbereich |
| :--- | :---: | :---: | :---: |
| **Need for Cognitive Closure (16-NCCS)** | **{ncc:.2f}** *(Summe: {scores['ncc_sum']})* | 3.30 *(Summe: 53)* | 1.00 – 6.00 |
| **Internale Kontrollüberzeugung (IKÜ)** | **{ikue:.2f}** | 4.32 *(Norm: 4.12)* | 1.00 – 5.00 |
| **Externale Kontrollüberzeugung (EKÜ)** | **{ekue:.2f}** | 2.31 *(Norm: 2.56)* | 1.00 – 5.00 |
{description}
"""
# ==============================================================================
# 3. DATENSCHUTZ-KONFORMER IN-MEMORY-SPEICHER
# ==============================================================================
class PrivacyMemoryStore:
def __init__(self):
self._lock = threading.Lock()
self._records: List[Dict[str, Any]] = []
self._admin_pin = (
os.environ.get("ADMIN_PIN")
or os.environ.get("PIN")
or os.environ.get("LECTURER_PIN")
or "2026"
)
def verify_pin(self, pin: str) -> bool:
return bool(pin) and pin.strip() == self._admin_pin
def upsert_entry(self, code: str, scores: Dict[str, Any], previous_code: Optional[str] = None) -> str:
with self._lock:
if previous_code:
self._records = [r for r in self._records if r["code"] != previous_code]
existing_codes = {r["code"] for r in self._records}
final_code = code
counter = 2
while final_code in existing_codes:
final_code = f"{code}-{counter}"
counter += 1
self._records.append({
"code": final_code,
"ncc_mean": scores["ncc_mean"],
"ncc_sum": scores["ncc_sum"],
"ikue_mean": scores["ikue_mean"],
"ekue_mean": scores["ekue_mean"],
})
random.shuffle(self._records)
return final_code
def get_all_entries(self) -> List[Dict[str, Any]]:
with self._lock:
return list(self._records)
def clear_all(self, pin: str) -> Tuple[bool, str]:
if pin.strip() != self._admin_pin:
return False, "❌ PIN ungültig."
with self._lock:
count = len(self._records)
self._records.clear()
return True, f"✅ {count} Datensätze gelöscht."
def seed_demo_data(self, pin: str, count: int = 14) -> Tuple[bool, str]:
if pin.strip() != self._admin_pin:
return False, "❌ PIN ungültig."
with self._lock:
self._records.clear()
used_words = random.sample(NEUTRAL_WORDS, min(count, len(NEUTRAL_WORDS)))
for i in range(count):
code = f"{used_words[i]}-{random.randint(10, 99)}"
ncc_mean = round(random.uniform(2.1, 4.9), 2)
ncc_sum = int(round(ncc_mean * 16))
ikue_mean = random.choice([2.5, 3.0, 3.5, 4.0, 4.5, 5.0])
ekue_mean = random.choice([1.5, 2.0, 2.5, 3.0, 3.5, 4.0])
self._records.append({
"code": code,
"ncc_mean": ncc_mean,
"ncc_sum": ncc_sum,
"ikue_mean": ikue_mean,
"ekue_mean": ekue_mean,
})
random.shuffle(self._records)
return True, f"✅ {count} Demo-Datensätze geladen."
SESSION_STORE = PrivacyMemoryStore()
# ==============================================================================
# 4. BEAMER-OPTIMIERTE PLOTLY-ENGINE (MIT SACHLICH-PSYCHOMETRISCHEN LABELS)
# ==============================================================================
def _deterministic_jitter(code: str, scale: float = 0.05) -> Tuple[float, float]:
h = hashlib.md5(code.encode("utf-8")).hexdigest()
jx = ((int(h[:4], 16) / 65535.0) - 0.5) * 2 * scale
jy = ((int(h[4:8], 16) / 65535.0) - 0.5) * 2 * scale
return jx, jy
def build_compass_plot(
records: List[Dict[str, Any]],
y_mode: str = "EKUE",
x_cutoff_mode: str = "EMPIRICAL",
show_labels: bool = True,
beamer_mode: bool = True,
highlight_code: Optional[str] = None,
title: str = "Psychometrisches 2x2-Profil (16-NCCS & IE-4)"
) -> go.Figure:
x_min, x_max = 1.0, 6.0
x_cut = NORMS["NCC_MEAN"] if x_cutoff_mode == "EMPIRICAL" else NORMS["NCC_MID"]
x_cut_label = f"Referenz M={x_cut:.2f}" if x_cutoff_mode == "EMPIRICAL" else f"Skalenmitte ({x_cut:.2f})"
if y_mode == "EKUE":
y_key = "ekue_mean"
y_min, y_max = 1.0, 5.0
y_cut = NORMS["EKUE_POP"] # 2.56
y_axis_title = "Externale Kontrollüberzeugung (EKÜ: 1 = niedrig ↔ 5 = hoch)"
y_cut_label = f"Referenz EKÜ M={y_cut:.2f}"
external_is_top = True
y_pad = 0.35
low_ext_label = "Niedrige EKÜ (unter Referenz)"
high_ext_label = "Hohe EKÜ (über Referenz)"
else:
y_key = "ikue_mean"
y_min, y_max = 1.0, 5.0
y_cut = NORMS["IKUE_POP"] # 4.12
y_axis_title = "Internale Kontrollüberzeugung (IKÜ: 1 = niedrig ↔ 5 = hoch)"
y_cut_label = f"Referenz IKÜ M={y_cut:.2f}"
external_is_top = False
y_pad = 0.35
low_ext_label = "Hohe IKÜ (über Referenz)"
high_ext_label = "Niedrige IKÜ (unter Referenz)"
x_pad = 0.35
quad_title_size = 15 if beamer_mode else 12
quad_sub_size = 12 if beamer_mode else 10
marker_size = 18 if beamer_mode else 13
label_font_size = 14 if beamer_mode else 11
axis_title_size = 16 if beamer_mode else 13
tick_font_size = 14 if beamer_mode else 12
plot_height = 760 if beamer_mode else 620
bg_alpha = 0.16 if beamer_mode else 0.10
fig = go.Figure()
if external_is_top:
ext_y0, ext_y1 = y_cut, y_max
int_y0, int_y1 = y_min, y_cut
ext_text_y = y_cut + (y_max - y_cut) * 0.55
int_text_y = y_min + (y_cut - y_min) * 0.45
else:
int_y0, int_y1 = y_cut, y_max
ext_y0, ext_y1 = y_min, y_cut
int_text_y = y_cut + (y_max - y_cut) * 0.55
ext_text_y = y_min + (y_cut - y_min) * 0.45
left_text_x = x_min + (x_cut - x_min) * 0.5
right_text_x = x_cut + (x_max - x_cut) * 0.5
# Wertneutrale akademische Farbpalette (Petrol, Indigo, Schieferblau, Violett statt Grün/Rot-Ampel)
quadrant_shapes = [
dict(type="rect", x0=x_min, x1=x_max, y0=y_min, y1=y_max,
line=dict(color="#cbd5e1", width=1), layer="below"),
dict(type="rect", x0=x_min, x1=x_cut, y0=int_y0, y1=int_y1,
fillcolor=f"rgba(13, 148, 136, {bg_alpha})", line=dict(width=0), layer="below"),
dict(type="rect", x0=x_cut, x1=x_max, y0=int_y0, y1=int_y1,
fillcolor=f"rgba(79, 70, 229, {bg_alpha})", line=dict(width=0), layer="below"),
dict(type="rect", x0=x_min, x1=x_cut, y0=ext_y0, y1=ext_y1,
fillcolor=f"rgba(2, 132, 199, {bg_alpha})", line=dict(width=0), layer="below"),
dict(type="rect", x0=x_cut, x1=x_max, y0=ext_y0, y1=ext_y1,
fillcolor=f"rgba(124, 58, 237, {bg_alpha})", line=dict(width=0), layer="below"),
dict(type="line", x0=x_cut, x1=x_cut, y0=y_min, y1=y_max,
line=dict(color="#1e293b", width=3, dash="dash")),
dict(type="line", x0=x_min, x1=x_max, y0=y_cut, y1=y_cut,
line=dict(color="#1e293b", width=3, dash="dash")),
]
fig.update_layout(shapes=quadrant_shapes)
annotations = [
dict(
x=left_text_x, y=int_text_y,
text=f"<b>Niedriges NCC & {low_ext_label}</b><br>"
f"<span style='font-size:{quad_sub_size}px'>+ Offenheit & Eigenverantwortung<br>− Extrem: Paralyse & Selbstüberforderung</span>",
showarrow=False, font=dict(size=quad_title_size, color="rgba(15, 118, 110, 0.92)"), align="center"
),
dict(
x=right_text_x, y=int_text_y,
text=f"<b>Hohes NCC & {low_ext_label}</b><br>"
f"<span style='font-size:{quad_sub_size}px'>+ Entscheidungskraft & Struktur<br>− Extrem: Vorschnelles Urteil & Kontrollillusion</span>",
showarrow=False, font=dict(size=quad_title_size, color="rgba(55, 48, 163, 0.92)"), align="center"
),
dict(
x=left_text_x, y=ext_text_y,
text=f"<b>Niedriges NCC & {high_ext_label}</b><br>"
f"<span style='font-size:{quad_sub_size}px'>+ Ambiguitätstoleranz & Systemblick<br>− Extrem: Abwarten & Aufschieben</span>",
showarrow=False, font=dict(size=quad_title_size, color="rgba(3, 105, 161, 0.92)"), align="center"
),
dict(
x=right_text_x, y=ext_text_y,
text=f"<b>Hohes NCC & {high_ext_label}</b><br>"
f"<span style='font-size:{quad_sub_size}px'>+ Regelorientierung & Kontextrealismus<br>− Extrem: Unsicherheitsstress & Passivität</span>",
showarrow=False, font=dict(size=quad_title_size, color="rgba(91, 33, 182, 0.92)"), align="center"
),
dict(
x=x_cut, y=y_max, text=f"<b> {x_cut_label} </b>",
showarrow=False, yanchor="bottom", font=dict(size=tick_font_size, color="#0f172a")
),
dict(
x=x_max, y=y_cut, text=f"<b> {y_cut_label} </b>",
showarrow=False, xanchor="right", yanchor="bottom", font=dict(size=tick_font_size, color="#0f172a")
),
]
fig.update_layout(annotations=annotations)
if records:
xs, ys, texts, hover_texts, colors, sizes = [], [], [], [], [], []
seen_coords = {}
for r in records:
raw_x = r["ncc_mean"]
raw_y = r[y_key]
coord_key = (raw_x, raw_y)
seen_coords[coord_key] = seen_coords.get(coord_key, 0) + 1
if seen_coords[coord_key] > 1:
jx, jy = _deterministic_jitter(r["code"])
plot_x = max(x_min, min(x_max, raw_x + jx))
plot_y = max(y_min, min(y_max, raw_y + jy))
else:
plot_x, plot_y = raw_x, raw_y
xs.append(plot_x)
ys.append(plot_y)
texts.append(f"<b>{r['code']}</b>" if show_labels else "")
is_highlighted = (highlight_code is not None and r["code"] == highlight_code)
colors.append("#dc2626" if is_highlighted else "#0f172a")
sizes.append(marker_size + 4 if is_highlighted else marker_size)
hover_texts.append(
f"<b>Code: {r['code']}</b><br>"
f"16-NCCS: {r['ncc_mean']:.2f} (Summe: {r['ncc_sum']})<br>"
f"IKÜ: {r['ikue_mean']:.2f} | EKÜ: {r['ekue_mean']:.2f}<extra></extra>"
)
mode = "markers+text" if show_labels else "markers"
fig.add_trace(
go.Scatter(
x=xs,
y=ys,
mode=mode,
text=texts,
textposition="top center",
cliponaxis=False,
textfont=dict(size=label_font_size, color="#0f172a", family="Arial Black, Arial, sans-serif"),
hovertemplate=hover_texts,
marker=dict(
size=sizes,
color=colors,
line=dict(width=3, color="#ffffff"),
opacity=0.96
),
showlegend=False
)
)
fig.update_layout(
title=dict(text=f"<b>{title}</b>", x=0.5, xanchor="center", font=dict(size=axis_title_size + 3, color="#0f172a")),
xaxis=dict(
title=dict(
text="<b>Need for Cognitive Closure (16-NCCS: 1.0 = niedrig ↔ 6.0 = hoch)</b>",
font=dict(size=axis_title_size, color="#0f172a")
),
range=[x_min - x_pad, x_max + x_pad],
tickvals=[1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0],
tickfont=dict(size=tick_font_size, color="#0f172a"),
gridcolor="rgba(148, 163, 184, 0.35)",
zeroline=False
),
yaxis=dict(
title=dict(
text=f"<b>{y_axis_title}</b>",
font=dict(size=axis_title_size, color="#0f172a")
),
range=[y_min - y_pad, y_max + y_pad],
tickvals=[1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0],
tickfont=dict(size=tick_font_size, color="#0f172a"),
gridcolor="rgba(148, 163, 184, 0.35)",
zeroline=False
),
plot_bgcolor="#ffffff",
paper_bgcolor="#ffffff",
height=plot_height,
margin=dict(l=80, r=45, t=75, b=85),
)
return fig
# ==============================================================================
# 5. CALLBACKS (SPRACHWECHSEL, AUSWERTUNG & DOZENTEN-ANSICHT)
# ==============================================================================
def switch_language(lang_choice: str):
lang_code = "DE"
if "English" in lang_choice:
lang_code = "EN"
elif "中文" in lang_choice:
lang_code = "ZH"
t = TRANSLATIONS[lang_code]
updates = [
lang_code,
t["lang_notice"],
t["intro"],
gr.update(label=t["code_label"], placeholder=t["code_placeholder"]),
f"{t['part_a_title']}\n{t['part_a_instr']}",
]
for i in range(16):
updates.append(
gr.update(
label=f"{i + 1}. {t['nccs_items'][i]}",
info=t["nccs_scale_hint"],
choices=t["nccs_choices"],
value=None
)
)
updates.append(f"{t['part_b_title']}\n{t['part_b_instr']}")
for i in range(4):
updates.append(
gr.update(
label=f"{i + 1}. {t['ie4_items'][i]}",
info=t["ie4_scale_hint"],
choices=t["ie4_choices"],
value=None
)
)
dl_labels = {
"DE": "📥 Persönliche Ergebnisse herunterladen (.md)",
"EN": "📥 Download Personal Results (.md)",
"ZH": "📥 下载个人评估结果 (.md)",
}
updates.append(gr.update(value=t["submit_btn"]))
updates.append(gr.update(label=dl_labels.get(lang_code, dl_labels["DE"])))
updates.append(t["sources_footer"])
return updates
def create_student_download_file(code: str, scores: Dict[str, Any], lang: str = "DE") -> str:
report_md = (
f"# 🧭 Umgang mit Ungewissheit & Kontrollüberzeugung – Ergebnisprofil ({code})\n\n"
+ generate_profile_feedback(scores, lang=lang)
)
tmp_dir = tempfile.mkdtemp()
safe_code = "".join(c for c in code if c.isalnum() or c in ("-", "_")) or "Profil"
file_path = os.path.join(tmp_dir, f"Ergebnis_{safe_code}.md")
with open(file_path, "w", encoding="utf-8") as f:
f.write(report_md)
return file_path
def process_student_submission(lang_code: str, custom_code: str, current_token: Optional[str], *answers):
t = TRANSLATIONS.get(lang_code, TRANSLATIONS["DE"])
missing_nccs = [i + 1 for i in range(16) if answers[i] is None]
missing_ie4 = [i + 1 for i in range(4) if answers[16 + i] is None]
if missing_nccs or missing_ie4:
msg_parts = [t["missing_msg"]]
if missing_nccs:
msg_parts.append(f"- **16-NCCS:** {', '.join(map(str, missing_nccs))}")
if missing_ie4:
msg_parts.append(f"- **IE-4:** {', '.join(map(str, missing_ie4))}")
return (
"\n".join(msg_parts),
gr.update(visible=False, value=None),
gr.update(visible=False, value=None),
current_token
)
nccs_raw = [int(str(a).strip()) for a in answers[:16]]
ie4_raw = [int(str(a).strip()) for a in answers[16:20]]
scores = evaluate_questionnaire(nccs_raw, ie4_raw)
desired_code = generate_anonymous_code(custom_code)
final_code = SESSION_STORE.upsert_entry(
code=desired_code,
scores=scores,
previous_code=current_token
)
code_banner = (
f"<div class='code-banner'>🎯 Code: <span>{final_code}</span></div>\n\n"
+ generate_profile_feedback(scores, lang=lang_code)
)
fig = build_compass_plot(
records=[{"code": final_code, **scores}],
y_mode="EKUE",
x_cutoff_mode="EMPIRICAL",
show_labels=True,
beamer_mode=False,
highlight_code=final_code,
title=f"Psychometrisches 2x2-Profil ({final_code})"
)
download_path = create_student_download_file(final_code, scores, lang=lang_code)
dl_labels = {
"DE": "📥 Persönliche Ergebnisse herunterladen (.md)",
"EN": "📥 Download Personal Results (.md)",
"ZH": "📥 下载个人评估结果 (.md)",
}
return (
code_banner,
gr.update(visible=True, value=fig),
gr.update(visible=True, value=download_path, label=dl_labels.get(lang_code, dl_labels["DE"])),
final_code
)
def refresh_lecturer_dashboard(
expected_count: int,
y_mode_label: str,
x_cutoff_label: str,
show_labels: bool,
beamer_mode: bool
):
records = SESSION_STORE.get_all_entries()
count = len(records)
if count > 0:
avg_ncc = sum(r["ncc_mean"] for r in records) / count
avg_ikue = sum(r["ikue_mean"] for r in records) / count
avg_ekue = sum(r["ekue_mean"] for r in records) / count
status_md = (
f"<div class='beamer-stats'>"
f"<span>👥 Antworten: <b>{count} / {int(expected_count)}</b></span>"
f"<span>Ø 16-NCCS: <b>{avg_ncc:.2f}</b> <small>(Ref: 3.30)</small></span>"
f"<span>Ø IKÜ: <b>{avg_ikue:.2f}</b> <small>(Ref: 4.32)</small></span>"
f"<span>Ø EKÜ: <b>{avg_ekue:.2f}</b> <small>(Ref: 2.56)</small></span>"
f"</div>"
)
else:
status_md = (
f"<div class='beamer-stats'>"
f"<span>👥 Eingegangene Antworten: <b>0 / {int(expected_count)}</b></span>"
f"</div>"
)
y_mode_map = {
"EKÜ: Externale Kontrollüberzeugung (1.0 bis 5.0)": "EKUE",
"IKÜ: Internale Kontrollüberzeugung (1.0 bis 5.0)": "IKUE",
}
x_cut_map = {
"Empirischer Referenzwert (M = 3.30)": "EMPIRICAL",
"Theoretische Skalenmitte (3.50)": "MID",
}
fig = build_compass_plot(
records=records,
y_mode=y_mode_map.get(y_mode_label, "EKUE"),
x_cutoff_mode=x_cut_map.get(x_cutoff_label, "EMPIRICAL"),
show_labels=show_labels,
beamer_mode=beamer_mode,
title=f"Live-Auswertung Hörsaal (n = {count})"
)
return status_md, fig
def unlock_lecturer_view(
pin: str,
expected_count: int,
y_mode_label: str,
x_cutoff_label: str,
show_labels: bool,
beamer_mode: bool
):
if not SESSION_STORE.verify_pin(pin):
return (
False,
"",
"❌ **PIN ungültig.** Bitte prüfen Sie das im Space hinterlegte Secret.",
gr.update(visible=True),
gr.update(visible=False),
gr.skip(),
gr.skip(),
)
status_md, fig = refresh_lecturer_dashboard(
expected_count, y_mode_label, x_cutoff_label, show_labels, beamer_mode
)
return (
True,
"",
"",
gr.update(visible=False),
gr.update(visible=True),
status_md,
fig,
)
def lock_lecturer_view():
return (
False,
"",
gr.update(visible=True),
gr.update(visible=False),
)
def handle_admin_reset(
is_unlocked: bool,
expected_count: int,
y_mode_label: str,
x_cutoff_label: str,
show_labels: bool,
beamer_mode: bool
):
if not is_unlocked:
return "❌ Nicht autorisiert.", gr.skip(), gr.skip()
_, msg = SESSION_STORE.clear_all(SESSION_STORE._admin_pin)
status_md, fig = refresh_lecturer_dashboard(expected_count, y_mode_label, x_cutoff_label, show_labels, beamer_mode)
return msg, status_md, fig
def handle_admin_demo(
is_unlocked: bool,
expected_count: int,
y_mode_label: str,
x_cutoff_label: str,
show_labels: bool,
beamer_mode: bool
):
if not is_unlocked:
return "❌ Nicht autorisiert.", gr.skip(), gr.skip()
_, msg = SESSION_STORE.seed_demo_data(SESSION_STORE._admin_pin, count=int(expected_count))
status_md, fig = refresh_lecturer_dashboard(expected_count, y_mode_label, x_cutoff_label, show_labels, beamer_mode)
return msg, status_md, fig
# ==============================================================================
# 6. CUSTOM CSS & GRADIO BLOCKS UI
# ==============================================================================
CUSTOM_CSS = """
.question-card {
background: #f8fafc !important;
border: 1px solid #e2e8f0 !important;
border-radius: 12px !important;
padding: 16px 20px !important;
margin-bottom: 14px !important;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03) !important;
}
.question-card > label > span {
font-size: 1.05rem !important;
font-weight: 600 !important;
color: #0f172a !important;
line-height: 1.45 !important;
}
/* Gleichmäßig breite, klar klickbare Zahlen-Buttons */
.question-card .wrap {
display: flex !important;
gap: 10px !important;
margin-top: 8px !important;
}
.question-card .wrap label {
flex: 1 1 48px !important;
max-width: 80px !important;
justify-content: center !important;
padding: 10px 12px !important;
border-radius: 8px !important;
font-weight: 700 !important;
font-size: 1.05rem !important;
cursor: pointer !important;
}
.scale-legend {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 12px;
background: #eff6ff;
border: 1px solid #bfdbfe;
color: #1e3a8a;
padding: 10px 16px;
border-radius: 10px;
font-size: 0.95rem;
margin: 8px 0 14px 0;
}
.beamer-stats {
display: flex;
flex-wrap: wrap;
gap: 24px;
align-items: center;
background: #0f172a;
color: #ffffff;
padding: 16px 24px;
border-radius: 12px;
font-size: 1.25rem;
margin-bottom: 8px;
}
.beamer-stats b {
color: #38bdf8;
font-size: 1.4rem;
}
.beamer-stats small {
color: #cbd5e1;
font-size: 0.95rem;
}
.code-banner {
background: #eff6ff;
border: 2px solid #3b82f6;
color: #1e3a8a;
padding: 14px 20px;
border-radius: 12px;
font-size: 1.35rem;
font-weight: 700;
text-align: center;
}
.code-banner span {
font-family: monospace;
font-size: 1.6rem;
background: #dbeafe;
padding: 2px 10px;
border-radius: 6px;
}
.sources-box {
margin-top: 28px !important;
padding-top: 8px !important;
color: #334155 !important;
font-size: 0.95rem !important;
line-height: 1.55 !important;
}
"""
with gr.Blocks(
title="Umgang mit Ungewissheit & Kontrollüberzeugung (16-NCCS & IE-4)",
theme=gr.themes.Soft(),
css=CUSTOM_CSS
) as demo:
gr.Markdown("# 🧭 Umgang mit Ungewissheit & Kontrollüberzeugung (`16-NCCS` & `IE-4`)")
with gr.Tabs():
# ----------------------------------------------------------------------
# TAB 1: SELBSTTEST (STUDIERENDE)
# ----------------------------------------------------------------------
with gr.TabItem("📝 1. Selbsttest"):
student_token_state = gr.State(value=None)
lang_state = gr.State(value="DE")
with gr.Row():
lang_selector = gr.Radio(
choices=[
"🇩🇪 Deutsch (Validierte Originalskala)",
"🇬🇧 English (Unvalidated translation for comprehension)",
"🇨🇳 中文 (非验证翻译,仅供参考理解)",
],
value="🇩🇪 Deutsch (Validierte Originalskala)",
label="Sprache / Language / 语言"
)
lang_notice_md = gr.Markdown("")
intro_md = gr.Markdown(TRANSLATIONS["DE"]["intro"])
custom_code_input = gr.Textbox(
label=TRANSLATIONS["DE"]["code_label"],
placeholder=TRANSLATIONS["DE"]["code_placeholder"],
max_lines=1
)
part_a_header_md = gr.Markdown(
f"{TRANSLATIONS['DE']['part_a_title']}\n{TRANSLATIONS['DE']['part_a_instr']}"
)
nccs_inputs = []
for idx, text in enumerate(TRANSLATIONS["DE"]["nccs_items"]):
rb = gr.Radio(
choices=TRANSLATIONS["DE"]["nccs_choices"],
value=None,
label=f"{idx + 1}. {text}",
info=TRANSLATIONS["DE"]["nccs_scale_hint"],
interactive=True,
elem_classes=["question-card"]
)
nccs_inputs.append(rb)
part_b_header_md = gr.Markdown(
f"{TRANSLATIONS['DE']['part_b_title']}\n{TRANSLATIONS['DE']['part_b_instr']}"
)
ie4_inputs = []
for idx, text in enumerate(TRANSLATIONS["DE"]["ie4_items"]):
rb = gr.Radio(
choices=TRANSLATIONS["DE"]["ie4_choices"],
value=None,
label=f"{idx + 1}. {text}",
info=TRANSLATIONS["DE"]["ie4_scale_hint"],
interactive=True,
elem_classes=["question-card"]
)
ie4_inputs.append(rb)
submit_btn = gr.Button(
TRANSLATIONS["DE"]["submit_btn"],
variant="primary",
size="lg"
)
feedback_output = gr.Markdown()
personal_plot_output = gr.Plot(visible=False, label="Psychometrisches 2x2-Profil")
student_download_btn = gr.DownloadButton(
label="📥 Persönliche Ergebnisse herunterladen (.md)",
visible=False,
variant="secondary",
size="lg",
)
sources_footer_md = gr.Markdown(
TRANSLATIONS["DE"]["sources_footer"],
elem_classes=["sources-box"]
)
lang_selector.change(
fn=switch_language,
inputs=[lang_selector],
outputs=[
lang_state,
lang_notice_md,
intro_md,
custom_code_input,
part_a_header_md,
*nccs_inputs,
part_b_header_md,
*ie4_inputs,
submit_btn,
student_download_btn,
sources_footer_md,
],
api_name=False
)
submit_btn.click(
fn=process_student_submission,
inputs=[lang_state, custom_code_input, student_token_state] + nccs_inputs + ie4_inputs,
outputs=[feedback_output, personal_plot_output, student_download_btn, student_token_state],
api_name=False
)
# ----------------------------------------------------------------------
# TAB 2: LIVE-AUSWERTUNG HÖRSAAL (DOZENT MIT PIN-SCHUTZ)
# ----------------------------------------------------------------------
with gr.TabItem("🖥️ 2. Live-Auswertung Hörsaal"):
lecturer_unlocked_state = gr.State(value=False)
# PIN-Sperrbildschirm (standardmäßig sichtbar)
with gr.Column(visible=True) as lecturer_login_group:
gr.Markdown(
"### 🔒 Geschützter Dozenten-Bereich (Beamer-Ansicht)\n"
"Bitte geben Sie die Dozenten-PIN ein (hinterlegt als Secret `ADMIN_PIN` bzw. `PIN` im Space), "
"um die Live-Auswertung für den Hörsaal freizuschalten."
)
with gr.Row():
lecturer_pin_input = gr.Textbox(
label="Dozenten-PIN",
type="password",
placeholder="PIN eingeben und mit Enter oder Button bestätigen...",
scale=3,
)
unlock_btn = gr.Button(
"🔓 Beamer-Ansicht freischalten",
variant="primary",
size="lg",
scale=1,
)
login_msg_output = gr.Markdown()
# Eigentliche Beamer-Ansicht (erst nach erfolgreicher PIN-Eingabe sichtbar)
with gr.Column(visible=False) as lecturer_content_group:
with gr.Row():
with gr.Column(scale=3):
lecturer_status_md = gr.Markdown(
"<div class='beamer-stats'><span>👥 Eingegangene Antworten: <b>0 / 14</b></span></div>"
)
with gr.Column(scale=1):
refresh_btn = gr.Button(
"🔄 Aktualisieren",
variant="primary",
size="lg"
)
with gr.Column(scale=1):
lock_btn = gr.Button(
"🔒 Ansicht sperren",
variant="secondary",
size="lg"
)
lecturer_plot_output = gr.Plot(
value=build_compass_plot([], beamer_mode=True, title="Live-Auswertung Hörsaal (n = 0)"),
label="Psychometrisches 2x2-Profil"
)
with gr.Accordion("⚙️ Anzeige- & Admin-Optionen", open=False):
with gr.Row():
expected_course_size = gr.Number(
value=14,
label="Kursgröße",
precision=0,
minimum=1,
maximum=200
)
show_labels_checkbox = gr.Checkbox(
value=True,
label="Teilnehmer-Codes anzeigen"
)
beamer_mode_checkbox = gr.Checkbox(
value=True,
label="Beamer-Modus (Maximaler Kontrast & große Schrift)"
)
with gr.Row():
y_mode_dropdown = gr.Dropdown(
choices=[
"EKÜ: Externale Kontrollüberzeugung (1.0 bis 5.0)",
"IKÜ: Internale Kontrollüberzeugung (1.0 bis 5.0)",
],
value="EKÜ: Externale Kontrollüberzeugung (1.0 bis 5.0)",
label="Y-Achse (IE-4 Subskala nach Kovaleva et al., 2012)"
)
x_cutoff_radio = gr.Radio(
choices=[
"Empirischer Referenzwert (M = 3.30)",
"Theoretische Skalenmitte (3.50)",
],
value="Empirischer Referenzwert (M = 3.30)",
label="Trennlinie X-Achse"
)
with gr.Row():
reset_btn = gr.Button("🗑️ Reset (Arbeitsspeicher leeren)", variant="stop", scale=1)
demo_btn = gr.Button("🧪 Demo-Daten laden", variant="secondary", scale=1)
admin_msg_output = gr.Markdown()
gr.Markdown(
TRANSLATIONS["DE"]["sources_footer"],
elem_classes=["sources-box"]
)
plot_controls = [
expected_course_size,
y_mode_dropdown,
x_cutoff_radio,
show_labels_checkbox,
beamer_mode_checkbox
]
dashboard_outputs = [
lecturer_status_md,
lecturer_plot_output,
]
unlock_outputs = [
lecturer_unlocked_state,
lecturer_pin_input,
login_msg_output,
lecturer_login_group,
lecturer_content_group,
*dashboard_outputs,
]
unlock_btn.click(
fn=unlock_lecturer_view,
inputs=[lecturer_pin_input] + plot_controls,
outputs=unlock_outputs,
api_name=False
)
lecturer_pin_input.submit(
fn=unlock_lecturer_view,
inputs=[lecturer_pin_input] + plot_controls,
outputs=unlock_outputs,
api_name=False
)
lock_btn.click(
fn=lock_lecturer_view,
inputs=[],
outputs=[
lecturer_unlocked_state,
login_msg_output,
lecturer_login_group,
lecturer_content_group,
],
api_name=False
)
refresh_btn.click(
fn=refresh_lecturer_dashboard,
inputs=plot_controls,
outputs=dashboard_outputs,
api_name=False
)
for ctrl in plot_controls:
ctrl.change(
fn=refresh_lecturer_dashboard,
inputs=plot_controls,
outputs=dashboard_outputs,
api_name=False
)
reset_btn.click(
fn=handle_admin_reset,
inputs=[lecturer_unlocked_state] + plot_controls,
outputs=[admin_msg_output] + dashboard_outputs,
api_name=False
)
demo_btn.click(
fn=handle_admin_demo,
inputs=[lecturer_unlocked_state] + plot_controls,
outputs=[admin_msg_output] + dashboard_outputs,
api_name=False
)
if __name__ == "__main__":
demo.launch()
import os
import random
import hashlib
import tempfile
import threading
from typing import Dict, List, Tuple, Any, Optional
import gradio as gr
import plotly.graph_objects as go
# ==============================================================================
# 1. SKALEN, ITEMS (DE / EN / ZH), NORMWERTE & QUELLENVERZEICHNIS
# ==============================================================================
NORMS = {
"NCC_MEAN": 3.30, # Schlink & Walther (2007), Studierendenstichprobe (SD = 0.65)
"NCC_SD": 0.65,
"NCC_MID": 3.50, # Theoretische Skalenmitte (1-6)
"IKUE_STUD": 4.32, # Kovaleva et al. (2012) - Hohe Bildung (SD = 0.66)
"IKUE_POP": 4.12, # Gesamtbevölkerung (M = 4.12, SD = 0.81)
"EKUE_STUD": 2.31, # Kovaleva et al. (2012) - Hohe Bildung (SD = 0.88)
"EKUE_POP": 2.56, # Gesamtbevölkerung (M = 2.56, SD = 0.96)
}
# Umpolung bei Item 4, 11, 12 (0-basierter Index: 3, 10, 11)
NCCS_REVERSED_INDICES = {3, 10, 11}
TRANSLATIONS: Dict[str, Dict[str, Any]] = {
"DE": {
"lang_notice": "",
"intro": "**20 kurze Aussagen** (ca. 3 Min.). **100 % anonym** (keine IPs, Cookies oder Zeitstempel).",
"code_label": "Optional: Eigenes Kurz-Pseudonym (z. B. HERMANN-07)",
"code_placeholder": "Leer lassen für automatischen Code (z. B. FALKE-42)...",
"part_a_title": "### Teil A: Umgang mit Ungewissheit (16-NCCS)",
"part_a_instr": (
"*Bitte entscheiden Sie, wie stark die nachfolgenden Sätze Ihre persönliche Einstellung, Meinung und Erfahrung ausdrücken.*\n\n"
"<div class='scale-legend'><span><b>1</b> = stimme gar nicht zu</span><span>↔</span><span><b>6</b> = stimme völlig zu</span></div>"
),
"nccs_scale_hint": "1 = stimme gar nicht zu ↔ 6 = stimme völlig zu",
"nccs_choices": ["1", "2", "3", "4", "5", "6"],
"nccs_items": [
"Ich mag es nicht, wenn die Aussage einer Person mehrdeutig ist.",
"Ich finde, nachdem ich eine Lösung für ein Problem gefunden habe, ist es Zeitverschwendung, weitere mögliche Lösungen in Betracht zu ziehen.",
"Ich mag keine unvorhersehbaren Situationen.",
"Ich finde es spannend, nicht zu wissen, was das Leben einem bringen wird.",
"Ein Problem aus verschiedenen Blickwinkeln zu betrachten, führt nur zu Verwirrung.",
"Im Allgemeinen suche ich nicht nach Alternativlösungen für Probleme, für welche ich schon eine Lösung parat habe.",
"Ich bevorzuge die Gesellschaft guter Freunde, weil ich weiß, was ich von ihnen zu erwarten habe.",
"Ich fühle mich unbehaglich, wenn ich es nicht schaffe, eine schnelle Antwort auf Probleme zu geben, denen ich gegenüberstehe.",
"Ich bevorzuge Tätigkeiten, bei denen stets klar ist, was getan und wie es getan werden muss.",
"Wenn ich ein Problem lösen muss, verschwende ich im Allgemeinen keine Zeit damit, die unterschiedlichen Standpunkte dazu zu erwägen.",
"Ich mag Aufgaben, bei denen noch unklar ist, wie der genaue Lösungsweg aussieht.",
"Ich liebe die Ungewissheit und die Überraschung, die oft im Alltäglichen steckt.",
"Jedwede Lösung eines Problems ist besser, als in einem Zustand der Ungewissheit zu verharren.",
"Ich ziehe Dinge, die ich gewohnt bin, solchen vor, die ich nicht kenne und die ich nicht vorhersagen kann.",
"Im Allgemeinen vermeide ich es, mich an Diskussionen über uneindeutige und umstrittene Themen zu beteiligen.",
"Ich bevorzuge es, mich für die erstmögliche Lösung zu entscheiden, anstelle lange darüber nachzudenken, was für eine Entscheidung ich treffen sollte.",
],
"part_b_title": "### Teil B: Kontrollüberzeugung (IE-4)",
"part_b_instr": (
"*Die folgenden Aussagen können mehr oder weniger auf Sie zutreffen. Bitte geben Sie bei jeder Aussage an, inwieweit diese auf Sie persönlich zutrifft.*\n\n"
"<div class='scale-legend'><span><b>1</b> = trifft gar nicht zu</span><span><b>2</b> = wenig</span><span><b>3</b> = etwas</span><span><b>4</b> = ziemlich</span><span><b>5</b> = trifft voll und ganz zu</span></div>"
),
"ie4_scale_hint": "1 = trifft gar nicht zu | 2 = wenig | 3 = etwas | 4 = ziemlich | 5 = trifft voll und ganz zu",
"ie4_choices": ["1", "2", "3", "4", "5"],
"ie4_items": [
"Ich habe mein Leben selbst in der Hand.",
"Wenn ich mich anstrenge, werde ich auch Erfolg haben.",
"Egal ob privat oder im Beruf: Mein Leben wird zum großen Teil von anderen bestimmt.",
"Meine Pläne werden oft vom Schicksal durchkreuzt.",
],
"submit_btn": "📊 Auswerten & persönliches Profil anzeigen",
"missing_msg": "⚠️ **Bitte beantworten Sie noch alle offenen Fragen:**",
"sources_footer": """
---
### 📚 Wissenschaftliche Quellen & Original-Referenzen
**1. Eingesetzte Messinstrumente (Fragebögen):**
* **Teil A – Need for Cognitive Closure (16-NCCS, deutsche Kurzskala; 16 Items, 6er-Skala):** Schlink, S., & Walther, E. (2007). Kurz und gut: Eine deutsche Kurzskala zur Erfassung des Bedürfnisses nach kognitiver Geschlossenheit. *Zeitschrift für Sozialpsychologie*, 38(3), 153–161. [https://doi.org/10.1024/0044-3514.38.3.153](https://doi.org/10.1024/0044-3514.38.3.153) *(Englische 16-Item-Referenzskala: Roets, A., & Van Hiel, A., 2011, Item selection and validation of a brief, 15-item version of the Need for Closure Scale. Personality and Individual Differences, 50(1), 90–94; Ursprungsskala: Webster, D. M., & Kruglanski, A. W., 1994, Individual differences in need for cognitive closure. Journal of Personality and Social Psychology, 67(6), 1049–1062).*
* **Teil B – Internale-Externale-Kontrollüberzeugung-4 (IE-4; 4 Items, 5er-Skala):** Kovaleva, A., Beierlein, C., Kemper, C. J., & Rammstedt, B. (2012). *Eine Kurzskala zur Messung von Kontrollüberzeugung: Die Skala Internale-Externale-Kontrollüberzeugung-4 (IE-4)*. GESIS-Working Papers 2012/19. Köln: GESIS – Leibniz-Institut für Sozialwissenschaften. [https://nbn-resolving.org/urn:nbn:de:0168-ssoar-312096](https://nbn-resolving.org/urn:nbn:de:0168-ssoar-312096) *(Dokumentation im ZIS – Zusammenstellung sozialwissenschaftlicher Items und Skalen: [https://doi.org/10.6102/zis184](https://doi.org/10.6102/zis184)).*
**2. Theoretisches Fundament (Lay Epistemics, Need for Closure & Locus of Control):**
* Kruglanski, A. W. (1989). *Lay epistemics and human knowledge: Cognitive and motivational bases*. New York: Plenum Press.
* Kruglanski, A. W., & Webster, D. M. (1996). Motivated closing of the mind: "Seizing" and "freezing". *Psychological Review*, 103(2), 263–283. [https://doi.org/10.1037/0033-295X.103.2.263](https://doi.org/10.1037/0033-295X.103.2.263)
* Rotter, J. B. (1966). Generalized expectancies for internal versus external control of reinforcement. *Psychological Monographs: General and Applied*, 80(1), 1–28. [https://doi.org/10.1037/h0092976](https://doi.org/10.1037/h0092976)
""",
},
"EN": {
"lang_notice": "ℹ️ *Note: The English translation is provided for comprehension only and is not psychometrically validated as a combined instrument. Norm comparisons refer to the German reference sample.*",
"intro": "**20 brief statements** (approx. 3 min.). **100% anonymous** (no IPs, cookies, or timestamps stored).",
"code_label": "Optional: Custom short pseudonym (e.g., HERMANN-07)",
"code_placeholder": "Leave blank for an automatic code (e.g., FALKE-42)...",
"part_a_title": "### Part A: Dealing with Uncertainty (16-NCCS)",
"part_a_instr": (
"*To what extent do the following statements express your personal attitude, opinion, and experience?*\n\n"
"<div class='scale-legend'><span><b>1</b> = strongly disagree</span><span>↔</span><span><b>6</b> = strongly agree</span></div>"
),
"nccs_scale_hint": "1 = strongly disagree ↔ 6 = strongly agree",
"nccs_choices": ["1", "2", "3", "4", "5", "6"],