yonilev commited on
Commit
fdcac18
Β·
verified Β·
1 Parent(s): 49e610e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +46 -120
app.py CHANGED
@@ -430,93 +430,73 @@ def render_parse_panel(parse, completed, show_vat=True):
430
 
431
  def decide_fiscal(text, issuer_status, parse, explicit_doc_choice,
432
  clarify_vat_basis=None, clarify_client_tax_id=None):
433
- """
434
- Returns (issuer_dict, final_parse, questions_dict) where:
435
- - issuer_dict: ready for core.complete()
436
- - final_parse: enriched parse ready for core.complete()
437
- - questions_dict: {field: question_text} for any remaining mandatory gaps
438
- """
439
- questions = {}
440
  today = _today()
441
 
442
- # ── 1. Issuer ────────────────────────────────────────────────────────────
443
  if not issuer_status:
444
- questions["issuer_status"] = "ΧžΧ™ אΧͺΧ”? Χ‘Χ—Χ¨ Χ‘Χ˜Χ˜Χ•Χ‘ ΧžΧ Χ€Χ™Χ§ ΧœΧžΧ’ΧœΧ” (Χ’Χ•Χ‘Χ§ Χ€Χ˜Χ•Χ¨ / Χ’Χ•Χ‘Χ§ ΧžΧ•Χ¨Χ©Χ”)."
 
 
445
  exempt = (issuer_status == "Χ€Χ˜Χ•Χ¨")
446
  issuer = {**DEMO_ISSUER_BASE,
447
  "status": "exempt_dealer" if exempt else "authorized_dealer",
448
  "is_company": False}
449
 
450
- # ── 2. Mandatory content fields ──────────────────────────────────────────
451
  if not parse.get("client_name", "").strip():
452
  questions["client_name"] = "שם Χ”ΧœΧ§Χ•Χ— / Χ”ΧžΧ©ΧœΧ?"
453
  if not parse.get("items"):
454
  questions["items"] = "ΧžΧ” Χ”Χ©Χ™Χ¨Χ•Χͺ/Χ”ΧžΧ•Χ¦Χ¨ ובאיזה בכום? (למשל: Χ™Χ™Χ’Χ•Χ₯ Χ’Χ‘Χ§Χ™ β‚ͺ500)"
455
 
456
- # ── 3. Date: extract from text β†’ today ───────────────────────────────────
457
- detected_date = _detect_date_from_text(text) or today.isoformat()
458
  parse = dict(parse)
459
- parse.setdefault("date", detected_date)
460
 
461
- # ── 4. Doc type decision ─────────────────────────────────────────────────
462
- is_gross = _text_has(text, _GROSS_PATTERNS) or clarify_vat_basis == "Χ›Χ•ΧœΧœ מג״מ"
463
- is_net = _text_has(text, _NET_PATTERNS) or clarify_vat_basis == "ΧœΧ€Χ Χ™ מג״מ"
464
  has_vat_cue = is_gross or is_net
465
 
466
  if exempt:
467
- # Χ’Χ•Χ‘Χ§ Χ€Χ˜Χ•Χ¨ β†’ always receipt, no VAT
468
- doc_type = "receipt"
469
- amount_basis = "net" # irrelevant (vat_rate = 0)
470
  elif explicit_doc_choice and explicit_doc_choice != "Χ–Χ™Χ”Χ•Χ™ ΧΧ•Χ˜Χ•ΧžΧ˜Χ™":
471
- doc_type = {
472
- "Χ§Χ‘ΧœΧ”": "receipt",
473
- "Χ—Χ©Χ‘Χ•Χ Χ™Χͺ מב": "tax_invoice",
474
- "Χ—Χ©Χ‘Χ•Χ Χ™Χͺ מב Χ•Χ§Χ‘ΧœΧ”": "tax_invoice_receipt",
475
- }.get(explicit_doc_choice, "receipt")
476
- # amount basis for tax-invoice types
477
  if doc_type != "receipt":
478
- if is_gross: amount_basis = "gross"
479
- elif is_net: amount_basis = "net"
480
- elif clarify_vat_basis is None:
481
- questions["vat_basis"] = (
482
- f"הבכום {'β‚ͺ' + str(int(sum(i.get('unit_price',0)*i.get('quantity',1) for i in parse.get('items',[]))))+ ' ' if parse.get('items') else ''}β€”"
483
- f" **Χ›Χ•ΧœΧœ מג״מ** או **ΧœΧ€Χ Χ™ מג״מ**?")
484
- amount_basis = "net" # placeholder until answered
 
485
  else:
