Avisut commited on
Commit
c4e49b0
Β·
verified Β·
1 Parent(s): 36f4962

Update media_plan_templater.py

Browse files
Files changed (1) hide show
  1. media_plan_templater.py +104 -91
media_plan_templater.py CHANGED
@@ -610,107 +610,120 @@ def render(get_conn):
610
 
611
  updated_inputs = edited_inputs.copy()
612
 
613
- # ── Row-by-row dependent Selectboxes with PACKAGE_EXIST rules
614
- st.markdown("#### Choose Placement for each row")
615
-
616
- col_a, col_b = st.columns([1, 4])
617
- with col_a:
618
- if st.button("Reload options", use_container_width=True):
619
- fetch_media_plan_options.clear()
620
- st.rerun()
621
- with col_b:
622
- st.caption("When a package *exists*, Details auto-fills with its elements and the Placement selector is disabled.")
623
-
 
624
  for idx, row in updated_inputs.iterrows():
625
- pkg = (row.get("Product") or "").strip()
626
- exists = opts["packageExistsMap"].get(pkg, False)
627
- label = f"Row {idx+1} placement (Package: {pkg or 'β€”'})"
628
-
629
- if exists:
630
- # Clear any stale placement and render disabled selector
631
- updated_inputs.at[idx, "Placement"] = ""
632
- st.selectbox(
633
- label,
634
- options=["(package-defined elements; no placement selection)"],
635
- index=0,
636
- key=f"placement_row_{idx}",
637
- disabled=True,
 
 
 
 
 
 
 
 
 
638
  )
639
- # Build bullet list from PLACEMENT_EXTRACTION for this package
640
- bullets = opts["extractionMap"].get(pkg, [])
641
- details_text = "\n".join(f"β€’ {b}" for b in bullets) if bullets else ""
642
- updated_inputs.at[idx, "Details"] = details_text
643
- else:
644
- choices = build_row_placement_choices(row)
645
- if choices:
646
- current = (row.get("Placement") or "").strip()
647
- default_ix = choices.index(current) if current in choices else 0
648
- sel = st.selectbox(
649
- label,
650
- choices,
651
- index=default_ix,
652
- key=f"placement_row_{idx}",
653
- )
654
- updated_inputs.at[idx, "Placement"] = sel
655
- # When package doesn't exist, user must select a placement; Details = selection
656
- updated_inputs.at[idx, "Details"] = sel
657
- else:
658
- updated_inputs.at[idx, "Placement"] = ""
659
- updated_inputs.at[idx, "Details"] = ""
660
  st.selectbox(
661
- label,
662
- options=["(no placements available for this package)"],
663
  index=0,
664
- key=f"placement_row_{idx}",
665
  disabled=True,
666
  )
667
-
668
- # ── Row-by-row Targeting multi-selects
669
- st.markdown("#### Choose Targeting for each row")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
670
 
671
- for idx, row in updated_inputs.iterrows():
672
- current_raw = (row.get("Targeting") or "")
673
- # Preselect any presets already present (split on 'OR')
674
- current_tokens = [p.strip() for p in re.split(r"\s+OR\s+", str(current_raw)) if p and p.strip()]
675
- default_presets = [p for p in current_tokens if p in TARGETING_OPTIONS]
 
 
 
676
 
677
- sel = st.multiselect(
678
- f"Row {idx+1} targeting",
679
- options=TARGETING_OPTIONS, # curated presets
680
- default=default_presets, # preselect if they match
681
- key=f"targeting_row_{idx}",
682
- help="Pick one or more targeting presets. You can still type custom text in the table if you prefer."
683
- )
 
684
 
685
- # If user picked presets here, overwrite this row's Targeting cell with them
686
- if sel:
687
- updated_inputs.at[idx, "Targeting"] = " OR ".join(sel)
688
-
689
- # Canonicalize what we store
690
- canonical_inputs = _normalize_input_table(updated_inputs[DEFAULT_INPUT_COLUMNS])
691
- if "Rate" in updated_inputs.columns:
692
- canonical_inputs["Rate"] = updated_inputs["Rate"].fillna("").astype(str)
693
- if "Placement" in updated_inputs.columns:
694
- canonical_inputs["Placement"] = updated_inputs["Placement"].fillna("").astype(str)
695
-
696
- st.session_state["media_plan_inputs"] = canonical_inputs
697
-
698
- # Downloadable, cleaned CSV of inputs
699
- download_df = st.session_state["media_plan_inputs"].copy()
700
- if "Targeting" in download_df.columns:
701
- download_df["Targeting"] = download_df["Targeting"].apply(_normalize_targeting_value)
702
- for numeric_col in ("Budget", "Rate"):
703
- if numeric_col in download_df.columns:
704
- download_df[numeric_col] = download_df[numeric_col].apply(
705
- lambda val: "" if pd.isna(val) else val
706
  )
707
- st.download_button(
708
- "Download input table",
709
- data=download_df.to_csv(index=False).encode("utf-8"),
710
- file_name="media_plan_inputs.csv",
711
- mime="text/csv",
712
- use_container_width=True,
713
- )
 
 
 
 
 
 
 
 
 
 
 
 
714
 
715
  # (The remainder of your app continues here: Word upload/parsing, Excel template
716
  # mapping, fill + export logic, etc. The code above includes the PACKAGE_EXIST
 
610
 
611
  updated_inputs = edited_inputs.copy()
612
 
