Ctrl K
ComunidadGuías…

test_deepcw_lang.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# -*- coding: utf-8 -*-
"""Test suite for the CW decoder's language layer (deepcw_lang.py).

Run as a script (not pytest): python test_deepcw_lang.py
Windows: PYTHONIOENCODING=utf-8 python test_deepcw_lang.py (for Polish characters in the console).
"""
import deepcw_lang as L

passed = 0
failed = 0


def check(label, cond):
    global passed, failed
    if cond:
        passed += 1
    else:
        failed += 1
        print(f"  FAIL: {label}")


print("=== _sub_cost: Morse-close characters are cheap, Morse-far ones are expensive ===")
# E "." vs T "-" -> 1 symbol difference -> cost 1 (like the old flat distance)
check("E<->T cost 1 (morse-close)", L._sub_cost("E", "T") == 1)
# O "---" vs T "-" -> 2 extra dashes -> cost 2 (previously flat=1, now costs more)
check("O<->T cost >1 (morse-far)", L._sub_cost("O", "T") > 1)
check("same char with itself cost 0", L._sub_cost("K", "K") == 0)

print("\n=== _best_match: a morse-close mistake still gets corrected ===")
# TNX: T=- N=-. X=-..-. E<->T mistake (close) -> "ENX" should match "TNX"
check("ENX -> TNX (E/T close)", L._best_match("ENX", {"TNX"}, max_dist=1) == "TNX")

print("\n=== _best_match: a morse-far mistake is NOT corrected (regression guard) ===")
# ONX: O is morse-far from T (cost 2 > max_dist=1) -> should not match
check("ONX -> None (O/T far, max_dist=1)", L._best_match("ONX", {"TNX"}, max_dist=1) is None)

print("\n=== correct(): existing behavior, no regressions ===")
check("RST passthrough 599", L.correct("TNX 599 GL") == "TNX 599 GL")
check("cut numbers 5NN -> 5NN (already a sensible report)", L.correct("UR 5NN") == "UR 5NN")
check("segmentation TKSFER -> TKS FER", L.correct("TKSFER") == "TKS FER")
check("name after NAME — example from the file header (KEITH -> KEHTHA)",
      L.correct("NAME KEHTHA") == "NAME KEITH")
check("unknown garbage passes through unchanged", L.correct("XQZVWK") == "XQZVWK")

print("\n=== correct(): known_calls — close mistake corrected, far one is not ===")
calls = {"SQ3MZM"}
# Are Q<->0 morse-close? Q=--.- O=--- are different, let's check another scenario:
# a single close letter within SQ3MZM, e.g. M<->N (-- vs -.), cost 1
res_close = L.correct("SQ3MZN", known_calls=calls)
check("SQ3MZN -> SQ3MZM (N/M close, cost 1)", res_close == "SQ3MZM")

print("\n=== correct(): extended vocabulary (Q-codes, BT prosign, new names) ===")
check("BT (prosign) passes through unchanged", "BT" in L.correct("DE SQ3MZM BT TEST").split())
check("QSK (new Q-code) recognized", "QSK" in L.correct("QSK PSE").split())
check("PIOTRA -> PIOTR (new name, close mistake)", L.correct("NAME PIOTRA") == "NAME PIOTR")

print("\n=== _segment(): a known callsign and an RST report glued together ===")
check("DESQ3MZM -> DE SQ3MZM (known callsign glued to a word)",
      L.correct("DESQ3MZM", known_calls={"SQ3MZM"}) == "DE SQ3MZM")
check("without known_calls the token is left unchanged (we don't guess)",
      L.correct("DESQ3MZM") == "DESQ3MZM")
check("5NNTU -> 5NN TU (report glued to a word)", L.correct("5NNTU") == "5NN TU")

print("\n=== _segment(): PARTIAL coverage (real-world report) ===")
# Reported case: 'TNX FER QSO 73' got glued into one string with a garbage
# tail 'TFE' stuck on (end of transmission / noise). Previously, WHOLE
# matching required full coverage -> the whole string stayed unreadable
# without spaces.
check("TNXFERQSO73TFE -> partially recovered into sensible words",
      L.correct("TNXFERQSO73TFE") == "TNX FER QSO 73 TFE")
check("mostly garbage (below 50% recognized) is NOT split",
      L.correct("XQZVWKTU") == "XQZVWKTU")

print("\n=== '44' — a POTA/SOTA greeting/sign-off, like 73/88 ===")
check("44 passes through unchanged as a separate token", L.correct("TU 44") == "TU 44")
check("GM5NN44TU -> GM 5NN 44 TU (44 recognized, not treated as an 'island')",
      L.correct("GM5NN44TU") == "GM 5NN 44 TU")

print("\n=== _reglue_split_call: a callsign broken up by false gaps ===")
# Reported case: 'CQ CQ DE HB9TWX PSE K' had the decoder split the callsign
# into individual letters ('H B 9 T WX'). Without known_calls we only glue
# it partially based on shape (we don't guess that WX is the tail of the
# callsign rather than a real word); with known_calls (the callsign seen
# e.g. on the cluster/FT8/log) we glue it back together fully.
check("without known_calls: partial glue based on shape, WX stays separate",
      L.correct("H B 9 T WX") == "HB9T WX")
check("with known_calls: full glue into the known callsign",
      L.correct("H B 9 T WX", known_calls={"HB9TWX"}) == "HB9TWX")
check("real words (CQ/DE/PSE/K) are not swallowed by the gluing",
      L.correct("CQ CQ DE H B 9 T WX PSE K", known_calls={"HB9TWX"})
      == "CQ CQ DE HB9TWX PSE K")
check("a single short real word next to a digit is not glued nonsensically",
      L.correct("TU K") == "TU K")

print(f"\n{'='*50}")
if failed == 0:
    print(f"  RESULT: {passed}/{passed} - ALL OK")
else:
    print(f"  RESULT: {passed}/{passed+failed} - {failed} FAILURES")
print("="*50)