Skip to content

Commit 9eb0438

Browse files
authored
Merge branch 'main' into issue-163/re-implement-feedback-feature
2 parents 3b6ff22 + 367110c commit 9eb0438

25 files changed

+474
-390
lines changed
Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
name: CI-CD
1+
name: CI-CD (Production)
22

3-
on:
3+
on:
44
push:
55
branches: [ main ]
66
workflow_dispatch:
@@ -11,15 +11,9 @@ concurrency:
1111

1212
jobs:
1313
deploy:
14-
runs-on: ubuntu-latest
14+
environment: production
1515

16-
env:
17-
# local paths
18-
BACKEND_DIR: backend
19-
FRONTEND_DIR: frontend
20-
# remote paths
21-
REMOTE_APP_DIR: /var/www/tenantfirstaid
22-
SERVICE_NAME: tenantfirstaid-backend
16+
runs-on: ubuntu-latest
2317

2418
steps:
2519
- uses: actions/checkout@v4
@@ -29,43 +23,43 @@ jobs:
2923
with:
3024
node-version: 20
3125
cache: npm
32-
cache-dependency-path: ${{ env.FRONTEND_DIR }}/package-lock.json
26+
cache-dependency-path: ${{ vars.FRONTEND_DIR }}/package-lock.json
3327

3428
- name: Build UI
35-
working-directory: ${{ env.FRONTEND_DIR }}
29+
working-directory: ${{ vars.FRONTEND_DIR }}
3630
run: |
3731
npm ci
3832
npm run build
3933
4034
- name: Upload backend code via SCP
4135
uses: appleboy/[email protected]
4236
with:
43-
host: ${{ secrets.DO_HOST }}
44-
username: ${{ secrets.DO_USER }}
37+
host: ${{ vars.URL }}
38+
username: ${{ vars.SSH_USER }}
4539
key: ${{ secrets.SSH_KEY }}
46-
source: ${{ env.BACKEND_DIR }}/
47-
target: ${{ env.REMOTE_APP_DIR }}
40+
source: ${{ vars.BACKEND_DIR }}/
41+
target: ${{ vars.REMOTE_APP_DIR }}
4842
rm: true
4943

5044
- name: Upload frontend code via SCP
5145
uses: appleboy/[email protected]
5246
with:
53-
host: ${{ secrets.DO_HOST }}
54-
username: ${{ secrets.DO_USER }}
47+
host: ${{ vars.URL }}
48+
username: ${{ vars.SSH_USER }}
5549
key: ${{ secrets.SSH_KEY }}
56-
source: ${{ env.FRONTEND_DIR }}/dist
57-
target: ${{ env.REMOTE_APP_DIR }}
50+
source: ${{ vars.FRONTEND_DIR }}/dist
51+
target: ${{ vars.REMOTE_APP_DIR }}
5852
rm: false # Otherwise we wipe out the backend code
5953

6054
- name: Bootstrap on droplet
6155
uses: appleboy/[email protected]
6256
with:
63-
host: ${{ secrets.DO_HOST }}
64-
username: ${{ secrets.DO_USER }}
57+
host: ${{ vars.URL }}
58+
username: ${{ vars.SSH_USER }}
6559
key: ${{ secrets.SSH_KEY }}
6660
script: |
6761
set -e
68-
cd ${{ env.REMOTE_APP_DIR }}/backend/
62+
cd ${{ vars.REMOTE_APP_DIR }}/backend/
6963
7064
# Install uv (fast installer from Astral) if it isn't there
7165
if ! command -v uv >/dev/null 2>&1; then
@@ -81,19 +75,23 @@ jobs:
8175
sudo chmod 750 /etc/tenantfirstaid
8276
sudo chown root:root /etc/tenantfirstaid
8377
cat > /etc/tenantfirstaid/env <<EOF
84-
ENV=prod
85-
OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
78+
ENV=${{ vars.ENV }}
8679
FLASK_SECRET_KEY=${{ secrets.FLASK_SECRET_KEY }}
8780
DB_HOST=${{secrets.DB_HOST}}
8881
DB_PASSWORD=${{secrets.DB_PASSWORD}}
89-
DB_PORT=${{secrets.DB_PORT}}
82+
DB_PORT=${{vars.DB_PORT}}
9083
DB_USER=default
91-
MODEL_REASONING_EFFORT=high
92-
VECTOR_STORE_ID=${{secrets.VECTOR_STORE_ID}}
84+
GEMINI_RAG_CORPUS=${{vars.GEMINI_RAG_CORPUS}}
85+
GOOGLE_SERVICE_ACCOUNT_CREDENTIALS_FILE=/etc/tenantfirstaid/google-service-account.json
9386
EOF
9487
chmod 640 /etc/tenantfirstaid/env
88+
89+
cat > /etc/tenantfirstaid/google-service-account.json <<EOF
90+
${{ secrets.GOOGLE_SERVICE_ACCOUNT_CREDENTIALS}}
91+
EOF
92+
chmod 640 /etc/tenantfirstaid/google-service-account.json
9593
9694
# Ownership, restart, reload
97-
sudo chown -R $USER:www-data ${{ env.REMOTE_APP_DIR }}
98-
sudo systemctl restart ${{ env.SERVICE_NAME }}
95+
sudo chown -R $USER:www-data ${{ vars.REMOTE_APP_DIR }}
96+
sudo systemctl restart ${{ vars.SERVICE_NAME }}
9997
sudo systemctl reload nginx

