From 0741a8b717e865e376076c662ebb9720a272991c Mon Sep 17 00:00:00 2001 From: hermes Date: Thu, 30 Jul 2026 22:05:19 +0000 Subject: [PATCH] =?UTF-8?q?Vision-verified=20UI=20fixes:=20flat=20company/?= =?UTF-8?q?title=20joins=20on=20kanban+detail,=20keyword=20coverage=20rati?= =?UTF-8?q?o=20object=20rendering,=20change=20log=20action/detail=20fields?= =?UTF-8?q?,=20Swedish=20diacritics=20in=20demo=20seed=20and=20UI=20labels?= =?UTF-8?q?=20(Sk=C3=A5ne,=20Malm=C3=B6,=20G=C3=B6teborg,=20=C3=84ngelholm?= =?UTF-8?q?,=20Fr=C3=A5ga,=20Arbetsf=C3=B6rmedlingen)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with Hermes Agent --- apps/api/app/llm.py | 4 ++-- apps/api/app/main.py | 20 +++++++++--------- apps/web/src/types/index.ts | 21 +++++++++++++------ .../views/ApplicationDetail.tailor.test.ts | 14 ++++++------- apps/web/src/views/ApplicationDetail.test.ts | 8 +++---- apps/web/src/views/ApplicationDetail.vue | 21 ++++++++++++------- .../web/src/views/Applications.badges.test.ts | 8 +++---- apps/web/src/views/Applications.test.ts | 16 ++++++-------- apps/web/src/views/Applications.vue | 4 ++-- apps/web/src/views/Research.vue | 2 +- apps/web/src/views/Welcome.vue | 2 +- 11 files changed, 62 insertions(+), 58 deletions(-) diff --git a/apps/api/app/llm.py b/apps/api/app/llm.py index dc68ad0..ccac115 100644 --- a/apps/api/app/llm.py +++ b/apps/api/app/llm.py @@ -175,10 +175,10 @@ def _mock_cv_tailor(prompt: str) -> dict[str, Any]: change_log = [] for b in bullets[:2]: tailored.append(f"{b} (tailored for this posting)") - change_log.append({"action": "rephrased", "detail": f"Emphasized relevance of: {b[:60]}..."}) + change_log.append({"action": "rephrased", "detail": f"Emphasized relevance of {b[:60]}"}) for b in bullets[2:]: tailored.append(b) - change_log.append({"action": "kept", "detail": f"Retained as-is: {b[:60]}..."}) + change_log.append({"action": "kept", "detail": f"Retained as-is {b[:60]}"}) return { "tailored_cv": {"summary": bullets[0][:160], "bullets": tailored}, "change_log": change_log, diff --git a/apps/api/app/main.py b/apps/api/app/main.py index ddad94b..088c218 100644 --- a/apps/api/app/main.py +++ b/apps/api/app/main.py @@ -1151,9 +1151,9 @@ def seed_demo() -> Any: profile = repo_profile.update_profile({ "full_name": "Demo Demosson", "email": "demo@example.com", - "location": "Malmo", + "location": "Malmö", "headline": "Software Developer", - "summary": "Experienced developer looking for opportunities in Skane.", + "summary": "Experienced developer looking for opportunities in Skåne.", "languages": [{"code": "sv", "level": "native"}, {"code": "en", "level": "fluent"}], }) if profile is None: @@ -1161,7 +1161,7 @@ def seed_demo() -> Any: # Create demo CV sections demo_sections = [ - {"kind": "experience", "title": "Backend Developer", "org": "TechSkane AB", "bullets": ["Built REST APIs", "Improved performance by 30%"], "tags": ["python", "fastapi"]}, + {"kind": "experience", "title": "Backend Developer", "org": "TechSkåne AB", "bullets": ["Built REST APIs", "Improved performance by 30%"], "tags": ["python", "fastapi"]}, {"kind": "experience", "title": "Junior Developer", "org": "Lund Software", "bullets": ["Maintained web apps"], "tags": ["javascript"]}, {"kind": "education", "title": "MSc Computer Science", "org": "Lunds Universitet", "bullets": ["Distributed systems specialization"], "tags": ["algorithms"]}, {"kind": "skills", "title": "Technical Skills", "bullets": ["Python", "PostgreSQL", "Docker", "FastAPI"], "tags": ["python", "docker"]}, @@ -1176,12 +1176,12 @@ def seed_demo() -> Any: # -- Create 6 standalone demo postings with varied states -- demo_postings = [ - {"company": "Skane Tech AB", "title": "Senior Python Developer", "location": "Malmo", "url": "https://example.com/af/1", "state": "scored", "score": 85}, + {"company": "Skåne Tech AB", "title": "Senior Python Developer", "location": "Malmö", "url": "https://example.com/af/1", "state": "scored", "score": 85}, {"company": "Lund Systems", "title": "Fullstack Engineer", "location": "Lund", "url": "https://example.com/af/2", "state": "discovered", "score": None}, {"company": "Copenhagen Digital", "title": "Backend Developer", "location": "Copenhagen", "url": "https://example.com/af/3", "state": "approved", "score": 70}, {"company": "Helsingborg IT", "title": "DevOps Engineer", "location": "Helsingborg", "url": "https://example.com/af/4", "state": "sent", "score": 65}, - {"company": "Malmo Startup", "title": "Software Engineer", "location": "Malmo", "url": "https://example.com/af/5", "state": "rejected", "score": 30}, - {"company": "Angelholm Tech", "title": "Data Engineer", "location": "Angelholm", "url": "https://example.com/af/6", "state": "discovered", "score": None}, + {"company": "Malmö Startup", "title": "Software Engineer", "location": "Malmö", "url": "https://example.com/af/5", "state": "rejected", "score": 30}, + {"company": "Ängelholm Tech", "title": "Data Engineer", "location": "Ängelholm", "url": "https://example.com/af/6", "state": "discovered", "score": None}, ] for dp in demo_postings: @@ -1258,7 +1258,7 @@ def seed_demo() -> Any: url=f"https://example.com/af/deadline/{j+1}", company=f"Deadline Corp {j+1}", title=f"Urgent Developer Role {j+1}", - location="Goteborg", + location="Göteborg", description="Urgent hire for a developer with deadline approaching.", raw={}, ) @@ -1290,7 +1290,7 @@ def seed_demo() -> Any: url="https://example.com/af/interview/1", company="Festina Digital AB", title="Full Stack Developer", - location="Malmo", + location="Malmö", description="Full stack developer with React, Python, and cloud experience.", raw={}, ) @@ -1398,8 +1398,8 @@ def seed_demo() -> Any: create_email_suggestion( application_id=interviewing_app_id, mailbox_from="hr@festina-demo.se", - subject="Fraga om din erfarenhet", - snippet="Vi har nagra fragor om din bakgrund inom Python...", + subject="Fråga om din erfarenhet", + snippet="Vi har några frågor om din bakgrund inom Python...", classification="question", state_proposal=None, received_at=datetime.now(timezone.utc) - timedelta(hours=1), diff --git a/apps/web/src/types/index.ts b/apps/web/src/types/index.ts index 97cbe20..6fd5d9c 100644 --- a/apps/web/src/types/index.ts +++ b/apps/web/src/types/index.ts @@ -66,8 +66,10 @@ export interface Application { notes: string state_changed_at: string created_at: string - // joined posting info (from GET /applications) - posting?: JobPosting + // joined posting info (flat fields from GET /applications) + company?: string | null + title?: string | null + location?: string | null } export type ArtifactKind = 'cv' | 'cover_letter' | 'email' | 'other' @@ -262,14 +264,21 @@ export interface Cluster { postings: ClusterPosting[] } +export interface TailorKeywordCoverage { + ratio: number + matched: string[] + missing: string[] +} + export interface TailorChangeLogEntry { - section: string - change: string - bullet_id: string + action?: string + section?: string + detail?: string + change?: string } export interface TailorCvResponse { artifact_id: string change_log: TailorChangeLogEntry[] - keyword_coverage: number + keyword_coverage: TailorKeywordCoverage } \ No newline at end of file diff --git a/apps/web/src/views/ApplicationDetail.tailor.test.ts b/apps/web/src/views/ApplicationDetail.tailor.test.ts index 04ae89b..d4776fe 100644 --- a/apps/web/src/views/ApplicationDetail.tailor.test.ts +++ b/apps/web/src/views/ApplicationDetail.tailor.test.ts @@ -34,11 +34,9 @@ function makeApp(): Application { notes: '', state_changed_at: '2026-01-01T00:00:00Z', created_at: '2026-01-01T00:00:00Z', - posting: { - id: 'j-1', source: 'manual_url', external_id: null, url: 'http://x', - company: 'Acme', title: 'Engineer', location: 'Remote', description: '', - raw: {}, fetched_at: '2026-01-01T00:00:00Z' - } + company: 'Acme', + title: 'Engineer', + location: 'Remote' } } @@ -60,10 +58,10 @@ function makeTailorResult(): TailorCvResponse { return { artifact_id: 'art-tailor-1', change_log: [ - { section: 'experience', change: 'Reordered to highlight Python backend work', bullet_id: 'b-1' }, - { section: 'skills', change: 'Moved Docker and Kubernetes higher', bullet_id: 'b-5' } + { action: 'experience', detail: 'Reordered to highlight Python backend work' }, + { action: 'skills', detail: 'Moved Docker and Kubernetes higher' } ], - keyword_coverage: 0.75 + keyword_coverage: { ratio: 0.75, matched: ['python', 'docker'], missing: ['kubernetes'] } } } diff --git a/apps/web/src/views/ApplicationDetail.test.ts b/apps/web/src/views/ApplicationDetail.test.ts index 0c9f2a2..71950b6 100644 --- a/apps/web/src/views/ApplicationDetail.test.ts +++ b/apps/web/src/views/ApplicationDetail.test.ts @@ -34,11 +34,9 @@ function makeApp(): Application { notes: '', state_changed_at: '2026-01-01T00:00:00Z', created_at: '2026-01-01T00:00:00Z', - posting: { - id: 'j-1', source: 'manual_url', external_id: null, url: 'http://x', - company: 'Acme', title: 'Engineer', location: 'Remote', description: '', - raw: {}, fetched_at: '2026-01-01T00:00:00Z' - } + company: 'Acme', + title: 'Engineer', + location: 'Remote' } } diff --git a/apps/web/src/views/ApplicationDetail.vue b/apps/web/src/views/ApplicationDetail.vue index bbb4be4..a0ed403 100644 --- a/apps/web/src/views/ApplicationDetail.vue +++ b/apps/web/src/views/ApplicationDetail.vue @@ -52,17 +52,22 @@ const severityClass: Record = { low: 'bg-blue-50 border-blue-200' } +const coverageRatio = computed(() => { + if (!tailorResult.value) return 0 + const kc = tailorResult.value.keyword_coverage + return typeof kc === 'object' && kc !== null ? (kc.ratio ?? 0) : Number(kc) || 0 +}) + const coverageColor = computed(() => { if (!tailorResult.value) return 'bg-gray-300' - const c = tailorResult.value.keyword_coverage + const c = coverageRatio.value if (c >= 0.7) return 'bg-green-500' if (c >= 0.4) return 'bg-yellow-500' return 'bg-red-500' }) const coveragePercent = computed(() => { - if (!tailorResult.value) return 0 - return Math.round(tailorResult.value.keyword_coverage * 100) + return Math.round(coverageRatio.value * 100) }) const downloadUrl = computed(() => { @@ -140,7 +145,7 @@ async function sendOutbox() { if (!approval.value || !canSend.value) return sending.value = true try { - await api.outboxSend(approval.value.id, { to: application.value?.posting?.company ?? '' }) + await api.outboxSend(approval.value.id, { to: application.value?.company ?? '' }) toast.push('Sent successfully', 'success') } catch (err) { let msg = 'Send failed' @@ -195,8 +200,8 @@ onMounted(loadData)