486
  amount_basis = "net"
487
  else:
488
  amount_basis = "net"
489
  else:
490
- # Auto mode
491
  if has_vat_cue:
492
- doc_type = "tax_invoice_receipt"
493
- amount_basis = "gross" if is_gross else "net"
494
  else:
495
- # No VAT mention β†’ receipt without VAT breakdown
496
- doc_type = "receipt"
497
- amount_basis = "net"
498
-
499
- # ── 5. Payment received vs. demanded ─────────────────────────────────────
500
- # Χ—Χ©Χ‘Χ•Χ Χ™Χͺ מב (no Χ§Χ‘ΧœΧ”) = money not yet received β€” explicit choice only
501
- # Auto never produces pure tax_invoice (always /Χ§Χ‘ΧœΧ” if VAT)
502
 
503
- # ── 6. Client tax ID for allocation ──────────────────────────────────────
504
  if doc_type in ("tax_invoice", "tax_invoice_receipt") and not exempt:
505
- subtotal = sum(
506
- it.get("unit_price", 0) * it.get("quantity", 1)
507
- for it in parse.get("items", []))
508
- threshold = core.allocation_threshold_for_date(
509
- _dt.date.fromisoformat(parse["date"]))
510
- client_biz = parse.get("client_is_business", False)
511
- if client_biz and subtotal >= threshold and not parse.get("client_tax_id"):
512
- if clarify_client_tax_id:
513
  parse["client_tax_id"] = clarify_client_tax_id.strip()
514
- else:
515
- questions["client_tax_id"] = (
516
- f"Χ—.Χ€. Χ”ΧœΧ§Χ•Χ— β€” Χ Χ“Χ¨Χ© למב׀ר הקצאה (Χ’Χ‘Χ§Χ” β‰₯ β‚ͺ{threshold:,})")
517
 
518
- parse["doc_type"] = doc_type
519
- parse["amount_basis"] = amount_basis
520
  parse.setdefault("payment_method", "bank_transfer")
521
  parse.setdefault("currency", "ILS")
522
  parse.setdefault("client_tax_id", None)
@@ -552,16 +532,19 @@ def _build_clarification_message(questions: dict, note_text: str) -> str:
552
 
553
  def generate(note, issuer_radio, doc_choice,
554
  clarify_vat, clarify_tax_id):
555
- """
556
- Single-shot, stateless pipeline. Returns:
557
- (status_md, clarify_html, parse_html, doc_html, recs_html,
558
- clarify_visible)
559
- """
560
  note = (note or "").strip()
561
  if not note:
562
  return ("✏️ Χ›ΧͺΧ•Χ‘ Χ€ΧͺΧ§ Χ”Χ›Χ Χ‘Χ” Χ•ΧœΧ—Χ₯ Χ”Χ€Χ§ מבמך.",
563
  "", "", "", "", False)
564
 
 
 
 
 
 
 
 
565
  _lazy_init()
566
  parse = model_parse(note)
567
 
@@ -570,10 +553,7 @@ def generate(note, issuer_radio, doc_choice,
570
  "'Χ§Χ™Χ‘ΧœΧͺΧ™ 500β‚ͺ ΧžΧžΧ©Χ” גל Χ™Χ™Χ’Χ•Χ₯'.",
571
  "", "", "", "", False)
572
 
573
- issuer_status = issuer_radio if issuer_radio in ("Χ€Χ˜Χ•Χ¨", "ΧžΧ•Χ¨Χ©Χ”") else None
574
-
575
- # apply clarification answers from the note text itself
576
- cvat = clarify_vat if clarify_vat != "לא Χ¦Χ•Χ™ΧŸ" else None
577
  ctax = clarify_tax_id.strip() if clarify_tax_id and clarify_tax_id.strip() else None
578
 
