
    r5i                    ~    d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	  G d de
      Z G d	 d
      Z G d d      Zy)z4KPI calculation engine with safe formula evaluation.    )annotationsN)Any   )KPITreeKPINodec                      e Zd ZdZy)FormulaErrorz'Error in formula parsing or evaluation.N)__name__
__module____qualname____doc__     g/var/www/tkim.planitai.co.jp/gemegg/20251207-make-pdf-report/project/planitai-kpi/src/kpi/calculator.pyr	   r	      s    1r   r	   c            
          e Zd ZdZej
                  ej                  ej                  ej                  ej                  ej                  ej                  ej                  ej                  ej                  f
Zd	dZd
dZddZddZddZddZy)FormulaParserzSafe formula parser using AST.

    Only allows basic arithmetic operations: +, -, *, /
    Variables are referenced as {variable_name}
    c                8    t        j                  d      | _        y)zInitialize parser.z	\{(\w+)\}N)recompile_variable_pattern)selfs    r   __init__zFormulaParser.__init__'   s    !#L!9r   c                8    | j                   j                  |      S )zExtract variable names from formula.

        Args:
            formula: Formula string with {variable} syntax.

        Returns:
            List of variable names.
        )r   findall)r   formulas     r   parse_variableszFormulaParser.parse_variables+   s     %%--g66r   c                    |}| j                  |      D ]6  }||vrt        d|       |j                  d| dt        ||               }8 |S )aA  Replace variables with values.

        Args:
            formula: Formula string with {variable} syntax.
            values: Dictionary mapping variable names to values.

        Returns:
            Formula with variables replaced by values.

        Raises:
            FormulaError: If a variable is missing.
        zMissing variable: {})r   r	   replacestr)r   r   valuesresultvars        r   substitute_variablesz"FormulaParser.substitute_variables6   sh     ''0 	DC& "%7u#=>>^^bRL#fSk2BCF	D
 r   c                    | j                  ||      }	 t        j                  |d      }| j                  |       | j                  |j                        S # t        $ r}t	        d|       d}~ww xY w)a?  Evaluate formula with given values.

        Args:
            formula: Formula string with {variable} syntax.
            values: Dictionary mapping variable names to values.

        Returns:
            Calculated result.

        Raises:
            FormulaError: If formula is invalid or evaluation fails.
        eval)modezInvalid formula syntax: N)r%   astparseSyntaxErrorr	   _validate_ast
_eval_nodebody)r   r   r"   
expressiontreees         r   evaluatezFormulaParser.evaluateL   su     ..w?
	?99Zf5D
 	4  tyy))  	?!9!=>>	?s   A 	A3 A..A3c                    t        j                  |      D ]:  }t        || j                        rt	        dt        |      j                   d       y)zValidate that AST only contains allowed node types.

        Args:
            tree: AST to validate.

        Raises:
            FormulaError: If tree contains disallowed nodes.
        zInvalid expression: z is not allowedN)r)   walk
isinstanceALLOWED_NODESr	   typer
   )r   r0   nodes      r   r,   zFormulaParser._validate_asth   sO     HHTN 	DdD$6$67"*4:+>+>*?O 	r   c                   t        |t        j                        rt        |j                        S t        |t        j
                        r| j                  |j                        }| j                  |j                        }t        |j                  t        j                        r||z   S t        |j                  t        j                        r||z
  S t        |j                  t        j                        r||z  S t        |j                  t        j                        r|dk(  ry||z  S t        |t        j                        rh| j                  |j                        }t        |j                  t        j                         r| S t        |j                  t        j"                        r|S t%        dt'        |      j(                         )zEvaluate a single AST node.

        Args:
            node: AST node to evaluate.

        Returns:
            Evaluated value.

        Raises:
            FormulaError: If node type is not supported.
        r           zUnsupported node type: )r5   r)   ConstantfloatvalueBinOpr-   leftrightopAddSubMultDivUnaryOpoperandUSubUAddr	   r7   r
   )r   r8   r?   r@   rG   s        r   r-   zFormulaParser._eval_nodew   s;    dCLL)$$dCII&??499-DOODJJ/E$''377+e|#$''377+e|#$''388,e|#$''377+A:e|#dCKK(oodll3G$''388,x$''388,4T$Z5H5H4IJKKr   N)returnNone)r   r!   rJ   	list[str])r   r!   r"   dict[str, float]rJ   r!   )r   r!   r"   rM   rJ   r<   )r0   ast.ASTrJ   rK   )r8   rN   rJ   r<   )r
   r   r   r   r)   
