11import json
22import os
33from datetime import date
4- from typing import Any
5- from typing import Dict
64from typing import List
75from typing import Optional
86from typing import Tuple
1311from finalynx import Dashboard
1412from finalynx import Fetch
1513from finalynx import Portfolio
16- from finalynx .config import DEFAULT_CURRENCY
1714from finalynx .config import get_active_theme as TH
1815from finalynx .config import set_active_theme
16+ from finalynx .copilot .recommendations import render_recommendations
1917from finalynx .fetch .source_base import SourceBase
2018from finalynx .fetch .source_finary import SourceFinary
2119from finalynx .portfolio .bucket import Bucket
2220from finalynx .portfolio .envelope import Envelope
23- from finalynx .portfolio .folder import Folder
24- from finalynx .portfolio .folder import FolderDisplay
25- from finalynx .portfolio .folder import SharedFolder
2621from finalynx .portfolio .folder import Sidecar
27- from finalynx .portfolio .node import Node
28- from finalynx .portfolio .targets import Target
2922from html2image import Html2Image
3023from rich import inspect # noqa F401
3124from rich import pretty
@@ -261,7 +254,7 @@ def render_panels(self) -> Columns:
261254 panels : List [ConsoleRenderable ] = [
262255 Text (" " ),
263256 Panel (
264- self . render_recommendations (),
257+ render_recommendations (self . portfolio , self . envelopes ),
265258 title = "Recommendations" ,
266259 padding = (1 , 2 ),
267260 expand = False ,
@@ -288,80 +281,6 @@ def render_performance_report(self) -> Tree:
288281 tree .add (f"[{ TH ().TEXT } ]Planned: [bold][{ TH ().ACCENT } ]{ perf_ideal :.1f} %[/] / year" )
289282 return tree
290283
291- def render_recommendations (self ) -> Tree :
292- """Sort lines with non-zero deltas by envelopes and display them as a summary of transfers to make.
293- Call either run() or initialize() first.
294- """
295- dict_envs : Dict [str , Any ] = {}
296-
297- # Guide the user to set envelopes if not already done
298- if not self .envelopes :
299- return Tree (
300- f"[dim { TH ().TEXT } ]"
301- "To activate recommendations, set\n "
302- "envelopes to your lines and give\n "
303- "them to Assistant (tutorial #11)"
304- )
305-
306- # Find all folders with non-zero deltas and non-zero amounts (to avoid empty shared folders)
307- def _get_folders (node : Folder ) -> List [Folder ]:
308- found : List [Folder ] = []
309- for child in node .children :
310- if isinstance (child , SharedFolder ):
311- if child .get_amount () > 0 :
312- found .append (child )
313- elif isinstance (child , Folder ):
314- if child .display == FolderDisplay .EXPANDED :
315- found += _get_folders (child )
316- else :
317- found .append (child )
318- return found
319-
320- # Check if a folder has non-zero deltas to be displayed in the recommendations
321- def _check_node (node : Node ) -> bool :
322- return node .get_delta () != 0 and node .target .check () not in [
323- Target .RESULT_NONE ,
324- Target .RESULT_OK ,
325- Target .RESULT_TOLERATED ,
326- ]
327-
328- # Render each envelope of folder's parent with a custom style along with the
329- def _render_title (children : List [Any ], name : str ) -> Tuple [int , str ]:
330- total_delta = round (sum ([c .get_delta () for c in children ]))
331- return total_delta , (
332- f"[{ TH ().DELTA_POS if total_delta > 0 else TH ().DELTA_NEG } ]"
333- f"{ '+' if total_delta > 0 else '' } { total_delta } { DEFAULT_CURRENCY } "
334- f"[{ TH ().FOLDER_COLOR } { TH ().FOLDER_STYLE } ]{ name } [/]"
335- )
336-
337- # For each envelope, find all lines with non-zero deltas
338- for envelope in self .envelopes :
339- if lines := [line for line in envelope .lines if _check_node (line )]:
340- delta , title = _render_title (lines , envelope .name )
341- dict_envs [title ] = (delta , lines )
342-
343- # Render folders with non-zero deltas, classify them by parent name
344- if folders := [f for f in _get_folders (self .portfolio ) if _check_node (f )]:
345- for parent_name in {f .parent .name for f in folders if f .parent }:
346- items = [f for f in folders if f .parent and f .parent .name == parent_name ]
347- delta , title = _render_title (items , parent_name )
348- dict_envs [title ] = (delta , items )
349-
350- # Render the tree with folders containing lines with non-zero deltas (sorted by delta)
351- tree = Tree ("Envelopes" , hide_root = True , guide_style = TH ().TREE_BRANCH )
352- dict_sorted = {k : v [1 ] for k , v in sorted (dict_envs .items (), key = lambda item : item [1 ][0 ])} # type: ignore
353- for i_env , envelope_name in enumerate (dict_sorted ):
354- node = tree .add (envelope_name )
355- for i_line , line in enumerate (dict_sorted [envelope_name ]):
356- render = f"[{ TH ().TEXT } ]{ line ._render_delta (children = dict_sorted [envelope_name ])} { line ._render_name ()} " # type: ignore
357- newline = bool (i_line == len (dict_sorted [envelope_name ]) - 1 and i_env < len (dict_envs .keys ()) - 1 )
358- node .add (render + ("\n " if newline else "" ))
359-
360- # If no envelopes are displayed, show a nice message instead
361- if not tree .children :
362- tree .add ("You're on track! 🎉" )
363- return tree
364-
365284 def dashboard (self ) -> None :
366285 """Launch an interactive web dashboard! Call either run() or initialize() first."""
367286 console .log ("Launching dashboard." )
@@ -421,9 +340,12 @@ def export_img(
421340 output_html = dashboard_console .export_html ().replace ("body {" , f"body {{\n zoom: { zoom } ;" )
422341
423342 # Convert the HTML to PNG
424- Html2Image (output_path = full_path ).screenshot (html_str = output_html , save_as = file_name , size = size )
343+ try :
344+ Html2Image (output_path = full_path ).screenshot (html_str = output_html , save_as = file_name , size = size )
345+ console .print (f"Saved portfolio PNG to '{ full_path + file_name } '" )
346+ except Exception as e :
347+ console .log (f"[red][bold]Error:[/] Package html2image failed, skipping ({ e } )" )
425348
426349 # Restore theme and return the image path
427350 set_active_theme (previous_theme )
428- console .print (f"Saved portfolio PNG to '{ full_path + file_name } '" )
429351 return full_path + file_name
0 commit comments