Bird frame for Raspberry Pi or your homelab - real-time bird detection by audio, fully local AI, rendered as real, hand-cut 1800s bird illustrations. On an e-ink panel, a TV, or any screen.
This report summarizes scanner evidence for arnegiacomo/fugleramme, a Python project with 3,613 GitHub stars. SynthScan examined 52,199 lines of code across 150 source files, recording 520 pattern matches distributed across 10 syntactic categories. The legacy adjusted pattern signal is 12.4 (Moderate 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.
Can maintainers explain intent, assumptions, and trade-offs? 433 finding(s).
Are imports declared and package/API names verified? 76 finding(s).
Are wrappers, guards, and repeated structures necessary? 429 finding(s).
Which changes need focused human review? 520 finding(s).
Run additional scans to build a history of the legacy pattern signal. Score changes are not a measure of AI adoption or code quality.
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 520 distinct pattern matches across 10 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 | tests/test_fake.py | 59 | def test_summary_windows_the_range_and_drops_false_positives(api): | CODE |
| LOW | tests/test_fake.py | 81 | def test_summary_limit_keeps_the_busiest(api): | CODE |
| LOW | tests/test_fake.py | 88 | def test_recent_is_newest_first_and_respects_limit(api): | CODE |
| LOW | tests/test_fake.py | 100 | def test_only_listed_locales_with_a_dictionary_answer(api): | CODE |
| LOW | tests/test_fake.py | 112 | def test_a_dictionary_revalidates_by_etag(api): | CODE |
| LOW | tests/test_fake.py | 123 | def test_auth_gates_everything_until_login(api): | CODE |
| LOW | tests/test_fake.py | 155 | def test_every_language_adds_a_dictionary_per_script_and_upstream_s_shape_does_not(api): | CODE |
| LOW | tests/test_artwork_names.py | 104 | def test_every_artwork_name_is_a_birdnet_label_or_exception(): | CODE |
| LOW | tests/test_artwork_names.py | 120 | def test_every_detectable_plate_has_a_body_mass(): | CODE |
| LOW | tests/test_artwork_names.py | 137 | def test_every_bird_box_names_a_plate_that_exists(): | CODE |
| LOW | tests/test_artwork_names.py | 151 | def test_every_shipped_bird_has_a_box(): | CODE |
| LOW | tests/test_artwork_names.py | 170 | def test_every_bird_box_is_well_formed(): | CODE |
| LOW | tests/test_artwork_names.py | 197 | def test_every_bird_box_was_drawn_on_the_plate_it_is_filed_under(library): | CODE |
| LOW | tests/test_artwork_names.py | 218 | def test_every_artwork_image_has_attribution(): | CODE |
| LOW | tests/test_artwork_names.py | 232 | def test_every_manifest_source_is_named_in_attribution(): | CODE |
| LOW | tests/test_artwork_names.py | 268 | def test_every_bird_is_cut_large_enough(library): | CODE |
| LOW⚡ | tests/test_admin.py | 45 | def test_the_page_renders_before_anything_has_written_to_the_data_dir(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 52 | def test_every_slot_in_the_template_is_filled(tmp_path, source, mode): | CODE |
| LOW⚡ | tests/test_admin.py | 57 | def test_the_page_still_fills_every_slot_with_the_detector_gone(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 63 | def test_the_detector_tab_counts_every_bird_heard_without_art(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 70 | def test_every_asset_the_page_links_is_one_the_server_serves(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 76 | def test_the_config_blob_carries_everything_admin_js_reads(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 83 | def test_a_species_with_no_artwork_is_marked_rather_than_dropped(tmp_path): | CODE |
| LOW⚡ | tests/test_admin.py | 91 | def test_a_bird_without_art_is_a_line_the_issue_form_takes(): | CODE |
| LOW⚡ | tests/test_admin.py | 100 | def test_the_issue_carries_the_list_while_github_takes_it(): | CODE |
| LOW⚡ | tests/test_admin.py | 109 | def test_a_station_with_art_for_every_bird_says_so(): | CODE |
| LOW⚡ | tests/test_admin.py | 113 | def test_a_plate_with_a_citation_links_to_it(tmp_path): | CODE |
| LOW⚡ | tests/test_admin.py | 120 | def test_every_species_name_carries_its_scientific_name_for_the_birdnet_link(tmp_path): | CODE |
| LOW⚡ | tests/test_admin.py | 127 | def test_the_update_row_offers_the_install_only_once_a_release_is_known(): | CODE |
| LOW⚡ | tests/test_admin.py | 156 | def test_a_working_password_is_not_reported_as_a_missing_one(): | CODE |
| LOW⚡ | tests/test_admin.py | 162 | def test_the_detector_password_says_what_it_is_for(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 170 | def test_a_stored_password_never_reaches_the_page(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 177 | def test_the_placeholder_posts_back_as_leave_it_alone(field): | CODE |
| LOW⚡ | tests/test_admin.py | 284 | def test_the_resolution_names_the_size_the_kiosk_renders_locked_or_not(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 292 | def test_the_kiosk_shape_switches_are_declared_so_unticking_one_is_a_change(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 301 | def test_a_dimmed_portrait_box_keeps_its_saved_value(tmp_path): | CODE |
| LOW⚡ | tests/test_admin.py | 311 | def test_with_no_panel_the_lock_is_disabled_declaration_and_all(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 323 | def test_the_panel_has_a_title_over_its_outputs(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 329 | def test_every_tab_has_the_pane_admin_js_shows(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 335 | def test_the_margin_offers_each_edge_while_there_is_a_panel(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 368 | def test_the_birdnet_link_only_substitutes_this_host_for_a_loopback_one(url, expected): | CODE |
| LOW⚡ | tests/test_admin.py | 374 | def test_the_page_carries_the_link_for_a_detector_on_another_machine(tmp_path, source, monkeypatch): | CODE |
| LOW⚡ | tests/test_admin.py | 383 | def test_the_connection_test_tells_the_three_answers_apart(detector): | CODE |
| LOW⚡ | tests/test_admin.py | 442 | def test_the_credentials_ask_for_a_password_and_no_username(tmp_path, source): | CODE |
| LOW⚡ | tests/test_admin.py | 448 | def test_the_names_field_says_why_it_has_only_the_scientific_name(tmp_path, source, monkeypatch): | CODE |
| LOW⚡ | tests/test_admin.py | 457 | def test_the_display_tab_names_the_password_rather_than_calling_it_unreachable(tmp_path, source): | CODE |
| LOW | tests/test_admin.py | 138 | def test_the_detector_row_carries_the_version_it_reports(): | CODE |
| LOW | tests/test_admin.py | 145 | def test_the_detector_row_says_a_password_is_wanted_rather_than_unreachable(): | CODE |
| LOW | tests/test_admin.py | 193 | def test_the_form_cannot_set_the_session_secret(): | CODE |
| LOW | tests/test_admin.py | 201 | def test_the_sign_out_button_is_only_offered_where_there_is_a_session_to_end(tmp_path, source): | CODE |
| LOW | tests/test_admin.py | 212 | def test_the_access_note_says_where_the_switch_and_the_password_leave_things(tmp_path, source): | CODE |
| LOW | tests/test_admin.py | 228 | def test_the_access_field_says_whether_the_admin_has_a_password(tmp_path, source): | CODE |
| LOW | tests/test_admin.py | 237 | def test_the_access_switches_are_declared_so_unticking_one_is_a_change(tmp_path, source, field): | CODE |
| LOW | tests/test_admin.py | 248 | def test_saving_the_form_untouched_leaves_the_password_standing(tmp_path): | CODE |
| LOW | tests/test_admin.py | 259 | def test_the_collage_fields_render_and_a_plate_mode_save_leaves_the_layout_alone(tmp_path, source): | CODE |
| LOW | tests/test_admin.py | 271 | def test_the_spotlight_box_saves_both_ways(tmp_path, source): | CODE |
| LOW | tests/test_admin.py | 351 | def test_unticking_the_margin_lock_is_a_change(tmp_path): | CODE |
| LOW | tests/test_admin.py | 399 | def test_the_connection_test_reads_the_stored_password_behind_the_placeholder(detector): | CODE |
| LOW | tests/test_admin.py | 405 | def test_the_connection_test_answers_in_the_status_row_s_words_too(detector): | CODE |
| LOW | tests/test_admin.py | 425 | def test_the_connection_test_catches_a_detector_that_only_gates_the_names(detector): | CODE |
| 356 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | tools/bird_box.py | 506 | let drag = null; | CODE |
| HIGH | tools/bird_box.py | 537 | const w = art.naturalWidth || 1, h = art.naturalHeight || 1; | CODE |
| HIGH | tools/bird_box.py | 618 | drag = null; | CODE |
| HIGH | tools/bird_box.py | 644 | box = moved(box, picked, step[0] * px / (art.naturalWidth || 1), | CODE |
| HIGH | tools/bird_box.py | 645 | step[1] * px / (art.naturalHeight || 1)); | CODE |
| HIGH⚡ | tests/test_panel.py | 50 | panel.push(Image.new("P", (1600, 1200))) | CODE |
| HIGH⚡ | tests/test_panel.py | 60 | panel.push(Image.new("RGB", size), rotation) | CODE |
| HIGH⚡ | tests/test_panel.py | 69 | panel.push(source, 90) | CODE |
| HIGH⚡ | tests/test_panel.py | 76 | panel.push(Image.new("P", (800, 480))) | CODE |
| HIGH⚡ | tests/test_panel.py | 84 | panel.push(Image.new("P", (1200, 1600))) | CODE |
| HIGH | src/fugleramme/service.py | 211 | panel.push(*pending) | CODE |
| HIGH | src/fugleramme/updates.py | 34 | CONTAINER_COMMAND = "docker compose pull && docker compose up -d" | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tools/add_bird.py | 30 | CODE | |
| LOW | tools/preview_plates.py | 8 | CODE | |
| LOW | tools/species_map.py | 22 | CODE | |
| LOW | tools/vendor_fonts.py | 11 | CODE | |
| LOW | tools/bird_box.py | 25 | CODE | |
| LOW | tools/bird_boxes.py | 31 | CODE | |
| LOW | tests/test_fake.py | 4 | CODE | |
| LOW | tests/test_artwork_names.py | 13 | CODE | |
| LOW | tests/test_admin.py | 5 | CODE | |
| LOW | tests/conftest.py | 5 | CODE | |
| LOW | tests/test_artwork_duplicates.py | 10 | CODE | |
| LOW | tests/test_service.py | 5 | CODE | |
| LOW | tests/test_container.py | 8 | CODE | |
| LOW | tests/test_add_bird.py | 13 | CODE | |
| LOW | tests/test_panel.py | 6 | CODE | |
| LOW | tests/test_languages.py | 5 | CODE | |
| LOW | tests/test_updates.py | 6 | CODE | |
| LOW | tests/test_selection.py | 7 | CODE | |
| LOW | tests/test_buttons.py | 4 | CODE | |
| LOW | tests/test_labels.py | 5 | CODE | |
| LOW | tests/test_names_styles.py | 5 | CODE | |
| LOW | tests/test_server.py | 3 | CODE | |
| LOW | tests/test_check.py | 3 | CODE | |
| LOW | tests/test_picks.py | 5 | CODE | |
| LOW | tests/test_packing.py | 5 | CODE | |
| LOW | tests/test_taxa.py | 7 | CODE | |
| LOW | tests/test_api.py | 8 | CODE | |
| LOW | tests/test_e6.py | 5 | CODE | |
| LOW | tests/test_paper.py | 4 | CODE | |
| LOW | tests/test_modes.py | 5 | CODE | |
| LOW | tests/test_sizes.py | 10 | CODE | |
| LOW | tests/test_settings.py | 4 | CODE | |
| LOW | src/fugleramme/service.py | 13 | CODE | |
| LOW | src/fugleramme/picks.py | 15 | CODE | |
| LOW | src/fugleramme/config.py | 8 | CODE | |
| LOW | src/fugleramme/check.py | 12 | CODE | |
| LOW | src/fugleramme/fake.py | 15 | CODE | |
| LOW | src/fugleramme/updates.py | 8 | CODE | |
| LOW | src/fugleramme/modes.py | 14 | CODE | |
| LOW | src/fugleramme/names.py | 31 | CODE | |
| LOW | src/fugleramme/api.py | 16 | CODE | |
| LOW | src/fugleramme/taxa.py | 18 | CODE | |
| LOW | src/fugleramme/cli.py | 11 | CODE | |
| LOW | src/fugleramme/buttons.py | 9 | CODE | |
| LOW | src/fugleramme/settings.py | 11 | CODE | |
| LOW | src/fugleramme/dev.py | 13 | CODE | |
| LOW | src/fugleramme/panel.py | 13 | CODE | |
| LOW | src/fugleramme/languages.py | 30 | CODE | |
| LOW | src/fugleramme/source.py | 8 | CODE | |
| LOW | src/fugleramme/e6.py | 19 | CODE | |
| LOW | src/fugleramme/status.py | 7 | CODE | |
| LOW | src/fugleramme/web/server.py | 23 | CODE | |
| LOW | src/fugleramme/web/__init__.py | 3 | CODE | |
| LOW | src/fugleramme/web/admin.py | 7 | CODE | |
| LOW | src/fugleramme/web/hostinfo.py | 6 | CODE | |
| LOW | src/fugleramme/render/plate.py | 9 | CODE | |
| LOW | src/fugleramme/render/collage.py | 24 | CODE | |
| LOW | src/fugleramme/render/dither.py | 16 | CODE | |
| LOW | src/fugleramme/render/paper.py | 11 | CODE | |
| LOW | src/fugleramme/render/page.py | 7 | CODE | |
| 3 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | README.md | 157 | - [AvianVisitors](https://github.com/Twarner491/AvianVisitors) - BirdNET-Pi, AI-generated illustrations and photo cutout | CODE |
| HIGH | README.md | 160 | - [belkins-birdnet](https://github.com/Belkins/belkins-birdnet) - BirdNET-Pi, AI-generated kachō-e style illustrations | CODE |
| HIGH | docs/faq.md | 85 | ## Is any of the included art AI-generated? | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tools/platereview/crops.py | 54 | except Exception as error: | CODE |
| LOW | examples/ee02/main.py | 194 | except Exception as error: | CODE |
| LOW | examples/ee02/main.py | 374 | except Exception as error: | CODE |
| LOW | src/fugleramme/service.py | 83 | except Exception as exc: | CODE |
| LOW | src/fugleramme/service.py | 214 | except Exception as exc: | CODE |
| LOW | src/fugleramme/check.py | 51 | except Exception as error: # a shape the frame cannot read is a failure too | CODE |
| LOW | src/fugleramme/buttons.py | 72 | except Exception as exc: | CODE |
| LOW | src/fugleramme/buttons.py | 90 | except Exception as exc: | CODE |
| LOW | src/fugleramme/buttons.py | 100 | except Exception: # a failed write must not take the thread with it | CODE |
| LOW | src/fugleramme/panel.py | 60 | except Exception as exc: # library not installed (dev loop) | CODE |
| LOW | src/fugleramme/panel.py | 65 | except Exception as exc: # library present but no panel wired up | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| CRITICAL | assets/artwork/classic/manifest.json | 3264 | "url": "https://commons.wikimedia.org/wiki/File:Blue_Song_Grosbeak._1._Male._2._Female._3._Young_(NYPL_b13559627-108 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | examples/ee02/main.py | 212 | CODE | |
| LOW | src/fugleramme/service.py | 114 | CODE | |
| LOW | src/fugleramme/fake.py | 257 | CODE | |
| LOW | src/fugleramme/fake.py | 375 | CODE | |
| LOW | src/fugleramme/languages.py | 311 | CODE | |
| LOW | src/fugleramme/web/server.py | 122 | CODE | |
| LOW | src/fugleramme/web/server.py | 459 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tools/platereview/index.html | 121 | const store = { get: k => { try { return JSON.parse(localStorage.getItem("plate-review") || "{}")[k] || {}; } catch { re | COMMENT |
| LOW | docs/species.md | 1 | # Species coverage | COMMENT |
| LOW | docs/species.md | 21 | #species-art label:last-of-type { border-radius: 0 .2rem .2rem 0; } | COMMENT |
| LOW | docs/species.md | 41 | #species-map-shapes [data-bin="3"] { fill: var(--bin-3); } | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | CHANGELOG.md | 527 | - **tools**: #135 add agent-based plate cut skill and review tooling | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/fugleramme/render/plate.py | 45 | CODE | |
| LOW | src/fugleramme/render/collage.py | 421 | CODE |