chromaDB + streamlit + elyza:jp8b

import streamlit as st
from openai import OpenAI
import chromadb
from docx import Document
import requests


# ChromaDBの設定
DB_DIR = "./chroma_db"
chroma_client = chromadb.PersistentClient(path=DB_DIR)

if "collection" not in st.session_state:
    st.session_state.collection = chroma_client.get_or_create_collection(
        name="local_docs"
    )

# Ollamaからインストールしたモデルを使ったベクトル化関数
def ollama_embed(text):
    r = requests.post(
        "http://localhost:12000/api/embeddings",
        json={"model": "nomic-embed-text", "prompt": text}
    )
    data = r.json()
    return data["embedding"]

# Wordファイルを読み込む関数
def load_word_document(file):
    return "\n".join(para.text for para in Document(file).paragraphs)

# テキスト分割関数
def split_text(text):
    chunk_size = 200
    overlap = 50
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start += chunk_size - overlap
    return chunks

## サイドバーの設定 ##
st.set_page_config(page_title="Local LLM Chat")

st.sidebar.title("設定")
model = st.sidebar.text_input("モデル名", value="llama3.1:8b")
temperature = st.sidebar.slider("temperature", 0.0, 2.0, 0.3, 0.1)
system_prompt = st.sidebar.text_area(
    "System Prompt",
    "あなたは有能なアシスタントです。日本語で回答してください",
)

# ワードファイルのアップロード
uploaded_files = st.sidebar.file_uploader(
    "Wordファイルをアップロード(.docx)",
    type=["docx"],
    accept_multiple_files=True
)

if st.sidebar.button("インデックス作成"):
    for file in uploaded_files:
        text = load_word_document(file)
        chunks = split_text(text)
        for i, chunk in enumerate(chunks):
            embed = ollama_embed(chunk)
            st.session_state.collection.add(
                documents=[chunk],
                embeddings=,
                ids=[f"{file.name}_{i}"]
            )
    st.sidebar.success("インデックス作成完了")

# タイトル
st.title("Local LLM Chat")

# 会話の履歴を保管
if "messages" not in st.session_state:
    st.session_state.messages = []

# 会話の履歴をリセットするボタン
if st.sidebar.button("会話をリセット"):
    st.session_state.messages = []

# 会話の履歴を表示
for m in st.session_state.messages:
    with st.chat_message(m["role"]):
        st.write(m["content"])


prompt = st.chat_input("メッセージを入力")

client = OpenAI(
    api_key="ollama",   
    base_url="http://localhost:12000/v1"
)

if prompt:

    # ユーザーのプロンプトを表示
    with st.chat_message("user"):
        st.write(prompt)

    # RAG検索
    query_embed = ollama_embed(prompt)
    results = st.session_state.collection.query(
        query_embeddings=[query_embed],
        n_results=2
    )

    # {
    #     "ids":
    #     "documents": [["doc1", "doc2"]]
    #     "distances": [[XXX, XXX]]
    # }

    if results["documents"]:
        context_text = "\n".join(results["documents"][0])
        rag_prompt = f"""
        以下は関連ドキュメントの抜粋です。
        {context_text}
        この情報を参考に以下の質問に答えてください。
        {prompt}
        """
        final_user_prompt = rag_prompt
    else:
        final_user_prompt = prompt

    st.session_state.messages.append({"role": "user", "content": final_user_prompt})

    if system_prompt.strip():
        messages = [{"role": "system", "content": system_prompt}] + st.session_state.messages
    else:
        messages = st.session_state.messages
    
    # LLMの返答を表示
    with st.chat_message("assistant"):
        placeholder = st.empty()
        stream_response = ""
        stream = client.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature,
            stream=True
        )
        for chunk in stream:
            stream_response += chunk.choices[0].delta.content
            placeholder.write(stream_response)

    # 会話の履歴を保存
    
    st.session_state.messages.append({"role": "assistant", "content": stream_response})

のコードを
OpenAI のAPIではなく
Ollama のelyza:jp8bを使用するようにコードを変更