.github/workflows/deploy.staging.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: CI-CD (Staging)
2+
3+
on:
4+
workflow_dispatch:
5+
6+
concurrency:
7+
group: deploy-to-droplet
8+
cancel-in-progress: true
9+
10+
jobs:
11+
deploy:
12+
environment: staging
13+
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Node
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version: 20
23+
cache: npm
24+
cache-dependency-path: ${{ vars.FRONTEND_DIR }}/package-lock.json
25+
26+
- name: Build UI
27+
working-directory: ${{ vars.FRONTEND_DIR }}
28+
run: |
29+
npm ci
30+
npm run build
31+
32+
- name: Upload backend code via SCP
33+
uses: appleboy/[email protected]
34+
with:
35+
host: ${{ vars.URL }}
36+
username: ${{ secrets.SSH_USER }}
37+
key: ${{ secrets.SSH_KEY }}
38+
source: ${{ vars.BACKEND_DIR }}/
39+
target: ${{ vars.REMOTE_APP_DIR }}
40+
rm: true
41+
42+
- name: Upload frontend code via SCP
43+
uses: appleboy/[email protected]
44+
with:
45+
host: ${{ vars.URL }}
46+
username: ${{ secrets.SSH_USER }}
47+
key: ${{ secrets.SSH_KEY }}
48+
source: ${{ vars.FRONTEND_DIR }}/dist
49+
target: ${{ vars.REMOTE_APP_DIR }}
50+
rm: false # Otherwise we wipe out the backend code
51+
52+
- name: Bootstrap on droplet
53+
uses: appleboy/[email protected]
54+
with:
55+
host: ${{ vars.URL }}
56+
username: ${{ secrets.SSH_USER }}
57+
key: ${{ secrets.SSH_KEY }}
58+
script: |
59+
set -e
60+
cd ${{ vars.REMOTE_APP_DIR }}/backend/
61+
62+
# Install uv (fast installer from Astral) if it isn't there
63+
if ! command -v uv >/dev/null 2>&1; then
64+
curl -LsSf https://astral.sh/uv/install.sh | sh
65+
export PATH="$HOME/.local/bin:$PATH"
66+
fi
67+
68+
# Sync dependencies directly from pyproject.toml
69+
uv sync
70+
71+
# Inject environment secrets
72+
sudo mkdir -p /etc/tenantfirstaid
73+
sudo chmod 750 /etc/tenantfirstaid
74+
sudo chown root:root /etc/tenantfirstaid
75+
cat > /etc/tenantfirstaid/env <<EOF
76+
ENV=${{ vars.ENV }}
77+
FLASK_SECRET_KEY=${{ secrets.FLASK_SECRET_KEY }}
78+
DB_HOST=${{secrets.DB_HOST}}
79+
DB_PASSWORD=${{secrets.DB_PASSWORD}}
80+
DB_PORT=${{vars.DB_PORT}}
81+
DB_USER=default
82+
GEMINI_RAG_CORPUS=${{vars.GEMINI_RAG_CORPUS}}
83+
GOOGLE_SERVICE_ACCOUNT_CREDENTIALS_FILE=/etc/tenantfirstaid/google-service-account.json
84+
EOF
85+
chmod 640 /etc/tenantfirstaid/env
86+
87+
cat > /etc/tenantfirstaid/google-service-account.json <<EOF
88+
${{ secrets.GOOGLE_SERVICE_ACCOUNT_CREDENTIALS}}
89+
EOF
90+
chmod 640 /etc/tenantfirstaid/google-service-account.json
91+
92+
# Ownership, restart, reload
93+
sudo chown -R $USER:www-data ${{ vars.REMOTE_APP_DIR }}
94+
sudo systemctl restart ${{ vars.SERVICE_NAME }}
95+
sudo systemctl reload nginx

