ai-parrot 0.17.2__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentui/.prettierrc +15 -0
- agentui/QUICKSTART.md +272 -0
- agentui/README.md +59 -0
- agentui/env.example +16 -0
- agentui/jsconfig.json +14 -0
- agentui/package-lock.json +4242 -0
- agentui/package.json +34 -0
- agentui/scripts/postinstall/apply-patches.mjs +260 -0
- agentui/src/app.css +61 -0
- agentui/src/app.d.ts +13 -0
- agentui/src/app.html +12 -0
- agentui/src/components/LoadingSpinner.svelte +64 -0
- agentui/src/components/ThemeSwitcher.svelte +159 -0
- agentui/src/components/index.js +4 -0
- agentui/src/lib/api/bots.ts +60 -0
- agentui/src/lib/api/chat.ts +22 -0
- agentui/src/lib/api/http.ts +25 -0
- agentui/src/lib/components/BotCard.svelte +33 -0
- agentui/src/lib/components/ChatBubble.svelte +63 -0
- agentui/src/lib/components/Toast.svelte +21 -0
- agentui/src/lib/config.ts +20 -0
- agentui/src/lib/stores/auth.svelte.ts +73 -0
- agentui/src/lib/stores/theme.svelte.js +64 -0
- agentui/src/lib/stores/toast.svelte.ts +31 -0
- agentui/src/lib/utils/conversation.ts +39 -0
- agentui/src/routes/+layout.svelte +20 -0
- agentui/src/routes/+page.svelte +232 -0
- agentui/src/routes/login/+page.svelte +200 -0
- agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
- agentui/src/routes/talk/[agentId]/+page.ts +7 -0
- agentui/static/README.md +1 -0
- agentui/svelte.config.js +11 -0
- agentui/tailwind.config.ts +53 -0
- agentui/tsconfig.json +3 -0
- agentui/vite.config.ts +10 -0
- ai_parrot-0.17.2.dist-info/METADATA +472 -0
- ai_parrot-0.17.2.dist-info/RECORD +535 -0
- ai_parrot-0.17.2.dist-info/WHEEL +6 -0
- ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
- ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
- ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
- crew-builder/.prettierrc +15 -0
- crew-builder/QUICKSTART.md +259 -0
- crew-builder/README.md +113 -0
- crew-builder/env.example +17 -0
- crew-builder/jsconfig.json +14 -0
- crew-builder/package-lock.json +4182 -0
- crew-builder/package.json +37 -0
- crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
- crew-builder/src/app.css +62 -0
- crew-builder/src/app.d.ts +13 -0
- crew-builder/src/app.html +12 -0
- crew-builder/src/components/LoadingSpinner.svelte +64 -0
- crew-builder/src/components/ThemeSwitcher.svelte +149 -0
- crew-builder/src/components/index.js +9 -0
- crew-builder/src/lib/api/bots.ts +60 -0
- crew-builder/src/lib/api/chat.ts +80 -0
- crew-builder/src/lib/api/client.ts +56 -0
- crew-builder/src/lib/api/crew/crew.ts +136 -0
- crew-builder/src/lib/api/index.ts +5 -0
- crew-builder/src/lib/api/o365/auth.ts +65 -0
- crew-builder/src/lib/auth/auth.ts +54 -0
- crew-builder/src/lib/components/AgentNode.svelte +43 -0
- crew-builder/src/lib/components/BotCard.svelte +33 -0
- crew-builder/src/lib/components/ChatBubble.svelte +67 -0
- crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
- crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
- crew-builder/src/lib/components/JsonViewer.svelte +24 -0
- crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
- crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
- crew-builder/src/lib/components/Toast.svelte +67 -0
- crew-builder/src/lib/components/Toolbar.svelte +157 -0
- crew-builder/src/lib/components/index.ts +10 -0
- crew-builder/src/lib/config.ts +8 -0
- crew-builder/src/lib/stores/auth.svelte.ts +228 -0
- crew-builder/src/lib/stores/crewStore.ts +369 -0
- crew-builder/src/lib/stores/theme.svelte.js +145 -0
- crew-builder/src/lib/stores/toast.svelte.ts +69 -0
- crew-builder/src/lib/utils/conversation.ts +39 -0
- crew-builder/src/lib/utils/markdown.ts +122 -0
- crew-builder/src/lib/utils/talkHistory.ts +47 -0
- crew-builder/src/routes/+layout.svelte +20 -0
- crew-builder/src/routes/+page.svelte +539 -0
- crew-builder/src/routes/agents/+page.svelte +247 -0
- crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
- crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
- crew-builder/src/routes/builder/+page.svelte +204 -0
- crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
- crew-builder/src/routes/crew/ask/+page.ts +1 -0
- crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
- crew-builder/src/routes/login/+page.svelte +197 -0
- crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
- crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
- crew-builder/static/README.md +1 -0
- crew-builder/svelte.config.js +11 -0
- crew-builder/tailwind.config.ts +53 -0
- crew-builder/tsconfig.json +3 -0
- crew-builder/vite.config.ts +10 -0
- mcp_servers/calculator_server.py +309 -0
- parrot/__init__.py +27 -0
- parrot/__pycache__/__init__.cpython-310.pyc +0 -0
- parrot/__pycache__/version.cpython-310.pyc +0 -0
- parrot/_version.py +34 -0
- parrot/a2a/__init__.py +48 -0
- parrot/a2a/client.py +658 -0
- parrot/a2a/discovery.py +89 -0
- parrot/a2a/mixin.py +257 -0
- parrot/a2a/models.py +376 -0
- parrot/a2a/server.py +770 -0
- parrot/agents/__init__.py +29 -0
- parrot/bots/__init__.py +12 -0
- parrot/bots/a2a_agent.py +19 -0
- parrot/bots/abstract.py +3139 -0
- parrot/bots/agent.py +1129 -0
- parrot/bots/basic.py +9 -0
- parrot/bots/chatbot.py +669 -0
- parrot/bots/data.py +1618 -0
- parrot/bots/database/__init__.py +5 -0
- parrot/bots/database/abstract.py +3071 -0
- parrot/bots/database/cache.py +286 -0
- parrot/bots/database/models.py +468 -0
- parrot/bots/database/prompts.py +154 -0
- parrot/bots/database/retries.py +98 -0
- parrot/bots/database/router.py +269 -0
- parrot/bots/database/sql.py +41 -0
- parrot/bots/db/__init__.py +6 -0
- parrot/bots/db/abstract.py +556 -0
- parrot/bots/db/bigquery.py +602 -0
- parrot/bots/db/cache.py +85 -0
- parrot/bots/db/documentdb.py +668 -0
- parrot/bots/db/elastic.py +1014 -0
- parrot/bots/db/influx.py +898 -0
- parrot/bots/db/mock.py +96 -0
- parrot/bots/db/multi.py +783 -0
- parrot/bots/db/prompts.py +185 -0
- parrot/bots/db/sql.py +1255 -0
- parrot/bots/db/tools.py +212 -0
- parrot/bots/document.py +680 -0
- parrot/bots/hrbot.py +15 -0
- parrot/bots/kb.py +170 -0
- parrot/bots/mcp.py +36 -0
- parrot/bots/orchestration/README.md +463 -0
- parrot/bots/orchestration/__init__.py +1 -0
- parrot/bots/orchestration/agent.py +155 -0
- parrot/bots/orchestration/crew.py +3330 -0
- parrot/bots/orchestration/fsm.py +1179 -0
- parrot/bots/orchestration/hr.py +434 -0
- parrot/bots/orchestration/storage/__init__.py +4 -0
- parrot/bots/orchestration/storage/memory.py +100 -0
- parrot/bots/orchestration/storage/mixin.py +119 -0
- parrot/bots/orchestration/verify.py +202 -0
- parrot/bots/product.py +204 -0
- parrot/bots/prompts/__init__.py +96 -0
- parrot/bots/prompts/agents.py +155 -0
- parrot/bots/prompts/data.py +216 -0
- parrot/bots/prompts/output_generation.py +8 -0
- parrot/bots/scraper/__init__.py +3 -0
- parrot/bots/scraper/models.py +122 -0
- parrot/bots/scraper/scraper.py +1173 -0
- parrot/bots/scraper/templates.py +115 -0
- parrot/bots/stores/__init__.py +5 -0
- parrot/bots/stores/local.py +172 -0
- parrot/bots/webdev.py +81 -0
- parrot/cli.py +17 -0
- parrot/clients/__init__.py +16 -0
- parrot/clients/base.py +1491 -0
- parrot/clients/claude.py +1191 -0
- parrot/clients/factory.py +129 -0
- parrot/clients/google.py +4567 -0
- parrot/clients/gpt.py +1975 -0
- parrot/clients/grok.py +432 -0
- parrot/clients/groq.py +986 -0
- parrot/clients/hf.py +582 -0
- parrot/clients/models.py +18 -0
- parrot/conf.py +395 -0
- parrot/embeddings/__init__.py +9 -0
- parrot/embeddings/base.py +157 -0
- parrot/embeddings/google.py +98 -0
- parrot/embeddings/huggingface.py +74 -0
- parrot/embeddings/openai.py +84 -0
- parrot/embeddings/processor.py +88 -0
- parrot/exceptions.c +13868 -0
- parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/exceptions.pxd +22 -0
- parrot/exceptions.pxi +15 -0
- parrot/exceptions.pyx +44 -0
- parrot/generators/__init__.py +29 -0
- parrot/generators/base.py +200 -0
- parrot/generators/html.py +293 -0
- parrot/generators/react.py +205 -0
- parrot/generators/streamlit.py +203 -0
- parrot/generators/template.py +105 -0
- parrot/handlers/__init__.py +4 -0
- parrot/handlers/agent.py +861 -0
- parrot/handlers/agents/__init__.py +1 -0
- parrot/handlers/agents/abstract.py +900 -0
- parrot/handlers/bots.py +338 -0
- parrot/handlers/chat.py +915 -0
- parrot/handlers/creation.sql +192 -0
- parrot/handlers/crew/ARCHITECTURE.md +362 -0
- parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
- parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
- parrot/handlers/crew/__init__.py +0 -0
- parrot/handlers/crew/handler.py +801 -0
- parrot/handlers/crew/models.py +229 -0
- parrot/handlers/crew/redis_persistence.py +523 -0
- parrot/handlers/jobs/__init__.py +10 -0
- parrot/handlers/jobs/job.py +384 -0
- parrot/handlers/jobs/mixin.py +627 -0
- parrot/handlers/jobs/models.py +115 -0
- parrot/handlers/jobs/worker.py +31 -0
- parrot/handlers/models.py +596 -0
- parrot/handlers/o365_auth.py +105 -0
- parrot/handlers/stream.py +337 -0
- parrot/interfaces/__init__.py +6 -0
- parrot/interfaces/aws.py +143 -0
- parrot/interfaces/credentials.py +113 -0
- parrot/interfaces/database.py +27 -0
- parrot/interfaces/google.py +1123 -0
- parrot/interfaces/hierarchy.py +1227 -0
- parrot/interfaces/http.py +651 -0
- parrot/interfaces/images/__init__.py +0 -0
- parrot/interfaces/images/plugins/__init__.py +24 -0
- parrot/interfaces/images/plugins/abstract.py +58 -0
- parrot/interfaces/images/plugins/analisys.py +148 -0
- parrot/interfaces/images/plugins/classify.py +150 -0
- parrot/interfaces/images/plugins/classifybase.py +182 -0
- parrot/interfaces/images/plugins/detect.py +150 -0
- parrot/interfaces/images/plugins/exif.py +1103 -0
- parrot/interfaces/images/plugins/hash.py +52 -0
- parrot/interfaces/images/plugins/vision.py +104 -0
- parrot/interfaces/images/plugins/yolo.py +66 -0
- parrot/interfaces/images/plugins/zerodetect.py +197 -0
- parrot/interfaces/o365.py +978 -0
- parrot/interfaces/onedrive.py +822 -0
- parrot/interfaces/sharepoint.py +1435 -0
- parrot/interfaces/soap.py +257 -0
- parrot/loaders/__init__.py +8 -0
- parrot/loaders/abstract.py +1131 -0
- parrot/loaders/audio.py +199 -0
- parrot/loaders/basepdf.py +53 -0
- parrot/loaders/basevideo.py +1568 -0
- parrot/loaders/csv.py +409 -0
- parrot/loaders/docx.py +116 -0
- parrot/loaders/epubloader.py +316 -0
- parrot/loaders/excel.py +199 -0
- parrot/loaders/factory.py +55 -0
- parrot/loaders/files/__init__.py +0 -0
- parrot/loaders/files/abstract.py +39 -0
- parrot/loaders/files/html.py +26 -0
- parrot/loaders/files/text.py +63 -0
- parrot/loaders/html.py +152 -0
- parrot/loaders/markdown.py +442 -0
- parrot/loaders/pdf.py +373 -0
- parrot/loaders/pdfmark.py +320 -0
- parrot/loaders/pdftables.py +506 -0
- parrot/loaders/ppt.py +476 -0
- parrot/loaders/qa.py +63 -0
- parrot/loaders/splitters/__init__.py +10 -0
- parrot/loaders/splitters/base.py +138 -0
- parrot/loaders/splitters/md.py +228 -0
- parrot/loaders/splitters/token.py +143 -0
- parrot/loaders/txt.py +26 -0
- parrot/loaders/video.py +89 -0
- parrot/loaders/videolocal.py +218 -0
- parrot/loaders/videounderstanding.py +377 -0
- parrot/loaders/vimeo.py +167 -0
- parrot/loaders/web.py +599 -0
- parrot/loaders/youtube.py +504 -0
- parrot/manager/__init__.py +5 -0
- parrot/manager/manager.py +1030 -0
- parrot/mcp/__init__.py +28 -0
- parrot/mcp/adapter.py +105 -0
- parrot/mcp/cli.py +174 -0
- parrot/mcp/client.py +119 -0
- parrot/mcp/config.py +75 -0
- parrot/mcp/integration.py +842 -0
- parrot/mcp/oauth.py +933 -0
- parrot/mcp/server.py +225 -0
- parrot/mcp/transports/__init__.py +3 -0
- parrot/mcp/transports/base.py +279 -0
- parrot/mcp/transports/grpc_session.py +163 -0
- parrot/mcp/transports/http.py +312 -0
- parrot/mcp/transports/mcp.proto +108 -0
- parrot/mcp/transports/quic.py +1082 -0
- parrot/mcp/transports/sse.py +330 -0
- parrot/mcp/transports/stdio.py +309 -0
- parrot/mcp/transports/unix.py +395 -0
- parrot/mcp/transports/websocket.py +547 -0
- parrot/memory/__init__.py +16 -0
- parrot/memory/abstract.py +209 -0
- parrot/memory/agent.py +32 -0
- parrot/memory/cache.py +175 -0
- parrot/memory/core.py +555 -0
- parrot/memory/file.py +153 -0
- parrot/memory/mem.py +131 -0
- parrot/memory/redis.py +613 -0
- parrot/models/__init__.py +46 -0
- parrot/models/basic.py +118 -0
- parrot/models/compliance.py +208 -0
- parrot/models/crew.py +395 -0
- parrot/models/detections.py +654 -0
- parrot/models/generation.py +85 -0
- parrot/models/google.py +223 -0
- parrot/models/groq.py +23 -0
- parrot/models/openai.py +30 -0
- parrot/models/outputs.py +285 -0
- parrot/models/responses.py +938 -0
- parrot/notifications/__init__.py +743 -0
- parrot/openapi/__init__.py +3 -0
- parrot/openapi/components.yaml +641 -0
- parrot/openapi/config.py +322 -0
- parrot/outputs/__init__.py +32 -0
- parrot/outputs/formats/__init__.py +108 -0
- parrot/outputs/formats/altair.py +359 -0
- parrot/outputs/formats/application.py +122 -0
- parrot/outputs/formats/base.py +351 -0
- parrot/outputs/formats/bokeh.py +356 -0
- parrot/outputs/formats/card.py +424 -0
- parrot/outputs/formats/chart.py +436 -0
- parrot/outputs/formats/d3.py +255 -0
- parrot/outputs/formats/echarts.py +310 -0
- parrot/outputs/formats/generators/__init__.py +0 -0
- parrot/outputs/formats/generators/abstract.py +61 -0
- parrot/outputs/formats/generators/panel.py +145 -0
- parrot/outputs/formats/generators/streamlit.py +86 -0
- parrot/outputs/formats/generators/terminal.py +63 -0
- parrot/outputs/formats/holoviews.py +310 -0
- parrot/outputs/formats/html.py +147 -0
- parrot/outputs/formats/jinja2.py +46 -0
- parrot/outputs/formats/json.py +87 -0
- parrot/outputs/formats/map.py +933 -0
- parrot/outputs/formats/markdown.py +172 -0
- parrot/outputs/formats/matplotlib.py +237 -0
- parrot/outputs/formats/mixins/__init__.py +0 -0
- parrot/outputs/formats/mixins/emaps.py +855 -0
- parrot/outputs/formats/plotly.py +341 -0
- parrot/outputs/formats/seaborn.py +310 -0
- parrot/outputs/formats/table.py +397 -0
- parrot/outputs/formats/template_report.py +138 -0
- parrot/outputs/formats/yaml.py +125 -0
- parrot/outputs/formatter.py +152 -0
- parrot/outputs/templates/__init__.py +95 -0
- parrot/pipelines/__init__.py +0 -0
- parrot/pipelines/abstract.py +210 -0
- parrot/pipelines/detector.py +124 -0
- parrot/pipelines/models.py +90 -0
- parrot/pipelines/planogram.py +3002 -0
- parrot/pipelines/table.sql +97 -0
- parrot/plugins/__init__.py +106 -0
- parrot/plugins/importer.py +80 -0
- parrot/py.typed +0 -0
- parrot/registry/__init__.py +18 -0
- parrot/registry/registry.py +594 -0
- parrot/scheduler/__init__.py +1189 -0
- parrot/scheduler/models.py +60 -0
- parrot/security/__init__.py +16 -0
- parrot/security/prompt_injection.py +268 -0
- parrot/security/security_events.sql +25 -0
- parrot/services/__init__.py +1 -0
- parrot/services/mcp/__init__.py +8 -0
- parrot/services/mcp/config.py +13 -0
- parrot/services/mcp/server.py +295 -0
- parrot/services/o365_remote_auth.py +235 -0
- parrot/stores/__init__.py +7 -0
- parrot/stores/abstract.py +352 -0
- parrot/stores/arango.py +1090 -0
- parrot/stores/bigquery.py +1377 -0
- parrot/stores/cache.py +106 -0
- parrot/stores/empty.py +10 -0
- parrot/stores/faiss_store.py +1157 -0
- parrot/stores/kb/__init__.py +9 -0
- parrot/stores/kb/abstract.py +68 -0
- parrot/stores/kb/cache.py +165 -0
- parrot/stores/kb/doc.py +325 -0
- parrot/stores/kb/hierarchy.py +346 -0
- parrot/stores/kb/local.py +457 -0
- parrot/stores/kb/prompt.py +28 -0
- parrot/stores/kb/redis.py +659 -0
- parrot/stores/kb/store.py +115 -0
- parrot/stores/kb/user.py +374 -0
- parrot/stores/models.py +59 -0
- parrot/stores/pgvector.py +3 -0
- parrot/stores/postgres.py +2853 -0
- parrot/stores/utils/__init__.py +0 -0
- parrot/stores/utils/chunking.py +197 -0
- parrot/telemetry/__init__.py +3 -0
- parrot/telemetry/mixin.py +111 -0
- parrot/template/__init__.py +3 -0
- parrot/template/engine.py +259 -0
- parrot/tools/__init__.py +23 -0
- parrot/tools/abstract.py +644 -0
- parrot/tools/agent.py +363 -0
- parrot/tools/arangodbsearch.py +537 -0
- parrot/tools/arxiv_tool.py +188 -0
- parrot/tools/calculator/__init__.py +3 -0
- parrot/tools/calculator/operations/__init__.py +38 -0
- parrot/tools/calculator/operations/calculus.py +80 -0
- parrot/tools/calculator/operations/statistics.py +76 -0
- parrot/tools/calculator/tool.py +150 -0
- parrot/tools/cloudwatch.py +988 -0
- parrot/tools/codeinterpreter/__init__.py +127 -0
- parrot/tools/codeinterpreter/executor.py +371 -0
- parrot/tools/codeinterpreter/internals.py +473 -0
- parrot/tools/codeinterpreter/models.py +643 -0
- parrot/tools/codeinterpreter/prompts.py +224 -0
- parrot/tools/codeinterpreter/tool.py +664 -0
- parrot/tools/company_info/__init__.py +6 -0
- parrot/tools/company_info/tool.py +1138 -0
- parrot/tools/correlationanalysis.py +437 -0
- parrot/tools/database/abstract.py +286 -0
- parrot/tools/database/bq.py +115 -0
- parrot/tools/database/cache.py +284 -0
- parrot/tools/database/models.py +95 -0
- parrot/tools/database/pg.py +343 -0
- parrot/tools/databasequery.py +1159 -0
- parrot/tools/db.py +1800 -0
- parrot/tools/ddgo.py +370 -0
- parrot/tools/decorators.py +271 -0
- parrot/tools/dftohtml.py +282 -0
- parrot/tools/document.py +549 -0
- parrot/tools/ecs.py +819 -0
- parrot/tools/edareport.py +368 -0
- parrot/tools/elasticsearch.py +1049 -0
- parrot/tools/employees.py +462 -0
- parrot/tools/epson/__init__.py +96 -0
- parrot/tools/excel.py +683 -0
- parrot/tools/file/__init__.py +13 -0
- parrot/tools/file/abstract.py +76 -0
- parrot/tools/file/gcs.py +378 -0
- parrot/tools/file/local.py +284 -0
- parrot/tools/file/s3.py +511 -0
- parrot/tools/file/tmp.py +309 -0
- parrot/tools/file/tool.py +501 -0
- parrot/tools/file_reader.py +129 -0
- parrot/tools/flowtask/__init__.py +19 -0
- parrot/tools/flowtask/tool.py +761 -0
- parrot/tools/gittoolkit.py +508 -0
- parrot/tools/google/__init__.py +18 -0
- parrot/tools/google/base.py +169 -0
- parrot/tools/google/tools.py +1251 -0
- parrot/tools/googlelocation.py +5 -0
- parrot/tools/googleroutes.py +5 -0
- parrot/tools/googlesearch.py +5 -0
- parrot/tools/googlesitesearch.py +5 -0
- parrot/tools/googlevoice.py +2 -0
- parrot/tools/gvoice.py +695 -0
- parrot/tools/ibisworld/README.md +225 -0
- parrot/tools/ibisworld/__init__.py +11 -0
- parrot/tools/ibisworld/tool.py +366 -0
- parrot/tools/jiratoolkit.py +1718 -0
- parrot/tools/manager.py +1098 -0
- parrot/tools/math.py +152 -0
- parrot/tools/metadata.py +476 -0
- parrot/tools/msteams.py +1621 -0
- parrot/tools/msword.py +635 -0
- parrot/tools/multidb.py +580 -0
- parrot/tools/multistoresearch.py +369 -0
- parrot/tools/networkninja.py +167 -0
- parrot/tools/nextstop/__init__.py +4 -0
- parrot/tools/nextstop/base.py +286 -0
- parrot/tools/nextstop/employee.py +733 -0
- parrot/tools/nextstop/store.py +462 -0
- parrot/tools/notification.py +435 -0
- parrot/tools/o365/__init__.py +42 -0
- parrot/tools/o365/base.py +295 -0
- parrot/tools/o365/bundle.py +522 -0
- parrot/tools/o365/events.py +554 -0
- parrot/tools/o365/mail.py +992 -0
- parrot/tools/o365/onedrive.py +497 -0
- parrot/tools/o365/sharepoint.py +641 -0
- parrot/tools/openapi_toolkit.py +904 -0
- parrot/tools/openweather.py +527 -0
- parrot/tools/pdfprint.py +1001 -0
- parrot/tools/powerbi.py +518 -0
- parrot/tools/powerpoint.py +1113 -0
- parrot/tools/pricestool.py +146 -0
- parrot/tools/products/__init__.py +246 -0
- parrot/tools/prophet_tool.py +171 -0
- parrot/tools/pythonpandas.py +630 -0
- parrot/tools/pythonrepl.py +910 -0
- parrot/tools/qsource.py +436 -0
- parrot/tools/querytoolkit.py +395 -0
- parrot/tools/quickeda.py +827 -0
- parrot/tools/resttool.py +553 -0
- parrot/tools/retail/__init__.py +0 -0
- parrot/tools/retail/bby.py +528 -0
- parrot/tools/sandboxtool.py +703 -0
- parrot/tools/sassie/__init__.py +352 -0
- parrot/tools/scraping/__init__.py +7 -0
- parrot/tools/scraping/docs/select.md +466 -0
- parrot/tools/scraping/documentation.md +1278 -0
- parrot/tools/scraping/driver.py +436 -0
- parrot/tools/scraping/models.py +576 -0
- parrot/tools/scraping/options.py +85 -0
- parrot/tools/scraping/orchestrator.py +517 -0
- parrot/tools/scraping/readme.md +740 -0
- parrot/tools/scraping/tool.py +3115 -0
- parrot/tools/seasonaldetection.py +642 -0
- parrot/tools/shell_tool/__init__.py +5 -0
- parrot/tools/shell_tool/actions.py +408 -0
- parrot/tools/shell_tool/engine.py +155 -0
- parrot/tools/shell_tool/models.py +322 -0
- parrot/tools/shell_tool/tool.py +442 -0
- parrot/tools/site_search.py +214 -0
- parrot/tools/textfile.py +418 -0
- parrot/tools/think.py +378 -0
- parrot/tools/toolkit.py +298 -0
- parrot/tools/webapp_tool.py +187 -0
- parrot/tools/whatif.py +1279 -0
- parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
- parrot/tools/workday/__init__.py +6 -0
- parrot/tools/workday/models.py +1389 -0
- parrot/tools/workday/tool.py +1293 -0
- parrot/tools/yfinance_tool.py +306 -0
- parrot/tools/zipcode.py +217 -0
- parrot/utils/__init__.py +2 -0
- parrot/utils/helpers.py +73 -0
- parrot/utils/parsers/__init__.py +5 -0
- parrot/utils/parsers/toml.c +12078 -0
- parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/parsers/toml.pyx +21 -0
- parrot/utils/toml.py +11 -0
- parrot/utils/types.cpp +20936 -0
- parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/types.pyx +213 -0
- parrot/utils/uv.py +11 -0
- parrot/version.py +10 -0
- parrot/yaml-rs/Cargo.lock +350 -0
- parrot/yaml-rs/Cargo.toml +19 -0
- parrot/yaml-rs/pyproject.toml +19 -0
- parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
- parrot/yaml-rs/src/lib.rs +222 -0
- requirements/docker-compose.yml +24 -0
- requirements/requirements-dev.txt +21 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
from typing import List, Dict, Optional, Literal, Any, Mapping
|
|
2
|
+
import uuid
|
|
3
|
+
from pydantic import BaseModel, Field, field_validator
|
|
4
|
+
|
|
5
|
+
class BoundingBox(BaseModel):
|
|
6
|
+
"""Normalized bounding box coordinates"""
|
|
7
|
+
x1: float = Field(..., description="The leftmost x-coordinate (normalized)", ge=0, le=1)
|
|
8
|
+
y1: float = Field(..., description="The topmost y-coordinate (normalized)", ge=0, le=1)
|
|
9
|
+
x2: float = Field(..., description="The rightmost x-coordinate (normalized)", ge=0, le=1)
|
|
10
|
+
y2: float = Field(..., description="The bottommost y-coordinate (normalized)", ge=0, le=1)
|
|
11
|
+
|
|
12
|
+
def get_coordinates(self) -> tuple[float, float, float, float]:
|
|
13
|
+
"""Return bounding box as (x1, y1, x2, y2)"""
|
|
14
|
+
return (self.x1, self.y1, self.x2, self.y2)
|
|
15
|
+
|
|
16
|
+
def get_pixel_coordinates(self, width: int, height: int) -> tuple[int, int, int, int]:
|
|
17
|
+
"""Return bounding box as (x1, y1, x2, y2) absolute integer pixels."""
|
|
18
|
+
px1 = int(self.x1 * width)
|
|
19
|
+
py1 = int(self.y1 * height)
|
|
20
|
+
px2 = int(self.x2 * width)
|
|
21
|
+
py2 = int(self.y2 * height)
|
|
22
|
+
return (px1, py1, px2, py2)
|
|
23
|
+
|
|
24
|
+
class Detection(BaseModel):
|
|
25
|
+
"""Generic detection result"""
|
|
26
|
+
label: Optional[str] = Field(None, description="Optional label for the detection")
|
|
27
|
+
confidence: float = Field(ge=0.0, le=1.0, description="Detection confidence")
|
|
28
|
+
content: Optional[str] = Field(None, description="The recognized text content within the bounding box, if any.")
|
|
29
|
+
bbox: BoundingBox
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Detections(BaseModel):
|
|
33
|
+
"""Collection of detections in an image"""
|
|
34
|
+
detections: List[Detection] = Field(default_factory=list, description="List of detected bounding boxes")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DetectionBox(BaseModel):
|
|
38
|
+
"""Bounding box from object detection"""
|
|
39
|
+
x1: int = Field(description="Left x coordinate")
|
|
40
|
+
y1: int = Field(description="Top y coordinate")
|
|
41
|
+
x2: int = Field(description="Right x coordinate")
|
|
42
|
+
y2: int = Field(description="Bottom y coordinate")
|
|
43
|
+
confidence: float = Field(ge=0.0, le=1.0, description="Detection confidence")
|
|
44
|
+
class_id: int = Field(default=None, description="Detected class ID")
|
|
45
|
+
class_name: str = Field(default=None, description="Detected class name")
|
|
46
|
+
area: int = Field(default=None, description="Bounding box area in pixels")
|
|
47
|
+
label: Optional[str] = Field(None, description="Optional label for the detection")
|
|
48
|
+
ocr_text: Optional[str] = Field(
|
|
49
|
+
None,
|
|
50
|
+
description="OCR text within the bounding box, if any"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
class ShelfRegion(BaseModel):
|
|
54
|
+
"""Detected shelf region"""
|
|
55
|
+
shelf_id: str = Field(description="Unique shelf identifier")
|
|
56
|
+
bbox: DetectionBox = Field(description="Shelf bounding box")
|
|
57
|
+
level: str = Field(description="Shelf level (top, middle, bottom)")
|
|
58
|
+
objects: List[DetectionBox] = Field(default_factory=list, description="Objects on this shelf")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class IdentifiedProduct(BaseModel):
|
|
62
|
+
"""Product identified by LLM using reference images"""
|
|
63
|
+
detection_id: int = Field(None, description="The unique ID of the corresponding detection box.")
|
|
64
|
+
product_type: str = Field(description="Type of product")
|
|
65
|
+
product_model: Optional[str] = Field(None, description="Specific product model")
|
|
66
|
+
brand: Optional[str] = Field(None, description="Brand on the item (e.g., Epson)")
|
|
67
|
+
confidence: float = Field(ge=0.0, le=1.0, description="Confidence score")
|
|
68
|
+
visual_features: List[str] = Field(default_factory=list, description="List of key visual identifiers.")
|
|
69
|
+
reference_match: Optional[str] = Field(None, description="Which reference image was matched, or 'none'.")
|
|
70
|
+
shelf_location: Optional[str] = Field(
|
|
71
|
+
None, description="The shelf level where the product is located: 'header', 'top', 'middle', 'bottom'."
|
|
72
|
+
)
|
|
73
|
+
position_on_shelf: Optional[str] = Field(
|
|
74
|
+
None, description="Position on the shelf: 'left', 'center', 'right'."
|
|
75
|
+
)
|
|
76
|
+
advertisement_type: Optional[str] = Field(
|
|
77
|
+
None, description="Ad type if promotional (backlit_graphic, endcap_poster, shelf_talker, banner, digital_display)"
|
|
78
|
+
)
|
|
79
|
+
detection_box: Optional[DetectionBox] = Field(None, description="Detection box information")
|
|
80
|
+
extra: Dict[str, str] = Field(default_factory=dict, description="Any Extra descriptive tags")
|
|
81
|
+
|
|
82
|
+
@field_validator('confidence', mode='before')
|
|
83
|
+
@classmethod
|
|
84
|
+
def validate_confidence(cls, v: Any) -> float:
|
|
85
|
+
"""Ensure confidence is between 0 and 1."""
|
|
86
|
+
if isinstance(v, str):
|
|
87
|
+
if v.lower() == 'high':
|
|
88
|
+
return 0.9
|
|
89
|
+
elif v.lower() == 'medium':
|
|
90
|
+
return 0.6
|
|
91
|
+
elif v.lower() == 'low':
|
|
92
|
+
return 0.3
|
|
93
|
+
else:
|
|
94
|
+
return 0.5 # Default for unrecognized strings
|
|
95
|
+
if not (0 <= v <= 1):
|
|
96
|
+
raise ValueError(f"confidence must be between 0 and 1, got {v}")
|
|
97
|
+
return v
|
|
98
|
+
|
|
99
|
+
@field_validator('position_on_shelf', mode='before')
|
|
100
|
+
@classmethod
|
|
101
|
+
def validate_position_on_shelf(cls, v: Any) -> Optional[str]:
|
|
102
|
+
"""Ensure position_on_shelf is one of the accepted values."""
|
|
103
|
+
if isinstance(v, int):
|
|
104
|
+
mapping = {0: "left", 1: "center", 2: "right"}
|
|
105
|
+
return mapping.get(v, None)
|
|
106
|
+
valid_positions = {"left", "center", "right"}
|
|
107
|
+
if v is not None and v.lower() not in valid_positions:
|
|
108
|
+
raise ValueError(f"position_on_shelf must be one of {valid_positions}, got '{v}'")
|
|
109
|
+
return v.lower() if v else v
|
|
110
|
+
|
|
111
|
+
@field_validator('detection_id', mode='before')
|
|
112
|
+
@classmethod
|
|
113
|
+
def set_id_for_llm_found_items(cls, v: Any) -> int:
|
|
114
|
+
"""If detection_id is null, generate a unique negative ID."""
|
|
115
|
+
if v is None:
|
|
116
|
+
# Generate a unique integer to avoid collisions. Negative values clearly
|
|
117
|
+
# indicate that this item was found by the LLM, not YOLO.
|
|
118
|
+
return -int(str(uuid.uuid4().int)[:8])
|
|
119
|
+
return v
|
|
120
|
+
|
|
121
|
+
# VALIDATOR 2: Converts a coordinate list into a DetectionBox object.
|
|
122
|
+
@field_validator('detection_box', mode='before')
|
|
123
|
+
@classmethod
|
|
124
|
+
def convert_list_to_detection_box(cls, v: Any, values: Any) -> Any:
|
|
125
|
+
"""If detection_box is a list [x1,y1,x2,y2], convert it to a DetectionBox object."""
|
|
126
|
+
# The 'v' is the value of the 'detection_box' field itself.
|
|
127
|
+
if isinstance(v, list) and len(v) == 4:
|
|
128
|
+
x1, y1, x2, y2 = v
|
|
129
|
+
|
|
130
|
+
# We need the confidence to create a valid DetectionBox.
|
|
131
|
+
# 'values.data' gives us access to the other raw data in the JSON object.
|
|
132
|
+
confidence = values.data.get('confidence', 0.95) # Default to 0.95 if not found
|
|
133
|
+
|
|
134
|
+
return DetectionBox(
|
|
135
|
+
x1=int(x1),
|
|
136
|
+
y1=int(y1),
|
|
137
|
+
x2=int(x2),
|
|
138
|
+
y2=int(y2),
|
|
139
|
+
confidence=float(confidence),
|
|
140
|
+
class_id=0, # Placeholder ID for LLM-found items
|
|
141
|
+
class_name='llm_detected',
|
|
142
|
+
area=abs(int(x2) - int(x1)) * abs(int(y2) - int(y1))
|
|
143
|
+
)
|
|
144
|
+
# If it's already a dict or a DetectionBox object, pass it through.
|
|
145
|
+
return v
|
|
146
|
+
|
|
147
|
+
class IdentificationResponse(BaseModel):
|
|
148
|
+
"""Response from product identification"""
|
|
149
|
+
identified_products: List[IdentifiedProduct] = Field(
|
|
150
|
+
alias="detections",
|
|
151
|
+
description="List of identified products from the image"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Enhanced models for pipeline planogram description
|
|
155
|
+
class BrandDetectionConfig(BaseModel):
|
|
156
|
+
"""Configuration for brand detection parameters"""
|
|
157
|
+
enabled: bool = Field(default=True, description="Enable brand detection")
|
|
158
|
+
target_brands: List[str] = Field(default_factory=list, description="List of brands to detect")
|
|
159
|
+
confidence_threshold: float = Field(default=0.7, description="Minimum confidence for brand detection")
|
|
160
|
+
ocr_enabled: bool = Field(default=True, description="Use OCR for brand text detection")
|
|
161
|
+
case_sensitive: bool = Field(default=False, description="Case-sensitive brand matching")
|
|
162
|
+
|
|
163
|
+
class CategoryDetectionConfig(BaseModel):
|
|
164
|
+
"""Configuration for product category detection"""
|
|
165
|
+
enabled: bool = Field(default=True, description="Enable category detection")
|
|
166
|
+
target_categories: List[str] = Field(default_factory=list, description="Categories to detect (printers, boxes, etc.)")
|
|
167
|
+
size_based_classification: bool = Field(default=True, description="Use size for category classification")
|
|
168
|
+
visual_features_weight: float = Field(default=0.6, description="Weight of visual features in classification")
|
|
169
|
+
confidence_threshold: float = Field(default=0.6, description="Minimum confidence for category detection")
|
|
170
|
+
|
|
171
|
+
class ShelfProduct(BaseModel):
|
|
172
|
+
"""Configuration for products expected on a shelf"""
|
|
173
|
+
name: str = Field(description="Product name/model")
|
|
174
|
+
product_type: str = Field(description="Type: printer, product_box, promotional_graphic, etc.")
|
|
175
|
+
quantity_range: tuple[int, int] = Field(default=(1, 1), description="Min and max quantity expected")
|
|
176
|
+
position_preference: Optional[Literal["left", "center", "right"]] = Field(default=None, description="Preferred position on shelf")
|
|
177
|
+
mandatory: bool = Field(default=True, description="Whether this product is required")
|
|
178
|
+
visual_features: Optional[List[str]] = Field(default=None, description="Expected key visual identifiers and features for this product")
|
|
179
|
+
|
|
180
|
+
class ShelfConfig(BaseModel):
|
|
181
|
+
"""Configuration for a single shelf"""
|
|
182
|
+
level: str = Field(description="Shelf level: header, top, middle, bottom")
|
|
183
|
+
products: List[ShelfProduct] = Field(description="Expected products on this shelf")
|
|
184
|
+
compliance_threshold: float = Field(default=0.8, description="Compliance threshold for this shelf")
|
|
185
|
+
allow_extra_products: bool = Field(default=False, description="Allow products not in the specification")
|
|
186
|
+
position_strict: bool = Field(default=False, description="Enforce strict positioning")
|
|
187
|
+
height_ratio: Optional[float] = Field(default=0.30) # Add this field: 0.30 = 30% of ROI height
|
|
188
|
+
|
|
189
|
+
class TextRequirement(BaseModel):
|
|
190
|
+
"""Text requirement for promotional materials"""
|
|
191
|
+
required_text: str = Field(description="Text that must be present")
|
|
192
|
+
match_type: Literal["exact", "contains", "regex"] = Field(default="contains", description="Type of text matching")
|
|
193
|
+
case_sensitive: bool = Field(default=False, description="Case-sensitive matching")
|
|
194
|
+
confidence_threshold: float = Field(default=0.7, description="OCR confidence threshold")
|
|
195
|
+
mandatory: bool = Field(default=True, description="Whether this text is required")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class AdvertisementEndcap(BaseModel):
|
|
199
|
+
"""Configuration for advertisement endcap"""
|
|
200
|
+
enabled: bool = Field(default=True, description="Whether endcap advertisement is present")
|
|
201
|
+
promotional_type: Literal["backlit_graphic", "endcap_poster", "shelf_talker", "banner", "digital_display", "integrated_display", "promotional_base"] = Field(
|
|
202
|
+
default="backlit_graphic", description="Type of promotional display"
|
|
203
|
+
)
|
|
204
|
+
position: Literal["header", "top", "middle", "bottom", "side"] = Field(default="header", description="Position of endcap")
|
|
205
|
+
product_weight: float = Field(default=0.8, description="Weight of product compliance in overall score")
|
|
206
|
+
text_weight: float = Field(default=0.2, description="Weight of text compliance in overall score")
|
|
207
|
+
top_margin_percent: float = Field(default=0.02, description="Top margin percent of image for panel detection")
|
|
208
|
+
width_margin_percent: float = Field(default=0.45, description="Width percent of image for panel detection")
|
|
209
|
+
height_margin_percent: float = Field(default=0.33, description="Height percent of image for panel detection")
|
|
210
|
+
side_margin_percent: float = Field(default=0.05, description="Side margin percent for panel detection")
|
|
211
|
+
brand_requirements: List[str] = Field(default_factory=list, description="Required brand elements")
|
|
212
|
+
text_requirements: List[TextRequirement] = Field(default_factory=list, description="Required text elements")
|
|
213
|
+
reference_image_path: Optional[str] = Field(default=None, description="Path to reference image for comparison")
|
|
214
|
+
allow_additional_text: bool = Field(default=True, description="Allow additional text beyond requirements")
|
|
215
|
+
|
|
216
|
+
class AisleConfig(BaseModel):
|
|
217
|
+
"""Configuration for aisle-specific settings"""
|
|
218
|
+
name: str = Field(description="Aisle name (electronics, furniture, etc.)")
|
|
219
|
+
category_hints: List[str] = Field(default_factory=list, description="Product category hints for this aisle")
|
|
220
|
+
lighting_conditions: Literal["bright", "normal", "dim", "retail_standard"] = Field(default="normal", description="Expected lighting")
|
|
221
|
+
shelf_spacing: Optional[float] = Field(default=None, description="Expected spacing between shelves")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class PlanogramDescription(BaseModel):
|
|
225
|
+
"""
|
|
226
|
+
Comprehensive, configurable planogram description
|
|
227
|
+
"""
|
|
228
|
+
# Basic identification
|
|
229
|
+
brand: str = Field(description="Primary brand for this planogram")
|
|
230
|
+
category: str = Field(description="Product category")
|
|
231
|
+
aisle: AisleConfig = Field(description="Aisle configuration")
|
|
232
|
+
tags: List[str] = Field(default_factory=list, description="Tags for special features or promotions")
|
|
233
|
+
advertisement: Dict[str, Any] = Field(default_factory=dict, description="Advertisement sizing and positioning")
|
|
234
|
+
text_tokens: List[str] = Field(default_factory=list, description="Additional text tokens for detection")
|
|
235
|
+
# Detection configuration
|
|
236
|
+
brand_detection: BrandDetectionConfig = Field(default_factory=BrandDetectionConfig, description="Brand detection settings")
|
|
237
|
+
category_detection: CategoryDetectionConfig = Field(default_factory=CategoryDetectionConfig, description="Category detection settings")
|
|
238
|
+
|
|
239
|
+
# Shelf layout
|
|
240
|
+
shelves: List[ShelfConfig] = Field(description="Configuration for each shelf level")
|
|
241
|
+
|
|
242
|
+
# Advertisement configuration
|
|
243
|
+
advertisement_endcap: Optional[AdvertisementEndcap] = Field(default=None, description="Advertisement endcap configuration")
|
|
244
|
+
|
|
245
|
+
# Global compliance settings
|
|
246
|
+
global_compliance_threshold: float = Field(default=0.8, description="Default compliance threshold")
|
|
247
|
+
weighted_scoring: Dict[str, float] = Field(
|
|
248
|
+
default_factory=lambda: {"product_compliance": 0.7, "text_compliance": 0.3},
|
|
249
|
+
description="Weights for different compliance aspects"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Additional metadata
|
|
253
|
+
planogram_id: Optional[str] = Field(default=None, description="Unique identifier for this planogram")
|
|
254
|
+
created_date: Optional[str] = Field(default=None, description="Creation date")
|
|
255
|
+
version: str = Field(default="1.0", description="Planogram version")
|
|
256
|
+
notes: Optional[str] = Field(default=None, description="Additional notes or instructions")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class PlanogramDescriptionFactory:
|
|
260
|
+
"""Factory class for creating PlanogramDescription objects from dictionaries"""
|
|
261
|
+
|
|
262
|
+
@staticmethod
|
|
263
|
+
def create_planogram_description(config_dict: Dict[str, Any]) -> PlanogramDescription:
|
|
264
|
+
"""
|
|
265
|
+
Create a PlanogramDescription object from a dictionary configuration.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
config_dict: Dictionary containing all planogram configuration
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
PlanogramDescription object ready for compliance checking
|
|
272
|
+
|
|
273
|
+
Example config_dict structure:
|
|
274
|
+
{
|
|
275
|
+
"brand": "Epson",
|
|
276
|
+
"category": "Printers",
|
|
277
|
+
"aisle": {
|
|
278
|
+
"name": "Electronics",
|
|
279
|
+
"category_hints": ["printers", "ink", "paper"],
|
|
280
|
+
"lighting_conditions": "normal"
|
|
281
|
+
},
|
|
282
|
+
"tags": ["goodbye", "hello", "savings", "cartridges"],
|
|
283
|
+
# Advertisement sizing and positioning
|
|
284
|
+
"advertisement": {
|
|
285
|
+
"width_percent": 0.45, # 45% of image width
|
|
286
|
+
"height_percent": 0.25, # 25% of image height
|
|
287
|
+
"top_margin_percent": 0.02, # 2% margin above detected brand
|
|
288
|
+
"side_margin_percent": 0.05 # 5% margin on sides
|
|
289
|
+
},
|
|
290
|
+
"brand_detection": {
|
|
291
|
+
"enabled": True,
|
|
292
|
+
"target_brands": ["Epson", "Canon", "HP"],
|
|
293
|
+
"confidence_threshold": 0.8
|
|
294
|
+
},
|
|
295
|
+
"category_detection": {
|
|
296
|
+
"enabled": True,
|
|
297
|
+
"target_categories": ["printer", "product_box", "promotional_graphic"],
|
|
298
|
+
"confidence_threshold": 0.7
|
|
299
|
+
},
|
|
300
|
+
"shelves": [
|
|
301
|
+
{
|
|
302
|
+
"level": "header",
|
|
303
|
+
"products": [
|
|
304
|
+
{
|
|
305
|
+
"name": "Epson EcoTank Advertisement",
|
|
306
|
+
"product_type": "promotional_graphic",
|
|
307
|
+
"mandatory": True
|
|
308
|
+
}
|
|
309
|
+
],
|
|
310
|
+
"compliance_threshold": 0.9
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"level": "top",
|
|
314
|
+
"products": [
|
|
315
|
+
{
|
|
316
|
+
"name": "ET-2980",
|
|
317
|
+
"product_type": "printer",
|
|
318
|
+
"quantity_range": [1, 2],
|
|
319
|
+
"position_preference": "left"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
"name": "ET-3950",
|
|
323
|
+
"product_type": "printer",
|
|
324
|
+
"quantity_range": [1, 1],
|
|
325
|
+
"position_preference": "center"
|
|
326
|
+
}
|
|
327
|
+
],
|
|
328
|
+
"compliance_threshold": 0.8
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
"advertisement_endcap": {
|
|
332
|
+
"enabled": True,
|
|
333
|
+
"promotional_type": "backlit_graphic",
|
|
334
|
+
"position": "header",
|
|
335
|
+
"brand_requirements": ["Epson"],
|
|
336
|
+
"text_requirements": [
|
|
337
|
+
{
|
|
338
|
+
"required_text": "Goodbye Cartridges",
|
|
339
|
+
"match_type": "contains",
|
|
340
|
+
"mandatory": True
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"required_text": "Hello Savings",
|
|
344
|
+
"match_type": "contains",
|
|
345
|
+
"mandatory": True
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
# Process aisle configuration
|
|
353
|
+
aisle_data = config_dict.get("aisle", {})
|
|
354
|
+
if isinstance(aisle_data, str):
|
|
355
|
+
# Simple string aisle name
|
|
356
|
+
aisle_config = AisleConfig(name=aisle_data)
|
|
357
|
+
else:
|
|
358
|
+
# Full aisle configuration
|
|
359
|
+
aisle_config = AisleConfig(**aisle_data)
|
|
360
|
+
|
|
361
|
+
# Process brand detection configuration
|
|
362
|
+
brand_detection_data = config_dict.get("brand_detection", {})
|
|
363
|
+
brand_detection_config = BrandDetectionConfig(**brand_detection_data)
|
|
364
|
+
|
|
365
|
+
# Process category detection configuration
|
|
366
|
+
category_detection_data = config_dict.get("category_detection", {})
|
|
367
|
+
category_detection_config = CategoryDetectionConfig(**category_detection_data)
|
|
368
|
+
|
|
369
|
+
# Process shelves configuration
|
|
370
|
+
shelves_data = config_dict.get("shelves", [])
|
|
371
|
+
shelf_configs = []
|
|
372
|
+
|
|
373
|
+
for shelf_data in shelves_data:
|
|
374
|
+
# Process products for this shelf
|
|
375
|
+
products_data = shelf_data.get("products", [])
|
|
376
|
+
shelf_products = []
|
|
377
|
+
|
|
378
|
+
for product_data in products_data:
|
|
379
|
+
shelf_product = ShelfProduct(**product_data)
|
|
380
|
+
shelf_products.append(shelf_product)
|
|
381
|
+
|
|
382
|
+
# Create shelf config
|
|
383
|
+
shelf_config = ShelfConfig(
|
|
384
|
+
level=shelf_data["level"],
|
|
385
|
+
products=shelf_products,
|
|
386
|
+
compliance_threshold=shelf_data.get("compliance_threshold", 0.8),
|
|
387
|
+
allow_extra_products=shelf_data.get("allow_extra_products", False),
|
|
388
|
+
position_strict=shelf_data.get("position_strict", False)
|
|
389
|
+
)
|
|
390
|
+
shelf_configs.append(shelf_config)
|
|
391
|
+
|
|
392
|
+
# Process advertisement endcap configuration
|
|
393
|
+
advertisement_endcap = None
|
|
394
|
+
endcap_data = config_dict.get("advertisement_endcap")
|
|
395
|
+
if endcap_data:
|
|
396
|
+
# Process text requirements
|
|
397
|
+
text_requirements = []
|
|
398
|
+
for text_req_data in endcap_data.get("text_requirements", []):
|
|
399
|
+
text_req = TextRequirement(**text_req_data)
|
|
400
|
+
text_requirements.append(text_req)
|
|
401
|
+
|
|
402
|
+
# Update endcap data with processed text requirements
|
|
403
|
+
endcap_data = endcap_data.copy()
|
|
404
|
+
endcap_data["text_requirements"] = text_requirements
|
|
405
|
+
|
|
406
|
+
advertisement_endcap = AdvertisementEndcap(**endcap_data)
|
|
407
|
+
|
|
408
|
+
# Create the main PlanogramDescription object
|
|
409
|
+
planogram_description = PlanogramDescription(
|
|
410
|
+
brand=config_dict["brand"],
|
|
411
|
+
category=config_dict["category"],
|
|
412
|
+
aisle=aisle_config,
|
|
413
|
+
text_tokens=config_dict.get("text_tokens", []),
|
|
414
|
+
advertisement=config_dict.get("advertisement", {}),
|
|
415
|
+
tags=config_dict.get("tags", []),
|
|
416
|
+
brand_detection=brand_detection_config,
|
|
417
|
+
category_detection=category_detection_config,
|
|
418
|
+
shelves=shelf_configs,
|
|
419
|
+
advertisement_endcap=advertisement_endcap,
|
|
420
|
+
global_compliance_threshold=config_dict.get("global_compliance_threshold", 0.8),
|
|
421
|
+
weighted_scoring=config_dict.get("weighted_scoring", {"product_compliance": 0.7, "text_compliance": 0.3}),
|
|
422
|
+
planogram_id=config_dict.get("planogram_id"),
|
|
423
|
+
created_date=config_dict.get("created_date"),
|
|
424
|
+
version=config_dict.get("version", "1.0"),
|
|
425
|
+
notes=config_dict.get("notes")
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
return planogram_description
|
|
429
|
+
|
|
430
|
+
class PlanogramConfigBuilder:
|
|
431
|
+
"""Builder class for easier construction of planogram configurations"""
|
|
432
|
+
|
|
433
|
+
def __init__(self):
|
|
434
|
+
self.config = {
|
|
435
|
+
"brand": "",
|
|
436
|
+
"category": "",
|
|
437
|
+
"aisle": {},
|
|
438
|
+
"shelves": [],
|
|
439
|
+
"global_compliance_threshold": 0.8
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
def set_basic_info(self, brand: str, category: str, aisle: str) -> "PlanogramConfigBuilder":
|
|
443
|
+
"""Set basic planogram information"""
|
|
444
|
+
self.config["brand"] = brand
|
|
445
|
+
self.config["category"] = category
|
|
446
|
+
self.config["aisle"] = {"name": aisle}
|
|
447
|
+
return self
|
|
448
|
+
|
|
449
|
+
def add_shelf(
|
|
450
|
+
self,
|
|
451
|
+
level: str,
|
|
452
|
+
products: List[Dict[str, Any]],
|
|
453
|
+
compliance_threshold: float = 0.8
|
|
454
|
+
) -> "PlanogramConfigBuilder":
|
|
455
|
+
"""Add a shelf configuration"""
|
|
456
|
+
shelf_config = {
|
|
457
|
+
"level": level,
|
|
458
|
+
"products": products,
|
|
459
|
+
"compliance_threshold": compliance_threshold
|
|
460
|
+
}
|
|
461
|
+
self.config["shelves"].append(shelf_config)
|
|
462
|
+
return self
|
|
463
|
+
|
|
464
|
+
def add_product_to_shelf(
|
|
465
|
+
self,
|
|
466
|
+
shelf_level: str,
|
|
467
|
+
name: str,
|
|
468
|
+
product_type: str,
|
|
469
|
+
quantity_range: tuple = (1, 1),
|
|
470
|
+
mandatory: bool = True
|
|
471
|
+
) -> "PlanogramConfigBuilder":
|
|
472
|
+
"""Add a product to an existing shelf"""
|
|
473
|
+
# Find the shelf
|
|
474
|
+
for shelf in self.config["shelves"]:
|
|
475
|
+
if shelf["level"] == shelf_level:
|
|
476
|
+
product = {
|
|
477
|
+
"name": name,
|
|
478
|
+
"product_type": product_type,
|
|
479
|
+
"quantity_range": quantity_range,
|
|
480
|
+
"mandatory": mandatory
|
|
481
|
+
}
|
|
482
|
+
shelf["products"].append(product)
|
|
483
|
+
break
|
|
484
|
+
return self
|
|
485
|
+
|
|
486
|
+
def set_advertisement_endcap(
|
|
487
|
+
self,
|
|
488
|
+
promotional_type: str,
|
|
489
|
+
position: str = "header",
|
|
490
|
+
brand_requirements: List[str] = None,
|
|
491
|
+
text_requirements: List[Dict[str, Any]] = None
|
|
492
|
+
) -> "PlanogramConfigBuilder":
|
|
493
|
+
"""Configure advertisement endcap"""
|
|
494
|
+
endcap_config = {
|
|
495
|
+
"enabled": True,
|
|
496
|
+
"promotional_type": promotional_type,
|
|
497
|
+
"position": position,
|
|
498
|
+
"brand_requirements": brand_requirements or [],
|
|
499
|
+
"text_requirements": text_requirements or []
|
|
500
|
+
}
|
|
501
|
+
self.config["advertisement_endcap"] = endcap_config
|
|
502
|
+
return self
|
|
503
|
+
|
|
504
|
+
def set_brand_detection(
|
|
505
|
+
self,
|
|
506
|
+
target_brands: List[str],
|
|
507
|
+
confidence_threshold: float = 0.7
|
|
508
|
+
) -> "PlanogramConfigBuilder":
|
|
509
|
+
"""Configure brand detection"""
|
|
510
|
+
self.config["brand_detection"] = {
|
|
511
|
+
"enabled": True,
|
|
512
|
+
"target_brands": target_brands,
|
|
513
|
+
"confidence_threshold": confidence_threshold
|
|
514
|
+
}
|
|
515
|
+
return self
|
|
516
|
+
|
|
517
|
+
def build(self) -> Dict[str, Any]:
|
|
518
|
+
"""Build the final configuration dictionary"""
|
|
519
|
+
return self.config
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _dump(obj) -> Dict[str, Any]:
|
|
523
|
+
"""Works with Pydantic v1/v2 or plain dicts."""
|
|
524
|
+
if obj is None:
|
|
525
|
+
return {}
|
|
526
|
+
if hasattr(obj, "model_dump"):
|
|
527
|
+
return obj.model_dump(exclude_none=True)
|
|
528
|
+
if hasattr(obj, "dict"):
|
|
529
|
+
return obj.dict(exclude_none=True)
|
|
530
|
+
if isinstance(obj, dict):
|
|
531
|
+
return obj
|
|
532
|
+
# Fallback: shallow attr dump
|
|
533
|
+
return {
|
|
534
|
+
k: getattr(obj, k) for k in dir(obj) if not k.startswith("_") and not callable(getattr(obj, k))
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
def _product_label(p) -> str:
|
|
538
|
+
# ShelfProduct may be a pydantic model or dict
|
|
539
|
+
if p is None:
|
|
540
|
+
return "UNKNOWN"
|
|
541
|
+
if isinstance(p, dict):
|
|
542
|
+
name = p.get("name") or p.get("product_type") or "UNKNOWN"
|
|
543
|
+
return str(name)
|
|
544
|
+
name = getattr(p, "name", None) or getattr(p, "product_type", None) or "UNKNOWN"
|
|
545
|
+
return str(name)
|
|
546
|
+
|
|
547
|
+
def build_planogram_json_diagram(planogram) -> Dict[str, Any]:
|
|
548
|
+
"""
|
|
549
|
+
Produce a compact, human-friendly JSON 'diagram' of a PlanogramDescription.
|
|
550
|
+
Keys/shape kept simple for rendering and reporting.
|
|
551
|
+
"""
|
|
552
|
+
shelves: List[Dict[str, Any]] = []
|
|
553
|
+
for s in getattr(planogram, "shelves", []):
|
|
554
|
+
# Accept model or dict
|
|
555
|
+
s_d = _dump(s)
|
|
556
|
+
level = s_d.get("level") or s_d.get("name") or s_d.get("label") or "unknown"
|
|
557
|
+
products = s_d.get("products", [])
|
|
558
|
+
expected = [_product_label(p) for p in products]
|
|
559
|
+
shelves.append({
|
|
560
|
+
"shelf": str(level),
|
|
561
|
+
"slots": len(products),
|
|
562
|
+
"expected_left_to_right": expected,
|
|
563
|
+
"compliance_threshold": s_d.get(
|
|
564
|
+
"compliance_threshold", getattr(planogram, "global_compliance_threshold", 0.8)
|
|
565
|
+
),
|
|
566
|
+
"allow_extra_products": s_d.get("allow_extra_products", False),
|
|
567
|
+
"position_strict": s_d.get("position_strict", False),
|
|
568
|
+
"notes": s_d.get("notes"),
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
ad = _dump(getattr(planogram, "advertisement_endcap", None)) or None
|
|
572
|
+
aisle = _dump(getattr(planogram, "aisle", None))
|
|
573
|
+
|
|
574
|
+
diagram: Dict[str, Any] = {
|
|
575
|
+
"brand": getattr(planogram, "brand", ""),
|
|
576
|
+
"category": getattr(planogram, "category", ""),
|
|
577
|
+
"aisle": aisle,
|
|
578
|
+
"advertisement_endcap": ad, # will be None if not configured
|
|
579
|
+
"shelves": shelves,
|
|
580
|
+
"global_compliance_threshold": getattr(planogram, "global_compliance_threshold", 0.8),
|
|
581
|
+
"weighted_scoring": dict(getattr(planogram, "weighted_scoring", {"product_compliance": 0.7, "text_compliance": 0.3})),
|
|
582
|
+
"metadata": {
|
|
583
|
+
"planogram_id": getattr(planogram, "planogram_id", None),
|
|
584
|
+
"created_date": getattr(planogram, "created_date", None),
|
|
585
|
+
"version": getattr(planogram, "version", "1.0"),
|
|
586
|
+
"notes": getattr(planogram, "notes", None),
|
|
587
|
+
},
|
|
588
|
+
}
|
|
589
|
+
return diagram
|
|
590
|
+
|
|
591
|
+
def planogram_diagram_to_markdown(diagram: Mapping[str, Any]) -> str:
|
|
592
|
+
"""Render the JSON diagram as Markdown ready for reports."""
|
|
593
|
+
brand = diagram.get("brand", "")
|
|
594
|
+
category = diagram.get("category", "")
|
|
595
|
+
gthr = diagram.get("global_compliance_threshold", "")
|
|
596
|
+
weights = diagram.get("weighted_scoring", {})
|
|
597
|
+
meta = diagram.get("metadata", {})
|
|
598
|
+
aisle = diagram.get("aisle", {})
|
|
599
|
+
ad = diagram.get("advertisement_endcap", None)
|
|
600
|
+
shelves = diagram.get("shelves", [])
|
|
601
|
+
|
|
602
|
+
def _fmt_dict(d: Mapping[str, Any]) -> str:
|
|
603
|
+
if not d:
|
|
604
|
+
return "-"
|
|
605
|
+
# compact key: value list
|
|
606
|
+
parts = []
|
|
607
|
+
for k, v in d.items():
|
|
608
|
+
if isinstance(v, (list, dict)):
|
|
609
|
+
parts.append(f"**{k}**: `{str(v)}`")
|
|
610
|
+
else:
|
|
611
|
+
parts.append(f"**{k}**: {v}")
|
|
612
|
+
return "<br>".join(parts)
|
|
613
|
+
|
|
614
|
+
# Header table
|
|
615
|
+
md = []
|
|
616
|
+
md.append("| Field | Value |")
|
|
617
|
+
md.append("|---|---|")
|
|
618
|
+
md.append(f"| **Brand** | {brand} |")
|
|
619
|
+
md.append(f"| **Category** | {category} |")
|
|
620
|
+
md.append(f"| **Global Threshold** | {gthr} |")
|
|
621
|
+
md.append(f"| **Weighted Scoring** | product: {weights.get('product_compliance', '-')}, text: {weights.get('text_compliance', '-')} |")
|
|
622
|
+
md.append(f"| **Planogram ID** | {meta.get('planogram_id','-')} |")
|
|
623
|
+
md.append(f"| **Version** | {meta.get('version','-')} |")
|
|
624
|
+
md.append(f"| **Created** | {meta.get('created_date','-')} |")
|
|
625
|
+
md.append(f"| **Notes** | {meta.get('notes','-')} |")
|
|
626
|
+
md.append("")
|
|
627
|
+
# Aisle block
|
|
628
|
+
md.append("**Aisle**")
|
|
629
|
+
md.append("")
|
|
630
|
+
md.append(_fmt_dict(aisle))
|
|
631
|
+
md.append("")
|
|
632
|
+
# Advertisement block
|
|
633
|
+
md.append("**Advertisement Endcap**")
|
|
634
|
+
md.append("")
|
|
635
|
+
if ad:
|
|
636
|
+
md.append(_fmt_dict(ad))
|
|
637
|
+
else:
|
|
638
|
+
md.append("_None_")
|
|
639
|
+
md.append("")
|
|
640
|
+
# Shelves table
|
|
641
|
+
if shelves:
|
|
642
|
+
md.append("**Shelves**")
|
|
643
|
+
md.append("")
|
|
644
|
+
md.append("| Shelf | Slots | Expected (L→R) | Threshold | Allow Extra | Position Strict | Notes |")
|
|
645
|
+
md.append("|---:|---:|---|---:|:---:|:---:|---|")
|
|
646
|
+
for s in shelves:
|
|
647
|
+
exp = ", ".join(str(x) for x in s.get("expected_left_to_right", []))
|
|
648
|
+
md.append(
|
|
649
|
+
f"| {s.get('shelf','-')} | {s.get('slots','-')} | `{exp}` | "
|
|
650
|
+
f"{s.get('compliance_threshold','-')} | {s.get('allow_extra_products', False)} | "
|
|
651
|
+
f"{s.get('position_strict', False)} | {s.get('notes','-')} |"
|
|
652
|
+
)
|
|
653
|
+
md.append("")
|
|
654
|
+
return "\n".join(md)
|