Expressionr>   rF   r;   rB   rC   rD   rE   rH   rI   r6   r   r   r%   r2   r,   r-   r   r   r   r   r      su     			M:	7,*8%Lr   r   c                  8    e Zd ZdZddZd	dZd
dZddZddZy)KPICalculatorzKPI calculation engine.c                0    || _         t               | _        y)zZInitialize with KPI tree.

        Args:
            tree: KPI tree to calculate.
        N)r0   r   parser)r   r0   s     r   r   zKPICalculator.__init__   s     	#or   c                   | j                         }i }|D ]|  }| j                  j                  |   }|j                  r4	 | j                  j                  |j                  |      }||_        |||<   \|j                  |j                  ||<   xd||<   ~ |S # t        $ r }t        d| d|        d||<   Y d}~d}~ww xY w)zCalculate all KPIs in the tree.

        Uses topological sort to ensure dependencies are calculated first.

        Returns:
            Dictionary mapping node ID to calculated value.
        zWarning: Failed to calculate z: r:   N)	_get_calculation_orderr0   nodesr   rS   r2   r=   r	   print)r   orderresultsnode_idr8   r=   r1   s          r   	calculatezKPICalculator.calculate   s     ++-$& 	'G99??7+D||+ KK00wGE!&DJ',GG$
 '#'::  $' %	'(  $ +9'"QCHI'*GG$+s   2B	B?B::B?c                l    g t               dfd | j                  j                         S )zGet nodes in topological order for calculation.

        Returns nodes from leaves to root, ensuring all dependencies
        are calculated before dependents.

        Returns:
            List of node IDs in calculation order.
        c                    | j                   v ry j                  | j                          | j                  D ]
  } |        j                  | j                          y )N)idaddchildrenappend)r8   childrX   visitvisiteds     r   rc   z3KPICalculator._get_calculation_order.<locals>.visit   sP    ww'!KK   e LL!r   )r8   r   rJ   rK   )setr0   root)r   rX   rc   rd   s    @@@r   rU   z$KPICalculator._get_calculation_order   s-     E		" 	diinnr   c                   | j                   j                  j                         D ci c]  }|j                  |j                   }}|j                         D ]<  \  }}|| j                   j                  v s|| j                   j                  |   _        > | j                         }|j                         D ]#  \  }}|| j                   j                  |   _        % |S c c}w )zSimulate KPI changes without modifying the tree.

        Args:
            changes: Dictionary of node ID to new value.

        Returns:
            Dictionary mapping node ID to simulated value.
        )r0   rV   r"   r^   r=   itemsr[   )r   changesnsaved_valuesrZ   r=   rY   s          r   simulatezKPICalculator.simulate   s     04yy/E/E/GH!aggHH &mmo 	7NGU$))//)16		(.	7
 .." +002 	3NGU-2DIIOOG$*	3  Is   Cc                    | j                   j                  |      }|r|j                  sg S | j                  j	                  |j                        S )zGet all dependencies for a node's formula.

        Args:
            node_id: Node ID to check.

        Returns:
            List of node IDs that this node depends on.
        )r0   get_noder   rS   r   )r   rZ   r8   s      r   get_formula_dependenciesz&KPICalculator.get_formula_dependencies  s?     yy!!'*4<<I{{**4<<88r   N)r0   r   )rJ   rM   )rJ   rL   )ri   rM   rJ   rM   )rZ   r!   rJ   rL   )	r
   r   r   r   r   r[   rU   rl   ro   r   r   r   rQ   rQ      s    !&!F449r   rQ   )r   
__future__r   r)   r   typingr   r0   r   r   	Exceptionr	   r   rQ   r   r   r   <module>rs      s>    : " 
 	  "	9 	JL JLZp9 p9r   