Compare commits

...

2 commits

Author SHA1 Message Date
KoenigMjr 3e951c86ca
Merge 475e4bc5b3 into 22f1b7dc29 2025-12-03 21:29:08 +00:00
KoenigMjr 475e4bc5b3 CSV-Cleaning, new matching strategy, new Debug-message, updating docu
- csv-data sets will now be corrected, no matter if the "for"-value is: 1234567, "1234567" or '1234567'
- depending on "isregex" value in csv first exact matches, second regex matches. So it is safe that if double matches occur, exact matches always wins.
- debug for CSV row read implemented
- updating readme to newest development
2025-12-03 22:28:08 +01:00
2 changed files with 72 additions and 45 deletions

View file

@ -2,9 +2,9 @@
--- ---
## Beschreibung ## Beschreibung
Mit diesem Modul können einem Alarmpaket beliebige Beschreibungen in Abhänigkeit der enthaltenen Informationen hinzugefügt werden. Mit diesem Modul können einem Alarmpaket beliebige Beschreibungen in Abhängigkeit der enthaltenen Informationen hinzugefügt werden.
## Unterstütze Alarmtypen ## Unterstützte Alarmtypen
- Fms - Fms
- Pocsag - Pocsag
- Zvei - Zvei
@ -16,12 +16,12 @@ Mit diesem Modul können einem Alarmpaket beliebige Beschreibungen in Abhänigke
## Konfiguration ## Konfiguration
Informationen zum Aufbau eines [BOSWatch Pakets](../develop/packet.md) Informationen zum Aufbau eines [BOSWatch Pakets](../develop/packet.md)
**Achtung:** Zahlen welche führende Nullen entahlten müssen in Anführungszeichen gesetzt werden Bsp. `'0012345'` **Achtung:** Zahlen, die führende Nullen enthalten, müssen in der YAML-Konfiguration in Anführungszeichen gesetzt werden, z.B. `'0012345'`. In CSV-Dateien ist dies nicht zwingend erforderlich.
|Feld|Beschreibung|Default| |Feld|Beschreibung|Default|
|----|------------|-------| |----|------------|-------|
|scanField|Feld des BW Pakets welches geprüft werden soll|| |scanField|Feld des BW Pakets, welches geprüft werden soll||
|descrField|Name des Feldes im BW Paket in welchem die Beschreibung gespeichert werden soll|| |descrField|Name des Feldes im BW Paket, in welchem die Beschreibung gespeichert werden soll||
|wildcard|Optional: Es kann für das angelegte `descrField` automatisch ein Wildcard registriert werden|None| |wildcard|Optional: Es kann für das angelegte `descrField` automatisch ein Wildcard registriert werden|None|
|descriptions|Liste der Beschreibungen|| |descriptions|Liste der Beschreibungen||
|csvPath|Pfad der CSV-Datei (relativ zum Projektverzeichnis)|| |csvPath|Pfad der CSV-Datei (relativ zum Projektverzeichnis)||
@ -29,9 +29,9 @@ Informationen zum Aufbau eines [BOSWatch Pakets](../develop/packet.md)
#### `descriptions:` #### `descriptions:`
|Feld|Beschreibung|Default| |Feld|Beschreibung|Default|
|----|------------|-------| |----|------------|-------|
|for|Inhalt im `scanField` auf welchem geprüft werden soll|| |for|Wert im `scanField`, der geprüft werden soll||
|add|Beschreibungstext welcher im `descrField` hinterlegt werden soll|| |add|Beschreibungstext, der im `descrField` hinterlegt werden soll||
|isRegex|Muss explizit auf `true` gesetzt werden, falls RegEx verwendet wird|false| |isRegex|Muss explizit auf `true` gesetzt werden, wenn RegEx verwendet wird|false|
**Beispiel:** **Beispiel:**
```yaml ```yaml
@ -88,20 +88,25 @@ Eine neue CSV-Datei (z. B. `descriptions_tone.csv`) hat folgendes Format:
for,add,isRegex for,add,isRegex
11111,KBI Landkreis Z,false 11111,KBI Landkreis Z,false
12345,FF A-Dorf,false 12345,FF A-Dorf,false
23456,FF B-Dorf,false 23456,FF B-Dorf
^3456[0-9]$,FF Grossdorf, true ^3456[0-9]$,FF Grossdorf,true
``` ```
In der Spalte isRegex kann **zusätzlich** angegeben werden, ob der Wert in for als regulärer Ausdruck interpretiert werden soll (true/false). Standardmäßig ist `false`. **Hinweis:** In CSV-Dateien müssen Werte mit führenden Nullen **nicht** in Anführungszeichen gesetzt werden (können aber, falls gewünscht). Die Spalte `isRegex` gibt an, ob der Wert in `for` als regulärer Ausdruck interpretiert werden soll (true/false). Falls kein Wert angegeben ist, ist die Paarung standardmäßig `false`, wie z.B. beim Eintrag der FF B-Dorf im obigen Beispiel.
Wenn `isRegex` auf `true` gesetzt ist, wird der Wert aus `for` als regulärer Ausdruck ausgewertet.
### Kombination von YAML- und CSV-Konfiguration ### Kombination von YAML- und CSV-Konfiguration
Beide Varianten können parallel genutzt werden. In diesem Fall werden zuerst die Beschreibungen aus der YAML-Konfiguration und zusätzlich die Beschreibungen aus der angegebenen CSV-Datei geladen. Beide Varianten können parallel genutzt werden. In diesem Fall werden die Beschreibungen aus der YAML-Konfiguration und aus der angegebenen CSV-Datei in einer gemeinsamen Datenbank zusammengeführt.
**Beispiel** #### Matching-Reihenfolge und Priorität
``` Das Modul wendet folgende Prioritäten beim Matching an:
1. **Exakte Matches** (aus YAML und CSV) werden zuerst geprüft
2. **Regex-Matches** (aus YAML und CSV) werden nur geprüft, wenn kein exakter Match gefunden wurde
**Beispiel für Kombination:**
```yaml
- type: module - type: module
res: descriptor res: descriptor
config: config:
@ -110,12 +115,15 @@ Beide Varianten können parallel genutzt werden. In diesem Fall werden zuerst di
wildcard: "{DESCR}" wildcard: "{DESCR}"
descriptions: descriptions:
- for: 12345 - for: 12345
add: FF YAML-Test add: FF YAML-Test (exakt)
- for: '05678' # führende Nullen in '' ! - for: '123.*'
add: FF YAML-Nullen add: FF YAML-Regex
isRegex: true
csvPath: "config/descriptions_tone.csv" csvPath: "config/descriptions_tone.csv"
``` ```
Bei einem `scanField`-Wert von `12345` wird **immer** "FF YAML-Test (exakt)" verwendet, auch wenn der Regex ebenfalls zutreffen würde. Regex-Matches werden nur verwendet, wenn kein exakter Match gefunden wurde - unabhängig davon, ob die Einträge aus YAML oder CSV stammen.
--- ---
## Modul Abhängigkeiten ## Modul Abhängigkeiten
- keine - keine

