edouardlgp commited on
Commit
32610c7
·
verified ·
1 Parent(s): 970a84f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +166 -186
app.py CHANGED
@@ -834,7 +834,7 @@ def process_pdf(file):
834
  esco_occ_future = executor.submit(classify_esco_by_hierarchical_level, responsibilities)
835
  qualification_future = executor.submit(extract_qualification, responsibilities)
836
  skills_future = executor.submit(extract_skills, responsibilities)
837
-
838
  job_family = job_family_future.result()
839
  occ_group = occ_group_future.result()
840
  esco_occ = esco_occ_future.result()
@@ -843,16 +843,10 @@ def process_pdf(file):
843
 
844
  log_debug(f"Identified {job_family}")
845
 
846
- skill_map = map_proficiency_and_assessment(skills, responsibilities)
847
-
848
- has_esco = esco_occ.get("Level_5_ESCO_code") is not None
849
- skill_esco_extract = []
850
- skill_esco_map = []
851
- if has_esco:
852
- Level_5_code = esco_occ["Level_5_ESCO_code"]
853
- skill_esco_extract = review_skills(Level_5_code)
854
- skill_esco_map = map_proficiency_and_assessment(skill_esco_extract, responsibilities)
855
 
 
 
856
  time.sleep(6)
857
  assessment_lookup = {item['skill_name']: item for item in skill_map}
858
  joined_skills = [
@@ -870,6 +864,43 @@ def process_pdf(file):
870
  for skill in skills
871
  ]
872
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
873
  # Prepare all data for JSON output
874
  result_data = {
875
  "file_name": os.path.basename(file.name),
@@ -881,18 +912,8 @@ def process_pdf(file):
881
  "interview_questions": build_interview(responsibilities, skills),
882
  "skills": joined_skills,
883
  "esco_levels": {f"Level_{i}_ESCO_{field}": esco_occ.get(f"Level_{i}_ESCO_{field}")
884
- for i in range(1, 6) for field in ["code", "name", "desc"]},
885
- "esco_skills": {
886
- "skills": [
887
- {
888
- "skill_name": skill["skill_name"],
889
- "skill_description": skill["skill_description"],
890
- "skill_code": skill["skill_code"],
891
- **assessment_esco_lookup.get(skill["skill_name"], {})
892
- }
893
- for skill in (skill_esco_extract if has_esco else [])
894
- ]
895
- },
896
  "processing_time": time.strftime("%Y-%m-%d %H:%M:%S")
897
  }
898
 
@@ -902,7 +923,7 @@ def process_pdf(file):
902
  json_path = f.name
903
  log_debug(f"Results saved to temporary JSON file: {json_path}")
904
 
905
- # Format outputs for display
906
  formatted_skills = format_skill_cards(joined_skills)
907
  formatted_ccog = format_ccog_card(result_data['ccoq_levels'])
908
  formatted_esco_levels = format_esco_card(result_data['esco_levels'])
@@ -914,11 +935,11 @@ def process_pdf(file):
914
  job_family,
915
  "\n".join(qualification),
916
  formatted_ccog,
917
- "\n".join(result_data['interview_questions']),
918
  formatted_skills,
919
  formatted_esco_levels,
920
  formatted_esco_skills,
921
- "Processing completed successfully." if DEBUG else None,
922
  json_path # Return path to JSON file
923
  )
924
 
@@ -939,6 +960,8 @@ def process_pdf(file):
939
  error_message,
940
  None # No JSON path on error
941
  )
 
 
942
  # ================= Build Word Report =================
943
  from docx import Document
944
  import os
@@ -947,34 +970,37 @@ import time
947
  import tempfile
948
  from typing import Dict, List, Union
949
 
