feat(workflow): publish verified forecast controls and consolidated docs
Tests / test (push) Successful in 1m1s
Tests / test (push) Successful in 1m1s
Approved by Daniel Haefliger for develop and beta. Author dh_Agent, authenticated account dh. Preserve published battery, charging and overall Energy Pie changes. No deployment or plant control authorization.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""Check literal Symcon registrations and reviewed dynamic families against READMEs.
|
||||
|
||||
This is a documentation-presence check, not a PHP evaluator or hardware test.
|
||||
Only repository sources are read; no credentials, runtime state or device APIs.
|
||||
"""
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
MODULES = {
|
||||
'Manager': 'Manager', 'Batterie': 'Batterie',
|
||||
'Warmwassererwaermer': 'Wassererwaermer', 'Pufferspeicher': 'Pufferspeicher',
|
||||
'VerbraucherEinStufig': 'Verbraucher-1-Stufig', 'Waermepumpe': 'Waermepumpe',
|
||||
'LadestationStandAlone': 'Ladestation-Stand-Alone',
|
||||
'LadestationGateway': 'Ladestation-Gateway', 'EaseeGateway': 'Easee-Gateway',
|
||||
'Energiediagramm': 'Energiediagramm', 'CC100Hardware': 'CC100-Hardware',
|
||||
'ShellyModul': 'Shelly-Modul', 'Verbrauchskostenreport': 'Verbrauchskostenreport',
|
||||
'VirtuelleBatterie': 'Virtuelle-Batterie', 'VGTSchnittstelle': 'VGT-Schnittstelle',
|
||||
}
|
||||
DYNAMIC = {
|
||||
'Energiediagramm': ['FarbeEigenverbrauchRGB', 'FarbeAutarkieRGB',
|
||||
'FarbeHintergrundRGB', 'FarbeHintergrundAkzent1RGB', 'FarbeHintergrundAkzent2RGB'],
|
||||
'Manager': ['PVLeistungArchiv', 'HausverbrauchLeistungArchiv', 'NetzleistungArchiv',
|
||||
'BatterieleistungArchiv', 'SDLLeistungArchiv', 'PVEnergie', 'Hausenergie',
|
||||
'NetzbezugEnergie', 'EinspeisungEnergie', 'BatterieLadenEnergie',
|
||||
'BatterieEntladenEnergie', 'SDLLadenEnergie', 'SDLEntladenEnergie',
|
||||
'EnergieflussPV', 'EnergieflussNetz', 'EnergieflussHaus', 'EnergieflussBatterie',
|
||||
'EnergieflussSDL', 'EnergieflussIst_', 'EnergieflussSoll_', 'DiagrammHausLeistung',
|
||||
'DiagrammHausEnergie', 'DiagrammPVLeistung_', 'DiagrammPVEnergie_',
|
||||
'DiagrammBatterieLeistung_', 'DiagrammBatterieLaden_', 'DiagrammBatterieEntladen_',
|
||||
'DiagrammVerbraucherLeistung_', 'DiagrammVerbraucherEnergie_'],
|
||||
'CC100Hardware': ['DI1', 'DI8', 'DO1', 'DO4', 'AI1', 'AI2', 'AO1', 'AO2', 'PT1', 'PT2'],
|
||||
'ShellyModul': ['Typabbildung', 'Hash', 'switch:0.aenergy.total', 'Online', 'Switch.Set'],
|
||||
}
|
||||
|
||||
|
||||
def sources(path, seen=None):
|
||||
seen = set() if seen is None else seen
|
||||
path = path.resolve()
|
||||
if path in seen:
|
||||
return
|
||||
seen.add(path)
|
||||
text = path.read_text(encoding='utf-8')
|
||||
yield text
|
||||
for match in re.finditer(r"require_once\s+__DIR__\s*\.\s*['\"]([^'\"]+)['\"]", text):
|
||||
target = (path.parent / match[1].lstrip('/')).resolve()
|
||||
if ROOT in target.parents and target.is_file() and 'vendor' not in target.parts:
|
||||
yield from sources(target, seen)
|
||||
|
||||
|
||||
def main():
|
||||
modules = sorted(ROOT.glob('*/module.php'))
|
||||
errors, checked, dynamic = [], 0, 0
|
||||
for source in modules:
|
||||
name = source.parent.name
|
||||
if name not in MODULES:
|
||||
errors.append(name + ': missing documentation mapping')
|
||||
continue
|
||||
document = ROOT / 'docs/module' / MODULES[name] / 'README.md'
|
||||
if not document.is_file() or not (source.parent / 'README.md').is_file():
|
||||
errors.append(name + ': README missing')
|
||||
continue
|
||||
text = document.read_text(encoding='utf-8')
|
||||
shared = ROOT / 'docs/Schnittstelle.md'
|
||||
if name not in ['Manager', 'EaseeGateway'] and shared.exists():
|
||||
text += shared.read_text(encoding='utf-8')
|
||||
fields = set()
|
||||
for code in sources(source):
|
||||
fields.update(re.findall(r"->Register(Property|Variable)(?:Boolean|Integer|Float|String)\s*\(\s*['\"]([^'\"]+)['\"]", code))
|
||||
for kind, ident in sorted(fields):
|
||||
checked += 1
|
||||
if ident not in text:
|
||||
errors.append(name + ': undocumented ' + kind + ' ' + ident)
|
||||
for ident in DYNAMIC.get(name, []):
|
||||
dynamic += 1
|
||||
if ident not in text:
|
||||
errors.append(name + ': undocumented dynamic family ' + ident)
|
||||
if not modules:
|
||||
errors.append('No installable module sources found')
|
||||
for error in errors:
|
||||
print(error)
|
||||
print(f'{len(modules)} modules; {checked} literal registrations; {dynamic} dynamic markers; {len(errors)} gaps')
|
||||
return bool(errors)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user