brestok commited on
Commit
516495b
·
0 Parent(s):
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ *.index filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ env/
3
+ venv/
4
+ .venv/
5
+ .idea/
6
+ *.log
7
+ *.egg-info/
8
+ pip-wheel-EntityData/
9
+ .env
10
+ .DS_Store
11
+ static/
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12.7
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user ./requirements.txt requirements.txt
10
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
+
12
+ USER root
13
+ RUN apt-get update && apt-get install -y poppler-utils
14
+ USER user
15
+
16
+ COPY --chown=user . /app
17
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Supplier Segmentation
3
+ emoji: 💻
4
+ colorFrom: yellow
5
+ colorTo: blue
6
+ sdk: docker
7
+ pinned: false
8
+ ---
idreamers/__init__.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from starlette.exceptions import HTTPException as StarletteHTTPException
4
+
5
+ from idreamers.core.config import settings
6
+ from idreamers.core.wrappers import PilotResponseWrapper, ErrorPilotResponse
7
+
8
+
9
+ def create_app() -> FastAPI:
10
+ app = FastAPI()
11
+
12
+ from idreamers.api.calculation import calculation_router
13
+ app.include_router(calculation_router, tags=['calculation'])
14
+
15
+ app.add_middleware(
16
+ CORSMiddleware,
17
+ allow_origins=["*"],
18
+ allow_methods=["*"],
19
+ allow_headers=["*"],
20
+ )
21
+
22
+ @app.exception_handler(StarletteHTTPException)
23
+ async def http_exception_handler(_, exc):
24
+ return PilotResponseWrapper(
25
+ data=None,
26
+ successful=False,
27
+ error=ErrorPilotResponse(message=str(exc.detail))
28
+ ).response(exc.status_code)
29
+
30
+ @app.get("/")
31
+ async def read_root():
32
+ return {"calculation": "Hello world!"}
33
+
34
+ return app
idreamers/api/calculation/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from fastapi.routing import APIRouter
2
+
3
+ calculation_router = APIRouter(
4
+ prefix="/api/calculation", tags=["calculation"]
5
+ )
6
+
7
+ from . import views
idreamers/api/calculation/openai_request.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Literal
2
+
3
+ from idreamers.api.calculation.prompts import CalculationPrompts
4
+ from idreamers.api.calculation.schemas import PilotRequest
5
+ from idreamers.core.wrappers import openai_wrapper
6
+
7
+
8
+ @openai_wrapper(is_json=True, return_='score')
9
+ async def calculate_scores(data: PilotRequest, type_: Literal["business", "competitive", "supplier", "critical"]):
10
+ if type_ == "competitive":
11
+ prompt = CalculationPrompts.competitive
12
+ elif type_ == "supplier":
13
+ prompt = CalculationPrompts.supplier
14
+ elif type_ == "business":
15
+ prompt = CalculationPrompts.business
16
+ else:
17
+ prompt = CalculationPrompts.critical
18
+ messages = [
19
+ {
20
+ "role": "system",
21
+ "content": prompt
22
+ .replace("{supplier}", data.supplier)
23
+ .replace("{category}", data.category)
24
+ .replace("{buying_company}", data.buyingCompany)
25
+ }
26
+ ]
27
+ return messages
idreamers/api/calculation/prompts.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class CalculationPrompts:
2
+ competitive = """## Task
3
+ You must answer on the question below and set a score.
4
+
5
+ Question:
6
+ ```
7
+ How dependent is your business on this supplier for critical operations?
8
+ ```
9
+
10
+ Think about whether this supplier is essential for your day-to-day operations.
11
+
12
+ ## Data
13
+
14
+ **Supplier**:
15
+ ```
16
+ {supplier}
17
+ ```
18
+
19
+ **Category**:
20
+ ```
21
+ {category}
22
+ ```
23
+
24
+ **Buying Company**:
25
+ ```
26
+ {buying_company}
27
+ ```
28
+
29
+ ## JSON Response format
30
+
31
+ ```json
32
+ {
33
+ "score": integer
34
+ }
35
+
36
+ - [score]: Numerical score between 1 and 4. 1 = Many, 4 = Very few."""
37
+ supplier = """## Task
38
+ You must answer on the question below and set a score.
39
+
40
+ Question:
41
+ ```
42
+ How competitive is the market where this supplier operates?
43
+ ```
44
+
45
+ Evaluate whether this supplier faces competition or has significant market control.
46
+
47
+ ## Data
48
+
49
+ **Supplier**:
50
+ ```
51
+ {supplier}
52
+ ```
53
+
54
+ **Category**:
55
+ ```
56
+ {category}
57
+ ```
58
+
59
+ **Buying Company**:
60
+ ```
61
+ {buying_company}
62
+ ```
63
+
64
+ ## JSON Response format
65
+
66
+ ```json
67
+ {
68
+ "score": integer
69
+ }
70
+
71
+ - [score]: Numerical score between 1 and 4. 1 = Highly competitive, 4 = Monopoly/Oligopoly."""
72
+ business = """## Task
73
+ You must answer on the question below and set a score.
74
+
75
+ Question:
76
+ ```
77
+ How dependent is the supplier on your organization for their revenue?
78
+ ```
79
+
80
+ Reflect on how important your business is to this supplier’s overall revenue.
81
+
82
+ ## Data
83
+
84
+ **Supplier**:
85
+ ```
86
+ {supplier}
87
+ ```
88
+
89
+ **Category**:
90
+ ```
91
+ {category}
92
+ ```
93
+
94
+ **Buying Company**:
95
+ ```
96
+ {buying_company}
97
+ ```
98
+
99
+ ## JSON Response format
100
+
101
+ ```json
102
+ {
103
+ "score": integer
104
+ }
105
+
106
+ - [score]: Numerical score between 1 and 4. 1 = Not dependent, 4 = Highly dependent."""
107
+ critical = """## Task
108
+ You must answer on the question below and set a score.
109
+
110
+ Question:
111
+ ```
112
+ How critical is this supplier for delivering unique or differentiated products/services?
113
+ ```
114
+
115
+ Think about whether this supplier provides something that others cannot easily replicate.
116
+
117
+ ## Data
118
+
119
+ **Supplier**:
120
+ ```
121
+ {supplier}
122
+ ```
123
+
124
+ **Category**:
125
+ ```
126
+ {category}
127
+ ```
128
+
129
+ **Buying Company**:
130
+ ```
131
+ {buying_company}
132
+ ```
133
+
134
+ ## JSON Response format
135
+
136
+ ```json
137
+ {
138
+ "score": integer
139
+ }
140
+
141
+ - [score]: Numerical score between 1 and 4. Think about whether this supplier provides something that others cannot easily replicate."""
idreamers/api/calculation/schemas.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class PilotRequest(BaseModel):
5
+ category: str
6
+ buyingCompany: str
7
+ supplier: str
8
+
9
+
10
+ class PilotResponse(BaseModel):
11
+ x: float
12
+ y: float
idreamers/api/calculation/utils.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from idreamers.api.calculation.schemas import PilotResponse
2
+
3
+
4
+ def calculate_pilot_axes(q1: int, q2: int, q3: int, q4: int) -> PilotResponse:
5
+ return PilotResponse(
6
+ x=round((q1 + q2) / 2, 2),
7
+ y=round((q3 + q4) / 2, 2),
8
+ )
idreamers/api/calculation/views.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+
3
+ from idreamers.api.calculation import calculation_router
4
+ from idreamers.api.calculation.openai_request import calculate_scores
5
+ from idreamers.api.calculation.schemas import PilotRequest, PilotResponse
6
+ from idreamers.api.calculation.utils import calculate_pilot_axes
7
+ from idreamers.core.wrappers import PilotResponseWrapper
8
+
9
+
10
+ @calculation_router.post('/pilot/calculate')
11
+ async def calculate_pilot(
12
+ data: PilotRequest
13
+ ) -> PilotResponseWrapper[PilotResponse]:
14
+ depend_business, competitive, depend_supplier, critical_supplier = await asyncio.gather(
15
+ calculate_scores(data, 'business'),
16
+ calculate_scores(data, 'competitive'),
17
+ calculate_scores(data, 'supplier'),
18
+ calculate_scores(data, 'critical'),
19
+ )
20
+ response = calculate_pilot_axes(depend_business, competitive, depend_supplier, critical_supplier)
21
+ return PilotResponseWrapper(data=response)
idreamers/core/config.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pathlib
3
+ from functools import lru_cache
4
+
5
+ from dotenv import load_dotenv
6
+ from openai import AsyncClient
7
+
8
+ load_dotenv()
9
+
10
+
11
+ class BaseConfig:
12
+ BASE_DIR: pathlib.Path = pathlib.Path(__file__).parent.parent.parent
13
+ SECRET_KEY = os.getenv('SECRET')
14
+ OPENAI_CLIENT = AsyncClient(api_key=os.getenv('OPENAI_API_KEY'))
15
+
16
+
17
+ class DevelopmentConfig(BaseConfig):
18
+ pass
19
+
20
+
21
+ class ProductionConfig(BaseConfig):
22
+ pass
23
+
24
+
25
+ @lru_cache()
26
+ def get_settings() -> DevelopmentConfig | ProductionConfig:
27
+ config_cls_dict = {
28
+ 'development': DevelopmentConfig,
29
+ 'production': ProductionConfig,
30
+ }
31
+ config_name = os.getenv('FASTAPI_CONFIG', default='development')
32
+ config_cls = config_cls_dict[config_name]
33
+ return config_cls()
34
+
35
+
36
+ settings = get_settings()
idreamers/core/wrappers.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from functools import wraps
3
+ from typing import Generic, Optional, TypeVar
4
+
5
+ import pydash
6
+ from pydantic import BaseModel
7
+ from starlette.responses import JSONResponse
8
+
9
+ from idreamers.core.config import settings
10
+
11
+ T = TypeVar('T')
12
+
13
+
14
+ class ErrorPilotResponse(BaseModel):
15
+ message: str
16
+
17
+
18
+ class PilotResponseWrapper(BaseModel, Generic[T]):
19
+ data: Optional[T] = None
20
+ successful: bool = True
21
+ error: Optional[ErrorPilotResponse] = None
22
+
23
+ def response(self, status_code: int):
24
+ return JSONResponse(
25
+ status_code=status_code,
26
+ content={
27
+ "data": self.data,
28
+ "successful": self.successful,
29
+ "error": self.error.model_dump(mode='json') if self.error else None
30
+ }
31
+ )
32
+
33
+
34
+ def openai_wrapper(
35
+ temperature: int | float = 0, model: str = "gpt-4o-mini", is_json: bool = False, return_: str = None
36
+ ):
37
+ def decorator(func):
38
+ @wraps(func)
39
+ async def wrapper(*args, **kwargs) -> str:
40
+ messages = await func(*args, **kwargs)
41
+ completion = await settings.OPENAI_CLIENT.chat.completions.create(
42
+ messages=messages,
43
+ temperature=temperature,
44
+ n=1,
45
+ model=model,
46
+ response_format={"type": "json_object"} if is_json else {"type": "text"}
47
+ )
48
+ response = completion.choices[0].message.content
49
+ if is_json:
50
+ response = json.loads(response)
51
+ if return_:
52
+ return pydash.get(response, return_)
53
+ return response
54
+
55
+ return wrapper
56
+
57
+ return decorator
main.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from idreamers import create_app
2
+
3
+ app = create_app()
requirements.txt ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated-types==0.7.0
2
+ anyio==4.8.0
3
+ certifi==2024.12.14
4
+ click==8.1.8
5
+ distro==1.9.0
6
+ fastapi==0.115.7
7
+ h11==0.14.0
8
+ httpcore==1.0.7
9
+ httptools==0.6.4
10
+ httpx==0.28.1
11
+ idna==3.10
12
+ jiter==0.8.2
13
+ openai==1.60.1
14
+ pydantic==2.10.6
15
+ pydantic_core==2.27.2
16
+ python-dotenv==1.0.1
17
+ PyYAML==6.0.2
18
+ sniffio==1.3.1
19
+ starlette==0.45.3
20
+ tqdm==4.67.1
21
+ typing_extensions==4.12.2
22
+ uvicorn==0.34.0
23
+ uvloop==0.21.0
24
+ watchfiles==1.0.4
25
+ websockets==14.2