Skip to main content

Computational core for tourism travel safety risk assessment (fuzzy multi-level model M_I -> M_R1 -> M_R2 -> M_R3)

Project description

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

Project details


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.1.1.dev18.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.1.1.dev18-py3-none-any.whl (19.0 kB view details)

Uploaded Python 3

File details

Details for the file tourism_risk-0.1.1.dev18.tar.gz.

File metadata

  • Download URL: tourism_risk-0.1.1.dev18.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.1.1.dev18.tar.gz
Algorithm Hash digest
SHA256 58158bf72d15b6875465763921b094786787f51584992bdfde883985fe923a55
MD5 0913be5dbd8e8f0d74b0debce8adfaa0
BLAKE2b-256 195b61d83a5cb798d00b3af92ddeaad152b85e17c0752c610894b1e2a4a8689f

See more details on using hashes here.

File details

Details for the file tourism_risk-0.1.1.dev18-py3-none-any.whl.

File metadata

  • Download URL: tourism_risk-0.1.1.dev18-py3-none-any.whl
  • Upload date:
  • Size: 19.0 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.1.1.dev18-py3-none-any.whl
Algorithm Hash digest
SHA256 847fa5837fbef8db9ca13d4a8f2c6fc7be1a701af1c1c436758cf0b170077ad9
MD5 8051736cac3dcda8bafa1132957d8bca
BLAKE2b-256 5f6a55c6e2edb6888729aeb7b7dd042763bd7c45b06f15f7af3d8967d60dca80

See more details on using hashes here.

Supported by

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