Visualize your year in travel using your Google Location History (Timeline) data
This report summarizes scanner evidence for mahlernim/google-timeline-visualizer, a Kotlin project with 3,057 GitHub stars. SynthScan examined 76,651 lines of code across 524 source files, recording 132 pattern matches distributed across 8 syntactic categories. The legacy adjusted pattern signal is 3.1 (Low pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Payload schema: 1.0.0 · Engine: 2.0.0 · Scoring: heuristic-density-v1 · Scan date: 2026-10-31.
Can maintainers explain intent, assumptions, and trade-offs? 112 finding(s).
Are imports declared and package/API names verified? 12 finding(s).
Are wrappers, guards, and repeated structures necessary? 117 finding(s).
Which changes need focused human review? 132 finding(s).
This chart tracks the legacy adjusted pattern score across scan runs. Changes can reflect source changes, scan coverage, or detector/scoring versions; they do not measure AI adoption or prove that a team refactored code. Compare runs only when their scoring versions are compatible. The dashed line tracks the raw pattern-hit count.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 132 distinct pattern matches across 8 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | visualizer.py | 572 | def _parse_timeline_timestamp(value: Any) -> Optional[Tuple[datetime, bool]]: | CODE |
| LOW | visualizer.py | 594 | def _normalize_segment_direction(segments: List[Any]) -> List[Any]: | CODE |
| LOW | visualizer.py | 629 | def _without_internal_point_fields(points: List[Dict[str, Any]]) -> List[Dict[str, Any]]: | CODE |
| LOW | visualizer.py | 782 | def extract_journal_route_points( | CODE |
| LOW | visualizer.py | 1156 | def build_visual_journey_timing( | CODE |
| LOW | visualizer.py | 1229 | def calculate_overview_viewport( | CODE |
| LOW | tools/daily_metrics.py | 190 | def _github_assets_from_snapshot(snapshot: dict[str, Any] | None) -> dict[str, Any]: | CODE |
| LOW | tools/daily_metrics.py | 304 | def normalize_cloudflare_response( | STRING |
| LOW | web/src/duration.ts | 8 | export function recommendedDurationSeconds(track: CameraTrack, largeTransferCount = 0): number { | CODE |
| LOW | web/src/distance-unit.ts | 35 | export function convertDistanceFromKilometers(kilometers: number, unit: DistanceUnit): number { | CODE |
| LOW | web/src/distance-unit.ts | 47 | export function readDistanceUnitPreference(): DistanceUnitPreference { | CODE |
| LOW | web/src/distance-unit.ts | 57 | export function writeDistanceUnitPreference(preference: DistanceUnitPreference): void { | CODE |
| LOW | web/src/timeline.ts | 166 | function normalizeSegmentDirection(segments: ParsedTimelineSegment[]): ParsedTimelineSegment[] { | CODE |
| LOW | web/src/timeline.ts | 262 | export function reconcileTimelineSegments( | CODE |
| LOW | web/src/timeline.ts | 384 | export function isShortImpossibleRawSpike( | CODE |
| LOW | tests/test_parser.py | 14 | def test_journal_route_prefers_usable_detail_and_keeps_semantic_backup_outside_coverage(): | CODE |
| LOW | tests/test_parser.py | 76 | def test_ios_array_root_and_activity(): | CODE |
| LOW | tests/test_parser.py | 92 | def test_standalone_paths_are_suppressed_only_during_semantic_coverage(): | CODE |
| LOW | tests/test_parser.py | 127 | def test_path_detail_inside_the_same_semantic_segment_is_preserved(): | CODE |
| LOW | tests/test_parser.py | 139 | def test_timezone_less_activity_keeps_start_path_end_order(): | CODE |
| LOW | tests/test_parser.py | 155 | def test_timezone_less_date_line_crossing_preserves_export_order(): | CODE |
| LOW | tests/test_parser.py | 172 | def test_reverse_ordered_mixed_timezone_export_is_normalized_without_crashing(): | CODE |
| LOW | tests/test_parser.py | 193 | def test_journal_route_accepts_mixed_timezone_sources_without_crashing(): | CODE |
| LOW | tests/test_parser.py | 220 | def test_journal_timezone_less_raw_date_line_keeps_source_order(): | CODE |
| LOW | tests/test_parser.py | 241 | def test_journal_without_detail_keeps_timezone_less_semantic_source_order(): | CODE |
| LOW | tests/test_parser.py | 266 | def test_offset_path_timestamps_match_absolute_time_behavior(): | CODE |
| LOW | tests/test_parser.py | 291 | def test_invalid_offset_path_timestamps_are_ignored(): | CODE |
| LOW | tests/test_parser.py | 309 | def test_date_range_filtering(): | CODE |
| LOW | tests/test_parser.py | 336 | def test_title_template_expansion(): | CODE |
| LOW | tests/test_parser.py | 347 | def test_shared_platform_parity_fixture(): | CODE |
| LOW | tests/test_journal_lab_packaging.py | 12 | def test_journal_lab_has_a_separate_installation_identity() -> None: | CODE |
| LOW | tests/test_journal_lab_packaging.py | 31 | def test_travel_journal_is_enabled_for_production_flavors() -> None: | CODE |
| LOW | tests/test_journal_lab_packaging.py | 43 | def test_normal_validation_builds_the_journal_lab_variant() -> None: | CODE |
| LOW | tests/test_journal_lab_packaging.py | 55 | def test_lab_release_is_immutable_verified_and_coinstallable() -> None: | CODE |
| LOW | tests/test_cli_errors.py | 21 | def test_missing_input_is_rejected_by_argparse(tmp_path): | CODE |
| LOW | tests/test_cli_errors.py | 30 | def test_directory_input_is_rejected_by_argparse(tmp_path): | CODE |
| LOW | tests/test_cli_errors.py | 45 | def test_export_values_outside_android_limits_are_rejected(tmp_path, arguments): | CODE |
| LOW | tests/test_cli_errors.py | 53 | def test_malformed_json_raises_typed_error(tmp_path): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 73 | def test_missing_file_raises_typed_error_when_parser_is_called_directly(tmp_path): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 80 | def test_no_matching_year_raises_no_data_error(tmp_path): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 88 | def test_ffmpeg_availability_uses_matplotlib_configuration(monkeypatch): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 94 | def test_missing_ffmpeg_raises_typed_error(monkeypatch): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 101 | def test_ffmpeg_writer_embeds_resolved_title(monkeypatch): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 110 | def test_real_ffmpeg_output_has_requested_rate_duration_dimensions_and_title(tmp_path): | CODE |
| LOW | tests/test_cli_errors.py | 129 | def test_main_checks_ffmpeg_before_parsing(monkeypatch, tmp_path, capsys): | CODE |
| LOW | tests/test_cli_errors.py | 149 | def test_main_reports_typed_parse_failure(monkeypatch, tmp_path, capsys): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 167 | def test_start_date_month_uses_first_day(value, expected): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 177 | def test_end_date_month_uses_last_day(value, expected): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 182 | def test_custom_large_dimensions_are_accepted(tmp_path, width, height): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 198 | def test_invalid_resolved_dimensions_fail_before_rendering(tmp_path, monkeypatch, capsys, arguments, message): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 208 | def test_one_custom_axis_resolves_against_the_preset(): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 213 | def test_formats_match_shared_fixture_and_custom_pairs(): | CODE |
| LOW⚡ | tests/test_cli_errors.py | 222 | def test_hide_dates_is_opt_in(tmp_path): | CODE |
| LOW | tests/test_camera.py | 25 | def test_cli_defaults_match_android(tmp_path): | CODE |
| LOW | tests/test_camera.py | 36 | def test_cli_accepts_android_export_limits(tmp_path): | CODE |
| LOW | tests/test_camera.py | 48 | def test_android_aligned_cli_names_are_backward_compatible_aliases(tmp_path): | CODE |
| LOW | tests/test_camera.py | 59 | def test_selected_duration_includes_android_overview_ending(): | CODE |
| LOW | tests/test_camera.py | 66 | def test_balanced_compression_reduces_long_segments_share_without_changing_duration(): | CODE |
| LOW | tests/test_camera.py | 78 | def test_stronger_compression_reduces_share_further(): | CODE |
| LOW | tests/test_camera.py | 88 | def test_visual_pacing_gives_a_zoomed_out_transfer_less_time_than_linear_distance(): | CODE |
| 49 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| CRITICAL | …va/dev/mahlernim/timelinevisualizer/DeviceSmokeTest.kt | 63 | androidx.core.view.WindowInsetsCompat.Type.systemBars() or | CODE |
| CRITICAL | …va/dev/mahlernim/timelinevisualizer/DeviceSmokeTest.kt | 64 | androidx.core.view.WindowInsetsCompat.Type.displayCutout(), | CODE |
| CRITICAL | …va/dev/mahlernim/timelinevisualizer/DeviceSmokeTest.kt | 117 | ?.getInsets(androidx.core.view.WindowInsetsCompat.Type.systemBars())?.bottom | CODE |
| CRITICAL | …im/timelinevisualizer/export/VectorExportDeviceTest.kt | 33 | androidx.test.core.app.ActivityScenario.launch(dev.mahlernim.timelinevisualizer.MainActivity::class.java).use { | CODE |
| CRITICAL | …ernim/timelinevisualizer/render/TimelinePainterTest.kt | 546 | val projected = dev.mahlernim.timelinevisualizer.model.WebMercator.project(sample.point) | CODE |
| CRITICAL | …nim/timelinevisualizer/render/VectorBasemapRenderer.kt | 143 | org.maplibre.android.log.Logger.setVerbosity(org.maplibre.android.log.Logger.NONE) | CODE |
| CRITICAL | …nim/timelinevisualizer/render/VectorBasemapRenderer.kt | 157 | withTimeout(30_000) { dev.mahlernim.timelinevisualizer.data.VectorMapCache.prepare(app) } | CODE |
| CRITICAL | …dev/mahlernim/timelinevisualizer/export/Mp4Exporter.kt | 177 | if (useVectorBasemap) vector = dev.mahlernim.timelinevisualizer.render.VectorBasemapRenderer.create(requireN | CODE |
| CRITICAL | …dev/mahlernim/timelinevisualizer/export/Mp4Exporter.kt | 246 | vector = dev.mahlernim.timelinevisualizer.render.VectorBasemapRenderer.create(requireNotNull(context),ov | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | app/src/main/assets/geonames/countryInfo.txt | 1 | # ================================ | COMMENT |
| MEDIUM | app/src/main/assets/geonames/countryInfo.txt | 5 | # ============ | COMMENT |
| MEDIUM | app/src/main/assets/geonames/countryInfo.txt | 22 | # ============ | COMMENT |
| MEDIUM | app/src/main/assets/geonames/countryInfo.txt | 43 | # =========== | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH⚡ | tests/test_release_workflow.py | 80 | "LAB_RELEASE_TAG: ${{ inputs.release_tag || github.ref_name }}", | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | visualizer.py | 45 | print(f"Error: Missing dependency {e.name}. Please run: pip install -r requirements.txt") | CODE |
| LOW | visualizer.py | 458 | except Exception as exc: | CODE |
| LOW | visualizer.py | 1376 | except Exception as err: | CODE |
| MEDIUM | visualizer.py | 1413 | print(f"Error: {error}", file=sys.stderr) | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tools/daily_metrics.py | 4 | CODE | |
| LOW | tests/test_daily_metrics.py | 1 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | app/src/main/assets/geonames/countryInfo.txt | 1 | # ================================ | COMMENT |
| LOW | app/src/main/assets/geonames/countryInfo.txt | 21 | # Currencies : | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tools/daily_metrics.py | 196 | CODE |