openai の依存を削除し、/api/chat(ストリーミング)を requests で直接呼び出し。
モデル既定値を elyza:jp8b に変更。
Ollama のベースURL をサイドバーで指定可能(既定: http://localhost:11434)。埋め込みも同じURLを使用。
温度などは options 経由で送信。
ストリーミングは1行ごとの JSON を読み、message.content を随時描画。

import streamlit as st
import chromadb
from docx import Document
import requests
import json

# -----------------------------
# 共通設定
# -----------------------------
st.set_page_config(page_title="Local LLM Chat")

st.sidebar.title("設定")
ollama_base_url = st.sidebar.text_input("Ollama ベースURL", value="http://localhost:11434")
model = st.sidebar.text_input("モデル名", value="elyza:jp8b")  # 既定を elyza:jp8b に
temperature = st.sidebar.slider("temperature", 0.0, 2.0, 0.3, 0.1)
system_prompt = st.sidebar.text_area(
    "System Prompt",
    "あなたは有能なアシスタントです。日本語で回答してください",
)

# -----------------------------
# ChromaDB の設定
# -----------------------------
DB_DIR = "./chroma_db"
chroma_client = chromadb.PersistentClient(path=DB_DIR)

if "collection" not in st.session_state:
    st.session_state.collection = chroma_client.get_or_create_collection(
        name="local_docs"
    )

# -----------------------------
# Ollama API ユーティリティ
# -----------------------------
def ollama_embed(text: str):
    """Ollama の /api/embeddings を使ってベクトルを取得"""
    url = f"{ollama_base_url}/api/embeddings"
    r = requests.post(
        url,
        json={"model": "nomic-embed-text", "prompt": text},
        timeout=60,
    )
    r.raise_for_status()
    data = r.json()
    return data["embedding"]

def ollama_chat_stream(messages, model_name: str, temperature: float = 0.3):
    """
    Ollama の /api/chat ストリームを逐次 yield するジェネレータ。
    messages: [{"role":"system|user|assistant", "content":"..."}]
    """
    url = f"{ollama_base_url}/api/chat"
    payload = {
        "model": model_name,
        "messages": messages,
        "stream": True,
        "options": {
            "temperature": float(temperature)
        }
    }
    with requests.post(url, json=payload, stream=True) as r:
        r.raise_for_status()
        for line in r.iter_lines(decode_unicode=True):
            if not line:
                continue
            # 各行は JSON。末尾で {"done":true} が来る
            try:
                obj = json.loads(line)
            except json.JSONDecodeError:
                continue
            if obj.get("done"):
                break
            msg = obj.get("message", {})
            delta = msg.get("content", "")
            if delta:
                yield delta

# -----------------------------
# Word 読み込み & チャンク
# -----------------------------
def load_word_document(file):
    return "\n".join(para.text for para in Document(file).paragraphs)

def split_text(text):
    chunk_size = 200
    overlap = 50
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start += chunk_size - overlap
    return chunks

# -----------------------------
# UI: アップロード → インデックス
# -----------------------------
uploaded_files = st.sidebar.file_uploader(
    "Wordファイルをアップロード(.docx)",
    type=["docx"],
    accept_multiple_files=True
)

if st.sidebar.button("インデックス作成"):
    try:
        for file in uploaded_files or []:
            text = load_word_document(file)
            chunks = split_text(text)
            for i, chunk in enumerate(chunks):
                embed = ollama_embed(chunk)
                st.session_state.collection.add(
                    documents=[chunk],
                    embeddings=,
                    ids=[f"{file.name}_{i}"]
                )
        st.sidebar.success("インデックス作成完了")
    except Exception as e:
        st.sidebar.error(f"インデックス作成でエラー: {e}")

# -----------------------------
# 会話領域
# -----------------------------
st.title("Local LLM Chat")

if "messages" not in st.session_state:
    st.session_state.messages = []

# リセットボタン
if st.sidebar.button("会話をリセット"):
    st.session_state.messages = []

# 既存履歴の表示
for m in st.session_state.messages:
    with st.chat_message(m["role"]):
        st.write(m["content"])

# 入力
prompt = st.chat_input("メッセージを入力")

if prompt:
    # ユーザーの入力を表示
    with st.chat_message("user"):
        st.write(prompt)

    # --- RAG: 近傍検索 ---
    try:
        query_embed = ollama_embed(prompt)
        results = st.session_state.collection.query(
            query_embeddings=[query_embed],
            n_results=2
        )
    except Exception as e:
        results = {"documents": []}
        st.warning(f"RAG検索でエラーが発生しました: {e}")

    # ドキュメントの結合
    if results.get("documents"):
        context_text = "\n".join(results["documents"][0])
        rag_prompt = f"""
以下は関連ドキュメントの抜粋です。
{context_text}

この情報を参考に以下の質問に答えてください。
{prompt}
"""
        final_user_prompt = rag_prompt
    else:
        final_user_prompt = prompt

    # セッション履歴にユーザー発話(RAG統合後)を追加
    st.session_state.messages.append({"role": "user", "content": final_user_prompt})

    # Ollama へ投げる message 配列を組み立て
    chat_messages = []
    if system_prompt.strip():
        chat_messages.append({"role": "system", "content": system_prompt})
    # 既存履歴をそのまま渡す(role は "user"/"assistant")
    chat_messages.extend(st.session_state.messages)

    # --- ストリーミング応答 ---
    with st.chat_message("assistant"):
        placeholder = st.empty()
        stream_response = ""
        try:
            for delta in ollama_chat_stream(chat_messages, model, temperature):
                stream_response += delta
                placeholder.write(stream_response)
        except Exception as e:
            st.error(f"Ollama応答でエラーが発生しました: {e}")

    # 履歴へ保存
    st.session_state.messages.append({"role": "assistant", "content": stream_response})


国土数値情報の洪水浸水想定区域データをダウンロード

OGIS用データのダウンロード

国土数値情報の洪水浸水想定区域データをダウンロード

https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-A31.html

一番新しいものは
最新のデータは第4.0版(2022年度)に基づいています。データ詳細はこちら(データ作成年度:2022年度(令和4年度))

https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-A31a-v4_0.html
が最新のダウンロードになり

各都道府県で分かれている

静岡 世界測地系 2021年(令和3年) 141MB A31-21_22_GML.zip
を今回はダウンロード

ダウンロードしたら解凍

この中に
想定最大規模

SHP
を選択すると大量のシェープファイルがある

この中から適当に.shpファイルを
QGISにドラッグ&ドロップしてみる

今回は
A31-20-21_8505030001.shp
をドラッグ&ドロップ

なおレイヤは上から優先して表示しているので
Google map などはこの shpファイルの下になるように配置しないと
地図だけしか表示されないので注意

この地域図の中身を見ると文字化けする時があるので
geoJSONの方で確認する

この時に複数の座標が表示されるが
ファイル名のAから始まる番号は河川名のコードなので
探すとどの川なのかがわかる

また、この中には
https://nlftp.mlit.go.jp/ksj/gml/codelist/water_depth_code.html
にあるような浸水深ランクコードがあり
数が大きいほど危険度が増える

これを使って浸水深ランクにより表示を変えるようにする

これは
プロパティ > シンポロジで
第一定義から
連続値による定義に変更

値を
A31_205 を選ぶ

次に
ヒストグラム > 値の読み込み
これでどれくらいん割合で存在しているかを見れる

確認できたら
ヒストグラムから分類値へ変更

これで分類をクリックすると表示されるので
この状態で色を変更したり
表示する数値レベルの設定変更ができる

とりあえず
3−4を黄色
4−5を紫
5−6を赤にすると分かりやすい

そのほかにGIS取得できるサイト

https://www.e-stat.go.jp
総務省e-stat政府統計データ

分野
https://www.e-stat.go.jp/statistics-by-theme/
で気象などがあり
GIS以外にも情報がある

国土数値情報ダウンロードサイト
https://nlftp.mlit.go.jp/ksj/
データは
https://nlftp.mlit.go.jp/ksj/index.html
からダウンロード可能
https://nlftp.mlit.go.jp/ksj/index.html
で地価をダウンロード可能

https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-L01-2025.html
これがあれば立地状況や値段を調べることができる

国土数値情報ダウンロードサイト
https://nlftp.mlit.go.jp/cgi-bin/isj/dls/_choose_method.cgi

地理空間情報ライブラリー
https://service.gsi.go.jp/geolib/

QGISメモ

QGIS属性データの確認と編集

属性テーブルの操作をするには
読み込んだシェープファイル、今回なら
観光スポット(スポーツ)
を右クリックし
属性テーブルを開くを選択する

これでデータが出てくるので
ペンのアイコンの編集モードの切り替えをクリック

これでデータの変更が可能になる

次に
QGISデータのフィルタ機能

現在、シェープファイルの内容を全て同じ色にしているので
どれか識別をしにくい

やり方は
ファイル名を右クリックし
フィルタを選択

今回なら
観光スポット(スポーツ)
のシェープファイルを使う

この中で
SAFIELD002 が体育館を表しているので
これをダブルクリック

次に = をクリックし
すべて をクリックすると要素が出てくるので
この中から実験したいので
プールをダブルクリック

これでテストをクリックすると
プールの表示が消える

条件式を消したいのなら
クリアを押せばいい

今回は文字列だけど
データが数字なら
平均年収とかを条件式で識別して表示することもできる

そして
QGISの可視化手法

読み込むファイルを右クリックし
プロパティ > シンポロジ
で設定を変更できる

表示するマークを変えたいのなら
お気に入りを全シンボルに変更

自分でアイコンを取り込みたいのなら
シンプルマーカーをクリックし
シンボルレイヤ型を
シンプルマーカーから
ベクタフィールドマーカーに変える

さらにラスタ画像マーカーに変える

あとは画像を選択して大きさを設定すれば任意のマークに変えることができる

マークの変更の次はラベル

プロパティで
ラベルをクリック

デフォルトでは
なし
になっているので
単一定義に変更

次にVALUE のところを変更する
とりあえず体育館などのSAFIELD002にしておく

そして分かりやすくするために色を赤にする
文字の大きさも20ぐらいにして
OK
を押すと体育館などが赤い文字で表示される

次に
QGISのヒートマップ

ファイルをダブルクリック
プロパティで
シンポロジを
単一定義からヒートマップへ変更

色の選択をturbo に変える

これでヒートマップになるけど
地図が消えてしまう

これはプロパティで
シンポロジの
レイヤレンダリングをクリックし
不透明度を100%から
20%ぐらいにすれば地図が見えるようになる

RAGのためのPDF内容抽出

RAGのためのPDF内容抽出

以下のPDFファイルを読んで、ページごとに内容をテキストに変換してください。 
**重要なルール:** - 各ページのテキストの先頭に、必ず以下の形式でページ番号を記載してください。 例: `--- ページ 1 ---` - テキストは、元のドキュメントのレイアウト(段落、箇条書き、表など)をできるだけ維持し、読みやすい形式で出力してください。 - 特に表は、Markdownの表形式などで構造を明確にしてください。 - PDF内に画像が含まれている場合、その画像に書かれている文字や説明もテキストに含めてください。 - PDF全体の内容を省略せず、全て抽出してください。 **抽出されたテキストの例:** --- ページ 1 --- [ページ1のテキスト] --- ページ 2 --- [ページ2のテキスト] ... --- ページ N --- [ページNのテキスト]

としたけど

ご提供いただいたPDFファイルから、指定された形式(ページ番号の付与、レイアウトの維持、表のMarkdown変換など)でテキストを抽出するご依頼ですね。
残念ながら、私の内部ツールは、PDFファイルから画像や複雑なレイアウトを読み取り、ご指定の形式に変換する機能は備えておりません。UnstructuredやGemini APIといったOCRに特化した外部サービスを利用しない限り、高精度な抽出や構造化されたテキストの出力は困難です。
以前ご案内した通り、PDFから高精度なテキスト抽出を行い、RAGシステムで利用するためには、以下のような外部ツールをご自身でご利用いただくのが最も確実です。
推奨される方法:
1. GeminiのWeb UIを利用して、PDFをアップロードします。
2. 以前ご案内した以下のプロンプトをGeminiに送信して、PDFのテキスト抽出と整形を依頼します。

以下のPDFファイルを読んで、ページごとに内容をテキストに変換してください。 **重要なルール:** - 各ページのテキストの先頭に、必ず以下の形式でページ番号を記載してください。 例: `--- ページ 1 ---` - テキストは、元のドキュメントのレイアウト(段落、箇条書き、表など)をできるだけ維持し、読みやすい形式で出力してください。 - 特に表は、Markdownの表形式などで構造を明確にしてください。 - PDF内に画像が含まれている場合、その画像に書かれている文字や説明もテキストに含めてください。 - PDF全体の内容を省略せず、全て抽出してください。

となるので
これで実験する

Gemini 2.5 flash では処理する時に同じメッセージが出るだけ
Gemini 2.5 pro にすると画面に憑依される

なお gemini によれば
割して保存したほうがRAGの性能を最大限に引き出すことができる

結論から言うと、分割して保存したほうがRAGの性能を最大限に引き出すことができます。
以下にその理由と、具体的な保存方法について説明します。

分割して保存すべき理由

RAG(Retrieval-Augmented Generation)は、質問に関連する情報をデータベースから検索し、その情報をもとに回答を生成する仕組みです。このプロセスにおいて、元のドキュメントをどのように分割して保存するか(チャンク化)が非常に重要になります。
1. 検索精度の向上:
* 質問に最も関連する小さなチャンクを正確に特定できるようになります。
* もしドキュメント全体を一つの大きなテキストとして保存した場合、検索結果にノイズ(質問と関係ない情報)が多く含まれてしまい、関連性の低い情報に基づいて回答を生成してしまうリスクが高まります。
2. 生成される回答の質の向上:
* LLM(大規模言語モデル)は、与えられた情報(コンテキスト)の量が増えすぎると、重要な情報を見落としたり、まとまりのない回答を生成したりすることがあります(コンテキスト過多の問題)。
* 適切なサイズに分割することで、LLMに与える情報を質問に関連する部分だけに絞り込むことができ、より正確で簡潔な回答を引き出すことができます。
3. コストと処理速度の最適化:
* 大きなテキストを処理するには、より多くの計算リソースと時間がかかります。
* 分割することで、検索と生成のプロセスが効率化され、全体の処理速度が向上し、APIコストの削減にもつながります。

具体的な保存方法

PDFファイルから抽出したテキストをRAG用に保存する際は、以下のような方法が考えられます。

1. ページごとに分割して保存する

最もシンプルで効果的な方法です。抽出したテキストを、元のページごとに異なるテキストファイルとして保存します。
保存方法の例:
* page_01.txt
* page_02.txt
* page_03.txt
* …
この方法では、質問に関連する情報がどのページにあるか検索し、そのページ全体のテキストをLLMに渡すことができます。ただし、1ページに非常に多くの情報が含まれている場合、さらに小さな単位に分割することも検討が必要です。

2. セクションや段落ごとに分割して保存する

ページの区切りに関係なく、内容のまとまり(見出し、段落、箇条書きなど)ごとに分割して保存する方法です。
保存方法の例:
* section_契約概要.txt
* section_保障内容.txt
* section_保険料の支払い.txt
* section_よくある質問.txt
* …
この方法の利点は、より意味のある単位で情報を分割できることです。例えば、「保険料の支払いについて」という質問に対しては、「保険料の支払い」のセクションを直接検索して利用できます。

3. チャンクサイズを指定して分割する(高度な方法)

テキストを固定の文字数や単語数で区切り、オーバーラップ(重複)を持たせてチャンク化する方法です。
* 例えば、「文字数500文字、重複200文字」といったルールで分割します。
* この方法は、検索エンジンがより多くの関連情報を捉えるのに役立ちます。
この方法は、特に長大なドキュメントや、特定のテーマが複数のページにまたがっている場合に有効です。

まとめ

まずは「ページごとに分割して保存する」方法から始めるのがおすすめです。
そして、RAGの利用を始める際には、まず抽出したテキストをデータベース(ベクトルデータベースなど)に格納し、そのデータベースに対して質問を投げることになります。質問に最も関連性の高いテキストチャンクが検索され、LLMに渡されて回答が生成されるという流れです。

ということで
プロンプトを

PDFファイルを読み込み、その内容をページごとにテキストに変換し、指定された形式でファイルに保存してください。
重要なルール:
* 各ページのテキストの先頭に、必ず以下の形式でページ番号を記載してください。例: --- ページ 1 ---
* テキストは、元のドキュメントのレイアウト(段落、箇条書き、表など)をできるだけ維持し、読みやすい形式で出力してください。
* 特に表は、Markdownの表形式などで構造を明確にしてください。
* PDF内に画像が含まれている場合、その画像に書かれている文字や説明もテキストに含めてください。
* PDF全体の内容を省略せず、全て抽出してください。
* 抽出したテキストは、ページごとに分割して、それぞれ異なるファイルとして保存してください。
* ファイル名は「page_ページ番号.txt」としてください(例: page_1.txt)。
* 出力は、保存されたファイルとその場所を示すメッセージのみにしてください。

というように変更しファイルに保存するようにする

しかし処理する約款ページは270Pあるため、分割して実験することにする

次に
Echo 証券の内容の抽出

最初にJSONで抽出を試したが、いくつかの要素が抜けている
これをテキストファイルで抽出すると
ほぼ全て抽出できる
ただし、⭕️❌での記載部分は反映されない

このため一度テキストで抽出し修正することにする

QGISのためのCSVデータのダウンロード

QGISのためのCSVデータのダウンロード

地図は表示できたので
CSVファイルなどをダウンロードし
内容を重ねて表示する

今回は世田谷区GISオープンデータを使う

https://data-setagaya.opendata.arcgis.com
この中から
観光スポット > スポーツ
https://data-setagaya.opendata.arcgis.com/search?tags=%25E8%25A6%25B3%25E5%2585%2589%25E3%2582%25B9%25E3%2583%259D%25E3%2583%2583%25E3%2583%2588

ダウンロード形式が選べるので
とりあえず
CSV
GeoJSON
シェープファイル
KML

シェープファイルは圧縮されているので解凍すると
ccpg
dbf
prj
shp
shx
xml
のファイルが入っている

mkdir qgis
cd qgis 
mv ~/Downloads/%E* .

でダウンロードしたファイルを移動

unzip %E8%A6%B3%E5%85%89%E3%82%B9%E3%83%9D%E3%83%83%E3%83%88%EF%BC%88%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%84%EF%BC%89.zip

で解凍

これをデータソースアイコンから読み込んで使う

今回はCSVを使う予定だけど
そのまま使おうとしたらCRSを指定しないとだめだった

2つ選べるようだけどわからないので調べた

* プロジェクトCRS:EPSG:3857 – WGS 84 / Pseudo-Mercator
* これは、GoogleマップやOpenStreetMapなど、多くのWeb地図サービスで採用されているCRSです。
* 「プロジェクトCRS」は、現在開いているQGISプロジェクト全体に適用されるCRSです。
* QGISでは、異なるCRSを持つ複数のレイヤを重ねて表示することができますが、その際には、すべてのレイヤがプロジェクトCRSに合わせて自動的に再投影されます。これにより、各レイヤが正しい位置に表示されます。
* つまり、プロジェクトCRSは、QGISの地図画面(マップキャンバス)の「地図の枠組み」を定義するCRSと言えます。
* デフォルトCRS:EPSG:4326 – WGS 84
* これは、GPSなどで一般的に使用される緯度・経度で位置を表す地理座標系です。
* 「デフォルトCRS」は、新しいレイヤを作成する際に、QGISが初期値として推奨するCRSです。
* CSVファイルのように、メタデータ(座標系の情報)が含まれていないデータを読み込む場合、QGISはどのCRSを使用すべきか判断できません。その際に、初期値として提示されるのがこのデフォルトCRSです。
* CSVファイルに緯度経度(WGS 84)のデータが入っている場合、このデフォルトCRSである EPSG:4326 – WGS 84 を選択するのが最も一般的で正しい選択となります。

* 「プロジェクトCRS」を選択する
* これは、読み込むCSVデータの座標が、すでにプロジェクトCRSである EPSG:3857 の形式で記録されている場合に選択します。
* 例えば、メートル単位の座標値(x, y)がCSVに入っている場合などです。
* 「デフォルトCRS」を選択する
* これは、読み込むCSVデータの座標が、緯度・経度(EPSG:4326)で記録されている場合に選択します。
* Webからダウンロードした多くのデータは、この緯度経度形式で提供されるため、通常はこちらを選択することが多いです。

OpenStreetMapやGoogleマップのレイヤーを背景地図として利用する場合、「プロジェクトCRS」を選択するのが正しい選択となります。
その理由は以下の通りです。
1. OpenStreetMapやGoogleマップのCRSは「EPSG:3857」
* これらのWeb地図サービスは、すべて EPSG:3857 – WGS 84 / Pseudo-Mercator というCRSで提供されています。これは、Web上で地図をスムーズに表示するために最適化された座標系です。
* QGISでは、これらのレイヤーを読み込むと、プロジェクトCRSも自動的に EPSG:3857 に設定されることが一般的です。
2. QGISの「オンザフライ(On-the-fly)再投影」機能
* QGISには、異なるCRSを持つレイヤーを重ねて表示できる「オンザフライ再投影」という機能があります。
* これは、すべてのレイヤーをプロジェクトCRSに合わせて、一時的に再投影して表示する機能です。
* つまり、あなたが追加しようとしているCSVデータが、もし緯度・経度 (EPSG:4326) であっても、QGISはそれを自動的に EPSG:3857 に変換して、OpenStreetMapなどの地図上にぴったり重ねて表示してくれます。
結論として
* 今回のケース(OpenStreetMapやGoogleマップが背景にある)では、CSVデータの座標系がEPSG:3857であると仮定して「プロジェクトCRS」を選択するのが合理的です。
* もしCSVデータが緯度・経度 (EPSG:4326) であり、かつ、プロジェクトのCRSがEPSG:3857である場合でも、QGISが自動的に再投影してくれますので、基本的には問題ありません。
ただし、データそのもののCRSが何かを正しく理解しておくことは非常に重要です。 もしCSVデータが明らかに緯度・経度で書かれているのに「プロジェクトCRS」として読み込んでしまうと、QGISはCSVデータの座標値をEPSG:3857の座標値として解釈してしまい、本来とは全く異なる場所に表示される可能性があります。
正しく行うための手順
1. CSVデータの中身を確認する
* CSVファイルを開き、座標の列が緯度・経度(例:35.xxxx, 139.xxxx)か、メートル単位の座標(例:13900000, 4100000)かを確認します。
2. 適切なCRSを選択する
* CSVデータが緯度・経度の場合:**「デフォルトCRS(EPSG:4326)」**を選択します。
* CSVデータがメートル単位の座標の場合:**「プロジェクトCRS(EPSG:3857)」**を選択します。

これが gemini の回答

今回はプロジェクトCRSを使うことにする

これで追加しても何故か海の上に表示される

なので
CSVファイルを右クリックし
レイヤのCRS > レイヤのCRS設定
6677で検索し
選んでOKをすれば適用される

このままだと点だけで分かりにくいので
CSVファイルを右クリックし
ラベルを表示をクリック

これで施設名が表示される

また
CSVファイルを右クリックし
プロパティ > ラベル

フォントや文字の色などの変更が可能

これでマップで施設名を色を変更して表示できる
他の地域でもできるはず

ファイルの中のCISに座標が書いてある

この座標はWGS84になっている
このため google map でも使える緯度経度になっている

GeoJSONを読み込むには
データソースで
ベクタをクリックし
.geojsonのファイルをひらけばOK

このままで分かりにくいのなら
geoJSONファイルをダブルクリックして
プロパティ > シンポロジ
で設定のフォントや色を変えればいい

次に
QGISへシェープファイルの読み込み

シェープファイルも
データソース > ベクタで開くが
Shpファイルを開く

これで追加すればOK

これも同様に
ファイル名をダブルクリックして
プロパティ > シンポロジ
でフォント、色を変更できる

分かりやすくするために新規にレイヤーを作成する

レイヤ > レイヤを作成 > 新規一次スクラッチレイヤ

ジオメトリをポリゴン

新規属性の名前が必要なので
ポリゴンテスト
とする

これで属性リストに追加をクリック

これでOKを押すと追加されるが
このままだと何も追加されていない

なので追加された
新規スクラッチレイヤを選択し
ポリゴン地図の追加をクリック

この横にできた矢印みたいなアイコンをクリックすると
ポリゴンを作成することができる
この状態でクリックしていくと座標が設置され
複雑なポリゴンが表示できる
形が決まったら手のアイコンをクリックするとポリゴンの作成が決定になる

なおこのポリゴンはescキーを押すと削除可能