Jacek Zadrożny commited on
Commit
344016f
·
1 Parent(s): 0f360ca

Alternatywny interface

Browse files
Files changed (2) hide show
  1. README.md +1 -1
  2. app2.py +168 -0
README.md CHANGED
@@ -5,7 +5,7 @@ colorFrom: yellow
5
  colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.25.0
8
- app_file: app.py
9
  pinned: true
10
  license: cc-by-4.0
11
  thumbnail: >-
 
5
  colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.25.0
8
+ app_file: app2.py
9
  pinned: true
10
  license: cc-by-4.0
11
  thumbnail: >-
app2.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ from langchain_core.prompts import PromptTemplate
4
+ from langchain_openai import ChatOpenAI
5
+ from langchain_core.output_parsers import StrOutputParser
6
+ from pydantic import BaseModel, Field, field_validator
7
+ from langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader
8
+ from langchain.output_parsers import PydanticOutputParser
9
+ from docx import Document
10
+ from datetime import datetime
11
+ import os
12
+ import tempfile
13
+
14
+ # Model danych
15
+ class QuestionAnswer(BaseModel):
16
+ question_number: int = Field(..., description="Numer pytania")
17
+ answer: str = Field(..., description="Odpowiedź, tylko TAK lub NIE")
18
+ citation: str = Field(..., description="Fragment cytatu")
19
+
20
+ @field_validator("answer")
21
+ def validate_answer(cls, v):
22
+ if v not in {"TAK", "NIE"}:
23
+ raise ValueError("Odpowiedź musi być TAK lub NIE")
24
+ return v
25
+
26
+ class JobAdAnalysis(BaseModel):
27
+ answers: list[QuestionAnswer]
28
+
29
+ parser = PydanticOutputParser(pydantic_object=JobAdAnalysis)
30
+
31
+ # Wczytanie matrycy danych
32
+ matryca_df = pd.read_csv('matryca.csv', header=None,
33
+ names=['area', 'prompt', 'true', 'false', 'more', 'hint'])
34
+
35
+ question_to_area_map = {}
36
+
37
+ def prepare_questions(df):
38
+ global question_to_area_map
39
+ question_to_area_map = {}
40
+ questions_text = ""
41
+ for index, row in df.iterrows():
42
+ question_number = index + 1
43
+ questions_text += f"{question_number} {row['prompt']}\n"
44
+ question_to_area_map[question_number] = {
45
+ 'area': row['area'],
46
+ 'true': row['true'],
47
+ 'false': row['false'],
48
+ 'hint': row['hint'],
49
+ 'more': row['more']
50
+ }
51
+ return questions_text
52
+
53
+ def doc_to_text(file):
54
+ extension = os.path.splitext(file.name)[1].lower()
55
+ if extension == ".docx":
56
+ loader = Docx2txtLoader(file.name)
57
+ elif extension == ".pdf":
58
+ loader = PyPDFLoader(file.name)
59
+ else:
60
+ return "error"
61
+ pages = loader.load()
62
+ return "\n".join(page.page_content for page in pages)
63
+
64
+ def create_html_inline(result: pd.DataFrame) -> str:
65
+ html = "<h1>Raport analizy ogłoszenia o pracę</h1>"
66
+ html += f"<p><strong>Data wygenerowania:</strong> {datetime.now().strftime('%d.%m.%Y %H:%M')}</p>"
67
+ for _, row in result.iterrows():
68
+ html += f"<h2>{row['area']}</h2>"
69
+ html += f"<blockquote>{row['citation']}</blockquote>"
70
+ for line in str(row['content']).split('\n'):
71
+ if line.strip():
72
+ html += f"<p>{line}</p>"
73
+ if pd.notna(row['more']):
74
+ html += "<details><summary>Dodatkowe informacje</summary>"
75
+ for line in str(row['more']).split('\n'):
76
+ if line.strip():
77
+ html += f"<p>{line}</p>"
78
+ html += "</details>"
79
+ return html
80
+
81
+ def create_report(result: pd.DataFrame) -> str:
82
+ doc = Document('template.docx')
83
+ doc.add_heading('Raport analizy ogłoszenia o pracę', 0)
84
+ doc.add_paragraph(f'Data wygenerowania: {datetime.now().strftime("%d.%m.%Y %H:%M")}')
85
+ for _, row in result.iterrows():
86
+ doc.add_heading(str(row['area']), 1)
87
+ doc.add_paragraph(str(row['citation']), style='Intense Quote')
88
+ for line in str(row['content']).split('\n'):
89
+ if line.strip():
90
+ doc.add_paragraph(line)
91
+ if pd.notna(row['more']):
92
+ for line in str(row['more']).split('\n'):
93
+ if line.strip():
94
+ doc.add_paragraph(line)
95
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp:
96
+ doc.save(tmp.name)
97
+ return tmp.name
98
+
99
+ def analyze_job_ad(job_ad, file):
100
+ if file:
101
+ job_ad = doc_to_text(file)
102
+ if job_ad == "error":
103
+ return None, None
104
+ questions = prepare_questions(matryca_df)
105
+ prompt_template = PromptTemplate.from_template(
106
+ """Przeanalizuj poniższe ogłoszenie o pracę pod kątem dostępności dla osób z niepełnosprawnościami.
107
+
108
+ Ogłoszenie:
109
+ {job_ad}
110
+
111
+ Odpowiedz na następujące pytania:
112
+ {questions}
113
+
114
+ Format odpowiedzi powinien być w następującej strukturze JSON:
115
+ {{
116
+ "answers": [
117
+ {{
118
+ "question_number": 1,
119
+ "answer": "TAK/NIE",
120
+ "citation": "dokładny cytat z tekstu"
121
+ }}
122
+ ]
123
+ }}
124
+ """
125
+ )
126
+
127
+ model = ChatOpenAI(temperature=0, model="gpt-4o-mini")
128
+ chain = prompt_template | model | parser
129
+ response = chain.invoke({"job_ad": job_ad, "questions": questions})
130
+
131
+ output_df = pd.DataFrame(columns=['area', 'answer', 'citation', 'content', 'more'])
132
+ for i in range(16):
133
+ if response.answers[i].answer in {"TAK", "NIE"}:
134
+ new_row = {
135
+ 'area': matryca_df.area[i],
136
+ 'answer': response.answers[i].answer,
137
+ 'citation': response.answers[i].citation,
138
+ 'content': matryca_df.true[i] if response.answers[i].answer == 'TAK' else matryca_df.false[i],
139
+ 'more': matryca_df.more[i]
140
+ }
141
+ output_df = pd.concat([output_df, pd.DataFrame([new_row])], ignore_index=True)
142
+
143
+ word_file_path = create_report(output_df)
144
+ html_output = create_html_inline(output_df)
145
+ return html_output, word_file_path
146
+
147
+ # Gradio z Blocks
148
+ with gr.Blocks(title="KoREKtor – analiza ogłoszenia") as demo:
149
+ gr.HTML("""
150
+ <div style='text-align: center;'>
151
+ <img src='logo-korektor.png' alt='KoREKtor' style='max-height: 80px;'>
152
+ <h1>KoREKtor</h1>
153
+ </div>
154
+ <p>Aplikacja KoREKtor wykorzystuje sztuczną inteligencję do analizowania ogłoszeń rekrutacyjnych pod kątem informacji dla osób z niepełnosprawnością. Możesz wkleić treść ogłoszenia do pola tekstowego lub przesłać plik w formacie PDF lub DOCX. Potem kliknij na przycisk <strong>Sprawdź</strong> i poczekaj kilkanaście sekund.</p>
155
+ <img src='belka.png' alt='Logotypy sponsorów' style='width: 100%; max-height: 60px;'>
156
+ """)
157
+
158
+ with gr.Row():
159
+ job_ad_input = gr.TextArea(label="Ogłoszenie (opcjonalnie)")
160
+ file_input = gr.File(label="Plik PDF lub DOCX", file_count="single")
161
+
162
+ analyze_button = gr.Button("Sprawdź")
163
+ html_output = gr.HTML(label="Wyniki analizy")
164
+ word_output = gr.File(label="Pobierz raport w formacie Word")
165
+
166
+ analyze_button.click(analyze_job_ad, inputs=[job_ad_input, file_input], outputs=[html_output, word_output])
167
+
168
+ demo.launch(pwa=True, show_api=False, favicon_path="logo-korektor.png")