tourism-risk
Computational core for tourism travel safety risk assessment — a pure-Python (zero-dependency) implementation of the fuzzy multi-level risk model from the intelligent analytical platform for assessing the safety risk of tourist travel (PhD thesis chapter 4 / the 2025 article, eq. 4.2–4.9).
The library computes the quantitative ($\mu_R$) and linguistic ($T_R$) safety risk assessment of a tourist region from questionnaire data, implementing the operator
$$ T_5(R, E, \Xi, \Delta, M_I, M_{R1}, M_{R2}, M_{R3}) \rightarrow Y(f_5) = {\mu_R, T_R} $$
where $R$ is the set of regions, $E$ the set of respondents (tourism participants), $\Xi(R) \in [0,1]$ the predicted level of repeat visits, $\Delta(R)$ the expert safety level of the regional tourism system, and $M_I, M_{R1}, M_{R2}, M_{R3}$ the four computation modules described below.
It is the engine behind the web platform of the same project, but has no dependency on any web layer — researchers and tourism managers can apply it directly in their own computations.
The pipeline
M_I — information module (eq. 4.2)
Each respondent rates criteria on a five-point linguistic scale $l_1$ "completely disagree" … $l_5$ "completely agree", mapped numerically as $\tau(l_k) = k$. Criteria are organised into configurable groups (article preset: $G_1$ infrastructure safety, 5 criteria $K_{11}..K_{15}$; $G_2$ social and ecological safety, 7 criteria $K_{21}..K_{27}$; $G_3$ medical safety, 5 criteria $K_{31}..K_{35}$). Per group:
$$\theta_g = \sum_{i=1}^{m_g} \tau_{gi}$$
M_R1 — individual level (eq. 4.3–4.4)
A characteristic function converts $\theta_g$ to a term level $T_1..T_5$ using boundaries that are multiples of the group size $m_g$ (default multipliers 1, 2, 3, 4):
$$ T_g = \begin{cases} T_1 & \theta_g < m_g \ T_2 & m_g \le \theta_g < 2m_g \ T_3 & 2m_g \le \theta_g < 3m_g \ T_4 & 3m_g \le \theta_g < 4m_g \ T_5 & \theta_g \ge 4m_g \end{cases} $$
Group terms are aggregated into the individual risk term $r^*(e) \in {L, BA, A, AA, H}$ (low → high risk) by ordered "If–Then" rules. Each rule is a multiset of minimum term levels; it fires iff the group terms can be assigned injectively to the slots, each term ≥ its slot level. Rules are evaluated top-down, first match wins; the default output is $H$. Article preset: $[T_5,T_4,T_4] \to L$; $[T_5,T_4,T_3] \to BA$; $[T_4,T_3,T_2] \to A$; $[T_3,T_2,T_2] \to AA$; else $H$.
M_R2 — regional level (eq. 4.5–4.7)
Risk terms map to a percentage scale $\chi$ (default $L\to15$, $BA\to30$, $A\to50$, $AA\to80$, $H\to100$) and average into the regional value
$$\delta(R) = \frac{1}{n}\sum_{j=1}^{n} \chi(e_j)$$
(optionally a weighted mean). A quadratic Z-spline with parameters $a=60$, $b=100$ produces $\varphi(R)$:
$$ \varphi(\delta) = \begin{cases} 1 & \delta \le a \ 1 - \dfrac{(\delta - a)^2}{2\left(\frac{b-a}{2}\right)^2} & a < \delta \le \frac{a+b}{2} \ \dfrac{(b - \delta)^2}{2\left(\frac{b-a}{2}\right)^2} & \frac{a+b}{2} < \delta < b \ 0 & \delta \ge b \end{cases} $$
A cone-shaped membership function combines $\varphi(R)$ with the DM-entered $\Xi(R)$ into the regional feeling-of-safety level:
$$m_S(R) = 1 - \frac{1}{2}\sqrt{(\varphi(R) - 1)^2 + (\Xi(R) - 1)^2}$$
M_R3 — national level (eq. 4.8–4.9)
With interval boundaries $a_1..a_6 = (0, 20, 40, 60, 80, 100)$ and the expert level $\Delta(R) = \Delta_k$, fuzzification gives
$$\omega(R) = a_{k+1} \cdot m_S(R)$$
and an S-shaped membership function the final quantitative estimate:
$$ \mu_R(\omega) = \begin{cases} 0 & \omega \le a_1 \ 2\left(\dfrac{\omega - a_1}{a_6 - a_1}\right)^2 & a_1 < \omega \le \frac{a_1+a_6}{2} \ 1 - 2\left(\dfrac{a_6 - \omega}{a_6 - a_1}\right)^2 & \frac{a_1+a_6}{2} < \omega < a_6 \ 1 & \omega \ge a_6 \end{cases} $$
$\mu_R$ is interpreted linguistically: $[0, 0.2) \to R_1$ very high risk, $[0.2, 0.4) \to R_2$ high, $[0.4, 0.6) \to R_3$ medium, $[0.6, 0.8) \to R_4$ low, $[0.8, 1] \to R_5$ very low risk.
Installation
PyPI publication is pending; once published:
pip install tourism-risk
or, with uv:
uv add tourism-risk
Until then, install from source (from the repository root):
uv pip install ./core # or: pip install ./core
Requires Python ≥ 3.12. No runtime dependencies.
Usage: reproducing the article's worked example
The article evaluates three control respondents and three regions ($R_1$ Zakarpattia, $R_2$ Ivano-Frankivsk, $R_3$ Lviv oblast). Individual level, end to end:
from tourism_risk import DEFAULT_CONFIG, evaluate_respondent
# Ratings in group order: K11..K15, K21..K27, K31..K35
e1 = [5, 5, 1, 1, 1] + [1] * 7 + [2, 2, 2, 1, 1]
e210 = [4, 2, 2, 2, 2] + [2, 2, 3, 2, 3, 2, 1] + [2, 3, 3, 4, 5]
e251 = [1, 1, 2, 1, 2] + [1, 1, 2, 1, 2, 3, 3] + [1, 2, 4, 2, 3]
for name, ratings in [("e1", e1), ("e210", e210), ("e251", e251)]:
result = evaluate_respondent(ratings, DEFAULT_CONFIG)
print(name, result.theta, result.group_terms, result.risk_term.name)
Expected output (matches article Table 1):
e1 (13, 7, 8) (3, 2, 2) AA
e210 (12, 15, 17) (3, 3, 4) A
e251 (7, 13, 12) (2, 2, 3) AA
Regional and national level, feeding the article's published step values:
from tourism_risk import (
DEFAULT_CONFIG, cone_membership, omega, risk_class, s_membership, z_spline,
)
# R1 Zakarpattia: published delta = 79.54, Xi = 0.85, expert level Delta_5
phi = z_spline(79.54, a=60.0, b=100.0) # eq. 4.6 -> 0.5227355
m_s = cone_membership(phi=0.5, xi=0.85) # eq. 4.7 (article carries phi rounded to 0.5)
w = omega(0.74, 5, DEFAULT_CONFIG.fuzz_boundaries) # eq. 4.8
mu = s_membership(74.0, 0.0, 100.0) # eq. 4.9
t_r = risk_class(mu, DEFAULT_CONFIG.risk_thresholds)
print(f"m_S = {m_s:.2f}, omega = {w:.0f}, mu_R = {mu:.4f}, class = {t_r.name}")
Expected output (matches the published chain $m_S=0.74$, $\omega=74$, $\mu_R=0.86$, $R_5$ — very low risk):
m_S = 0.74, omega = 74, mu_R = 0.8648, class = R5
Or evaluate a whole region in one call, with every intermediate retained:
from tourism_risk import DEFAULT_CONFIG, evaluate_region
region = evaluate_region(
respondent_ratings=[e1, e210, e251],
xi=0.85, # predicted level of repeat visits (DM input)
delta_level=5, # expert safety level Delta_5 (DM input)
config=DEFAULT_CONFIG,
)
print(region.delta) # 70.0 (eq. 4.5)
print(region.phi) # 0.875 (eq. 4.6)
print(round(region.m_s, 4)) # 0.9024 (eq. 4.7)
print(round(region.omega, 2)) # 90.24 (eq. 4.8)
print(round(region.mu, 4)) # 0.9809 (eq. 4.9)
print(region.risk_class.name) # R5 (very low risk)
Customising the model
Every constant of the model is a field of the frozen ModelConfig — criteria
groups, rule set, term boundaries, the $\chi$ scale, Z-spline parameters,
cone base/scale, interval boundaries $a_1..a_6$ and risk-class thresholds:
import dataclasses
from tourism_risk import (
DEFAULT_CONFIG, CriteriaGroup, ModelConfig, RiskTerm, Rule, RuleSet,
evaluate_region,
)
# A stricter rule set (the article's prose variant of rule 4: two groups at T3)
strict_rules = RuleSet(
rules=(
Rule(pattern=(5, 4, 4), output=RiskTerm.L),
Rule(pattern=(5, 4, 3), output=RiskTerm.BA),
Rule(pattern=(4, 3, 2), output=RiskTerm.A),
Rule(pattern=(3, 3, 2), output=RiskTerm.AA),
),
default=RiskTerm.H,
)
strict_config = dataclasses.replace(DEFAULT_CONFIG, rules=strict_rules)
# Or a fully custom model: two criteria groups and a shifted chi scale
custom_config = ModelConfig(
groups=(
CriteriaGroup(id="G1", name="Transport", criteria=("C1", "C2", "C3")),
CriteriaGroup(id="G2", name="Health", criteria=("C4", "C5", "C6", "C7")),
),
rules=RuleSet(
rules=(
Rule(pattern=(5, 4), output=RiskTerm.L),
Rule(pattern=(4, 3), output=RiskTerm.A),
),
default=RiskTerm.H,
),
chi_scale=(10.0, 30.0, 50.0, 70.0, 90.0),
zspline_a=50.0,
zspline_b=90.0,
)
result = evaluate_region(
respondent_ratings=[[5, 5, 4, 4, 4, 5, 4]],
xi=0.9,
delta_level=4,
config=custom_config,
)
print(result.risk_class.name)
Inputs are validated with clear ValueErrors: ratings must be integers 1–5
with the exact criteria count, $\Xi \in [0, 1]$, boundaries strictly
increasing, rule patterns non-empty with levels 1–5 and at most one slot per
criteria group.
Publishing (maintainers)
Build the sdist + wheel and upload to PyPI with uv (from the repo root):
uv build --project core --out-dir core/dist
uv publish core/dist/*
uv publish authenticates with a PyPI API token — set the
UV_PUBLISH_TOKEN environment variable or pass --token. Bump version in
core/pyproject.toml (and CITATION.cff) before building.
Citation
If you use this library in academic work, please cite the article:
Shafar, A. (2025). Intellectual-analytical platform for assessing the security risk of tourist travel. Scientific Journal of Ternopil National Technical University, 120(4), 78–89. https://doi.org/10.33108/visnyk_tntu2025.04.078
and the software itself (see
CITATION.cff):
Shafar, A. (2026). tourism-risk: Intelligent analytical platform for tourism travel safety risk assessment (Version 0.1.0) [Computer software].
License
MIT © 2026 Andrii Shafar
Metadata
Release files for tourism-risk 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tourism_risk-0.3.0.tar.gz | 23.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tourism_risk-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.3 kB
Release files / tourism_risk-0.3.0.tar.gz
| Download URL | tourism_risk-0.3.0.tar.gz |
|---|---|
| Size | 23.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
33556ec41b2a96616d9928893c0063c77d61bf99a98a75136b884408a5407c1a
|
|
BLAKE2b-256 checksum How to use checksums |
d1fcce06ba976654c4797ff18aaaa0dac5027543667df5c6681b3b431cd51de8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / tourism_risk-0.3.0-py3-none-any.whl
| Download URL | tourism_risk-0.3.0-py3-none-any.whl |
|---|---|
| Size | 18.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b2f42d38b8d086daa7e40de80032af421f5044dd7a97f7724005e3c08cd89ead
|
|
BLAKE2b-256 checksum How to use checksums |
5bbd6a98a3eb2edeab34c524350d17502ea188926ab6754e4db7f0764ab789fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|