579
  issuer, final, questions = decide_fiscal(
@@ -581,14 +561,13 @@ def generate(note, issuer_radio, doc_choice,
581
 
582
  if questions:
583
  msg = _build_clarification_message(questions, note)
584
- return (msg, "", "", "", "", True) # show clarification fields
585
 
586
  try:
587
  completed = core.complete(issuer, final, _STATE["rng"])
588
  except Exception as e:
589
  return (f"⚠️ שגיאה: {e}", "", "", "", "", False)
590
 
591
- # override VAT display for receipt (no breakdown)
592
  show_vat = (final["doc_type"] != "receipt")
593
 
594
  return ("βœ… Χ”ΧžΧ‘ΧžΧš Χ”Χ•Χ€Χ§ Χ‘Χ”Χ¦ΧœΧ—Χ”.",
@@ -598,59 +577,6 @@ def generate(note, issuer_radio, doc_choice,
598
  render_recommendations(recommend(note)),
599
  False)
600
 
601
- """'Behind the scenes' panel: what the MODEL extracted vs what the
602
- DETERMINISTIC engine computed. Demonstrates the model/rules separation β€”
603
- the model predicts only the extraction; all arithmetic is rule-based."""
604
- items = parse.get("items", [])
605
- item_rows = "".join(
606
- f'<div style="display:flex;justify-content:space-between;font-size:12px;'
607
- f'color:#334155;padding:3px 0;"><span>{it.get("description","")}</span>'
608
- f'<span style="color:#64748b;">{it.get("quantity",1)} Γ— β‚ͺ{it.get("unit_price",0):,.0f}</span></div>'
609
- for it in items)
610
-
611
- chips = (
612
- f'<span style="background:#ecfdf5;color:#047857;border:1px solid #a7f3d0;'
613
- f'border-radius:999px;padding:2px 9px;font-size:11px;">ΧœΧ§Χ•Χ—: {parse.get("client_name","β€”")}</span>'
614
- f'<span style="background:#ecfdf5;color:#047857;border:1px solid #a7f3d0;'
615
- f'border-radius:999px;padding:2px 9px;font-size:11px;">'
616
- f'{"Χ’Χ‘Χ§" if parse.get("client_is_business") else "Χ€Χ¨Χ˜Χ™"}</span>'
617
- f'<span style="background:#ecfdf5;color:#047857;border:1px solid #a7f3d0;'
618
- f'border-radius:999px;padding:2px 9px;font-size:11px;">{len(items)} Χ€Χ¨Χ™Χ˜Χ™Χ</span>')
619
-
620
- alloc_line = ""
621
- if completed.get("allocation_required"):
622
- alloc_line = ('<div style="font-size:12px;color:#334155;padding:3px 0;">'
623
- 'מב׀ר הקצאה <span style="color:#0d9488;">βœ“ Χ Χ“Χ¨Χ©</span> '
624
- '(Χͺנאי: Χ—Χ©Χ‘Χ•Χ Χ™Χͺ Β· Χ’Χ‘Χ§ Β· β‰₯ Χ‘Χ£)</div>')
625
-
626
- return f"""
627
- <div dir="rtl" style="font-family:'Segoe UI',Arial,sans-serif;max-width:660px;
628
- margin:14px auto 0;display:grid;grid-template-columns:1fr 1fr;gap:12px;">
629
-
630
- <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:14px 16px;">
631
- <div style="font-size:11px;font-weight:700;color:#0d9488;letter-spacing:.05em;
632
- text-transform:uppercase;margin-bottom:10px;">🧠 Χ”ΧžΧ•Χ“Χœ Χ—Χ™ΧœΧ₯</div>
633
- <div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px;">{chips}</div>
634
- {item_rows}
635
- <div style="font-size:10px;color:#94a3b8;margin-top:10px;border-top:1px dashed #e2e8f0;
636
- padding-top:8px;">gemma-2-2b + LoRA Β· Χ—Χ•Χ–Χ” Χ¨Χ§ <code>parse</code></div>
637
- </div>
638
-
639
- <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:14px 16px;">
640
- <div style="font-size:11px;font-weight:700;color:#7c3aed;letter-spacing:.05em;
641
- text-transform:uppercase;margin-bottom:10px;">βš™οΈ Χ”ΧžΧ Χ•Χ’ Χ—Χ™Χ©Χ‘</div>
642
- <div style="font-size:12px;color:#334155;padding:3px 0;">מג"מ {int(round(completed.get("vat_rate",0)*100))}% ·
643
- β‚ͺ{completed.get("vat_amount",0):,.2f}</div>
644
- <div style="font-size:12px;color:#334155;padding:3px 0;">Χ‘Χ”"Χ› β‚ͺ{completed.get("total",0):,.2f}</div>
645
- <div style="font-size:12px;color:#334155;padding:3px 0;">מב׳ מבמך {completed.get("serial_number","")}</div>
646
- {alloc_line}
647
- <div style="font-size:10px;color:#94a3b8;margin-top:10px;border-top:1px dashed #e2e8f0;
648
- padding-top:8px;">Χ“Χ˜Χ¨ΧžΧ™Χ Χ™Χ‘Χ˜Χ™ Β· א׀ב Χ”Χ–Χ™Χ•Χͺ Χ‘Χ—Χ©Χ‘Χ•ΧŸ</div>
649
- </div>
650
- </div>"""
651
-
652
-
653
-
654
  # ═════════════════════════════════════════════════════════════════════════════
655
  # 7. UI β€” Gradio 6.17 compatible, ChatGPT/Claude style
656
  # ═════════════════════════════════════════════════════════════════════════════
 
430
 
431
  def decide_fiscal(text, issuer_status, parse, explicit_doc_choice,
432
  clarify_vat_basis=None, clarify_client_tax_id=None):
433
+ """Returns (issuer_dict, final_parse, questions_dict)."""
 
 
 
 
 
 
434
  today = _today()
435
 
436
+ # ── 1. Issuer required first β€” no model run needed if missing ────────────
437
  if not issuer_status:
438
+ return None, None, {"issuer_status": "Χ‘Χ—Χ¨ Χ‘Χ˜Χ˜Χ•Χ‘ ΧžΧ Χ€Χ™Χ§ ΧœΧžΧ’ΧœΧ” (Χ’Χ•Χ‘Χ§ Χ€Χ˜Χ•Χ¨ / Χ’Χ•Χ‘Χ§ ΧžΧ•Χ¨Χ©Χ”)."}
439
+
440
+ questions = {}
441
  exempt = (issuer_status == "Χ€Χ˜Χ•Χ¨")
442
  issuer = {**DEMO_ISSUER_BASE,
443
  "status": "exempt_dealer" if exempt else "authorized_dealer",
444
  "is_company": False}
445
 
446
+ # ── 2. Mandatory content ──────────────────────────────────────────────────
447
  if not parse.get("client_name", "").strip():
448
  questions["client_name"] = "שם Χ”ΧœΧ§Χ•Χ— / Χ”ΧžΧ©ΧœΧ?"
449
  if not parse.get("items"):
450
  questions["items"] = "ΧžΧ” Χ”Χ©Χ™Χ¨Χ•Χͺ/Χ”ΧžΧ•Χ¦Χ¨ ובאיזה בכום? (למשל: Χ™Χ™Χ’Χ•Χ₯ Χ’Χ‘Χ§Χ™ β‚ͺ500)"
451
 
452
+ # ── 3. Date ───────────────────────────────────────────────────────────────
 
453
  parse = dict(parse)
454
+ parse.setdefault("date", _detect_date_from_text(text) or today.isoformat())
455
 
456
+ # ── 4. Doc type + VAT basis ───────────────────────────────────────────────
457
+ is_gross = _text_has(text, _GROSS_PATTERNS) or clarify_vat_basis == "Χ›Χ•ΧœΧœ מג״מ"
458
+ is_net = _text_has(text, _NET_PATTERNS) or clarify_vat_basis == "ΧœΧ€Χ Χ™ מג״מ"
459
  has_vat_cue = is_gross or is_net
460
 
461
  if exempt:
462
+ doc_type, amount_basis = "receipt", "net"
463
+
 
464
  elif explicit_doc_choice and explicit_doc_choice != "Χ–Χ™Χ”Χ•Χ™ ΧΧ•Χ˜Χ•ΧžΧ˜Χ™":
465
+ doc_type = {"Χ§Χ‘ΧœΧ”": "receipt", "Χ—Χ©Χ‘Χ•Χ Χ™Χͺ מב": "tax_invoice",
466
+ "Χ—Χ©Χ‘Χ•Χ Χ™Χͺ מב Χ•Χ§Χ‘ΧœΧ”": "tax_invoice_receipt"}.get(explicit_doc_choice, "receipt")
 
 
 
 
467
  if doc_type != "receipt":
468
+ if is_gross:
469
+ amount_basis = "gross"
470
+ elif is_net:
471
+ amount_basis = "net"
472
+ elif clarify_vat_basis in (None, "לא Χ¦Χ•Χ™ΧŸ"):
473
+ total = sum(i.get("unit_price",0)*i.get("quantity",1) for i in parse.get("items",[]))
474
+ questions["vat_basis"] = f"הבכום (β‚ͺ{total:,.0f}) β€” Χ›Χ•ΧœΧœ מג״מ או ΧœΧ€Χ Χ™ מג״מ?"
475
+ amount_basis = "net"
476
  else:
477
  amount_basis = "net"
478
  else:
479
  amount_basis = "net"
480
  else:
481
+ # Auto: VAT cue β†’ tax_invoice_receipt; no cue β†’ receipt (no VAT)
482
  if has_vat_cue:
483
+ doc_type, amount_basis = "tax_invoice_receipt", "gross" if is_gross else "net"
 
484
  else:
485
+ doc_type, amount_basis = "receipt", "net"
 
 
 
 
 
 
486
 
487
+ # ── 5. Allocation: Χ—.Χ€. only for Χ‘Χ’"מ/Χ—Χ‘Χ¨Χ” clients + large amounts ───────
488
  if doc_type in ("tax_invoice", "tax_invoice_receipt") and not exempt:
489
+ subtotal = sum(it.get("unit_price",0)*it.get("quantity",1) for it in parse.get("items",[]))
490
+ threshold = core.allocation_threshold_for_date(_dt.date.fromisoformat(parse["date"]))
491
+ name = parse.get("client_name", "")
492
+ is_corp = any(kw in name for kw in ['Χ‘Χ’"מ', "Χ‘Χ’Χž", "Χ—Χ‘Χ¨Χ”", "Χ‘Χ’'מ", "Χ‘Χ’.מ"])
493
+ if is_corp and subtotal >= threshold:
494
+ if clarify_client_tax_id and clarify_client_tax_id.strip():
 
 
495
  parse["client_tax_id"] = clarify_client_tax_id.strip()
496
+ elif not parse.get("client_tax_id"):
497
+ questions["client_tax_id"] = f"Χ—.Χ€. Χ”ΧœΧ§Χ•Χ— (Χ Χ“Χ¨Χ© ΧœΧ”Χ§Χ¦ΧΧ” β€” Χ’Χ‘Χ§Χ” β‰₯ β‚ͺ{threshold:,})"
 
498
 
499
+ parse.update({"doc_type": doc_type, "amount_basis": amount_basis})
 
500
  parse.setdefault("payment_method", "bank_transfer")
501
  parse.setdefault("currency", "ILS")
502
  parse.setdefault("client_tax_id", None)
 
532
 
533
  def generate(note, issuer_radio, doc_choice,
534
  clarify_vat, clarify_tax_id):
535
+ """Single-shot, stateless pipeline."""
 
 
 
 
536
  note = (note or "").strip()
537
  if not note:
538
  return ("✏️ Χ›ΧͺΧ•Χ‘ Χ€ΧͺΧ§ Χ”Χ›Χ Χ‘Χ” Χ•ΧœΧ—Χ₯ Χ”Χ€Χ§ מבמך.",
539
  "", "", "", "", False)
540
 
541
+ issuer_status = issuer_radio if issuer_radio in ("Χ€Χ˜Χ•Χ¨", "ΧžΧ•Χ¨Χ©Χ”") else None
542
+
543
+ # ── Check issuer BEFORE running the model (saves 90s on CPU) ─────────────
544
+ if not issuer_status:
545
+ return ("πŸ‘€ Χ‘Χ—Χ¨ ΧͺΧ—Χ™ΧœΧ”: **Χ’Χ•Χ‘Χ§ Χ€Χ˜Χ•Χ¨** או **Χ’Χ•Χ‘Χ§ ΧžΧ•Χ¨Χ©Χ”** ΧœΧžΧ’ΧœΧ”.",
546
+ "", "", "", "", False)
547
+
548
  _lazy_init()
549
  parse = model_parse(note)
550
 
 
553
  "'Χ§Χ™Χ‘ΧœΧͺΧ™ 500β‚ͺ ΧžΧžΧ©Χ” גל Χ™Χ™Χ’Χ•Χ₯'.",
554
  "", "", "", "", False)
555
 
556
+ cvat = clarify_vat if clarify_vat not in (None, "לא Χ¦Χ•Χ™ΧŸ") else None
 
 
 
557
  ctax = clarify_tax_id.strip() if clarify_tax_id and clarify_tax_id.strip() else None
558
 
559
  issuer, final, questions = decide_fiscal(
 
561
 
562
  if questions:
563
  msg = _build_clarification_message(questions, note)
564
+ return (msg, "", "", "", "", True)
565
 
566
  try:
567
  completed = core.complete(issuer, final, _STATE["rng"])
568
  except Exception as e:
569
  return (f"⚠️ שגיאה: {e}", "", "", "", "", False)
570
 
 
571
  show_vat = (final["doc_type"] != "receipt")
572
 
573
  return ("βœ… Χ”ΧžΧ‘ΧžΧš Χ”Χ•Χ€Χ§ Χ‘Χ”Χ¦ΧœΧ—Χ”.",
 
577
  render_recommendations(recommend(note)),
578
  False)
579
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  # ═════════════════════════════════════════════════════════════════════════════
581
  # 7. UI β€” Gradio 6.17 compatible, ChatGPT/Claude style
582
  # ═════════════════════════════════════════════════════════════════════════════