Spaces:
Sleeping
Sleeping
| import re | |
| import gradio as gr | |
| CUSTOM_CSS = """ | |
| body { | |
| background: linear-gradient(180deg, #f4f6f8 0%, #edf1f5 100%); | |
| } | |
| .gradio-container { | |
| max-width: 1280px !important; | |
| padding-top: 20px !important; | |
| padding-bottom: 36px !important; | |
| font-family: "Aptos", "Segoe UI", "Helvetica Neue", sans-serif !important; | |
| color: #1c2733; | |
| } | |
| .hero-shell { | |
| background: linear-gradient(135deg, #11283f 0%, #1a3d5f 100%); | |
| border-radius: 10px; | |
| overflow: hidden; | |
| box-shadow: 0 18px 40px rgba(18, 36, 53, 0.16); | |
| margin-bottom: 18px; | |
| border: 1px solid rgba(255, 255, 255, 0.08); | |
| } | |
| .hero-inner { | |
| padding: 30px 34px; | |
| color: #ffffff; | |
| } | |
| .eyebrow { | |
| display: inline-block; | |
| font-size: 0.78rem; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| color: #ffffff !important; | |
| margin-bottom: 12px; | |
| } | |
| .hero-title { | |
| margin: 0 0 12px 0; | |
| font-size: 2.5rem; | |
| line-height: 1.08; | |
| font-weight: 800; | |
| letter-spacing: -0.02em; | |
| color: #ffffff !important; | |
| opacity: 1 !important; | |
| text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18); | |
| } | |
| .hero-subtitle { | |
| max-width: 860px; | |
| margin: 0; | |
| font-size: 1.02rem; | |
| line-height: 1.75; | |
| color: #ffffff !important; | |
| } | |
| .hero-bar { | |
| display: grid; | |
| grid-template-columns: repeat(4, minmax(0, 1fr)); | |
| gap: 1px; | |
| margin-top: 22px; | |
| background: rgba(255, 255, 255, 0.12); | |
| border-radius: 8px; | |
| overflow: hidden; | |
| } | |
| .hero-stat { | |
| background: rgba(255, 255, 255, 0.08); | |
| padding: 14px 16px; | |
| } | |
| .hero-stat strong { | |
| display: block; | |
| font-size: 0.92rem; | |
| margin-bottom: 4px; | |
| color: #ffffff !important; | |
| } | |
| .hero-stat span { | |
| font-size: 0.84rem; | |
| color: #ffffff !important; | |
| line-height: 1.45; | |
| } | |
| .panel { | |
| background: #ffffff; | |
| border: 1px solid #d7dde5; | |
| border-radius: 10px; | |
| box-shadow: 0 8px 20px rgba(31, 47, 61, 0.06); | |
| overflow: hidden; | |
| padding: 0 !important; | |
| } | |
| /* When .panel is applied to a gr.Column, neutralize the Gradio block | |
| wrapping the panel-head HTML so the gradient bleeds edge-to-edge. | |
| The header lives inside the FIRST .block child of .panel. */ | |
| .gradio-container .panel > .block:first-child, | |
| .gradio-container .panel > div:first-child { | |
| background: transparent !important; | |
| background-color: transparent !important; | |
| border: none !important; | |
| box-shadow: none !important; | |
| padding: 0 !important; | |
| margin: 0 !important; | |
| } | |
| /* Give all body content (everything except the header) consistent | |
| internal padding so the textbox, buttons, and examples don't touch | |
| the rounded edges of the panel. */ | |
| .gradio-container .panel > *:not(:first-child) { | |
| padding-left: 22px !important; | |
| padding-right: 22px !important; | |
| } | |
| .gradio-container .panel > *:nth-child(2) { | |
| padding-top: 18px !important; | |
| } | |
| .gradio-container .panel > *:last-child { | |
| padding-bottom: 22px !important; | |
| } | |
| .panel-head { | |
| padding: 20px 22px 14px 22px; | |
| border-bottom: 1px solid #e3e8ee; | |
| background: linear-gradient(180deg, #fbfcfd 0%, #f5f8fb 100%); | |
| } | |
| .panel-title { | |
| margin: 0 0 6px 0; | |
| font-size: 1.2rem; | |
| font-weight: 800; | |
| color: #17324a; | |
| } | |
| .panel-copy { | |
| margin: 0; | |
| color: #56697a; | |
| font-size: 0.95rem; | |
| line-height: 1.6; | |
| } | |
| .panel-body { | |
| padding: 20px 22px 22px 22px; | |
| } | |
| .metric-strip { | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 12px; | |
| margin: 0; | |
| padding: 18px 22px 4px 22px; | |
| } | |
| .metric-card { | |
| background: #f8fafc; | |
| border: 1px solid #dfe5eb; | |
| border-left: 4px solid #244e73; | |
| border-radius: 8px; | |
| padding: 14px 14px 12px 14px; | |
| } | |
| .metric-card strong { | |
| display: block; | |
| color: #173954; | |
| font-size: 1rem; | |
| margin-bottom: 4px; | |
| } | |
| .metric-card span { | |
| color: #607180; | |
| font-size: 0.88rem; | |
| line-height: 1.45; | |
| } | |
| /* Base Textbox & Textarea styling */ | |
| textarea, [data-testid="textbox"] textarea { | |
| background: #ffffff !important; | |
| border: 1px solid #ccd5de !important; | |
| border-radius: 8px !important; | |
| font-size: 0.98rem !important; | |
| color: #27272A !important; | |
| box-shadow: none !important; | |
| } | |
| textarea::placeholder, [data-testid="textbox"] textarea::placeholder { | |
| color: #7a8896 !important; | |
| } | |
| textarea:focus, [data-testid="textbox"] textarea:focus { | |
| border: 1px solid #244e73 !important; | |
| box-shadow: 0 0 0 3px rgba(36, 78, 115, 0.12) !important; | |
| } | |
| /* Button styling */ | |
| button.primary { | |
| background: #173a59 !important; | |
| color: #ffffff !important; | |
| border: 1px solid #173a59 !important; | |
| border-radius: 8px !important; | |
| min-height: 46px !important; | |
| font-weight: 700 !important; | |
| transition: all 0.2s ease !important; | |
| } | |
| button.primary:hover { | |
| background: #214d75 !important; | |
| border-color: #214d75 !important; | |
| color: #ffffff !important; | |
| } | |
| button.secondary { | |
| background: #ffffff !important; | |
| color: #173954 !important; | |
| border: 1px solid #ccd5de !important; | |
| border-radius: 8px !important; | |
| min-height: 46px !important; | |
| font-weight: 700 !important; | |
| transition: all 0.2s ease !important; | |
| } | |
| button.secondary:hover { | |
| background: #eef4f8 !important; | |
| color: #16324a !important; | |
| border-color: #b8c7d4 !important; | |
| } | |
| /* Output Prose styling */ | |
| .prose, .prose * { | |
| color: #1e2f3d !important; | |
| line-height: 1.72 !important; | |
| } | |
| .prose h1, .prose h2, .prose h3 { | |
| color: #16324a !important; | |
| font-weight: 800 !important; | |
| } | |
| /* Footer Banner */ | |
| .footer-banner { | |
| margin-top: 16px; | |
| background: #ffffff; | |
| border: 1px solid #d8dfe6; | |
| border-radius: 10px; | |
| padding: 16px 20px; | |
| color: #4d6272; | |
| font-size: 0.94rem; | |
| line-height: 1.65; | |
| box-shadow: 0 8px 18px rgba(31, 47, 61, 0.05); | |
| } | |
| .footer-banner strong { | |
| color: #16324a; | |
| } | |
| /* Keep hero stats white no matter what */ | |
| .hero-shell *, | |
| .hero-inner *, | |
| .hero-subtitle, | |
| .eyebrow, | |
| .hero-stat strong, | |
| .hero-stat span { | |
| color: #ffffff !important; | |
| } | |
| @media (max-width: 900px) { | |
| .hero-title { | |
| font-size: 2rem; | |
| } | |
| .hero-bar, | |
| .metric-strip { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| /* ========================================= | |
| SLEDGEHAMMER OVERRIDES FOR GRADIO BUGS | |
| ========================================= */ | |
| /* 1. HERO TITLE FIX (Bypasses Gradio's internal h1 color reset) */ | |
| .gradio-container .hero-shell h1.hero-title, | |
| .gradio-container .hero-shell h1, | |
| .hero-shell .hero-title { | |
| color: #ffffff !important; | |
| -webkit-text-fill-color: #ffffff !important; /* Forces white even if webkit overrides it */ | |
| opacity: 1 !important; | |
| text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18) !important; | |
| margin: 0 0 12px 0 !important; | |
| font-size: 2.5rem !important; | |
| font-weight: 800 !important; | |
| } | |
| /* 2. TABS VISIBILITY FIX */ | |
| div.tab-nav { | |
| gap: 8px !important; | |
| padding: 14px 22px 0 22px !important; | |
| background: #ffffff !important; | |
| } | |
| button[role="tab"] { | |
| color: #41586b !important; /* Forces unselected text to be dark grey */ | |
| border-radius: 999px !important; | |
| border: 1px solid #d5dce4 !important; | |
| background: #f7f9fb !important; | |
| font-weight: 700 !important; | |
| transition: all 0.2s ease !important; | |
| } | |
| button[role="tab"]:hover { | |
| background: #edf3f8 !important; | |
| color: #173954 !important; | |
| border-color: #bfd0dd !important; | |
| } | |
| button[role="tab"].selected { | |
| background: #173a59 !important; /* Force background on active */ | |
| color: #ffffff !important; /* Force white text on active */ | |
| border-color: #173a59 !important; | |
| } | |
| /* ========================================================= | |
| EXAMPLES (gr.Examples -> gr.Dataset) STYLING | |
| gr.Examples renders a gr.Dataset. Its root is .dataset, and the | |
| clickable items are .gallery-item / table rows depending on type | |
| (here, single-text examples render as a samples table OR as | |
| gallery buttons depending on Gradio version). We scope everything | |
| under our own .ba-examples class (set via elem_classes) and use | |
| high-specificity selectors with the .gradio-container prefix to | |
| beat Gradio's default grey :hover. | |
| ========================================================= */ | |
| /* Section label "Sample Business Scenarios" */ | |
| .gradio-container .ba-examples .label-wrap, | |
| .gradio-container .ba-examples .label-wrap *, | |
| .gradio-container .ba-examples > label, | |
| .gradio-container .ba-examples > label * { | |
| color: #000000 !important; | |
| font-weight: 700 !important; | |
| opacity: 1 !important; | |
| } | |
| /* ---- BASE (un-hovered) state ---- | |
| Cover every form the dataset items can take: table cells, | |
| gallery items, and generic buttons. */ | |
| .gradio-container .ba-examples .dataset td, | |
| .gradio-container .ba-examples table td, | |
| .gradio-container .ba-examples .gallery-item, | |
| .gradio-container .ba-examples button, | |
| .gradio-container .ba-examples [role="button"] { | |
| background-color: #ffffff !important; | |
| border: 1px solid #ccd5de !important; | |
| color: #000000 !important; | |
| } | |
| .gradio-container .ba-examples .dataset td *, | |
| .gradio-container .ba-examples table td *, | |
| .gradio-container .ba-examples .gallery-item *, | |
| .gradio-container .ba-examples button *, | |
| .gradio-container .ba-examples [role="button"] * { | |
| color: #000000 !important; | |
| -webkit-text-fill-color: #000000 !important; | |
| opacity: 1 !important; | |
| } | |
| /* ---- HOVER / SELECTED state ---- | |
| This is the part that kept failing. Gradio applies its own grey | |
| hover with high specificity, so we match the SAME elements and | |
| force BOTH background and text. The .gradio-container prefix + | |
| element + pseudo gives us enough specificity to win. */ | |
| .gradio-container .ba-examples .dataset tr:hover td, | |
| .gradio-container .ba-examples .dataset td:hover, | |
| .gradio-container .ba-examples table tr:hover td, | |
| .gradio-container .ba-examples table td:hover, | |
| .gradio-container .ba-examples .gallery-item:hover, | |
| .gradio-container .ba-examples button:hover, | |
| .gradio-container .ba-examples [role="button"]:hover, | |
| .gradio-container .ba-examples .gallery-item.selected, | |
| .gradio-container .ba-examples tr.selected td, | |
| .gradio-container .ba-examples [aria-selected="true"] { | |
| background-color: #173a59 !important; /* dark blue */ | |
| border-color: #173a59 !important; | |
| } | |
| /* Force the TEXT white on hover/selected — target the element itself | |
| AND all descendants, with -webkit-text-fill-color to defeat any | |
| webkit text-fill override. */ | |
| .gradio-container .ba-examples .dataset tr:hover td, | |
| .gradio-container .ba-examples .dataset tr:hover td *, | |
| .gradio-container .ba-examples .dataset td:hover, | |
| .gradio-container .ba-examples .dataset td:hover *, | |
| .gradio-container .ba-examples table tr:hover td, | |
| .gradio-container .ba-examples table tr:hover td *, | |
| .gradio-container .ba-examples table td:hover, | |
| .gradio-container .ba-examples table td:hover *, | |
| .gradio-container .ba-examples .gallery-item:hover, | |
| .gradio-container .ba-examples .gallery-item:hover *, | |
| .gradio-container .ba-examples button:hover, | |
| .gradio-container .ba-examples button:hover *, | |
| .gradio-container .ba-examples [role="button"]:hover, | |
| .gradio-container .ba-examples [role="button"]:hover *, | |
| .gradio-container .ba-examples .gallery-item.selected, | |
| .gradio-container .ba-examples .gallery-item.selected *, | |
| .gradio-container .ba-examples tr.selected td, | |
| .gradio-container .ba-examples tr.selected td *, | |
| .gradio-container .ba-examples [aria-selected="true"], | |
| .gradio-container .ba-examples [aria-selected="true"] * { | |
| color: #ffffff !important; | |
| -webkit-text-fill-color: #ffffff !important; | |
| opacity: 1 !important; | |
| } | |
| /* ========================================================= | |
| GRADIO FOOTER FIX | |
| The default footer ("Use via API · Built with Gradio · Settings") | |
| has a broken hover state — text fades to nearly invisible on hover. | |
| We give it a readable base color and a clearly visible hover color | |
| that matches the app's dark-blue accent. Targets <footer> directly | |
| plus all link/button descendants in any version of Gradio. */ | |
| .gradio-container footer, | |
| .gradio-container .footer, | |
| footer.svelte-1ax1toq, | |
| footer { | |
| background: transparent !important; | |
| border-top: 1px solid #d8dfe6 !important; | |
| padding: 12px 18px !important; | |
| } | |
| /* Base state — readable medium-grey */ | |
| .gradio-container footer, | |
| .gradio-container footer *, | |
| .gradio-container footer a, | |
| .gradio-container footer a *, | |
| .gradio-container footer button, | |
| .gradio-container footer button *, | |
| .gradio-container .footer a, | |
| .gradio-container .footer button { | |
| color: #4d6272 !important; | |
| -webkit-text-fill-color: #4d6272 !important; | |
| opacity: 1 !important; | |
| text-decoration: none !important; | |
| font-weight: 600 !important; | |
| } | |
| /* Hover state — dark blue, fully opaque, underline for affordance */ | |
| .gradio-container footer a:hover, | |
| .gradio-container footer a:hover *, | |
| .gradio-container footer button:hover, | |
| .gradio-container footer button:hover *, | |
| .gradio-container footer a:focus, | |
| .gradio-container footer a:focus *, | |
| .gradio-container footer button:focus, | |
| .gradio-container footer button:focus *, | |
| .gradio-container .footer a:hover, | |
| .gradio-container .footer button:hover { | |
| color: #173a59 !important; | |
| -webkit-text-fill-color: #173a59 !important; | |
| opacity: 1 !important; | |
| text-decoration: underline !important; | |
| background: transparent !important; | |
| cursor: pointer !important; | |
| } | |
| /* Keep the separator dots and emojis visible too */ | |
| .gradio-container footer span, | |
| .gradio-container footer svg { | |
| color: #4d6272 !important; | |
| -webkit-text-fill-color: #4d6272 !important; | |
| opacity: 1 !important; | |
| } | |
| /* ========================================================= | |
| GRADIO 6 LIGHT-THEME ENFORCEMENT | |
| In Gradio 6 + Svelte 5, the .block and .form wrappers | |
| around components (Textbox, Markdown, etc.) default to a | |
| dark fill when the browser is in dark mode or when CSS | |
| variables resolve to dark values. This makes the area | |
| AROUND the textbox look black on light pages. We pin all | |
| the relevant CSS variables to light values and force the | |
| wrapper backgrounds explicitly. */ | |
| .gradio-container, | |
| .gradio-container * { | |
| --block-background-fill: #ffffff !important; | |
| --block-border-color: #d7dde5 !important; | |
| --block-label-background-fill: transparent !important; | |
| --block-label-text-color: #173a59 !important; | |
| --block-title-text-color: #173a59 !important; | |
| --body-background-fill: transparent !important; | |
| --background-fill-primary: #ffffff !important; | |
| --background-fill-secondary: #f5f8fb !important; | |
| --panel-background-fill: #ffffff !important; | |
| --input-background-fill: #ffffff !important; | |
| --input-border-color: #ccd5de !important; | |
| --color-accent-soft: #eef4f8 !important; | |
| --neutral-50: #f8fafc !important; | |
| --neutral-100: #f1f5f9 !important; | |
| --neutral-200: #e2e8f0 !important; | |
| } | |
| /* Force every block/form/wrap to render light */ | |
| .gradio-container .block, | |
| .gradio-container .form, | |
| .gradio-container .wrap, | |
| .gradio-container [class*="block-"], | |
| .gradio-container [data-testid="block-info"] { | |
| background: #ffffff !important; | |
| background-color: #ffffff !important; | |
| border-color: #d7dde5 !important; | |
| color: #173a59 !important; | |
| } | |
| /* The column/row containers themselves stay transparent so the | |
| page background shows through (matching the surrounding panels). */ | |
| .gradio-container .gr-column, | |
| .gradio-container .gr-row, | |
| .gradio-container > .main, | |
| .gradio-container > .main > .wrap { | |
| background: transparent !important; | |
| background-color: transparent !important; | |
| } | |
| /* Labels (the "Raw Stakeholder Notes" text) — dark on light */ | |
| .gradio-container label, | |
| .gradio-container label *, | |
| .gradio-container .label-wrap, | |
| .gradio-container .label-wrap *, | |
| .gradio-container span[data-testid="block-info"], | |
| .gradio-container .block > .label, | |
| .gradio-container .block > label > span, | |
| .gradio-container .block > label > .label { | |
| color: #173a59 !important; | |
| -webkit-text-fill-color: #173a59 !important; | |
| background: transparent !important; | |
| background-color: transparent !important; | |
| opacity: 1 !important; | |
| font-weight: 700 !important; | |
| } | |
| """ | |
| def guess_title(notes): | |
| text = notes.lower() | |
| if "checkout" in text or "cart" in text or "payment" in text: | |
| return "Checkout Experience Improvement" | |
| if "hospital" in text or "patient" in text or "reception" in text: | |
| return "Patient Registration Process Improvement" | |
| if "login" in text or "signup" in text or "authentication" in text: | |
| return "Login and Access Experience Improvement" | |
| if "dashboard" in text: | |
| return "Operational Dashboard Enhancement" | |
| return "Business Process Improvement Initiative" | |
| def extract_payment_methods(text): | |
| methods = [] | |
| options = ["upi", "cards", "wallets", "net banking", "cod", "cash on delivery"] | |
| lowered = text.lower() | |
| for item in options: | |
| if item in lowered: | |
| methods.append(item.upper() if item == "upi" else item.title()) | |
| return methods | |
| def build_objectives(text): | |
| objectives = [] | |
| lowered = text.lower() | |
| if "cart" in lowered or "checkout" in lowered: | |
| objectives.append("Reduce checkout drop-off and improve completion rate") | |
| if "mobile" in lowered: | |
| objectives.append("Improve the experience for mobile users") | |
| if "support" in lowered: | |
| objectives.append("Reduce support tickets related to the process") | |
| if "faster" in lowered or "slow" in lowered: | |
| objectives.append("Reduce time required to complete the process") | |
| if "payment" in lowered: | |
| objectives.append("Make payment completion simpler and more flexible") | |
| if "dashboard" in lowered: | |
| objectives.append("Provide clearer visibility for business users") | |
| if not objectives: | |
| objectives = [ | |
| "Improve process efficiency", | |
| "Increase consistency in user experience", | |
| "Reduce manual effort" | |
| ] | |
| return objectives[:4] | |
| def build_scope(text): | |
| scope = [] | |
| lowered = text.lower() | |
| if "checkout" in lowered or "cart" in lowered: | |
| scope.append("Covers the end-to-end checkout journey") | |
| if "payment" in lowered: | |
| scope.append("Includes payment option selection and payment completion flow") | |
| if "guest checkout" in lowered or "guest" in lowered: | |
| scope.append("Includes guest user purchase flow") | |
| if "mobile" in lowered or "tablet" in lowered: | |
| scope.append("Includes responsive experience on mobile and tablet devices") | |
| if "dashboard" in lowered: | |
| scope.append("Includes dashboard access for internal users") | |
| if not scope: | |
| scope = [ | |
| "Covers the target business process described in the stakeholder notes", | |
| "Includes the main user interactions and supporting business workflow" | |
| ] | |
| return scope | |
| def build_functional_requirements(text): | |
| reqs = [] | |
| lowered = text.lower() | |
| payments = extract_payment_methods(text) | |
| if "guest checkout" in lowered or "guest" in lowered: | |
| reqs.append("The system should allow users to continue without mandatory account login") | |
| if payments: | |
| reqs.append(f"The system should support the following payment methods: {', '.join(payments)}") | |
| if "mobile" in lowered: | |
| reqs.append("The system should provide a mobile-friendly experience") | |
| if "tablet" in lowered: | |
| reqs.append("The system should work effectively on tablet devices") | |
| if "otp" in lowered: | |
| reqs.append("The system should support OTP-based verification") | |
| if "dashboard" in lowered: | |
| reqs.append("The system should provide a dashboard for business users") | |
| if "login" in lowered and "optional" in lowered: | |
| reqs.append("The system should make login optional where appropriate") | |
| if "faster" in lowered or "slow" in lowered: | |
| reqs.append("The system should reduce unnecessary user steps in the process") | |
| if not reqs: | |
| reqs = [ | |
| "The system should support the core business process described by stakeholders", | |
| "The system should simplify the current user journey", | |
| "The system should improve consistency across the workflow" | |
| ] | |
| return reqs[:6] | |
| def build_non_functional_requirements(text): | |
| reqs = [ | |
| "The solution should be easy to use for target users", | |
| "The solution should be reliable during normal business usage" | |
| ] | |
| lowered = text.lower() | |
| if "mobile" in lowered or "tablet" in lowered: | |
| reqs.append("The interface should be responsive across supported screen sizes") | |
| if "payment" in lowered or "otp" in lowered or "login" in lowered: | |
| reqs.append("The solution should protect user and transaction data") | |
| if "faster" in lowered or "slow" in lowered: | |
| reqs.append("The process should complete within an acceptable response time") | |
| return reqs[:4] | |
| def build_assumptions(text): | |
| assumptions = [ | |
| "Stakeholder notes represent the primary business need for the first version", | |
| "Existing systems can be updated without a full platform replacement" | |
| ] | |
| lowered = text.lower() | |
| if "launch" in lowered or "month" in lowered: | |
| assumptions.append("Delivery timeline expectations are realistic and approved by stakeholders") | |
| if "payment" in lowered: | |
| assumptions.append("Payment integrations can be configured within the current technical landscape") | |
| return assumptions[:4] | |
| def build_risks(text): | |
| risks = [ | |
| "Incomplete stakeholder input may lead to missing requirements", | |
| "Tight delivery timelines may reduce testing time" | |
| ] | |
| lowered = text.lower() | |
| if "payment" in lowered: | |
| risks.append("Payment integration complexity may delay release") | |
| if "guest" in lowered: | |
| risks.append("Guest user journeys may create reporting or tracking challenges") | |
| if "mobile" in lowered or "tablet" in lowered: | |
| risks.append("Cross-device usability issues may affect adoption") | |
| return risks[:4] | |
| def build_success_metrics(text): | |
| metrics = [ | |
| "Reduced time spent preparing business documentation", | |
| "Improved consistency of requirement outputs" | |
| ] | |
| lowered = text.lower() | |
| if "cart" in lowered or "checkout" in lowered: | |
| metrics.append("Higher checkout completion rate") | |
| if "support" in lowered: | |
| metrics.append("Lower support ticket volume") | |
| if "mobile" in lowered: | |
| metrics.append("Improved mobile task completion rate") | |
| return metrics[:4] | |
| def build_user_stories(text): | |
| lowered = text.lower() | |
| stories = [] | |
| if "guest checkout" in lowered or "guest" in lowered: | |
| stories.append({ | |
| "role": "customer", | |
| "goal": "complete checkout without creating an account", | |
| "benefit": "I can purchase faster", | |
| "given": "I have items in my cart", | |
| "when": "I proceed to checkout", | |
| "then": "I should be able to continue as a guest" | |
| }) | |
| if "payment" in lowered or extract_payment_methods(text): | |
| methods = extract_payment_methods(text) | |
| method_text = ", ".join(methods) if methods else "multiple payment options" | |
| stories.append({ | |
| "role": "customer", | |
| "goal": f"choose from {method_text}", | |
| "benefit": "I can use my preferred payment method", | |
| "given": "I am on the payment step", | |
| "when": "I review the payment options", | |
| "then": f"I should see {method_text}" | |
| }) | |
| if "mobile" in lowered or "tablet" in lowered: | |
| device = "mobile device" if "mobile" in lowered else "tablet" | |
| stories.append({ | |
| "role": "user", | |
| "goal": f"complete the journey smoothly on my {device}", | |
| "benefit": "I can finish the task without frustration", | |
| "given": f"I am using a {device}", | |
| "when": "I open the process flow", | |
| "then": "the experience should be clear and easy to use" | |
| }) | |
| if "dashboard" in lowered: | |
| stories.append({ | |
| "role": "business user", | |
| "goal": "view information in a dashboard", | |
| "benefit": "I can manage work more efficiently", | |
| "given": "I log into the dashboard", | |
| "when": "I review current records or updates", | |
| "then": "I should be able to access the information I need clearly" | |
| }) | |
| if not stories: | |
| stories.append({ | |
| "role": "end user", | |
| "goal": "complete the main process with fewer steps", | |
| "benefit": "I can save time and effort", | |
| "given": "I start the process", | |
| "when": "I move through the required steps", | |
| "then": "the journey should feel simple and efficient" | |
| }) | |
| stories.append({ | |
| "role": "business stakeholder", | |
| "goal": "receive clearer requirement documentation", | |
| "benefit": "delivery can be more consistent", | |
| "given": "stakeholder notes are captured", | |
| "when": "documentation is generated", | |
| "then": "the output should be structured and easy to review" | |
| }) | |
| return stories[:5] | |
| def format_brd(notes): | |
| title = guess_title(notes) | |
| objectives = build_objectives(notes) | |
| scope = build_scope(notes) | |
| functional = build_functional_requirements(notes) | |
| non_functional = build_non_functional_requirements(notes) | |
| assumptions = build_assumptions(notes) | |
| risks = build_risks(notes) | |
| metrics = build_success_metrics(notes) | |
| def bullets(items): | |
| return "\n".join([f"- {item}" for item in items]) | |
| brd = f"""# Business Requirements Document | |
| ## Project Title | |
| {title} | |
| ## Executive Summary | |
| This document translates unstructured stakeholder notes into a cleaner, review-ready business requirements format. It is designed to help Business Analysts move faster from conversation capture to formal documentation. | |
| ## Business Problem | |
| Stakeholder feedback suggests that the current process contains friction, inconsistency, or avoidable manual effort. A more structured solution is needed to improve user outcomes and business performance. | |
| ## Objectives | |
| {bullets(objectives)} | |
| ## Scope | |
| {bullets(scope)} | |
| ## Functional Requirements | |
| {bullets(functional)} | |
| ## Non-Functional Requirements | |
| {bullets(non_functional)} | |
| ## Assumptions | |
| {bullets(assumptions)} | |
| ## Risks | |
| {bullets(risks)} | |
| ## Success Metrics | |
| {bullets(metrics)} | |
| """ | |
| return brd | |
| def format_stories(notes): | |
| stories = build_user_stories(notes) | |
| sections = ["# BDD-Style User Stories"] | |
| for index, story in enumerate(stories, start=1): | |
| sections.append( | |
| f""" | |
| ## User Story {index} | |
| As a {story['role']}, | |
| I want to {story['goal']}, | |
| So that {story['benefit']}. | |
| ### Acceptance Criteria | |
| - Given {story['given']} | |
| - When {story['when']} | |
| - Then {story['then']} | |
| """.strip() | |
| ) | |
| return "\n\n".join(sections) | |
| def format_before_after(notes): | |
| cleaned = notes.strip() | |
| bullet_lines = [line.strip(" -•\t") for line in cleaned.splitlines() if line.strip()] | |
| if not bullet_lines: | |
| bullet_lines = [part.strip() for part in re.split(r"[.;]\s*", cleaned) if part.strip()] | |
| before = "\n".join([f"- {line}" for line in bullet_lines[:8]]) | |
| return f"""# Before vs After | |
| ## Before: Raw Stakeholder Notes | |
| {before} | |
| ## After: Business-Ready Output | |
| - Raw notes were grouped into a formal BRD structure | |
| - Requirements were separated into functional and non-functional areas | |
| - User needs were rewritten as BDD-style stories | |
| - The output is now easier to review, share, and present | |
| ## Portfolio Value | |
| This demonstrates BA lifecycle thinking, requirement structuring, and workflow automation in a recruiter-friendly format. | |
| """ | |
| def generate_documents(notes): | |
| notes = notes.strip() | |
| if not notes: | |
| msg = "Please paste stakeholder notes first." | |
| return msg, msg, msg | |
| brd = format_brd(notes) | |
| stories = format_stories(notes) | |
| before_after = format_before_after(notes) | |
| return brd, stories, before_after | |
| with gr.Blocks() as demo: | |
| gr.HTML(""" | |
| <div class="hero-shell"> | |
| <div class="hero-inner"> | |
| <div class="eyebrow">Strategy • Requirements • Workflow Automation</div> | |
| <h1 class="hero-title">Agentic BA</h1> | |
| <p class="hero-subtitle"> | |
| A Business Analyst portfolio solution that converts unstructured stakeholder notes into | |
| structured BRDs, BDD-style user stories, and a clear before-versus-after documentation view. | |
| </p> | |
| <div class="hero-bar"> | |
| <div class="hero-stat"> | |
| <strong>Structured Documentation</strong> | |
| <span>Converts rough notes into formal requirement outputs.</span> | |
| </div> | |
| <div class="hero-stat"> | |
| <strong>Public Portfolio Demo</strong> | |
| <span>Built to share with recruiters and hiring teams.</span> | |
| </div> | |
| <div class="hero-stat"> | |
| <strong>100% Free Stack</strong> | |
| <span>No paid APIs, subscriptions, or hidden usage costs.</span> | |
| </div> | |
| <div class="hero-stat"> | |
| <strong>BA Lifecycle Focus</strong> | |
| <span>Highlights analysis, structuring, and requirement thinking.</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| """) | |
| with gr.Row(equal_height=True): | |
| with gr.Column(scale=5, elem_classes="panel"): | |
| gr.HTML(""" | |
| <div class="panel-head"> | |
| <h2 class="panel-title">Input Workspace</h2> | |
| <p class="panel-copy"> | |
| Provide stakeholder notes, workshop summaries, or interview observations. | |
| The application reorganizes them into clearer business deliverables. | |
| </p> | |
| </div> | |
| <div class="metric-strip"> | |
| <div class="metric-card"> | |
| <strong>BRD Output</strong> | |
| <span>Formal business requirement structure</span> | |
| </div> | |
| <div class="metric-card"> | |
| <strong>BDD Stories</strong> | |
| <span>User-centered requirement framing</span> | |
| </div> | |
| <div class="metric-card"> | |
| <strong>Showcase View</strong> | |
| <span>Before-and-after transformation for interviews</span> | |
| </div> | |
| </div> | |
| """) | |
| notes_input = gr.Textbox( | |
| lines=12, | |
| label="Raw Stakeholder Notes", | |
| placeholder="Example: Customers are abandoning carts before payment. Stakeholders want easier checkout, UPI, cards, wallets, guest checkout, mobile-friendly flow, and fewer support issues." | |
| ) | |
| with gr.Row(): | |
| generate_btn = gr.Button("Generate Deliverables", variant="primary") | |
| clear_btn = gr.Button("Clear Input", variant="secondary") | |
| with gr.Column(elem_classes="ba-examples"): | |
| gr.Examples( | |
| examples=[ | |
| ["Customers are abandoning carts before payment. They want easier checkout, UPI, cards, wallets, mobile-friendly pages, guest checkout, and fewer support issues."], | |
| ["Hospital reception staff say patient registration is slow. They want OTP verification, fewer manual steps, tablet support, and a simple dashboard for receptionists."], | |
| ["Users find login confusing. Stakeholders want optional sign-in where possible, a simpler user flow, faster completion, and fewer helpdesk calls."] | |
| ], | |
| inputs=notes_input, | |
| label="Sample Business Scenarios" | |
| ) | |
| with gr.Column(scale=7, elem_classes="panel"): | |
| gr.HTML(""" | |
| <div class="panel-head"> | |
| <h2 class="panel-title">Output Review</h2> | |
| <p class="panel-copy"> | |
| Review the generated documentation in a format designed for portfolio presentation | |
| and rapid recruiter understanding. | |
| </p> | |
| </div> | |
| """) | |
| with gr.Tabs(): | |
| with gr.Tab("BRD"): | |
| brd_output = gr.Markdown() | |
| with gr.Tab("BDD User Stories"): | |
| stories_output = gr.Markdown() | |
| with gr.Tab("Before vs After"): | |
| before_after_output = gr.Markdown() | |
| gr.HTML(""" | |
| <div class="footer-banner"> | |
| <strong>Recruiter takeaway:</strong> This project demonstrates how a Business Analyst can apply | |
| structured automation thinking to convert unclear stakeholder input into clearer, more consistent, | |
| and more presentation-ready requirement documentation. | |
| </div> | |
| """) | |
| generate_btn.click( | |
| fn=generate_documents, | |
| inputs=notes_input, | |
| outputs=[brd_output, stories_output, before_after_output] | |
| ) | |
| clear_btn.click( | |
| fn=lambda: ("", "", "", ""), | |
| inputs=[], | |
| outputs=[notes_input, brd_output, stories_output, before_after_output] | |
| ) | |
| demo.launch(css=CUSTOM_CSS, theme=gr.themes.Base()) | |