.github/workflows/generate_conversations.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ on:
2525
model:
2626
description: 'Model to use'
2727
required: true
28-
default: 'o3'
28+
default: 'gpt-2.5-flash'
2929
type: string
3030

3131
jobs:

.github/workflows/pr-check.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ jobs:
4343
run: uv run ty check
4444

4545
- name: Run tests
46-
env:
47-
OPENAI_API_KEY: XXXX
48-
PERSISTENT_STORAGE_DIR: /tmp/tenantfirstaid_data
4946
run: uv run pytest -v -s
5047

5148
frontend-build:

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ venv/
1313
# Env config
1414
.env
1515
*.env
16+
google-service-account.json
17+
.python-version
1618

1719
# Python tooling
1820
pip-log.txt
@@ -57,5 +59,6 @@ chatlog.jsonl
5759
/backend/feedback.jsonl
5860
/backend/data
5961
/backend/scripts/eval_results.json
60-
combined_training_gpt-4_1.jsonl
61-
combined_training_o3.jsonl
62+
/backend/scripts/generate_conversation/*.csv
63+
!/backend/scripts/generate_conversation/tenant_questions_facts_full.csv
64+
combined_training_gpt-4_1.jsonl

backend/.env.example

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
1-
OPENAI_API_KEY=openai_api_key
2-
# If you want to use Github Models instead, omit OPENAI_API_KEY and use
3-
# GITHUB_API_KEY and MODEL_ENDPOINT
4-
GITHUB_API_KEY=github_api_key
5-
MODEL_ENDPOINT=https://api.openai.com/v1
6-
71
# Specify a different model
8-
MODEL_NAME=o3
9-
10-
# Change the persistent storage directory from '/root/tenantfirstaid'
11-
PERSISTENT_STORAGE_DIR=../storage
2+
MODEL_NAME=gpt-2.5-flash
123

134
# Vector store ID for OpenAI (use the create_vector_store script to create one)
145
VECTOR_STORE_ID=my_vector_store_id

backend/Makefile

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,16 @@ lint: uv.lock
2020
typecheck: uv.lock
2121
$(PYTHON) run ty check $(TYPECHECK_OPTIONS)
2222

23-
typecheck-mypy: uv.lock
24-
$(PYTHON) run mypy $(TYPECHECK_OPTIONS) -p tenantfirstaid --python-executable .venv/bin/python3 --check-untyped-defs
23+
MYPY_CMD := $(PYTHON) run mypy $(TYPECHECK_OPTIONS)
24+
MYPY_ARGS := --python-executable .venv/bin/python3 --pretty --check-untyped-defs
25+
26+
typecheck-mypy-scripts: uv.lock
27+
$(MYPY_CMD) -p scripts $(MYPY_ARGS)
28+
29+
typecheck-mypy-tenantfirstaid: uv.lock
30+
$(MYPY_CMD) -p tenantfirstaid $(MYPY_ARGS)
31+
32+
typecheck-mypy: typecheck-mypy-tenantfirstaid typecheck-mypy-scripts
2533

2634
typecheck-pyrefly: uv.lock
2735
$(PYTHON) run pyrefly check $(TYPECHECK_OPTIONS) --python-interpreter .venv/bin/python3

backend/scripts/create_vector_store.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
from pathlib import Path
33
from openai import OpenAI
44

5-
6-
if Path(".env").exists():
5+
dotenv_path = Path(__file__).parent.parent / ".env"
6+
if dotenv_path.exists():
77
from dotenv import load_dotenv
88

9-
load_dotenv(override=True)
9+
load_dotenv(dotenv_path=dotenv_path, override=True)
1010

1111
API_KEY = os.getenv("OPENAI_API_KEY", os.getenv("GITHUB_API_KEY"))
1212

@@ -64,11 +64,11 @@
6464
path.write_text(path.read_text(encoding="utf-8"), encoding="utf-8")
6565

6666
print(f"Uploading {file_path} to vector store '{vector_store.name}'.")
67-
file = client.files.create(
67+
file_obj = client.files.create(
6868
file=open(file_path, "rb"),
6969
purpose="assistants",
7070
)
71-
file_ids.append(file.id)
71+
file_ids.append(file_obj.id)
7272

7373
# Add files to the vector store
7474
batch_upload = client.vector_stores.file_batches.create(
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ORS 109.697
2+
The Legislative Assembly finds that there are in the State of Oregon unemancipated minors who are living apart from their parents and are homeless. Many of these minors are able financially to provide housing and utility services for themselves and their children, but cannot contract for these necessities due to perceived legal limitations affecting contracts with minors. The purpose of this legislation is to address those limitations.
3+
(2)For purposes of this section, “minor” means an unemancipated and unmarried person who is living apart from the person’s parent, parents or legal guardian, and who is either:
4+
(a)Sixteen or 17 years of age;
5+
(b)Under 16 years of age and the parent of a child or children who are living in the physical custody of the person; or
6+
(c)Under 16 years of age, pregnant and expecting the birth of a child who will be living in the physical custody of the person.
7+
Notwithstanding any other provision of law, a minor may contract for the necessities of a residential dwelling unit and for utility services to that unit. Such a contract is binding upon the minor and cannot be voided or disaffirmed by the minor based upon the minor’s age or status as a minor.
8+
(4)The consent of the parent or legal guardian of such minor shall not be necessary to contract for a residential dwelling unit or utility services to that unit. The parent or legal guardian of such minor shall not be liable under a contract by that minor for a residential dwelling unit or for utility services to that unit unless the parent or guardian is a party to the minor’s contract, or enters another contract, for the purpose of acting as guarantor of the minor’s debt. [1993 c.369 §29]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
ORS 659A.421
2+
Discrimination in selling, renting or leasing real property prohibited
3+
Text
4+
Annotations 6
5+
(1)As used in this section:
6+
(a)“Dwelling” means:
7+
(A)A building or structure, or portion of a building or structure, that is occupied, or designed or intended for occupancy, as a residence by one or more families; or
8+
(B)Vacant land offered for sale or lease for the construction or location of a building or structure, or portion of a building or structure, that is occupied, or designed or intended for occupancy, as a residence by one or more families.
9+
(b)“Purchaser” includes an occupant, prospective occupant, renter, prospective renter, lessee, prospective lessee, buyer or prospective buyer.
10+
(c)“Real property” includes a dwelling.
11+
(d)Intentionally left blank —Ed.
12+
(A)“Source of income” includes federal rent subsidy payments under 42 U.S.C. 1437f and any other local, state or federal housing assistance.
13+
(B)“Source of income” does not include income derived from a specific occupation or income derived in an illegal manner.
14+
(2)A person may not, because of the race, color, religion, sex, sexual orientation, gender identity, national origin, marital status, familial status or source of income of any person:
15+
(a)Refuse to sell, lease or rent any real property to a purchaser. This paragraph does not prevent a person from refusing to lease or rent real property to a prospective renter or prospective lessee:
16+
(A)Based upon the past conduct of a prospective renter or prospective lessee provided the refusal to lease or rent based on past conduct is consistent with local, state and federal law, including but not limited to fair housing laws; or
17+
(B)Based upon the prospective renter’s or prospective lessee’s inability to pay rent, taking into account the value of the prospective renter’s or prospective lessee’s local, state and federal housing assistance, provided the refusal to lease or rent based on inability to pay rent is consistent with local, state and federal law, including but not limited to fair housing laws.
18+
(b)Expel a purchaser from any real property.
19+
(c)Make any distinction, discrimination or restriction against a purchaser in the price, terms, conditions or privileges relating to the sale, rental, lease or occupancy of real property or in the furnishing of any facilities or services in connection therewith.
20+
(d)Attempt to discourage the sale, rental or lease of any real property to a purchaser.
21+
(e)Publish, circulate, issue or display, or cause to be published, circulated, issued or displayed, any communication, notice, advertisement or sign of any kind relating to the sale, rental or leasing of real property that indicates any preference, limitation, specification or unlawful discrimination based on race, color, religion, sex, sexual orientation, gender identity, national origin, marital status, familial status or source of income.
22+
(f)Assist, induce, incite or coerce another person to commit an act or engage in a practice that violates this section.
23+
(g)Coerce, intimidate, threaten or interfere with any person in the exercise or enjoyment of, or on account of the person having exercised or enjoyed or having aided or encouraged any other person in the exercise or enjoyment of, any right granted or protected by this section.
24+
(h)Deny access to, or membership or participation in, any multiple listing service, real estate brokers’ organization or other service, organization or facility relating to the business of selling or renting dwellings, or discriminate against any person in the terms or conditions of the access, membership or participation.
25+
(i)Represent to a person that a dwelling is not available for inspection, sale or rental when the dwelling in fact is available for inspection, sale or rental.
26+
(j)Otherwise make unavailable or deny a dwelling to a person.
27+
(3)Intentionally left blank —Ed.
28+
(a)A person whose business includes engaging in residential real estate related transactions may not discriminate against any person in making a transaction available, or in the terms or conditions of the transaction, because of race, color, religion, sex, sexual orientation, gender identity, national origin, marital status, familial status or source of income.
29+
(b)As used in this subsection, “residential real estate related transaction” means any of the following:
30+
(A)The making or purchasing of loans or providing other financial assistance:
31+
(i)For purchasing, constructing, improving, repairing or maintaining a dwelling; or
32+
(ii)Secured by residential real estate; or
33+
(B)The selling, brokering or appraising of residential real property.
34+
(4)A real estate licensee may not accept or retain a listing of real property for sale, lease or rental with an understanding that a purchaser may be discriminated against with respect to the sale, rental or lease thereof because of race, color, religion, sex, sexual orientation, gender identity, national origin, marital status, familial status or source of income.
35+
(5)A person may not, for profit, induce or attempt to induce any other person to sell or rent any dwelling by representations regarding the entry or prospective entry into the neighborhood of a person or persons of a particular race, color, religion, sex, sexual orientation, gender identity, national origin, marital status, familial status or source of income.
36+
(6)This section does not apply with respect to sex distinction, discrimination or restriction if the real property involved is such that the application of this section would necessarily result in common use of bath or bedroom facilities by unrelated persons of opposite sex.
37+
(7)Intentionally left blank —Ed.
38+
(a)This section does not apply to familial status distinction, discrimination or restriction with respect to housing for older persons.
39+
(b)As used in this subsection, “housing for older persons” means housing:
40+
(A)Provided under any state or federal program that is specifically designed and operated to assist elderly persons, as defined by the state or federal program;
41+
(B)Intended for, and solely occupied by, persons 62 years of age or older; or
42+
(C)Intended and operated for occupancy by at least one person 55 years of age or older per unit. Housing qualifies as housing for older persons under this subparagraph if:
43+
(i)At least 80 percent of the dwellings are occupied by at least one person 55 years of age or older per unit; and
44+
(ii)Policies and procedures that demonstrate an intent by the owner or manager to provide housing for persons 55 years of age or older are published and adhered to.
45+
(c)Housing does not fail to meet the requirements for housing for older persons if:
46+
(A)Persons residing in the housing as of September 13, 1988, do not meet the requirements of paragraph (b)(B) or (C) of this subsection. However, new occupants of the housing shall meet the age requirements of paragraph (b)(B) or (C) of this subsection; or
47+
(B)The housing includes unoccupied units that are reserved for occupancy by persons who meet the age requirements of paragraph (b)(B) or (C) of this subsection.
48+
(d)Nothing in this section limits the applicability of any reasonable local, state or federal restrictions regarding the maximum number of occupants permitted to occupy a dwelling.
49+
(8)The provisions of subsection (2)(a) to (d) and (f) of this section that prohibit actions based upon sex, sexual orientation, gender identity or familial status do not apply to the renting of space within a single-family residence if the owner actually maintains and occupies the residence as the owner’s primary residence and all occupants share some common space within the residence.
50+
(9)Any violation of this section is an unlawful practice. [Formerly 659.033; 2007 c.100 §8; 2007 c.903 §4a; 2008 c.36 §6; 2013 c.740 §1; 2021 c.367 §40]

0 commit comments

Comments
 (0)