View file

@ -10,7 +10,7 @@ r"""!
by Bastian Schroll by Bastian Schroll
@file: descriptor.py @file: descriptor.py
@date: 04.08.2025 @date: 03.12.2025
@author: Bastian Schroll @author: Bastian Schroll
@description: Module to add descriptions to bwPackets with CSV and Regex support @description: Module to add descriptions to bwPackets with CSV and Regex support
""" """
@ -89,11 +89,15 @@ class BoswatchModule(ModuleBase):
reader = csv.DictReader(csvfile) reader = csv.DictReader(csvfile)
for row in reader: for row in reader:
# Set default values if columns are missing # Set default values if columns are missing
raw_for = str(row.get('for', '')).strip()
# Remove enclosing quotes
clean_for = raw_for.strip().strip('"').strip("'")
entry = { entry = {
'for': str(row.get('for', '')), 'for': clean_for,
'add': row.get('add', ''), 'add': row.get('add', '').strip(),
'isRegex': row.get('isRegex', 'false').lower() == 'true' # Default: False 'isRegex': row.get('isRegex', 'false').lower() == 'true' # Default: False
} }
logging.debug("CSV row read: %s", row)
self.unified_cache[descriptor_key].append(entry) self.unified_cache[descriptor_key].append(entry)
csv_count += 1 csv_count += 1
@ -103,41 +107,56 @@ class BoswatchModule(ModuleBase):
logging.error("Error loading CSV file %s: %s", csv_path, str(e)) logging.error("Error loading CSV file %s: %s", csv_path, str(e))
def _find_description(self, descriptor_key, scan_value, bw_packet): def _find_description(self, descriptor_key, scan_value, bw_packet):
r"""!Find matching description for a scan value with Regex group support.""" r"""!Find matching description for a scan value with Regex group support.
The search is performed in two passes for performance optimization:
1. First pass: Check for exact string matches (fast, no regex compilation)
2. Second pass: Check regex patterns only if no exact match was found
Regex patterns support capture groups that can be referenced in the description
using standard regex backreferences (\1, \2, etc.) via match.expand().
Example:
Pattern: r"(\d{7})"
Input: "1234567"
Description template: "RIC: \1"
Result: "RIC: 1234567"
@param descriptor_key: Cache key identifying the descriptor configuration
@param scan_value: Value to search for in the descriptor cache
@param bw_packet: BOSWatch packet for wildcard replacement
@return: Matched description string or None if no match found
"""
descriptions = self.unified_cache.get(descriptor_key, []) descriptions = self.unified_cache.get(descriptor_key, [])
scan_value_str = str(scan_value) scan_value_str = str(scan_value).strip()
# Search for matching description # First pass: Search for exact matches (performance optimization)
# Exact matches are checked first because they don't require regex compilation
for desc in descriptions: for desc in descriptions:
description_text = desc.get('add', '') if not desc.get('isRegex', False):
match_pattern = desc.get('for', '') if desc['for'] == scan_value_str:
is_regex = desc.get('isRegex', False) description_text = desc.get('add', '')
final_description = self._replace_wildcards(description_text, bw_packet)
return final_description
if is_regex: # Second pass: Search for regex matches
# Regex matching # Only executed if no exact match was found in the first pass
for desc in descriptions:
if desc.get('isRegex', False):
match_pattern = desc.get('for', '')
try: try:
match = re.search(match_pattern, scan_value_str) match = re.search(match_pattern, scan_value_str)
if match: if match:
# Expand regex groups (\1, \2) in the description description_text = desc.get('add', '')
# match.expand() replaces backreferences (\1, \2, etc.) with captured groups
# Example: pattern="(\d+)-(\d+)", input="123-456", template="First: \1, Second: \2"
# result="First: 123, Second: 456"
expanded_description = match.expand(description_text) expanded_description = match.expand(description_text)
# Replace standard wildcards like {TONE}
final_description = self._replace_wildcards(expanded_description, bw_packet) final_description = self._replace_wildcards(expanded_description, bw_packet)
logging.debug("Regex match '%s' -> '%s' for descriptor '%s'",
match_pattern, final_description, descriptor_key)
return final_description return final_description
except re.error as e: except re.error as e:
logging.error("Invalid regex pattern '%s': %s", match_pattern, str(e)) logging.error("Invalid regex pattern '%s': %s", match_pattern, e)
continue
else:
# Exact match
if match_pattern == scan_value_str:
# Replace standard wildcards like {TONE}
final_description = self._replace_wildcards(description_text, bw_packet)
logging.debug("Exact match '%s' -> '%s' for descriptor '%s'",
match_pattern, final_description, descriptor_key)
return final_description
return None return None