Skip to main content

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tourism_risk-0.3.0.tar.gz (23.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tourism_risk-0.3.0-py3-none-any.whl (18.9 kB view details)

Uploaded Python 3

File details

Details for the file tourism_risk-0.3.0.tar.gz.

File metadata

  • Download URL: tourism_risk-0.3.0.tar.gz
  • Upload date:
  • Size: 23.4 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for tourism_risk-0.3.0.tar.gz
Algorithm Hash digest
SHA256 33556ec41b2a96616d9928893c0063c77d61bf99a98a75136b884408a5407c1a
MD5 876e11c9f186e97175d820f58ff878ff
BLAKE2b-256 d1fcce06ba976654c4797ff18aaaa0dac5027543667df5c6681b3b431cd51de8

See more details on using hashes here.

File details

Details for the file tourism_risk-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: tourism_risk-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 18.9 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for tourism_risk-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b2f42d38b8d086daa7e40de80032af421f5044dd7a97f7724005e3c08cd89ead
MD5 283734c057bc22a6a8104b7e868d5de0
BLAKE2b-256 5bbd6a98a3eb2edeab34c524350d17502ea188926ab6754e4db7f0764ab789fa

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page