Do not use cached skills if content updated
Add candidate/job route to job-analysis
This commit is contained in:
parent
a0cb23df28
commit
4a7a72812f
@ -199,6 +199,9 @@ RUN pip install openapi-python-client
|
|||||||
# QR code generator
|
# QR code generator
|
||||||
RUN pip install pyqrcode pypng
|
RUN pip install pyqrcode pypng
|
||||||
|
|
||||||
|
# Anthropic and other backends
|
||||||
|
RUN pip install anthropic pydantic_ai
|
||||||
|
|
||||||
# Automatic type conversion pydantic -> typescript
|
# Automatic type conversion pydantic -> typescript
|
||||||
RUN pip install pydantic typing-inspect jinja2
|
RUN pip install pydantic typing-inspect jinja2
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
|
@ -52,7 +52,7 @@ export const navigationConfig: NavigationConfig = {
|
|||||||
{
|
{
|
||||||
id: 'job-analysis',
|
id: 'job-analysis',
|
||||||
label: 'Job Analysis',
|
label: 'Job Analysis',
|
||||||
path: '/job-analysis',
|
path: '/job-analysis/:candidateId?/:jobId?/:stepId?',
|
||||||
variant: 'fullWidth',
|
variant: 'fullWidth',
|
||||||
icon: <WorkIcon />,
|
icon: <WorkIcon />,
|
||||||
component: <JobAnalysisPage />,
|
component: <JobAnalysisPage />,
|
||||||
|
@ -29,6 +29,7 @@ import { JobCreator } from 'components/JobCreator';
|
|||||||
import { LoginRestricted } from 'components/ui/LoginRestricted';
|
import { LoginRestricted } from 'components/ui/LoginRestricted';
|
||||||
import { ResumeGenerator } from 'components/ResumeGenerator';
|
import { ResumeGenerator } from 'components/ResumeGenerator';
|
||||||
import { JobsView } from 'components/ui/JobsView';
|
import { JobsView } from 'components/ui/JobsView';
|
||||||
|
import { Navigate, useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
function WorkAddIcon(): JSX.Element {
|
function WorkAddIcon(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
@ -78,7 +79,13 @@ const capitalize = (str: string): string => {
|
|||||||
// Main component
|
// Main component
|
||||||
const JobAnalysisPage: React.FC<BackstoryPageProps> = () => {
|
const JobAnalysisPage: React.FC<BackstoryPageProps> = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
const { user, guest, apiClient } = useAuth();
|
const { user, guest, apiClient } = useAuth();
|
||||||
|
const { candidateId, jobId, stepId } = useParams<{
|
||||||
|
candidateId?: string;
|
||||||
|
jobId?: string;
|
||||||
|
stepId?: string;
|
||||||
|
}>();
|
||||||
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
|
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
|
||||||
const { selectedJob, setSelectedJob } = useSelectedJob();
|
const { selectedJob, setSelectedJob } = useSelectedJob();
|
||||||
|
|
||||||
@ -95,6 +102,61 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = () => {
|
|||||||
const [activeStep, setActiveStep] = useState<number>(user === null ? 0 : 1);
|
const [activeStep, setActiveStep] = useState<number>(user === null ? 0 : 1);
|
||||||
const maxStep = 4;
|
const maxStep = 4;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (jobId && candidateId && stepId && activeStep !== parseFloat(stepId)) {
|
||||||
|
setActiveStep(parseFloat(stepId));
|
||||||
|
}
|
||||||
|
}, [jobId, candidateId, activeStep, stepId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let routeCandidateId = candidateId || '';
|
||||||
|
let routeJobId = jobId || '';
|
||||||
|
const routeStepId = stepId ? `/${stepId}` : '';
|
||||||
|
|
||||||
|
if (routeCandidateId && routeCandidateId !== selectedCandidate?.id) {
|
||||||
|
apiClient
|
||||||
|
.getCandidate(routeCandidateId)
|
||||||
|
.then((candidate: Candidate | null) => {
|
||||||
|
if (candidate) {
|
||||||
|
setSelectedCandidate(candidate);
|
||||||
|
routeCandidateId = candidate.id || '';
|
||||||
|
} else {
|
||||||
|
setError('Candidate not found.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
console.error('Error fetching candidate:', err);
|
||||||
|
setError('Failed to fetch candidate information.');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
routeCandidateId = selectedCandidate?.id || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeJobId && routeJobId !== selectedJob?.id) {
|
||||||
|
apiClient
|
||||||
|
.getJob(routeJobId)
|
||||||
|
.then((job: Job | null) => {
|
||||||
|
if (job) {
|
||||||
|
setSelectedJob(job);
|
||||||
|
routeJobId = job.id || '';
|
||||||
|
} else {
|
||||||
|
setError('Job not found.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
console.error('Error fetching job:', err);
|
||||||
|
setError('Failed to fetch job information.');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
routeJobId = selectedJob?.id || '';
|
||||||
|
}
|
||||||
|
if (routeCandidateId && routeJobId) {
|
||||||
|
navigate(`/job-analysis/${routeCandidateId}/${routeJobId}${routeStepId}`, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [candidateId, jobId, setSelectedCandidate, setSelectedJob]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedCandidate === null && user !== null) {
|
if (selectedCandidate === null && user !== null) {
|
||||||
apiClient
|
apiClient
|
||||||
@ -209,6 +271,12 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setActiveStep(step);
|
setActiveStep(step);
|
||||||
|
if (selectedCandidate?.id && selectedJob?.id) {
|
||||||
|
const routeStep = step ? `/${step.toString()}` : '';
|
||||||
|
navigate(`/job-analysis/${selectedCandidate.id}/${selectedJob.id}${routeStep}`, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCandidateSelect = (candidate: Candidate): void => {
|
const onCandidateSelect = (candidate: Candidate): void => {
|
||||||
|
@ -1482,6 +1482,24 @@ async def post_job_analysis(
|
|||||||
continue
|
continue
|
||||||
cache_key = get_skill_cache_key(candidate.id, skill)
|
cache_key = get_skill_cache_key(candidate.id, skill)
|
||||||
assessment: SkillAssessment | None = await database.get_cached_skill_match(cache_key)
|
assessment: SkillAssessment | None = await database.get_cached_skill_match(cache_key)
|
||||||
|
# Determine if we need to regenerate the assessment
|
||||||
|
if assessment:
|
||||||
|
# Get the latest RAG data update time for the current user
|
||||||
|
user_rag_update_time = await database.get_user_rag_update_time(candidate.id)
|
||||||
|
|
||||||
|
updated = assessment.updated_at
|
||||||
|
# Check if cached result is still valid
|
||||||
|
# Regenerate if user's RAG data was updated after cache date
|
||||||
|
if user_rag_update_time and user_rag_update_time >= updated:
|
||||||
|
logger.info(f"🔄 Out-of-date cached entry for {candidate.username} skill {assessment.skill}")
|
||||||
|
assessment = None
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
f"✅ Using cached skill match for {candidate.username} skill {assessment.skill}: {cache_key}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info(f"💾 No cached skill match data: {cache_key}, {candidate.id}, {skill}")
|
||||||
|
|
||||||
if not assessment:
|
if not assessment:
|
||||||
logger.info(f"💾 No cached skill match data: {cache_key}, {candidate.id}, {skill}")
|
logger.info(f"💾 No cached skill match data: {cache_key}, {candidate.id}, {skill}")
|
||||||
continue
|
continue
|
||||||
|
Loading…
x
Reference in New Issue
Block a user