progress
This commit is contained in:
27
app.py
27
app.py
@@ -125,7 +125,8 @@ def convert_to_pacific_time(date_str):
|
||||
|
||||
def fetch_all_projects():
|
||||
"""Fetch all projects for a user and store them in Firestore"""
|
||||
|
||||
# This function is now only used by sync.py
|
||||
# In production, this should be removed or marked as deprecated
|
||||
print("Fetching projects....")
|
||||
# Initialize Filevine client
|
||||
client = FilevineClient()
|
||||
@@ -138,19 +139,11 @@ def fetch_all_projects():
|
||||
# Fetch details for each
|
||||
detailed_rows = []
|
||||
|
||||
import worker_pool
|
||||
detailed_rows = worker_pool.process_projects_parallel(projects, client, 9)
|
||||
# Store the results in Firestore
|
||||
projects_ref = db.collection("projects")
|
||||
|
||||
# Add new projects
|
||||
for row in detailed_rows:
|
||||
project_id = str(row.get("ProjectId"))
|
||||
if project_id:
|
||||
projects_ref.document(project_id).set(row)
|
||||
|
||||
print(f"Stored {len(detailed_rows)} projects in Firestore")
|
||||
return detailed_rows
|
||||
# This functionality has been moved to sync.py
|
||||
# The worker_pool module has been removed
|
||||
# This function is kept for backward compatibility but should not be used in production
|
||||
print("[DEPRECATED] fetch_all_projects() is deprecated. Use sync.py instead.")
|
||||
return []
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
@@ -212,12 +205,6 @@ def welcome():
|
||||
# --- Filevine API ---
|
||||
# Filevine client is now in filevine_client.py
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@app.route("/dashboard")
|
||||
@app.route("/dashboard/<int:page>")
|
||||
@login_required
|
||||
|
||||
BIN
models/__pycache__/project_model.cpython-310.pyc
Normal file
BIN
models/__pycache__/project_model.cpython-310.pyc
Normal file
Binary file not shown.
245
models/project_model.py
Normal file
245
models/project_model.py
Normal file
@@ -0,0 +1,245 @@
|
||||
"""
|
||||
Shared data model for Project entities used across the application.
|
||||
This defines the structure of project data that is fetched from Filevine and stored in Firestore.
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
|
||||
class ProjectModel:
|
||||
"""
|
||||
Data model for a Filevine project with all its associated fields.
|
||||
This model defines the structure that will be used for Firestore storage
|
||||
and API responses.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
client: str = "",
|
||||
matter_description: str = "",
|
||||
defendant_1: str = "",
|
||||
matter_open: str = "",
|
||||
notice_type: str = "",
|
||||
case_number: str = "",
|
||||
premises_address: str = "",
|
||||
premises_city: str = "",
|
||||
responsible_attorney: str = "",
|
||||
staff_person: str = "",
|
||||
staff_person_2: str = "",
|
||||
phase_name: str = "",
|
||||
completed_tasks: List[Dict[str, Any]] = None,
|
||||
pending_tasks: List[Dict[str, Any]] = None,
|
||||
notice_service_date: str = "",
|
||||
notice_expiration_date: str = "",
|
||||
case_field_date: str = "",
|
||||
daily_rent_damages: str = "",
|
||||
default_date: str = "",
|
||||
demurrer_hearing_date: str = "",
|
||||
motion_to_strike_hearing_date: str = "",
|
||||
motion_to_quash_hearing_date: str = "",
|
||||
other_motion_hearing_date: str = "",
|
||||
msc_date: str = "",
|
||||
msc_time: str = "",
|
||||
msc_address: str = "",
|
||||
msc_div_dept_room: str = "",
|
||||
trial_date: str = "",
|
||||
trial_time: str = "",
|
||||
trial_address: str = "",
|
||||
trial_div_dept_room: str = "",
|
||||
final_result: str = "",
|
||||
date_of_settlement: str = "",
|
||||
final_obligation: str = "",
|
||||
def_comply_stip: str = "",
|
||||
judgment_date: str = "",
|
||||
writ_issued_date: str = "",
|
||||
scheduled_lockout: str = "",
|
||||
oppose_stays: str = "",
|
||||
premises_safety: str = "",
|
||||
matter_gate_code: str = "",
|
||||
date_possession_recovered: str = "",
|
||||
attorney_fees: str = "",
|
||||
costs: str = "",
|
||||
documents_url: str = "",
|
||||
service_attempt_date_1: str = "",
|
||||
contacts: List[Dict[str, Any]] = None,
|
||||
project_email_address: str = "",
|
||||
number: str = "",
|
||||
incident_date: str = "",
|
||||
project_id: str = "",
|
||||
project_name: str = "",
|
||||
project_url: str = "",
|
||||
property_contacts: Dict[str, Any] = None):
|
||||
|
||||
self.client = client
|
||||
self.matter_description = matter_description
|
||||
self.defendant_1 = defendant_1
|
||||
self.matter_open = matter_open
|
||||
self.notice_type = notice_type
|
||||
self.case_number = case_number
|
||||
self.premises_address = premises_address
|
||||
self.premises_city = premises_city
|
||||
self.responsible_attorney = responsible_attorney
|
||||
self.staff_person = staff_person
|
||||
self.staff_person_2 = staff_person_2
|
||||
self.phase_name = phase_name
|
||||
self.completed_tasks = completed_tasks or []
|
||||
self.pending_tasks = pending_tasks or []
|
||||
self.notice_service_date = notice_service_date
|
||||
self.notice_expiration_date = notice_expiration_date
|
||||
self.case_field_date = case_field_date
|
||||
self.daily_rent_damages = daily_rent_damages
|
||||
self.default_date = default_date
|
||||
self.demurrer_hearing_date = demurrer_hearing_date
|
||||
self.motion_to_strike_hearing_date = motion_to_strike_hearing_date
|
||||
self.motion_to_quash_hearing_date = motion_to_quash_hearing_date
|
||||
self.other_motion_hearing_date = other_motion_hearing_date
|
||||
self.msc_date = msc_date
|
||||
self.msc_time = msc_time
|
||||
self.msc_address = msc_address
|
||||
self.msc_div_dept_room = msc_div_dept_room
|
||||
self.trial_date = trial_date
|
||||
self.trial_time = trial_time
|
||||
self.trial_address = trial_address
|
||||
self.trial_div_dept_room = trial_div_dept_room
|
||||
self.final_result = final_result
|
||||
self.date_of_settlement = date_of_settlement
|
||||
self.final_obligation = final_obligation
|
||||
self.def_comply_stip = def_comply_stip
|
||||
self.judgment_date = judgment_date
|
||||
self.writ_issued_date = writ_issued_date
|
||||
self.scheduled_lockout = scheduled_lockout
|
||||
self.oppose_stays = oppose_stays
|
||||
self.premises_safety = premises_safety
|
||||
self.matter_gate_code = matter_gate_code
|
||||
self.date_possession_recovered = date_possession_recovered
|
||||
self.attorney_fees = attorney_fees
|
||||
self.costs = costs
|
||||
self.documents_url = documents_url
|
||||
self.service_attempt_date_1 = service_attempt_date_1
|
||||
self.contacts = contacts or []
|
||||
self.project_email_address = project_email_address
|
||||
self.number = number
|
||||
self.incident_date = incident_date
|
||||
self.project_id = project_id
|
||||
self.project_name = project_name
|
||||
self.project_url = project_url
|
||||
self.property_contacts = property_contacts or {}
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert the ProjectModel to a dictionary for Firestore storage."""
|
||||
return {
|
||||
"client": self.client,
|
||||
"matter_description": self.matter_description,
|
||||
"defendant_1": self.defendant_1,
|
||||
"matter_open": self.matter_open,
|
||||
"notice_type": self.notice_type,
|
||||
"case_number": self.case_number,
|
||||
"premises_address": self.premises_address,
|
||||
"premises_city": self.premises_city,
|
||||
"responsible_attorney": self.responsible_attorney,
|
||||
"staff_person": self.staff_person,
|
||||
"staff_person_2": self.staff_person_2,
|
||||
"phase_name": self.phase_name,
|
||||
"completed_tasks": self.completed_tasks,
|
||||
"pending_tasks": self.pending_tasks,
|
||||
"notice_service_date": self.notice_service_date,
|
||||
"notice_expiration_date": self.notice_expiration_date,
|
||||
"case_field_date": self.case_field_date,
|
||||
"daily_rent_damages": self.daily_rent_damages,
|
||||
"default_date": self.default_date,
|
||||
"demurrer_hearing_date": self.demurrer_hearing_date,
|
||||
"motion_to_strike_hearing_date": self.motion_to_strike_hearing_date,
|
||||
"motion_to_quash_hearing_date": self.motion_to_quash_hearing_date,
|
||||
"other_motion_hearing_date": self.other_motion_hearing_date,
|
||||
"msc_date": self.msc_date,
|
||||
"msc_time": self.msc_time,
|
||||
"msc_address": self.msc_address,
|
||||
"msc_div_dept_room": self.msc_div_dept_room,
|
||||
"trial_date": self.trial_date,
|
||||
"trial_time": self.trial_time,
|
||||
"trial_address": self.trial_address,
|
||||
"trial_div_dept_room": self.trial_div_dept_room,
|
||||
"final_result": self.final_result,
|
||||
"date_of_settlement": self.date_of_settlement,
|
||||
"final_obligation": self.final_obligation,
|
||||
"def_comply_stip": self.def_comply_stip,
|
||||
"judgment_date": self.judgment_date,
|
||||
"writ_issued_date": self.writ_issued_date,
|
||||
"scheduled_lockout": self.scheduled_lockout,
|
||||
"oppose_stays": self.oppose_stays,
|
||||
"premises_safety": self.premises_safety,
|
||||
"matter_gate_code": self.matter_gate_code,
|
||||
"date_possession_recovered": self.date_possession_recovered,
|
||||
"attorney_fees": self.attorney_fees,
|
||||
"costs": self.costs,
|
||||
"documents_url": self.documents_url,
|
||||
"service_attempt_date_1": self.service_attempt_date_1,
|
||||
"contacts": self.contacts,
|
||||
"ProjectEmailAddress": self.project_email_address,
|
||||
"Number": self.number,
|
||||
"IncidentDate": self.incident_date,
|
||||
"ProjectId": self.project_id,
|
||||
"ProjectName": self.project_name,
|
||||
"ProjectUrl": self.project_url,
|
||||
"property_contacts": self.property_contacts
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'ProjectModel':
|
||||
"""Create a ProjectModel instance from a dictionary (e.g., from Firestore)."""
|
||||
return cls(
|
||||
client=data.get("client", ""),
|
||||
matter_description=data.get("matter_description", ""),
|
||||
defendant_1=data.get("defendant_1", ""),
|
||||
matter_open=data.get("matter_open", ""),
|
||||
notice_type=data.get("notice_type", ""),
|
||||
case_number=data.get("case_number", ""),
|
||||
premises_address=data.get("premises_address", ""),
|
||||
premises_city=data.get("premises_city", ""),
|
||||
responsible_attorney=data.get("responsible_attorney", ""),
|
||||
staff_person=data.get("staff_person", ""),
|
||||
staff_person_2=data.get("staff_person_2", ""),
|
||||
phase_name=data.get("phase_name", ""),
|
||||
completed_tasks=data.get("completed_tasks", []),
|
||||
pending_tasks=data.get("pending_tasks", []),
|
||||
notice_service_date=data.get("notice_service_date", ""),
|
||||
notice_expiration_date=data.get("notice_expiration_date", ""),
|
||||
case_field_date=data.get("case_field_date", ""),
|
||||
daily_rent_damages=data.get("daily_rent_damages", ""),
|
||||
default_date=data.get("default_date", ""),
|
||||
demurrer_hearing_date=data.get("demurrer_hearing_date", ""),
|
||||
motion_to_strike_hearing_date=data.get("motion_to_strike_hearing_date", ""),
|
||||
motion_to_quash_hearing_date=data.get("motion_to_quash_hearing_date", ""),
|
||||
other_motion_hearing_date=data.get("other_motion_hearing_date", ""),
|
||||
msc_date=data.get("msc_date", ""),
|
||||
msc_time=data.get("msc_time", ""),
|
||||
msc_address=data.get("msc_address", ""),
|
||||
msc_div_dept_room=data.get("msc_div_dept_room", ""),
|
||||
trial_date=data.get("trial_date", ""),
|
||||
trial_time=data.get("trial_time", ""),
|
||||
trial_address=data.get("trial_address", ""),
|
||||
trial_div_dept_room=data.get("trial_div_dept_room", ""),
|
||||
final_result=data.get("final_result", ""),
|
||||
date_of_settlement=data.get("date_of_settlement", ""),
|
||||
final_obligation=data.get("final_obligation", ""),
|
||||
def_comply_stip=data.get("def_comply_stip", ""),
|
||||
judgment_date=data.get("judgment_date", ""),
|
||||
writ_issued_date=data.get("writ_issued_date", ""),
|
||||
scheduled_lockout=data.get("scheduled_lockout", ""),
|
||||
oppose_stays=data.get("oppose_stays", ""),
|
||||
premises_safety=data.get("premises_safety", ""),
|
||||
matter_gate_code=data.get("matter_gate_code", ""),
|
||||
date_possession_recovered=data.get("date_possession_recovered", ""),
|
||||
attorney_fees=data.get("attorney_fees", ""),
|
||||
costs=data.get("costs", ""),
|
||||
documents_url=data.get("documents_url", ""),
|
||||
service_attempt_date_1=data.get("service_attempt_date_1", ""),
|
||||
contacts=data.get("contacts", []),
|
||||
project_email_address=data.get("ProjectEmailAddress", ""),
|
||||
number=data.get("Number", ""),
|
||||
incident_date=data.get("IncidentDate", ""),
|
||||
project_id=data.get("ProjectId", ""),
|
||||
project_name=data.get("ProjectName", ""),
|
||||
project_url=data.get("ProjectUrl", ""),
|
||||
property_contacts=data.get("property_contacts", {})
|
||||
)
|
||||
264
sync.py
264
sync.py
@@ -6,24 +6,276 @@ This can be run manually from the command line to update the projects collection
|
||||
|
||||
import sys
|
||||
import os
|
||||
import concurrent.futures
|
||||
import threading
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
|
||||
# Add the current directory to the Python path so we can import app
|
||||
# Add the current directory to the Python path so we can import app and models
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from app import fetch_all_projects
|
||||
from app import fetch_all_projects, convert_to_pacific_time
|
||||
from models.project_model import ProjectModel
|
||||
from filevine_client import FilevineClient
|
||||
|
||||
# Global thread-local storage for FilevineClient to avoid passing it around
|
||||
_thread_local = threading.local()
|
||||
|
||||
def get_filevine_client():
|
||||
"""Get FilevineClient from thread local storage"""
|
||||
return getattr(_thread_local, 'client', None)
|
||||
|
||||
def set_filevine_client(client):
|
||||
"""Set FilevineClient in thread local storage"""
|
||||
_thread_local.client = client
|
||||
|
||||
def worker_init(client: FilevineClient):
|
||||
"""Initialize worker with FilevineClient"""
|
||||
set_filevine_client(client)
|
||||
|
||||
def process_project(index: int, total: int, project_data: dict, client: FilevineClient) -> Dict[str, Any]:
|
||||
"""
|
||||
Process a single project with all its API calls.
|
||||
This is the function that will be executed by workers in parallel.
|
||||
"""
|
||||
# Set the FilevineClient for this thread
|
||||
set_filevine_client(client)
|
||||
|
||||
p = project_data
|
||||
pid = (p.get("projectId") or {}).get("native")
|
||||
print(f"Working on {pid} ({index}/{total})")
|
||||
client = get_filevine_client()
|
||||
|
||||
if pid is None:
|
||||
return {}
|
||||
|
||||
try:
|
||||
c = client.fetch_client((p.get("clientId") or {}).get("native"))
|
||||
cs = client.fetch_contacts(pid)
|
||||
detail = client.fetch_project_detail(pid)
|
||||
except Exception as e:
|
||||
print(f"[WARN] Failed to fetch essential data for {pid}: {e}")
|
||||
return {}
|
||||
|
||||
defendant_one = next((c.get('orgContact', {}) for c in cs if "Defendant" in c.get('orgContact', {}).get('personTypes', [])), {})
|
||||
|
||||
try:
|
||||
new_file_review = client.fetch_form(pid, "newFileReview") or {}
|
||||
dates_and_deadlines = client.fetch_form(pid, "datesAndDeadlines") or {}
|
||||
service_info = client.fetch_collection(pid, "serviceInfo") or []
|
||||
property_info = client.fetch_form(pid, "propertyInfo") or {}
|
||||
matter_overview = client.fetch_form(pid, "matterOverview") or {}
|
||||
fees_and_costs = client.fetch_form(pid, "feesAndCosts") or {}
|
||||
property_contacts = client.fetch_form(pid, "propertyContacts") or {}
|
||||
lease_info_np = client.fetch_form(pid, "leaseInfoNP") or {}
|
||||
|
||||
tasks_result = client.fetch_project_tasks(pid)
|
||||
completed_tasks = [{"description": x.get("body"),
|
||||
"completed": convert_to_pacific_time(x.get("completedDate"))}
|
||||
for x in tasks_result.get("items", [])
|
||||
if x.get("isCompleted")]
|
||||
pending_tasks = [{"description": x.get("body"),
|
||||
"completed": convert_to_pacific_time(x.get("completedDate"))}
|
||||
for x in tasks_result.get("items", [])
|
||||
if not x.get("isCompleted")]
|
||||
|
||||
team = client.fetch_project_team(pid)
|
||||
assigned_attorney = next((m.get('fullname')
|
||||
for m in team
|
||||
if ('Assigned Attorney' in [r.get('name') for r in m.get('teamOrgRoles')])
|
||||
), '')
|
||||
primary_contact = next((m.get('fullname')
|
||||
for m in team
|
||||
if ('Primary' in [r.get('name') for r in m.get('teamOrgRoles')])
|
||||
), '')
|
||||
secondary_paralegal = next((m.get('fullname')
|
||||
for m in team
|
||||
if ('Secondary Paralegal' in [r.get('name') for r in m.get('teamOrgRoles')])
|
||||
), '')
|
||||
|
||||
# Extract notice service and expiration dates
|
||||
notice_service_date = convert_to_pacific_time(new_file_review.get("noticeServiceDate")) or ''
|
||||
notice_expiration_date = convert_to_pacific_time(new_file_review.get("noticeExpirationDate")) or ''
|
||||
|
||||
# Extract daily rent damages
|
||||
daily_rent_damages = lease_info_np.get("dailyRentDamages") or dates_and_deadlines.get("dailyRentDamages") or ''
|
||||
|
||||
# Extract default date
|
||||
default_date = convert_to_pacific_time(dates_and_deadlines.get("defaultDate")) or ''
|
||||
case_filed_date = convert_to_pacific_time(dates_and_deadlines.get("dateCaseFiled")) or ''
|
||||
|
||||
# Extract motion hearing dates
|
||||
demurrer_hearing_date = convert_to_pacific_time(dates_and_deadlines.get("demurrerHearingDate")) or ''
|
||||
motion_to_strike_hearing_date = convert_to_pacific_time(dates_and_deadlines.get("mTSHearingDate")) or ''
|
||||
motion_to_quash_hearing_date = convert_to_pacific_time(dates_and_deadlines.get("mTQHearingDate")) or ''
|
||||
other_motion_hearing_date = convert_to_pacific_time(dates_and_deadlines.get("otherMotion1HearingDate")) or ''
|
||||
|
||||
# Extract MSC details
|
||||
msc_date = convert_to_pacific_time(dates_and_deadlines.get("mSCDate")) or ''
|
||||
msc_time = dates_and_deadlines.get("mSCTime") or '' # Time field, not converting
|
||||
msc_address = dates_and_deadlines.get("mSCAddress") or ''
|
||||
msc_div_dept_room = dates_and_deadlines.get("mSCDeptDiv") or ''
|
||||
|
||||
# Extract trial details
|
||||
trial_date = convert_to_pacific_time(dates_and_deadlines.get("trialDate")) or ''
|
||||
trial_time = dates_and_deadlines.get("trialTime") or '' # Time field, not converting
|
||||
trial_address = dates_and_deadlines.get("trialAddress") or ''
|
||||
trial_div_dept_room = dates_and_deadlines.get("trialDeptDivRoom") or ''
|
||||
|
||||
# Extract final result of trial/MSC
|
||||
final_result = dates_and_deadlines.get("finalResultOfTrialMSCCa") or ''
|
||||
|
||||
# Extract settlement details
|
||||
date_of_settlement = convert_to_pacific_time(dates_and_deadlines.get("dateOfStipulation")) or ''
|
||||
final_obligation = dates_and_deadlines.get("finalObligationUnderTheStip") or ''
|
||||
def_comply_stip = dates_and_deadlines.get("defendantsComplyWithStip") or ''
|
||||
|
||||
# Extract judgment and writ details
|
||||
judgment_date = convert_to_pacific_time(dates_and_deadlines.get("dateOfJudgment")) or ''
|
||||
writ_issued_date = convert_to_pacific_time(dates_and_deadlines.get("writIssuedDate")) or ''
|
||||
|
||||
# Extract lockout and stay details
|
||||
scheduled_lockout = convert_to_pacific_time(dates_and_deadlines.get("sheriffScheduledDate")) or ''
|
||||
oppose_stays = dates_and_deadlines.get("opposeStays") or ''
|
||||
|
||||
# Extract premises safety and entry code
|
||||
premises_safety = new_file_review.get("lockoutSafetyIssuesOrSpecialCareIssues") or ''
|
||||
matter_gate_code = property_info.get("propertyEntryCodeOrInstructions") or ''
|
||||
|
||||
# Extract possession recovered date
|
||||
date_possession_recovered = convert_to_pacific_time(dates_and_deadlines.get("datePossessionRecovered")) or ''
|
||||
|
||||
# Extract attorney fees and costs
|
||||
attorney_fees = fees_and_costs.get("totalAttorneysFees") or ''
|
||||
costs = fees_and_costs.get("totalCosts") or ''
|
||||
|
||||
row = ProjectModel(
|
||||
client=c.get("firstName", ""),
|
||||
matter_description=p.get("projectName", ""),
|
||||
defendant_1=defendant_one.get('fullName', 'Unknown'),
|
||||
matter_open=convert_to_pacific_time(dates_and_deadlines.get("dateCaseFiled") or p.get("createdDate")),
|
||||
notice_type=new_file_review.get("noticeType", '') or '',
|
||||
case_number=dates_and_deadlines.get('caseNumber', '') or '',
|
||||
premises_address=property_info.get("premisesAddressWithUnit", "") or '',
|
||||
premises_city=property_info.get("premisesCity", "") or '',
|
||||
responsible_attorney=assigned_attorney,
|
||||
staff_person=primary_contact,
|
||||
staff_person_2=secondary_paralegal,
|
||||
phase_name=p.get("phaseName", ""),
|
||||
completed_tasks=completed_tasks,
|
||||
pending_tasks=pending_tasks,
|
||||
notice_service_date=notice_service_date,
|
||||
notice_expiration_date=notice_expiration_date,
|
||||
case_field_date=case_filed_date,
|
||||
daily_rent_damages=daily_rent_damages,
|
||||
default_date=default_date,
|
||||
demurrer_hearing_date=demurrer_hearing_date,
|
||||
motion_to_strike_hearing_date=motion_to_strike_hearing_date,
|
||||
motion_to_quash_hearing_date=motion_to_quash_hearing_date,
|
||||
other_motion_hearing_date=other_motion_hearing_date,
|
||||
msc_date=msc_date,
|
||||
msc_time=msc_time,
|
||||
msc_address=msc_address,
|
||||
msc_div_dept_room=msc_div_dept_room,
|
||||
trial_date=trial_date,
|
||||
trial_time=trial_time,
|
||||
trial_address=trial_address,
|
||||
trial_div_dept_room=trial_div_dept_room,
|
||||
final_result=final_result,
|
||||
date_of_settlement=date_of_settlement,
|
||||
final_obligation=final_obligation,
|
||||
def_comply_stip=def_comply_stip,
|
||||
judgment_date=judgment_date,
|
||||
writ_issued_date=writ_issued_date,
|
||||
scheduled_lockout=scheduled_lockout,
|
||||
oppose_stays=oppose_stays,
|
||||
premises_safety=premises_safety,
|
||||
matter_gate_code=matter_gate_code,
|
||||
date_possession_recovered=date_possession_recovered,
|
||||
attorney_fees=attorney_fees,
|
||||
costs=costs,
|
||||
documents_url=matter_overview.get('documentShareFolderURL', '') or '',
|
||||
service_attempt_date_1=convert_to_pacific_time(next(iter(service_info), {}).get('serviceDate')),
|
||||
contacts=cs,
|
||||
project_email_address=p.get("projectEmailAddress", ""),
|
||||
number=p.get("number", ""),
|
||||
incident_date=convert_to_pacific_time(p.get("incidentDate") or detail.get("incidentDate")),
|
||||
project_id=pid,
|
||||
project_name=p.get("projectName") or detail.get("projectName"),
|
||||
project_url=p.get("projectUrl") or detail.get("projectUrl"),
|
||||
property_contacts=property_contacts
|
||||
)
|
||||
# Store the results in Firestore
|
||||
from app import db # Import db from app
|
||||
|
||||
projects_ref = db.collection("projects")
|
||||
|
||||
# Add new projects
|
||||
project_id = row.project_id
|
||||
if project_id:
|
||||
projects_ref.document(str(project_id)).set(row.to_dict())
|
||||
|
||||
print(f"Finished on {pid} ({index}/{total})")
|
||||
return row.to_dict()
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Processing failed for {pid}: {e}")
|
||||
return {}
|
||||
|
||||
def process_projects_parallel(projects: List[dict], client: FilevineClient, max_workers: int = 9) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Process projects in parallel using a worker pool.
|
||||
|
||||
Args:
|
||||
projects: List of project data dictionaries
|
||||
client: FilevineClient instance
|
||||
max_workers: Number of concurrent workers (default 9)
|
||||
|
||||
Returns:
|
||||
List of processed project dictionaries
|
||||
"""
|
||||
# Create a thread pool with specified number of workers
|
||||
total = len(projects)
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers, initializer=worker_init, initargs=(client,)) as executor:
|
||||
# Submit all tasks to the executor
|
||||
future_to_project = {executor.submit(process_project, indx, total, project, client): project for indx, project in enumerate(projects)}
|
||||
|
||||
# Collect results as they complete
|
||||
results = []
|
||||
for future in concurrent.futures.as_completed(future_to_project):
|
||||
try:
|
||||
result = future.result()
|
||||
results.append(result)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Processing failed: {e}")
|
||||
# Add empty dict or handle error appropriately
|
||||
results.append({})
|
||||
|
||||
return results
|
||||
|
||||
def main():
|
||||
"""Main function to fetch and sync projects"""
|
||||
print("Starting project sync...")
|
||||
try:
|
||||
# Fetch all projects and store them in Firestore
|
||||
projects = fetch_all_projects()
|
||||
print(f"Successfully synced {len(projects)} projects to Firestore")
|
||||
# Initialize Filevine client
|
||||
client = FilevineClient()
|
||||
bearer = client.get_bearer_token()
|
||||
|
||||
# List projects (all pages)
|
||||
projects = client.list_all_projects()
|
||||
projects = projects[:20]
|
||||
|
||||
# Process projects in parallel
|
||||
detailed_rows = process_projects_parallel(projects, client, 9)
|
||||
|
||||
|
||||
print(f"Successfully synced {len(detailed_rows)} projects to Firestore")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during sync: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
226
worker_pool.py
226
worker_pool.py
@@ -1,226 +0,0 @@
|
||||
import concurrent.futures
|
||||
import threading
|
||||
from typing import List, Any, Callable, Tuple
|
||||
import time
|
||||
|
||||
# Global thread-local storage for FilevineClient to avoid passing it around
|
||||
_thread_local = threading.local()
|
||||
|
||||
def get_filevine_client():
|
||||
"""Get FilevineClient from thread local storage"""
|
||||
return getattr(_thread_local, 'client', None)
|
||||
|
||||
def set_filevine_client(client):
|
||||
"""Set FilevineClient in thread local storage"""
|
||||
_thread_local.client = client
|
||||
|
||||
def worker_init(client: 'FilevineClient'):
|
||||
"""Initialize worker with FilevineClient"""
|
||||
set_filevine_client(client)
|
||||
|
||||
def process_project(index: int, total: int, project_data: dict, client: 'FilevineClient') -> dict:
|
||||
"""
|
||||
Process a single project with all its API calls.
|
||||
This is the function that will be executed by workers in parallel.
|
||||
"""
|
||||
# Set the FilevineClient for this thread
|
||||
set_filevine_client(client)
|
||||
|
||||
from app import convert_to_pacific_time
|
||||
|
||||
p = project_data
|
||||
pid = (p.get("projectId") or {}).get("native")
|
||||
print(f"Working on {pid} ({index}/{total})")
|
||||
client = get_filevine_client()
|
||||
c = client.fetch_client((p.get("clientId") or {}).get("native"))
|
||||
cs = client.fetch_contacts(pid)
|
||||
|
||||
if pid is None:
|
||||
return {}
|
||||
|
||||
try:
|
||||
detail = client.fetch_project_detail(pid)
|
||||
except Exception as e:
|
||||
print(f"[WARN] detail fetch failed for {pid}: {e}")
|
||||
detail = {}
|
||||
|
||||
defendant_one = next((c.get('orgContact', {}) for c in cs if "Defendant" in c.get('orgContact', {}).get('personTypes', [])), {})
|
||||
|
||||
new_file_review = client.fetch_form(pid, "newFileReview") or {}
|
||||
dates_and_deadlines = client.fetch_form(pid, "datesAndDeadlines") or {}
|
||||
service_info = client.fetch_collection(pid, "serviceInfo") or []
|
||||
property_info = client.fetch_form(pid, "propertyInfo")
|
||||
matter_overview = client.fetch_form(pid, "matterOverview")
|
||||
fees_and_costs = client.fetch_form(pid, "feesAndCosts") or {}
|
||||
property_contacts = client.fetch_form(pid, "propertyContacts") or {}
|
||||
lease_info_np = client.fetch_form(pid, "leaseInfoNP") or {}
|
||||
|
||||
completed_tasks = [{"description": x.get("body"),
|
||||
"completed": convert_to_pacific_time(x.get("completedDate"))}
|
||||
for x in client.fetch_project_tasks(pid).get("items")
|
||||
if x.get("isCompleted")]
|
||||
pending_tasks = [{"description": x.get("body"),
|
||||
"completed": convert_to_pacific_time(x.get("completedDate"))}
|
||||
for x in client.fetch_project_tasks(pid).get("items")
|
||||
if not x.get("isCompleted")]
|
||||
|
||||
team = client.fetch_project_team(pid)
|
||||
assigned_attorney = next((m.get('fullname')
|
||||
for m in team
|
||||
if ('Assigned Attorney' in [r.get('name') for r in m.get('teamOrgRoles')])
|
||||
), '')
|
||||
primary_contact = next((m.get('fullname')
|
||||
for m in team
|
||||
if ('Primary' in [r.get('name') for r in m.get('teamOrgRoles')])
|
||||
), '')
|
||||
secondary_paralegal = next((m.get('fullname')
|
||||
for m in team
|
||||
if ('Secondary Paralegal' in [r.get('name') for r in m.get('teamOrgRoles')])
|
||||
), '')
|
||||
|
||||
# Extract notice service and expiration dates
|
||||
notice_service_date = convert_to_pacific_time(new_file_review.get("noticeServiceDate")) or ''
|
||||
notice_expiration_date = convert_to_pacific_time(new_file_review.get("noticeExpirationDate")) or ''
|
||||
|
||||
# Extract daily rent damages
|
||||
daily_rent_damages = lease_info_np.get("dailyRentDamages") or dates_and_deadlines.get("dailyRentDamages") or ''
|
||||
|
||||
# Extract default date
|
||||
default_date = convert_to_pacific_time(dates_and_deadlines.get("defaultDate")) or ''
|
||||
case_filed_date = convert_to_pacific_time(dates_and_deadlines.get("dateCaseFiled")) or ''
|
||||
|
||||
# Extract motion hearing dates
|
||||
demurrer_hearing_date = convert_to_pacific_time(dates_and_deadlines.get("demurrerHearingDate")) or ''
|
||||
motion_to_strike_hearing_date = convert_to_pacific_time(dates_and_deadlines.get("mTSHearingDate")) or ''
|
||||
motion_to_quash_hearing_date = convert_to_pacific_time(dates_and_deadlines.get("mTQHearingDate")) or ''
|
||||
other_motion_hearing_date = convert_to_pacific_time(dates_and_deadlines.get("otherMotion1HearingDate")) or ''
|
||||
|
||||
# Extract MSC details
|
||||
msc_date = convert_to_pacific_time(dates_and_deadlines.get("mSCDate")) or ''
|
||||
msc_time = dates_and_deadlines.get("mSCTime") or '' # Time field, not converting
|
||||
msc_address = dates_and_deadlines.get("mSCAddress") or ''
|
||||
msc_div_dept_room = dates_and_deadlines.get("mSCDeptDiv") or ''
|
||||
|
||||
# Extract trial details
|
||||
trial_date = convert_to_pacific_time(dates_and_deadlines.get("trialDate")) or ''
|
||||
trial_time = dates_and_deadlines.get("trialTime") or '' # Time field, not converting
|
||||
trial_address = dates_and_deadlines.get("trialAddress") or ''
|
||||
trial_div_dept_room = dates_and_deadlines.get("trialDeptDivRoom") or ''
|
||||
|
||||
# Extract final result of trial/MSC
|
||||
final_result = dates_and_deadlines.get("finalResultOfTrialMSCCa") or ''
|
||||
|
||||
# Extract settlement details
|
||||
date_of_settlement = convert_to_pacific_time(dates_and_deadlines.get("dateOfStipulation")) or ''
|
||||
final_obligation = dates_and_deadlines.get("finalObligationUnderTheStip") or ''
|
||||
def_comply_stip = dates_and_deadlines.get("defendantsComplyWithStip") or ''
|
||||
|
||||
# Extract judgment and writ details
|
||||
judgment_date = convert_to_pacific_time(dates_and_deadlines.get("dateOfJudgment")) or ''
|
||||
writ_issued_date = convert_to_pacific_time(dates_and_deadlines.get("writIssuedDate")) or ''
|
||||
|
||||
# Extract lockout and stay details
|
||||
scheduled_lockout = convert_to_pacific_time(dates_and_deadlines.get("sheriffScheduledDate")) or ''
|
||||
oppose_stays = dates_and_deadlines.get("opposeStays") or ''
|
||||
|
||||
# Extract premises safety and entry code
|
||||
premises_safety = new_file_review.get("lockoutSafetyIssuesOrSpecialCareIssues") or ''
|
||||
matter_gate_code = property_info.get("propertyEntryCodeOrInstructions") or ''
|
||||
|
||||
# Extract possession recovered date
|
||||
date_possession_recovered = convert_to_pacific_time(dates_and_deadlines.get("datePossessionRecovered")) or ''
|
||||
|
||||
# Extract attorney fees and costs
|
||||
attorney_fees = fees_and_costs.get("totalAttorneysFees") or ''
|
||||
costs = fees_and_costs.get("totalCosts") or ''
|
||||
|
||||
row = {
|
||||
"client": c.get("firstName"),
|
||||
"matter_description": p.get("projectName"),
|
||||
"defendant_1": defendant_one.get('fullName', 'Unknown'),
|
||||
"matter_open": convert_to_pacific_time(dates_and_deadlines.get("dateCaseFiled") or p.get("createdDate")),
|
||||
"notice_type": new_file_review.get("noticeType", '') or '',
|
||||
"case_number": dates_and_deadlines.get('caseNumber', '') or '',
|
||||
"premises_address": property_info.get("premisesAddressWithUnit") or '',
|
||||
"premises_city": property_info.get("premisesCity") or '',
|
||||
"responsible_attorney": assigned_attorney,
|
||||
"staff_person": primary_contact,
|
||||
"staff_person_2": secondary_paralegal,
|
||||
"phase_name": p.get("phaseName"),
|
||||
"completed_tasks": completed_tasks,
|
||||
"pending_tasks": pending_tasks,
|
||||
"notice_service_date": notice_service_date,
|
||||
"notice_expiration_date": notice_expiration_date,
|
||||
"case_field_date": case_filed_date,
|
||||
"daily_rent_damages": daily_rent_damages,
|
||||
"default_date": default_date,
|
||||
"demurrer_hearing_date": demurrer_hearing_date,
|
||||
"motion_to_strike_hearing_date": motion_to_strike_hearing_date,
|
||||
"motion_to_quash_hearing_date": motion_to_quash_hearing_date,
|
||||
"other_motion_hearing_date": other_motion_hearing_date,
|
||||
"msc_date": msc_date,
|
||||
"msc_time": msc_time,
|
||||
"msc_address": msc_address,
|
||||
"msc_div_dept_room": msc_div_dept_room,
|
||||
"trial_date": trial_date,
|
||||
"trial_time": trial_time,
|
||||
"trial_address": trial_address,
|
||||
"trial_div_dept_room": trial_div_dept_room,
|
||||
"final_result": final_result,
|
||||
"date_of_settlement": date_of_settlement,
|
||||
"final_obligation": final_obligation,
|
||||
"def_comply_stip": def_comply_stip,
|
||||
"judgment_date": judgment_date,
|
||||
"writ_issued_date": writ_issued_date,
|
||||
"scheduled_lockout": scheduled_lockout,
|
||||
"oppose_stays": oppose_stays,
|
||||
"premises_safety": premises_safety,
|
||||
"matter_gate_code": matter_gate_code,
|
||||
"date_possession_recovered": date_possession_recovered,
|
||||
"attorney_fees": attorney_fees,
|
||||
"costs": costs,
|
||||
"documents_url": matter_overview.get('documentShareFolderURL') or '',
|
||||
"service_attempt_date_1": convert_to_pacific_time(next(iter(service_info), {}).get('serviceDate')),
|
||||
"contacts": cs,
|
||||
"ProjectEmailAddress": p.get("projectEmailAddress"),
|
||||
"Number": p.get("number"),
|
||||
"IncidentDate": convert_to_pacific_time(p.get("incidentDate") or detail.get("incidentDate")),
|
||||
"ProjectId": pid,
|
||||
"ProjectName": p.get("projectName") or detail.get("projectName"),
|
||||
"ProjectUrl": p.get("projectUrl") or detail.get("projectUrl"),
|
||||
"property_contacts": property_contacts
|
||||
}
|
||||
print(f"Finished on {pid} ({index}/{total})")
|
||||
|
||||
return row
|
||||
|
||||
def process_projects_parallel(projects: List[dict], client: 'FilevineClient', max_workers: int = 9) -> List[dict]:
|
||||
"""
|
||||
Process projects in parallel using a worker pool.
|
||||
|
||||
Args:
|
||||
projects: List of project data dictionaries
|
||||
client: FilevineClient instance
|
||||
max_workers: Number of concurrent workers (default 20)
|
||||
|
||||
Returns:
|
||||
List of processed project dictionaries
|
||||
"""
|
||||
# Create a thread pool with specified number of workers
|
||||
total = len(projects)
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers, initializer=worker_init, initargs=(client,)) as executor:
|
||||
# Submit all tasks to the executor
|
||||
future_to_project = {executor.submit(process_project, indx, total, project, client): project for indx, project in enumerate(projects)}
|
||||
|
||||
# Collect results as they complete
|
||||
results = []
|
||||
for future in concurrent.futures.as_completed(future_to_project):
|
||||
try:
|
||||
result = future.result()
|
||||
results.append(result)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Processing failed: {e}")
|
||||
# Add empty dict or handle error appropriately
|
||||
results.append({})
|
||||
|
||||
return results
|
||||
Reference in New Issue
Block a user