613
+ # ─────────────────────────────────────────────────────────────────────────────
614
+ # Row-by-row controls (all selectors in one visual row per table row)
615
+ # - Package β†’ Placement dependency (bundle disables placement)
616
+ # - Targeting multiselect, Notes
617
+ # - No Details preview UI; Details is set silently
618
+ # ─────────────────────────────────────────────────────────────────────────────
619
+
620
+ st.markdown("#### Row controls")
621
+
622
+ # Fallbacks: allow placement selection even if Package is blank
623
+ ALL_PLACEMENTS = sorted({plc for lst in opts["placementMap"].values() for plc in lst})
624
+
625
  for idx, row in updated_inputs.iterrows():
626
+ pkg_current = (row.get("Product") or "").strip()
627
+ rate_current = (row.get("Rate") or "").strip()
628
+ plc_current = (row.get("Placement") or "").strip()
629
+ notes_current = str(row.get("Notes") or "")
630
+ tgt_raw = str(row.get("Targeting") or "")
631
+
632
+ # Preselect Targeting choices from existing string ("A OR B")
633
+ tgt_tokens = [p.strip() for p in re.split(r"\s+OR\s+", tgt_raw) if p.strip()]
634
+ tgt_default = [t for t in tgt_tokens if t in TARGETING_OPTIONS]
635
+
636
+ # One visual row: Package | Placement | Rate | Targeting | Notes
637
+ c_pkg, c_plc, c_rate, c_tgt, c_notes = st.columns([1.2, 1.4, 1.0, 2.0, 1.8])
638
+
639
+ with c_pkg:
640
+ pkg_options = [""] + package_opts
641
+ pkg_index = pkg_options.index(pkg_current) if pkg_current in pkg_options else 0
642
+ sel_pkg = st.selectbox(
643
+ f"Row {idx+1} β€” Package",
644
+ options=pkg_options,
645
+ index=pkg_index,
646
+ key=f"row{idx}_pkg",
647
+ help="Choose a Package. If left blank, you can still pick any Placement."
648
  )
649
+
650
+ with c_plc:
651
+ # Determine placement choices based on Package
652
+ pkg_exists = opts["packageExistsMap"].get(sel_pkg.strip(), False) if sel_pkg else False
653
+ if pkg_exists:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
  st.selectbox(
655
+ "Placement",
656
+ options=["(package-defined elements)"],
657
  index=0,
658
+ key=f"row{idx}_plc_disabled",
659
  disabled=True,
660
  )
661
+ sel_plc = ""
662
+ else:
663
+ choices = opts["placementMap"].get(sel_pkg.strip(), []) if sel_pkg else ALL_PLACEMENTS
664
+ if choices:
665
+ plc_index = choices.index(plc_current) if plc_current in choices else 0
666
+ sel_plc = st.selectbox(
667
+ "Placement",
668
+ options=choices,
669
+ index=plc_index,
670
+ key=f"row{idx}_plc",
671
+ help="Placement options depend on the selected Package."
672
+ )
673
+ else:
674
+ st.selectbox(
675
+ "Placement",
676
+ options=["(no placements available)"],
677
+ index=0,
678
+ key=f"row{idx}_plc_empty",
679
+ disabled=True,
680
+ )
681
+ sel_plc = ""
682
 
683
+ with c_rate:
684
+ rate_index = rate_opts.index(rate_current) if rate_current in rate_opts else 0
685
+ sel_rate = st.selectbox(
686
+ "Rate",
687
+ options=rate_opts,
688
+ index=rate_index,
689
+ key=f"row{idx}_rate",
690
+ )
691
 
692
+ with c_tgt:
693
+ sel_tgts = st.multiselect(
694
+ "Targeting",
695
+ options=TARGETING_OPTIONS,
696
+ default=tgt_default,
697
+ key=f"row{idx}_tgt",
698
+ help="Pick one or more presets; saved as 'A OR B'."
699
+ )
700
 
701
+ with c_notes:
702
+ sel_notes = st.text_input(
703
+ "Notes",
704
+ value=notes_current,
705
+ key=f"row{idx}_notes",
706
+ placeholder="Optional",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
707
  )
708
+
709
+ # Write back to the table
710
+ updated_inputs.at[idx, "Product"] = sel_pkg
711
+ updated_inputs.at[idx, "Rate"] = sel_rate
712
+ updated_inputs.at[idx, "Notes"] = sel_notes
713
+
714
+ if sel_tgts:
715
+ updated_inputs.at[idx, "Targeting"] = " OR ".join(sel_tgts)
716
+
717
+ # Details logic (silent)
718
+ if sel_pkg and opts["packageExistsMap"].get(sel_pkg.strip(), False):
719
+ # Bundle: Details = package elements; no placement
720
+ updated_inputs.at[idx, "Placement"] = ""
721
+ bullets = opts["extractionMap"].get(sel_pkg.strip(), [])
722
+ updated_inputs.at[idx, "Details"] = "\n".join(f"β€’ {b}" for b in bullets) if bullets else ""
723
+ else:
724
+ # Non-bundle or no package: Details = chosen placement
725
+ updated_inputs.at[idx, "Placement"] = sel_plc
726
+ updated_inputs.at[idx, "Details"] = sel_plc
727
 
728
  # (The remainder of your app continues here: Word upload/parsing, Excel template
729
  # mapping, fill + export logic, etc. The code above includes the PACKAGE_EXIST