PaperNest Function-by-Function Call Graph
PaperNest Function-by-Function Call Graph
This is a function-level call graph for the current runtime architecture.
Legend:
- A -> B means function A directly calls function B.
- UI thread means Kivy main thread.
- Worker thread means Python background thread.
- Callback means nested function/lambda scheduled or bound to events.
1) App startup and dependency wiring
main.py
- module entrypoint -> ReceiptTrackerApp().run()
receipt_app/views/app.py: ReceiptTrackerApp
- init -> super().init; initializes busy-token registry
- begin_busy -> updates busy token map, busy_task_count, busy_message
- end_busy -> removes token, updates busy_task_count, resolves latest busy message
- build -> _configure_logging, ReceiptRepository(…), ImageStorageService(…), CsvExportService(…), OCRService(…), ReceiptService(…), _load_kv_files, ReceiptRoot(…)
- _load_kv_files -> Builder.load_file for shared/components/screens KV files
- _configure_logging -> logging.basicConfig with file + stream handlers
receipt_app/views/screens/root/root_screen.py: ReceiptRoot
- on_kv_post -> add_receipt_screen.configure, timeline_screen.configure, gallery_screen.configure
- switch_screen -> sets screen_manager.current
2) Core + data layer call graph
receipt_app/core/models.py
- Receipt.from_row -> date.fromisoformat, float(…), datetime.fromisoformat, Receipt(…)
- OCRSuggestion.has_suggestion -> bool(…) on suggestion/candidate fields
receipt_app/data/receipt_repository.py: ReceiptRepository
- init -> _initialize_schema
- _connect -> sqlite3.connect; sets row_factory
- _initialize_schema -> _connect; executes schema script
- add_receipt -> _connect; INSERT; returns cursor.lastrowid
- get_receipt_by_id -> _connect; SELECT; Receipt.from_row
- update_receipt -> _connect; UPDATE; get_receipt_by_id
- delete_receipt -> _connect; SELECT then DELETE; Receipt.from_row
- count_by_image_path -> _connect; SELECT COUNT(*)
- get_receipts_between -> _connect; SELECT range; list map Receipt.from_row
- get_total_between -> _connect; SELECT SUM(amount)
- get_all_receipts -> _connect; SELECT all; list map Receipt.from_row
3) Business service layer call graph
receipt_app/business/image_storage_service.py: ImageStorageService
- init -> ensures image directory exists
- _build_file_name -> datetime.now().strftime
- allocate_capture_target -> _build_file_name
- validate_captured_image -> Path(…).exists; returns resolved path
- store_existing_image -> _build_file_name, shutil.copy2 (unless already in app image dir)
- delete_image -> Path.unlink (best effort)
receipt_app/business/csv_export_service.py: CsvExportService
- init -> ensures export directory exists
- _resolve_export_dir -> validates/creates destination directory
- _build_padded_rows -> computes max widths and padded headers/rows
- export_timeline_report -> _resolve_export_dir, _build_padded_rows, csv.writer writes report
receipt_app/business/ocr_service.py: OCRService
- init -> _setup_engine
- available -> checks selected engine and unavailable_reason
- unavailable_reason -> returns reason text
- _setup_engine -> _setup_android_mlkit OR import pytesseract/Pillow and validate tesseract binary
- _setup_android_mlkit -> pyjnius autoclass bindings for Android OCR classes
- _resolve_android_uri -> Uri.parse or Uri.fromFile
- _request_android_gc -> java.lang.System.gc best effort
- _load_android_sampled_bitmap -> BitmapFactory bounds pass, inSampleSize decode retries, _request_android_gc
- _extract_android_text -> _resolve_android_uri/_load_android_sampled_bitmap, ML Kit recognizer, Tasks.await, recognizer.close, bitmap.recycle, _request_android_gc
- extract_suggestions -> validates image path, dispatches OCR engine, _extract_date_candidates, _extract_amount_candidates, _select_largest_amount
- _select_largest_amount -> float parse and max selection
- _extract_date -> _extract_date_candidates first result
- _extract_date_candidates -> _normalize_ocr_digits, datetime.strptime formats, cue-aware compact token parsing
- _extract_amount -> _extract_amount_candidates first result
- _extract_amount_candidates -> _rank_amount_candidates OR _parse_amount_tokens fallback
- _rank_amount_candidates -> _extract_item_line_amount, _extract_line_amount_candidates, _infer_itemized_total
- _extract_item_line_amount -> _normalize_amount_token
- _infer_itemized_total -> sum item line amounts
- _extract_line_amount_candidates -> _normalize_amount_token
- _normalize_ocr_digits -> translate OCR-confusable chars
- _normalize_amount_token -> normalize locale punctuation then float parse
- _parse_amount_tokens -> _extract_line_amount_candidates
receipt_app/business/receipt_service.py: ReceiptService
- init -> stores repository, image_storage, OCR service, CSV service
- ocr_available -> delegates to ocr_service.available
- ocr_unavailable_reason -> delegates to ocr_service.unavailable_reason
- _parse_date -> datetime.strptime
- _parse_amount -> float parse, non-negative check, rounding
- add_receipt -> _parse_date, _parse_amount, image_storage.validate_captured_image OR image_storage.store_existing_image, repository.add_receipt, returns Receipt(…)
- suggest_fields_from_image -> image_storage.validate_captured_image, ocr_service.extract_suggestions
- update_receipt -> _parse_date, _parse_amount, repository.update_receipt
- delete_receipt -> repository.delete_receipt, repository.count_by_image_path, image_storage.delete_image (best effort)
- get_timeline_summary -> _parse_date start/end, repository.get_receipts_between, repository.get_total_between
- export_timeline_csv -> get_timeline_summary, csv_export_service.export_timeline_report
- export_receipt_images -> repository.get_all_receipts, shutil.copy2 to year/month-name folders
- get_gallery_grouped_by_month -> repository.get_all_receipts grouped by YYYY-MM
- get_gallery_grouped_by_year_month -> repository.get_all_receipts grouped by YYYY then MM
4) Add receipt screen call graph (full runtime controller)
File: receipt_app/views/screens/add_receipt/add_receipt_screen.py
Lifecycle and layout update
- init -> super().init, initializes camera state, draft manager, busy token, scan generation
- on_kv_post -> widget binds; _schedule_preview_resize; _schedule_suggestion_refresh
- on_size -> _schedule_preview_resize
- on_selected_image_path -> _schedule_preview_resize
- on_resolved_date -> _format_iso_date_for_display
- on_pre_leave -> s(False)
- on_touch_down -> captures swipe start if touch on preview image
- on_touch_up -> show_next_receipt/show_previous_receipt on swipe gesture
- _schedule_preview_resize -> Clock.unschedule/_schedule _update_preview_layout
- _update_preview_layout -> computes preview_image_height from texture/card width
Setup, status, and busy overlay
- configure -> _clear_selection_state, _set_status using OCR availability
- _set_status -> updates status_message
- s -> toggles local loading state; _begin_global_busy/_end_global_busy
- _begin_global_busy -> MDApp.get_running_app().begin_busy
- _end_global_busy -> MDApp.get_running_app().end_busy
Suggestion UI generation
- _schedule_suggestion_refresh -> Clock.unschedule/_schedule _refresh_suggestion_buttons
- _refresh_suggestion_buttons -> _current_date_candidates, _current_amount_candidates, _populate_suggestions_grid
- _populate_suggestions_grid -> builds suggestion buttons and binds on_select callback
Draft state and image navigation
- _current_receipt -> draft_manager.current
- _update_current_receipt_label -> draft_manager.current_label
- _apply_current_receipt_to_ui -> syncs selected paths/values/preview + _schedule_suggestion_refresh
- _set_selected_images -> draft_manager.set_selected_images, _apply_current_receipt_to_ui
- _format_iso_date_for_display -> converts YYYY-MM-DD to Mon DD, YYYY
- _clear_selection_state -> draft_manager.clear, _apply_current_receipt_to_ui
- _persist_current_receipt_values -> draft_manager.persist_current_values
- show_previous_receipt -> _persist_current_receipt_values, draft_manager.move_previous, _apply_current_receipt_to_ui
- show_next_receipt -> _persist_current_receipt_values, draft_manager.move_next, _apply_current_receipt_to_ui
Android permission helpers
- _run_with_android_permissions -> check/request permissions; schedules on_granted or popup/status
- _camera_permissions -> builds CAMERA + media-read list
- _media_read_permissions -> returns media read permissions
Selection and detail editing helpers
- _apply_selected_images_and_scan -> _set_selected_images, _set_status, _auto_fill_after_image
- _clear_resolved_values -> clears date/amount and _refresh_details_summary
- _refresh_details_summary -> computes details_summary from current resolved values
- _validate_amount_only -> float parse, non-negative, 2dp normalize
- _current_date_candidates -> from current draft
- _current_amount_candidates -> from current draft
- _dedupe_non_empty -> normalization/dedup helper
- _open_candidate_selector -> popup UI for selecting candidates or manual input
- _open_resolved_date_picker -> MDDatePicker; on save updates state + _persist_current_receipt_values
- _apply_selected_date_candidate -> applies date candidate + refreshes UI state
- _format_amount_for_display -> formats candidate display amount
- _open_resolved_amount_numpad -> open_amount_numpad
- _apply_selected_amount_candidate -> _validate_amount_only then persist/refresh
- edit_resolved_date -> candidate selector OR _open_resolved_date_picker
- edit_resolved_amount -> candidate selector OR _open_resolved_amount_numpad
- _apply_resolved_amount_edit -> _validate_amount_only + persist/refresh
- _apply_resolved_values -> applies date/amount and status
- _auto_fill_after_image -> run_ocr(auto_trigger=True, scan_all=True)
- _validate_manual_values -> validates popup manual date/amount
- _safe_parse_iso_date -> safe parser helper
- _prompt_detected_values -> confirmation popup; confirm uses _apply_resolved_values; edit opens _prompt_manual_values
- _prompt_manual_values -> manual entry popup; save uses _validate_manual_values then _apply_resolved_values
Camera capture and file picker flow
- capture_photo -> permission flow then _start_capture_photo
- _start_capture_photo -> _allocate_camera_target_path; Android route _start_android_camera_capture else plyer.camera.take_picture
- _start_android_camera_capture -> binds android activity result callback; schedules _handle_camera_result on UI thread; writes bitmap to target; _on_capture_complete
- _allocate_camera_target_path -> image_storage.allocate_capture_target
- _on_capture_complete -> Clock.schedule_once(_apply_capture_result)
- _apply_capture_result -> image_storage.validate_captured_image; _apply_selected_images_and_scan
- open_file_picker -> permission flow then _open_android_media_picker or _open_file_picker_internal
- _open_android_media_picker -> plyer.filechooser.open_file callback to _on_android_media_selected
- _on_android_media_selected -> Clock.schedule_once(_apply_android_media_selection)
- _apply_android_media_selection -> _on_image_files_selected with selected paths
- _copy_android_content_uri_to_local_image -> pyjnius stream copy content:// URI into app storage
- _open_file_picker_internal -> FilePickerHelper.open_file_picker(callback=_on_image_files_selected)
- _on_image_files_selected -> normalize/copy paths; _apply_selected_images_and_scan
OCR worker-thread flow
- run_ocr -> builds image batch; sets scan generation; s(True); starts worker thread _scan_receipts_worker
- _scan_receipts_worker (worker thread) -> _attach_android_jnius_thread, receipt_service.suggest_fields_from_image for each image, _release_android_memory_pressure between items, Clock.schedule_once(_apply_scan_results_safely), finally _detach_android_jnius_thread
- _attach_android_jnius_thread -> jnius.attach on Android
- _detach_android_jnius_thread -> jnius.detach on Android
- _release_android_memory_pressure -> gc.collect + java.lang.System.gc
- _apply_scan_results_safely (UI thread) -> _apply_scan_results with exception guard
- _handle_scan_worker_failure (UI thread) -> s(False) and error popup/status
- _apply_scan_results (UI thread) -> writes draft suggestions, _apply_current_receipt_to_ui, _set_status summary
Save worker-thread flow
- save_receipt -> validates current draft, builds save payload, s(True), starts worker thread _save_receipt_worker
- _save_receipt_worker (worker thread) -> receipt_service.add_receipt, Clock.schedule_once(_apply_saved_receipt) or _handle_save_error
- _handle_save_error (UI thread) -> s(False), _set_status, show_popup
- _apply_saved_receipt (UI thread) -> s(False), remove saved draft, _clear_selection_state OR _apply_current_receipt_to_ui, _notify_data_changed
- _notify_data_changed -> refreshes timeline and gallery screens if present
5) Timeline screen call graph
File: receipt_app/views/screens/timeline/timeline_screen.py
TimelineReceiptRow
- trigger_edit -> on_edit callback with receipt id
- trigger_delete -> on_delete callback with receipt id
TimelineScreen
- init -> initializes receipt map and editor placeholder
- configure -> assigns service, creates ReceiptEditor, initializes date range
- _parse_iso_date -> safe parser
- _open_date_picker -> MDDatePicker; callback writes start/end date text
- open_start_date_picker -> _open_date_picker(target=start)
- open_end_date_picker -> _open_date_picker(target=end)
- on_pre_enter -> refresh_summary(silent=True)
- refresh_summary -> receipt_service.get_timeline_summary, updates summary, renders TimelineReceiptRow widgets
- open_edit_popup -> ReceiptEditor.open with callback _after_receipt_edited
- _after_receipt_edited -> refresh_summary + _notify_data_changed
- confirm_delete_receipt -> show_confirm_popup -> delete_receipt
- delete_receipt -> receipt_service.delete_receipt, refresh_summary, _notify_data_changed
- export_csv -> _downloads_export_dir, show_confirm_popup -> _export_csv_to_directory
- export_images -> _downloads_export_dir, show_confirm_popup -> _export_images_to_directory
- _downloads_export_dir -> platform-aware Downloads path
- _export_images_to_directory -> receipt_service.export_receipt_images, show_popup status
- _export_csv_to_directory -> receipt_service.export_timeline_csv, show_popup status
- _notify_data_changed -> refreshes gallery screen if present
6) Gallery screen call graph
File: receipt_app/views/screens/gallery/gallery_screen.py
GalleryReceiptCard
- trigger_edit -> on_edit callback
- trigger_delete -> on_delete callback
- trigger_popup -> show_popup with Edit/Delete/Cancel
- on_touch_up -> opens popup or custom on_open callback
GalleryScreen
- init -> initializes hierarchy cache, editor ref, loading state, tap suppression timer
- _suppress_receipt_taps -> updates suppression deadline
- _receipt_taps_suppressed -> checks suppression window
- _open_receipt_card_popup -> opens card popup unless loading/suppressed
- configure -> stores service and creates ReceiptEditor
- on_pre_enter -> refresh_gallery(silent=True)
- on_pre_leave -> _set_loading(False)
- _set_loading -> toggles loading popup + app global busy token
- _begin_global_busy -> MDApp.begin_busy
- _end_global_busy -> MDApp.end_busy
- navigate_up -> adjusts selected year/month and _render_gallery
- open_year -> set year and schedule _render_gallery
- open_month -> set month and schedule _render_gallery
- _render_year_list -> creates year buttons bound to open_year
- _render_month_list -> creates month buttons bound to open_month
- _render_receipt_cards -> creates GalleryReceiptCard widgets and binds callbacks
- _render_gallery -> renders year/month/receipt level depending on selection
- refresh_gallery -> _set_loading(True), starts worker thread _refresh_gallery_worker
- _refresh_gallery_worker (worker thread) -> receipt_service.get_gallery_grouped_by_year_month, schedules _apply_refreshed_gallery or _on_gallery_refresh_error
- _on_gallery_refresh_error (UI thread) -> _set_loading(False), optional popup
- _apply_refreshed_gallery (UI thread) -> _set_loading(False), update caches, _render_gallery
- open_edit_popup -> ReceiptEditor.open callback _after_receipt_edited
- _after_receipt_edited -> refresh_gallery + timeline.refresh_summary
- confirm_delete_receipt -> show_confirm_popup -> delete_receipt
- delete_receipt -> _set_loading(True), starts worker thread _delete_receipt_worker
- _delete_receipt_worker (worker thread) -> receipt_service.delete_receipt, schedules _on_delete_succeeded/_on_delete_failed
- _on_delete_failed (UI thread) -> _set_loading(False), show_popup
- _on_delete_succeeded (UI thread) -> _set_loading(False), refresh_gallery, refresh timeline
7) Shared UI component call graph
receipt_app/views/components/receipt_editor/receipt_editor.py
- TapToEditTextInput.on_touch_down -> invokes tap_callback if tapped
- ReceiptEditor.init -> stores receipt_service
- ReceiptEditor.open -> builds popup, binds date/amount tap callbacks, save callback calls receipt_service.update_receipt
- ReceiptEditor._open_date_picker_for_input -> MDDatePicker updates input text
- ReceiptEditor._parse_iso_date -> safe parser
receipt_app/views/components/amount_numpad/amount_numpad_popup.py
- AmountNumpadPopup.init -> _clean_initial, _refresh_display
- _clean_initial -> parse/normalize initial amount
- _display_text -> current or initial hint
- _refresh_display -> display text/color update
- _append_char -> mutates current input buffer
- _backspace -> removes trailing char
- _clear -> clears current input
- _normalize_for_apply -> validates/normalizes value to 2dp
- _apply -> _normalize_for_apply, invokes on_apply callback
- open_amount_numpad -> singleton-like active popup guard, bind dismiss cleanup, open popup
receipt_app/views/shared/file_picker_helper.py
- _normalize_start_path -> resolves usable chooser start folder
- open_file_picker -> popup chooser, selection callback returns paths
- open_directory_picker -> popup chooser, returns selected/current directory
receipt_app/views/shared/popup_utils.py
- bind_label_for_wrapping -> binds label size to text wrapping
- create_themed_popup -> applies popup style + optional content surface
- _apply_content_surface -> draws popup content background rectangle
- style_button -> assigns role-based colors
- _button_role_from_text -> heuristics for primary/danger role
- show_popup -> generic popup with optional button callbacks
- show_loading_popup -> spinner popup, returns popup handle
- show_confirm_popup -> confirm/cancel popup and on_confirm callback
receipt_app/views/shared/outlined_card.py
- TappableOutlinedCard.on_touch_down -> consume touch for feedback/ripple
- TappableOutlinedCard.on_touch_up -> invoke tap_callback and consume event
8) Add-receipt draft manager call graph
File: receipt_app/views/screens/add_receipt/receipt_draft_manager.py
- init -> initializes draft list/index
- items -> returns draft list
- count -> returns list length
- current_index getter/setter -> bounds index
- clear -> resets list/index
- set_selected_images -> normalize, dedupe, create ReceiptDraft list
- current -> returns current draft or None
- current_label -> human-readable image index label
- persist_current_values -> writes resolved date/amount to current draft
- move_previous -> decrements index if possible
- move_next -> increments index if possible
- remove_by_image_path -> removes one draft and re-bounds current index
9) Optional/non-primary runtime call graph
Batch review module (currently not wired in root ScreenManager)
File: receipt_app/views/screens/batch_review/batch_review_screen.py
- start_review -> builds BatchReviewItem list from payload and applies first item
- confirm_current_receipt -> validates current item and advances or _finish_review
- _finish_review -> emits normalized payload via on_apply callback
- cancel_review -> emits on_cancel callback
- Other methods mirror add-receipt helpers for suggestions/date/amount editing and preview navigation
Image processing test utility
Files:
- image_processing_test_ui.py
- receipt_app/business/image_processing_service.py
Used for OCR preprocessing experiments; not required for core app flow.