mirror of
https://github.com/BOSWatch/BW3-Core.git
synced 2025-12-06 07:12:04 +01:00
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
This commit is contained in:
parent
1a721f4258
commit
475e4bc5b3
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
if not desc.get('isRegex', False):
|
||||||
|
if desc['for'] == scan_value_str:
|
||||||
description_text = desc.get('add', '')
|
description_text = desc.get('add', '')
|
||||||
match_pattern = desc.get('for', '')
|
final_description = self._replace_wildcards(description_text, bw_packet)
|
||||||
is_regex = desc.get('isRegex', False)
|
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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue