This file provides context for AI coding assistants working on this codebase.
Full developer documentation: docs/developer_guidelines.md
src/sysdiagnose/
├── parsers/ # Parse raw sysdiagnose files into structured data
├── analysers/ # Process parsed data to produce insights
└── utils/
├── base.py # BaseParserInterface, BaseAnalyserInterface, BaseInterface
└── summary.py # ResultSummary, ResultSummaryExecutionHandler, ResultSummaryFactory
tests/ # Unit tests (one per parser/analyser)
docs/ # Documentation
- Parsers extend
BaseParserInterface, analysers extendBaseAnalyserInterface - Constructor signature:
def __init__(self, config: SysdiagnoseConfig, case: dict)caseis the full case metadata dict (containscase_id,ios_version,model, etc.)self.case_idis derived automatically fromcase.get("case_id")
- Only override
execute()andget_log_files()(parsers) — the base class handles I/O, caching, and summary tracking - Override
_write_result()only for custom output formats (CSV, GPX, KML) - Override
_load_output()only for multi-file parsers
- Declare
ios_version = ">=17.0"(PEP 440 specifier) on a parser/analyser class to restrict it to specific iOS versions - Default is
ios_version = "*"(all versions) _execute_and_write()automatically skips incompatible versions withExecutionStatus.SKIPPED- Use
self.is_compatible()to check programmatically - The
test_parsers_filestructureandtest_analysers_filestructuretests validate that allios_versionvalues are valid PEP 440 specifiers
- Parsers:
execute()must guard against missing files: checkget_log_files()first,logger.warning()+ return empty if none found - Parsers: never let
IndexErrorpropagate fromget_log_files()[0] - Never raise exceptions for expected conditions (missing files, empty data, unsupported iOS versions)
- Use
logger(notprint()) — warnings/errors are captured byResultSummaryExecutionHandler print()is enforced by ruff ruleT201— only CLI entry points are exempt- Return type must match
format: json→dict/list, jsonl→list[dict] or Generator[dict], custom→str - For jsonl, use
Eventdataclass and returnevent.to_dict() - For large datasets (jsonl only), return a Generator to enable lazy streaming
- Analysers instantiate parsers using
ParserClass(self.config, self.case)to pass through case metadata
- Use
self.subTest(case_id=case_id, ios_version=_case.get('ios_version'))when iterating over multiple cases so each case is reported independently - Check compatibility first:
if not p.is_compatible(): self.skipTest(...) - Parsers: then check for log files:
if not files: self.fail(...) - Use
self.assert_has_required_fields_jsonl(item)for jsonl validation - Use
self.assert_result_summary_consistent(instance, result)to validate summary matches output and fail on execution errors - Use
self.assert_result_summary_consistent(instance, result, allow_errors=True)for parsers with known upstream issues, but that is not recommended as it will spam the user when using the CLI - Call
save_result(force=True)to ensure fresh execution - Test execution order: parser tests run before analyser tests (configured in
tests/conftest.py) so analysers benefit from cached parsed data
save_result() → _execute_and_write() → [is_compatible() check] → execute() + _write_result() + ResultSummaryExecutionHandler
If incompatible: _execute_and_write() returns empty result with ExecutionStatus.SKIPPED.
Outliers (custom I/O) use: execute_with_result_summary() → execute() + summary (no file write)
The persisted summary file (<module>.summary.json) has two purposes:
- Trust signal for cached data. When loading from cache (CLI or API), the summary provides a glimpse of data quality (event count, errors, warnings, timing) without re-executing.
- Staleness detection (future). The
start_timefield enables identifying outdated cached results — relevant for analysers depending on previously parsed data, since CLI logs only capture the current run.
- Override
save_result()unless absolutely necessary - Catch broad
ExceptionorIndexErrorto mask missing files - Return
{"error": "..."}for missing files — uselogger.warning()+ empty return instead - Use
print()for diagnostics — ruffT201will reject it - Hold large datasets in memory when a Generator suffices
- Use
case_iddirectly in constructors — always pass the fullcasedict