950
- def generate_word_document(
951
- file_name: str,
952
- responsibilities: str,
953
- job_family: str,
954
- qualification: str,
955
- ccoq_levels: Dict,
956
- interview: str,
957
- skills: List[Dict],
958
- esco_levels: Dict,
959
- esco_skills: Dict
960
- ) -> str:
961
- """
962
- Generate a comprehensive Word document from analysis results with multiple fallback mechanisms.
963
 
964
- Args:
965
- file_name: Original PDF filename
966
- responsibilities: Extracted responsibilities text
967
- job_family: Identified job family
968
- qualification: Required qualifications
969
- ccoq_levels: CCOG classification levels
970
- interview: Generated interview questions
971
- skills: List of required skills
972
- esco_levels: ESCO classification levels
973
- esco_skills: ESCO mapped skills
974
 
 
 
 
 
 
 
 
975
  Returns:
976
  Path to the generated Word document
977
  """
 
 
 
 
 
 
 
 
 
 
978
  # Initialize document with metadata
979
  doc = Document()
980
  doc.core_properties.author = "IOM Talent Management System"
@@ -983,10 +1009,10 @@ def generate_word_document(
983
  # Default values for all fields
984
  default_values = {
985
  "file": "Unknown file",
986
- "responsibilities": "No responsibilities extracted",
987
- "classified_job_family": "No job family identified",
988
- "qualification": ["No qualification information available"],
989
- "interview": ["No interview questions generated"],
990
  "skills": {"skills": [{"skill_name": "No skills identified", "description": "", "code": ""}]},
991
  "skills_esco": {"skills": [{"skill_name": "No ESCO skills identified", "description": "", "code": ""}]}
992
  }
@@ -994,13 +1020,15 @@ def generate_word_document(
994
  # Safely build the result dictionary with fallbacks
995
  try:
996
  result = {
997
- "file": file_name if file_name and isinstance(file_name, str) else default_values["file"],
998
- "responsibilities": responsibilities if responsibilities else default_values["responsibilities"],
999
- "classified_job_family": job_family if job_family else default_values["classified_job_family"],
1000
- "qualification": qualification.split('\n') if qualification else default_values["qualification"],
1001
- "interview": interview.split('\n') if interview else default_values["interview"],
1002
- "skills": {"skills": skills} if skills and isinstance(skills, list) else default_values["skills"],
1003
- "skills_esco": esco_skills if esco_skills and isinstance(esco_skills, dict) else default_values["skills_esco"]
 
 
1004
  }
1005
 
1006
  # Add level information with validation
@@ -1014,115 +1042,49 @@ def generate_word_document(
1014
  log_debug(f"Error building result dictionary: {str(e)}")
1015
  result = default_values
1016
 
1017
- # ================= DOCUMENT CONTENT GENERATION =================
1018
  try:
1019
  # Document header
1020
  doc.add_heading('Job Description Analysis Report', level=0)
1021
  doc.add_paragraph(f"Generated on {time.strftime('%Y-%m-%d %H:%M:%S')}")
1022
  doc.add_paragraph("International Organization for Migration", style="Intense Quote")
1023
 
1024
- # Metadata table
1025
- table = doc.add_table(rows=1, cols=2)
1026
- table.style = 'Light Shading Accent 1'
1027
- hdr_cells = table.rows[0].cells
1028
- hdr_cells[0].text = 'Field'
1029
- hdr_cells[1].text = 'Value'
1030
-
1031
- def _add_table_row(table, field, value):
1032
- row = table.add_row().cells
1033
- row[0].text = field
1034
- row[1].text = str(value or "Not available")
1035
-
1036
- _add_table_row(table, "File Name", result["file"])
1037
- _add_table_row(table, "Job Family", result["classified_job_family"])
1038
-
1039
- # Section generator with error handling
1040
- def _add_section(heading, content, level=2):
1041
- doc.add_heading(heading, level=level)
1042
- if not content:
1043
- doc.add_paragraph("No information available", style='Subtle Emphasis')
1044
- return
1045
-
1046
- if isinstance(content, (list, tuple)):
1047
- for item in content:
1048
- if item and str(item).strip():
1049
- doc.add_paragraph(str(item).strip(), style='List Bullet' if level > 2 else None)
1050
- elif isinstance(content, dict):
1051
- for k, v in content.items():
1052
- if v is not None:
1053
- doc.add_paragraph(f"{k}: {v}")
1054
- elif isinstance(content, str):
1055
- doc.add_paragraph(content)
1056
-
1057
- # Core sections
1058
- _add_section("1. Responsibilities", result["responsibilities"])
1059
- _add_section("2. Qualifications", result["qualification"])
1060
-
1061
- # Skills sections with robust handling
1062
- def _add_skills_section(heading, skills_data):
1063
- doc.add_heading(heading, level=2)
1064
- if not skills_data or not skills_data.get("skills"):
1065
- doc.add_paragraph("No skills information available", style='Subtle Emphasis')
1066
- return
1067
-
1068
- try:
1069
- skills_table = doc.add_table(rows=1, cols=4)
1070
- skills_table.style = 'Medium List 2 Accent 1'
1071
- hdr = skills_table.rows[0].cells
1072
- hdr[0].text = 'Skill'
1073
- hdr[1].text = 'Description'
1074
- hdr[2].text = 'Proficiency'
1075
- hdr[3].text = 'Assessment'
1076
-
1077
- for skill in skills_data["skills"]:
1078
- if not isinstance(skill, dict):
1079
- continue
1080
-
1081
- row = skills_table.add_row().cells
1082
- row[0].text = str(skill.get("skill_name", "Unnamed skill"))
1083
- row[1].text = str(skill.get("skill_description", ""))[:100] + ("..." if len(str(skill.get("skill_description", ""))) > 100 else "")
1084
- row[2].text = str(skill.get("proficiency_level", "Not specified"))
1085
- row[3].text = str(skill.get("assessment_method", "Not specified"))
1086
- except Exception as e:
1087
- doc.add_paragraph(f"Could not display skills table: {str(e)}", style='Subtle Emphasis')
1088
-
1089
- _add_skills_section("3. Required Skills", result["skills"])
1090
- _add_skills_section("4. ESCO Mapped Skills", result["skills_esco"])
1091
-
1092
- # Classification sections
1093
- def _add_classification_section(heading, prefix, levels=4):
1094
- doc.add_heading(heading, level=2)
1095
- found = False
1096
- for i in range(1, levels+1):
1097
- code = result.get(f"{prefix}_{i}_code")
1098
- name = result.get(f"{prefix}_{i}_name")
1099
- desc = result.get(f"{prefix}_{i}_desc")
1100
-
1101
- if any([code, name, desc]):
1102
- found = True
1103
- doc.add_heading(f"Level {i}", level=3)
1104
- if code:
1105
- doc.add_paragraph(f"Code: {code}")
1106
- if name:
1107
- doc.add_paragraph(f"Name: {name}")
1108
- if desc:
1109
- doc.add_paragraph(f"Description: {desc}")
1110
-
1111
- if not found:
1112
- doc.add_paragraph("No classification information available", style='Subtle Emphasis')
1113
-
1114
- _add_classification_section("5. CCOG Classification", "Level_CCOG")
1115
- _add_classification_section("6. ESCO Classification", "Level_ESCO", levels=5)
1116
-
1117
- # Interview questions
1118
- doc.add_heading("7. Suggested Interview Questions", level=2)
1119
- if result["interview"] and any(q.strip() for q in result["interview"]):
1120
- for i, question in enumerate(result["interview"], 1):
1121
- if question.strip():
1122
- doc.add_paragraph(f"{i}. {question}", style='List Number')
1123
- else:
1124
- doc.add_paragraph("No interview questions generated", style='Subtle Emphasis')
1125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1126
  # Footer
1127
  doc.add_paragraph()
1128
  doc.add_paragraph("Generated by IOM Talent Management AI Tool", style='Footer')
@@ -1134,7 +1096,7 @@ def generate_word_document(
1134
  doc.add_heading("Partial Report Generated", level=1)
1135
  doc.add_paragraph(f"Some sections could not be generated due to: {str(e)}")
1136
 
1137
- # ================= FILE SAVING WITH MULTIPLE FALLBACKS =================
1138
  try:
1139
  # Generate appropriate filename
1140
  if file_name and isinstance(file_name, str):
@@ -1167,6 +1129,7 @@ def generate_word_document(
1167
  error_doc.save(fallback_path)
1168
  return fallback_path
1169
 
 
1170
  # ================= GRADIO INTERFACE =================
1171
  with gr.Blocks(
1172
  title="AI-powered tool to review Job Position Description",
@@ -1197,17 +1160,30 @@ css="""
1197
  /* Gradio layout fixes */
1198
  .gradio-container {
1199
  max-width: none !important;
1200
- padding: 0 !important;
1201
  }
1202
 
1203
  .gradio-container .gradio-row {
1204
- max-width: none !important;
1205
- margin: 0 !important;
 
 
 
1206
  }
1207
-
1208
  .gradio-container .gradio-column {
1209
- min-width: 0 !important;
1210
- padding: 0 !important;
 
 
 
 
 
 
 
 
 
 
1211
  }
1212
 
1213
  /* Set the size of the SVG icon for file download */
@@ -1363,14 +1339,15 @@ label {
1363
  box-sizing: border-box;
1364
  }
1365
 
 
 
1366
  .skills-container {
1367
- display: grid;
1368
- grid-template-columns: repeat(auto-fill, minmax(min(350px, 100%), 1fr));
1369
- gap: 1.5rem;
1370
- padding: 1rem;
1371
- width: 100%;
1372
- max-width: 100%;
1373
- box-sizing: border-box;
1374
  }
1375
 
1376
  /* Card styling */
@@ -1678,6 +1655,15 @@ progress::-webkit-progress-value {
1678
  padding: 1rem !important;
1679
  }
1680
  /* Responsive Layout */
 
 
 
 
 
 
 
 
 
1681
  @media (max-width: 768px) {
1682
  .gr-row {
1683
  flex-direction: column !important;
@@ -1789,7 +1775,7 @@ progress::-webkit-progress-value {
1789
  with gr.Row():
1790
  with gr.Column():
1791
  gr.Markdown("## Interview Questions")
1792
- interview_output = gr.Textbox(label="Interview Questions", lines=10, interactive=False)
1793
 
1794
  with gr.Row():
1795
  with gr.Column():
@@ -1824,6 +1810,7 @@ progress::-webkit-progress-value {
1824
  interactive=False,
1825
  elem_classes=["debug-console"]
1826
  )
 
1827
 
1828
  submit_btn.click(
1829
  fn=process_pdf,
@@ -1838,22 +1825,15 @@ progress::-webkit-progress-value {
1838
  skills_output,
1839
  esco_levels_output,
1840
  esco_skills_output,
1841
- debug_console if DEBUG else None
 
1842
  ]
1843
  )
1844
 
1845
  download_btn.click(
1846
  fn=generate_word_document,
1847
  inputs=[
1848
- file_name_output,
1849
- responsibilities_output,
1850
- job_family_output,
1851
- qualification_output,
1852
- ccoq_levels_output,
1853
- interview_output,
1854
- skills_output,
1855
- esco_levels_output,
1856
- esco_skills_output
1857
  ],
1858
  outputs=gr.File(label="Download the corresponding Word report")
1859
  )
 
834
  esco_occ_future = executor.submit(classify_esco_by_hierarchical_level, responsibilities)
835
  qualification_future = executor.submit(extract_qualification, responsibilities)
836
  skills_future = executor.submit(extract_skills, responsibilities)
837
+
838
  job_family = job_family_future.result()
839
  occ_group = occ_group_future.result()
840
  esco_occ = esco_occ_future.result()
 
843
 
844
  log_debug(f"Identified {job_family}")
845
 
846
+ interview = build_interview(responsibilities, skills)
 
 
 
 
 
 
 
 
847
 
848
+ ## Map skills from responsibilities
849
+ skill_map = map_proficiency_and_assessment(skills, responsibilities)
850
  time.sleep(6)
851
  assessment_lookup = {item['skill_name']: item for item in skill_map}
852
  joined_skills = [
 
864
  for skill in skills
865
  ]
866
 
867
+
868
+ ## Generate ESCO skills if we have level 5 mapping....
869
+ has_esco = esco_occ.get("Level_5_ESCO_code") is not None
870
+ skill_esco_extract = []
871
+ skill_esco_map = []
872
+ if has_esco:
873
+ Level_5_code = esco_occ["Level_5_ESCO_code"]
874
+ skill_esco_extract = review_skills(Level_5_code)
875
+ skill_esco_map = map_proficiency_and_assessment(skill_esco_extract, responsibilities)
876
+ else:
877
+ log_debug(f"No Level 5 ESCO code found for {os.path.basename(file.name)}, skipping ESCO skills mapping")
878
+
879
+ joined_skills_esco = []
880
+ if has_esco and skill_esco_extract:
881
+ assessment_esco_lookup = {item['skill_name']: item for item in skill_esco_map}
882
+ joined_skills_esco = [
883
+ {
884
+ "skill_name": skill["skill_name"],
885
+ "skill_description": skill["skill_description"],
886
+ "skill_code": skill["skill_code"],
887
+ **assessment_esco_lookup.get(skill["skill_name"], {})
888
+ }
889
+ for skill in skill_esco_extract
890
+ ]
891
+
892
+ if has_esco:
893
+ esco_levels = {f"Level_{i}_ESCO_{field}": esco_occ.get(f"Level_{i}_ESCO_{field}")
894
+ for i in range(1, 6) for field in ["code", "name", "desc"]}
895
+ esco_skills = {
896
+ "skills": joined_skills_esco
897
+ }
898
+ else:
899
+ esco_levels = {f"Level_{i}_ESCO_{field}": None
900
+ for i in range(1, 6) for field in ["code", "name", "desc"]}
901
+ esco_skills = None
902
+
903
+
904
  # Prepare all data for JSON output
905
  result_data = {
906
  "file_name": os.path.basename(file.name),
 
912
  "interview_questions": build_interview(responsibilities, skills),
913
  "skills": joined_skills,
914
  "esco_levels": {f"Level_{i}_ESCO_{field}": esco_occ.get(f"Level_{i}_ESCO_{field}")
915
+ for i in range(1, 5) for field in ["code", "name", "desc"]},
916
+ "esco_skills": esco_skills,
 
 
 
 
 
 
 
 
 
 
917
  "processing_time": time.strftime("%Y-%m-%d %H:%M:%S")
918
  }
919
 
 
923
  json_path = f.name
924
  log_debug(f"Results saved to temporary JSON file: {json_path}")
925
 
926
+ # Format outputs for display through html cards
927
  formatted_skills = format_skill_cards(joined_skills)
928
  formatted_ccog = format_ccog_card(result_data['ccoq_levels'])
929
  formatted_esco_levels = format_esco_card(result_data['esco_levels'])
 
935
  job_family,
936
  "\n".join(qualification),
937
  formatted_ccog,
938
+ "\n".join(interview),
939
  formatted_skills,
940
  formatted_esco_levels,
941
  formatted_esco_skills,
942
+ debug_message if DEBUG else None,
943
  json_path # Return path to JSON file
944
  )
945
 
 
960
  error_message,
961
  None # No JSON path on error
962
  )
963
+
964
+
965
  # ================= Build Word Report =================
966
  from docx import Document
967
  import os
 
970
  import tempfile
971
  from typing import Dict, List, Union
972
 
973
+ def create_error_doc(message: str) -> str:
974
+ """Create a simple Word document with an error message."""
975
+ doc = Document()
976
+ doc.add_heading('Error Generating Report', level=1)
977
+ doc.add_paragraph(message)
978
+ temp_file = tempfile.NamedTemporaryFile(suffix=".docx", delete=False)
979
+ doc.save(temp_file.name)
980
+ return temp_file.name
981
+
 
 
 
 
982
 
 
 
 
 
 
 
 
 
 
 
983
 
984
+ def generate_word_document(json_path: Optional[str]) -> str:
985
+ """
986
+ Generate a Word document from the analysis results JSON file.
987
+
988
+ Args:
989
+ json_path: Path to the JSON file containing analysis results
990
+
991
  Returns:
992
  Path to the generated Word document
993
  """
994
+ if not json_path or not os.path.exists(json_path):
995
+ return create_error_doc("No valid analysis data was provided.")
996
+
997
+ try:
998
+ with open(json_path, 'r') as f:
999
+ data = json.load(f)
1000
+ except Exception as e:
1001
+ return create_error_doc(f"Failed to load JSON file: {str(e)}")
1002
+
1003
+
1004
  # Initialize document with metadata
1005
  doc = Document()
1006
  doc.core_properties.author = "IOM Talent Management System"
 
1009
  # Default values for all fields
1010
  default_values = {
1011
  "file": "Unknown file",
1012
+ "responsibilities": "No responsibilities extracted.",
1013
+ "classified_job_family": "No job family identified.",
1014
+ "qualification": ["No qualification information available."],
1015
+ "interview": ["No interview questions generated."],
1016
  "skills": {"skills": [{"skill_name": "No skills identified", "description": "", "code": ""}]},
1017
  "skills_esco": {"skills": [{"skill_name": "No ESCO skills identified", "description": "", "code": ""}]}
1018
  }
 
1020
  # Safely build the result dictionary with fallbacks
1021
  try:
1022
  result = {
1023
+ "file": data.get("file", default_values["file"]),
1024
+ "responsibilities": data.get("responsibilities", default_values["responsibilities"]),
1025
+ "classified_job_family": data.get("job_family", default_values["classified_job_family"]),
1026
+ "qualification": data.get("qualification", default_values["qualification"]),
1027
+ "interview": data.get("interview", default_values["interview"]),
1028
+ "skills": data.get("skills", default_values["skills"]),
1029
+ "skills_esco": data.get("skills_esco", default_values["skills_esco"]),
1030
+ "ccog_levels": data.get("ccog_levels", {}),
1031
+ "esco_levels": data.get("esco_levels", {})
1032
  }
1033
 
1034
  # Add level information with validation
 
1042
  log_debug(f"Error building result dictionary: {str(e)}")
1043
  result = default_values
1044
 
1045
+ # DOCUMENT CONTENT GENERATION
1046
  try:
1047
  # Document header
1048
  doc.add_heading('Job Description Analysis Report', level=0)
1049
  doc.add_paragraph(f"Generated on {time.strftime('%Y-%m-%d %H:%M:%S')}")
1050
  doc.add_paragraph("International Organization for Migration", style="Intense Quote")
1051
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1052
 
1053
+
1054
+ doc.add_heading('Position Description Analysis Report', level=1)
1055
+ doc.add_paragraph(f"File: {result['file']}")
1056
+ doc.add_paragraph(f"Job Family: {result['classified_job_family']}")
1057
+
1058
+ doc.add_heading('Responsibilities', level=2)
1059
+ doc.add_paragraph(result['responsibilities'])
1060
+
1061
+ doc.add_heading('Qualifications', level=2)
1062
+ for item in result['qualification']:
1063
+ doc.add_paragraph(item, style='List Bullet')
1064
+
1065
+ doc.add_heading('Interview Questions', level=2)
1066
+ for item in result['interview']:
1067
+ doc.add_paragraph(item, style='List Bullet')
1068
+
1069
+ doc.add_heading('Skills (Extracted)', level=2)
1070
+ for skill in result['skills'].get("skills", []):
1071
+ doc.add_paragraph(f"{skill.get('skill_name', 'Unnamed Skill')} - {skill.get('description', '')}")
1072
+
1073
+ doc.add_heading('Skills (ESCO)', level=2)
1074
+ for skill in result['skills_esco'].get("skills", []):
1075
+ doc.add_paragraph(f"{skill.get('skill_name', 'Unnamed Skill')} - {skill.get('description', '')}")
1076
+
1077
+ if result["ccog_levels"]:
1078
+ doc.add_heading('C-COG Levels', level=2)
1079
+ for key, value in result["ccog_levels"].items():
1080
+ doc.add_paragraph(f"{key}: {value}")
1081
+
1082
+ if result["esco_levels"]:
1083
+ doc.add_heading('ESCO Levels', level=2)
1084
+ for key, value in result["esco_levels"].items():
1085
+ doc.add_paragraph(f"{key}: {value}")
1086
+
1087
+
1088
  # Footer
1089
  doc.add_paragraph()
1090
  doc.add_paragraph("Generated by IOM Talent Management AI Tool", style='Footer')
 
1096
  doc.add_heading("Partial Report Generated", level=1)
1097
  doc.add_paragraph(f"Some sections could not be generated due to: {str(e)}")
1098
 
1099
+ # FILE SAVING WITH MULTIPLE FALLBACKS
1100
  try:
1101
  # Generate appropriate filename
1102
  if file_name and isinstance(file_name, str):
 
1129
  error_doc.save(fallback_path)
1130
  return fallback_path
1131
 
1132
+
1133
  # ================= GRADIO INTERFACE =================
1134
  with gr.Blocks(
1135
  title="AI-powered tool to review Job Position Description",
 
1160
  /* Gradio layout fixes */
1161
  .gradio-container {
1162
  max-width: none !important;
1163
+ padding: 0 20px !important;
1164
  }
1165
 
1166
  .gradio-container .gradio-row {
1167
+ max-width: 100% !important;
1168
+ margin: 0 auto !important;
1169
+ flex: 1 !important;
1170
+ display: grid !important;
1171
+ grid-template-columns: 1fr !important;
1172
  }
1173
+
1174
  .gradio-container .gradio-column {
1175
+ min-width: 0 !important;
1176
+ padding: 0 !important;
1177
+ flex: 1 !important;
1178
+ max-width: none !important;
1179
+ }
1180
+
1181
+ /* Ensure the parent container doesn't constrain the grid */
1182
+ .container-wrap {
1183
+ width: 100%;
1184
+ max-width: none !important;
1185
+ padding: 0 !important;
1186
+ margin: 0 !important;
1187
  }
1188
 
1189
  /* Set the size of the SVG icon for file download */
 
1339
  box-sizing: border-box;
1340
  }
1341
 
1342
+
1343
+
1344
  .skills-container {
1345
+ display: grid;
1346
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
1347
+ gap: 1.5rem;
1348
+ padding: 1rem;
1349
+ width: 100%;
1350
+ margin: 0 auto;
 
1351
  }
1352
 
1353
  /* Card styling */
 
1655
  padding: 1rem !important;
1656
  }
1657
  /* Responsive Layout */
1658
+
1659
+ /* For larger screens */
1660
+ @media (min-width: 1200px) {
1661
+ .skills-container {
1662
+ grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
1663
+ max-width: 1400px;
1664
+ }
1665
+ }
1666
+
1667
  @media (max-width: 768px) {
1668
  .gr-row {
1669
  flex-direction: column !important;
 
1775
  with gr.Row():
1776
  with gr.Column():
1777
  gr.Markdown("## Interview Questions")
1778
+ interview_output = gr.Textbox(label="", lines=10, interactive=False)
1779
 
1780
  with gr.Row():
1781
  with gr.Column():
 
1810
  interactive=False,
1811
  elem_classes=["debug-console"]
1812
  )
1813
+ temp_json_path = gr.Textbox(label="", interactive=False)
1814
 
1815
  submit_btn.click(
1816
  fn=process_pdf,
 
1825
  skills_output,
1826
  esco_levels_output,
1827
  esco_skills_output,
1828
+ debug_console if DEBUG else None,
1829
+ temp_json_path_ouput
1830
  ]
1831
  )
1832
 
1833
  download_btn.click(
1834
  fn=generate_word_document,
1835
  inputs=[
1836
+ temp_json_path_ouput
 
 
 
 
 
 
 
 
1837
  ],
1838
  outputs=gr.File(label="Download the corresponding Word report")
1839
  )